From cf30ab658157157c898cb9a9b6211eee77cd5ef0 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Thu, 19 Oct 2023 16:33:04 +0300 Subject: [PATCH] [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'