From de3fbe8d69ebab14da17913704db3f6a0e35983a Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Thu, 19 Oct 2023 16:33:04 +0300 Subject: [PATCH 1/4] Move topics and cache indicators in library. --- monitor-admin/topic/indicators.component.html | 490 ++++++ monitor-admin/topic/indicators.component.less | 53 + monitor-admin/topic/indicators.component.ts | 1494 +++++++++++++++++ monitor-admin/topic/topic-routing.module.ts | 19 + monitor-admin/topic/topic.component.html | 380 +++++ monitor-admin/topic/topic.component.ts | 795 +++++++++ monitor-admin/topic/topic.module.ts | 49 + monitor-admin/utils/adminDashboard.guard.ts | 43 + .../cache-indicators.component.less | 9 + .../cache-indicators.component.ts | 78 + .../cache-indicators.module.ts | 11 + .../cache-indicators.service.ts | 24 + .../cache-indicators/cache-indicators.ts | 261 +++ monitor-admin/utils/indicator-utils.ts | 1005 +++++++++++ .../utils/services/statistics.service.ts | 24 + .../utils/services/stats-profiles.service.ts | 19 + services/servicesUtils/customOptions.class.ts | 1 - 17 files changed, 4754 insertions(+), 1 deletion(-) create mode 100644 monitor-admin/topic/indicators.component.html create mode 100644 monitor-admin/topic/indicators.component.less create mode 100644 monitor-admin/topic/indicators.component.ts create mode 100644 monitor-admin/topic/topic-routing.module.ts create mode 100644 monitor-admin/topic/topic.component.html create mode 100644 monitor-admin/topic/topic.component.ts create mode 100644 monitor-admin/topic/topic.module.ts create mode 100644 monitor-admin/utils/adminDashboard.guard.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.component.less create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.component.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.module.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.service.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.ts create mode 100644 monitor-admin/utils/indicator-utils.ts create mode 100644 monitor-admin/utils/services/statistics.service.ts create mode 100644 monitor-admin/utils/services/stats-profiles.service.ts diff --git a/monitor-admin/topic/indicators.component.html b/monitor-admin/topic/indicators.component.html new file mode 100644 index 00000000..ecdc0cdb --- /dev/null +++ b/monitor-admin/topic/indicators.component.html @@ -0,0 +1,490 @@ +
+
+
Number Indicators
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+ + + + +
+ +
+
+
{{indicator.name}}
+
+ + -- +
+
+
+
+
+ +
+
+
+ +
+
+
+
+
Chart Indicators
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+ + + + +
+ +
+
+
+
+ {{indicator.name}} +
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+ Create a custom indicator +
+
+ Use our advance tool to create a custom Indicator that suit the needs of your funding + KPI's. +
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{urlParameterizedMessage}}
+
+
+ +
+
+
+
+ There are schema enhancements that can be applied in this query.Apply + now +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JSON Path +
+
+
+ This JSON path is not valid or the result has not been calculated yet. + Please press calculate on box below to see the result. +
+
+
+
+
+ + + + + + +
+
+ +
+
+
+
+
+ + + + + -- + + + +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{urlParameterizedMessage}}
+
+
+ +
+
+
+
+ There are schema enhancements that can be applied in this query. Apply + now +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+ +
+ You are about to delete + "{{indicator.name ? indicator.name : (indicator.indicatorPaths[0]?.parameters?.title?indicator.indicatorPaths[0].parameters.title:'')}}" indicator permanently. +
+ Indicators of all profiles based on this default indicator, will be deleted as well. +
+ + + + Are you sure you want to proceed? +
+
+
+ + +
+
+ +
+ You are about to delete this section and its indicators permanently. +
+ Sections of all profiles based on this default section and their contents, will be deleted as well. +
+ + + + Are you sure you want to proceed? +
+
+ + +
+
+ +
+
+
diff --git a/monitor-admin/topic/indicators.component.less b/monitor-admin/topic/indicators.component.less new file mode 100644 index 00000000..627ab18a --- /dev/null +++ b/monitor-admin/topic/indicators.component.less @@ -0,0 +1,53 @@ +@import (reference) "~src/assets/openaire-theme/less/_import-variables"; + +.number-preview { + border: @global-border-width solid @global-border; + background: transparent; + border-radius: @global-border-radius; + min-width: 100px; + min-height: 70px; +} + +.refresh-indicator { + background-color: @global-overlay-background; + border-radius: @global-border-radius; + position: absolute; + color: @global-inverse-color; + z-index: 1; +} + +.section { + padding: 60px 45px; + border-radius: @global-border-radius; + border: @global-border-width solid @global-border; + position: relative; + background: @global-inverse-color; + border-left: 5px @global-primary-background solid; + + .tools { + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, -100%); + max-width: 50px; + padding: 5px 10px; + background-image: @global-primary-gradient; + color: @global-inverse-color; + -webkit-clip-path: polygon(20% 5%, 80% 5%, 100% 100%, 0% 100%); + clip-path: polygon(20% 5%, 80% 5%, 100% 100%, 0% 100%); + display: none; + } + + &:hover { + .tools { + display: block; + + a { + color: currentColor; + &:hover { + text-decoration: none; + } + } + } + } +} diff --git a/monitor-admin/topic/indicators.component.ts b/monitor-admin/topic/indicators.component.ts new file mode 100644 index 00000000..d97f5f82 --- /dev/null +++ b/monitor-admin/topic/indicators.component.ts @@ -0,0 +1,1494 @@ +import { + AfterViewInit, + ChangeDetectorRef, + Component, + HostListener, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + ViewChild +} from "@angular/core"; +import { + Format, + Indicator, + IndicatorPath, + IndicatorSize, + IndicatorType, + Section, + Stakeholder, + Visibility +} from "../../monitor/entities/stakeholder"; +import {IndicatorUtils, StakeholderUtils} from "../utils/indicator-utils"; +import { + AbstractControl, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validators +} from "@angular/forms"; +import {AlertModal} from "../../utils/modal/alert"; +import {StatisticsService} from "../utils/services/statistics.service"; +import {HelperFunctions} from "../../utils/HelperFunctions.class"; +import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser"; +import {Reorder, StakeholderService} from "../../monitor/services/stakeholder.service"; +import {EnvProperties} from "../../utils/properties/env-properties"; +import {Observable, Subscriber} from "rxjs"; +import {LayoutService} from "../../dashboard/sharedComponents/sidebar/layout.service"; +import {Router} from "@angular/router"; +import {Role, Session, User} from "../../login/utils/helper.class"; +import {StringUtils} from "../../utils/string-utils.class"; +import {Notification} from "../../notifications/notifications"; +import {NotificationUtils} from "../../notifications/notification-utils"; +import {NotifyFormComponent} from "../../notifications/notify-form/notify-form.component"; +import {NotificationService} from "../../notifications/notification.service"; +import {properties} from "src/environments/environment"; +import {NotificationHandler} from "../../utils/notification-handler"; + +declare var UIkit; + +@Component({ + selector: 'indicators', + templateUrl: './indicators.component.html', + styleUrls: ['indicators.component.less'] +}) +export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { + filesToUpload: Array; + errorMessage = ""; + public properties: EnvProperties = properties; + @Input() + public topicIndex: number = 0; + @Input() + public categoryIndex: number = 0; + @Input() + public subcategoryIndex: number = 0; + @Input() + public stakeholder: Stakeholder = null; + @Input() + public changed: Observable; + @Input() + public user: User = null; + public preview: string; + public indicatorUtils: IndicatorUtils = new IndicatorUtils(); + public stakeholderUtils: StakeholderUtils = new StakeholderUtils(); + public numberIndicatorFb: UntypedFormGroup; + public chartIndicatorFb: UntypedFormGroup; + public chartSections: UntypedFormArray; + public numberSections: UntypedFormArray; + /** + * Editable indicator + */ + public section: Section; + public indicator: Indicator; + public index: number = -1; + public editing: boolean = false; + public dragging: boolean = false; + /** Caches */ + public safeUrls: Map = new Map([]); + public numberResponses: Map = new Map(); + public numberResults: Map = new Map(); + /** Import / Export Indicators */ + importLoading: boolean = false; + @ViewChild('editChartModal', {static: true}) editChartModal: AlertModal; + @ViewChild('editNumberModal', {static: true}) editNumberModal: AlertModal; + @ViewChild('deleteModal', {static: true}) deleteModal: AlertModal; + @ViewChild('deleteSectionModal', {static: true}) deleteSectionModal: AlertModal; + public sectionTypeToDelete: string; + public sectionChildrenActionOnDelete: string; + public indicatorChildrenActionOnDelete: string; + urlParameterizedMessage = null; + showCheckForSchemaEnhancements: boolean = false; + private notification: Notification; + @ViewChild('editNumberNotify', {static: true}) editNumberNotify: NotifyFormComponent; + @ViewChild('editChartNotify', {static: true}) editChartNotify: NotifyFormComponent; + @ViewChild('deleteNotify', {static: true}) deleteNotify: NotifyFormComponent; + + public isFullscreen: boolean = false; + + @HostListener('fullscreenchange', ['$event']) + @HostListener('webkitfullscreenchange', ['$event']) + @HostListener('mozfullscreenchange', ['$event']) + @HostListener('MSFullscreenChange', ['$event']) + screenChange(event) { + this.isFullscreen = !this.isFullscreen; + } + + /** + * Subscriptions + **/ + private subscriptions: any[] = []; + private urlSubscriptions: any[] = []; + private numberSubscription: any[] = []; + + constructor(private layoutService: LayoutService, + private stakeholderService: StakeholderService, + private statisticsService: StatisticsService, + private notificationService: NotificationService, + private fb: UntypedFormBuilder, + private router: Router, + private cdr: ChangeDetectorRef, + private sanitizer: DomSanitizer) { + this.filesToUpload = []; + } + + ngOnInit(): void { + if (this.stakeholder) { + this.setCharts(); + this.setNumbers(); + } + this.changed.subscribe(() => { + this.setCharts(); + this.setNumbers(); + this.initReorder(); + }) + } + + ngOnDestroy(): void { + this.subscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } else if (value instanceof Function) { + value(); + } + }); + this.urlSubscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.numberSubscription.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + } + + ngAfterViewInit(): void { + this.initReorder(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (this.canEdit) { + if (changes.topicIndex || changes.categoryIndex || changes.subcategoryIndex) { + this.initReorder(); + this.setCharts(); + this.setNumbers(); + } + } + } + + initReorder() { + this.subscriptions.forEach(value => { + if (value instanceof Function) { + value(); + } + }); + if (document !== undefined) { + let callback = (list, type: IndicatorType, action: 'moved' | 'added' | 'removed'): void => { + let items: HTMLCollection = list.current.children; + let reordered = []; + for (let i = 0; i < items.length; i++) { + if (items.item(i).id) { + reordered.push(items.item(i).id); + } + } + let reorder: Reorder = { + action: action, + target: list.detail[1].id, + ids: reordered + } + this.reorderIndicators(list.current.id.toString().split('-')[1], type, reorder); + }; + this.numbers.forEach((section) => { + this.subscriptions.push(UIkit.util.on(document, 'start', '#number-' + section._id, (): void => { + this.dragging = true; + })); + this.subscriptions.push(UIkit.util.on(document, 'stop', '#number-' + section._id, (): void => { + this.dragging = false; + })); + this.subscriptions.push(UIkit.util.on(document, 'moved', '#number-' + section._id, (list): void => { + callback(list, "number", 'moved'); + })); + this.subscriptions.push(UIkit.util.on(document, 'added', '#number-' + section._id, (list): void => { + callback(list, "number", 'added'); + })); + this.subscriptions.push(UIkit.util.on(document, 'removed', '#number-' + section._id, (list): void => { + callback(list, "number", 'removed'); + })); + }); + this.charts.forEach((section) => { + this.subscriptions.push(UIkit.util.on(document, 'moved', '#chart-' + section._id, (list): void => { + callback(list, "chart", 'moved'); + })); + this.subscriptions.push(UIkit.util.on(document, 'added', '#chart-' + section._id, (list): void => { + callback(list, "chart", 'added'); + })); + this.subscriptions.push(UIkit.util.on(document, 'removed', '#chart-' + section._id, (list): void => { + callback(list, "chart", 'removed'); + })); + }); + } + } + + hide(element: any) { + UIkit.dropdown(element).hide(); + } + + setCharts() { + this.chartSections = this.fb.array([]); + this.charts.forEach(section => { + this.chartSections.push(this.fb.group({ + _id: this.fb.control(section._id), + title: this.fb.control(section.title), + creationDate: this.fb.control(section.creationDate), + stakeholderAlias: this.fb.control(section.stakeholderAlias), + defaultId: this.fb.control(section.defaultId), + type: this.fb.control(section.type), + indicators: this.fb.control(section.indicators) + })); + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + let url = this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath); + if (!this.safeUrls.get('url')) { + indicatorPath.safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + this.safeUrls.set(url, indicatorPath.safeResourceUrl); + } + }); + }) + }); + } + + setNumbers() { + this.numberSections = this.fb.array([]); + this.numberResults.clear(); + let urls: Map = new Map(); + this.numbers.forEach((section, i) => { + this.numberSections.push(this.fb.group({ + _id: this.fb.control(section._id), + title: this.fb.control(section.title), + creationDate: this.fb.control(section.creationDate), + stakeholderAlias: this.fb.control(section.stakeholderAlias), + defaultId: this.fb.control(section.defaultId), + type: this.fb.control(section.type), + indicators: this.fb.control(section.indicators) + })); + section.indicators.forEach((number, j) => { + let url = this.indicatorUtils.getFullUrl(this.stakeholder, 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); + }); + }); + this.numberSubscription.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + urls.forEach((indexes, pair) => { + let parsed = JSON.parse(pair); + let response = this.numberResponses.get(pair); + if (response) { + this.calculateResults(response, indexes); + } else { + this.numberSubscription.push(this.statisticsService.getNumbers(this.indicatorUtils.getSourceType(parsed[0]), parsed[1]).subscribe(response => { + this.calculateResults(response, indexes); + this.numberResponses.set(pair, response); + })); + } + }); + } + + private calculateResults(response: any, indexes: [number, number][]) { + indexes.forEach(([i, j]) => { + let result = JSON.parse(JSON.stringify(response)); + this.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); + }); + } + + get charts(): Section[] { + if (this.stakeholder.topics[this.topicIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]) { + return this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].charts; + } else { + return []; + } + } + + get numbers(): Section[] { + if (this.stakeholder.topics[this.topicIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]) { + return this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].numbers; + } else { + return []; + } + } + + set numbers(sections: Section[]) { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].numbers = sections; + } + + get open(): boolean { + return this.layoutService.open; + } + + get canEdit() { + return this.stakeholder && + this.stakeholder.topics[this.topicIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]; + } + + public get numberIndicatorPaths(): UntypedFormArray { + return this.numberIndicatorFb.get('indicatorPaths') as UntypedFormArray; + } + + public get chartIndicatorPaths(): UntypedFormArray { + return this.chartIndicatorFb.get('indicatorPaths') as UntypedFormArray; + } + + 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 addJsonPath(index: number) { + if (index == 0 && this.getJsonPath(index).getRawValue()[index].indexOf(",") != -1) { + //if in the first path there are more than one paths comma separated, split them and autogenerate the forms + let paths = this.getJsonPath(index).getRawValue()[index].split(","); + for (let i = 0; i < paths.length; i++) { + if (i != 0) { + this.getJsonPath(index).push(this.fb.control('', Validators.required)); + } + } + this.getJsonPath(index).setValue(paths) + } else { + this.getJsonPath(index).push(this.fb.control('', Validators.required)); + } + } + + public removeJsonPath(i: number, j: number) { + if (this.getJsonPath(i).enabled) { + this.getJsonPath(i).removeAt(j); + } + } + + public validateJsonPath(index: number, dirty: boolean = false) { + let indicatorPath: UntypedFormGroup = this.numberIndicatorPaths.at(index); + if (this.indicator.defaultId === null) { + this.getJsonPath(index).disable(); + } + indicatorPath.get('result').setErrors({validating: true}); + this.subscriptions.push(this.statisticsService.getNumbers(null, indicatorPath.get('url').value).subscribe(response => { + let result = JSON.parse(JSON.stringify(response)); + this.getJsonPath(index).controls.forEach(jsonPath => { + if (result) { + result = result[jsonPath.value]; + } + }); + setTimeout(() => { + if (this.indicator.defaultId === null) { + this.getJsonPath(index).enable(); + if (dirty) { + this.getJsonPath(index).markAsDirty(); + } + } + indicatorPath.get('result').setErrors(null); + if (typeof result === 'string' || typeof result === 'number') { + result = Number(result); + if (result !== Number.NaN) { + indicatorPath.get('result').setValue(result); + } else { + indicatorPath.get('result').setValue(0); + } + } else { + indicatorPath.get('result').setValue(0); + } + }, 500); + }, error => { + setTimeout(() => { + if (this.indicator.defaultId === null) { + this.getJsonPath(index).enable(); + if (dirty) { + this.getJsonPath(index).markAsDirty(); + } + } + indicatorPath.get('result').setErrors(null); + indicatorPath.get('result').setValue(0); + }, 500); + })); + } + + public getJsonPath(index: number): UntypedFormArray { + return this.numberIndicatorPaths.at(index).get('jsonPath') as UntypedFormArray; + } + + public getCurrentJsonPath(index: number): string[] { + return this.section.indicators[this.index].indicatorPaths[index].jsonPath; + } + + public getParameters(index: number): UntypedFormArray { + return this.chartIndicatorPaths.at(index).get('parameters') as UntypedFormArray; + } + + public getParameter(index: number, key: string): UntypedFormControl { + return this.getParameters(index).controls.filter(control => control.value.key === key)[0] as UntypedFormControl; + } + + private getSecureUrlByStakeHolder(indicatorPath: IndicatorPath) { + return this.sanitizer.bypassSecurityTrustResourceUrl( + this.indicatorUtils.getChartUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath))); + } + + private getUrlByStakeHolder(indicatorPath: IndicatorPath) { + return this.indicatorUtils.getChartUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath)); + } + + public addNumberIndicatorPath(url: string = '', parameters: UntypedFormArray = new UntypedFormArray([]), source: string = 'stats-tool', jsonPath: UntypedFormArray = new UntypedFormArray([]), format: Format = "NUMBER") { + if (jsonPath.length === 0) { + jsonPath.push(this.fb.control('', Validators.required)); + } + this.numberIndicatorPaths.push(this.fb.group({ + url: this.fb.control(url, [Validators.required, StringUtils.urlValidator()]), + jsonPath: jsonPath, + result: this.fb.control(0, Validators.required), + source: this.fb.control(source, Validators.required), + format: this.fb.control(format, Validators.required) + } + )); + let index = this.numberIndicatorPaths.length - 1; + if (this.numberIndicatorPaths.at(index).get('url').valid) { + this.validateJsonPath(index); + this.checkForSchemaEnhancements(this.numberIndicatorPaths.at(index).get('url').value); + } + if (this.indicator.defaultId === null) { + this.subscriptions.push(this.numberIndicatorPaths.at(index).get('url').valueChanges.subscribe(value => { + this.numberIndicatorPaths.at(index).get('result').setValue(null); + if (this.numberIndicatorPaths.at(index).get('url').valid) { + let indicatorPath: IndicatorPath = this.indicatorUtils.generateIndicatorByNumberUrl(this.indicatorUtils.getNumberSource(value), value, this.stakeholder, this.numberIndicatorPaths.at(index).get('jsonPath').value, this.indicatorUtils.numberSources.get(this.indicatorUtils.getNumberSource(value))); + if (!this.isStakeholderParametersValid(indicatorPath)) { + // default profile + if (this.stakeholder.defaultId == null) { + this.urlParameterizedMessage = "This indicator couldn't be generated properly. Stakeholders based on this profile may not inherit the data correctly." + } else { + this.urlParameterizedMessage = "This indicator couldn't be generated properly. Please make sure chart data is for the current stakeholder." + } + } else { + this.urlParameterizedMessage = null; + } + this.checkForSchemaEnhancements(this.numberIndicatorPaths.at(index).get('url').value); + if (this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index] = indicatorPath; + } else { + this.indicator.indicatorPaths.push(indicatorPath); + } + if (indicatorPath.source) { + this.numberIndicatorPaths.at(index).get('source').setValue(indicatorPath.source); + } + if (indicatorPath.jsonPath.length > 1 && this.getJsonPath(index).length == 1) { + let paths = indicatorPath.jsonPath; + for (let i = 0; i < paths.length; i++) { + if (i == this.getJsonPath(index).length) { + this.getJsonPath(index).push(this.fb.control('', Validators.required)); + } + } + this.getJsonPath(index).setValue(paths) + } + } else { + this.urlParameterizedMessage = null; + } + }) + ); + + this.subscriptions.push(this.numberIndicatorPaths.at(index).get('jsonPath').valueChanges.subscribe(value => { + if (this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index].jsonPath = value; + } + this.numberIndicatorPaths.at(index).get('result').setValue(null); + }) + ); + this.subscriptions.push(this.numberIndicatorPaths.at(index).get('source').valueChanges.subscribe(value => { + if (this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index].source = value; + } + }) + ); + } else { + this.numberIndicatorPaths.at(index).get('url').disable(); + this.numberIndicatorPaths.at(index).get('jsonPath').disable(); + this.numberIndicatorPaths.at(index).get('source').disable(); + } + } + + public addChartIndicatorPath(value: string = '', parameters: UntypedFormArray = new UntypedFormArray([]), disableUrl: boolean = false, type: string = null) { + this.chartIndicatorPaths.push(this.fb.group({ + url: this.fb.control(value, [Validators.required, StringUtils.urlValidator()]), + parameters: parameters, + type: this.fb.control(type) + } + )); + let index = this.chartIndicatorPaths.length - 1; + if (disableUrl) { + this.chartIndicatorPaths.at(index).get('url').disable(); + } else { + this.checkForSchemaEnhancements(this.chartIndicatorPaths.at(index).get('url').value); + this.urlSubscriptions.push(this.chartIndicatorPaths.at(index).get('url').valueChanges.subscribe(value => { + if (this.chartIndicatorPaths.at(index).get('url').valid) { + let indicatorPath: IndicatorPath = this.indicatorUtils.generateIndicatorByChartUrl(this.indicatorUtils.getChartSource(value), value, this.chartIndicatorPaths.at(index).get('type').value, this.stakeholder); + if (!this.isStakeholderParametersValid(indicatorPath)) { + // default profile + if (this.stakeholder.defaultId == null) { + this.urlParameterizedMessage = "This chart couldn't be generated properly. Stakeholders based on this profile may not inherit the data correctly." + } else { + this.urlParameterizedMessage = "This chart couldn't be generated properly. Please make sure chart data is for the current stakeholder." + } + } else { + this.urlParameterizedMessage = null; + } + this.checkForSchemaEnhancements(this.chartIndicatorPaths.at(index).get('url').value); + (this.chartIndicatorPaths.at(index) as UntypedFormGroup).get('type').setValue(indicatorPath.type); + let parameters = this.getParametersAsFormArray(indicatorPath); + (this.chartIndicatorPaths.at(index) as UntypedFormGroup).setControl('parameters', parameters); + if (!this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index] = indicatorPath; + this.indicator.indicatorPaths[index].safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + } else { + indicatorPath.safeResourceUrl = this.indicator.indicatorPaths[index].safeResourceUrl; + this.indicator.indicatorPaths[index] = indicatorPath; + } + } else { + this.urlParameterizedMessage = null; + } + })); + } + } + + private isStakeholderParametersValid(indicatorPath: IndicatorPath) { + return !((indicatorPath.chartObject && Object.keys(indicatorPath.parameters).indexOf("index_id") == -1 && Object.keys(indicatorPath.parameters).indexOf("index_name") == -1 && Object.keys(indicatorPath.parameters).indexOf("index_shortName") == -1) + || (!indicatorPath.chartObject && indicatorPath.url.indexOf("index_id") == -1 && indicatorPath.url.indexOf("index_name") == -1 && (indicatorPath.url).indexOf("index_shortName") == -1)); + + } + + private getJsonPathAsFormArray(indicatorPath: IndicatorPath): UntypedFormArray { + let jsonPath = this.fb.array([]); + if (indicatorPath.jsonPath) { + indicatorPath.jsonPath.forEach(path => { + jsonPath.push(this.fb.control(path, Validators.required)); + }); + } + return jsonPath; + } + + private getParametersAsFormArray(indicatorPath: IndicatorPath): UntypedFormArray { + let parameters = this.fb.array([]); + if (indicatorPath.parameters) { + Object.keys(indicatorPath.parameters).forEach(key => { + if (this.indicatorUtils.ignoredParameters.indexOf(key) === -1) { + if (this.indicatorUtils.parametersValidators.has(key)) { + parameters.push(this.fb.group({ + key: this.fb.control(key), + value: this.fb.control(indicatorPath.parameters[key], this.indicatorUtils.parametersValidators.get(key)) + })); + } else { + parameters.push(this.fb.group({ + key: this.fb.control(key), + value: this.fb.control(indicatorPath.parameters[key]) + })); + } + } + }); + } + return parameters; + } + + public editNumberIndicatorOpen(section: Section, id = null) { + this.urlParameterizedMessage = null; + this.section = section; + this.index = (id) ? section.indicators.findIndex(value => value._id === id) : -1; + if (this.index !== -1) { + this.indicator = HelperFunctions.copy(this.section.indicators[this.index]); + this.numberIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name, Validators.required), + description: this.fb.control(this.indicator.description), + creationDate: this.fb.control(this.indicator.creationDate), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([], Validators.required), + type: this.fb.control(this.indicator.type), + width: this.fb.control(this.indicator.width), + height: this.fb.control(this.indicator.height), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.indicator.indicatorPaths.forEach(indicatorPath => { + this.addNumberIndicatorPath(this.indicatorUtils.getNumberUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath)), indicatorPath.parameters, indicatorPath.source, this.getJsonPathAsFormArray(indicatorPath), indicatorPath.format); + }); + } else { + this.indicator = new Indicator('', '', '', 'number', 'small', 'small', "PUBLIC", []); + this.numberIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name, Validators.required), + description: this.fb.control(this.indicator.description), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([], Validators.required), + type: this.fb.control(this.indicator.type), + width: this.fb.control(this.indicator.width), + height: this.fb.control(this.indicator.height), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.addNumberIndicatorPath(); + } + if (this.indicator.defaultId) { + setTimeout(() => { + this.numberIndicatorFb.get('description').disable(); + }, 0); + } + this.editNumberModal.cancelButtonText = 'Cancel'; + this.editNumberModal.okButtonLeft = false; + this.editNumberModal.alertMessage = false; + if (this.index === -1) { + this.editNumberModal.alertTitle = 'Create a new number indicator'; + this.editNumberModal.okButtonText = 'Save'; + this.notification = NotificationUtils.createIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.editNumberNotify.reset(this.notification.message); + } else { + this.editNumberModal.alertTitle = 'Edit number indicator\'s information'; + this.editNumberModal.okButtonText = 'Save Changes'; + this.notification = NotificationUtils.editIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.editNumberNotify.reset(this.notification.message); + } + this.editNumberModal.stayOpen = true; + this.editNumberModal.open(); + } + + public editChartIndicatorOpen(section: Section, id = null) { + this.urlParameterizedMessage = null; + this.urlSubscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.section = section; + this.index = (id) ? section.indicators.findIndex(value => value._id === id) : -1; + if (this.index !== -1) { + this.indicator = HelperFunctions.copy(this.section.indicators[this.index]); + this.chartIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name), + creationDate: this.fb.control(this.indicator.creationDate), + description: this.fb.control(this.indicator.description), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([]), + width: this.fb.control(this.indicator.width), + height: this.fb.control(this.indicator.height), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.indicator.indicatorPaths.forEach(indicatorPath => { + this.addChartIndicatorPath(this.getUrlByStakeHolder(indicatorPath), + this.getParametersAsFormArray(indicatorPath), this.indicator.defaultId !== null, indicatorPath.type); + indicatorPath.safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + }); + } else { + this.indicator = new Indicator('', '', '', 'chart', 'medium', 'medium', "PUBLIC", []); + this.chartIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name), + description: this.fb.control(this.indicator.description), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([]), + width: this.fb.control(this.indicator.width, Validators.required), + height: this.fb.control(this.indicator.height, Validators.required), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.addChartIndicatorPath(); + } + if (this.indicator.defaultId) { + setTimeout(() => { + this.chartIndicatorFb.get('description').disable(); + }, 0); + } + this.editChartModal.cancelButtonText = 'Cancel'; + this.editChartModal.okButtonLeft = false; + this.editChartModal.alertMessage = false; + if (this.index === -1) { + this.editChartModal.alertTitle = 'Create a new chart indicator'; + this.editChartModal.okButtonText = 'Save'; + this.notification = NotificationUtils.createIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.editChartNotify.reset(this.notification.message); + } else { + this.editChartModal.alertTitle = 'Edit chart indicator\'s information'; + this.editChartModal.okButtonText = 'Save Changes'; + this.notification = NotificationUtils.editIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + ; + this.editChartNotify.reset(this.notification.message); + } + this.editChartModal.stayOpen = true; + this.editChartModal.open(); + } + + saveIndicator() { + this.editing = true; + if (this.indicator.type === 'chart') { + this.chartIndicatorFb.get('description').enable(); + this.indicator = this.indicatorUtils.generateIndicatorByForm(this.chartIndicatorFb.value, this.indicator.indicatorPaths, this.indicator.type, true); + this.section = this.charts.find(section => section._id === this.section._id); + } else { + this.numberIndicatorFb.get('description').enable(); + this.indicator = this.indicatorUtils.generateIndicatorByForm(this.numberIndicatorFb.value, this.indicator.indicatorPaths, this.indicator.type, false); + this.section = this.numbers.find(section => section._id === this.section._id); + } + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + this.section._id + ]; + this.subscriptions.push(this.stakeholderService.saveElement(this.properties.monitorServiceAPIURL, this.indicator, path).subscribe(indicator => { + if (this.index !== -1) { + this.section.indicators[this.index] = indicator; + } else { + this.section.indicators.push(indicator); + } + this.notification.entity = indicator._id; + this.notification.stakeholder = this.stakeholder.alias; + this.notification.stakeholderType = this.stakeholder.type; + this.notification.groups = [Role.curator(this.stakeholder.type)]; + if (this.stakeholder.defaultId) { + this.notification.groups.push(Role.manager(this.stakeholder.type, this.stakeholder.alias)); + if (this.indicator.type === "chart") { + this.setCharts(); + this.chartIndicatorFb = null; + this.editChartNotify.sendNotification(this.notification); + } else { + this.setNumbers(); + this.numberIndicatorFb = null; + this.editNumberNotify.sendNotification(this.notification); + } + } else { + this.stakeholderService.getStakeholders(this.properties.monitorServiceAPIURL, null, this.stakeholder._id).subscribe(stakeholders => { + stakeholders.forEach(value => { + this.notification.groups.push(Role.manager(value.type, value.alias)) + }); + if (this.indicator.type === "chart") { + this.setCharts(); + this.chartIndicatorFb = null; + this.editChartNotify.sendNotification(this.notification); + } else { + this.setNumbers(); + this.numberIndicatorFb = null; + this.editNumberNotify.sendNotification(this.notification); + } + }); + } + UIkit.notification('Indicator has been successfully saved', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + if (this.indicator.type === "chart") { + this.editChartModal.cancel(); + } else { + this.editNumberModal.cancel(); + } + }, error => { + this.chartIndicatorFb = null; + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + if (this.indicator.type === "chart") { + this.editChartModal.cancel(); + } else { + this.editNumberModal.cancel(); + } + })); + } + + saveIndicators(sections) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id + ]; + this.subscriptions.push(this.stakeholderService.saveBulkElements(this.properties.monitorServiceAPIURL, sections, path).subscribe(stakeholder => { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].charts = stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].charts; + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].numbers = stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].numbers; + this.setCharts(); + this.setNumbers(); + this.initReorder(); + this.notification = NotificationUtils.importIndicators(this.user.fullname, this.stakeholder.alias); + this.notification.entity = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id; + this.notification.name = this.user.firstname; + this.notification.surname = this.user.lastname; + this.notification.stakeholder = this.stakeholder.alias; + this.notification.stakeholderType = this.stakeholder.type; + this.notification.groups = [Role.curator(this.stakeholder.type)]; + if (this.stakeholder.defaultId) { + this.notification.groups.push(Role.manager(this.stakeholder.type, this.stakeholder.alias)); + this.notificationService.sendNotification(this.notification).subscribe(notification => { + UIkit.notification('A notification has been sent successfully', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + }, error => { + UIkit.notification('An error has occurred. Please try again later', { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + }); + } else { + this.stakeholderService.getStakeholders(this.properties.monitorServiceAPIURL, null, this.stakeholder._id).subscribe(stakeholders => { + stakeholders.forEach(value => { + this.notification.groups.push(Role.manager(value.type, value.alias)) + }); + this.notificationService.sendNotification(this.notification).subscribe(notification => { + NotificationHandler.rise('A notification has been sent successfully'); + }, error => { + NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); + }); + }); + } + this.editing = false; + this.importLoading = false; + NotificationHandler.rise('Indicators have been imported successfully!'); + }, error => { + this.chartIndicatorFb = null; + NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); + this.editing = false; + this.importLoading = false; + })); + + + } + + reorderIndicators(sectionId: string, type: IndicatorType, reorder: Reorder) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + sectionId + ]; + this.subscriptions.push(this.stakeholderService.reorderIndicators(this.properties.monitorServiceAPIURL, path, reorder, type).subscribe(indicators => { + if (type === 'chart') { + this.charts.find(section => section._id === sectionId).indicators = indicators; + this.setCharts(); + } else { + this.numbers.find(section => section._id === sectionId).indicators = indicators; + this.setNumbers(); + } + this.editing = false; + })); + } + + hasDifference(index: number): boolean { + let hasDifference = false; + this.chartIndicatorPaths.at(index).value.parameters.forEach((parameter) => { + if (parameter.value !== this.indicator.indicatorPaths[index].parameters[parameter.key]) { + hasDifference = true; + return; + } + }); + return hasDifference || this.indicator.indicatorPaths[index].safeResourceUrl.toString() !== + this.getSecureUrlByStakeHolder(this.indicator.indicatorPaths[index]).toString(); + } + + public get isAdministrator(): boolean { + return Session.isPortalAdministrator(this.user); + } + + public get isCurator(): boolean { + return this.isAdministrator || Session.isCurator(this.stakeholder.type, this.user); + } + + refreshIndicator() { + this.indicator = this.indicatorUtils.generateIndicatorByForm(this.chartIndicatorFb.value, this.indicator.indicatorPaths, 'chart', true); + this.indicator.indicatorPaths.forEach(indicatorPath => { + indicatorPath.safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + }); + } + + deleteIndicatorOpen(section: Section, indicatorId: string, type: string, childrenAction: string = null) { + this.indicatorChildrenActionOnDelete = null; + if (childrenAction == "delete") { + this.indicatorChildrenActionOnDelete = childrenAction; + } else if (childrenAction == "disconnect") { + this.indicatorChildrenActionOnDelete = childrenAction; + } + + this.section = section; + if (type === 'chart') { + this.index = this.charts.find(value => value._id == section._id).indicators.findIndex(value => value._id == indicatorId); + } else { + this.index = this.numbers.find(value => value._id == section._id).indicators.findIndex(value => value._id == indicatorId); + } + this.indicator = section.indicators.find(value => value._id == indicatorId); + this.deleteModal.alertTitle = 'Delete indicator'; + this.deleteModal.cancelButtonText = 'No'; + this.deleteModal.okButtonText = 'Yes'; + this.notification = NotificationUtils.deleteIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.deleteNotify.reset(this.notification.message); + this.deleteModal.stayOpen = true; + this.deleteModal.open(); + } + + deleteIndicator() { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + this.section._id, + this.indicator._id + ]; + this.subscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, path, this.indicatorChildrenActionOnDelete).subscribe(() => { + if (this.indicator.type === 'chart') { + this.charts.find(section => section._id === this.section._id).indicators.splice(this.index, 1); + this.setCharts(); + } else { + this.numbers.find(section => section._id === this.section._id).indicators.splice(this.index, 1); + this.setNumbers(); + } + UIkit.notification('Indicator has been successfully deleted', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.notification.entity = this.indicator._id; + this.notification.stakeholder = this.stakeholder.alias; + this.notification.stakeholderType = this.stakeholder.type; + this.notification.groups = [Role.curator(this.stakeholder.type)]; + if (this.stakeholder.defaultId) { + this.notification.groups.push(Role.manager(this.stakeholder.type, this.stakeholder.alias)); + this.deleteNotify.sendNotification(this.notification); + } else { + this.stakeholderService.getStakeholders(this.properties.monitorServiceAPIURL, null, this.stakeholder._id).subscribe(stakeholders => { + stakeholders.forEach(value => { + this.notification.groups.push(Role.manager(value.type, value.alias)) + }); + this.deleteNotify.sendNotification(this.notification); + }); + } + this.editing = false; + this.deleteModal.cancel(); + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.deleteModal.cancel(); + })); + } + + changeIndicatorStatus(sectionId: string, indicator: Indicator, visibility: Visibility) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + sectionId, + indicator._id + ]; + this.subscriptions.push(this.stakeholderService.changeVisibility(this.properties.monitorServiceAPIURL, path, visibility).subscribe(returnedElement => { + indicator.visibility = returnedElement.visibility; + UIkit.notification('Indicator has been successfully changed to ' + indicator.visibility.toLowerCase(), { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + }, error => { + UIkit.notification('An error has been occurred. Try again later', { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + })); + } + + saveSection(focused: boolean, sectionControl: AbstractControl, index: number, type: IndicatorType = "chart") { + if (!focused && sectionControl.dirty) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id + ]; + this.subscriptions.push(this.stakeholderService.saveSection(this.properties.monitorServiceAPIURL, sectionControl.value, path, index).subscribe(section => { + if (type === 'chart') { + this.charts[index] = section; + this.setCharts(); + } else { + this.numbers[index] = section; + this.setNumbers(); + } + this.initReorder(); + UIkit.notification('Section has been successfully saved', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + })); + } + } + + createSection(index = -1, type: IndicatorType = 'chart') { + this.editing = true; + this.section = new Section(type, null, null, this.stakeholder.alias); + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id + ]; + this.subscriptions.push(this.stakeholderService.saveSection(this.properties.monitorServiceAPIURL, this.section, path, index).subscribe(section => { + if (type === 'chart') { + if (index !== -1) { + this.charts.splice(index, 0, section); + } else { + this.charts.push(section); + } + this.setCharts(); + } else { + if (index !== -1) { + this.numbers.splice(index, 0, section); + } else { + this.numbers.push(section); + } + this.setNumbers(); + } + this.initReorder(); + UIkit.notification('Section has been successfully created', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + })); + } + + // deleteNumberSectionOpen(section: Section, index: number) { + // this.section = section; + // this.index = index; + // this.deleteNumberSectionModal.alertTitle = 'Delete Section'; + // this.deleteNumberSectionModal.cancelButtonText = 'No'; + // this.deleteNumberSectionModal.okButtonText = 'Yes'; + // this.deleteNumberSectionModal.open(); + // } + // + // deleteChartSectionOpen(section: Section, index: number) { + // this.section = section; + // this.index = index; + // this.deleteChartSectionModal.alertTitle = 'Delete Section'; + // this.deleteChartSectionModal.cancelButtonText = 'No'; + // this.deleteChartSectionModal.okButtonText = 'Yes'; + // this.deleteChartSectionModal.open(); + // } + + deleteSectionOpen(section: Section, index: number, type: IndicatorType, childrenAction: string = null) { + if (!this.editing && !section.defaultId) { + this.sectionTypeToDelete = type; + this.sectionChildrenActionOnDelete = null; + if (childrenAction == "delete") { + this.sectionChildrenActionOnDelete = childrenAction; + } else if (childrenAction == "disconnect") { + this.sectionChildrenActionOnDelete = childrenAction; + } + + this.section = section; + this.index = index; + this.deleteSectionModal.alertTitle = 'Delete Section'; + this.deleteSectionModal.cancelButtonText = 'No'; + this.deleteSectionModal.okButtonText = 'Yes'; + this.deleteSectionModal.stayOpen = true; + this.deleteSectionModal.open(); + } + } + + deleteSection() { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + this.section._id + ]; + this.subscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, path, this.sectionChildrenActionOnDelete).subscribe(() => { + if (this.sectionTypeToDelete === "chart") { + this.charts.splice(this.index, 1); + this.setCharts(); + } else { + this.numbers.splice(this.index, 1); + this.setNumbers(); + } + this.initReorder(); + UIkit.notification('Section has been successfully deleted', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.deleteSectionModal.cancel(); + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.deleteSectionModal.cancel(); + })); + } + + private checkForSchemaEnhancements(url: string) { + this.showCheckForSchemaEnhancements = this.isAdministrator && url && !this.properties.useOldStatisticsSchema && this.indicatorUtils.checkForSchemaEnhancements(url); + } + + migrateFromOldImportJsonFile(charts) { + // first section contains numbers + // second contains charts + let hasNumbers = false; + for (let chart of charts) { + if (chart['type'] == 'number') { + hasNumbers = true; + break; + } + } + let chartsSection = (hasNumbers ? 1 : 0); // no numbers: first sections contains charts + for (let chart of charts) { + if (chart['sectionIndex'] == null) { + chart['sectionIndex'] = chart['type'] == 'chart' ? chartsSection : 0; + } + } + return charts; + } + + importIndicatorsAndSave(charts: any[]) { + let sectionsToSave: Section[] = []; + let countIndicators = 0; + // name description additionalDescription, height, width, visibility + let noValidParams = 0; + let duplicates = 0; + charts = this.migrateFromOldImportJsonFile(charts); + for (let chart of charts) { + if (!sectionsToSave[chart['sectionIndex']]) { + let sectionToSave = new Section(chart['sectionType'] ? chart['sectionType'] : chart['type'], chart['sectionTitle']); + sectionToSave.indicators = []; + sectionsToSave[chart['sectionIndex']] = sectionToSave; + } + let exists = false; + let indicatorPath; + // validate indicators' schema from file + let invalid_file_message; + if (!chart.type) { + invalid_file_message = "No indicator type is specified. Type should be chart or number."; + } else if (chart.type != "chart" && chart.type != "number") { + invalid_file_message = "Invalid indicator type. Type should be chart or number."; + } else if (chart.type == "number" && !chart.jsonPath) { + invalid_file_message = "No jsonPath is specified for number indicator." + } else if (!chart.url) { + invalid_file_message = "No indicator url is specified."; + } + + if (invalid_file_message) { + UIkit.notification(invalid_file_message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + break; + } + + if (chart.type == "chart") { + indicatorPath = this.indicatorUtils.generateIndicatorByChartUrl(this.indicatorUtils.getChartSource(chart.url), chart.url, chart.type, this.stakeholder); + for (let section of this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].charts) { + for (let chart of section.indicators) { + if (JSON.stringify(chart.indicatorPaths[0].chartObject) == JSON.stringify(indicatorPath.chartObject)) { + duplicates++; + exists = true; + } + } + + } + } else if (chart.type == "number") { + indicatorPath = this.indicatorUtils.generateIndicatorByNumberUrl(this.indicatorUtils.getNumberSource(chart.url), chart.url, this.stakeholder, + chart.jsonPath, this.indicatorUtils.numberSources.get(this.indicatorUtils.getNumberSource(chart.url))); + for (let section of this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].numbers) { + for (let chart of section.indicators) { + if (JSON.stringify(chart.indicatorPaths[0].chartObject) == JSON.stringify(indicatorPath.chartObject)) { + duplicates++; + exists = true; + } + } + + } + } + if (!this.isStakeholderParametersValid(indicatorPath)) { + noValidParams++; + } + if (!exists) { + let i: Indicator = new Indicator(chart.name, chart.description, chart.additionalDescription, chart.type, chart.width, chart.height, "RESTRICTED", [indicatorPath]); + sectionsToSave[chart['sectionIndex']].indicators.push(i); + countIndicators++; + } + + } + + if (duplicates > 0) { + UIkit.notification(duplicates + " urls already exist and will not be imported!", { + status: 'warning', + timeout: 6000, + pos: 'bottom-right' + }); + } + if (noValidParams > 0) { + let noValidMessage = "Some indicators couldn't be generated properly. Please make sure chart data is for the current stakeholder."; + if (this.stakeholder.defaultId == null) { + noValidMessage = "Some indicators couldn't be generated properly. Stakeholders based on this profile may not inherit the data correctly."; + } + UIkit.notification(noValidMessage, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + } else if (sectionsToSave.length > 0 && countIndicators > 0) { + this.saveIndicators(sectionsToSave.filter(section => !!section)); + } + if (sectionsToSave.length == 0 || countIndicators == 0) { + UIkit.notification(" No urls imported!", { + status: 'warning', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + } + } + + public exportIndicators(subcategoryIndex) { + this.editing = true; + let indicators = []; + let index: number = 0; + let indexIndicator: number = 0; + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[subcategoryIndex].numbers.forEach(section => { + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + indicators[indexIndicator] = { + "type": indicator.type, "name": indicator.name, "jsonPath": indicatorPath.jsonPath, + "description": indicator.description, "additionalDescription": indicator.additionalDescription, + "visibility": indicator.visibility, "width": indicator.width, "height": indicator.height, + "url": this.indicatorUtils.getNumberUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath)), + "sectionTitle": section.title, + "sectionType": section.type, + "sectionIndex": index + }; + indexIndicator++; + }); + }); + index++; + }); + + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[subcategoryIndex].charts.forEach(section => { + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + indicators[indexIndicator] = { + "type": indicator.type, "name": indicator.name, + "description": indicator.description, "additionalDescription": indicator.additionalDescription, + "visibility": indicator.visibility, "width": indicator.width, "height": indicator.height, + "url": this.getUrlByStakeHolder(indicatorPath), + "sectionTitle": section.title, + "sectionType": section.type, + "sectionIndex": index + }; + indexIndicator++; + }); + }); + index++; + + }); + + let topic = this.stakeholder ? this.stakeholder.topics[this.topicIndex] : null; + let category = topic ? topic.categories[this.categoryIndex] : null; + let subCategory = category ? category.subCategories[this.subcategoryIndex] : null; + + var jsonFileUrl = window.URL.createObjectURL(new Blob([JSON.stringify(indicators)], {type: 'application/json'})); + var a = window.document.createElement('a'); + window.document.body.appendChild(a); + a.setAttribute('style', 'display: none'); + a.href = jsonFileUrl; + a.download = this.stakeholder.alias + "_" + topic.alias + "_" + category.alias + "_" + subCategory.alias + ".json"; + a.click(); + window.URL.revokeObjectURL(jsonFileUrl); + a.remove(); // remove the element + + this.editing = false; + } + + fileChangeEvent(fileInput: any, index) { + this.index = index; + this.editing = true; + this.importLoading = true; + this.filesToUpload = >fileInput.target.files; + this.upload(); + } + + upload() { + if (this.filesToUpload.length == 0) { + console.error("There is no selected file to upload."); + UIkit.notification("There is no selected file to upload.", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + return; + } else { + if (this.filesToUpload[0].name.indexOf(".json") == -1 || (this.filesToUpload[0].type != "application/json")) { + console.error("No valid file type. The required type is JSON"); + UIkit.notification("No valid file type. The required type is JSON", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + return; + } + } + + this.makeFileRequest(this.properties.utilsService + '/upload?type=json', [], this.filesToUpload).then(async (result: string) => { + + let json_result = JSON.parse(result); + + // validate file + if (!json_result || json_result.length == 0) { + UIkit.notification("Importing file is empty", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + } else { + this.importIndicatorsAndSave(json_result); + } + }, (error) => { + console.error("Error importing files", error); + UIkit.notification("Error importing files", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + }); + } + + makeFileRequest(url: string, params: Array, files: Array) { + return new Promise((resolve, reject) => { + const formData: any = new FormData(); + const xhr = new XMLHttpRequest(); + for (let i = 0; i < files.length; i++) { + formData.append("uploads[]", files[i], files[i].name); + } + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + resolve(xhr.response); + } else { + reject(xhr.response); + } + } + }; + xhr.open("POST", url, true); + xhr.send(formData); + }); + } + + copyToClipboard(value) { + const tempBox = document.createElement('textarea'); + tempBox.style.position = 'fixed'; + tempBox.style.left = '0'; + tempBox.style.top = '0'; + tempBox.style.opacity = '0'; + tempBox.value = value; + document.body.appendChild(tempBox); + tempBox.focus(); + tempBox.select(); + tempBox.setSelectionRange(0, 99999); + document.execCommand('copy'); + document.body.removeChild(tempBox); + NotificationHandler.rise('Copied to clipboard'); + } +} diff --git a/monitor-admin/topic/topic-routing.module.ts b/monitor-admin/topic/topic-routing.module.ts new file mode 100644 index 00000000..bccc637d --- /dev/null +++ b/monitor-admin/topic/topic-routing.module.ts @@ -0,0 +1,19 @@ +import {NgModule} from '@angular/core'; +import {RouterModule} from '@angular/router'; +import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard'; +import {TopicComponent} from "./topic.component"; +import {CanExitGuard} from "../../utils/can-exit.guard"; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + component: TopicComponent, + canDeactivate: [PreviousRouteRecorder, CanExitGuard] + } + ]) + ] +}) +export class TopicRoutingModule { +} diff --git a/monitor-admin/topic/topic.component.html b/monitor-admin/topic/topic.component.html new file mode 100644 index 00000000..2e5c7ab1 --- /dev/null +++ b/monitor-admin/topic/topic.component.html @@ -0,0 +1,380 @@ + + + +
+
+ +
+ You are about to delete "{{element.name}}" {{type}} permanently. +
+ {{getPluralTypeName()}} of all profiles based on this default {{type}}, will be deleted as well. +
+ Are you sure you want to proceed? +
+
+ +
+ +
+
+
+
+
+
+
+
+ +
+ You have the option to change the visibility status of your {{type}}, with or without applying the changed status to + its contents. +
+
+ +
+
+ +
+
+

Note:

+
+ The status of the {{type}} prevails the status of its contents. + For example, if a {{type}}'s status is private, while it has + a category, subcategory or an indicator + a subcategory or an indicator + an indicator + that is public, the private status of the {{type}} dominates. +
+
+
diff --git a/monitor-admin/topic/topic.component.ts b/monitor-admin/topic/topic.component.ts new file mode 100644 index 00000000..c9d6f7ff --- /dev/null +++ b/monitor-admin/topic/topic.component.ts @@ -0,0 +1,795 @@ +import { + AfterViewInit, + ChangeDetectorRef, + Component, Inject, + OnDestroy, + OnInit, PLATFORM_ID, + QueryList, + ViewChild, + ViewChildren +} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; +import {Title} from '@angular/platform-browser'; +import {EnvProperties} from '../../utils/properties/env-properties'; +import {Category, Stakeholder, SubCategory, Topic, Visibility} from "../../monitor/entities/stakeholder"; +import {StakeholderService} from "../../monitor/services/stakeholder.service"; +import {HelperFunctions} from "../../utils/HelperFunctions.class"; +import {AlertModal} from "../../utils/modal/alert"; +import {BehaviorSubject, Subject, Subscriber, Subscription} from "rxjs"; +import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms"; +import {StakeholderUtils} from "../utils/indicator-utils"; +import {StringUtils} from "../../utils/string-utils.class"; +import {IDeactivateComponent} from "../../utils/can-exit.guard"; +import {LayoutService} from "../../dashboard/sharedComponents/sidebar/layout.service"; +import {Option} from "../../sharedComponents/input/input.component"; +import {properties} from "src/environments/environment"; +import {Session, User} from "../../login/utils/helper.class"; +import {UserManagementService} from "../../services/user-management.service"; +import {TransitionGroupComponent} from "../../utils/transition-group/transition-group.component"; +import {NotificationHandler} from "../../utils/notification-handler"; + +declare var UIkit; + +@Component({ + selector: 'topic', + templateUrl: './topic.component.html', +}) +export class TopicComponent implements OnInit, OnDestroy, AfterViewInit, IDeactivateComponent { + private topicSubscriptions: any[] = []; + private subscriptions: any[] = []; + public properties: EnvProperties = properties; + public stakeholderUtils: StakeholderUtils = new StakeholderUtils(); + public loading: boolean = false; + public stakeholder: Stakeholder; + public user: User; + /** + * Stakeholder change event + * */ + public change: Subject = new Subject(); + /** + * Current topic + **/ + public topicIndexSubject: BehaviorSubject = new BehaviorSubject(0); + public topicIndex: number = 0; + /** + * Current category + */ + public categoryIndexSubject: BehaviorSubject = new BehaviorSubject(0); + public categoryIndex: number = 0; + /** + * Current Subcategory + */ + public subCategoryIndexSubject: BehaviorSubject = new BehaviorSubject(0); + public subCategoryIndex: number = 0; + /** + * Current element and index of topic, category or subcategory to be deleted. + */ + public form: UntypedFormGroup; + public element: Topic | Category | SubCategory; + public type: 'topic' | 'category' | 'subcategory' = "topic"; + public index: number = -1; + public visibility: Visibility; + + @ViewChild('deleteModal', {static: true}) deleteModal: AlertModal; + @ViewChild('editModal', {static: true}) editModal: AlertModal; + @ViewChild('visibilityModal', {static: true}) visibilityModal: AlertModal; + @ViewChildren(TransitionGroupComponent) transitions: QueryList; + + public elementChildrenActionOnDelete: string; + public filters: UntypedFormGroup; + public all: Option = { + value: 'all', + label: 'All' + }; + + constructor( + private route: ActivatedRoute, + private router: Router, + private title: Title, + private fb: UntypedFormBuilder, + private stakeholderService: StakeholderService, + private userManagementService: UserManagementService, + private layoutService: LayoutService, + private cdr: ChangeDetectorRef, + @Inject(PLATFORM_ID) private platformId) { + } + + public ngOnInit() { + let subscription: Subscription; + this.subscriptions.push(this.topicIndexSubject.asObservable().subscribe(index => { + this.topicChanged(() => { + this.topicIndex = index; + }); + })); + this.subscriptions.push(this.categoryIndexSubject.asObservable().subscribe(index => { + this.categoryChanged(() => { + this.categoryIndex = index; + }); + })); + this.subscriptions.push(this.subCategoryIndexSubject.asObservable().subscribe(index => { + this.subCategoryChanged(() => { + this.subCategoryIndex = index; + }); + })); + this.subscriptions.push(this.route.params.subscribe(params => { + if (subscription) { + subscription.unsubscribe(); + } + subscription = this.stakeholderService.getStakeholderAsObservable().subscribe(stakeholder => { + if (stakeholder) { + this.stakeholder = stakeholder; + if (params['topic']) { + this.chooseTopic(this.stakeholder.topics.findIndex(topic => topic.alias === params['topic'])); + } else { + this.chooseTopic(0); + } + this.chooseCategory(0); + this.filters = this.fb.group({ + chartType: this.fb.control('all'), + status: this.fb.control('all'), + keyword: this.fb.control('') + }); + if (this.topicIndex === -1) { + this.navigateToError(); + } else { + this.title.setTitle(stakeholder.name + " | Indicators"); + } + } + }); + this.topicSubscriptions.push(subscription); + })); + this.topicSubscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + this.user = user; + })) + } + + ngAfterViewInit() { + if(this.topics) { + let activeIndex = UIkit.nav(this.topics.element.nativeElement).items.findIndex(item => item.classList.contains('uk-open')); + if(activeIndex !== this.topicIndex) { + setTimeout(() => { + UIkit.nav(this.topics.element.nativeElement).toggle(this.topicIndex, true); + }); + } + } + } + + get isBrowser() { + return this.platformId === 'browser'; + } + + public ngOnDestroy() { + this.topicSubscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.subscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + } + + canExit(): boolean { + this.topicSubscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.stakeholderService.setStakeholder(this.stakeholder); + return true; + } + + private findById(id: string) { + return this.transitions?this.transitions.find(item => item.id === id):null; + } + + get topics(): TransitionGroupComponent { + return this.findById('topics'); + } + + get categories(): TransitionGroupComponent { + return this.findById('categories-' + this.topicIndex); + } + + get subCategories(): TransitionGroupComponent { + return this.findById('subCategories'); + } + + hide(element: any) { + UIkit.dropdown(element).hide(); + } + + stakeholderChanged() { + this.change.next(); + } + + public saveElement() { + if (this.type === "topic") { + this.saveTopic(); + } else if (this.type === "category") { + this.saveCategory(); + } else { + this.saveSubCategory(); + } + } + + public deleteElement() { + if (this.type === "topic") { + this.deleteTopic(); + } else if (this.type === "category") { + this.deleteCategory(); + } else { + this.deleteSubcategory(); + } + } + + public changeElementStatus(propagate: boolean = false) { + if (this.type === "topic") { + this.changeTopicStatus(propagate); + } else if (this.type === "category") { + this.changeCategoryStatus(propagate); + } else { + this.changeSubcategoryStatus(propagate); + } + } + + public chooseTopic(topicIndex: number) { + this.topicIndexSubject.next(topicIndex); + } + + topicChanged(callback: Function, save: boolean = false) { + if(this.topics && save) { + this.topics.disable(); + } + if(this.categories) { + this.categories.disable(); + } + if(this.subCategories) { + this.subCategories.disable(); + } + if(callback) { + callback(); + } + this.cdr.detectChanges(); + if(this.topics && save) { + this.topics.init(); + this.topics.enable(); + } + if(this.categories) { + this.categories.init(); + this.categories.enable(); + } + if(this.subCategories) { + this.subCategories.init(); + this.subCategories.enable(); + } + } + + private buildTopic(topic: Topic) { + let topics = this.stakeholder.topics.filter(element => element._id !== topic._id); + this.form = this.fb.group({ + _id: this.fb.control(topic._id), + name: this.fb.control(topic.name, Validators.required), + description: this.fb.control(topic.description), + creationDate: this.fb.control(topic.creationDate), + alias: this.fb.control(topic.alias, [ + Validators.required, + this.stakeholderUtils.aliasValidator(topics) + ] + ), + visibility: this.fb.control(topic.visibility), + defaultId: this.fb.control(topic.defaultId), + categories: this.fb.control(topic.categories), + icon: this.fb.control(topic.icon) + }); + this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => { + let i = 1; + value = this.stakeholderUtils.generateAlias(value); + this.form.controls['alias'].setValue(value); + while (this.form.get('alias').invalid) { + this.form.controls['alias'].setValue(value + i); + i++; + } + })); + } + + public editTopicOpen(index = -1) { + this.index = index; + this.type = 'topic'; + if (index === -1) { + this.buildTopic(new Topic(null, null, null, "PUBLIC")); + } else { + this.buildTopic(this.stakeholder.topics[index]); + } + this.editOpen(); + } + + public saveTopic() { + if (!this.form.invalid) { + let path = [this.stakeholder._id]; + let callback = (topic: Topic): void => { + this.topicChanged(() => { + if (this.index === -1) { + this.stakeholder.topics.push(topic); + } else { + this.stakeholder.topics[this.index] = HelperFunctions.copy(topic); + } + }, true); + }; + if (this.index === -1) { + this.save('Topic has been successfully created', path, this.form.value, callback); + } else { + this.save('Topic has been successfully saved', path, this.form.value, callback); + } + } + } + + public changeTopicStatus(propagate: boolean = false) { + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.index]._id + ]; + let callback = (topic: Topic): void => { + this.topicChanged(() => { + this.stakeholder.topics[this.index] = HelperFunctions.copy(topic); + }, true); + } + this.changeStatus(this.stakeholder.topics[this.index], path, this.visibility, callback, propagate); + this.visibilityModal.cancel(); + } + + public deleteTopicOpen(index = this.topicIndex, childrenAction: string = null) { + this.type = 'topic'; + this.index = index; + this.element = this.stakeholder.topics[this.index]; + this.deleteOpen(childrenAction); + } + + public deleteTopic() { + let path: string[] = [ + this.stakeholder._id, + this.stakeholder.topics[this.index]._id + ]; + let callback = (): void => { + this.topicChanged(() => { + this.stakeholder.topics.splice(this.index, 1); + if(this.topicIndex === this.index) { + this.chooseTopic(Math.max(0, this.index - 1)); + } + }, true); + }; + this.delete('Topic has been successfully be deleted', path, callback, (this.topicIndex === this.index)); + } + + public moveTopic(index: number, newIndex: number = index - 1) { + this.topics.init(); + let path = [this.stakeholder._id]; + let ids = this.stakeholder.topics.map(topic => topic._id); + HelperFunctions.swap(ids, index, newIndex); + this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => { + HelperFunctions.swap(this.stakeholder.topics, index, newIndex); + if(this.topicIndex === index) { + this.chooseTopic(newIndex); + } else if(this.topicIndex === newIndex) { + this.chooseTopic(index); + } + }, error => { + NotificationHandler.rise(error.error.message) + }); + } + + public chooseCategory(index: number) { + this.categoryIndexSubject.next(index); + this.chooseSubcategory(0); + } + + categoryChanged(callback: Function, save: boolean = false) { + if(this.categories && save) { + this.categories.disable(); + } + if(this.subCategories) { + this.subCategories.disable(); + } + if(callback) { + callback(); + } + this.cdr.detectChanges(); + if(this.categories && save) { + this.categories.init(); + this.categories.enable(); + } + if(this.subCategories) { + this.subCategories.init(); + this.subCategories.enable(); + } + } + + private buildCategory(category: Category) { + let categories = this.stakeholder.topics[this.topicIndex].categories.filter(element => element._id !== category._id); + this.form = this.fb.group({ + _id: this.fb.control(category._id), + name: this.fb.control(category.name, Validators.required), + description: this.fb.control(category.description), + creationDate: this.fb.control(category.creationDate), + alias: this.fb.control(category.alias, [ + Validators.required, + this.stakeholderUtils.aliasValidator(categories) + ] + ), + visibility: this.fb.control(category.visibility), + defaultId: this.fb.control(category.defaultId), + subCategories: this.fb.control(category.subCategories) + }); + this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => { + let i = 1; + value = this.stakeholderUtils.generateAlias(value); + this.form.controls['alias'].setValue(value); + while (this.form.get('alias').invalid) { + this.form.controls['alias'].setValue(value + i); + i++; + } + })); + } + + public editCategoryOpen(index: number = -1) { + this.index = index; + this.type = 'category'; + if (index === -1) { + this.buildCategory(new Category(null, null, null, "PUBLIC")); + } else { + this.buildCategory(this.stakeholder.topics[this.topicIndex].categories[index]); + } + this.editOpen(); + } + + public saveCategory() { + if (!this.form.invalid) { + let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id]; + let callback = (category: Category): void => { + this.categoryChanged(() => { + if (this.index === -1) { + this.stakeholder.topics[this.topicIndex].categories.push(category); + this.categories.init(); + } else { + this.stakeholder.topics[this.topicIndex].categories[this.index] = HelperFunctions.copy(category); + } + }, true); + }; + if (this.index === -1) { + this.save('Category has been successfully created', path, this.form.value, callback); + } else { + this.save('Category has been successfully saved', path, this.form.value, callback); + } + } + } + + public changeCategoryStatus(propagate: boolean = false) { + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.index]._id + ]; + let callback = (category: Category): void => { + this.categoryChanged(() => { + this.stakeholder.topics[this.topicIndex].categories[this.index] = HelperFunctions.copy(category); + }, true); + } + this.changeStatus(this.stakeholder.topics[this.topicIndex].categories[this.index], path, this.visibility, callback, propagate); + this.visibilityModal.cancel(); + } + + public deleteCategoryOpen(index: number, childrenAction: string = null) { + this.type = 'category'; + this.index = index; + this.element = this.stakeholder.topics[this.topicIndex].categories[this.index]; + this.deleteOpen(childrenAction); + } + + public deleteCategory() { + let path: string[] = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.index]._id + ]; + let callback = (): void => { + this.categoryChanged(() => { + this.stakeholder.topics[this.topicIndex].categories.splice(this.index, 1); + if(this.categoryIndex === this.index) { + this.chooseCategory(Math.max(0, this.index - 1)); + } + }, true); + }; + this.delete('Category has been successfully be deleted', path, callback); + } + + public moveCategory(index: number, newIndex: number = index - 1) { + this.categories.init(); + let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id]; + let ids = this.stakeholder.topics[this.topicIndex].categories.map(category => category._id); + HelperFunctions.swap(ids, index, newIndex); + this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => { + HelperFunctions.swap(this.stakeholder.topics[this.topicIndex].categories, index, newIndex); + if(this.categoryIndex === index) { + this.chooseCategory(newIndex); + } else if(this.categoryIndex === newIndex) { + this.chooseCategory(index); + } + }, error => { + NotificationHandler.rise(error.error.message) + }); + } + + chooseSubcategory(subcategoryIndex: number) { + this.subCategoryIndexSubject.next(subcategoryIndex); + } + + subCategoryChanged(callback: Function, save: boolean = false) { + if(this.subCategories && save) { + this.subCategories.disable(); + } + if(callback) { + callback(); + } + this.cdr.detectChanges(); + if(this.subCategories && save) { + this.subCategories.init(); + this.subCategories.enable(); + } + } + + private buildSubcategory(subCategory: SubCategory) { + let subCategories = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.filter(element => element._id !== subCategory._id); + this.form = this.fb.group({ + _id: this.fb.control(subCategory._id), + name: this.fb.control(subCategory.name, Validators.required), + description: this.fb.control(subCategory.description), + creationDate: this.fb.control(subCategory.creationDate), + alias: this.fb.control(subCategory.alias, [ + Validators.required, + this.stakeholderUtils.aliasValidator(subCategories) + ] + ), + visibility: this.fb.control(subCategory.visibility), + defaultId: this.fb.control(subCategory.defaultId), + charts: this.fb.control(subCategory.charts), + numbers: this.fb.control(subCategory.numbers) + }); + this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => { + let i = 1; + value = this.stakeholderUtils.generateAlias(value); + this.form.controls['alias'].setValue(value); + while (this.form.get('alias').invalid) { + this.form.controls['alias'].setValue(value + i); + i++; + } + })); + } + + public editSubCategoryOpen(index: number = -1) { + this.index = index; + this.type = 'subcategory'; + if (index === -1) { + this.buildSubcategory(new SubCategory(null, null, null, "PUBLIC")); + } else { + this.buildSubcategory(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[index]); + } + this.editOpen(); + } + + public saveSubCategory() { + if (!this.form.invalid) { + let path: string[] = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id + ]; + let callback = (subCategory: SubCategory): void => { + this.subCategoryChanged(() => { + if (this.index === -1) { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.push(subCategory); + } else { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index] = HelperFunctions.copy(subCategory); + } + }, true); + }; + if (this.index === -1) { + this.save('Subcategory has been successfully created', path, this.form.value, callback); + } else { + this.save('Subcategory has been successfully saved', path, this.form.value, callback); + } + } + } + + public changeSubcategoryStatus(propagate: boolean = false) { + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id + ]; + let callback = (subcategory: SubCategory): void => { + this.subCategoryChanged(() => { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index] = HelperFunctions.copy(subcategory); + }, true); + } + this.changeStatus(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index], path, this.visibility, callback, propagate); + this.visibilityModal.cancel(); + } + + + public deleteSubcategoryOpen(index, childrenAction: string = null) { + this.type = 'subcategory'; + this.index = index; + this.element = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]; + this.deleteOpen(childrenAction); + } + + public deleteSubcategory() { + let path: string[] = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id + ]; + let callback = (): void => { + this.subCategoryChanged(() => { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.splice(this.index, 1); + if(this.subCategoryIndex === this.index) { + this.chooseSubcategory(Math.max(0, this.index - 1)); + } + }, true); + }; + this.delete('Subcategory has been successfully be deleted', path, callback); + } + + public moveSubCategory(index: number, newIndex: number = index - 1) { + this.subCategories.init(); + let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id]; + let ids = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.map(subCategory => subCategory._id); + HelperFunctions.swap(ids, index, newIndex); + this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => { + HelperFunctions.swap(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories, index, newIndex); + if(this.subCategoryIndex === index) { + this.chooseSubcategory(newIndex); + } else if(this.subCategoryIndex === newIndex) { + this.chooseSubcategory(index); + } + }, error => { + NotificationHandler.rise(error.error.message) + }); + } + + private navigateToError() { + this.router.navigate([this.properties.errorLink], {queryParams: {'page': this.router.url}}); + } + + get isCurator(): boolean { + return Session.isPortalAdministrator(this.user) || Session.isCurator(this.stakeholder.type, this.user); + } + + private editOpen() { + this.editModal.cancelButtonText = 'Cancel'; + this.editModal.okButtonLeft = false; + this.editModal.alertMessage = false; + if (this.index === -1) { + this.editModal.alertTitle = 'Create a new ' + this.type; + this.editModal.okButtonText = 'Create'; + } else { + this.editModal.alertTitle = 'Edit ' + this.type + '\'s information '; + this.editModal.okButtonText = 'Save'; + } + this.editModal.stayOpen = true; + this.editModal.open(); + } + + private deleteOpen(childrenAction: string = null) { + this.elementChildrenActionOnDelete = null; + if (childrenAction == "delete") { + this.elementChildrenActionOnDelete = childrenAction; + } else if (childrenAction == "disconnect") { + this.elementChildrenActionOnDelete = childrenAction; + } + + this.deleteModal.alertTitle = 'Delete ' + this.type; + this.deleteModal.cancelButtonText = 'No'; + this.deleteModal.okButtonText = 'Yes'; + // this.deleteModal.cancelButton = false; + // this.deleteModal.okButton = false; + this.deleteModal.stayOpen = true; + this.deleteModal.open(); + } + + private save(message: string, path: string[], saveElement: any, callback: Function, redirect = false) { + this.loading = true; + this.topicSubscriptions.push(this.stakeholderService.saveElement(this.properties.monitorServiceAPIURL, saveElement, path).subscribe(saveElement => { + callback(saveElement); + this.stakeholderChanged(); + this.loading = false; + this.editModal.cancel(); + NotificationHandler.rise(message); + if (redirect) { + this.router.navigate(['../' + saveElement.alias], { + relativeTo: this.route + }); + } + }, error => { + this.loading = false; + this.editModal.cancel(); + NotificationHandler.rise(error.error.message, 'danger'); + })); + } + + private delete(message: string, path: string[], callback: Function, redirect = false) { + this.loading = true; + this.topicSubscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, path, this.elementChildrenActionOnDelete).subscribe(() => { + callback(); + this.stakeholderChanged(); + this.loading = false; + this.deleteModal.cancel(); + NotificationHandler.rise(message); + if (redirect) { + this.back(); + } + }, error => { + this.loading = false; + this.deleteModal.cancel(); + NotificationHandler.rise(error.error.message, 'danger'); + })); + } + + private changeStatus(element: Topic | Category | SubCategory, path: string[], visibility: Visibility, callback: Function = null, propagate: boolean = false) { + this.topicSubscriptions.push(this.stakeholderService.changeVisibility(this.properties.monitorServiceAPIURL, path, visibility, propagate).subscribe(returnedElement => { + if(propagate) { + callback(returnedElement); + NotificationHandler.rise(StringUtils.capitalize(this.type) + ' has been successfully changed to ' + returnedElement.visibility.toLowerCase()); + } else { + element.visibility = returnedElement.visibility; + NotificationHandler.rise(StringUtils.capitalize(this.type) + ' has been successfully changed to ' + element.visibility.toLowerCase()); + } + }, error => { + NotificationHandler.rise(error.error.message, 'danger'); + })); + } + + back() { + this.router.navigate(['../'], { + relativeTo: this.route + }); + } + + public getPluralTypeName(): string { + if (this.type == "topic") { + return "Topics"; + } else if (this.type == "category") { + return "Categories"; + } else if (this.type == "subcategory") { + return "Subcategories"; + } else { + return this.type; + } + } + + public get isSmallScreen() { + return this.layoutService.isSmallScreen; + } + + public get open() { + return this.layoutService.open; + } + + public toggleOpen(event: MouseEvent) { + event.preventDefault(); + this.layoutService.setOpen(!this.open); + } + + public openVisibilityModal(index: number, visibility: Visibility, type: any) { + this.index = index; + this.visibility = visibility; + this.type = type; + this.visibilityModal.alertTitle = 'Visibility Status'; + this.visibilityModal.alertFooter = false; + this.visibilityModal.open(); + } +} diff --git a/monitor-admin/topic/topic.module.ts b/monitor-admin/topic/topic.module.ts new file mode 100644 index 00000000..748b6572 --- /dev/null +++ b/monitor-admin/topic/topic.module.ts @@ -0,0 +1,49 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; + +import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard'; + +import {PiwikService} from '../../utils/piwik/piwik.service'; +import {TopicComponent} from "./topic.component"; +import {TopicRoutingModule} from "./topic-routing.module"; +import {RouterModule} from "@angular/router"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {IndicatorsComponent} from "./indicators.component"; +import {AlertModalModule} from "../../utils/modal/alertModal.module"; +import {InputModule} from "../../sharedComponents/input/input.module"; +import {ClickModule} from "../../utils/click/click.module"; +import {IconsService} from "../../utils/icons/icons.service"; +import {earth, incognito, restricted} from "../../utils/icons/icons"; +import {IconsModule} from "../../utils/icons/icons.module"; +import {PageContentModule} from "../../dashboard/sharedComponents/page-content/page-content.module"; +import {LoadingModule} from "../../utils/loading/loading.module"; +import {NotifyFormModule} from "../../notifications/notify-form/notify-form.module"; +import {LogoUrlPipeModule} from "../../utils/pipes/logoUrlPipe.module"; +import {TransitionGroupModule} from "../../utils/transition-group/transition-group.module"; +import {NumberRoundModule} from "../../utils/pipes/number-round.module"; +import {SideBarModule} from "../../dashboard/sharedComponents/sidebar/sideBar.module"; +import { + SidebarMobileToggleModule +} from "../../dashboard/sharedComponents/sidebar/sidebar-mobile-toggle/sidebar-mobile-toggle.module"; + +@NgModule({ + imports: [ + CommonModule, TopicRoutingModule, ClickModule, RouterModule, FormsModule, AlertModalModule, + ReactiveFormsModule, InputModule, IconsModule, PageContentModule, LoadingModule, NotifyFormModule, LogoUrlPipeModule, TransitionGroupModule, NumberRoundModule, SideBarModule, SidebarMobileToggleModule + ], + declarations: [ + TopicComponent, IndicatorsComponent + ], + providers: [ + PreviousRouteRecorder, + PiwikService + ], + exports: [ + TopicComponent + ] +}) +export class TopicModule { + constructor(private iconsService: IconsService) { + this.iconsService.registerIcons([earth, incognito, restricted]); + } +} diff --git a/monitor-admin/utils/adminDashboard.guard.ts b/monitor-admin/utils/adminDashboard.guard.ts new file mode 100644 index 00000000..664fa7d3 --- /dev/null +++ b/monitor-admin/utils/adminDashboard.guard.ts @@ -0,0 +1,43 @@ +import {Injectable} from '@angular/core'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; +import {map, take, tap} from "rxjs/operators"; +import {UserManagementService} from "../../services/user-management.service"; +import {LoginErrorCodes} from "../../login/utils/guardHelper.class"; +import {Session} from "../../login/utils/helper.class"; +import {StakeholderService} from "../../monitor/services/stakeholder.service"; +import {Observable, zip} from "rxjs"; + + +@Injectable() +export class AdminDashboardGuard { + + constructor(private router: Router, + private stakeholderService: StakeholderService, + private userManagementService: UserManagementService) { + } + + check(path: string, alias: string): Observable | boolean { + let errorCode = LoginErrorCodes.NOT_LOGIN; + return zip( + this.userManagementService.getUserInfo(), this.stakeholderService.getStakeholder(alias) + ).pipe(take(1),map(res => { + if(res[0]) { + errorCode = LoginErrorCodes.NOT_ADMIN; + } + return res[0] && res[1] && (Session.isPortalAdministrator(res[0]) || + Session.isCurator(res[1].type, res[0]) || Session.isManager(res[1].type, res[1].alias, res[0])) + }),tap(authorized => { + if(!authorized){ + this.router.navigate(['/user-info'], {queryParams: {'errorCode': errorCode, 'redirectUrl':path}}); + } + })); + } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + return this.check(state.url, route.params.stakeholder); + } + + canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + return this.check(state.url, childRoute.params.stakeholder); + } +} diff --git a/monitor-admin/utils/cache-indicators/cache-indicators.component.less b/monitor-admin/utils/cache-indicators/cache-indicators.component.less new file mode 100644 index 00000000..08d5c2a9 --- /dev/null +++ b/monitor-admin/utils/cache-indicators/cache-indicators.component.less @@ -0,0 +1,9 @@ +@import (reference) "~src/assets/openaire-theme/less/_import-variables.less"; + +.cache-progress { + position: fixed; + bottom: 0; + right: 0; + transform: translate(-50%, -50%); + z-index: @global-z-index; +} diff --git a/monitor-admin/utils/cache-indicators/cache-indicators.component.ts b/monitor-admin/utils/cache-indicators/cache-indicators.component.ts new file mode 100644 index 00000000..8f2211aa --- /dev/null +++ b/monitor-admin/utils/cache-indicators/cache-indicators.component.ts @@ -0,0 +1,78 @@ +import {Component, Inject, Input, OnChanges, OnDestroy, OnInit, PLATFORM_ID, SimpleChanges} from "@angular/core"; +import {Report} from "./cache-indicators"; +import {CacheIndicatorsService} from "./cache-indicators.service"; +import {interval, Subject, Subscription} from "rxjs"; +import {map, switchMap, takeUntil} from "rxjs/operators"; + +@Component({ + selector: 'cache-indicators', + template: ` +
+
+
+ +
+
+ `, + styleUrls: ['cache-indicators.component.less'] +}) +export class CacheIndicatorsComponent implements OnInit, OnChanges, OnDestroy { + report: Report; + subscriptions: Subscription[] = []; + interval: number = 10000; + readonly destroy$ = new Subject(); + @Input() alias: string; + + constructor(private cacheIndicatorsService: CacheIndicatorsService, + @Inject(PLATFORM_ID) private platformId) { + } + + ngOnInit() { + this.getReport(); + } + + ngOnChanges(changes: SimpleChanges) { + if(changes.alias) { + this.getReport(); + } + } + + getReport() { + this.clear(); + this.subscriptions.push(this.cacheIndicatorsService.getReport(this.alias).subscribe(report => { + this.getReportInterval(report); + })); + } + + getReportInterval(report: Report) { + if(this.isBrowser && (this.report || !report?.completed)) { + this.report = report; + this.subscriptions.push(interval(this.interval).pipe( + map(() => this.cacheIndicatorsService.getReport(this.alias)), + switchMap(report => report), + takeUntil(this.destroy$)).subscribe(report => { + console.log(this.alias); + this.report = report; + if(this.report.completed) { + this.destroy$.next(); + } + })); + } + } + + clear() { + this.subscriptions.forEach(subscription => { + subscription.unsubscribe(); + }) + this.report = null; + } + + + get isBrowser() { + return this.platformId === 'browser'; + } + + ngOnDestroy() { + this.clear(); + } +} diff --git a/monitor-admin/utils/cache-indicators/cache-indicators.module.ts b/monitor-admin/utils/cache-indicators/cache-indicators.module.ts new file mode 100644 index 00000000..15c7200c --- /dev/null +++ b/monitor-admin/utils/cache-indicators/cache-indicators.module.ts @@ -0,0 +1,11 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {CacheIndicatorsComponent} from "./cache-indicators.component"; +import {IconsModule} from "../../../utils/icons/icons.module"; + +@NgModule({ + imports: [CommonModule, IconsModule], + declarations: [CacheIndicatorsComponent], + exports: [CacheIndicatorsComponent] +}) +export class CacheIndicatorsModule {} diff --git a/monitor-admin/utils/cache-indicators/cache-indicators.service.ts b/monitor-admin/utils/cache-indicators/cache-indicators.service.ts new file mode 100644 index 00000000..8919896d --- /dev/null +++ b/monitor-admin/utils/cache-indicators/cache-indicators.service.ts @@ -0,0 +1,24 @@ +import {Injectable} from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {properties} from "src/environments/environment"; +import {CustomOptions} from "../../../services/servicesUtils/customOptions.class"; +import {map} from "rxjs/operators"; + +@Injectable({ + providedIn: 'root' +}) +export class CacheIndicatorsService { + + constructor(private http: HttpClient) { + } + + createReport(alias: string) { + return this.http.post(properties.domain + properties.baseLink + '/cache/' + alias, {}, CustomOptions.registryOptions()) + .pipe(map(res => res.report)); + } + + getReport(alias: string) { + return this.http.get(properties.domain + properties.baseLink + '/cache/' + alias, CustomOptions.registryOptions()) + .pipe(map(res => res.report)); + } +} diff --git a/monitor-admin/utils/cache-indicators/cache-indicators.ts b/monitor-admin/utils/cache-indicators/cache-indicators.ts new file mode 100644 index 00000000..8b7418b3 --- /dev/null +++ b/monitor-admin/utils/cache-indicators/cache-indicators.ts @@ -0,0 +1,261 @@ +import {IndicatorType, Stakeholder} from "../../../monitor/entities/stakeholder"; +import axios from "axios"; +import {IndicatorUtils} from "../indicator-utils"; +import {Composer} from "../../../utils/email/composer"; +import {properties} from "src/environments/environment"; + + +export interface CacheItem { + reportId: string, + type: IndicatorType, + url: string +} + +export class Report { + creator: string; + name: string; + success: number; + errors: { + url: string, + status: number + }[]; + total: number; + completed: boolean; + percentage: number + + constructor(total: number, name: string, creator: string) { + this.creator = creator; + this.name = name; + this.success = 0; + this.errors = []; + this.total = total; + this.completed = false; + } + + setPercentage() { + this.percentage = Math.floor((this.success + this.errors.length) / this.total * 100); + } +} + +export class CacheIndicators { + + private static BATCH_SIZE = 10; + + private reports: Map = new Map(); + private queue: CacheItem[] = []; + private process: Promise; + private isFinished: boolean = true; + + stakeholderToCacheItems(stakeholder: Stakeholder) { + let cacheItems: CacheItem[] = []; + let indicatorUtils = new IndicatorUtils(); + stakeholder.topics.forEach(topic => { + topic.categories.forEach(category => { + category.subCategories.forEach(subCategory => { + subCategory.numbers.forEach(section => { + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + let url = indicatorUtils.getNumberUrl(indicatorPath.source, indicatorUtils.getFullUrl(stakeholder, indicatorPath)); + cacheItems.push({ + reportId: stakeholder._id, + type: 'number', + url: url + }); + }); + }); + }); + subCategory.charts.forEach(section => { + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + let url = indicatorUtils.getChartUrl(indicatorPath.source, indicatorUtils.getFullUrl(stakeholder, indicatorPath)); + cacheItems.push({ + reportId: stakeholder._id, + type: 'chart', + url: url + }); + }); + }); + }); + }); + }); + }); + return cacheItems; + } + + public exists(id: string) { + return this.reports.has(id); + } + + public completed(id: string) { + return !this.exists(id) || this.reports.get(id).completed; + } + + public createReport(id: string, cacheItems: CacheItem[], name: string, creator: string) { + let report = new Report(cacheItems.length, name, creator); + this.reports.set(id, report); + this.addItemsToQueue(cacheItems); + return report; + } + + public getReport(id: string) { + return this.reports.get(id); + } + + private async processQueue() { + this.isFinished = false; + while (this.queue.length > 0) { + let batch = this.queue.splice(0, CacheIndicators.BATCH_SIZE); + await this.processBatch(batch); + } + } + + private async processBatch(batch: CacheItem[]) { + let promises: Promise[] = []; + let ids = new Set(); + batch.forEach(item => { + let promise; + ids.add(item.reportId); + if (item.type === 'chart') { + let [url, json] = item.url.split('?json='); + json = decodeURIComponent(json); + json = statsToolParser(JSON.parse(json)); + promise = axios.post(url, json); + } else { + promise = axios.get(item.url); + } + promises.push(promise.then(response => { + let report = this.reports.get(item.reportId); + if (report) { + report.success++; + report.setPercentage(); + } + return response; + }).catch(error => { + let report = this.reports.get(item.reportId); + if (report) { + report.errors.push({url: item.url, status: error.response.status}); + report.setPercentage(); + } + return error.response; + })); + }); + await Promise.all(promises); + ids.forEach(id => { + let report = this.reports.get(id); + if (report?.percentage === 100) { + report.completed = true; + this.sendEmail(report); + } + }); + } + + private addItemsToQueue(cacheItems: CacheItem[]) { + cacheItems.forEach(item => { + this.queue.push(item); + }); + if (this.isFinished) { + this.processQueue().then(() => { + this.isFinished = true; + }); + } + } + + sendEmail(report: Report) { + let email = Composer.composeEmailToReportCachingProcess(report); + axios.post(properties.adminToolsAPIURL + "sendMail/", email).catch(error => { + console.error(error); + }); + } +} + +export function statsToolParser(dataJSONobj: any): any { + let RequestInfoObj = Object.assign({}); + switch (dataJSONobj.library) { + case "GoogleCharts": + //Pass the Chart library to ChartDataFormatter + RequestInfoObj.library = dataJSONobj.library; + RequestInfoObj.orderBy = dataJSONobj.orderBy; + + //Create ChartInfo Object Array + RequestInfoObj.chartsInfo = []; + //Create ChartInfo and pass the Chart data queries to ChartDataFormatter + //along with the requested Chart type + RequestInfoObj.chartsInfo = dataJSONobj.chartDescription.queriesInfo; + break; + case "eCharts": + //Pass the Chart library to ChartDataFormatter + RequestInfoObj.library = dataJSONobj.library; + RequestInfoObj.orderBy = dataJSONobj.orderBy; + + //Create ChartInfo Object Array + RequestInfoObj.chartsInfo = []; + + //Create ChartInfo and pass the Chart data queries to ChartDataFormatter + //along with the requested Chart type + for (let index = 0; index < dataJSONobj.chartDescription.queries.length; index++) { + let element = dataJSONobj.chartDescription.queries[index]; + var ChartInfoObj = Object.assign({}); + + if (element.type === undefined) + ChartInfoObj.type = dataJSONobj.chartDescription.series[index].type; + else + ChartInfoObj.type = element.type; + + if (element.name === undefined) + ChartInfoObj.name = null; + else + ChartInfoObj.name = element.name; + + ChartInfoObj.query = element.query; + RequestInfoObj.chartsInfo.push(ChartInfoObj); + } + break; + case "HighCharts": + RequestInfoObj.library = dataJSONobj.library; + RequestInfoObj.orderBy = dataJSONobj.orderBy; + //Pass the Chart type to ChartDataFormatter + var defaultType = dataJSONobj.chartDescription.chart.type; + //Create ChartInfo Object Array + RequestInfoObj.chartsInfo = []; + //Create ChartInfo and pass the Chart data queries to ChartDataFormatter + //along with the requested Chart type + dataJSONobj.chartDescription.queries.forEach(element => { + var ChartInfoObj = Object.assign({}); + + if (element.type === undefined) + ChartInfoObj.type = defaultType; + else + ChartInfoObj.type = element.type; + + if (element.name === undefined) + ChartInfoObj.name = null; + else + ChartInfoObj.name = element.name; + + ChartInfoObj.query = element.query; + RequestInfoObj.chartsInfo.push(ChartInfoObj); + }); + break; + case "HighMaps": + RequestInfoObj.library = dataJSONobj.library; + //Create ChartInfo Object Array + RequestInfoObj.chartsInfo = []; + + //Create ChartInfo and pass the Chart data queries to ChartDataFormatter + dataJSONobj.mapDescription.queries.forEach(element => { + var ChartInfoObj = Object.assign({}); + + if (element.name === undefined) + ChartInfoObj.name = null; + else + ChartInfoObj.name = element.name; + + ChartInfoObj.query = element.query; + RequestInfoObj.chartsInfo.push(ChartInfoObj); + }); + break; + default: + console.log("Unsupported Library: " + dataJSONobj.library); + } + return RequestInfoObj; +} diff --git a/monitor-admin/utils/indicator-utils.ts b/monitor-admin/utils/indicator-utils.ts new file mode 100644 index 00000000..f7c71c0b --- /dev/null +++ b/monitor-admin/utils/indicator-utils.ts @@ -0,0 +1,1005 @@ +import { + ChartHelper, + FilterType, + Indicator, + IndicatorFilterUtils, + IndicatorPath, + IndicatorPathType, + IndicatorType, + SourceType, + Stakeholder, + StakeholderEntities, + SubCategory, + Topic, + Visibility +} from "../../monitor/entities/stakeholder"; +import {AbstractControl, ValidatorFn, Validators} from "@angular/forms"; +import {Option} from "../../sharedComponents/input/input.component"; +import {Session} from "../../login/utils/helper.class"; +import {HelperFunctions} from "../../utils/HelperFunctions.class"; +import {properties} from "src/environments/environment"; + +export class StakeholderUtils { + + statuses: Option[] = [ + {value: 'PUBLIC', label: 'Public'}, + {value: 'RESTRICTED', label: 'Restricted'}, + {value: 'PRIVATE', label: 'Private'} + ]; + + types: Option[] = [ + {value: 'funder', label: StakeholderEntities.FUNDER}, + {value: 'ri', label: StakeholderEntities.RI}, + {value: 'project', label: StakeholderEntities.PROJECT}, + {value: 'organization', label: StakeholderEntities.ORGANIZATION} + ]; + + visibility: Option[] = [ + {icon: 'earth', value: "PUBLIC", label: 'Public'}, + {icon: 'restricted', value: "RESTRICTED", label: 'Restricted'}, + {icon: 'incognito', value: "PRIVATE", label: 'Private'}, + ]; + + locales: Option[] = [ + {value: "en", label: 'English'}, + {value: "eu", label: 'Europe'} + ]; + + visibilityIcon: Map = new Map([ + ["PUBLIC", 'earth'], + ["PRIVATE", 'incognito'], + ["RESTRICTED", 'restricted'] + ]); + + getTypesByUserRoles(user, id: string = null): Option[] { + let types = []; + for (let type of this.types) { + if (Session.isCurator(type.value, user) || Session.isPortalAdministrator(user) || (id && Session.isManager(type.value, id, user))) { + types.push(type); + } + } + return types; + } + + public createFunderFromDefaultProfile(funder: Stakeholder, defaultTopics: Topic[], isDefault: boolean = false): Stakeholder { + funder.topics = HelperFunctions.copy(defaultTopics); + for (let topic of funder.topics) { + topic.defaultId = !isDefault ? topic._id : null; + topic._id = null; + for (let category of topic.categories) { + category.defaultId = !isDefault ? category._id : null; + category._id = null; + let subTokeep: SubCategory[] = []; + for (let subCategory of category.subCategories) { + subCategory.defaultId = !isDefault ? subCategory._id : null; + subCategory._id = null; + subTokeep.push(subCategory); + for (let section of subCategory.charts) { + let chartsTokeep: Indicator[] = []; + section.defaultId = !isDefault ? section._id : null; + section.stakeholderAlias = funder.alias; + section._id = null; + for (let indicator of section.indicators) { + indicator.defaultId = !isDefault ? indicator._id : null; + indicator._id = null; + chartsTokeep.push(indicator); + for (let indicatorPath of indicator.indicatorPaths) { + if (indicatorPath.parameters) { + Object.keys(indicatorPath.parameters).forEach(key => { + if (key == "index_name") { + indicatorPath.parameters[key] = funder.index_name; + } else if (key == "index_id") { + indicatorPath.parameters[key] = funder.index_id; + } else if (key == "index_shortName") { + indicatorPath.parameters[key] = funder.index_shortName.toLowerCase(); + } + }); + } + } + } + section.indicators = chartsTokeep; + } + for (let section of subCategory.numbers) { + section.defaultId = !isDefault ? section._id : null; + section.stakeholderAlias = funder.alias; + section._id = null; + for (let indicator of section.indicators) { + indicator.defaultId = !isDefault ? indicator._id : null; + indicator._id = null; + } + } + + } + category.subCategories = subTokeep; + } + } + return funder; + } + + aliasValidatorString(elements: string[]): ValidatorFn { + return (control: AbstractControl): { [key: string]: string } | null => { + if (control.value && elements.find(element => + element === control.value + )) { + return {'error': 'Alias already in use'}; + } + return null; + } + } + + aliasValidator(elements: any[]): ValidatorFn { + return (control: AbstractControl): { [key: string]: string } | null => { + if (control.value && elements.find(element => + element.alias === control.value + )) { + return {'error': 'Alias already in use'}; + } + return null; + } + } + + generateAlias(name: string): string { + let alias = name.toLowerCase(); + while (alias.includes('/') || alias.includes(' ')) { + alias = alias.replace(' / ', '-'); + alias = alias.replace('/', '-'); + alias = alias.replace(' ', '-'); + } + return alias; + } +} + +export class IndicatorUtils { + + allChartTypes: Option[] = [ + {value: 'pie', label: 'Pie'}, + {value: 'table', label: 'Table'}, + {value: 'line', label: 'Line'}, + {value: 'column', label: 'Column'}, + {value: 'bar', label: 'Bar'}, + {value: 'other', label: 'Other'} + ]; + basicChartTypes: IndicatorPathType[] = ["pie", "line", "column", "bar"]; + defaultChartType: IndicatorPathType = "other"; + indicatorSizes: Option[] = [ + {value: 'small', label: 'Small (Enabled only for large screens)'}, + {value: 'medium', label: 'Medium'}, + {value: 'large', label: 'Large'} + ]; + + allSourceTypes: Option[] = [ + {value: 'search', label: 'Search'}, + {value: 'statistics', label: 'Statistics'}, + {value: 'stats-tool', label: 'Statistics tool'} + ]; + + formats: Option[] = [ + {value: "NUMBER", label: "Number"}, + {value: "PERCENTAGE", label: "Percentage"} + ]; + + sourceTypes: Option[] = [ + {value: 'stats-tool', label: 'Statistics tool'} + ]; + + isActive: Option[] = [ + {icon: 'brightness_1', iconClass: '', value: true, label: 'Active'}, + {icon: 'brightness_1', value: false, label: 'Inactive'}, + ]; + + parametersValidators: Map = new Map([ + ['start_year', [Validators.required, Validators.pattern('^\\d+$')]], + ['end_year', [Validators.required, Validators.pattern('^\\d+$')]] + ]); + ignoredParameters = ['index_name', 'index_id', 'index_shortName']; + statsProfileParameter = 'profile'; + + numberSources: Map = new Map(); + chartSources: Map = new Map(); + + constructor() { + this.numberSources.set('statistics', [properties.statisticsAPIURL]); + this.numberSources.set('search', [properties.searchAPIURLLAst]); + this.numberSources.set('stats-tool', [properties.monitorStatsFrameUrl, "http://marilyn.athenarc.gr:8080/stats-api/", "http://88.197.53.71:8080/stats-api/", "https://stats.madgik.di.uoa.gr/stats-api/","https://beta.services.openaire.eu/stats-tool/","https://services.openaire.eu/stats-tool/","https://services.openaire.eu/monitor-stats-tool/"]); + this.chartSources.set('stats-tool', [properties.monitorStatsFrameUrl, "http://marilyn.athenarc.gr:8080/stats-api/", "http://88.197.53.71:8080/stats-api/", "https://stats.madgik.di.uoa.gr/stats-api/","https://beta.services.openaire.eu/stats-tool/","https://services.openaire.eu/stats-tool/","https://services.openaire.eu/monitor-stats-tool/"]); + this.chartSources.set('old', [properties.statisticsFrameAPIURL]); + this.chartSources.set('image', [""]); + } + + getSourceType(source:string): SourceType{ + let sourceType: SourceType = 'search'; + this.numberSources.forEach((values, key) => { + if(key == source) { + sourceType = key; + } + }); + return sourceType; + } + + getChartUrl(source: SourceType, url: string): string { + return this.chartSources.get(source)[0] + url; + } + + getNumberUrl(source: SourceType, url: string): string { + return this.numberSources.get(this.getSourceType(source))[0] + url; + } + + getNumberSource(url: string): SourceType { + let source: SourceType = 'search'; + this.numberSources.forEach((values, key) => { + values.forEach((value) => { + if (value !== '' && url.indexOf(value) !== -1) { + source = key; + } + }); + }); + return source; + } + + getChartSource(url: string): SourceType { + let source: SourceType = 'image'; + this.chartSources.forEach((values, key) => { + values.forEach((value) => { + if (value !== '' && url.indexOf(value) !== -1) { + source = key; + } + }); + }); + return source; + } + + getChartTypes(initialType) { + let types: Option[] = []; + if (this.basicChartTypes.indexOf(initialType) != -1) { + (this.allChartTypes).forEach(option => { + if (this.basicChartTypes.indexOf(option.value) != -1) { + types.push(option); + } + }); + return types; + } else if (initialType == "table") { + (this.allChartTypes).forEach(option => { + if (initialType == option.value) { + types.push(option); + } + }); + return types; + } else { + return this.allChartTypes; + } + } + + public getFullUrl(stakeholder: Stakeholder, indicatorPath: IndicatorPath, fundingL0: string = null, startYear: string = null, endYear: string = null): string { + let replacedUrl = indicatorPath.chartObject ? indicatorPath.chartObject : indicatorPath.url; + if (stakeholder.statsProfile) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + this.statsProfileParameter + ChartHelper.suffix).join(stakeholder.statsProfile) + } + if (indicatorPath.parameters) { + Object.keys(indicatorPath.parameters).forEach(key => { + let replacedValue = indicatorPath.parameters[key]; + if (startYear && key == "start_year" && indicatorPath.filters["start_year"]) { + replacedValue = (replacedValue < startYear) ? startYear : replacedValue; + } + if (endYear && key == "end_year" && indicatorPath.filters["end_year"]) { + replacedValue = (replacedValue > endYear) ? endYear : replacedValue; + } + if (key == "index_id") { + replacedValue = stakeholder.index_id; + } + if (key == "index_name") { + replacedValue = stakeholder.index_name; + } + if (key == "index_shortName") { + replacedValue = stakeholder.index_shortName.toLowerCase(); + } + + replacedUrl = replacedUrl.split(ChartHelper.prefix + key + ChartHelper.suffix).join(replacedValue) + }); + } + if (indicatorPath.chartObject) { + if (fundingL0 && indicatorPath.filters["fundingL0"]) { + let newJsonObject = JSON.parse(replacedUrl); + for (let queries of this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)] : newJsonObject[this.getDescriptionObjectName(newJsonObject)]) { + if (!queries["query"]["filters"] || queries["query"]["filters"].length == 0) { + queries["query"]["filters"] = []; + } + //TODO check how it works if the query already has a filter + queries["query"]["filters"].push(JSON.parse(indicatorPath.filters["fundingL0"].replace(ChartHelper.prefix + "fundingL0" + ChartHelper.suffix, fundingL0))); + } + replacedUrl = JSON.stringify(newJsonObject); + } + if (startYear && indicatorPath.filters["start_year"]) { + let newJsonObject = JSON.parse(replacedUrl); + + for (let queries of this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)] : newJsonObject[this.getDescriptionObjectName(newJsonObject)]) { + if (!queries["query"]["filters"] || queries["query"]["filters"].length == 0) { + queries["query"]["filters"] = []; + } + //TODO check how it works if the query already has a filter + queries["query"]["filters"].push(JSON.parse(indicatorPath.filters["start_year"].replace(ChartHelper.prefix + "start_year" + ChartHelper.suffix, startYear))); + } + replacedUrl = JSON.stringify(newJsonObject); + } + if (endYear && indicatorPath.filters["end_year"]) { + let newJsonObject = JSON.parse(replacedUrl); + for (let queries of this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)] : newJsonObject[this.getDescriptionObjectName(newJsonObject)]) { + if (!queries["query"]["filters"] || queries["query"]["filters"].length == 0) { + queries["query"]["filters"] = []; + } + //TODO check how it works if the query already has a filter + queries["query"]["filters"].push(JSON.parse(indicatorPath.filters["end_year"].replace(ChartHelper.prefix + "end_year" + ChartHelper.suffix, endYear))); + } + replacedUrl = JSON.stringify(newJsonObject); + } + + } + //For numbers (e.g. from stats-api , search service, etc) + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_id' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_id' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_id)); + } + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_name' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_name' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_name)); + } + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_shortName' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_shortName' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_shortName)); + } + return (indicatorPath.chartObject ? indicatorPath.url + encodeURIComponent(replacedUrl) : replacedUrl); + } + + public getFullUrlWithFilters(stakeholder: Stakeholder, indicatorPath: IndicatorPath, fundingL0: string = null, startYear: string = null, endYear: string = null, coFunded: boolean = false): string { + indicatorPath.filtersApplied = 0; + let replacedUrl = indicatorPath.chartObject ? indicatorPath.chartObject : indicatorPath.url; + if (stakeholder.statsProfile) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + this.statsProfileParameter + ChartHelper.suffix).join(stakeholder.statsProfile); + } + if (indicatorPath.parameters) { + Object.keys(indicatorPath.parameters).forEach(key => { + let replacedValue = indicatorPath.parameters[key]; + if (startYear && key == "start_year") { + replacedValue = (replacedValue < startYear) ? startYear : replacedValue; + //if there is a parameter that is filtered and the value of the parameter changes, count the filter as applied + indicatorPath.filtersApplied++; + } + if (endYear && key == "end_year") { + replacedValue = (replacedValue > endYear) ? endYear : replacedValue; + //if there is a parameter that is filtered and the value of the parameter changes, count the filter as applied + indicatorPath.filtersApplied++; + } + if (key == "index_id") { + replacedValue = stakeholder.index_id; + } + if (key == "index_name") { + replacedValue = stakeholder.index_name; + } + if (key == "index_shortName") { + replacedValue = stakeholder.index_shortName.toLowerCase(); + } + + replacedUrl = replacedUrl.split(ChartHelper.prefix + key + ChartHelper.suffix).join(replacedValue) + }); + } + if (fundingL0) { + if (indicatorPath.source == "stats-tool" && indicatorPath.chartObject) { + let filterResults = this.addFilter(replacedUrl, 'fundingL0', fundingL0); + replacedUrl = filterResults.url; + indicatorPath.filtersApplied += filterResults.filtersApplied; + } + } + if (startYear) { + if (indicatorPath.source == "stats-tool" && indicatorPath.chartObject) { + let filterResults = this.addFilter(replacedUrl, 'start_year', startYear); + replacedUrl = filterResults.url; + indicatorPath.filtersApplied += filterResults.filtersApplied; + } + } + if (endYear) { + if (indicatorPath.source == "stats-tool" && indicatorPath.chartObject) { + let filterResults = this.addFilter(replacedUrl, 'end_year', endYear); + replacedUrl = filterResults.url; + indicatorPath.filtersApplied += filterResults.filtersApplied; + } + } + if (coFunded) { + if (indicatorPath.source == "stats-tool" && indicatorPath.chartObject) { + let filterResults = this.addFilter(replacedUrl, 'co-funded', endYear); + replacedUrl = filterResults.url; + indicatorPath.filtersApplied += filterResults.filtersApplied; + } + } + + //For numbers + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_id' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_id' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_id)); + } + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_name' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_name' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_name)); + } + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_shortName' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_shortName' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_shortName)); + } + //Check apply enhancements return this.applySchemaEnhancements( ..); + return (indicatorPath.chartObject ? indicatorPath.url + encodeURIComponent(replacedUrl) : replacedUrl); + + } + + private addFilter(replacedUrl, filterType: FilterType, filterValue) { + let newJsonObject = JSON.parse(replacedUrl); + let filterApplied: boolean = false; + let queryIndex = 0; + for (let queries of this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)] : newJsonObject[this.getDescriptionObjectName(newJsonObject)]) { + /*Chart with Named Queries*/ + if (queries["query"]["name"] && !queries["query"]["select"]) { + + if (queries["query"]["name"].indexOf("monitor.") == -1 || !queries["query"]["parameters"]) { + continue; + } + if (filterType == 'fundingL0') { + let paramFields = queries["query"]["name"].split(".").slice(3); + let filterPosition = queries["query"]["name"].split(".").indexOf(filterType == "fundingL0" ? 'fl0' : filterType); + if (filterPosition != -1) { + //already filtered + //TODO double check if we need to override if the fl0 is already filtered + filterPosition -= 3; + /* //update the value + if(paramFields.length == queries["query"]["parameters"].length ){ + //ok + queries["query"]["parameters"][filterPosition] = filterValue; + }else if((paramFields.length + 2) == queries["query"]["parameters"].length || (paramFields.length*2 + 4) == queries["query"]["parameters"].length){ + queries["query"]["parameters"][filterPosition + 2]=filterValue; + filterApplied = true; + } + if((paramFields.length*2 + 4) == queries["query"]["parameters"].length){ + queries["query"]["parameters"][(2* filterPosition) + 5]=filterValue; + }*/ + //if applied with the same value mark as filtered + if (paramFields.length == queries["query"]["parameters"].length && queries["query"]["parameters"][filterPosition] == filterValue) { + filterApplied = true; + } else if ((paramFields.length + 2) == queries["query"]["parameters"].length || (paramFields.length * 2 + 4) == queries["query"]["parameters"].length && queries["query"]["parameters"][filterPosition + 2] == filterValue) { + filterApplied = true; + } + } else { + // if((paramFields.length*2) == queries["query"]["parameters"].length){ + // queries["query"]["parameters"].splice(paramFields.length, 0, filterValue); + // } + if ((paramFields.length * 2 + 4) == queries["query"]["parameters"].length) { + queries["query"]["parameters"].splice(paramFields.length + 1, 0, filterValue); + } + queries["query"]["name"] = queries["query"]["name"] + ".fl0"; + queries["query"]["parameters"].push(filterValue); + filterApplied = true; + } + } else { + let paramFields = queries["query"]["name"].split(".").slice(3); + if ((paramFields.length + 2) == queries["query"]["parameters"].length || (paramFields.length * 2 + 4) == queries["query"]["parameters"].length) { + filterApplied = true; + if (filterType == "start_year") { + queries["query"]["parameters"][0] = parseInt(filterValue); + } else if (filterType == "end_year") { + queries["query"]["parameters"][1] = parseInt(filterValue); + } + } + if ((paramFields.length * 2 + 4) == queries["query"]["parameters"].length) { + filterApplied = true; + if (filterType == "start_year") { + queries["query"]["parameters"][paramFields.length + 2] = parseInt(filterValue); + } else if (filterType == "end_year") { + queries["query"]["parameters"][paramFields.length + 3] = parseInt(filterValue); + } + } + } + // it is a name query + continue; + } + if (!queries["query"]["filters"] || queries["query"]["filters"].length == 0) { + queries["query"]["filters"] = []; + } + /*Chart with proper json object*/ + //apply the filter in any select fields + for (let select of queries["query"]["select"]) { + let filterString = IndicatorFilterUtils.getFilter(select["field"], filterType); + if (filterString) { + let filter = JSON.parse(filterString); + //check if filter already exists + let filterposition = IndicatorFilterUtils.filterIndexOf(filter, queries["query"]["filters"]); + if (filterposition) { + if (queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] != filter['groupFilters'][0]["values"][0].replace(ChartHelper.prefix + filterType + ChartHelper.suffix, filterValue)) { + //change filter value + // queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] = filter['groupFilters'][0]["values"][0].replace(ChartHelper.prefix + filterType + ChartHelper.suffix, filterValue); + //add user filter value + // queries["query"]["filters"].push(JSON.parse(filterString.replace(ChartHelper.prefix + filterType + ChartHelper.suffix, filterValue))); + // update colors + //if noit a pie, map and chart has more than one query + // + if (!newJsonObject.hasOwnProperty("mapDescription") && queries["type"] != "pie" && this.isComparingChart(newJsonObject, filter)) { + let activeColors = ["#7CB5EC", "#434348", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"]; + let inActiveColors = ["#E4EFFB", "#D8D8D9", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"]; + if (!newJsonObject[this.getDescriptionObjectName(newJsonObject)]["colors"]) { + newJsonObject[this.getDescriptionObjectName(newJsonObject)]["colors"] = activeColors; + } + newJsonObject[this.getDescriptionObjectName(newJsonObject)]["colors"][queryIndex] = inActiveColors[queryIndex]; + filterApplied = true; + } else if (filterType == "start_year" || filterType == "end_year") { + //if has date filter already + if (filterType == "start_year" && parseInt(filterValue) > parseInt(queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0])) { + queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] = filterValue; + } else if (filterType == "end_year" && parseInt(filterValue) < parseInt(queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0])) { + queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] = filterValue; + } + filterApplied = true; + } + } else { + filterApplied = true; + } + } else { + queries["query"]["filters"].push(JSON.parse(filterString.replace(ChartHelper.prefix + filterType + ChartHelper.suffix, filterValue))); + filterApplied = true; + } + } + } + queryIndex++; + } + return {"url": JSON.stringify(newJsonObject), "filtersApplied": (filterApplied) ? 1 : 0}; + } + + isComparingChart(newJsonObject, filter,) { + let queriesCount = this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)].length : newJsonObject[this.getDescriptionObjectName(newJsonObject)].length; + let values = []; + if (queriesCount < 2) { + return false; + } + for (let queries of this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)] : newJsonObject[this.getDescriptionObjectName(newJsonObject)]) { + let filterposition = IndicatorFilterUtils.filterIndexOf(filter, queries["query"]["filters"]); + if (filterposition) { + if (values.indexOf(queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0]) == -1) { + values.push(queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0]); + } + } + } + return values.length > 1; + } + + generateIndicatorByForm(form: any, indicatorPaths: IndicatorPath[], type: IndicatorType, addParameters: boolean = true): Indicator { + let indicator: Indicator = new Indicator(form.name, form.description, form.additionalDescription, type, + form.width, form.height, form.visibility, indicatorPaths, form.defaultId); + indicator._id = form._id; + form.indicatorPaths.forEach((indicatorPath, index) => { + indicator.indicatorPaths[index].type = indicatorPath.type; + indicator.indicatorPaths[index].format = indicatorPath.format; + if (addParameters) { + indicatorPath.parameters.forEach(parameter => { + indicator.indicatorPaths[index].parameters[parameter.key] = parameter.value; + if (parameter.key === 'type') { + indicator.indicatorPaths[index].type = parameter.value; + } + }); + } + }); + return indicator; + } + + generateIndicatorByNumberUrl(source: SourceType, url: string, stakeholder: Stakeholder, jsonPath = [], sourceServices: string[] = []): IndicatorPath { + let indicatorPath = new IndicatorPath(null, source, url, null, jsonPath); + if (source === 'stats-tool') { + indicatorPath.url = url.split("json=")[0] + "json="; + indicatorPath.url = indicatorPath.url.split("/")[indicatorPath.url.split("/").length - 1]; + indicatorPath.chartObject = decodeURIComponent(url.indexOf("json=") != -1 ? url.split("json=")[1] : ""); + let chart = JSON.parse(indicatorPath.chartObject); + this.parameterizeDefaultQuery(chart, indicatorPath, stakeholder); + this.extractStakeHolders(chart, indicatorPath, stakeholder); + indicatorPath.chartObject = JSON.stringify(chart); + if (!jsonPath || jsonPath.length == 0 || (jsonPath.length == 1 && jsonPath[0] == "")) { + indicatorPath.jsonPath = ["data", "0", "0", "0"]; + } + // this.addResultFilters(chart, indicatorPath); + } else { + for (let service of sourceServices) { + if (url.indexOf(service) != -1) { + url = url.split(service)[1]; + } + } + try { + if (url.indexOf(encodeURIComponent(stakeholder.index_id)) !== -1) { + url = url.split(encodeURIComponent(stakeholder.index_id)).join(ChartHelper.prefix + "index_id" + ChartHelper.suffix); + } + if (url.indexOf(encodeURIComponent(stakeholder.index_name)) !== -1) { + url = url.split(encodeURIComponent(stakeholder.index_name)).join(ChartHelper.prefix + "index_name" + ChartHelper.suffix); + } + if (url.indexOf(encodeURIComponent(stakeholder.index_shortName)) !== -1) { + url = url.split(encodeURIComponent(stakeholder.index_shortName)).join(ChartHelper.prefix + "index_shortName" + ChartHelper.suffix); + } + indicatorPath.url = url; + } catch (e) { + console.error(e); + } + } + return indicatorPath; + } + + generateIndicatorByChartUrl(source: SourceType, url: string, type: IndicatorPathType = null, stakeholder: Stakeholder): IndicatorPath { + let indicatorPath = new IndicatorPath(type, source, null, null, []); + try { + if (source === 'stats-tool') { + indicatorPath.url = url.split("json=")[0] + "json="; + indicatorPath.url = indicatorPath.url.split("/")[indicatorPath.url.split("/").length - 1]; + indicatorPath.chartObject = decodeURIComponent(url.split("json=")[1]); + let chart = JSON.parse(indicatorPath.chartObject); + if (indicatorPath.url == "chart?json=") { + + if (chart["library"] && (chart["library"] == "HighCharts" || chart["library"] == "eCharts" || chart["library"] == "HighMaps")) { + indicatorPath.type = this.extractType(chart, indicatorPath); + } else { + indicatorPath.type = this.defaultChartType; + } + + this.extractTitle(chart, indicatorPath); + this.extractSubTitle(chart, indicatorPath); + this.extractXTitle(chart, indicatorPath); + this.extractYTitle(chart, indicatorPath); + } else if (indicatorPath.url == "table?json=") { + indicatorPath.type = "table"; + } + if (indicatorPath.url == "chart?json=" || indicatorPath.url == "table?json=") { + // common for tables and other chart types + this.extractDataTitle(chart, indicatorPath); + this.parameterizeDefaultQuery(chart, indicatorPath, stakeholder); + this.extractStakeHolders(chart, indicatorPath, stakeholder); + this.extractStartYear(chart, indicatorPath); + this.extractEndYear(chart, indicatorPath); + indicatorPath.chartObject = JSON.stringify(chart); + } + } else if (source === 'old') { + indicatorPath.url = url.split("data=")[0].split("/stats/")[1] + "data="; + indicatorPath.chartObject = decodeURIComponent(url.split("data=")[1].split("&")[0]); + indicatorPath.type = type; + let chart = JSON.parse(indicatorPath.chartObject); + this.extractOldToolTitle(chart, indicatorPath); + this.extractOldToolXTitle(chart, indicatorPath); + this.extractOldToolYTitle(chart, indicatorPath); + indicatorPath.chartObject = JSON.stringify(chart); + } else { + indicatorPath.url = url; + indicatorPath.type = type; + } + } catch (e) { + console.error(e); + indicatorPath.url = url; + indicatorPath.type = type; + } + if (indicatorPath.type == null) { + indicatorPath.type = this.defaultChartType; + } + return indicatorPath; + } + + private getQueryObjectName(obj) { + if ((obj[this.getDescriptionObjectName(obj)]).hasOwnProperty("queriesInfo")) { + return "queriesInfo"; + } else if ((obj[this.getDescriptionObjectName(obj)]).hasOwnProperty("queries")) { + return "queries"; + } + } + + private getDescriptionObjectName(obj) { + if (obj.hasOwnProperty("mapDescription")) { + return "mapDescription"; + } else if (obj.hasOwnProperty("chartDescription")) { + return "chartDescription"; + } else if (obj.hasOwnProperty("tableDescription")) { + return "tableDescription"; + } else if (obj.hasOwnProperty("series")) { + return "series"; + } + } + + private extractType(obj, indicatorPath: IndicatorPath): IndicatorPathType { + let type = (obj[this.getDescriptionObjectName(obj)] && obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)][0]["type"]) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)][0]["type"] : ""; + if (this.basicChartTypes.indexOf(type) == -1) { + type = this.defaultChartType; + } else { + obj[this.getDescriptionObjectName(obj)]["queries"][0]["type"] = ChartHelper.prefix + "type" + ChartHelper.suffix; + indicatorPath.parameters['type'] = type; + } + return type; + } + + private extractStakeHolders(obj, indicatorPath: IndicatorPath, stakeholder: Stakeholder) { + this.extractFunder(obj, indicatorPath, stakeholder); + this.extractRI(obj, indicatorPath, stakeholder); + this.extractOrganization(obj, indicatorPath, stakeholder); + } + + private extractFunder(obj, indicatorPath: IndicatorPath, stakeholder: Stakeholder) { + if (stakeholder.type != "funder") { + return; + } + for (let query of this.getQueryObjectName(obj) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)] : obj[this.getDescriptionObjectName(obj)]) { + if (query["query"]["profile"]) { + query["query"]["profile"] = ChartHelper.prefix + this.statsProfileParameter + ChartHelper.suffix; + } + if (!query["query"]["filters"]) { + return; + } + for (let filter of query["query"]["filters"]) { + for (let gfilter of filter["groupFilters"]) { + //ignore field No Of Funders + if (gfilter["field"].indexOf(" funder") != -1 && gfilter["field"].indexOf(" funders") == -1) {//new statistcs schema + gfilter["values"][0] = ChartHelper.prefix + "index_name" + ChartHelper.suffix; + indicatorPath.parameters["index_name"] = stakeholder.index_name; + } else if (gfilter["field"].indexOf(".funder") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_name" + ChartHelper.suffix; + indicatorPath.parameters["index_name"] = stakeholder.index_name; + } else if (gfilter["field"].indexOf(".funder.id") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_shortName" + ChartHelper.suffix; + indicatorPath.parameters["index_shortName"] = stakeholder.index_shortName; + } + } + } + } + } + + private extractRI(obj, indicatorPath: IndicatorPath, stakeholder: Stakeholder) { + if (stakeholder.type != "ri") { + return; + } + for (let query of this.getQueryObjectName(obj) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)] : obj[this.getDescriptionObjectName(obj)]) { + if (query["query"]["profile"]) { + query["query"]["profile"] = ChartHelper.prefix + this.statsProfileParameter + ChartHelper.suffix; + } + if (!query["query"]["filters"]) { + return; + } + for (let filter of query["query"]["filters"]) { + for (let gfilter of filter["groupFilters"]) { + if (gfilter["field"].indexOf(".context.name") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_name" + ChartHelper.suffix; + indicatorPath.parameters["index_name"] = stakeholder.index_name; + } else if (gfilter["field"].indexOf(".context.id") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_shortName" + ChartHelper.suffix; + indicatorPath.parameters["index_shortName"] = stakeholder.index_shortName; + } + } + } + } + } + + private extractOrganization(obj, indicatorPath: IndicatorPath, stakeholder: Stakeholder) { + // works for publication.project.organization.name + // and publication.organization.name + if (stakeholder.type != "organization") { + return; + } + for (let query of this.getQueryObjectName(obj) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)] : obj[this.getDescriptionObjectName(obj)]) { + if (query["query"]["profile"]) { + query["query"]["profile"] = ChartHelper.prefix + this.statsProfileParameter + ChartHelper.suffix; + } + if (!query["query"]["filters"]) { + return; + } + for (let filter of query["query"]["filters"]) { + for (let gfilter of filter["groupFilters"]) { + if (gfilter["field"].indexOf(".organization.name") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_name" + ChartHelper.suffix; + indicatorPath.parameters["index_name"] = stakeholder.index_name; + } else if (gfilter["field"].indexOf(".organization.id") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_shortName" + ChartHelper.suffix; + indicatorPath.parameters["index_shortName"] = stakeholder.index_shortName; + } + } + } + } + } + + private extractStartYear(obj, indicatorPath: IndicatorPath) { + let start_year; + for (let query of obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)]) { + if (!query["query"]["filters"]) { + return; + } + for (let filter of query["query"]["filters"]) { + for (let gfilter of filter["groupFilters"]) { + if ((gfilter["field"].indexOf(".year") != -1 || gfilter["field"].indexOf(".start year") != -1) && gfilter["type"].indexOf(">") != -1) { + start_year = gfilter["values"][0]; + gfilter["values"][0] = ChartHelper.prefix + "start_year" + ChartHelper.suffix; + indicatorPath.parameters["start_year"] = start_year; + } + } + } + } + } + + private extractEndYear(obj, indicatorPath: IndicatorPath) { + let end_year; + for (let query of obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)]) { + if (!query["query"]["filters"]) { + return; + } + for (let filter of query["query"]["filters"]) { + for (let gfilter of filter["groupFilters"]) { + if ((gfilter["field"].indexOf(".year") != -1 || gfilter["field"].indexOf(".start year") != -1) && gfilter["type"].indexOf("<") != -1) { + end_year = gfilter["values"][0]; + gfilter["values"][0] = ChartHelper.prefix + "end_year" + ChartHelper.suffix; + indicatorPath.parameters["end_year"] = end_year; + } + } + } + } + } + + private parameterizeDefaultQuery(obj, indicatorPath: IndicatorPath, stakeholder: Stakeholder) { + let name = ""; + for (let query of this.getQueryObjectName(obj) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)] : obj[this.getDescriptionObjectName(obj)]) { + //monitor.{{stakeholderType}}.{{queryname}} + //parameters: stakeholderId*, type + if (query["query"]["name"]) { + name = query["query"]["name"]; + let parameters = (query["query"]["parameters"]) ? query["query"]["parameters"] : []; + if (name.split('.')[0] == "rcd" && parameters.length > 0 && stakeholder.type == "ri") { + //rcd.{{queryname}} + parameters[0] = ChartHelper.prefix + "index_id" + ChartHelper.suffix; + indicatorPath.parameters["index_id"] = stakeholder.index_id; + } else if (name.split('.')[0] == "monitor" && parameters.length == 0 && stakeholder.type == "funder") { + // old saved queries without params + //monitor.{{funder_shortName}}.{{type}}.{{queryname}} + let stakeholderSN = name.split('.')[1]; + query["query"]["name"] = name.split('.' + stakeholderSN + ".")[0] + "." + ChartHelper.prefix + "index_shortName" + ChartHelper.suffix + "." + name.split('.' + stakeholderSN + ".")[1]; + indicatorPath.parameters["index_shortName"] = stakeholder.index_shortName.toLowerCase(); + } else if (name.split('.')[0] == "monitor" && parameters.length > 0 && name.split('.')[1] == stakeholder.type) { + // new parameterized queries + //monitor.{{type}}.{{queryname}}.{{param1 - id }}.{{param2 result-type}}.{{fl0}} --> params [start year, end year, id, result type, fl0] + + let index = (name.split('.').slice(3).length + 2 == parameters.length) ? [2] : ((name.split('.').slice(3).length * 2 + 4 == parameters.length) ? [2, name.split('.').slice(3).length + 4] : [0]); + for (let i of index) { + if (name.split('.').length > 3 && name.split('.')[3] == "id") { + parameters[i] = ChartHelper.prefix + "index_id" + ChartHelper.suffix; + indicatorPath.parameters["index_id"] = stakeholder.index_id; + } else if (name.split('.').length > 3 && name.split('.')[3] == "shortname") { + parameters[i] = ChartHelper.prefix + "index_shortName" + ChartHelper.suffix; + indicatorPath.parameters["index_shortName"] = stakeholder.index_shortName.toLowerCase(); + } else if (name.split('.').length > 3 && name.split('.')[3] == "name") { + parameters[i] = ChartHelper.prefix + "index_name" + ChartHelper.suffix; + indicatorPath.parameters["index_name"] = stakeholder.index_name; + } + } + } + } + } + } + + private extractDataTitle(obj, indicatorPath: IndicatorPath) { + let index = 0; + if (!obj[this.getDescriptionObjectName(obj)] || !obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)]) { + return; + } + for (let query of obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)]) { + if (query["name"]) { + let name = query["name"]; + query["name"] = ChartHelper.prefix + "data_title_" + index + ChartHelper.suffix; + indicatorPath.parameters["data_title_" + index] = name; + } + index++; + } + } + + private extractTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj[this.getDescriptionObjectName(obj)]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["title"]["text"]; + obj[this.getDescriptionObjectName(obj)]["title"]["text"] = ChartHelper.prefix + "title" + ChartHelper.suffix; + } else if (obj[this.getDescriptionObjectName(obj)]["options"] && obj[this.getDescriptionObjectName(obj)]["options"]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["options"]["title"]; + obj[this.getDescriptionObjectName(obj)]["options"]["title"] = ChartHelper.prefix + "title" + ChartHelper.suffix; + } + indicatorPath.parameters["title"] = title ? title : ""; + } + + private extractSubTitle(obj, indicatorPath: IndicatorPath) { + let subtitle = ""; + if (obj[this.getDescriptionObjectName(obj)]["subtitle"]) { + subtitle = obj[this.getDescriptionObjectName(obj)]["subtitle"]["text"]; + obj[this.getDescriptionObjectName(obj)]["subtitle"]["text"] = ChartHelper.prefix + "subtitle" + ChartHelper.suffix; + indicatorPath.parameters["subtitle"] = subtitle ? subtitle : ""; + } else if (obj[this.getDescriptionObjectName(obj)]["title"] && obj[this.getDescriptionObjectName(obj)]["title"] && obj[this.getDescriptionObjectName(obj)]["title"]["subtext"]) { + subtitle = obj[this.getDescriptionObjectName(obj)]["title"]["subtext"]; + obj[this.getDescriptionObjectName(obj)]["title"]["subtext"] = ChartHelper.prefix + "subtitle" + ChartHelper.suffix; + indicatorPath.parameters["subtitle"] = subtitle ? subtitle : ""; + } + } + + private extractXTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj[this.getDescriptionObjectName(obj)]["xAxis"] && obj[this.getDescriptionObjectName(obj)]["xAxis"]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["xAxis"]["title"]["text"]; + obj[this.getDescriptionObjectName(obj)]["xAxis"]["title"]["text"] = ChartHelper.prefix + "xAxisTitle" + ChartHelper.suffix; + } else if (obj[this.getDescriptionObjectName(obj)]["options"] && obj[this.getDescriptionObjectName(obj)]["options"]["hAxis"] && obj[this.getDescriptionObjectName(obj)]["options"]["hAxis"]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["options"]["hAxis"]["title"]; + obj[this.getDescriptionObjectName(obj)]["options"]["hAxis"]["title"] = ChartHelper.prefix + "xAxisTitle" + ChartHelper.suffix; + } else if (obj[this.getDescriptionObjectName(obj)]["xAxis"] && obj[this.getDescriptionObjectName(obj)]["xAxis"]["name"]) { + title = obj[this.getDescriptionObjectName(obj)]["xAxis"]["name"]; + obj[this.getDescriptionObjectName(obj)]["xAxis"]["name"] = ChartHelper.prefix + "xAxisTitle" + ChartHelper.suffix; + } + indicatorPath.parameters["xAxisTitle"] = title ? title : ""; + } + + private extractYTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj[this.getDescriptionObjectName(obj)]["yAxis"] && obj[this.getDescriptionObjectName(obj)]["yAxis"]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["yAxis"]["title"]["text"]; + obj[this.getDescriptionObjectName(obj)]["yAxis"]["title"]["text"] = ChartHelper.prefix + "yAxisTitle" + ChartHelper.suffix; + } else if (obj[this.getDescriptionObjectName(obj)]["options"] && obj[this.getDescriptionObjectName(obj)]["options"]["vAxis"] && obj[this.getDescriptionObjectName(obj)]["options"]["vAxis"]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["options"]["vAxis"]["title"]; + obj[this.getDescriptionObjectName(obj)]["options"]["vAxis"]["title"] = ChartHelper.prefix + "yAxisTitle" + ChartHelper.suffix; + } else if (obj[this.getDescriptionObjectName(obj)]["yAxis"] && obj[this.getDescriptionObjectName(obj)]["yAxis"]["name"]) { + title = obj[this.getDescriptionObjectName(obj)]["yAxis"]["name"]; + obj[this.getDescriptionObjectName(obj)]["yAxis"]["name"] = ChartHelper.prefix + "xAxisTitle" + ChartHelper.suffix; + } + indicatorPath.parameters["yAxisTitle"] = title ? title : ""; + } + + private extractOldToolTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj["title"]) { + title = obj["title"]; + obj["title"] = ChartHelper.prefix + "title" + ChartHelper.suffix; + indicatorPath.parameters["title"] = title; + + } + } + + private extractOldToolXTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj["xaxistitle"]) { + title = obj["xaxistitle"]; + obj["xaxistitle"] = ChartHelper.prefix + "xAxisTitle" + ChartHelper.suffix; + indicatorPath.parameters["xAxisTitle"] = title; + } + } + + private extractOldToolYTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj["fieldsheaders"]) { + title = Array.isArray(obj["fieldsheaders"]) ? obj["fieldsheaders"][0] : obj["fieldsheaders"]; + if (Array.isArray(obj["fieldsheaders"])) { + obj["fieldsheaders"][0] = ChartHelper.prefix + "yAxisTitle" + ChartHelper.suffix; + } else { + obj["fieldsheaders"] = ChartHelper.prefix + "yAxisTitle" + ChartHelper.suffix; + } + indicatorPath.parameters["yAxisTitle"] = title; + } + } + + public checkForSchemaEnhancements(url: string): boolean { + return url != this.applySchemaEnhancements(url); + } + + public applySchemaEnhancements(url: string): string { + let resultEnhancements = [ + [".project.acronym", ".project acronym"], + [".project.title", ".project title"], + [".project.funder", ".project funder"], + [".project.funding level 0", ".project funding level 0"], + [".datasource.name", ".HostedBy datasource"], + [".datasource.type", ".HostedBy datasource type"] + ]; + let changes = ""; + for (let field of resultEnhancements) { + for (let type of ["publication", "software", "dataset", "other", "result"]) { + if (url.indexOf(encodeURIComponent(type + field[0])) != -1) { + changes += "Changed " + type + field[0] + " to " + type + field[1] + "\n"; + url = url.split(encodeURIComponent(type + field[0])).join(encodeURIComponent(type + field[1])); + } + } + } + + if (url.split('json=').length > 1) { + let obj = JSON.parse(decodeURIComponent(url.split('json=')[1])); + for (let query of this.getQueryObjectName(obj) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)] : obj[this.getDescriptionObjectName(obj)]) { + if (!query["query"]["profile"] || query["query"]["profile"] == 'OpenAIRE All-inclusive' || query["query"]["profile"] == 'OpenAIRE original') { + changes += (query["query"]["profile"] ? ("Changed profile \"" + query["query"]["profile"] + "\" to ") : "Added profile ") + " \"monitor\""; + query["query"]["profile"] = 'monitor'; + } + } + url = url.split('json=')[0] + "json=" + encodeURIComponent(JSON.stringify(obj)); + } + return url; + } +} diff --git a/monitor-admin/utils/services/statistics.service.ts b/monitor-admin/utils/services/statistics.service.ts new file mode 100644 index 00000000..d4b6031c --- /dev/null +++ b/monitor-admin/utils/services/statistics.service.ts @@ -0,0 +1,24 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {SourceType} from "../../../monitor/entities/stakeholder"; +import {IndicatorUtils} from "../indicator-utils"; + + +@Injectable({ + providedIn: 'root' +}) +export class StatisticsService { + + indicatorsUtils = new IndicatorUtils(); + + constructor(private http: HttpClient) {} + + getNumbers(source: SourceType, url: string): Observable { + if (source !== null) { + return this.http.get(this.indicatorsUtils.getNumberUrl(source, url)); + } else { + return this.http.get(url); + } + } +} diff --git a/monitor-admin/utils/services/stats-profiles.service.ts b/monitor-admin/utils/services/stats-profiles.service.ts new file mode 100644 index 00000000..75cecc54 --- /dev/null +++ b/monitor-admin/utils/services/stats-profiles.service.ts @@ -0,0 +1,19 @@ +import {Injectable} from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {properties} from "src/environments/environment"; +import {Observable} from "rxjs"; +import {map} from "rxjs/operators"; + +@Injectable({ + providedIn: 'root' +}) +export class StatsProfilesService { + + constructor(private http: HttpClient) { + } + + getStatsProfiles(): Observable { + return this.http.get(properties.monitorStatsFrameUrl + 'schema/profiles') + .pipe(map(profiles => profiles.map(profile => profile.name))); + } +} diff --git a/services/servicesUtils/customOptions.class.ts b/services/servicesUtils/customOptions.class.ts index 4a9572e4..c935ac31 100644 --- a/services/servicesUtils/customOptions.class.ts +++ b/services/servicesUtils/customOptions.class.ts @@ -1,4 +1,3 @@ -import {COOKIE} from '../../login/utils/helper.class'; import {HttpHeaders} from "@angular/common/http"; export type MediaType = 'application/json' | 'text/plain' From cf30ab658157157c898cb9a9b6211eee77cd5ef0 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Thu, 19 Oct 2023 16:33:04 +0300 Subject: [PATCH 2/4] [monitor-admin-library | DONE | CHANGED]: Move topics and cache indicators in library. --- .../edit-stakeholder.component.less | 10 + .../edit-stakeholder.component.ts | 443 +++++ .../edit-stakeholder.module.ts | 14 + .../general/general-routing.module.ts | 19 + monitor-admin/general/general.component.html | 29 + monitor-admin/general/general.component.ts | 75 + monitor-admin/general/general.module.ts | 40 + monitor-admin/topic/indicators.component.html | 490 ++++++ monitor-admin/topic/indicators.component.less | 53 + monitor-admin/topic/indicators.component.ts | 1494 +++++++++++++++++ monitor-admin/topic/topic-routing.module.ts | 19 + monitor-admin/topic/topic.component.html | 380 +++++ monitor-admin/topic/topic.component.ts | 795 +++++++++ monitor-admin/topic/topic.module.ts | 49 + monitor-admin/utils/adminDashboard.guard.ts | 43 + .../cache-indicators.component.less | 9 + .../cache-indicators.component.ts | 78 + .../cache-indicators.module.ts | 11 + .../cache-indicators.service.ts | 24 + .../cache-indicators/cache-indicators.ts | 261 +++ monitor-admin/utils/indicator-utils.ts | 1005 +++++++++++ .../utils/services/statistics.service.ts | 24 + .../utils/services/stats-profiles.service.ts | 19 + services/servicesUtils/customOptions.class.ts | 1 - 24 files changed, 5384 insertions(+), 1 deletion(-) create mode 100644 monitor-admin/general/edit-stakeholder/edit-stakeholder.component.less create mode 100644 monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts create mode 100644 monitor-admin/general/edit-stakeholder/edit-stakeholder.module.ts create mode 100644 monitor-admin/general/general-routing.module.ts create mode 100644 monitor-admin/general/general.component.html create mode 100644 monitor-admin/general/general.component.ts create mode 100644 monitor-admin/general/general.module.ts create mode 100644 monitor-admin/topic/indicators.component.html create mode 100644 monitor-admin/topic/indicators.component.less create mode 100644 monitor-admin/topic/indicators.component.ts create mode 100644 monitor-admin/topic/topic-routing.module.ts create mode 100644 monitor-admin/topic/topic.component.html create mode 100644 monitor-admin/topic/topic.component.ts create mode 100644 monitor-admin/topic/topic.module.ts create mode 100644 monitor-admin/utils/adminDashboard.guard.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.component.less create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.component.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.module.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.service.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.ts create mode 100644 monitor-admin/utils/indicator-utils.ts create mode 100644 monitor-admin/utils/services/statistics.service.ts create mode 100644 monitor-admin/utils/services/stats-profiles.service.ts diff --git a/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.less b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.less new file mode 100644 index 00000000..e5f71cdc --- /dev/null +++ b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.less @@ -0,0 +1,10 @@ +.uk-border-circle { + width: 100px; + height: 100px; + position: relative; + + & > img { + max-width: 64px; + max-height: 64px; + } +} diff --git a/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts new file mode 100644 index 00000000..b66b680a --- /dev/null +++ b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts @@ -0,0 +1,443 @@ +import {Component, Input, OnDestroy, ViewChild} from "@angular/core"; +import {Stakeholder} from "../../openaireLibrary/monitor/entities/stakeholder"; +import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms"; +import {StakeholderUtils} from "../../openaireLibrary/monitor-admin/utils/indicator-utils"; +import {Option} from "../../openaireLibrary/sharedComponents/input/input.component"; +import {Subscription} from "rxjs"; +import {EnvProperties} from "../../openaireLibrary/utils/properties/env-properties"; +import {properties} from "../../../environments/environment"; +import {StakeholderService} from "../../openaireLibrary/monitor/services/stakeholder.service"; +import {UtilitiesService} from "../../openaireLibrary/services/utilities.service"; +import {Role, Session, User} from "../../openaireLibrary/login/utils/helper.class"; +import {UserManagementService} from "../../openaireLibrary/services/user-management.service"; +import {StringUtils} from "../../openaireLibrary/utils/string-utils.class"; +import {NotifyFormComponent} from "../../openaireLibrary/notifications/notify-form/notify-form.component"; +import {NotificationUtils} from "../../openaireLibrary/notifications/notification-utils"; +import {Notification} from "../../openaireLibrary/notifications/notifications"; +import {NotificationHandler} from "../../openaireLibrary/utils/notification-handler"; +import {StatsProfilesService} from "../../openaireLibrary/monitor-admin/utils/services/stats-profiles.service"; + +@Component({ + selector: 'edit-stakeholder', + template: ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+ OR +
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
{{uploadError}}
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+ `, + styleUrls: ['edit-stakeholder.component.less'] +}) +export class EditStakeholderComponent implements OnDestroy { + @Input() + public disableAlias: boolean = false; + public stakeholderFb: UntypedFormGroup; + public secure: boolean = false; + public stakeholderUtils: StakeholderUtils = new StakeholderUtils(); + public defaultStakeholdersOptions: Option[]; + public defaultStakeholders: Stakeholder[]; + public alias: string[]; + public stakeholder: Stakeholder; + public isDefault: boolean; + public isNew: boolean; + public loading: boolean = false; + public types: Option[]; + public statsProfiles: string[]; + public properties: EnvProperties = properties; + private subscriptions: any[] = []; + /** + * Photo upload + * */ + public file: File; + public photo: string | ArrayBuffer; + public uploadError: string; + public deleteCurrentPhoto: boolean = false; + private maxsize: number = 200 * 1024; + user: User; + @ViewChild('notify', {static: true}) notify: NotifyFormComponent; + private notification: Notification; + + constructor(private fb: UntypedFormBuilder, + private stakeholderService: StakeholderService, + private statsProfileService: StatsProfilesService, + private utilsService: UtilitiesService, private userManagementService: UserManagementService,) { + } + + ngOnDestroy() { + this.reset(); + } + + public init(stakeholder: Stakeholder, alias: string[], defaultStakeholders: Stakeholder[], isDefault: boolean, isNew: boolean) { + this.reset(); + this.deleteCurrentPhoto = false; + this.stakeholder = stakeholder; + this.alias = alias; + this.defaultStakeholders = defaultStakeholders; + this.isDefault = isDefault; + this.isNew = isNew; + this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + this.user = user; + if (this.isCurator) { + this.subscriptions.push(this.statsProfileService.getStatsProfiles().subscribe(statsProfiles => { + this.statsProfiles = statsProfiles; + }, error => { + this.statsProfiles = []; + })); + } else { + this.statsProfiles = []; + } + this.types = this.stakeholderUtils.getTypesByUserRoles(this.user, this.stakeholder.alias); + this.stakeholderFb = this.fb.group({ + _id: this.fb.control(this.stakeholder._id), + defaultId: this.fb.control(this.stakeholder.defaultId), + name: this.fb.control(this.stakeholder.name, Validators.required), + description: this.fb.control(this.stakeholder.description), + index_name: this.fb.control(this.stakeholder.index_name, Validators.required), + index_id: this.fb.control(this.stakeholder.index_id, Validators.required), + index_shortName: this.fb.control(this.stakeholder.index_shortName, Validators.required), + statsProfile: this.fb.control(this.stakeholder.statsProfile, Validators.required), + locale: this.fb.control(this.stakeholder.locale, Validators.required), + projectUpdateDate: this.fb.control(this.stakeholder.projectUpdateDate), + creationDate: this.fb.control(this.stakeholder.creationDate), + alias: this.fb.control(this.stakeholder.alias, + [ + Validators.required, + this.stakeholderUtils.aliasValidatorString( + this.alias.filter(alias => alias !== this.stakeholder.alias) + )] + ), + isDefault: this.fb.control((this.isDefault)), + visibility: this.fb.control(this.stakeholder.visibility, Validators.required), + type: this.fb.control(this.stakeholder.type, Validators.required), + topics: this.fb.control(this.stakeholder.topics), + isUpload: this.fb.control(this.stakeholder.isUpload), + logoUrl: this.fb.control(this.stakeholder.logoUrl), + }); + if (this.stakeholder.isUpload) { + this.stakeholderFb.get('logoUrl').clearValidators(); + this.stakeholderFb.get('logoUrl').updateValueAndValidity(); + } else { + this.stakeholderFb.get('logoUrl').setValidators([StringUtils.urlValidator()]); + this.stakeholderFb.get('logoUrl').updateValueAndValidity(); + } + this.subscriptions.push(this.stakeholderFb.get('isUpload').valueChanges.subscribe(value => { + if (value == true) { + this.stakeholderFb.get('logoUrl').clearValidators(); + this.stakeholderFb.updateValueAndValidity(); + } else { + this.stakeholderFb.get('logoUrl').setValidators([StringUtils.urlValidator()]); + this.stakeholderFb.updateValueAndValidity(); + } + })); + this.secure = (!this.stakeholderFb.get('logoUrl').value || this.stakeholderFb.get('logoUrl').value.includes('https://')); + this.subscriptions.push(this.stakeholderFb.get('logoUrl').valueChanges.subscribe(value => { + this.secure = (!value || value.includes('https://')); + })); + this.initPhoto(); + this.subscriptions.push(this.stakeholderFb.get('type').valueChanges.subscribe(value => { + this.onTypeChange(value, defaultStakeholders); + })); + this.stakeholderFb.setControl('defaultId', this.fb.control(stakeholder.defaultId, (this.isDefault && !this.isNew)?[]:Validators.required)); + if (!this.isNew) { + this.notification = NotificationUtils.editStakeholder(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.notify.reset(this.notification.message); + if (this.isAdmin) { + if (this.disableAlias) { + setTimeout(() => { + this.stakeholderFb.get('alias').disable(); + }, 0); + } + } else { + if (!this.isCurator) { + setTimeout(() => { + this.stakeholderFb.get('statsProfile').disable(); + }, 0); + } + setTimeout(() => { + this.stakeholderFb.get('alias').disable(); + this.stakeholderFb.get('index_id').disable(); + this.stakeholderFb.get('index_name').disable(); + this.stakeholderFb.get('index_shortName').disable(); + }, 0); + } + setTimeout(() => { + this.stakeholderFb.get('type').disable(); + }, 0); + } else { + this.notification = NotificationUtils.createStakeholder(this.user.firstname + ' ' + this.user.lastname); + this.notify.reset(this.notification.message); + setTimeout(() => { + this.stakeholderFb.get('type').enable(); + }, 0); + } + })); + } + + public get isAdmin() { + return Session.isPortalAdministrator(this.user); + } + + public get isCurator() { + return this.stakeholder && (this.isAdmin || Session.isCurator(this.stakeholder.type, this.user)); + } + + public get disabled(): boolean { + return (this.stakeholderFb && this.stakeholderFb.invalid) || + (this.stakeholderFb && this.stakeholderFb.pristine && !this.isNew && !this.file) || + (this.uploadError && this.uploadError.length > 0); + } + + public get dirty(): boolean { + return this.stakeholderFb && this.stakeholderFb.dirty; + } + + public get canChooseTemplate(): boolean { + return this.isNew && this.stakeholderFb.get('type').valid && !!this.defaultStakeholdersOptions; + } + + reset() { + this.uploadError = null; + this.stakeholderFb = null; + this.subscriptions.forEach(subscription => { + if (subscription instanceof Subscription) { + subscription.unsubscribe(); + } + }); + } + + onTypeChange(value, defaultStakeholders: Stakeholder[]) { + this.stakeholderFb.setControl('defaultId', this.fb.control(this.stakeholder.defaultId, (this.isDefault && !this.isNew)?[]:Validators.required)); + this.defaultStakeholdersOptions = [{ + label: 'New blank profile', + value: '-1' + }]; + defaultStakeholders.filter(stakeholder => stakeholder.type === value).forEach(stakeholder => { + this.defaultStakeholdersOptions.push({ + label: 'Use ' + stakeholder.name + ' profile', + value: stakeholder._id + }) + }); + } + + public save(callback: Function, errorCallback: Function = null) { + this.loading = true; + if (this.file) { + this.subscriptions.push(this.utilsService.uploadPhoto(this.properties.utilsService + "/upload/" + encodeURIComponent(this.stakeholderFb.getRawValue().type) + "/" + encodeURIComponent(this.stakeholderFb.getRawValue().alias), this.file).subscribe(res => { + this.deletePhoto(); + this.stakeholderFb.get('logoUrl').setValue(res.filename); + this.removePhoto(); + this.saveStakeholder(callback, errorCallback); + }, error => { + this.uploadError = "An error has been occurred during upload your image. Try again later"; + this.saveStakeholder(callback, errorCallback); + })); + } else if (this.deleteCurrentPhoto) { + this.deletePhoto(); + this.saveStakeholder(callback, errorCallback); + } else { + this.saveStakeholder(callback, errorCallback); + } + } + + public saveStakeholder(callback: Function, errorCallback: Function = null) { + if (this.isNew) { + let defaultStakeholder = this.defaultStakeholders.find(value => value._id === this.stakeholderFb.getRawValue().defaultId); + this.stakeholderFb.setValue(this.stakeholderUtils.createFunderFromDefaultProfile(this.stakeholderFb.getRawValue(), + (defaultStakeholder ? defaultStakeholder.topics : []), this.stakeholderFb.getRawValue().isDefault)); + this.removePhoto(); + if(this.stakeholderFb.getRawValue().isDefault) { + this.stakeholderFb.get('defaultId').setValue(null); + } + this.subscriptions.push(this.stakeholderService.buildStakeholder(this.properties.monitorServiceAPIURL, + this.stakeholderFb.getRawValue()).subscribe(stakeholder => { + this.notification.entity = stakeholder._id; + this.notification.stakeholder = stakeholder.alias; + this.notification.stakeholderType = stakeholder.type; + this.notification.groups = [Role.curator(stakeholder.type)]; + this.notify.sendNotification(this.notification); + NotificationHandler.rise(stakeholder.name + ' has been successfully created'); + callback(stakeholder); + this.loading = false; + }, error => { + NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); + if (errorCallback) { + errorCallback(error) + } + this.loading = false; + })); + } else { + this.subscriptions.push(this.stakeholderService.saveElement(this.properties.monitorServiceAPIURL, this.stakeholderFb.getRawValue()).subscribe(stakeholder => { + this.notification.entity = stakeholder._id; + this.notification.stakeholder = stakeholder.alias; + this.notification.stakeholderType = stakeholder.type; + this.notification.groups = [Role.curator(stakeholder.type), Role.manager(stakeholder.type, stakeholder.alias)]; + this.notify.sendNotification(this.notification); + NotificationHandler.rise(stakeholder.name + ' has been successfully saved'); + callback(stakeholder); + this.loading = false; + }, error => { + NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); + if (errorCallback) { + errorCallback(error) + } + this.loading = false; + })); + } + } + + fileChangeEvent(event) { + if (event.target.files && event.target.files[0]) { + this.file = event.target.files[0]; + if (this.file.type !== 'image/png' && this.file.type !== 'image/jpeg') { + this.uploadError = 'You must choose a file with type: image/png or image/jpeg!'; + this.stakeholderFb.get('isUpload').setValue(false); + this.stakeholderFb.get('isUpload').markAsDirty(); + this.removePhoto(); + } else if (this.file.size > this.maxsize) { + this.uploadError = 'File exceeds size\'s limit! Maximum resolution is 256x256 pixels.'; + this.stakeholderFb.get('isUpload').setValue(false); + this.stakeholderFb.get('isUpload').markAsDirty(); + this.removePhoto(); + } else { + this.uploadError = null; + const reader = new FileReader(); + reader.readAsDataURL(this.file); + reader.onload = () => { + this.photo = reader.result; + this.stakeholderFb.get('isUpload').setValue(true); + this.stakeholderFb.get('isUpload').markAsDirty(); + }; + } + } + } + + initPhoto() { + if (this.stakeholderFb.getRawValue().isUpload) { + this.photo = this.properties.utilsService + "/download/" + this.stakeholderFb.get('logoUrl').value; + } + } + + removePhoto() { + if (this.file) { + if (typeof document != 'undefined') { + (document.getElementById("photo")).value = ""; + } + this.initPhoto(); + this.file = null; + } + } + + remove() { + this.stakeholderFb.get('isUpload').setValue(false); + this.stakeholderFb.get('isUpload').markAsDirty(); + this.removePhoto(); + this.stakeholderFb.get('logoUrl').setValue(null); + if (this.stakeholder.isUpload) { + this.deleteCurrentPhoto = true; + } + } + + public deletePhoto() { + if (this.stakeholder.logoUrl && this.stakeholder.isUpload) { + this.subscriptions.push(this.utilsService.deletePhoto(this.properties.utilsService + '/delete/' + + encodeURIComponent(this.stakeholder.type) + "/" + encodeURIComponent(this.stakeholder.alias) + "/" + this.stakeholder.logoUrl).subscribe()); + } + } +} diff --git a/monitor-admin/general/edit-stakeholder/edit-stakeholder.module.ts b/monitor-admin/general/edit-stakeholder/edit-stakeholder.module.ts new file mode 100644 index 00000000..6f08416b --- /dev/null +++ b/monitor-admin/general/edit-stakeholder/edit-stakeholder.module.ts @@ -0,0 +1,14 @@ +import {NgModule} from "@angular/core"; +import {EditStakeholderComponent} from "./edit-stakeholder.component"; +import {CommonModule} from "@angular/common"; +import {InputModule} from "../../openaireLibrary/sharedComponents/input/input.module"; +import {ReactiveFormsModule} from "@angular/forms"; +import {IconsModule} from "../../openaireLibrary/utils/icons/icons.module"; +import {NotifyFormModule} from "../../openaireLibrary/notifications/notify-form/notify-form.module"; + +@NgModule({ + imports: [CommonModule, InputModule, ReactiveFormsModule, IconsModule, NotifyFormModule], + declarations: [EditStakeholderComponent], + exports: [EditStakeholderComponent] +}) +export class EditStakeholderModule {} diff --git a/monitor-admin/general/general-routing.module.ts b/monitor-admin/general/general-routing.module.ts new file mode 100644 index 00000000..0dec7485 --- /dev/null +++ b/monitor-admin/general/general-routing.module.ts @@ -0,0 +1,19 @@ +import {NgModule} from '@angular/core'; +import {RouterModule} from '@angular/router'; +import {PreviousRouteRecorder} from '../openaireLibrary/utils/piwik/previousRouteRecorder.guard'; +import {GeneralComponent} from "./general.component"; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + component: GeneralComponent, + canDeactivate: [PreviousRouteRecorder], + data: {hasSidebar: true} + } + ]) + ] +}) +export class GeneralRoutingModule { +} diff --git a/monitor-admin/general/general.component.html b/monitor-admin/general/general.component.html new file mode 100644 index 00000000..b6617a50 --- /dev/null +++ b/monitor-admin/general/general.component.html @@ -0,0 +1,29 @@ +
+
+ +
+
+ + +
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
diff --git a/monitor-admin/general/general.component.ts b/monitor-admin/general/general.component.ts new file mode 100644 index 00000000..03045f06 --- /dev/null +++ b/monitor-admin/general/general.component.ts @@ -0,0 +1,75 @@ +import {ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from "@angular/core"; +import {StakeholderService} from "../openaireLibrary/monitor/services/stakeholder.service"; +import {EnvProperties} from "../openaireLibrary/utils/properties/env-properties"; +import {Stakeholder} from "../openaireLibrary/monitor/entities/stakeholder"; +import { Subscription, zip} from "rxjs"; +import {EditStakeholderComponent} from "./edit-stakeholder/edit-stakeholder.component"; +import {properties} from "../../environments/environment"; +import {Title} from "@angular/platform-browser"; + +@Component({ + selector: 'general', + templateUrl: "./general.component.html" +}) +export class GeneralComponent implements OnInit, OnDestroy { + + public stakeholder: Stakeholder; + public alias: string[]; + public properties: EnvProperties = properties; + public defaultStakeholders: Stakeholder[]; + public loading: boolean = false; + private subscriptions: any[] = []; + @ViewChild('editStakeholderComponent') editStakeholderComponent: EditStakeholderComponent; + + constructor(private stakeholderService: StakeholderService, + private cdr: ChangeDetectorRef, + private title: Title) { + } + + ngOnInit() { + this.loading = true; + this.subscriptions.push(this.stakeholderService.getStakeholderAsObservable().subscribe(stakeholder => { + this.stakeholder = stakeholder; + this.cdr.detectChanges(); + if(this.stakeholder) { + this.title.setTitle(this.stakeholder.name + " | General"); + let data = zip( + this.stakeholderService.getDefaultStakeholders(this.properties.monitorServiceAPIURL), + this.stakeholderService.getAlias(this.properties.monitorServiceAPIURL) + ); + this.subscriptions.push(data.subscribe(res => { + this.defaultStakeholders = res[0]; + this.alias = res[1]; + this.reset(); + this.loading = false; + })); + } + })); + } + + public reset() { + this.editStakeholderComponent.init(this.stakeholder, this.alias, this.defaultStakeholders, this.stakeholder.defaultId == null, false) + } + + + public save() { + this.loading = true; + this.editStakeholderComponent.save((stakeholder) => { + this.stakeholder = stakeholder; + this.stakeholderService.setStakeholder(this.stakeholder); + this.reset(); + this.loading = false; + }, (error) => { + console.error(error); + this.loading = false; + }); + } + + ngOnDestroy() { + this.subscriptions.forEach(subscription => { + if(subscription instanceof Subscription) { + subscription.unsubscribe(); + } + }); + } +} diff --git a/monitor-admin/general/general.module.ts b/monitor-admin/general/general.module.ts new file mode 100644 index 00000000..054c309f --- /dev/null +++ b/monitor-admin/general/general.module.ts @@ -0,0 +1,40 @@ +import {NgModule} from "@angular/core"; +import {GeneralComponent} from "./general.component"; +import {GeneralRoutingModule} from "./general-routing.module"; +import {PreviousRouteRecorder} from "../openaireLibrary/utils/piwik/previousRouteRecorder.guard"; +import {CommonModule} from "@angular/common"; +import {RouterModule} from "@angular/router"; +import {InputModule} from "../openaireLibrary/sharedComponents/input/input.module"; +import {LoadingModule} from "../openaireLibrary/utils/loading/loading.module"; +import {AlertModalModule} from "../openaireLibrary/utils/modal/alertModal.module"; +import {ReactiveFormsModule} from "@angular/forms"; +import {EditStakeholderModule} from "./edit-stakeholder/edit-stakeholder.module"; +import {PageContentModule} from "../openaireLibrary/dashboard/sharedComponents/page-content/page-content.module"; +import {LogoUrlPipeModule} from "../openaireLibrary/utils/pipes/logoUrlPipe.module"; +import { + SidebarMobileToggleModule +} from "../openaireLibrary/dashboard/sharedComponents/sidebar/sidebar-mobile-toggle/sidebar-mobile-toggle.module"; + +@NgModule({ + declarations: [GeneralComponent], + imports: [ + GeneralRoutingModule, + CommonModule, + RouterModule, + InputModule, + LoadingModule, + AlertModalModule, + ReactiveFormsModule, + EditStakeholderModule, + PageContentModule, + LogoUrlPipeModule, + SidebarMobileToggleModule + ], + providers: [ + PreviousRouteRecorder, + ], + exports: [GeneralComponent] +}) +export class GeneralModule { + +} diff --git a/monitor-admin/topic/indicators.component.html b/monitor-admin/topic/indicators.component.html new file mode 100644 index 00000000..ecdc0cdb --- /dev/null +++ b/monitor-admin/topic/indicators.component.html @@ -0,0 +1,490 @@ +
+
+
Number Indicators
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+ + + + +
+ +
+
+
{{indicator.name}}
+
+ + -- +
+
+
+
+
+ +
+
+
+ +
+
+
+
+
Chart Indicators
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+ + + + +
+ +
+
+
+
+ {{indicator.name}} +
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+ Create a custom indicator +
+
+ Use our advance tool to create a custom Indicator that suit the needs of your funding + KPI's. +
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{urlParameterizedMessage}}
+
+
+ +
+
+
+
+ There are schema enhancements that can be applied in this query.Apply + now +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JSON Path +
+
+
+ This JSON path is not valid or the result has not been calculated yet. + Please press calculate on box below to see the result. +
+
+
+
+
+ + + + + + +
+
+ +
+
+
+
+
+ + + + + -- + + + +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{urlParameterizedMessage}}
+
+
+ +
+
+
+
+ There are schema enhancements that can be applied in this query. Apply + now +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+ +
+ You are about to delete + "{{indicator.name ? indicator.name : (indicator.indicatorPaths[0]?.parameters?.title?indicator.indicatorPaths[0].parameters.title:'')}}" indicator permanently. +
+ Indicators of all profiles based on this default indicator, will be deleted as well. +
+ + + + Are you sure you want to proceed? +
+
+
+ + +
+
+ +
+ You are about to delete this section and its indicators permanently. +
+ Sections of all profiles based on this default section and their contents, will be deleted as well. +
+ + + + Are you sure you want to proceed? +
+
+ + +
+
+ +
+
+
diff --git a/monitor-admin/topic/indicators.component.less b/monitor-admin/topic/indicators.component.less new file mode 100644 index 00000000..627ab18a --- /dev/null +++ b/monitor-admin/topic/indicators.component.less @@ -0,0 +1,53 @@ +@import (reference) "~src/assets/openaire-theme/less/_import-variables"; + +.number-preview { + border: @global-border-width solid @global-border; + background: transparent; + border-radius: @global-border-radius; + min-width: 100px; + min-height: 70px; +} + +.refresh-indicator { + background-color: @global-overlay-background; + border-radius: @global-border-radius; + position: absolute; + color: @global-inverse-color; + z-index: 1; +} + +.section { + padding: 60px 45px; + border-radius: @global-border-radius; + border: @global-border-width solid @global-border; + position: relative; + background: @global-inverse-color; + border-left: 5px @global-primary-background solid; + + .tools { + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, -100%); + max-width: 50px; + padding: 5px 10px; + background-image: @global-primary-gradient; + color: @global-inverse-color; + -webkit-clip-path: polygon(20% 5%, 80% 5%, 100% 100%, 0% 100%); + clip-path: polygon(20% 5%, 80% 5%, 100% 100%, 0% 100%); + display: none; + } + + &:hover { + .tools { + display: block; + + a { + color: currentColor; + &:hover { + text-decoration: none; + } + } + } + } +} diff --git a/monitor-admin/topic/indicators.component.ts b/monitor-admin/topic/indicators.component.ts new file mode 100644 index 00000000..d97f5f82 --- /dev/null +++ b/monitor-admin/topic/indicators.component.ts @@ -0,0 +1,1494 @@ +import { + AfterViewInit, + ChangeDetectorRef, + Component, + HostListener, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + ViewChild +} from "@angular/core"; +import { + Format, + Indicator, + IndicatorPath, + IndicatorSize, + IndicatorType, + Section, + Stakeholder, + Visibility +} from "../../monitor/entities/stakeholder"; +import {IndicatorUtils, StakeholderUtils} from "../utils/indicator-utils"; +import { + AbstractControl, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validators +} from "@angular/forms"; +import {AlertModal} from "../../utils/modal/alert"; +import {StatisticsService} from "../utils/services/statistics.service"; +import {HelperFunctions} from "../../utils/HelperFunctions.class"; +import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser"; +import {Reorder, StakeholderService} from "../../monitor/services/stakeholder.service"; +import {EnvProperties} from "../../utils/properties/env-properties"; +import {Observable, Subscriber} from "rxjs"; +import {LayoutService} from "../../dashboard/sharedComponents/sidebar/layout.service"; +import {Router} from "@angular/router"; +import {Role, Session, User} from "../../login/utils/helper.class"; +import {StringUtils} from "../../utils/string-utils.class"; +import {Notification} from "../../notifications/notifications"; +import {NotificationUtils} from "../../notifications/notification-utils"; +import {NotifyFormComponent} from "../../notifications/notify-form/notify-form.component"; +import {NotificationService} from "../../notifications/notification.service"; +import {properties} from "src/environments/environment"; +import {NotificationHandler} from "../../utils/notification-handler"; + +declare var UIkit; + +@Component({ + selector: 'indicators', + templateUrl: './indicators.component.html', + styleUrls: ['indicators.component.less'] +}) +export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { + filesToUpload: Array; + errorMessage = ""; + public properties: EnvProperties = properties; + @Input() + public topicIndex: number = 0; + @Input() + public categoryIndex: number = 0; + @Input() + public subcategoryIndex: number = 0; + @Input() + public stakeholder: Stakeholder = null; + @Input() + public changed: Observable; + @Input() + public user: User = null; + public preview: string; + public indicatorUtils: IndicatorUtils = new IndicatorUtils(); + public stakeholderUtils: StakeholderUtils = new StakeholderUtils(); + public numberIndicatorFb: UntypedFormGroup; + public chartIndicatorFb: UntypedFormGroup; + public chartSections: UntypedFormArray; + public numberSections: UntypedFormArray; + /** + * Editable indicator + */ + public section: Section; + public indicator: Indicator; + public index: number = -1; + public editing: boolean = false; + public dragging: boolean = false; + /** Caches */ + public safeUrls: Map = new Map([]); + public numberResponses: Map = new Map(); + public numberResults: Map = new Map(); + /** Import / Export Indicators */ + importLoading: boolean = false; + @ViewChild('editChartModal', {static: true}) editChartModal: AlertModal; + @ViewChild('editNumberModal', {static: true}) editNumberModal: AlertModal; + @ViewChild('deleteModal', {static: true}) deleteModal: AlertModal; + @ViewChild('deleteSectionModal', {static: true}) deleteSectionModal: AlertModal; + public sectionTypeToDelete: string; + public sectionChildrenActionOnDelete: string; + public indicatorChildrenActionOnDelete: string; + urlParameterizedMessage = null; + showCheckForSchemaEnhancements: boolean = false; + private notification: Notification; + @ViewChild('editNumberNotify', {static: true}) editNumberNotify: NotifyFormComponent; + @ViewChild('editChartNotify', {static: true}) editChartNotify: NotifyFormComponent; + @ViewChild('deleteNotify', {static: true}) deleteNotify: NotifyFormComponent; + + public isFullscreen: boolean = false; + + @HostListener('fullscreenchange', ['$event']) + @HostListener('webkitfullscreenchange', ['$event']) + @HostListener('mozfullscreenchange', ['$event']) + @HostListener('MSFullscreenChange', ['$event']) + screenChange(event) { + this.isFullscreen = !this.isFullscreen; + } + + /** + * Subscriptions + **/ + private subscriptions: any[] = []; + private urlSubscriptions: any[] = []; + private numberSubscription: any[] = []; + + constructor(private layoutService: LayoutService, + private stakeholderService: StakeholderService, + private statisticsService: StatisticsService, + private notificationService: NotificationService, + private fb: UntypedFormBuilder, + private router: Router, + private cdr: ChangeDetectorRef, + private sanitizer: DomSanitizer) { + this.filesToUpload = []; + } + + ngOnInit(): void { + if (this.stakeholder) { + this.setCharts(); + this.setNumbers(); + } + this.changed.subscribe(() => { + this.setCharts(); + this.setNumbers(); + this.initReorder(); + }) + } + + ngOnDestroy(): void { + this.subscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } else if (value instanceof Function) { + value(); + } + }); + this.urlSubscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.numberSubscription.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + } + + ngAfterViewInit(): void { + this.initReorder(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (this.canEdit) { + if (changes.topicIndex || changes.categoryIndex || changes.subcategoryIndex) { + this.initReorder(); + this.setCharts(); + this.setNumbers(); + } + } + } + + initReorder() { + this.subscriptions.forEach(value => { + if (value instanceof Function) { + value(); + } + }); + if (document !== undefined) { + let callback = (list, type: IndicatorType, action: 'moved' | 'added' | 'removed'): void => { + let items: HTMLCollection = list.current.children; + let reordered = []; + for (let i = 0; i < items.length; i++) { + if (items.item(i).id) { + reordered.push(items.item(i).id); + } + } + let reorder: Reorder = { + action: action, + target: list.detail[1].id, + ids: reordered + } + this.reorderIndicators(list.current.id.toString().split('-')[1], type, reorder); + }; + this.numbers.forEach((section) => { + this.subscriptions.push(UIkit.util.on(document, 'start', '#number-' + section._id, (): void => { + this.dragging = true; + })); + this.subscriptions.push(UIkit.util.on(document, 'stop', '#number-' + section._id, (): void => { + this.dragging = false; + })); + this.subscriptions.push(UIkit.util.on(document, 'moved', '#number-' + section._id, (list): void => { + callback(list, "number", 'moved'); + })); + this.subscriptions.push(UIkit.util.on(document, 'added', '#number-' + section._id, (list): void => { + callback(list, "number", 'added'); + })); + this.subscriptions.push(UIkit.util.on(document, 'removed', '#number-' + section._id, (list): void => { + callback(list, "number", 'removed'); + })); + }); + this.charts.forEach((section) => { + this.subscriptions.push(UIkit.util.on(document, 'moved', '#chart-' + section._id, (list): void => { + callback(list, "chart", 'moved'); + })); + this.subscriptions.push(UIkit.util.on(document, 'added', '#chart-' + section._id, (list): void => { + callback(list, "chart", 'added'); + })); + this.subscriptions.push(UIkit.util.on(document, 'removed', '#chart-' + section._id, (list): void => { + callback(list, "chart", 'removed'); + })); + }); + } + } + + hide(element: any) { + UIkit.dropdown(element).hide(); + } + + setCharts() { + this.chartSections = this.fb.array([]); + this.charts.forEach(section => { + this.chartSections.push(this.fb.group({ + _id: this.fb.control(section._id), + title: this.fb.control(section.title), + creationDate: this.fb.control(section.creationDate), + stakeholderAlias: this.fb.control(section.stakeholderAlias), + defaultId: this.fb.control(section.defaultId), + type: this.fb.control(section.type), + indicators: this.fb.control(section.indicators) + })); + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + let url = this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath); + if (!this.safeUrls.get('url')) { + indicatorPath.safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + this.safeUrls.set(url, indicatorPath.safeResourceUrl); + } + }); + }) + }); + } + + setNumbers() { + this.numberSections = this.fb.array([]); + this.numberResults.clear(); + let urls: Map = new Map(); + this.numbers.forEach((section, i) => { + this.numberSections.push(this.fb.group({ + _id: this.fb.control(section._id), + title: this.fb.control(section.title), + creationDate: this.fb.control(section.creationDate), + stakeholderAlias: this.fb.control(section.stakeholderAlias), + defaultId: this.fb.control(section.defaultId), + type: this.fb.control(section.type), + indicators: this.fb.control(section.indicators) + })); + section.indicators.forEach((number, j) => { + let url = this.indicatorUtils.getFullUrl(this.stakeholder, 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); + }); + }); + this.numberSubscription.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + urls.forEach((indexes, pair) => { + let parsed = JSON.parse(pair); + let response = this.numberResponses.get(pair); + if (response) { + this.calculateResults(response, indexes); + } else { + this.numberSubscription.push(this.statisticsService.getNumbers(this.indicatorUtils.getSourceType(parsed[0]), parsed[1]).subscribe(response => { + this.calculateResults(response, indexes); + this.numberResponses.set(pair, response); + })); + } + }); + } + + private calculateResults(response: any, indexes: [number, number][]) { + indexes.forEach(([i, j]) => { + let result = JSON.parse(JSON.stringify(response)); + this.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); + }); + } + + get charts(): Section[] { + if (this.stakeholder.topics[this.topicIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]) { + return this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].charts; + } else { + return []; + } + } + + get numbers(): Section[] { + if (this.stakeholder.topics[this.topicIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]) { + return this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].numbers; + } else { + return []; + } + } + + set numbers(sections: Section[]) { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].numbers = sections; + } + + get open(): boolean { + return this.layoutService.open; + } + + get canEdit() { + return this.stakeholder && + this.stakeholder.topics[this.topicIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]; + } + + public get numberIndicatorPaths(): UntypedFormArray { + return this.numberIndicatorFb.get('indicatorPaths') as UntypedFormArray; + } + + public get chartIndicatorPaths(): UntypedFormArray { + return this.chartIndicatorFb.get('indicatorPaths') as UntypedFormArray; + } + + 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 addJsonPath(index: number) { + if (index == 0 && this.getJsonPath(index).getRawValue()[index].indexOf(",") != -1) { + //if in the first path there are more than one paths comma separated, split them and autogenerate the forms + let paths = this.getJsonPath(index).getRawValue()[index].split(","); + for (let i = 0; i < paths.length; i++) { + if (i != 0) { + this.getJsonPath(index).push(this.fb.control('', Validators.required)); + } + } + this.getJsonPath(index).setValue(paths) + } else { + this.getJsonPath(index).push(this.fb.control('', Validators.required)); + } + } + + public removeJsonPath(i: number, j: number) { + if (this.getJsonPath(i).enabled) { + this.getJsonPath(i).removeAt(j); + } + } + + public validateJsonPath(index: number, dirty: boolean = false) { + let indicatorPath: UntypedFormGroup = this.numberIndicatorPaths.at(index); + if (this.indicator.defaultId === null) { + this.getJsonPath(index).disable(); + } + indicatorPath.get('result').setErrors({validating: true}); + this.subscriptions.push(this.statisticsService.getNumbers(null, indicatorPath.get('url').value).subscribe(response => { + let result = JSON.parse(JSON.stringify(response)); + this.getJsonPath(index).controls.forEach(jsonPath => { + if (result) { + result = result[jsonPath.value]; + } + }); + setTimeout(() => { + if (this.indicator.defaultId === null) { + this.getJsonPath(index).enable(); + if (dirty) { + this.getJsonPath(index).markAsDirty(); + } + } + indicatorPath.get('result').setErrors(null); + if (typeof result === 'string' || typeof result === 'number') { + result = Number(result); + if (result !== Number.NaN) { + indicatorPath.get('result').setValue(result); + } else { + indicatorPath.get('result').setValue(0); + } + } else { + indicatorPath.get('result').setValue(0); + } + }, 500); + }, error => { + setTimeout(() => { + if (this.indicator.defaultId === null) { + this.getJsonPath(index).enable(); + if (dirty) { + this.getJsonPath(index).markAsDirty(); + } + } + indicatorPath.get('result').setErrors(null); + indicatorPath.get('result').setValue(0); + }, 500); + })); + } + + public getJsonPath(index: number): UntypedFormArray { + return this.numberIndicatorPaths.at(index).get('jsonPath') as UntypedFormArray; + } + + public getCurrentJsonPath(index: number): string[] { + return this.section.indicators[this.index].indicatorPaths[index].jsonPath; + } + + public getParameters(index: number): UntypedFormArray { + return this.chartIndicatorPaths.at(index).get('parameters') as UntypedFormArray; + } + + public getParameter(index: number, key: string): UntypedFormControl { + return this.getParameters(index).controls.filter(control => control.value.key === key)[0] as UntypedFormControl; + } + + private getSecureUrlByStakeHolder(indicatorPath: IndicatorPath) { + return this.sanitizer.bypassSecurityTrustResourceUrl( + this.indicatorUtils.getChartUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath))); + } + + private getUrlByStakeHolder(indicatorPath: IndicatorPath) { + return this.indicatorUtils.getChartUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath)); + } + + public addNumberIndicatorPath(url: string = '', parameters: UntypedFormArray = new UntypedFormArray([]), source: string = 'stats-tool', jsonPath: UntypedFormArray = new UntypedFormArray([]), format: Format = "NUMBER") { + if (jsonPath.length === 0) { + jsonPath.push(this.fb.control('', Validators.required)); + } + this.numberIndicatorPaths.push(this.fb.group({ + url: this.fb.control(url, [Validators.required, StringUtils.urlValidator()]), + jsonPath: jsonPath, + result: this.fb.control(0, Validators.required), + source: this.fb.control(source, Validators.required), + format: this.fb.control(format, Validators.required) + } + )); + let index = this.numberIndicatorPaths.length - 1; + if (this.numberIndicatorPaths.at(index).get('url').valid) { + this.validateJsonPath(index); + this.checkForSchemaEnhancements(this.numberIndicatorPaths.at(index).get('url').value); + } + if (this.indicator.defaultId === null) { + this.subscriptions.push(this.numberIndicatorPaths.at(index).get('url').valueChanges.subscribe(value => { + this.numberIndicatorPaths.at(index).get('result').setValue(null); + if (this.numberIndicatorPaths.at(index).get('url').valid) { + let indicatorPath: IndicatorPath = this.indicatorUtils.generateIndicatorByNumberUrl(this.indicatorUtils.getNumberSource(value), value, this.stakeholder, this.numberIndicatorPaths.at(index).get('jsonPath').value, this.indicatorUtils.numberSources.get(this.indicatorUtils.getNumberSource(value))); + if (!this.isStakeholderParametersValid(indicatorPath)) { + // default profile + if (this.stakeholder.defaultId == null) { + this.urlParameterizedMessage = "This indicator couldn't be generated properly. Stakeholders based on this profile may not inherit the data correctly." + } else { + this.urlParameterizedMessage = "This indicator couldn't be generated properly. Please make sure chart data is for the current stakeholder." + } + } else { + this.urlParameterizedMessage = null; + } + this.checkForSchemaEnhancements(this.numberIndicatorPaths.at(index).get('url').value); + if (this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index] = indicatorPath; + } else { + this.indicator.indicatorPaths.push(indicatorPath); + } + if (indicatorPath.source) { + this.numberIndicatorPaths.at(index).get('source').setValue(indicatorPath.source); + } + if (indicatorPath.jsonPath.length > 1 && this.getJsonPath(index).length == 1) { + let paths = indicatorPath.jsonPath; + for (let i = 0; i < paths.length; i++) { + if (i == this.getJsonPath(index).length) { + this.getJsonPath(index).push(this.fb.control('', Validators.required)); + } + } + this.getJsonPath(index).setValue(paths) + } + } else { + this.urlParameterizedMessage = null; + } + }) + ); + + this.subscriptions.push(this.numberIndicatorPaths.at(index).get('jsonPath').valueChanges.subscribe(value => { + if (this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index].jsonPath = value; + } + this.numberIndicatorPaths.at(index).get('result').setValue(null); + }) + ); + this.subscriptions.push(this.numberIndicatorPaths.at(index).get('source').valueChanges.subscribe(value => { + if (this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index].source = value; + } + }) + ); + } else { + this.numberIndicatorPaths.at(index).get('url').disable(); + this.numberIndicatorPaths.at(index).get('jsonPath').disable(); + this.numberIndicatorPaths.at(index).get('source').disable(); + } + } + + public addChartIndicatorPath(value: string = '', parameters: UntypedFormArray = new UntypedFormArray([]), disableUrl: boolean = false, type: string = null) { + this.chartIndicatorPaths.push(this.fb.group({ + url: this.fb.control(value, [Validators.required, StringUtils.urlValidator()]), + parameters: parameters, + type: this.fb.control(type) + } + )); + let index = this.chartIndicatorPaths.length - 1; + if (disableUrl) { + this.chartIndicatorPaths.at(index).get('url').disable(); + } else { + this.checkForSchemaEnhancements(this.chartIndicatorPaths.at(index).get('url').value); + this.urlSubscriptions.push(this.chartIndicatorPaths.at(index).get('url').valueChanges.subscribe(value => { + if (this.chartIndicatorPaths.at(index).get('url').valid) { + let indicatorPath: IndicatorPath = this.indicatorUtils.generateIndicatorByChartUrl(this.indicatorUtils.getChartSource(value), value, this.chartIndicatorPaths.at(index).get('type').value, this.stakeholder); + if (!this.isStakeholderParametersValid(indicatorPath)) { + // default profile + if (this.stakeholder.defaultId == null) { + this.urlParameterizedMessage = "This chart couldn't be generated properly. Stakeholders based on this profile may not inherit the data correctly." + } else { + this.urlParameterizedMessage = "This chart couldn't be generated properly. Please make sure chart data is for the current stakeholder." + } + } else { + this.urlParameterizedMessage = null; + } + this.checkForSchemaEnhancements(this.chartIndicatorPaths.at(index).get('url').value); + (this.chartIndicatorPaths.at(index) as UntypedFormGroup).get('type').setValue(indicatorPath.type); + let parameters = this.getParametersAsFormArray(indicatorPath); + (this.chartIndicatorPaths.at(index) as UntypedFormGroup).setControl('parameters', parameters); + if (!this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index] = indicatorPath; + this.indicator.indicatorPaths[index].safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + } else { + indicatorPath.safeResourceUrl = this.indicator.indicatorPaths[index].safeResourceUrl; + this.indicator.indicatorPaths[index] = indicatorPath; + } + } else { + this.urlParameterizedMessage = null; + } + })); + } + } + + private isStakeholderParametersValid(indicatorPath: IndicatorPath) { + return !((indicatorPath.chartObject && Object.keys(indicatorPath.parameters).indexOf("index_id") == -1 && Object.keys(indicatorPath.parameters).indexOf("index_name") == -1 && Object.keys(indicatorPath.parameters).indexOf("index_shortName") == -1) + || (!indicatorPath.chartObject && indicatorPath.url.indexOf("index_id") == -1 && indicatorPath.url.indexOf("index_name") == -1 && (indicatorPath.url).indexOf("index_shortName") == -1)); + + } + + private getJsonPathAsFormArray(indicatorPath: IndicatorPath): UntypedFormArray { + let jsonPath = this.fb.array([]); + if (indicatorPath.jsonPath) { + indicatorPath.jsonPath.forEach(path => { + jsonPath.push(this.fb.control(path, Validators.required)); + }); + } + return jsonPath; + } + + private getParametersAsFormArray(indicatorPath: IndicatorPath): UntypedFormArray { + let parameters = this.fb.array([]); + if (indicatorPath.parameters) { + Object.keys(indicatorPath.parameters).forEach(key => { + if (this.indicatorUtils.ignoredParameters.indexOf(key) === -1) { + if (this.indicatorUtils.parametersValidators.has(key)) { + parameters.push(this.fb.group({ + key: this.fb.control(key), + value: this.fb.control(indicatorPath.parameters[key], this.indicatorUtils.parametersValidators.get(key)) + })); + } else { + parameters.push(this.fb.group({ + key: this.fb.control(key), + value: this.fb.control(indicatorPath.parameters[key]) + })); + } + } + }); + } + return parameters; + } + + public editNumberIndicatorOpen(section: Section, id = null) { + this.urlParameterizedMessage = null; + this.section = section; + this.index = (id) ? section.indicators.findIndex(value => value._id === id) : -1; + if (this.index !== -1) { + this.indicator = HelperFunctions.copy(this.section.indicators[this.index]); + this.numberIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name, Validators.required), + description: this.fb.control(this.indicator.description), + creationDate: this.fb.control(this.indicator.creationDate), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([], Validators.required), + type: this.fb.control(this.indicator.type), + width: this.fb.control(this.indicator.width), + height: this.fb.control(this.indicator.height), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.indicator.indicatorPaths.forEach(indicatorPath => { + this.addNumberIndicatorPath(this.indicatorUtils.getNumberUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath)), indicatorPath.parameters, indicatorPath.source, this.getJsonPathAsFormArray(indicatorPath), indicatorPath.format); + }); + } else { + this.indicator = new Indicator('', '', '', 'number', 'small', 'small', "PUBLIC", []); + this.numberIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name, Validators.required), + description: this.fb.control(this.indicator.description), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([], Validators.required), + type: this.fb.control(this.indicator.type), + width: this.fb.control(this.indicator.width), + height: this.fb.control(this.indicator.height), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.addNumberIndicatorPath(); + } + if (this.indicator.defaultId) { + setTimeout(() => { + this.numberIndicatorFb.get('description').disable(); + }, 0); + } + this.editNumberModal.cancelButtonText = 'Cancel'; + this.editNumberModal.okButtonLeft = false; + this.editNumberModal.alertMessage = false; + if (this.index === -1) { + this.editNumberModal.alertTitle = 'Create a new number indicator'; + this.editNumberModal.okButtonText = 'Save'; + this.notification = NotificationUtils.createIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.editNumberNotify.reset(this.notification.message); + } else { + this.editNumberModal.alertTitle = 'Edit number indicator\'s information'; + this.editNumberModal.okButtonText = 'Save Changes'; + this.notification = NotificationUtils.editIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.editNumberNotify.reset(this.notification.message); + } + this.editNumberModal.stayOpen = true; + this.editNumberModal.open(); + } + + public editChartIndicatorOpen(section: Section, id = null) { + this.urlParameterizedMessage = null; + this.urlSubscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.section = section; + this.index = (id) ? section.indicators.findIndex(value => value._id === id) : -1; + if (this.index !== -1) { + this.indicator = HelperFunctions.copy(this.section.indicators[this.index]); + this.chartIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name), + creationDate: this.fb.control(this.indicator.creationDate), + description: this.fb.control(this.indicator.description), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([]), + width: this.fb.control(this.indicator.width), + height: this.fb.control(this.indicator.height), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.indicator.indicatorPaths.forEach(indicatorPath => { + this.addChartIndicatorPath(this.getUrlByStakeHolder(indicatorPath), + this.getParametersAsFormArray(indicatorPath), this.indicator.defaultId !== null, indicatorPath.type); + indicatorPath.safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + }); + } else { + this.indicator = new Indicator('', '', '', 'chart', 'medium', 'medium', "PUBLIC", []); + this.chartIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name), + description: this.fb.control(this.indicator.description), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([]), + width: this.fb.control(this.indicator.width, Validators.required), + height: this.fb.control(this.indicator.height, Validators.required), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.addChartIndicatorPath(); + } + if (this.indicator.defaultId) { + setTimeout(() => { + this.chartIndicatorFb.get('description').disable(); + }, 0); + } + this.editChartModal.cancelButtonText = 'Cancel'; + this.editChartModal.okButtonLeft = false; + this.editChartModal.alertMessage = false; + if (this.index === -1) { + this.editChartModal.alertTitle = 'Create a new chart indicator'; + this.editChartModal.okButtonText = 'Save'; + this.notification = NotificationUtils.createIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.editChartNotify.reset(this.notification.message); + } else { + this.editChartModal.alertTitle = 'Edit chart indicator\'s information'; + this.editChartModal.okButtonText = 'Save Changes'; + this.notification = NotificationUtils.editIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + ; + this.editChartNotify.reset(this.notification.message); + } + this.editChartModal.stayOpen = true; + this.editChartModal.open(); + } + + saveIndicator() { + this.editing = true; + if (this.indicator.type === 'chart') { + this.chartIndicatorFb.get('description').enable(); + this.indicator = this.indicatorUtils.generateIndicatorByForm(this.chartIndicatorFb.value, this.indicator.indicatorPaths, this.indicator.type, true); + this.section = this.charts.find(section => section._id === this.section._id); + } else { + this.numberIndicatorFb.get('description').enable(); + this.indicator = this.indicatorUtils.generateIndicatorByForm(this.numberIndicatorFb.value, this.indicator.indicatorPaths, this.indicator.type, false); + this.section = this.numbers.find(section => section._id === this.section._id); + } + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + this.section._id + ]; + this.subscriptions.push(this.stakeholderService.saveElement(this.properties.monitorServiceAPIURL, this.indicator, path).subscribe(indicator => { + if (this.index !== -1) { + this.section.indicators[this.index] = indicator; + } else { + this.section.indicators.push(indicator); + } + this.notification.entity = indicator._id; + this.notification.stakeholder = this.stakeholder.alias; + this.notification.stakeholderType = this.stakeholder.type; + this.notification.groups = [Role.curator(this.stakeholder.type)]; + if (this.stakeholder.defaultId) { + this.notification.groups.push(Role.manager(this.stakeholder.type, this.stakeholder.alias)); + if (this.indicator.type === "chart") { + this.setCharts(); + this.chartIndicatorFb = null; + this.editChartNotify.sendNotification(this.notification); + } else { + this.setNumbers(); + this.numberIndicatorFb = null; + this.editNumberNotify.sendNotification(this.notification); + } + } else { + this.stakeholderService.getStakeholders(this.properties.monitorServiceAPIURL, null, this.stakeholder._id).subscribe(stakeholders => { + stakeholders.forEach(value => { + this.notification.groups.push(Role.manager(value.type, value.alias)) + }); + if (this.indicator.type === "chart") { + this.setCharts(); + this.chartIndicatorFb = null; + this.editChartNotify.sendNotification(this.notification); + } else { + this.setNumbers(); + this.numberIndicatorFb = null; + this.editNumberNotify.sendNotification(this.notification); + } + }); + } + UIkit.notification('Indicator has been successfully saved', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + if (this.indicator.type === "chart") { + this.editChartModal.cancel(); + } else { + this.editNumberModal.cancel(); + } + }, error => { + this.chartIndicatorFb = null; + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + if (this.indicator.type === "chart") { + this.editChartModal.cancel(); + } else { + this.editNumberModal.cancel(); + } + })); + } + + saveIndicators(sections) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id + ]; + this.subscriptions.push(this.stakeholderService.saveBulkElements(this.properties.monitorServiceAPIURL, sections, path).subscribe(stakeholder => { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].charts = stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].charts; + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].numbers = stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].numbers; + this.setCharts(); + this.setNumbers(); + this.initReorder(); + this.notification = NotificationUtils.importIndicators(this.user.fullname, this.stakeholder.alias); + this.notification.entity = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id; + this.notification.name = this.user.firstname; + this.notification.surname = this.user.lastname; + this.notification.stakeholder = this.stakeholder.alias; + this.notification.stakeholderType = this.stakeholder.type; + this.notification.groups = [Role.curator(this.stakeholder.type)]; + if (this.stakeholder.defaultId) { + this.notification.groups.push(Role.manager(this.stakeholder.type, this.stakeholder.alias)); + this.notificationService.sendNotification(this.notification).subscribe(notification => { + UIkit.notification('A notification has been sent successfully', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + }, error => { + UIkit.notification('An error has occurred. Please try again later', { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + }); + } else { + this.stakeholderService.getStakeholders(this.properties.monitorServiceAPIURL, null, this.stakeholder._id).subscribe(stakeholders => { + stakeholders.forEach(value => { + this.notification.groups.push(Role.manager(value.type, value.alias)) + }); + this.notificationService.sendNotification(this.notification).subscribe(notification => { + NotificationHandler.rise('A notification has been sent successfully'); + }, error => { + NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); + }); + }); + } + this.editing = false; + this.importLoading = false; + NotificationHandler.rise('Indicators have been imported successfully!'); + }, error => { + this.chartIndicatorFb = null; + NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); + this.editing = false; + this.importLoading = false; + })); + + + } + + reorderIndicators(sectionId: string, type: IndicatorType, reorder: Reorder) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + sectionId + ]; + this.subscriptions.push(this.stakeholderService.reorderIndicators(this.properties.monitorServiceAPIURL, path, reorder, type).subscribe(indicators => { + if (type === 'chart') { + this.charts.find(section => section._id === sectionId).indicators = indicators; + this.setCharts(); + } else { + this.numbers.find(section => section._id === sectionId).indicators = indicators; + this.setNumbers(); + } + this.editing = false; + })); + } + + hasDifference(index: number): boolean { + let hasDifference = false; + this.chartIndicatorPaths.at(index).value.parameters.forEach((parameter) => { + if (parameter.value !== this.indicator.indicatorPaths[index].parameters[parameter.key]) { + hasDifference = true; + return; + } + }); + return hasDifference || this.indicator.indicatorPaths[index].safeResourceUrl.toString() !== + this.getSecureUrlByStakeHolder(this.indicator.indicatorPaths[index]).toString(); + } + + public get isAdministrator(): boolean { + return Session.isPortalAdministrator(this.user); + } + + public get isCurator(): boolean { + return this.isAdministrator || Session.isCurator(this.stakeholder.type, this.user); + } + + refreshIndicator() { + this.indicator = this.indicatorUtils.generateIndicatorByForm(this.chartIndicatorFb.value, this.indicator.indicatorPaths, 'chart', true); + this.indicator.indicatorPaths.forEach(indicatorPath => { + indicatorPath.safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + }); + } + + deleteIndicatorOpen(section: Section, indicatorId: string, type: string, childrenAction: string = null) { + this.indicatorChildrenActionOnDelete = null; + if (childrenAction == "delete") { + this.indicatorChildrenActionOnDelete = childrenAction; + } else if (childrenAction == "disconnect") { + this.indicatorChildrenActionOnDelete = childrenAction; + } + + this.section = section; + if (type === 'chart') { + this.index = this.charts.find(value => value._id == section._id).indicators.findIndex(value => value._id == indicatorId); + } else { + this.index = this.numbers.find(value => value._id == section._id).indicators.findIndex(value => value._id == indicatorId); + } + this.indicator = section.indicators.find(value => value._id == indicatorId); + this.deleteModal.alertTitle = 'Delete indicator'; + this.deleteModal.cancelButtonText = 'No'; + this.deleteModal.okButtonText = 'Yes'; + this.notification = NotificationUtils.deleteIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.deleteNotify.reset(this.notification.message); + this.deleteModal.stayOpen = true; + this.deleteModal.open(); + } + + deleteIndicator() { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + this.section._id, + this.indicator._id + ]; + this.subscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, path, this.indicatorChildrenActionOnDelete).subscribe(() => { + if (this.indicator.type === 'chart') { + this.charts.find(section => section._id === this.section._id).indicators.splice(this.index, 1); + this.setCharts(); + } else { + this.numbers.find(section => section._id === this.section._id).indicators.splice(this.index, 1); + this.setNumbers(); + } + UIkit.notification('Indicator has been successfully deleted', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.notification.entity = this.indicator._id; + this.notification.stakeholder = this.stakeholder.alias; + this.notification.stakeholderType = this.stakeholder.type; + this.notification.groups = [Role.curator(this.stakeholder.type)]; + if (this.stakeholder.defaultId) { + this.notification.groups.push(Role.manager(this.stakeholder.type, this.stakeholder.alias)); + this.deleteNotify.sendNotification(this.notification); + } else { + this.stakeholderService.getStakeholders(this.properties.monitorServiceAPIURL, null, this.stakeholder._id).subscribe(stakeholders => { + stakeholders.forEach(value => { + this.notification.groups.push(Role.manager(value.type, value.alias)) + }); + this.deleteNotify.sendNotification(this.notification); + }); + } + this.editing = false; + this.deleteModal.cancel(); + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.deleteModal.cancel(); + })); + } + + changeIndicatorStatus(sectionId: string, indicator: Indicator, visibility: Visibility) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + sectionId, + indicator._id + ]; + this.subscriptions.push(this.stakeholderService.changeVisibility(this.properties.monitorServiceAPIURL, path, visibility).subscribe(returnedElement => { + indicator.visibility = returnedElement.visibility; + UIkit.notification('Indicator has been successfully changed to ' + indicator.visibility.toLowerCase(), { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + }, error => { + UIkit.notification('An error has been occurred. Try again later', { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + })); + } + + saveSection(focused: boolean, sectionControl: AbstractControl, index: number, type: IndicatorType = "chart") { + if (!focused && sectionControl.dirty) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id + ]; + this.subscriptions.push(this.stakeholderService.saveSection(this.properties.monitorServiceAPIURL, sectionControl.value, path, index).subscribe(section => { + if (type === 'chart') { + this.charts[index] = section; + this.setCharts(); + } else { + this.numbers[index] = section; + this.setNumbers(); + } + this.initReorder(); + UIkit.notification('Section has been successfully saved', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + })); + } + } + + createSection(index = -1, type: IndicatorType = 'chart') { + this.editing = true; + this.section = new Section(type, null, null, this.stakeholder.alias); + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id + ]; + this.subscriptions.push(this.stakeholderService.saveSection(this.properties.monitorServiceAPIURL, this.section, path, index).subscribe(section => { + if (type === 'chart') { + if (index !== -1) { + this.charts.splice(index, 0, section); + } else { + this.charts.push(section); + } + this.setCharts(); + } else { + if (index !== -1) { + this.numbers.splice(index, 0, section); + } else { + this.numbers.push(section); + } + this.setNumbers(); + } + this.initReorder(); + UIkit.notification('Section has been successfully created', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + })); + } + + // deleteNumberSectionOpen(section: Section, index: number) { + // this.section = section; + // this.index = index; + // this.deleteNumberSectionModal.alertTitle = 'Delete Section'; + // this.deleteNumberSectionModal.cancelButtonText = 'No'; + // this.deleteNumberSectionModal.okButtonText = 'Yes'; + // this.deleteNumberSectionModal.open(); + // } + // + // deleteChartSectionOpen(section: Section, index: number) { + // this.section = section; + // this.index = index; + // this.deleteChartSectionModal.alertTitle = 'Delete Section'; + // this.deleteChartSectionModal.cancelButtonText = 'No'; + // this.deleteChartSectionModal.okButtonText = 'Yes'; + // this.deleteChartSectionModal.open(); + // } + + deleteSectionOpen(section: Section, index: number, type: IndicatorType, childrenAction: string = null) { + if (!this.editing && !section.defaultId) { + this.sectionTypeToDelete = type; + this.sectionChildrenActionOnDelete = null; + if (childrenAction == "delete") { + this.sectionChildrenActionOnDelete = childrenAction; + } else if (childrenAction == "disconnect") { + this.sectionChildrenActionOnDelete = childrenAction; + } + + this.section = section; + this.index = index; + this.deleteSectionModal.alertTitle = 'Delete Section'; + this.deleteSectionModal.cancelButtonText = 'No'; + this.deleteSectionModal.okButtonText = 'Yes'; + this.deleteSectionModal.stayOpen = true; + this.deleteSectionModal.open(); + } + } + + deleteSection() { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + this.section._id + ]; + this.subscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, path, this.sectionChildrenActionOnDelete).subscribe(() => { + if (this.sectionTypeToDelete === "chart") { + this.charts.splice(this.index, 1); + this.setCharts(); + } else { + this.numbers.splice(this.index, 1); + this.setNumbers(); + } + this.initReorder(); + UIkit.notification('Section has been successfully deleted', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.deleteSectionModal.cancel(); + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.deleteSectionModal.cancel(); + })); + } + + private checkForSchemaEnhancements(url: string) { + this.showCheckForSchemaEnhancements = this.isAdministrator && url && !this.properties.useOldStatisticsSchema && this.indicatorUtils.checkForSchemaEnhancements(url); + } + + migrateFromOldImportJsonFile(charts) { + // first section contains numbers + // second contains charts + let hasNumbers = false; + for (let chart of charts) { + if (chart['type'] == 'number') { + hasNumbers = true; + break; + } + } + let chartsSection = (hasNumbers ? 1 : 0); // no numbers: first sections contains charts + for (let chart of charts) { + if (chart['sectionIndex'] == null) { + chart['sectionIndex'] = chart['type'] == 'chart' ? chartsSection : 0; + } + } + return charts; + } + + importIndicatorsAndSave(charts: any[]) { + let sectionsToSave: Section[] = []; + let countIndicators = 0; + // name description additionalDescription, height, width, visibility + let noValidParams = 0; + let duplicates = 0; + charts = this.migrateFromOldImportJsonFile(charts); + for (let chart of charts) { + if (!sectionsToSave[chart['sectionIndex']]) { + let sectionToSave = new Section(chart['sectionType'] ? chart['sectionType'] : chart['type'], chart['sectionTitle']); + sectionToSave.indicators = []; + sectionsToSave[chart['sectionIndex']] = sectionToSave; + } + let exists = false; + let indicatorPath; + // validate indicators' schema from file + let invalid_file_message; + if (!chart.type) { + invalid_file_message = "No indicator type is specified. Type should be chart or number."; + } else if (chart.type != "chart" && chart.type != "number") { + invalid_file_message = "Invalid indicator type. Type should be chart or number."; + } else if (chart.type == "number" && !chart.jsonPath) { + invalid_file_message = "No jsonPath is specified for number indicator." + } else if (!chart.url) { + invalid_file_message = "No indicator url is specified."; + } + + if (invalid_file_message) { + UIkit.notification(invalid_file_message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + break; + } + + if (chart.type == "chart") { + indicatorPath = this.indicatorUtils.generateIndicatorByChartUrl(this.indicatorUtils.getChartSource(chart.url), chart.url, chart.type, this.stakeholder); + for (let section of this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].charts) { + for (let chart of section.indicators) { + if (JSON.stringify(chart.indicatorPaths[0].chartObject) == JSON.stringify(indicatorPath.chartObject)) { + duplicates++; + exists = true; + } + } + + } + } else if (chart.type == "number") { + indicatorPath = this.indicatorUtils.generateIndicatorByNumberUrl(this.indicatorUtils.getNumberSource(chart.url), chart.url, this.stakeholder, + chart.jsonPath, this.indicatorUtils.numberSources.get(this.indicatorUtils.getNumberSource(chart.url))); + for (let section of this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].numbers) { + for (let chart of section.indicators) { + if (JSON.stringify(chart.indicatorPaths[0].chartObject) == JSON.stringify(indicatorPath.chartObject)) { + duplicates++; + exists = true; + } + } + + } + } + if (!this.isStakeholderParametersValid(indicatorPath)) { + noValidParams++; + } + if (!exists) { + let i: Indicator = new Indicator(chart.name, chart.description, chart.additionalDescription, chart.type, chart.width, chart.height, "RESTRICTED", [indicatorPath]); + sectionsToSave[chart['sectionIndex']].indicators.push(i); + countIndicators++; + } + + } + + if (duplicates > 0) { + UIkit.notification(duplicates + " urls already exist and will not be imported!", { + status: 'warning', + timeout: 6000, + pos: 'bottom-right' + }); + } + if (noValidParams > 0) { + let noValidMessage = "Some indicators couldn't be generated properly. Please make sure chart data is for the current stakeholder."; + if (this.stakeholder.defaultId == null) { + noValidMessage = "Some indicators couldn't be generated properly. Stakeholders based on this profile may not inherit the data correctly."; + } + UIkit.notification(noValidMessage, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + } else if (sectionsToSave.length > 0 && countIndicators > 0) { + this.saveIndicators(sectionsToSave.filter(section => !!section)); + } + if (sectionsToSave.length == 0 || countIndicators == 0) { + UIkit.notification(" No urls imported!", { + status: 'warning', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + } + } + + public exportIndicators(subcategoryIndex) { + this.editing = true; + let indicators = []; + let index: number = 0; + let indexIndicator: number = 0; + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[subcategoryIndex].numbers.forEach(section => { + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + indicators[indexIndicator] = { + "type": indicator.type, "name": indicator.name, "jsonPath": indicatorPath.jsonPath, + "description": indicator.description, "additionalDescription": indicator.additionalDescription, + "visibility": indicator.visibility, "width": indicator.width, "height": indicator.height, + "url": this.indicatorUtils.getNumberUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath)), + "sectionTitle": section.title, + "sectionType": section.type, + "sectionIndex": index + }; + indexIndicator++; + }); + }); + index++; + }); + + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[subcategoryIndex].charts.forEach(section => { + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + indicators[indexIndicator] = { + "type": indicator.type, "name": indicator.name, + "description": indicator.description, "additionalDescription": indicator.additionalDescription, + "visibility": indicator.visibility, "width": indicator.width, "height": indicator.height, + "url": this.getUrlByStakeHolder(indicatorPath), + "sectionTitle": section.title, + "sectionType": section.type, + "sectionIndex": index + }; + indexIndicator++; + }); + }); + index++; + + }); + + let topic = this.stakeholder ? this.stakeholder.topics[this.topicIndex] : null; + let category = topic ? topic.categories[this.categoryIndex] : null; + let subCategory = category ? category.subCategories[this.subcategoryIndex] : null; + + var jsonFileUrl = window.URL.createObjectURL(new Blob([JSON.stringify(indicators)], {type: 'application/json'})); + var a = window.document.createElement('a'); + window.document.body.appendChild(a); + a.setAttribute('style', 'display: none'); + a.href = jsonFileUrl; + a.download = this.stakeholder.alias + "_" + topic.alias + "_" + category.alias + "_" + subCategory.alias + ".json"; + a.click(); + window.URL.revokeObjectURL(jsonFileUrl); + a.remove(); // remove the element + + this.editing = false; + } + + fileChangeEvent(fileInput: any, index) { + this.index = index; + this.editing = true; + this.importLoading = true; + this.filesToUpload = >fileInput.target.files; + this.upload(); + } + + upload() { + if (this.filesToUpload.length == 0) { + console.error("There is no selected file to upload."); + UIkit.notification("There is no selected file to upload.", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + return; + } else { + if (this.filesToUpload[0].name.indexOf(".json") == -1 || (this.filesToUpload[0].type != "application/json")) { + console.error("No valid file type. The required type is JSON"); + UIkit.notification("No valid file type. The required type is JSON", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + return; + } + } + + this.makeFileRequest(this.properties.utilsService + '/upload?type=json', [], this.filesToUpload).then(async (result: string) => { + + let json_result = JSON.parse(result); + + // validate file + if (!json_result || json_result.length == 0) { + UIkit.notification("Importing file is empty", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + } else { + this.importIndicatorsAndSave(json_result); + } + }, (error) => { + console.error("Error importing files", error); + UIkit.notification("Error importing files", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + }); + } + + makeFileRequest(url: string, params: Array, files: Array) { + return new Promise((resolve, reject) => { + const formData: any = new FormData(); + const xhr = new XMLHttpRequest(); + for (let i = 0; i < files.length; i++) { + formData.append("uploads[]", files[i], files[i].name); + } + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + resolve(xhr.response); + } else { + reject(xhr.response); + } + } + }; + xhr.open("POST", url, true); + xhr.send(formData); + }); + } + + copyToClipboard(value) { + const tempBox = document.createElement('textarea'); + tempBox.style.position = 'fixed'; + tempBox.style.left = '0'; + tempBox.style.top = '0'; + tempBox.style.opacity = '0'; + tempBox.value = value; + document.body.appendChild(tempBox); + tempBox.focus(); + tempBox.select(); + tempBox.setSelectionRange(0, 99999); + document.execCommand('copy'); + document.body.removeChild(tempBox); + NotificationHandler.rise('Copied to clipboard'); + } +} diff --git a/monitor-admin/topic/topic-routing.module.ts b/monitor-admin/topic/topic-routing.module.ts new file mode 100644 index 00000000..bccc637d --- /dev/null +++ b/monitor-admin/topic/topic-routing.module.ts @@ -0,0 +1,19 @@ +import {NgModule} from '@angular/core'; +import {RouterModule} from '@angular/router'; +import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard'; +import {TopicComponent} from "./topic.component"; +import {CanExitGuard} from "../../utils/can-exit.guard"; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + component: TopicComponent, + canDeactivate: [PreviousRouteRecorder, CanExitGuard] + } + ]) + ] +}) +export class TopicRoutingModule { +} diff --git a/monitor-admin/topic/topic.component.html b/monitor-admin/topic/topic.component.html new file mode 100644 index 00000000..2e5c7ab1 --- /dev/null +++ b/monitor-admin/topic/topic.component.html @@ -0,0 +1,380 @@ + + + +
+
+ +
+ You are about to delete "{{element.name}}" {{type}} permanently. +
+ {{getPluralTypeName()}} of all profiles based on this default {{type}}, will be deleted as well. +
+ Are you sure you want to proceed? +
+
+ +
+ +
+
+
+
+
+
+
+
+ +
+ You have the option to change the visibility status of your {{type}}, with or without applying the changed status to + its contents. +
+
+ +
+
+ +
+
+

Note:

+
+ The status of the {{type}} prevails the status of its contents. + For example, if a {{type}}'s status is private, while it has + a category, subcategory or an indicator + a subcategory or an indicator + an indicator + that is public, the private status of the {{type}} dominates. +
+
+
diff --git a/monitor-admin/topic/topic.component.ts b/monitor-admin/topic/topic.component.ts new file mode 100644 index 00000000..c9d6f7ff --- /dev/null +++ b/monitor-admin/topic/topic.component.ts @@ -0,0 +1,795 @@ +import { + AfterViewInit, + ChangeDetectorRef, + Component, Inject, + OnDestroy, + OnInit, PLATFORM_ID, + QueryList, + ViewChild, + ViewChildren +} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; +import {Title} from '@angular/platform-browser'; +import {EnvProperties} from '../../utils/properties/env-properties'; +import {Category, Stakeholder, SubCategory, Topic, Visibility} from "../../monitor/entities/stakeholder"; +import {StakeholderService} from "../../monitor/services/stakeholder.service"; +import {HelperFunctions} from "../../utils/HelperFunctions.class"; +import {AlertModal} from "../../utils/modal/alert"; +import {BehaviorSubject, Subject, Subscriber, Subscription} from "rxjs"; +import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms"; +import {StakeholderUtils} from "../utils/indicator-utils"; +import {StringUtils} from "../../utils/string-utils.class"; +import {IDeactivateComponent} from "../../utils/can-exit.guard"; +import {LayoutService} from "../../dashboard/sharedComponents/sidebar/layout.service"; +import {Option} from "../../sharedComponents/input/input.component"; +import {properties} from "src/environments/environment"; +import {Session, User} from "../../login/utils/helper.class"; +import {UserManagementService} from "../../services/user-management.service"; +import {TransitionGroupComponent} from "../../utils/transition-group/transition-group.component"; +import {NotificationHandler} from "../../utils/notification-handler"; + +declare var UIkit; + +@Component({ + selector: 'topic', + templateUrl: './topic.component.html', +}) +export class TopicComponent implements OnInit, OnDestroy, AfterViewInit, IDeactivateComponent { + private topicSubscriptions: any[] = []; + private subscriptions: any[] = []; + public properties: EnvProperties = properties; + public stakeholderUtils: StakeholderUtils = new StakeholderUtils(); + public loading: boolean = false; + public stakeholder: Stakeholder; + public user: User; + /** + * Stakeholder change event + * */ + public change: Subject = new Subject(); + /** + * Current topic + **/ + public topicIndexSubject: BehaviorSubject = new BehaviorSubject(0); + public topicIndex: number = 0; + /** + * Current category + */ + public categoryIndexSubject: BehaviorSubject = new BehaviorSubject(0); + public categoryIndex: number = 0; + /** + * Current Subcategory + */ + public subCategoryIndexSubject: BehaviorSubject = new BehaviorSubject(0); + public subCategoryIndex: number = 0; + /** + * Current element and index of topic, category or subcategory to be deleted. + */ + public form: UntypedFormGroup; + public element: Topic | Category | SubCategory; + public type: 'topic' | 'category' | 'subcategory' = "topic"; + public index: number = -1; + public visibility: Visibility; + + @ViewChild('deleteModal', {static: true}) deleteModal: AlertModal; + @ViewChild('editModal', {static: true}) editModal: AlertModal; + @ViewChild('visibilityModal', {static: true}) visibilityModal: AlertModal; + @ViewChildren(TransitionGroupComponent) transitions: QueryList; + + public elementChildrenActionOnDelete: string; + public filters: UntypedFormGroup; + public all: Option = { + value: 'all', + label: 'All' + }; + + constructor( + private route: ActivatedRoute, + private router: Router, + private title: Title, + private fb: UntypedFormBuilder, + private stakeholderService: StakeholderService, + private userManagementService: UserManagementService, + private layoutService: LayoutService, + private cdr: ChangeDetectorRef, + @Inject(PLATFORM_ID) private platformId) { + } + + public ngOnInit() { + let subscription: Subscription; + this.subscriptions.push(this.topicIndexSubject.asObservable().subscribe(index => { + this.topicChanged(() => { + this.topicIndex = index; + }); + })); + this.subscriptions.push(this.categoryIndexSubject.asObservable().subscribe(index => { + this.categoryChanged(() => { + this.categoryIndex = index; + }); + })); + this.subscriptions.push(this.subCategoryIndexSubject.asObservable().subscribe(index => { + this.subCategoryChanged(() => { + this.subCategoryIndex = index; + }); + })); + this.subscriptions.push(this.route.params.subscribe(params => { + if (subscription) { + subscription.unsubscribe(); + } + subscription = this.stakeholderService.getStakeholderAsObservable().subscribe(stakeholder => { + if (stakeholder) { + this.stakeholder = stakeholder; + if (params['topic']) { + this.chooseTopic(this.stakeholder.topics.findIndex(topic => topic.alias === params['topic'])); + } else { + this.chooseTopic(0); + } + this.chooseCategory(0); + this.filters = this.fb.group({ + chartType: this.fb.control('all'), + status: this.fb.control('all'), + keyword: this.fb.control('') + }); + if (this.topicIndex === -1) { + this.navigateToError(); + } else { + this.title.setTitle(stakeholder.name + " | Indicators"); + } + } + }); + this.topicSubscriptions.push(subscription); + })); + this.topicSubscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + this.user = user; + })) + } + + ngAfterViewInit() { + if(this.topics) { + let activeIndex = UIkit.nav(this.topics.element.nativeElement).items.findIndex(item => item.classList.contains('uk-open')); + if(activeIndex !== this.topicIndex) { + setTimeout(() => { + UIkit.nav(this.topics.element.nativeElement).toggle(this.topicIndex, true); + }); + } + } + } + + get isBrowser() { + return this.platformId === 'browser'; + } + + public ngOnDestroy() { + this.topicSubscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.subscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + } + + canExit(): boolean { + this.topicSubscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.stakeholderService.setStakeholder(this.stakeholder); + return true; + } + + private findById(id: string) { + return this.transitions?this.transitions.find(item => item.id === id):null; + } + + get topics(): TransitionGroupComponent { + return this.findById('topics'); + } + + get categories(): TransitionGroupComponent { + return this.findById('categories-' + this.topicIndex); + } + + get subCategories(): TransitionGroupComponent { + return this.findById('subCategories'); + } + + hide(element: any) { + UIkit.dropdown(element).hide(); + } + + stakeholderChanged() { + this.change.next(); + } + + public saveElement() { + if (this.type === "topic") { + this.saveTopic(); + } else if (this.type === "category") { + this.saveCategory(); + } else { + this.saveSubCategory(); + } + } + + public deleteElement() { + if (this.type === "topic") { + this.deleteTopic(); + } else if (this.type === "category") { + this.deleteCategory(); + } else { + this.deleteSubcategory(); + } + } + + public changeElementStatus(propagate: boolean = false) { + if (this.type === "topic") { + this.changeTopicStatus(propagate); + } else if (this.type === "category") { + this.changeCategoryStatus(propagate); + } else { + this.changeSubcategoryStatus(propagate); + } + } + + public chooseTopic(topicIndex: number) { + this.topicIndexSubject.next(topicIndex); + } + + topicChanged(callback: Function, save: boolean = false) { + if(this.topics && save) { + this.topics.disable(); + } + if(this.categories) { + this.categories.disable(); + } + if(this.subCategories) { + this.subCategories.disable(); + } + if(callback) { + callback(); + } + this.cdr.detectChanges(); + if(this.topics && save) { + this.topics.init(); + this.topics.enable(); + } + if(this.categories) { + this.categories.init(); + this.categories.enable(); + } + if(this.subCategories) { + this.subCategories.init(); + this.subCategories.enable(); + } + } + + private buildTopic(topic: Topic) { + let topics = this.stakeholder.topics.filter(element => element._id !== topic._id); + this.form = this.fb.group({ + _id: this.fb.control(topic._id), + name: this.fb.control(topic.name, Validators.required), + description: this.fb.control(topic.description), + creationDate: this.fb.control(topic.creationDate), + alias: this.fb.control(topic.alias, [ + Validators.required, + this.stakeholderUtils.aliasValidator(topics) + ] + ), + visibility: this.fb.control(topic.visibility), + defaultId: this.fb.control(topic.defaultId), + categories: this.fb.control(topic.categories), + icon: this.fb.control(topic.icon) + }); + this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => { + let i = 1; + value = this.stakeholderUtils.generateAlias(value); + this.form.controls['alias'].setValue(value); + while (this.form.get('alias').invalid) { + this.form.controls['alias'].setValue(value + i); + i++; + } + })); + } + + public editTopicOpen(index = -1) { + this.index = index; + this.type = 'topic'; + if (index === -1) { + this.buildTopic(new Topic(null, null, null, "PUBLIC")); + } else { + this.buildTopic(this.stakeholder.topics[index]); + } + this.editOpen(); + } + + public saveTopic() { + if (!this.form.invalid) { + let path = [this.stakeholder._id]; + let callback = (topic: Topic): void => { + this.topicChanged(() => { + if (this.index === -1) { + this.stakeholder.topics.push(topic); + } else { + this.stakeholder.topics[this.index] = HelperFunctions.copy(topic); + } + }, true); + }; + if (this.index === -1) { + this.save('Topic has been successfully created', path, this.form.value, callback); + } else { + this.save('Topic has been successfully saved', path, this.form.value, callback); + } + } + } + + public changeTopicStatus(propagate: boolean = false) { + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.index]._id + ]; + let callback = (topic: Topic): void => { + this.topicChanged(() => { + this.stakeholder.topics[this.index] = HelperFunctions.copy(topic); + }, true); + } + this.changeStatus(this.stakeholder.topics[this.index], path, this.visibility, callback, propagate); + this.visibilityModal.cancel(); + } + + public deleteTopicOpen(index = this.topicIndex, childrenAction: string = null) { + this.type = 'topic'; + this.index = index; + this.element = this.stakeholder.topics[this.index]; + this.deleteOpen(childrenAction); + } + + public deleteTopic() { + let path: string[] = [ + this.stakeholder._id, + this.stakeholder.topics[this.index]._id + ]; + let callback = (): void => { + this.topicChanged(() => { + this.stakeholder.topics.splice(this.index, 1); + if(this.topicIndex === this.index) { + this.chooseTopic(Math.max(0, this.index - 1)); + } + }, true); + }; + this.delete('Topic has been successfully be deleted', path, callback, (this.topicIndex === this.index)); + } + + public moveTopic(index: number, newIndex: number = index - 1) { + this.topics.init(); + let path = [this.stakeholder._id]; + let ids = this.stakeholder.topics.map(topic => topic._id); + HelperFunctions.swap(ids, index, newIndex); + this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => { + HelperFunctions.swap(this.stakeholder.topics, index, newIndex); + if(this.topicIndex === index) { + this.chooseTopic(newIndex); + } else if(this.topicIndex === newIndex) { + this.chooseTopic(index); + } + }, error => { + NotificationHandler.rise(error.error.message) + }); + } + + public chooseCategory(index: number) { + this.categoryIndexSubject.next(index); + this.chooseSubcategory(0); + } + + categoryChanged(callback: Function, save: boolean = false) { + if(this.categories && save) { + this.categories.disable(); + } + if(this.subCategories) { + this.subCategories.disable(); + } + if(callback) { + callback(); + } + this.cdr.detectChanges(); + if(this.categories && save) { + this.categories.init(); + this.categories.enable(); + } + if(this.subCategories) { + this.subCategories.init(); + this.subCategories.enable(); + } + } + + private buildCategory(category: Category) { + let categories = this.stakeholder.topics[this.topicIndex].categories.filter(element => element._id !== category._id); + this.form = this.fb.group({ + _id: this.fb.control(category._id), + name: this.fb.control(category.name, Validators.required), + description: this.fb.control(category.description), + creationDate: this.fb.control(category.creationDate), + alias: this.fb.control(category.alias, [ + Validators.required, + this.stakeholderUtils.aliasValidator(categories) + ] + ), + visibility: this.fb.control(category.visibility), + defaultId: this.fb.control(category.defaultId), + subCategories: this.fb.control(category.subCategories) + }); + this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => { + let i = 1; + value = this.stakeholderUtils.generateAlias(value); + this.form.controls['alias'].setValue(value); + while (this.form.get('alias').invalid) { + this.form.controls['alias'].setValue(value + i); + i++; + } + })); + } + + public editCategoryOpen(index: number = -1) { + this.index = index; + this.type = 'category'; + if (index === -1) { + this.buildCategory(new Category(null, null, null, "PUBLIC")); + } else { + this.buildCategory(this.stakeholder.topics[this.topicIndex].categories[index]); + } + this.editOpen(); + } + + public saveCategory() { + if (!this.form.invalid) { + let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id]; + let callback = (category: Category): void => { + this.categoryChanged(() => { + if (this.index === -1) { + this.stakeholder.topics[this.topicIndex].categories.push(category); + this.categories.init(); + } else { + this.stakeholder.topics[this.topicIndex].categories[this.index] = HelperFunctions.copy(category); + } + }, true); + }; + if (this.index === -1) { + this.save('Category has been successfully created', path, this.form.value, callback); + } else { + this.save('Category has been successfully saved', path, this.form.value, callback); + } + } + } + + public changeCategoryStatus(propagate: boolean = false) { + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.index]._id + ]; + let callback = (category: Category): void => { + this.categoryChanged(() => { + this.stakeholder.topics[this.topicIndex].categories[this.index] = HelperFunctions.copy(category); + }, true); + } + this.changeStatus(this.stakeholder.topics[this.topicIndex].categories[this.index], path, this.visibility, callback, propagate); + this.visibilityModal.cancel(); + } + + public deleteCategoryOpen(index: number, childrenAction: string = null) { + this.type = 'category'; + this.index = index; + this.element = this.stakeholder.topics[this.topicIndex].categories[this.index]; + this.deleteOpen(childrenAction); + } + + public deleteCategory() { + let path: string[] = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.index]._id + ]; + let callback = (): void => { + this.categoryChanged(() => { + this.stakeholder.topics[this.topicIndex].categories.splice(this.index, 1); + if(this.categoryIndex === this.index) { + this.chooseCategory(Math.max(0, this.index - 1)); + } + }, true); + }; + this.delete('Category has been successfully be deleted', path, callback); + } + + public moveCategory(index: number, newIndex: number = index - 1) { + this.categories.init(); + let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id]; + let ids = this.stakeholder.topics[this.topicIndex].categories.map(category => category._id); + HelperFunctions.swap(ids, index, newIndex); + this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => { + HelperFunctions.swap(this.stakeholder.topics[this.topicIndex].categories, index, newIndex); + if(this.categoryIndex === index) { + this.chooseCategory(newIndex); + } else if(this.categoryIndex === newIndex) { + this.chooseCategory(index); + } + }, error => { + NotificationHandler.rise(error.error.message) + }); + } + + chooseSubcategory(subcategoryIndex: number) { + this.subCategoryIndexSubject.next(subcategoryIndex); + } + + subCategoryChanged(callback: Function, save: boolean = false) { + if(this.subCategories && save) { + this.subCategories.disable(); + } + if(callback) { + callback(); + } + this.cdr.detectChanges(); + if(this.subCategories && save) { + this.subCategories.init(); + this.subCategories.enable(); + } + } + + private buildSubcategory(subCategory: SubCategory) { + let subCategories = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.filter(element => element._id !== subCategory._id); + this.form = this.fb.group({ + _id: this.fb.control(subCategory._id), + name: this.fb.control(subCategory.name, Validators.required), + description: this.fb.control(subCategory.description), + creationDate: this.fb.control(subCategory.creationDate), + alias: this.fb.control(subCategory.alias, [ + Validators.required, + this.stakeholderUtils.aliasValidator(subCategories) + ] + ), + visibility: this.fb.control(subCategory.visibility), + defaultId: this.fb.control(subCategory.defaultId), + charts: this.fb.control(subCategory.charts), + numbers: this.fb.control(subCategory.numbers) + }); + this.topicSubscriptions.push(this.form.get('name').valueChanges.subscribe(value => { + let i = 1; + value = this.stakeholderUtils.generateAlias(value); + this.form.controls['alias'].setValue(value); + while (this.form.get('alias').invalid) { + this.form.controls['alias'].setValue(value + i); + i++; + } + })); + } + + public editSubCategoryOpen(index: number = -1) { + this.index = index; + this.type = 'subcategory'; + if (index === -1) { + this.buildSubcategory(new SubCategory(null, null, null, "PUBLIC")); + } else { + this.buildSubcategory(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[index]); + } + this.editOpen(); + } + + public saveSubCategory() { + if (!this.form.invalid) { + let path: string[] = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id + ]; + let callback = (subCategory: SubCategory): void => { + this.subCategoryChanged(() => { + if (this.index === -1) { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.push(subCategory); + } else { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index] = HelperFunctions.copy(subCategory); + } + }, true); + }; + if (this.index === -1) { + this.save('Subcategory has been successfully created', path, this.form.value, callback); + } else { + this.save('Subcategory has been successfully saved', path, this.form.value, callback); + } + } + } + + public changeSubcategoryStatus(propagate: boolean = false) { + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id + ]; + let callback = (subcategory: SubCategory): void => { + this.subCategoryChanged(() => { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index] = HelperFunctions.copy(subcategory); + }, true); + } + this.changeStatus(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index], path, this.visibility, callback, propagate); + this.visibilityModal.cancel(); + } + + + public deleteSubcategoryOpen(index, childrenAction: string = null) { + this.type = 'subcategory'; + this.index = index; + this.element = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]; + this.deleteOpen(childrenAction); + } + + public deleteSubcategory() { + let path: string[] = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id + ]; + let callback = (): void => { + this.subCategoryChanged(() => { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.splice(this.index, 1); + if(this.subCategoryIndex === this.index) { + this.chooseSubcategory(Math.max(0, this.index - 1)); + } + }, true); + }; + this.delete('Subcategory has been successfully be deleted', path, callback); + } + + public moveSubCategory(index: number, newIndex: number = index - 1) { + this.subCategories.init(); + let path = [this.stakeholder._id, this.stakeholder.topics[this.topicIndex]._id, this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id]; + let ids = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories.map(subCategory => subCategory._id); + HelperFunctions.swap(ids, index, newIndex); + this.stakeholderService.reorderElements(properties.monitorServiceAPIURL, path, ids).subscribe(() => { + HelperFunctions.swap(this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories, index, newIndex); + if(this.subCategoryIndex === index) { + this.chooseSubcategory(newIndex); + } else if(this.subCategoryIndex === newIndex) { + this.chooseSubcategory(index); + } + }, error => { + NotificationHandler.rise(error.error.message) + }); + } + + private navigateToError() { + this.router.navigate([this.properties.errorLink], {queryParams: {'page': this.router.url}}); + } + + get isCurator(): boolean { + return Session.isPortalAdministrator(this.user) || Session.isCurator(this.stakeholder.type, this.user); + } + + private editOpen() { + this.editModal.cancelButtonText = 'Cancel'; + this.editModal.okButtonLeft = false; + this.editModal.alertMessage = false; + if (this.index === -1) { + this.editModal.alertTitle = 'Create a new ' + this.type; + this.editModal.okButtonText = 'Create'; + } else { + this.editModal.alertTitle = 'Edit ' + this.type + '\'s information '; + this.editModal.okButtonText = 'Save'; + } + this.editModal.stayOpen = true; + this.editModal.open(); + } + + private deleteOpen(childrenAction: string = null) { + this.elementChildrenActionOnDelete = null; + if (childrenAction == "delete") { + this.elementChildrenActionOnDelete = childrenAction; + } else if (childrenAction == "disconnect") { + this.elementChildrenActionOnDelete = childrenAction; + } + + this.deleteModal.alertTitle = 'Delete ' + this.type; + this.deleteModal.cancelButtonText = 'No'; + this.deleteModal.okButtonText = 'Yes'; + // this.deleteModal.cancelButton = false; + // this.deleteModal.okButton = false; + this.deleteModal.stayOpen = true; + this.deleteModal.open(); + } + + private save(message: string, path: string[], saveElement: any, callback: Function, redirect = false) { + this.loading = true; + this.topicSubscriptions.push(this.stakeholderService.saveElement(this.properties.monitorServiceAPIURL, saveElement, path).subscribe(saveElement => { + callback(saveElement); + this.stakeholderChanged(); + this.loading = false; + this.editModal.cancel(); + NotificationHandler.rise(message); + if (redirect) { + this.router.navigate(['../' + saveElement.alias], { + relativeTo: this.route + }); + } + }, error => { + this.loading = false; + this.editModal.cancel(); + NotificationHandler.rise(error.error.message, 'danger'); + })); + } + + private delete(message: string, path: string[], callback: Function, redirect = false) { + this.loading = true; + this.topicSubscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, path, this.elementChildrenActionOnDelete).subscribe(() => { + callback(); + this.stakeholderChanged(); + this.loading = false; + this.deleteModal.cancel(); + NotificationHandler.rise(message); + if (redirect) { + this.back(); + } + }, error => { + this.loading = false; + this.deleteModal.cancel(); + NotificationHandler.rise(error.error.message, 'danger'); + })); + } + + private changeStatus(element: Topic | Category | SubCategory, path: string[], visibility: Visibility, callback: Function = null, propagate: boolean = false) { + this.topicSubscriptions.push(this.stakeholderService.changeVisibility(this.properties.monitorServiceAPIURL, path, visibility, propagate).subscribe(returnedElement => { + if(propagate) { + callback(returnedElement); + NotificationHandler.rise(StringUtils.capitalize(this.type) + ' has been successfully changed to ' + returnedElement.visibility.toLowerCase()); + } else { + element.visibility = returnedElement.visibility; + NotificationHandler.rise(StringUtils.capitalize(this.type) + ' has been successfully changed to ' + element.visibility.toLowerCase()); + } + }, error => { + NotificationHandler.rise(error.error.message, 'danger'); + })); + } + + back() { + this.router.navigate(['../'], { + relativeTo: this.route + }); + } + + public getPluralTypeName(): string { + if (this.type == "topic") { + return "Topics"; + } else if (this.type == "category") { + return "Categories"; + } else if (this.type == "subcategory") { + return "Subcategories"; + } else { + return this.type; + } + } + + public get isSmallScreen() { + return this.layoutService.isSmallScreen; + } + + public get open() { + return this.layoutService.open; + } + + public toggleOpen(event: MouseEvent) { + event.preventDefault(); + this.layoutService.setOpen(!this.open); + } + + public openVisibilityModal(index: number, visibility: Visibility, type: any) { + this.index = index; + this.visibility = visibility; + this.type = type; + this.visibilityModal.alertTitle = 'Visibility Status'; + this.visibilityModal.alertFooter = false; + this.visibilityModal.open(); + } +} diff --git a/monitor-admin/topic/topic.module.ts b/monitor-admin/topic/topic.module.ts new file mode 100644 index 00000000..748b6572 --- /dev/null +++ b/monitor-admin/topic/topic.module.ts @@ -0,0 +1,49 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; + +import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard'; + +import {PiwikService} from '../../utils/piwik/piwik.service'; +import {TopicComponent} from "./topic.component"; +import {TopicRoutingModule} from "./topic-routing.module"; +import {RouterModule} from "@angular/router"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {IndicatorsComponent} from "./indicators.component"; +import {AlertModalModule} from "../../utils/modal/alertModal.module"; +import {InputModule} from "../../sharedComponents/input/input.module"; +import {ClickModule} from "../../utils/click/click.module"; +import {IconsService} from "../../utils/icons/icons.service"; +import {earth, incognito, restricted} from "../../utils/icons/icons"; +import {IconsModule} from "../../utils/icons/icons.module"; +import {PageContentModule} from "../../dashboard/sharedComponents/page-content/page-content.module"; +import {LoadingModule} from "../../utils/loading/loading.module"; +import {NotifyFormModule} from "../../notifications/notify-form/notify-form.module"; +import {LogoUrlPipeModule} from "../../utils/pipes/logoUrlPipe.module"; +import {TransitionGroupModule} from "../../utils/transition-group/transition-group.module"; +import {NumberRoundModule} from "../../utils/pipes/number-round.module"; +import {SideBarModule} from "../../dashboard/sharedComponents/sidebar/sideBar.module"; +import { + SidebarMobileToggleModule +} from "../../dashboard/sharedComponents/sidebar/sidebar-mobile-toggle/sidebar-mobile-toggle.module"; + +@NgModule({ + imports: [ + CommonModule, TopicRoutingModule, ClickModule, RouterModule, FormsModule, AlertModalModule, + ReactiveFormsModule, InputModule, IconsModule, PageContentModule, LoadingModule, NotifyFormModule, LogoUrlPipeModule, TransitionGroupModule, NumberRoundModule, SideBarModule, SidebarMobileToggleModule + ], + declarations: [ + TopicComponent, IndicatorsComponent + ], + providers: [ + PreviousRouteRecorder, + PiwikService + ], + exports: [ + TopicComponent + ] +}) +export class TopicModule { + constructor(private iconsService: IconsService) { + this.iconsService.registerIcons([earth, incognito, restricted]); + } +} diff --git a/monitor-admin/utils/adminDashboard.guard.ts b/monitor-admin/utils/adminDashboard.guard.ts new file mode 100644 index 00000000..664fa7d3 --- /dev/null +++ b/monitor-admin/utils/adminDashboard.guard.ts @@ -0,0 +1,43 @@ +import {Injectable} from '@angular/core'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; +import {map, take, tap} from "rxjs/operators"; +import {UserManagementService} from "../../services/user-management.service"; +import {LoginErrorCodes} from "../../login/utils/guardHelper.class"; +import {Session} from "../../login/utils/helper.class"; +import {StakeholderService} from "../../monitor/services/stakeholder.service"; +import {Observable, zip} from "rxjs"; + + +@Injectable() +export class AdminDashboardGuard { + + constructor(private router: Router, + private stakeholderService: StakeholderService, + private userManagementService: UserManagementService) { + } + + check(path: string, alias: string): Observable | boolean { + let errorCode = LoginErrorCodes.NOT_LOGIN; + return zip( + this.userManagementService.getUserInfo(), this.stakeholderService.getStakeholder(alias) + ).pipe(take(1),map(res => { + if(res[0]) { + errorCode = LoginErrorCodes.NOT_ADMIN; + } + return res[0] && res[1] && (Session.isPortalAdministrator(res[0]) || + Session.isCurator(res[1].type, res[0]) || Session.isManager(res[1].type, res[1].alias, res[0])) + }),tap(authorized => { + if(!authorized){ + this.router.navigate(['/user-info'], {queryParams: {'errorCode': errorCode, 'redirectUrl':path}}); + } + })); + } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + return this.check(state.url, route.params.stakeholder); + } + + canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { + return this.check(state.url, childRoute.params.stakeholder); + } +} diff --git a/monitor-admin/utils/cache-indicators/cache-indicators.component.less b/monitor-admin/utils/cache-indicators/cache-indicators.component.less new file mode 100644 index 00000000..08d5c2a9 --- /dev/null +++ b/monitor-admin/utils/cache-indicators/cache-indicators.component.less @@ -0,0 +1,9 @@ +@import (reference) "~src/assets/openaire-theme/less/_import-variables.less"; + +.cache-progress { + position: fixed; + bottom: 0; + right: 0; + transform: translate(-50%, -50%); + z-index: @global-z-index; +} diff --git a/monitor-admin/utils/cache-indicators/cache-indicators.component.ts b/monitor-admin/utils/cache-indicators/cache-indicators.component.ts new file mode 100644 index 00000000..8f2211aa --- /dev/null +++ b/monitor-admin/utils/cache-indicators/cache-indicators.component.ts @@ -0,0 +1,78 @@ +import {Component, Inject, Input, OnChanges, OnDestroy, OnInit, PLATFORM_ID, SimpleChanges} from "@angular/core"; +import {Report} from "./cache-indicators"; +import {CacheIndicatorsService} from "./cache-indicators.service"; +import {interval, Subject, Subscription} from "rxjs"; +import {map, switchMap, takeUntil} from "rxjs/operators"; + +@Component({ + selector: 'cache-indicators', + template: ` +
+
+
+ +
+
+ `, + styleUrls: ['cache-indicators.component.less'] +}) +export class CacheIndicatorsComponent implements OnInit, OnChanges, OnDestroy { + report: Report; + subscriptions: Subscription[] = []; + interval: number = 10000; + readonly destroy$ = new Subject(); + @Input() alias: string; + + constructor(private cacheIndicatorsService: CacheIndicatorsService, + @Inject(PLATFORM_ID) private platformId) { + } + + ngOnInit() { + this.getReport(); + } + + ngOnChanges(changes: SimpleChanges) { + if(changes.alias) { + this.getReport(); + } + } + + getReport() { + this.clear(); + this.subscriptions.push(this.cacheIndicatorsService.getReport(this.alias).subscribe(report => { + this.getReportInterval(report); + })); + } + + getReportInterval(report: Report) { + if(this.isBrowser && (this.report || !report?.completed)) { + this.report = report; + this.subscriptions.push(interval(this.interval).pipe( + map(() => this.cacheIndicatorsService.getReport(this.alias)), + switchMap(report => report), + takeUntil(this.destroy$)).subscribe(report => { + console.log(this.alias); + this.report = report; + if(this.report.completed) { + this.destroy$.next(); + } + })); + } + } + + clear() { + this.subscriptions.forEach(subscription => { + subscription.unsubscribe(); + }) + this.report = null; + } + + + get isBrowser() { + return this.platformId === 'browser'; + } + + ngOnDestroy() { + this.clear(); + } +} diff --git a/monitor-admin/utils/cache-indicators/cache-indicators.module.ts b/monitor-admin/utils/cache-indicators/cache-indicators.module.ts new file mode 100644 index 00000000..15c7200c --- /dev/null +++ b/monitor-admin/utils/cache-indicators/cache-indicators.module.ts @@ -0,0 +1,11 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {CacheIndicatorsComponent} from "./cache-indicators.component"; +import {IconsModule} from "../../../utils/icons/icons.module"; + +@NgModule({ + imports: [CommonModule, IconsModule], + declarations: [CacheIndicatorsComponent], + exports: [CacheIndicatorsComponent] +}) +export class CacheIndicatorsModule {} diff --git a/monitor-admin/utils/cache-indicators/cache-indicators.service.ts b/monitor-admin/utils/cache-indicators/cache-indicators.service.ts new file mode 100644 index 00000000..8919896d --- /dev/null +++ b/monitor-admin/utils/cache-indicators/cache-indicators.service.ts @@ -0,0 +1,24 @@ +import {Injectable} from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {properties} from "src/environments/environment"; +import {CustomOptions} from "../../../services/servicesUtils/customOptions.class"; +import {map} from "rxjs/operators"; + +@Injectable({ + providedIn: 'root' +}) +export class CacheIndicatorsService { + + constructor(private http: HttpClient) { + } + + createReport(alias: string) { + return this.http.post(properties.domain + properties.baseLink + '/cache/' + alias, {}, CustomOptions.registryOptions()) + .pipe(map(res => res.report)); + } + + getReport(alias: string) { + return this.http.get(properties.domain + properties.baseLink + '/cache/' + alias, CustomOptions.registryOptions()) + .pipe(map(res => res.report)); + } +} diff --git a/monitor-admin/utils/cache-indicators/cache-indicators.ts b/monitor-admin/utils/cache-indicators/cache-indicators.ts new file mode 100644 index 00000000..8b7418b3 --- /dev/null +++ b/monitor-admin/utils/cache-indicators/cache-indicators.ts @@ -0,0 +1,261 @@ +import {IndicatorType, Stakeholder} from "../../../monitor/entities/stakeholder"; +import axios from "axios"; +import {IndicatorUtils} from "../indicator-utils"; +import {Composer} from "../../../utils/email/composer"; +import {properties} from "src/environments/environment"; + + +export interface CacheItem { + reportId: string, + type: IndicatorType, + url: string +} + +export class Report { + creator: string; + name: string; + success: number; + errors: { + url: string, + status: number + }[]; + total: number; + completed: boolean; + percentage: number + + constructor(total: number, name: string, creator: string) { + this.creator = creator; + this.name = name; + this.success = 0; + this.errors = []; + this.total = total; + this.completed = false; + } + + setPercentage() { + this.percentage = Math.floor((this.success + this.errors.length) / this.total * 100); + } +} + +export class CacheIndicators { + + private static BATCH_SIZE = 10; + + private reports: Map = new Map(); + private queue: CacheItem[] = []; + private process: Promise; + private isFinished: boolean = true; + + stakeholderToCacheItems(stakeholder: Stakeholder) { + let cacheItems: CacheItem[] = []; + let indicatorUtils = new IndicatorUtils(); + stakeholder.topics.forEach(topic => { + topic.categories.forEach(category => { + category.subCategories.forEach(subCategory => { + subCategory.numbers.forEach(section => { + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + let url = indicatorUtils.getNumberUrl(indicatorPath.source, indicatorUtils.getFullUrl(stakeholder, indicatorPath)); + cacheItems.push({ + reportId: stakeholder._id, + type: 'number', + url: url + }); + }); + }); + }); + subCategory.charts.forEach(section => { + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + let url = indicatorUtils.getChartUrl(indicatorPath.source, indicatorUtils.getFullUrl(stakeholder, indicatorPath)); + cacheItems.push({ + reportId: stakeholder._id, + type: 'chart', + url: url + }); + }); + }); + }); + }); + }); + }); + return cacheItems; + } + + public exists(id: string) { + return this.reports.has(id); + } + + public completed(id: string) { + return !this.exists(id) || this.reports.get(id).completed; + } + + public createReport(id: string, cacheItems: CacheItem[], name: string, creator: string) { + let report = new Report(cacheItems.length, name, creator); + this.reports.set(id, report); + this.addItemsToQueue(cacheItems); + return report; + } + + public getReport(id: string) { + return this.reports.get(id); + } + + private async processQueue() { + this.isFinished = false; + while (this.queue.length > 0) { + let batch = this.queue.splice(0, CacheIndicators.BATCH_SIZE); + await this.processBatch(batch); + } + } + + private async processBatch(batch: CacheItem[]) { + let promises: Promise[] = []; + let ids = new Set(); + batch.forEach(item => { + let promise; + ids.add(item.reportId); + if (item.type === 'chart') { + let [url, json] = item.url.split('?json='); + json = decodeURIComponent(json); + json = statsToolParser(JSON.parse(json)); + promise = axios.post(url, json); + } else { + promise = axios.get(item.url); + } + promises.push(promise.then(response => { + let report = this.reports.get(item.reportId); + if (report) { + report.success++; + report.setPercentage(); + } + return response; + }).catch(error => { + let report = this.reports.get(item.reportId); + if (report) { + report.errors.push({url: item.url, status: error.response.status}); + report.setPercentage(); + } + return error.response; + })); + }); + await Promise.all(promises); + ids.forEach(id => { + let report = this.reports.get(id); + if (report?.percentage === 100) { + report.completed = true; + this.sendEmail(report); + } + }); + } + + private addItemsToQueue(cacheItems: CacheItem[]) { + cacheItems.forEach(item => { + this.queue.push(item); + }); + if (this.isFinished) { + this.processQueue().then(() => { + this.isFinished = true; + }); + } + } + + sendEmail(report: Report) { + let email = Composer.composeEmailToReportCachingProcess(report); + axios.post(properties.adminToolsAPIURL + "sendMail/", email).catch(error => { + console.error(error); + }); + } +} + +export function statsToolParser(dataJSONobj: any): any { + let RequestInfoObj = Object.assign({}); + switch (dataJSONobj.library) { + case "GoogleCharts": + //Pass the Chart library to ChartDataFormatter + RequestInfoObj.library = dataJSONobj.library; + RequestInfoObj.orderBy = dataJSONobj.orderBy; + + //Create ChartInfo Object Array + RequestInfoObj.chartsInfo = []; + //Create ChartInfo and pass the Chart data queries to ChartDataFormatter + //along with the requested Chart type + RequestInfoObj.chartsInfo = dataJSONobj.chartDescription.queriesInfo; + break; + case "eCharts": + //Pass the Chart library to ChartDataFormatter + RequestInfoObj.library = dataJSONobj.library; + RequestInfoObj.orderBy = dataJSONobj.orderBy; + + //Create ChartInfo Object Array + RequestInfoObj.chartsInfo = []; + + //Create ChartInfo and pass the Chart data queries to ChartDataFormatter + //along with the requested Chart type + for (let index = 0; index < dataJSONobj.chartDescription.queries.length; index++) { + let element = dataJSONobj.chartDescription.queries[index]; + var ChartInfoObj = Object.assign({}); + + if (element.type === undefined) + ChartInfoObj.type = dataJSONobj.chartDescription.series[index].type; + else + ChartInfoObj.type = element.type; + + if (element.name === undefined) + ChartInfoObj.name = null; + else + ChartInfoObj.name = element.name; + + ChartInfoObj.query = element.query; + RequestInfoObj.chartsInfo.push(ChartInfoObj); + } + break; + case "HighCharts": + RequestInfoObj.library = dataJSONobj.library; + RequestInfoObj.orderBy = dataJSONobj.orderBy; + //Pass the Chart type to ChartDataFormatter + var defaultType = dataJSONobj.chartDescription.chart.type; + //Create ChartInfo Object Array + RequestInfoObj.chartsInfo = []; + //Create ChartInfo and pass the Chart data queries to ChartDataFormatter + //along with the requested Chart type + dataJSONobj.chartDescription.queries.forEach(element => { + var ChartInfoObj = Object.assign({}); + + if (element.type === undefined) + ChartInfoObj.type = defaultType; + else + ChartInfoObj.type = element.type; + + if (element.name === undefined) + ChartInfoObj.name = null; + else + ChartInfoObj.name = element.name; + + ChartInfoObj.query = element.query; + RequestInfoObj.chartsInfo.push(ChartInfoObj); + }); + break; + case "HighMaps": + RequestInfoObj.library = dataJSONobj.library; + //Create ChartInfo Object Array + RequestInfoObj.chartsInfo = []; + + //Create ChartInfo and pass the Chart data queries to ChartDataFormatter + dataJSONobj.mapDescription.queries.forEach(element => { + var ChartInfoObj = Object.assign({}); + + if (element.name === undefined) + ChartInfoObj.name = null; + else + ChartInfoObj.name = element.name; + + ChartInfoObj.query = element.query; + RequestInfoObj.chartsInfo.push(ChartInfoObj); + }); + break; + default: + console.log("Unsupported Library: " + dataJSONobj.library); + } + return RequestInfoObj; +} diff --git a/monitor-admin/utils/indicator-utils.ts b/monitor-admin/utils/indicator-utils.ts new file mode 100644 index 00000000..f7c71c0b --- /dev/null +++ b/monitor-admin/utils/indicator-utils.ts @@ -0,0 +1,1005 @@ +import { + ChartHelper, + FilterType, + Indicator, + IndicatorFilterUtils, + IndicatorPath, + IndicatorPathType, + IndicatorType, + SourceType, + Stakeholder, + StakeholderEntities, + SubCategory, + Topic, + Visibility +} from "../../monitor/entities/stakeholder"; +import {AbstractControl, ValidatorFn, Validators} from "@angular/forms"; +import {Option} from "../../sharedComponents/input/input.component"; +import {Session} from "../../login/utils/helper.class"; +import {HelperFunctions} from "../../utils/HelperFunctions.class"; +import {properties} from "src/environments/environment"; + +export class StakeholderUtils { + + statuses: Option[] = [ + {value: 'PUBLIC', label: 'Public'}, + {value: 'RESTRICTED', label: 'Restricted'}, + {value: 'PRIVATE', label: 'Private'} + ]; + + types: Option[] = [ + {value: 'funder', label: StakeholderEntities.FUNDER}, + {value: 'ri', label: StakeholderEntities.RI}, + {value: 'project', label: StakeholderEntities.PROJECT}, + {value: 'organization', label: StakeholderEntities.ORGANIZATION} + ]; + + visibility: Option[] = [ + {icon: 'earth', value: "PUBLIC", label: 'Public'}, + {icon: 'restricted', value: "RESTRICTED", label: 'Restricted'}, + {icon: 'incognito', value: "PRIVATE", label: 'Private'}, + ]; + + locales: Option[] = [ + {value: "en", label: 'English'}, + {value: "eu", label: 'Europe'} + ]; + + visibilityIcon: Map = new Map([ + ["PUBLIC", 'earth'], + ["PRIVATE", 'incognito'], + ["RESTRICTED", 'restricted'] + ]); + + getTypesByUserRoles(user, id: string = null): Option[] { + let types = []; + for (let type of this.types) { + if (Session.isCurator(type.value, user) || Session.isPortalAdministrator(user) || (id && Session.isManager(type.value, id, user))) { + types.push(type); + } + } + return types; + } + + public createFunderFromDefaultProfile(funder: Stakeholder, defaultTopics: Topic[], isDefault: boolean = false): Stakeholder { + funder.topics = HelperFunctions.copy(defaultTopics); + for (let topic of funder.topics) { + topic.defaultId = !isDefault ? topic._id : null; + topic._id = null; + for (let category of topic.categories) { + category.defaultId = !isDefault ? category._id : null; + category._id = null; + let subTokeep: SubCategory[] = []; + for (let subCategory of category.subCategories) { + subCategory.defaultId = !isDefault ? subCategory._id : null; + subCategory._id = null; + subTokeep.push(subCategory); + for (let section of subCategory.charts) { + let chartsTokeep: Indicator[] = []; + section.defaultId = !isDefault ? section._id : null; + section.stakeholderAlias = funder.alias; + section._id = null; + for (let indicator of section.indicators) { + indicator.defaultId = !isDefault ? indicator._id : null; + indicator._id = null; + chartsTokeep.push(indicator); + for (let indicatorPath of indicator.indicatorPaths) { + if (indicatorPath.parameters) { + Object.keys(indicatorPath.parameters).forEach(key => { + if (key == "index_name") { + indicatorPath.parameters[key] = funder.index_name; + } else if (key == "index_id") { + indicatorPath.parameters[key] = funder.index_id; + } else if (key == "index_shortName") { + indicatorPath.parameters[key] = funder.index_shortName.toLowerCase(); + } + }); + } + } + } + section.indicators = chartsTokeep; + } + for (let section of subCategory.numbers) { + section.defaultId = !isDefault ? section._id : null; + section.stakeholderAlias = funder.alias; + section._id = null; + for (let indicator of section.indicators) { + indicator.defaultId = !isDefault ? indicator._id : null; + indicator._id = null; + } + } + + } + category.subCategories = subTokeep; + } + } + return funder; + } + + aliasValidatorString(elements: string[]): ValidatorFn { + return (control: AbstractControl): { [key: string]: string } | null => { + if (control.value && elements.find(element => + element === control.value + )) { + return {'error': 'Alias already in use'}; + } + return null; + } + } + + aliasValidator(elements: any[]): ValidatorFn { + return (control: AbstractControl): { [key: string]: string } | null => { + if (control.value && elements.find(element => + element.alias === control.value + )) { + return {'error': 'Alias already in use'}; + } + return null; + } + } + + generateAlias(name: string): string { + let alias = name.toLowerCase(); + while (alias.includes('/') || alias.includes(' ')) { + alias = alias.replace(' / ', '-'); + alias = alias.replace('/', '-'); + alias = alias.replace(' ', '-'); + } + return alias; + } +} + +export class IndicatorUtils { + + allChartTypes: Option[] = [ + {value: 'pie', label: 'Pie'}, + {value: 'table', label: 'Table'}, + {value: 'line', label: 'Line'}, + {value: 'column', label: 'Column'}, + {value: 'bar', label: 'Bar'}, + {value: 'other', label: 'Other'} + ]; + basicChartTypes: IndicatorPathType[] = ["pie", "line", "column", "bar"]; + defaultChartType: IndicatorPathType = "other"; + indicatorSizes: Option[] = [ + {value: 'small', label: 'Small (Enabled only for large screens)'}, + {value: 'medium', label: 'Medium'}, + {value: 'large', label: 'Large'} + ]; + + allSourceTypes: Option[] = [ + {value: 'search', label: 'Search'}, + {value: 'statistics', label: 'Statistics'}, + {value: 'stats-tool', label: 'Statistics tool'} + ]; + + formats: Option[] = [ + {value: "NUMBER", label: "Number"}, + {value: "PERCENTAGE", label: "Percentage"} + ]; + + sourceTypes: Option[] = [ + {value: 'stats-tool', label: 'Statistics tool'} + ]; + + isActive: Option[] = [ + {icon: 'brightness_1', iconClass: '', value: true, label: 'Active'}, + {icon: 'brightness_1', value: false, label: 'Inactive'}, + ]; + + parametersValidators: Map = new Map([ + ['start_year', [Validators.required, Validators.pattern('^\\d+$')]], + ['end_year', [Validators.required, Validators.pattern('^\\d+$')]] + ]); + ignoredParameters = ['index_name', 'index_id', 'index_shortName']; + statsProfileParameter = 'profile'; + + numberSources: Map = new Map(); + chartSources: Map = new Map(); + + constructor() { + this.numberSources.set('statistics', [properties.statisticsAPIURL]); + this.numberSources.set('search', [properties.searchAPIURLLAst]); + this.numberSources.set('stats-tool', [properties.monitorStatsFrameUrl, "http://marilyn.athenarc.gr:8080/stats-api/", "http://88.197.53.71:8080/stats-api/", "https://stats.madgik.di.uoa.gr/stats-api/","https://beta.services.openaire.eu/stats-tool/","https://services.openaire.eu/stats-tool/","https://services.openaire.eu/monitor-stats-tool/"]); + this.chartSources.set('stats-tool', [properties.monitorStatsFrameUrl, "http://marilyn.athenarc.gr:8080/stats-api/", "http://88.197.53.71:8080/stats-api/", "https://stats.madgik.di.uoa.gr/stats-api/","https://beta.services.openaire.eu/stats-tool/","https://services.openaire.eu/stats-tool/","https://services.openaire.eu/monitor-stats-tool/"]); + this.chartSources.set('old', [properties.statisticsFrameAPIURL]); + this.chartSources.set('image', [""]); + } + + getSourceType(source:string): SourceType{ + let sourceType: SourceType = 'search'; + this.numberSources.forEach((values, key) => { + if(key == source) { + sourceType = key; + } + }); + return sourceType; + } + + getChartUrl(source: SourceType, url: string): string { + return this.chartSources.get(source)[0] + url; + } + + getNumberUrl(source: SourceType, url: string): string { + return this.numberSources.get(this.getSourceType(source))[0] + url; + } + + getNumberSource(url: string): SourceType { + let source: SourceType = 'search'; + this.numberSources.forEach((values, key) => { + values.forEach((value) => { + if (value !== '' && url.indexOf(value) !== -1) { + source = key; + } + }); + }); + return source; + } + + getChartSource(url: string): SourceType { + let source: SourceType = 'image'; + this.chartSources.forEach((values, key) => { + values.forEach((value) => { + if (value !== '' && url.indexOf(value) !== -1) { + source = key; + } + }); + }); + return source; + } + + getChartTypes(initialType) { + let types: Option[] = []; + if (this.basicChartTypes.indexOf(initialType) != -1) { + (this.allChartTypes).forEach(option => { + if (this.basicChartTypes.indexOf(option.value) != -1) { + types.push(option); + } + }); + return types; + } else if (initialType == "table") { + (this.allChartTypes).forEach(option => { + if (initialType == option.value) { + types.push(option); + } + }); + return types; + } else { + return this.allChartTypes; + } + } + + public getFullUrl(stakeholder: Stakeholder, indicatorPath: IndicatorPath, fundingL0: string = null, startYear: string = null, endYear: string = null): string { + let replacedUrl = indicatorPath.chartObject ? indicatorPath.chartObject : indicatorPath.url; + if (stakeholder.statsProfile) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + this.statsProfileParameter + ChartHelper.suffix).join(stakeholder.statsProfile) + } + if (indicatorPath.parameters) { + Object.keys(indicatorPath.parameters).forEach(key => { + let replacedValue = indicatorPath.parameters[key]; + if (startYear && key == "start_year" && indicatorPath.filters["start_year"]) { + replacedValue = (replacedValue < startYear) ? startYear : replacedValue; + } + if (endYear && key == "end_year" && indicatorPath.filters["end_year"]) { + replacedValue = (replacedValue > endYear) ? endYear : replacedValue; + } + if (key == "index_id") { + replacedValue = stakeholder.index_id; + } + if (key == "index_name") { + replacedValue = stakeholder.index_name; + } + if (key == "index_shortName") { + replacedValue = stakeholder.index_shortName.toLowerCase(); + } + + replacedUrl = replacedUrl.split(ChartHelper.prefix + key + ChartHelper.suffix).join(replacedValue) + }); + } + if (indicatorPath.chartObject) { + if (fundingL0 && indicatorPath.filters["fundingL0"]) { + let newJsonObject = JSON.parse(replacedUrl); + for (let queries of this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)] : newJsonObject[this.getDescriptionObjectName(newJsonObject)]) { + if (!queries["query"]["filters"] || queries["query"]["filters"].length == 0) { + queries["query"]["filters"] = []; + } + //TODO check how it works if the query already has a filter + queries["query"]["filters"].push(JSON.parse(indicatorPath.filters["fundingL0"].replace(ChartHelper.prefix + "fundingL0" + ChartHelper.suffix, fundingL0))); + } + replacedUrl = JSON.stringify(newJsonObject); + } + if (startYear && indicatorPath.filters["start_year"]) { + let newJsonObject = JSON.parse(replacedUrl); + + for (let queries of this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)] : newJsonObject[this.getDescriptionObjectName(newJsonObject)]) { + if (!queries["query"]["filters"] || queries["query"]["filters"].length == 0) { + queries["query"]["filters"] = []; + } + //TODO check how it works if the query already has a filter + queries["query"]["filters"].push(JSON.parse(indicatorPath.filters["start_year"].replace(ChartHelper.prefix + "start_year" + ChartHelper.suffix, startYear))); + } + replacedUrl = JSON.stringify(newJsonObject); + } + if (endYear && indicatorPath.filters["end_year"]) { + let newJsonObject = JSON.parse(replacedUrl); + for (let queries of this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)] : newJsonObject[this.getDescriptionObjectName(newJsonObject)]) { + if (!queries["query"]["filters"] || queries["query"]["filters"].length == 0) { + queries["query"]["filters"] = []; + } + //TODO check how it works if the query already has a filter + queries["query"]["filters"].push(JSON.parse(indicatorPath.filters["end_year"].replace(ChartHelper.prefix + "end_year" + ChartHelper.suffix, endYear))); + } + replacedUrl = JSON.stringify(newJsonObject); + } + + } + //For numbers (e.g. from stats-api , search service, etc) + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_id' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_id' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_id)); + } + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_name' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_name' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_name)); + } + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_shortName' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_shortName' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_shortName)); + } + return (indicatorPath.chartObject ? indicatorPath.url + encodeURIComponent(replacedUrl) : replacedUrl); + } + + public getFullUrlWithFilters(stakeholder: Stakeholder, indicatorPath: IndicatorPath, fundingL0: string = null, startYear: string = null, endYear: string = null, coFunded: boolean = false): string { + indicatorPath.filtersApplied = 0; + let replacedUrl = indicatorPath.chartObject ? indicatorPath.chartObject : indicatorPath.url; + if (stakeholder.statsProfile) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + this.statsProfileParameter + ChartHelper.suffix).join(stakeholder.statsProfile); + } + if (indicatorPath.parameters) { + Object.keys(indicatorPath.parameters).forEach(key => { + let replacedValue = indicatorPath.parameters[key]; + if (startYear && key == "start_year") { + replacedValue = (replacedValue < startYear) ? startYear : replacedValue; + //if there is a parameter that is filtered and the value of the parameter changes, count the filter as applied + indicatorPath.filtersApplied++; + } + if (endYear && key == "end_year") { + replacedValue = (replacedValue > endYear) ? endYear : replacedValue; + //if there is a parameter that is filtered and the value of the parameter changes, count the filter as applied + indicatorPath.filtersApplied++; + } + if (key == "index_id") { + replacedValue = stakeholder.index_id; + } + if (key == "index_name") { + replacedValue = stakeholder.index_name; + } + if (key == "index_shortName") { + replacedValue = stakeholder.index_shortName.toLowerCase(); + } + + replacedUrl = replacedUrl.split(ChartHelper.prefix + key + ChartHelper.suffix).join(replacedValue) + }); + } + if (fundingL0) { + if (indicatorPath.source == "stats-tool" && indicatorPath.chartObject) { + let filterResults = this.addFilter(replacedUrl, 'fundingL0', fundingL0); + replacedUrl = filterResults.url; + indicatorPath.filtersApplied += filterResults.filtersApplied; + } + } + if (startYear) { + if (indicatorPath.source == "stats-tool" && indicatorPath.chartObject) { + let filterResults = this.addFilter(replacedUrl, 'start_year', startYear); + replacedUrl = filterResults.url; + indicatorPath.filtersApplied += filterResults.filtersApplied; + } + } + if (endYear) { + if (indicatorPath.source == "stats-tool" && indicatorPath.chartObject) { + let filterResults = this.addFilter(replacedUrl, 'end_year', endYear); + replacedUrl = filterResults.url; + indicatorPath.filtersApplied += filterResults.filtersApplied; + } + } + if (coFunded) { + if (indicatorPath.source == "stats-tool" && indicatorPath.chartObject) { + let filterResults = this.addFilter(replacedUrl, 'co-funded', endYear); + replacedUrl = filterResults.url; + indicatorPath.filtersApplied += filterResults.filtersApplied; + } + } + + //For numbers + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_id' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_id' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_id)); + } + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_name' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_name' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_name)); + } + if (replacedUrl.indexOf(ChartHelper.prefix + 'index_shortName' + ChartHelper.suffix) != -1) { + replacedUrl = replacedUrl.split(ChartHelper.prefix + 'index_shortName' + ChartHelper.suffix).join(encodeURIComponent(stakeholder.index_shortName)); + } + //Check apply enhancements return this.applySchemaEnhancements( ..); + return (indicatorPath.chartObject ? indicatorPath.url + encodeURIComponent(replacedUrl) : replacedUrl); + + } + + private addFilter(replacedUrl, filterType: FilterType, filterValue) { + let newJsonObject = JSON.parse(replacedUrl); + let filterApplied: boolean = false; + let queryIndex = 0; + for (let queries of this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)] : newJsonObject[this.getDescriptionObjectName(newJsonObject)]) { + /*Chart with Named Queries*/ + if (queries["query"]["name"] && !queries["query"]["select"]) { + + if (queries["query"]["name"].indexOf("monitor.") == -1 || !queries["query"]["parameters"]) { + continue; + } + if (filterType == 'fundingL0') { + let paramFields = queries["query"]["name"].split(".").slice(3); + let filterPosition = queries["query"]["name"].split(".").indexOf(filterType == "fundingL0" ? 'fl0' : filterType); + if (filterPosition != -1) { + //already filtered + //TODO double check if we need to override if the fl0 is already filtered + filterPosition -= 3; + /* //update the value + if(paramFields.length == queries["query"]["parameters"].length ){ + //ok + queries["query"]["parameters"][filterPosition] = filterValue; + }else if((paramFields.length + 2) == queries["query"]["parameters"].length || (paramFields.length*2 + 4) == queries["query"]["parameters"].length){ + queries["query"]["parameters"][filterPosition + 2]=filterValue; + filterApplied = true; + } + if((paramFields.length*2 + 4) == queries["query"]["parameters"].length){ + queries["query"]["parameters"][(2* filterPosition) + 5]=filterValue; + }*/ + //if applied with the same value mark as filtered + if (paramFields.length == queries["query"]["parameters"].length && queries["query"]["parameters"][filterPosition] == filterValue) { + filterApplied = true; + } else if ((paramFields.length + 2) == queries["query"]["parameters"].length || (paramFields.length * 2 + 4) == queries["query"]["parameters"].length && queries["query"]["parameters"][filterPosition + 2] == filterValue) { + filterApplied = true; + } + } else { + // if((paramFields.length*2) == queries["query"]["parameters"].length){ + // queries["query"]["parameters"].splice(paramFields.length, 0, filterValue); + // } + if ((paramFields.length * 2 + 4) == queries["query"]["parameters"].length) { + queries["query"]["parameters"].splice(paramFields.length + 1, 0, filterValue); + } + queries["query"]["name"] = queries["query"]["name"] + ".fl0"; + queries["query"]["parameters"].push(filterValue); + filterApplied = true; + } + } else { + let paramFields = queries["query"]["name"].split(".").slice(3); + if ((paramFields.length + 2) == queries["query"]["parameters"].length || (paramFields.length * 2 + 4) == queries["query"]["parameters"].length) { + filterApplied = true; + if (filterType == "start_year") { + queries["query"]["parameters"][0] = parseInt(filterValue); + } else if (filterType == "end_year") { + queries["query"]["parameters"][1] = parseInt(filterValue); + } + } + if ((paramFields.length * 2 + 4) == queries["query"]["parameters"].length) { + filterApplied = true; + if (filterType == "start_year") { + queries["query"]["parameters"][paramFields.length + 2] = parseInt(filterValue); + } else if (filterType == "end_year") { + queries["query"]["parameters"][paramFields.length + 3] = parseInt(filterValue); + } + } + } + // it is a name query + continue; + } + if (!queries["query"]["filters"] || queries["query"]["filters"].length == 0) { + queries["query"]["filters"] = []; + } + /*Chart with proper json object*/ + //apply the filter in any select fields + for (let select of queries["query"]["select"]) { + let filterString = IndicatorFilterUtils.getFilter(select["field"], filterType); + if (filterString) { + let filter = JSON.parse(filterString); + //check if filter already exists + let filterposition = IndicatorFilterUtils.filterIndexOf(filter, queries["query"]["filters"]); + if (filterposition) { + if (queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] != filter['groupFilters'][0]["values"][0].replace(ChartHelper.prefix + filterType + ChartHelper.suffix, filterValue)) { + //change filter value + // queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] = filter['groupFilters'][0]["values"][0].replace(ChartHelper.prefix + filterType + ChartHelper.suffix, filterValue); + //add user filter value + // queries["query"]["filters"].push(JSON.parse(filterString.replace(ChartHelper.prefix + filterType + ChartHelper.suffix, filterValue))); + // update colors + //if noit a pie, map and chart has more than one query + // + if (!newJsonObject.hasOwnProperty("mapDescription") && queries["type"] != "pie" && this.isComparingChart(newJsonObject, filter)) { + let activeColors = ["#7CB5EC", "#434348", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"]; + let inActiveColors = ["#E4EFFB", "#D8D8D9", "#8bbc21", "#910000", "#1aadce", "#492970", "#f28f43", "#77a1e5", "#c42525", "#a6c96a"]; + if (!newJsonObject[this.getDescriptionObjectName(newJsonObject)]["colors"]) { + newJsonObject[this.getDescriptionObjectName(newJsonObject)]["colors"] = activeColors; + } + newJsonObject[this.getDescriptionObjectName(newJsonObject)]["colors"][queryIndex] = inActiveColors[queryIndex]; + filterApplied = true; + } else if (filterType == "start_year" || filterType == "end_year") { + //if has date filter already + if (filterType == "start_year" && parseInt(filterValue) > parseInt(queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0])) { + queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] = filterValue; + } else if (filterType == "end_year" && parseInt(filterValue) < parseInt(queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0])) { + queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0] = filterValue; + } + filterApplied = true; + } + } else { + filterApplied = true; + } + } else { + queries["query"]["filters"].push(JSON.parse(filterString.replace(ChartHelper.prefix + filterType + ChartHelper.suffix, filterValue))); + filterApplied = true; + } + } + } + queryIndex++; + } + return {"url": JSON.stringify(newJsonObject), "filtersApplied": (filterApplied) ? 1 : 0}; + } + + isComparingChart(newJsonObject, filter,) { + let queriesCount = this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)].length : newJsonObject[this.getDescriptionObjectName(newJsonObject)].length; + let values = []; + if (queriesCount < 2) { + return false; + } + for (let queries of this.getQueryObjectName(newJsonObject) ? newJsonObject[this.getDescriptionObjectName(newJsonObject)][this.getQueryObjectName(newJsonObject)] : newJsonObject[this.getDescriptionObjectName(newJsonObject)]) { + let filterposition = IndicatorFilterUtils.filterIndexOf(filter, queries["query"]["filters"]); + if (filterposition) { + if (values.indexOf(queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0]) == -1) { + values.push(queries["query"]["filters"][filterposition.filter]['groupFilters'][filterposition.groupFilter]["values"][0]); + } + } + } + return values.length > 1; + } + + generateIndicatorByForm(form: any, indicatorPaths: IndicatorPath[], type: IndicatorType, addParameters: boolean = true): Indicator { + let indicator: Indicator = new Indicator(form.name, form.description, form.additionalDescription, type, + form.width, form.height, form.visibility, indicatorPaths, form.defaultId); + indicator._id = form._id; + form.indicatorPaths.forEach((indicatorPath, index) => { + indicator.indicatorPaths[index].type = indicatorPath.type; + indicator.indicatorPaths[index].format = indicatorPath.format; + if (addParameters) { + indicatorPath.parameters.forEach(parameter => { + indicator.indicatorPaths[index].parameters[parameter.key] = parameter.value; + if (parameter.key === 'type') { + indicator.indicatorPaths[index].type = parameter.value; + } + }); + } + }); + return indicator; + } + + generateIndicatorByNumberUrl(source: SourceType, url: string, stakeholder: Stakeholder, jsonPath = [], sourceServices: string[] = []): IndicatorPath { + let indicatorPath = new IndicatorPath(null, source, url, null, jsonPath); + if (source === 'stats-tool') { + indicatorPath.url = url.split("json=")[0] + "json="; + indicatorPath.url = indicatorPath.url.split("/")[indicatorPath.url.split("/").length - 1]; + indicatorPath.chartObject = decodeURIComponent(url.indexOf("json=") != -1 ? url.split("json=")[1] : ""); + let chart = JSON.parse(indicatorPath.chartObject); + this.parameterizeDefaultQuery(chart, indicatorPath, stakeholder); + this.extractStakeHolders(chart, indicatorPath, stakeholder); + indicatorPath.chartObject = JSON.stringify(chart); + if (!jsonPath || jsonPath.length == 0 || (jsonPath.length == 1 && jsonPath[0] == "")) { + indicatorPath.jsonPath = ["data", "0", "0", "0"]; + } + // this.addResultFilters(chart, indicatorPath); + } else { + for (let service of sourceServices) { + if (url.indexOf(service) != -1) { + url = url.split(service)[1]; + } + } + try { + if (url.indexOf(encodeURIComponent(stakeholder.index_id)) !== -1) { + url = url.split(encodeURIComponent(stakeholder.index_id)).join(ChartHelper.prefix + "index_id" + ChartHelper.suffix); + } + if (url.indexOf(encodeURIComponent(stakeholder.index_name)) !== -1) { + url = url.split(encodeURIComponent(stakeholder.index_name)).join(ChartHelper.prefix + "index_name" + ChartHelper.suffix); + } + if (url.indexOf(encodeURIComponent(stakeholder.index_shortName)) !== -1) { + url = url.split(encodeURIComponent(stakeholder.index_shortName)).join(ChartHelper.prefix + "index_shortName" + ChartHelper.suffix); + } + indicatorPath.url = url; + } catch (e) { + console.error(e); + } + } + return indicatorPath; + } + + generateIndicatorByChartUrl(source: SourceType, url: string, type: IndicatorPathType = null, stakeholder: Stakeholder): IndicatorPath { + let indicatorPath = new IndicatorPath(type, source, null, null, []); + try { + if (source === 'stats-tool') { + indicatorPath.url = url.split("json=")[0] + "json="; + indicatorPath.url = indicatorPath.url.split("/")[indicatorPath.url.split("/").length - 1]; + indicatorPath.chartObject = decodeURIComponent(url.split("json=")[1]); + let chart = JSON.parse(indicatorPath.chartObject); + if (indicatorPath.url == "chart?json=") { + + if (chart["library"] && (chart["library"] == "HighCharts" || chart["library"] == "eCharts" || chart["library"] == "HighMaps")) { + indicatorPath.type = this.extractType(chart, indicatorPath); + } else { + indicatorPath.type = this.defaultChartType; + } + + this.extractTitle(chart, indicatorPath); + this.extractSubTitle(chart, indicatorPath); + this.extractXTitle(chart, indicatorPath); + this.extractYTitle(chart, indicatorPath); + } else if (indicatorPath.url == "table?json=") { + indicatorPath.type = "table"; + } + if (indicatorPath.url == "chart?json=" || indicatorPath.url == "table?json=") { + // common for tables and other chart types + this.extractDataTitle(chart, indicatorPath); + this.parameterizeDefaultQuery(chart, indicatorPath, stakeholder); + this.extractStakeHolders(chart, indicatorPath, stakeholder); + this.extractStartYear(chart, indicatorPath); + this.extractEndYear(chart, indicatorPath); + indicatorPath.chartObject = JSON.stringify(chart); + } + } else if (source === 'old') { + indicatorPath.url = url.split("data=")[0].split("/stats/")[1] + "data="; + indicatorPath.chartObject = decodeURIComponent(url.split("data=")[1].split("&")[0]); + indicatorPath.type = type; + let chart = JSON.parse(indicatorPath.chartObject); + this.extractOldToolTitle(chart, indicatorPath); + this.extractOldToolXTitle(chart, indicatorPath); + this.extractOldToolYTitle(chart, indicatorPath); + indicatorPath.chartObject = JSON.stringify(chart); + } else { + indicatorPath.url = url; + indicatorPath.type = type; + } + } catch (e) { + console.error(e); + indicatorPath.url = url; + indicatorPath.type = type; + } + if (indicatorPath.type == null) { + indicatorPath.type = this.defaultChartType; + } + return indicatorPath; + } + + private getQueryObjectName(obj) { + if ((obj[this.getDescriptionObjectName(obj)]).hasOwnProperty("queriesInfo")) { + return "queriesInfo"; + } else if ((obj[this.getDescriptionObjectName(obj)]).hasOwnProperty("queries")) { + return "queries"; + } + } + + private getDescriptionObjectName(obj) { + if (obj.hasOwnProperty("mapDescription")) { + return "mapDescription"; + } else if (obj.hasOwnProperty("chartDescription")) { + return "chartDescription"; + } else if (obj.hasOwnProperty("tableDescription")) { + return "tableDescription"; + } else if (obj.hasOwnProperty("series")) { + return "series"; + } + } + + private extractType(obj, indicatorPath: IndicatorPath): IndicatorPathType { + let type = (obj[this.getDescriptionObjectName(obj)] && obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)][0]["type"]) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)][0]["type"] : ""; + if (this.basicChartTypes.indexOf(type) == -1) { + type = this.defaultChartType; + } else { + obj[this.getDescriptionObjectName(obj)]["queries"][0]["type"] = ChartHelper.prefix + "type" + ChartHelper.suffix; + indicatorPath.parameters['type'] = type; + } + return type; + } + + private extractStakeHolders(obj, indicatorPath: IndicatorPath, stakeholder: Stakeholder) { + this.extractFunder(obj, indicatorPath, stakeholder); + this.extractRI(obj, indicatorPath, stakeholder); + this.extractOrganization(obj, indicatorPath, stakeholder); + } + + private extractFunder(obj, indicatorPath: IndicatorPath, stakeholder: Stakeholder) { + if (stakeholder.type != "funder") { + return; + } + for (let query of this.getQueryObjectName(obj) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)] : obj[this.getDescriptionObjectName(obj)]) { + if (query["query"]["profile"]) { + query["query"]["profile"] = ChartHelper.prefix + this.statsProfileParameter + ChartHelper.suffix; + } + if (!query["query"]["filters"]) { + return; + } + for (let filter of query["query"]["filters"]) { + for (let gfilter of filter["groupFilters"]) { + //ignore field No Of Funders + if (gfilter["field"].indexOf(" funder") != -1 && gfilter["field"].indexOf(" funders") == -1) {//new statistcs schema + gfilter["values"][0] = ChartHelper.prefix + "index_name" + ChartHelper.suffix; + indicatorPath.parameters["index_name"] = stakeholder.index_name; + } else if (gfilter["field"].indexOf(".funder") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_name" + ChartHelper.suffix; + indicatorPath.parameters["index_name"] = stakeholder.index_name; + } else if (gfilter["field"].indexOf(".funder.id") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_shortName" + ChartHelper.suffix; + indicatorPath.parameters["index_shortName"] = stakeholder.index_shortName; + } + } + } + } + } + + private extractRI(obj, indicatorPath: IndicatorPath, stakeholder: Stakeholder) { + if (stakeholder.type != "ri") { + return; + } + for (let query of this.getQueryObjectName(obj) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)] : obj[this.getDescriptionObjectName(obj)]) { + if (query["query"]["profile"]) { + query["query"]["profile"] = ChartHelper.prefix + this.statsProfileParameter + ChartHelper.suffix; + } + if (!query["query"]["filters"]) { + return; + } + for (let filter of query["query"]["filters"]) { + for (let gfilter of filter["groupFilters"]) { + if (gfilter["field"].indexOf(".context.name") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_name" + ChartHelper.suffix; + indicatorPath.parameters["index_name"] = stakeholder.index_name; + } else if (gfilter["field"].indexOf(".context.id") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_shortName" + ChartHelper.suffix; + indicatorPath.parameters["index_shortName"] = stakeholder.index_shortName; + } + } + } + } + } + + private extractOrganization(obj, indicatorPath: IndicatorPath, stakeholder: Stakeholder) { + // works for publication.project.organization.name + // and publication.organization.name + if (stakeholder.type != "organization") { + return; + } + for (let query of this.getQueryObjectName(obj) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)] : obj[this.getDescriptionObjectName(obj)]) { + if (query["query"]["profile"]) { + query["query"]["profile"] = ChartHelper.prefix + this.statsProfileParameter + ChartHelper.suffix; + } + if (!query["query"]["filters"]) { + return; + } + for (let filter of query["query"]["filters"]) { + for (let gfilter of filter["groupFilters"]) { + if (gfilter["field"].indexOf(".organization.name") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_name" + ChartHelper.suffix; + indicatorPath.parameters["index_name"] = stakeholder.index_name; + } else if (gfilter["field"].indexOf(".organization.id") != -1) { + gfilter["values"][0] = ChartHelper.prefix + "index_shortName" + ChartHelper.suffix; + indicatorPath.parameters["index_shortName"] = stakeholder.index_shortName; + } + } + } + } + } + + private extractStartYear(obj, indicatorPath: IndicatorPath) { + let start_year; + for (let query of obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)]) { + if (!query["query"]["filters"]) { + return; + } + for (let filter of query["query"]["filters"]) { + for (let gfilter of filter["groupFilters"]) { + if ((gfilter["field"].indexOf(".year") != -1 || gfilter["field"].indexOf(".start year") != -1) && gfilter["type"].indexOf(">") != -1) { + start_year = gfilter["values"][0]; + gfilter["values"][0] = ChartHelper.prefix + "start_year" + ChartHelper.suffix; + indicatorPath.parameters["start_year"] = start_year; + } + } + } + } + } + + private extractEndYear(obj, indicatorPath: IndicatorPath) { + let end_year; + for (let query of obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)]) { + if (!query["query"]["filters"]) { + return; + } + for (let filter of query["query"]["filters"]) { + for (let gfilter of filter["groupFilters"]) { + if ((gfilter["field"].indexOf(".year") != -1 || gfilter["field"].indexOf(".start year") != -1) && gfilter["type"].indexOf("<") != -1) { + end_year = gfilter["values"][0]; + gfilter["values"][0] = ChartHelper.prefix + "end_year" + ChartHelper.suffix; + indicatorPath.parameters["end_year"] = end_year; + } + } + } + } + } + + private parameterizeDefaultQuery(obj, indicatorPath: IndicatorPath, stakeholder: Stakeholder) { + let name = ""; + for (let query of this.getQueryObjectName(obj) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)] : obj[this.getDescriptionObjectName(obj)]) { + //monitor.{{stakeholderType}}.{{queryname}} + //parameters: stakeholderId*, type + if (query["query"]["name"]) { + name = query["query"]["name"]; + let parameters = (query["query"]["parameters"]) ? query["query"]["parameters"] : []; + if (name.split('.')[0] == "rcd" && parameters.length > 0 && stakeholder.type == "ri") { + //rcd.{{queryname}} + parameters[0] = ChartHelper.prefix + "index_id" + ChartHelper.suffix; + indicatorPath.parameters["index_id"] = stakeholder.index_id; + } else if (name.split('.')[0] == "monitor" && parameters.length == 0 && stakeholder.type == "funder") { + // old saved queries without params + //monitor.{{funder_shortName}}.{{type}}.{{queryname}} + let stakeholderSN = name.split('.')[1]; + query["query"]["name"] = name.split('.' + stakeholderSN + ".")[0] + "." + ChartHelper.prefix + "index_shortName" + ChartHelper.suffix + "." + name.split('.' + stakeholderSN + ".")[1]; + indicatorPath.parameters["index_shortName"] = stakeholder.index_shortName.toLowerCase(); + } else if (name.split('.')[0] == "monitor" && parameters.length > 0 && name.split('.')[1] == stakeholder.type) { + // new parameterized queries + //monitor.{{type}}.{{queryname}}.{{param1 - id }}.{{param2 result-type}}.{{fl0}} --> params [start year, end year, id, result type, fl0] + + let index = (name.split('.').slice(3).length + 2 == parameters.length) ? [2] : ((name.split('.').slice(3).length * 2 + 4 == parameters.length) ? [2, name.split('.').slice(3).length + 4] : [0]); + for (let i of index) { + if (name.split('.').length > 3 && name.split('.')[3] == "id") { + parameters[i] = ChartHelper.prefix + "index_id" + ChartHelper.suffix; + indicatorPath.parameters["index_id"] = stakeholder.index_id; + } else if (name.split('.').length > 3 && name.split('.')[3] == "shortname") { + parameters[i] = ChartHelper.prefix + "index_shortName" + ChartHelper.suffix; + indicatorPath.parameters["index_shortName"] = stakeholder.index_shortName.toLowerCase(); + } else if (name.split('.').length > 3 && name.split('.')[3] == "name") { + parameters[i] = ChartHelper.prefix + "index_name" + ChartHelper.suffix; + indicatorPath.parameters["index_name"] = stakeholder.index_name; + } + } + } + } + } + } + + private extractDataTitle(obj, indicatorPath: IndicatorPath) { + let index = 0; + if (!obj[this.getDescriptionObjectName(obj)] || !obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)]) { + return; + } + for (let query of obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)]) { + if (query["name"]) { + let name = query["name"]; + query["name"] = ChartHelper.prefix + "data_title_" + index + ChartHelper.suffix; + indicatorPath.parameters["data_title_" + index] = name; + } + index++; + } + } + + private extractTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj[this.getDescriptionObjectName(obj)]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["title"]["text"]; + obj[this.getDescriptionObjectName(obj)]["title"]["text"] = ChartHelper.prefix + "title" + ChartHelper.suffix; + } else if (obj[this.getDescriptionObjectName(obj)]["options"] && obj[this.getDescriptionObjectName(obj)]["options"]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["options"]["title"]; + obj[this.getDescriptionObjectName(obj)]["options"]["title"] = ChartHelper.prefix + "title" + ChartHelper.suffix; + } + indicatorPath.parameters["title"] = title ? title : ""; + } + + private extractSubTitle(obj, indicatorPath: IndicatorPath) { + let subtitle = ""; + if (obj[this.getDescriptionObjectName(obj)]["subtitle"]) { + subtitle = obj[this.getDescriptionObjectName(obj)]["subtitle"]["text"]; + obj[this.getDescriptionObjectName(obj)]["subtitle"]["text"] = ChartHelper.prefix + "subtitle" + ChartHelper.suffix; + indicatorPath.parameters["subtitle"] = subtitle ? subtitle : ""; + } else if (obj[this.getDescriptionObjectName(obj)]["title"] && obj[this.getDescriptionObjectName(obj)]["title"] && obj[this.getDescriptionObjectName(obj)]["title"]["subtext"]) { + subtitle = obj[this.getDescriptionObjectName(obj)]["title"]["subtext"]; + obj[this.getDescriptionObjectName(obj)]["title"]["subtext"] = ChartHelper.prefix + "subtitle" + ChartHelper.suffix; + indicatorPath.parameters["subtitle"] = subtitle ? subtitle : ""; + } + } + + private extractXTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj[this.getDescriptionObjectName(obj)]["xAxis"] && obj[this.getDescriptionObjectName(obj)]["xAxis"]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["xAxis"]["title"]["text"]; + obj[this.getDescriptionObjectName(obj)]["xAxis"]["title"]["text"] = ChartHelper.prefix + "xAxisTitle" + ChartHelper.suffix; + } else if (obj[this.getDescriptionObjectName(obj)]["options"] && obj[this.getDescriptionObjectName(obj)]["options"]["hAxis"] && obj[this.getDescriptionObjectName(obj)]["options"]["hAxis"]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["options"]["hAxis"]["title"]; + obj[this.getDescriptionObjectName(obj)]["options"]["hAxis"]["title"] = ChartHelper.prefix + "xAxisTitle" + ChartHelper.suffix; + } else if (obj[this.getDescriptionObjectName(obj)]["xAxis"] && obj[this.getDescriptionObjectName(obj)]["xAxis"]["name"]) { + title = obj[this.getDescriptionObjectName(obj)]["xAxis"]["name"]; + obj[this.getDescriptionObjectName(obj)]["xAxis"]["name"] = ChartHelper.prefix + "xAxisTitle" + ChartHelper.suffix; + } + indicatorPath.parameters["xAxisTitle"] = title ? title : ""; + } + + private extractYTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj[this.getDescriptionObjectName(obj)]["yAxis"] && obj[this.getDescriptionObjectName(obj)]["yAxis"]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["yAxis"]["title"]["text"]; + obj[this.getDescriptionObjectName(obj)]["yAxis"]["title"]["text"] = ChartHelper.prefix + "yAxisTitle" + ChartHelper.suffix; + } else if (obj[this.getDescriptionObjectName(obj)]["options"] && obj[this.getDescriptionObjectName(obj)]["options"]["vAxis"] && obj[this.getDescriptionObjectName(obj)]["options"]["vAxis"]["title"]) { + title = obj[this.getDescriptionObjectName(obj)]["options"]["vAxis"]["title"]; + obj[this.getDescriptionObjectName(obj)]["options"]["vAxis"]["title"] = ChartHelper.prefix + "yAxisTitle" + ChartHelper.suffix; + } else if (obj[this.getDescriptionObjectName(obj)]["yAxis"] && obj[this.getDescriptionObjectName(obj)]["yAxis"]["name"]) { + title = obj[this.getDescriptionObjectName(obj)]["yAxis"]["name"]; + obj[this.getDescriptionObjectName(obj)]["yAxis"]["name"] = ChartHelper.prefix + "xAxisTitle" + ChartHelper.suffix; + } + indicatorPath.parameters["yAxisTitle"] = title ? title : ""; + } + + private extractOldToolTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj["title"]) { + title = obj["title"]; + obj["title"] = ChartHelper.prefix + "title" + ChartHelper.suffix; + indicatorPath.parameters["title"] = title; + + } + } + + private extractOldToolXTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj["xaxistitle"]) { + title = obj["xaxistitle"]; + obj["xaxistitle"] = ChartHelper.prefix + "xAxisTitle" + ChartHelper.suffix; + indicatorPath.parameters["xAxisTitle"] = title; + } + } + + private extractOldToolYTitle(obj, indicatorPath: IndicatorPath) { + let title = ""; + if (obj["fieldsheaders"]) { + title = Array.isArray(obj["fieldsheaders"]) ? obj["fieldsheaders"][0] : obj["fieldsheaders"]; + if (Array.isArray(obj["fieldsheaders"])) { + obj["fieldsheaders"][0] = ChartHelper.prefix + "yAxisTitle" + ChartHelper.suffix; + } else { + obj["fieldsheaders"] = ChartHelper.prefix + "yAxisTitle" + ChartHelper.suffix; + } + indicatorPath.parameters["yAxisTitle"] = title; + } + } + + public checkForSchemaEnhancements(url: string): boolean { + return url != this.applySchemaEnhancements(url); + } + + public applySchemaEnhancements(url: string): string { + let resultEnhancements = [ + [".project.acronym", ".project acronym"], + [".project.title", ".project title"], + [".project.funder", ".project funder"], + [".project.funding level 0", ".project funding level 0"], + [".datasource.name", ".HostedBy datasource"], + [".datasource.type", ".HostedBy datasource type"] + ]; + let changes = ""; + for (let field of resultEnhancements) { + for (let type of ["publication", "software", "dataset", "other", "result"]) { + if (url.indexOf(encodeURIComponent(type + field[0])) != -1) { + changes += "Changed " + type + field[0] + " to " + type + field[1] + "\n"; + url = url.split(encodeURIComponent(type + field[0])).join(encodeURIComponent(type + field[1])); + } + } + } + + if (url.split('json=').length > 1) { + let obj = JSON.parse(decodeURIComponent(url.split('json=')[1])); + for (let query of this.getQueryObjectName(obj) ? obj[this.getDescriptionObjectName(obj)][this.getQueryObjectName(obj)] : obj[this.getDescriptionObjectName(obj)]) { + if (!query["query"]["profile"] || query["query"]["profile"] == 'OpenAIRE All-inclusive' || query["query"]["profile"] == 'OpenAIRE original') { + changes += (query["query"]["profile"] ? ("Changed profile \"" + query["query"]["profile"] + "\" to ") : "Added profile ") + " \"monitor\""; + query["query"]["profile"] = 'monitor'; + } + } + url = url.split('json=')[0] + "json=" + encodeURIComponent(JSON.stringify(obj)); + } + return url; + } +} diff --git a/monitor-admin/utils/services/statistics.service.ts b/monitor-admin/utils/services/statistics.service.ts new file mode 100644 index 00000000..d4b6031c --- /dev/null +++ b/monitor-admin/utils/services/statistics.service.ts @@ -0,0 +1,24 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {SourceType} from "../../../monitor/entities/stakeholder"; +import {IndicatorUtils} from "../indicator-utils"; + + +@Injectable({ + providedIn: 'root' +}) +export class StatisticsService { + + indicatorsUtils = new IndicatorUtils(); + + constructor(private http: HttpClient) {} + + getNumbers(source: SourceType, url: string): Observable { + if (source !== null) { + return this.http.get(this.indicatorsUtils.getNumberUrl(source, url)); + } else { + return this.http.get(url); + } + } +} diff --git a/monitor-admin/utils/services/stats-profiles.service.ts b/monitor-admin/utils/services/stats-profiles.service.ts new file mode 100644 index 00000000..75cecc54 --- /dev/null +++ b/monitor-admin/utils/services/stats-profiles.service.ts @@ -0,0 +1,19 @@ +import {Injectable} from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {properties} from "src/environments/environment"; +import {Observable} from "rxjs"; +import {map} from "rxjs/operators"; + +@Injectable({ + providedIn: 'root' +}) +export class StatsProfilesService { + + constructor(private http: HttpClient) { + } + + getStatsProfiles(): Observable { + return this.http.get(properties.monitorStatsFrameUrl + 'schema/profiles') + .pipe(map(profiles => profiles.map(profile => profile.name))); + } +} diff --git a/services/servicesUtils/customOptions.class.ts b/services/servicesUtils/customOptions.class.ts index 4a9572e4..c935ac31 100644 --- a/services/servicesUtils/customOptions.class.ts +++ b/services/servicesUtils/customOptions.class.ts @@ -1,4 +1,3 @@ -import {COOKIE} from '../../login/utils/helper.class'; import {HttpHeaders} from "@angular/common/http"; export type MediaType = 'application/json' | 'text/plain' From 1710819fb9d35bedeaca4d85db664b8f79be2e76 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Fri, 20 Oct 2023 19:07:33 +0300 Subject: [PATCH 3/4] [monitor-admin-library | DONE | CHANGED]: Move general in library --- .../edit-stakeholder.component.ts | 30 +++++++++---------- .../edit-stakeholder.module.ts | 6 ++-- .../general/general-routing.module.ts | 2 +- monitor-admin/general/general.component.ts | 8 ++--- monitor-admin/general/general.module.ts | 14 ++++----- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts index b66b680a..f84f197d 100644 --- a/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts +++ b/monitor-admin/general/edit-stakeholder/edit-stakeholder.component.ts @@ -1,21 +1,21 @@ import {Component, Input, OnDestroy, ViewChild} from "@angular/core"; -import {Stakeholder} from "../../openaireLibrary/monitor/entities/stakeholder"; +import {Stakeholder} from "../../../monitor/entities/stakeholder"; import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms"; -import {StakeholderUtils} from "../../openaireLibrary/monitor-admin/utils/indicator-utils"; -import {Option} from "../../openaireLibrary/sharedComponents/input/input.component"; +import {StakeholderUtils} from "../../utils/indicator-utils"; +import {Option} from "../../../sharedComponents/input/input.component"; import {Subscription} from "rxjs"; -import {EnvProperties} from "../../openaireLibrary/utils/properties/env-properties"; -import {properties} from "../../../environments/environment"; -import {StakeholderService} from "../../openaireLibrary/monitor/services/stakeholder.service"; -import {UtilitiesService} from "../../openaireLibrary/services/utilities.service"; -import {Role, Session, User} from "../../openaireLibrary/login/utils/helper.class"; -import {UserManagementService} from "../../openaireLibrary/services/user-management.service"; -import {StringUtils} from "../../openaireLibrary/utils/string-utils.class"; -import {NotifyFormComponent} from "../../openaireLibrary/notifications/notify-form/notify-form.component"; -import {NotificationUtils} from "../../openaireLibrary/notifications/notification-utils"; -import {Notification} from "../../openaireLibrary/notifications/notifications"; -import {NotificationHandler} from "../../openaireLibrary/utils/notification-handler"; -import {StatsProfilesService} from "../../openaireLibrary/monitor-admin/utils/services/stats-profiles.service"; +import {EnvProperties} from "../../../utils/properties/env-properties"; +import {properties} from "src/environments/environment"; +import {StakeholderService} from "../../../monitor/services/stakeholder.service"; +import {UtilitiesService} from "../../../services/utilities.service"; +import {Role, Session, User} from "../../../login/utils/helper.class"; +import {UserManagementService} from "../../../services/user-management.service"; +import {StringUtils} from "../../../utils/string-utils.class"; +import {NotifyFormComponent} from "../../../notifications/notify-form/notify-form.component"; +import {NotificationUtils} from "../../../notifications/notification-utils"; +import {Notification} from "../../../notifications/notifications"; +import {NotificationHandler} from "../../../utils/notification-handler"; +import {StatsProfilesService} from "../../utils/services/stats-profiles.service"; @Component({ selector: 'edit-stakeholder', diff --git a/monitor-admin/general/edit-stakeholder/edit-stakeholder.module.ts b/monitor-admin/general/edit-stakeholder/edit-stakeholder.module.ts index 6f08416b..0ebec4d3 100644 --- a/monitor-admin/general/edit-stakeholder/edit-stakeholder.module.ts +++ b/monitor-admin/general/edit-stakeholder/edit-stakeholder.module.ts @@ -1,10 +1,10 @@ import {NgModule} from "@angular/core"; import {EditStakeholderComponent} from "./edit-stakeholder.component"; import {CommonModule} from "@angular/common"; -import {InputModule} from "../../openaireLibrary/sharedComponents/input/input.module"; +import {InputModule} from "../../../sharedComponents/input/input.module"; import {ReactiveFormsModule} from "@angular/forms"; -import {IconsModule} from "../../openaireLibrary/utils/icons/icons.module"; -import {NotifyFormModule} from "../../openaireLibrary/notifications/notify-form/notify-form.module"; +import {IconsModule} from "../../../utils/icons/icons.module"; +import {NotifyFormModule} from "../../../notifications/notify-form/notify-form.module"; @NgModule({ imports: [CommonModule, InputModule, ReactiveFormsModule, IconsModule, NotifyFormModule], diff --git a/monitor-admin/general/general-routing.module.ts b/monitor-admin/general/general-routing.module.ts index 0dec7485..e0486667 100644 --- a/monitor-admin/general/general-routing.module.ts +++ b/monitor-admin/general/general-routing.module.ts @@ -1,6 +1,6 @@ import {NgModule} from '@angular/core'; import {RouterModule} from '@angular/router'; -import {PreviousRouteRecorder} from '../openaireLibrary/utils/piwik/previousRouteRecorder.guard'; +import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard'; import {GeneralComponent} from "./general.component"; @NgModule({ diff --git a/monitor-admin/general/general.component.ts b/monitor-admin/general/general.component.ts index 03045f06..611032b4 100644 --- a/monitor-admin/general/general.component.ts +++ b/monitor-admin/general/general.component.ts @@ -1,10 +1,10 @@ import {ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from "@angular/core"; -import {StakeholderService} from "../openaireLibrary/monitor/services/stakeholder.service"; -import {EnvProperties} from "../openaireLibrary/utils/properties/env-properties"; -import {Stakeholder} from "../openaireLibrary/monitor/entities/stakeholder"; +import {StakeholderService} from "../../monitor/services/stakeholder.service"; +import {EnvProperties} from "../../utils/properties/env-properties"; +import {Stakeholder} from "../../monitor/entities/stakeholder"; import { Subscription, zip} from "rxjs"; import {EditStakeholderComponent} from "./edit-stakeholder/edit-stakeholder.component"; -import {properties} from "../../environments/environment"; +import {properties} from "src/environments/environment"; import {Title} from "@angular/platform-browser"; @Component({ diff --git a/monitor-admin/general/general.module.ts b/monitor-admin/general/general.module.ts index 054c309f..7f4e7064 100644 --- a/monitor-admin/general/general.module.ts +++ b/monitor-admin/general/general.module.ts @@ -1,19 +1,19 @@ import {NgModule} from "@angular/core"; import {GeneralComponent} from "./general.component"; import {GeneralRoutingModule} from "./general-routing.module"; -import {PreviousRouteRecorder} from "../openaireLibrary/utils/piwik/previousRouteRecorder.guard"; +import {PreviousRouteRecorder} from "../../utils/piwik/previousRouteRecorder.guard"; import {CommonModule} from "@angular/common"; import {RouterModule} from "@angular/router"; -import {InputModule} from "../openaireLibrary/sharedComponents/input/input.module"; -import {LoadingModule} from "../openaireLibrary/utils/loading/loading.module"; -import {AlertModalModule} from "../openaireLibrary/utils/modal/alertModal.module"; +import {InputModule} from "../../sharedComponents/input/input.module"; +import {LoadingModule} from "../../utils/loading/loading.module"; +import {AlertModalModule} from "../../utils/modal/alertModal.module"; import {ReactiveFormsModule} from "@angular/forms"; import {EditStakeholderModule} from "./edit-stakeholder/edit-stakeholder.module"; -import {PageContentModule} from "../openaireLibrary/dashboard/sharedComponents/page-content/page-content.module"; -import {LogoUrlPipeModule} from "../openaireLibrary/utils/pipes/logoUrlPipe.module"; +import {PageContentModule} from "../../dashboard/sharedComponents/page-content/page-content.module"; +import {LogoUrlPipeModule} from "../../utils/pipes/logoUrlPipe.module"; import { SidebarMobileToggleModule -} from "../openaireLibrary/dashboard/sharedComponents/sidebar/sidebar-mobile-toggle/sidebar-mobile-toggle.module"; +} from "../../dashboard/sharedComponents/sidebar/sidebar-mobile-toggle/sidebar-mobile-toggle.module"; @NgModule({ declarations: [GeneralComponent], From b67c242fe70de58057b19620d3c80276c4473a8f Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Tue, 24 Oct 2023 11:51:55 +0300 Subject: [PATCH 4/4] [monitor-admin-library | DONE | CHANGED]: Make stakeholder types dynamic for Session methods. --- login/utils/helper.class.ts | 29 ++++--------------- monitor-admin/utils/indicator-utils.ts | 7 ++--- monitor/entities/stakeholder.ts | 8 +++++ .../indicators/indicator-themes.component.ts | 8 +++-- 4 files changed, 20 insertions(+), 32 deletions(-) diff --git a/login/utils/helper.class.ts b/login/utils/helper.class.ts index 63e15b2f..7ade96d7 100644 --- a/login/utils/helper.class.ts +++ b/login/utils/helper.class.ts @@ -1,3 +1,5 @@ +import {stakeholderTypes} from "../../monitor/entities/stakeholder"; + export class User { email: string; firstname: string; @@ -98,23 +100,11 @@ export class Session { } public static isMonitorCurator(user: User): boolean { - return this.isCommunityCurator(user) || this.isProjectCurator(user) || this.isFunderCurator(user) || this.isOrganizationCurator(user); + return stakeholderTypes.filter(stakeholderType => this.isTypeCurator(stakeholderType.value, user)).length > 0; } public static isCommunityCurator(user: User): boolean { - return this.isTypeCurator("Community", user); - } - - public static isFunderCurator(user: User): boolean { - return this.isTypeCurator("Funder", user); - } - - public static isProjectCurator(user: User): boolean { - return this.isTypeCurator("Project", user); - } - - public static isOrganizationCurator(user: User): boolean { - return this.isTypeCurator("Institution", user); + return this.isTypeCurator("community", user); } private static isTypeCurator(type: string, user: User): boolean { @@ -122,16 +112,7 @@ export class Session { } public static isCurator(type: string, user: User): boolean { - if (type == 'funder') { - return user && this.isFunderCurator(user); - } else if (type == 'ri' || type == 'community') { - return user && this.isCommunityCurator(user); - } else if (type == 'organization' || type == 'institution') { - return user && this.isOrganizationCurator(user); - } else if (type == 'project') { - return user && this.isProjectCurator(user); - } - return false; + return stakeholderTypes.find(stakeholderType => stakeholderType.value == type) && this.isTypeCurator(type, user); } public static isPortalAdministrator(user: User): boolean { diff --git a/monitor-admin/utils/indicator-utils.ts b/monitor-admin/utils/indicator-utils.ts index f7c71c0b..4dae319c 100644 --- a/monitor-admin/utils/indicator-utils.ts +++ b/monitor-admin/utils/indicator-utils.ts @@ -10,7 +10,7 @@ import { Stakeholder, StakeholderEntities, SubCategory, - Topic, + Topic, stakeholderTypes, Visibility } from "../../monitor/entities/stakeholder"; import {AbstractControl, ValidatorFn, Validators} from "@angular/forms"; @@ -28,10 +28,7 @@ export class StakeholderUtils { ]; types: Option[] = [ - {value: 'funder', label: StakeholderEntities.FUNDER}, - {value: 'ri', label: StakeholderEntities.RI}, - {value: 'project', label: StakeholderEntities.PROJECT}, - {value: 'organization', label: StakeholderEntities.ORGANIZATION} + ...stakeholderTypes ]; visibility: Option[] = [ diff --git a/monitor/entities/stakeholder.ts b/monitor/entities/stakeholder.ts index 09ab7022..13ed58cf 100644 --- a/monitor/entities/stakeholder.ts +++ b/monitor/entities/stakeholder.ts @@ -1,6 +1,7 @@ import {SafeResourceUrl} from "@angular/platform-browser"; import {properties} from "../../../../environments/environment"; import {Session, User} from "../../login/utils/helper.class"; +import {Option} from "../../sharedComponents/input/input.component"; export const ChartHelper = { prefix: "((__", @@ -309,3 +310,10 @@ export enum StakeholderEntities { ORGANIZATIONS = 'Research Institutions', PROJECTS = 'Projects' } + +export let stakeholderTypes: Option[] = [ + {value: 'funder', label: StakeholderEntities.FUNDER}, + {value: 'ri', label: StakeholderEntities.RI}, + {value: 'project', label: StakeholderEntities.PROJECT}, + {value: 'organization', label: StakeholderEntities.ORGANIZATION} +]; diff --git a/monitor/indicators/indicator-themes.component.ts b/monitor/indicators/indicator-themes.component.ts index fcb11a8e..4fb9e7e0 100644 --- a/monitor/indicators/indicator-themes.component.ts +++ b/monitor/indicators/indicator-themes.component.ts @@ -5,6 +5,7 @@ import {Meta, Title} from "@angular/platform-browser"; import {SEOService} from "../../sharedComponents/SEO/SEO.service"; import {Breadcrumb} from "../../utils/breadcrumbs/breadcrumbs.component"; import {Subscriber} from "rxjs"; +import {StakeholderEntities} from "../entities/stakeholder"; @Component({ selector: 'indicator-themes-page', @@ -53,9 +54,9 @@ import {Subscriber} from "rxjs"; 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) + 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.

@@ -67,6 +68,7 @@ import {Subscriber} from "rxjs"; export class IndicatorThemesComponent implements OnInit, OnDestroy { private subscriptions: any[] = []; public properties = properties; + public entities = StakeholderEntities; public breadcrumbs: Breadcrumb[] = [{name: 'home', route: '/'}, {name: 'Resources - Themes'}]; constructor(private router: Router,