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