import {ChangeDetectorRef, Component, HostListener, OnDestroy, OnInit, ViewChild, ViewRef} from '@angular/core'; import {ActivatedRoute, Params, Router} from '@angular/router'; import {DomSanitizer, Meta, Title} from '@angular/platform-browser'; import {EnvProperties} from '../openaireLibrary/utils/properties/env-properties'; import {PiwikService} from '../openaireLibrary/utils/piwik/piwik.service'; import {Dates, StringUtils} from '../openaireLibrary/utils/string-utils.class'; import {ErrorCodes} from '../openaireLibrary/utils/properties/errorCodes'; import {ErrorMessagesComponent} from '../openaireLibrary/utils/errorMessages.component'; import {HelperService} from "../openaireLibrary/utils/helper/helper.service"; import {SEOService} from "../openaireLibrary/sharedComponents/SEO/SEO.service"; import {StakeholderService} from "../openaireLibrary/monitor/services/stakeholder.service"; import { Category, Indicator, IndicatorPath, IndicatorSize, Section, Stakeholder, SubCategory, Topic, Visibility } from "../openaireLibrary/monitor/entities/stakeholder"; import {StatisticsService} from "../openaireLibrary/monitor-admin/utils/services/statistics.service"; import {IndicatorUtils} from "../openaireLibrary/monitor-admin/utils/indicator-utils"; import {LayoutService} from "../openaireLibrary/dashboard/sharedComponents/sidebar/layout.service"; import {UntypedFormBuilder, UntypedFormControl} from "@angular/forms"; import {Subscriber, Subscription} from "rxjs"; import {Session, User} from "../openaireLibrary/login/utils/helper.class"; import {UserManagementService} from "../openaireLibrary/services/user-management.service"; import {RangeFilter} from "../openaireLibrary/utils/rangeFilter/rangeFilterHelperClasses.class"; import {Filter, Value} from "../openaireLibrary/searchPages/searchUtils/searchHelperClasses.class"; import {RouterHelper} from "../openaireLibrary/utils/routerHelper.class"; import {properties} from "../../environments/environment"; import {IndexInfoService} from "../openaireLibrary/utils/indexInfo.service"; import {ConfigurationService} from "../openaireLibrary/utils/configuration/configuration.service"; import {ClickEvent} from '../openaireLibrary/utils/click/click-outside-or-esc.directive'; import {RangeFilterComponent} from "../openaireLibrary/utils/rangeFilter/rangeFilter.component"; @Component({ selector: 'monitor', templateUrl: 'monitor.component.html', styleUrls: ['monitor.component.less'] }) export class MonitorComponent implements OnInit, OnDestroy { public user: User; public subscriptions: any[] = []; title; description; public pageContents = null; public divContents = null; public status: number; public loading: boolean = true; public view: Visibility; public indicatorUtils: IndicatorUtils = new IndicatorUtils(); public activeTopic: Topic = null; public activeCategory: Category = null; public activeSubCategory: SubCategory = null; public errorCodes: ErrorCodes; public stakeholder: Stakeholder; public numberResults: Map = new Map(); public chartsActiveType: Map = new Map(); private errorMessages: ErrorMessagesComponent; properties: EnvProperties = properties; public routerHelper: RouterHelper = new RouterHelper(); filters: Filter[] = []; queryParams = {}; public currentYear = new Date().getFullYear(); periodFilter: RangeFilter = { title: "Time range", filterId: "year", originalFilterIdFrom: null, originalFilterIdTo: null, selectedFromValue: null, selectedToValue: null, selectedFromAndToValues: "" }; @ViewChild('rangeFilter') rangeFilter: RangeFilterComponent; privateStakeholder = false; public keyword: UntypedFormControl; public statsUpdateDate: Date; public isFullscreen: boolean = false; public isMobile: boolean = false; @HostListener('fullscreenchange', ['$event']) @HostListener('webkitfullscreenchange', ['$event']) @HostListener('mozfullscreenchange', ['$event']) @HostListener('MSFullscreenChange', ['$event']) screenChange(event) { this.isFullscreen = !this.isFullscreen; } constructor( private route: ActivatedRoute, private _router: Router, private _meta: Meta, private _title: Title, private _piwikService: PiwikService, private helper: HelperService, private stakeholderService: StakeholderService, private userManagementService: UserManagementService, private statisticsService: StatisticsService, private layoutService: LayoutService, private seoService: SEOService, private cdr: ChangeDetectorRef, private indexInfoService: IndexInfoService, private sanitizer: DomSanitizer, private _fb: UntypedFormBuilder, private router: Router, private configurationService: ConfigurationService) { this.errorCodes = new ErrorCodes(); this.errorMessages = new ErrorMessagesComponent(); this.status = this.errorCodes.LOADING; } public ngOnInit() { if (typeof document !== 'undefined') { this.subscriptions.push(this.indexInfoService.getStatsLastDate(this.properties).subscribe(lastIndexUpdate => { if (lastIndexUpdate) { this.statsUpdateDate = new Date(lastIndexUpdate); } })); } this.keyword = this._fb.control(''); let subscription: Subscription; this.layoutService.isMobile.subscribe(isMobile => { this.isMobile = isMobile; this.cdr.detectChanges(); }); this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { this.user = user; this.subscriptions.push(this.route.params.subscribe(params => { this.loading = true; this.activeTopic = null; this.activeCategory = null; this.activeSubCategory = null; if (subscription) { subscription.unsubscribe(); } var url = properties.domain + properties.baseLink + this._router.url; if (!this.stakeholder || this.stakeholder.alias !== params['stakeholder']) { this.status = this.errorCodes.LOADING; this.numberResults = new Map(); this.chartsActiveType = new Map(); subscription = this.stakeholderService.getStakeholderAsObservable().subscribe(stakeholder => { if (stakeholder) { this.stakeholder = stakeholder; // add fl0 filter only for EC if (this.stakeholder.index_id == "ec__________::EC") { this.filters.push({ title: "Funding Stream", filterId: "relfundinglevel0_id", originalFilterId: "relfundinglevel0_id", countSelectedValues: 0, values: [{name: "EC|FP7", id: "ec__________::EC::FP7", selected: false, number: 0}, { name: "EC|H2020", id: "ec__________::EC::H2020", selected: false, number: 0 }] , filterOperator: "or", valueIsExact: true, filterType: "radio", radioValue: "" }); } if (this.stakeholder.type == "funder") { // this.filters.push({title: "Co-funded research outcomes",filterId: "co-funded",originalFilterId: "co-funded", countSelectedValues: 0, // values:[{fname: "true", id: "co-funded", selected: false, number: 0}, {name: "false", id: "no-co-funded", selected: false, number: 0}] // ,filterOperator: "or", valueIsExact: true, filterType: "radio", radioValue:""}); this.filters.push({ title: "Co-funded", filterId: "co-funded", originalFilterId: "co-funded", countSelectedValues: 0, values: [{name: "Co-funded research output", id: "co-funded-results", selected: false, number: 0}] , filterOperator: "or", valueIsExact: true, filterType: "checkbox", radioValue: "" }); } this.subscriptions.push(this.route.queryParams.subscribe( queryParams => { this.handleQueryParams(queryParams, params); this.seoService.createLinkForCanonicalURL(url, false); this._meta.updateTag({content: url}, "property='og:url'"); this.description = "Monitor Dashboard | " + this.stakeholder.name; this.title = "Monitor Dashboard | " + this.stakeholder.name; this._meta.updateTag({content: this.description}, "name='description'"); this._meta.updateTag({content: this.description}, "property='og:description'"); this._meta.updateTag({content: this.title}, "property='og:title'"); this._title.setTitle(this.title); this.subscriptions.push(this._piwikService.trackView(this.properties, this.title).subscribe()); if (this.hasPermission((this.view && this.isManager(this.stakeholder))?this.view:this.stakeholder.visibility)) { //this.getDivContents(); // this.getPageContents(); this.status = this.errorCodes.DONE; this.setView(params); } else { this.privateStakeholder = true; // this.navigateToError(); if (subscription) { subscription.unsubscribe(); } } })); } else { this.navigateToError(); if (subscription) { subscription.unsubscribe(); } } }); this.subscriptions.push(subscription); } else { this.subscriptions.push(this._piwikService.trackView(this.properties, this.title).subscribe()); this.subscriptions.push(this.route.queryParams.subscribe( queryParams => { this.handleQueryParams(queryParams, params); })); } })); })); } get monitorLink() { return "https://" + (this.properties.environment == 'beta' ? 'beta.' : '') + 'monitor.openaire.eu'; } private handleQueryParams(queryParams, params) { this.queryParams = Object.assign({}, queryParams); this.initializeFilters(); this.setView(params); if(!this.user && (this.filters.filter(filter => this.queryParams[filter.filterId]).length > 0 || this.queryParams['year'])) { if(queryParams['view']) { this.router.navigate([], {queryParams: {view: queryParams['view']}}); } else { this.router.navigate([], {queryParams: {}}); } } this.view = queryParams['view']; } private initializeFilters() { this.periodFilter.selectedFromValue = (this.queryParams['year'] && this.queryParams['year'].indexOf("range") == 0) ? this.queryParams['year'].split("range")[1].split(":")[0] : ""; this.periodFilter.selectedToValue = (this.queryParams['year'] && this.queryParams['year'].indexOf("range") == 0) ? this.queryParams['year'].split("range")[1].split(":")[1] : ""; this.validateYearRange(false); for (let filter of this.filters) { if (this.queryParams[filter.filterId]) { for (let value of filter.values) { if (value.id == StringUtils.URIDecode(StringUtils.unquote(this.queryParams[filter.filterId]))) { value.selected = true; filter.countSelectedValues = 1; break; } } } else { this.clearFilter(filter); } } } private validateYearRange(navigateTo: boolean = false) { let validYears = true; if (this.periodFilter.selectedToValue && (this.periodFilter.selectedToValue.length == 0 || !Dates.isValidYear(this.periodFilter.selectedToValue, Dates.currentYear - 20, Dates.currentYear))) { this.periodFilter.selectedToValue = Dates.currentYear + ""; validYears = false; } if (this.periodFilter.selectedFromValue && (this.periodFilter.selectedFromValue.length == 0 || !Dates.isValidYear(this.periodFilter.selectedFromValue, Dates.currentYear - 20, Dates.currentYear))) { this.periodFilter.selectedFromValue = Dates.currentYear - 20 + ""; validYears = false; } if (this.periodFilter.selectedFromValue && this.periodFilter.selectedFromValue.length && this.periodFilter.selectedToValue && this.periodFilter.selectedToValue.length > 0 && parseInt(this.periodFilter.selectedFromValue, 10) > parseInt(this.periodFilter.selectedToValue, 10)) { this.periodFilter.selectedFromValue = this.periodFilter.selectedToValue; validYears = false; } if (!validYears || navigateTo) { if (this.periodFilter.selectedFromValue || this.periodFilter.selectedToValue) { this.queryParams["year"] = 'range' + (this.periodFilter.selectedFromValue ? this.periodFilter.selectedFromValue : '') + ":" + (this.periodFilter.selectedToValue ? this.periodFilter.selectedToValue : ""); } else { delete this.queryParams["year"]; } // this.location.go(location.pathname, this.routerHelper.createQueryParamsString( Object.keys(this.queryParams), Object.values(this.queryParams))); this.router.navigate([], {queryParams: this.queryParams}); this.setIndicators(); } } clearAll() { for (let filter of this.filters) { this.clearFilter(filter); } this.periodFilter.selectedFromValue = ""; this.periodFilter.selectedToValue = ""; this.validateYearRange(true) } clearFilter(filter: Filter) { filter.countSelectedValues = 0; filter.radioValue = ""; for (let value of filter.values) { if (value.selected) { value.selected = false; } } if (this.queryParams[filter.filterId]) { delete this.queryParams[filter.filterId]; } } countSelectedFilters(): number { let count = 0; if (this.periodFilter.selectedFromAndToValues.length > 0) { count += 1; } for (let filter of this.filters) { count += filter.countSelectedValues; } return count; } public get open() { return this.layoutService.open; } private getPageContents() { this.subscriptions.push(this.helper.getPageHelpContents(this.properties, this.properties.adminToolsCommunity, this._router.url).subscribe(contents => { this.pageContents = contents; })); } private getDivContents() { this.subscriptions.push(this.helper.getDivHelpContents(this.properties, this.properties.adminToolsCommunity, this._router.url).subscribe(contents => { this.divContents = contents; })); } private setView(params: Params) { this.loading = false; if (params['topic']) { this.activeTopic = this.stakeholder.topics.find(topic => topic.alias === decodeURIComponent(params['topic']) && this.hasPermission(topic.visibility)); if (this.activeTopic) { if (params['category']) { this.activeCategory = this.activeTopic.categories.find(category => (category.alias === params['category']) && this.hasPermission(category.visibility)); if (!this.activeCategory) { this.navigateToError(); return; } } else { this.activeCategory = this.activeTopic.categories.find(category => this.hasPermission(category.visibility)); if (this.activeCategory) { this.activeSubCategory = this.activeCategory.subCategories.find(subCategory => this.hasPermission(subCategory.visibility)); if (this.activeSubCategory) { this.setIndicators(); } } return; } if (this.activeCategory) { if (params['subCategory']) { this.activeSubCategory = this.activeCategory.subCategories.find(subCategory => (subCategory.alias === params['subCategory'] && this.hasPermission(subCategory.visibility))); if (!this.activeSubCategory) { this.navigateToError(); return; } } else { this.activeSubCategory = this.activeCategory.subCategories.find(subCategory => this.hasPermission(subCategory.visibility)); } if (this.activeSubCategory) { this.setIndicators(); } else { this.navigateToError(); } return; } else { this.activeSubCategory = null; } } else { this.navigateToError(); return; } } else { this.activeTopic = this.stakeholder.topics.find(topic => this.hasPermission(topic.visibility)); if (this.activeTopic) { this.activeCategory = this.activeTopic.categories.find(category => this.hasPermission(category.visibility)); if (this.activeCategory) { this.activeSubCategory = this.activeCategory.subCategories.find(subCategory => this.hasPermission(subCategory.visibility)); if (this.activeSubCategory) { this.setIndicators(); } } } } } filter() { this.validateYearRange(true); } filterChanged($event, navigate: boolean = true) { let selected = ""; for (let value of $event.value.values) { if (value.selected) { selected = value.id; break; } } if (selected) { this.queryParams[$event.value.filterId] = StringUtils.quote(StringUtils.URIEncode(selected)); } else { delete this.queryParams[$event.value.filterId]; } if (navigate) { this.router.navigate([], {queryParams: this.queryParams}); this.setIndicators(); } } private getfl0() { if (this.queryParams["relfundinglevel0_id"] && this.filters.length > 0) { let value = StringUtils.URIDecode(StringUtils.unquote(this.queryParams["relfundinglevel0_id"])); return value.split("::")[value.split("::").length - 1]; } return null; } private getCoFunded() { if (this.queryParams["co-funded"] && this.filters.length > 0) { return this.queryParams["co-funded"] && StringUtils.URIDecode(StringUtils.unquote(this.queryParams["co-funded"])) == "co-funded-results"; } return false; } clearPeriodFilter() { if (this.periodFilter.selectedFromValue || this.periodFilter.selectedToValue) { this.periodFilter.selectedFromValue = ""; this.periodFilter.selectedToValue = ""; if(this.rangeFilter) { this.rangeFilter.clearFilter(); } this.filter(); } } clearFilterValue(filter: Filter, value: Value) { value.selected = false; filter.radioValue = ''; filter.countSelectedValues = filter.countSelectedValues - 1; this.filterChanged({ value:filter }); } private setIndicators() { this.periodFilter.selectedFromAndToValues = (this.periodFilter.selectedFromValue || this.periodFilter.selectedToValue ? ((this.periodFilter.selectedFromValue && !this.periodFilter.selectedToValue ? "From " : "") + (!this.periodFilter.selectedFromValue && this.periodFilter.selectedToValue ? "Until " : "") + (this.periodFilter.selectedFromValue ? this.periodFilter.selectedFromValue : "") + (this.periodFilter.selectedFromValue && this.periodFilter.selectedToValue ? " - " : "") + (this.periodFilter.selectedToValue ? this.periodFilter.selectedToValue : "")) : ""); //clear numbers when filters change this.numberResults.clear(); let urls: Map = new Map(); this.activeSubCategory.numbers.forEach((section, i) => { section.indicators.forEach((number, j) => { if (this.hasPermission(number.visibility)) { let url = this.indicatorUtils.getFullUrlWithFilters(this.stakeholder, number.indicatorPaths[0], this.getfl0(), this.periodFilter.selectedFromValue, this.periodFilter.selectedToValue, this.getCoFunded()); const pair = JSON.stringify([number.indicatorPaths[0].source, url]); const indexes = urls.get(pair) ? urls.get(pair) : []; indexes.push([i, j]); urls.set(pair, indexes); } }); }); urls.forEach((indexes, pair) => { pair = JSON.parse(pair); let activeSubcategory = this.activeSubCategory._id; this.subscriptions.push(this.statisticsService.getNumbers(this.indicatorUtils.getSourceType(pair[0]), pair[1]).subscribe(response => { if(activeSubcategory === this.activeSubCategory._id) { indexes.forEach(([i, j]) => { if( this.activeSubCategory?.numbers[i]?.indicators[j]) { let result = JSON.parse(JSON.stringify(response)); this.activeSubCategory.numbers[i].indicators[j].indicatorPaths[0].jsonPath.forEach(jsonPath => { if (result) { result = result[jsonPath]; } }); if (typeof result === 'string' || typeof result === 'number') { result = Number(result); if (result === Number.NaN) { result = 0; } } else { result = 0; } this.numberResults.set(i + '-' + j, result); } }); } })); }); this.activeSubCategory.charts.forEach((section, i) => { section.indicators.forEach((indicator, j) => { if (indicator.indicatorPaths.length > 0) { indicator.indicatorPaths[0].safeResourceUrl = this.getUrlByStakeHolder(indicator.indicatorPaths[0]); this.chartsActiveType.set(i + '-' + j, indicator.indicatorPaths[0]); } }); }); if (this.cdr && !(this.cdr as ViewRef).destroyed) { this.cdr.detectChanges(); } } public getUrlByStakeHolder(indicatorPath: IndicatorPath) { return this.sanitizer.bypassSecurityTrustResourceUrl( this.indicatorUtils.getChartUrl(indicatorPath.source, this.indicatorUtils.getFullUrlWithFilters(this.stakeholder, indicatorPath, this.getfl0(), this.periodFilter.selectedFromValue, this.periodFilter.selectedToValue, this.getCoFunded()))); } public setActiveChart(i: number, j: number, type: string) { let activeChart = this.activeSubCategory.charts[i].indicators[j].indicatorPaths.filter(indicatorPath => indicatorPath.type === type)[0]; activeChart.safeResourceUrl = this.getUrlByStakeHolder(activeChart); this.chartsActiveType.set(i + '-' + j, activeChart); } private navigateToError() { this._router.navigate([this.properties.errorLink], {queryParams: {'page': this._router.url}}); } public quote(param: string): string { return StringUtils.quote(param); } public ngOnDestroy() { this.subscriptions.forEach(subscription => { if (subscription instanceof Subscriber) { subscription.unsubscribe(); } }); } public isMember(stakeholder: Stakeholder) { return this.user && (Session.isPortalAdministrator(this.user) || Session.isCurator(stakeholder.type, this.user) || Session.isManager(stakeholder.type, stakeholder.alias, this.user) || Session.isMember(stakeholder.type, stakeholder.alias, this.user)); } public isManager(stakeholder: Stakeholder) { return this.user && (Session.isPortalAdministrator(this.user) || Session.isCurator(stakeholder.type, this.user) || Session.isManager(stakeholder.type, stakeholder.alias, this.user)); } public hasPermission(visibility: Visibility): boolean { if(visibility === 'PUBLIC') { return true; } else if(visibility === 'RESTRICTED') { return (!this.view || this.view === 'RESTRICTED') && this.isMember(this.stakeholder); } else { return !this.view && this.isManager(this.stakeholder); } } public countSubCategoriesToShow(category: Category): number { return category.subCategories.filter(subCategory => this.hasPermission(subCategory.visibility)).length; } public countSectionsWithIndicatorsToShow(sections: Section[]):number { return sections.map(section => this.countIndicatorsToShow(section.indicators)).reduce((sum, current) => sum + current, 0); } public countIndicatorsToShow(indicators: Indicator[]): number { return indicators.filter(indicator => this.hasPermission(indicator.visibility)).length; } public get feedback() { return "mailto:" + this.properties.feedbackmail + "?subject=%5BOpenAIRE%20Monitor%5D%20" + (this.stakeholder ? this.stakeholder.name : "") + "%20dashboard%20feedback" } public getNumberClassBySize(size: IndicatorSize) { if (size === 'small') { return 'uk-width-medium@m uk-width-1-1'; } else if (size === 'medium') { return 'uk-width-1-4@l uk-width-1-2@m uk-width-1-1'; } else { return 'uk-width-1-2@l uk-width-1-1@m uk-width-1-1'; } } public getChartClassBySize(size: IndicatorSize) { if (size === 'small') { return 'uk-width-1-3@xl uk-width-1-2@m uk-width-1-1'; } else if (size === 'medium') { return 'uk-width-1-2@l uk-width-1-1'; } else { return 'uk-width-1-1'; } } public printReport() { window.print(); } logIn() { this.userManagementService.login(); } toggleDescriptionOverlay(event, indicator: Indicator) { event.stopPropagation(); indicator.descriptionOverlay = !indicator.descriptionOverlay; } closeDescriptionOverlay(event: ClickEvent, indicator: Indicator) { if(event.clicked && indicator.descriptionOverlay) { indicator.descriptionOverlay = false; } } }