diff --git a/monitor/indicators/indicator-themes.component.ts b/monitor/indicators/indicator-themes.component.ts index b2c4a2f5..01ed1aed 100644 --- a/monitor/indicators/indicator-themes.component.ts +++ b/monitor/indicators/indicator-themes.component.ts @@ -30,10 +30,10 @@ import {StakeholderBaseComponent} from "../../monitor-admin/utils/stakeholder-ba This is the current set of indicator themes we cover. We’ll keep enriching it as new requests and data are coming into the OpenAIRE Graph. We are at your disposal, should you have any recommendations!

- Check out the indicator pages (for funders, - research institutions and - research initiatives) - for the specific indicators for each type of dashboard, and the methodology and terminology page on how we produce the metrics. + Check out the indicator pages (for funders, + research institutions and + research initiatives) + for the specific indicators for each type of dashboard, and the methodology and terminology page on how we produce the metrics.

@@ -54,10 +54,10 @@ import {StakeholderBaseComponent} from "../../monitor-admin/utils/stakeholder-ba This is the current set of indicator themes we cover. We’ll keep enriching it as new requests and data are coming into the OpenAIRE Graph. We are at your disposal, should you have any recommendations!

- Check out the indicator pages (for {{entities.funders}}, - {{entities.organizations}} and - {{entities.ris}}) - for the specific indicators for each type of dashboard, and the methodology and terminology page on how we produce the metrics. + Check out the indicator pages (for {{entities.funders}}, + {{entities.organizations}} and + {{entities.ris}}) + for the specific indicators for each type of dashboard, and the methodology and terminology page on how we produce the metrics.

diff --git a/monitor/monitor-indicator-stakeholder-base.component.ts b/monitor/monitor-indicator-stakeholder-base.component.ts new file mode 100644 index 00000000..60686dfb --- /dev/null +++ b/monitor/monitor-indicator-stakeholder-base.component.ts @@ -0,0 +1,415 @@ +import {ChangeDetectorRef, Directive, HostListener, OnInit, ViewRef} from "@angular/core"; +import {IndicatorStakeholderBaseComponent} from "../monitor-admin/utils/stakeholder-base.component"; +import {DomSanitizer} from "@angular/platform-browser"; +import { + Category, + Indicator, IndicatorPath, + IndicatorSize, + Section, + Stakeholder, + SubCategory, + Topic, + Visibility +} from "./entities/stakeholder"; +import {LayoutService} from "../dashboard/sharedComponents/sidebar/layout.service"; +import {ClickEvent} from "../utils/click/click-outside-or-esc.directive"; +import {Session, User} from "../login/utils/helper.class"; +import {Filter, Value} from "../searchPages/searchUtils/searchHelperClasses.class"; +import {RangeFilter} from "../utils/rangeFilter/rangeFilterHelperClasses.class"; +import {RangeFilterComponent} from "../utils/rangeFilter/rangeFilter.component"; +import {Dates, StringUtils} from "../utils/string-utils.class"; +import {Params} from "@angular/router"; +import {StatisticsService} from "../monitor-admin/utils/services/statistics.service"; + +@Directive() +export abstract class MonitorIndicatorStakeholderBaseComponent extends IndicatorStakeholderBaseComponent implements OnInit { + /** Status */ + public loading: boolean = true; + public isMobile: boolean = false; + public isFullscreen: boolean = false; + + /** Variables */ + public user: User; + public view: Visibility; + public stakeholder: Stakeholder; + public activeTopic: Topic = null; + public activeCategory: Category = null; + public activeSubCategory: SubCategory = null; + public filters: Filter[] = []; + public queryParams: any = {}; + public periodFilter: RangeFilter = { + title: "Time range", + filterId: "year", + originalFilterIdFrom: null, + originalFilterIdTo: null, + selectedFromValue: null, + selectedToValue: null, + selectedFromAndToValues: "" + }; + rangeFilter: RangeFilterComponent; + + public numberResults: Map = new Map(); + public chartsActiveType: Map = new Map(); + public currentYear = new Date().getFullYear(); + + /** Services */ + protected sanitizer: DomSanitizer; + protected cdr: ChangeDetectorRef; + protected layoutService: LayoutService; + protected statisticsService: StatisticsService; + + @HostListener('fullscreenchange', ['$event']) + @HostListener('webkitfullscreenchange', ['$event']) + @HostListener('mozfullscreenchange', ['$event']) + @HostListener('MSFullscreenChange', ['$event']) + screenChange() { + this.isFullscreen = !this.isFullscreen; + } + + ngOnInit() { + this.layoutService.isMobile.subscribe(isMobile => { + this.isMobile = isMobile; + this.cdr.detectChanges(); + }); + } + + protected 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(); + } + } + } + } + } + + protected 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']; + } + + protected 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); + } + } + } + + protected 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._router.navigate([], {queryParams: this.queryParams}); + this.setIndicators(); + } + } + + protected getFullUrl(indicatorPath: IndicatorPath) { + return this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath); + } + + protected 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.getFullUrl(number.indicatorPaths[0]); + 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(); + } + } + + protected navigateToError() { + this._router.navigate([this.properties.errorLink], {queryParams: {'page': this._router.url}}); + } + + public getUrlByStakeHolder(indicatorPath: IndicatorPath) { + return this.sanitizer.bypassSecurityTrustResourceUrl( + this.indicatorUtils.getChartUrl(indicatorPath.source, this.getFullUrl(indicatorPath))); + } + + 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); + } + + public filter() { + this.validateYearRange(true); + } + + public 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(); + } + + } + + public 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 clearAll() { + for (let filter of this.filters) { + this.clearFilter(filter); + } + this.periodFilter.selectedFromValue = ""; + this.periodFilter.selectedToValue = ""; + this.validateYearRange(true) + } + + public 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]; + } + } + + 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 + }); + } + + 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 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 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 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(); + } + + toggleDescriptionOverlay(event, indicator: Indicator) { + event.stopPropagation(); + indicator.descriptionOverlay = !indicator.descriptionOverlay; + } + + closeDescriptionOverlay(event: ClickEvent, indicator: Indicator) { + if(event.clicked && indicator.descriptionOverlay) { + indicator.descriptionOverlay = false; + } + } +} diff --git a/sharedComponents/base/base.component.ts b/sharedComponents/base/base.component.ts index d7667a53..50e1a153 100644 --- a/sharedComponents/base/base.component.ts +++ b/sharedComponents/base/base.component.ts @@ -90,6 +90,10 @@ export abstract class BaseComponent implements OnDestroy { this._meta.updateTag({content: this.title}, "property='og:title'"); } } + this.trackView(); + } + + public trackView() { if (this._piwikService) { this.subscriptions.push(this._piwikService.trackView(properties, this.title).subscribe()); }