From 21c70d6d621cc6b1c17a26113f798065b7b3cd8a Mon Sep 17 00:00:00 2001 From: argirok Date: Fri, 28 Jul 2023 12:15:54 +0300 Subject: [PATCH 01/48] updated claims api: submit job and get status --- claims/claim-utils/service/claims.service.ts | 10 +++ .../insertClaim/insertClaim.component.ts | 67 ++++++++++++++++--- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/claims/claim-utils/service/claims.service.ts b/claims/claim-utils/service/claims.service.ts index b9f32054..26a3d4d3 100644 --- a/claims/claim-utils/service/claims.service.ts +++ b/claims/claim-utils/service/claims.service.ts @@ -106,6 +106,16 @@ export class ClaimsService { .pipe(catchError(this.handleError)); } + getStatus(jobId, apiUrl:string):any{ + + let url = apiUrl +"jobStatus/" + jobId; + + return this.http.get(url,CustomOptions.getAuthOptions()) + //.map(res => res.json()) + //.do(request => console.info("Insert Response:"+request) ) + .pipe(catchError(this.handleError)); + + } private handleError (error: Response) { // in a real world app, we may send the error to some remote logging infrastructure // instead of just logging it to the console diff --git a/claims/linking/insertClaim/insertClaim.component.ts b/claims/linking/insertClaim/insertClaim.component.ts index cb1cef70..2d1a276f 100644 --- a/claims/linking/insertClaim/insertClaim.component.ts +++ b/claims/linking/insertClaim/insertClaim.component.ts @@ -14,7 +14,8 @@ import { Message } from "../../claim-utils/claimHelper.class"; import {UserManagementService} from "../../../services/user-management.service"; -import {Subscriber} from "rxjs"; +import {Subscriber, timer} from "rxjs"; +import {map} from "rxjs/operators"; @Component({ selector: 'claim-insert', @@ -30,7 +31,18 @@ import {Subscriber} from "rxjs";
CONFIRM LINKING
- + + +
+
+ Initiating process....
+
+{{claimsJob.insertedIds.length}} out of {{claims2Insert}} links created.
+
{{feedRecordsJob.length}} records added in the index...
+
+ Please don't close the window, process is ongoing...
+
+

All the links you provided will be published in the OpenAIRE platform.
@@ -84,7 +96,9 @@ export class ClaimInsertComponent { private errorInClaims: ClaimRecord2Insert[] = []; private insertedRecords = []; private errorInRecords = []; - + public claimsJob; + public feedRecordsJob; + public claims2Insert; public insert() { this.confirmOpen(); } @@ -180,9 +194,9 @@ export class ClaimInsertComponent { if (directclaims.length > 0 && this.properties.environment != "development"){ this.subscriptions.push(this.claimService.insertDirectRecords(directclaims, this.properties.claimsAPIURL).subscribe( data => { - this.insertedRecords = data.insertedIds; - - this.errorInRecords = data.errorInClaims; + // this.insertedRecords = data.insertedIds; + // + // this.errorInRecords = data.errorInClaims; this.isertBulkClaims(claims); }, err => { @@ -214,7 +228,7 @@ export class ClaimInsertComponent { this.errors.splice(0, this.errors.length); this.subscriptions.push(this.claimService.insertBulkClaims(claims, this.properties.claimsAPIURL).subscribe( data => { - this.insertedClaims = data.insertedIds; + /*this.insertedClaims = data.insertedIds; this.errorInClaims = data.errorInClaims; //TODO remove - testing having errors in claims // this.insertedClaims.pop(); @@ -233,6 +247,17 @@ export class ClaimInsertComponent { this.errors.push(error); } this.afterclaimsInsertion(); + + */ + console.log(data) + this.claims2Insert = claims.length; + this.claimsJob = data.data; + let timerSubscription = timer(0, 10000).pipe( + map(() => { + this.getStatus(); // load data contains the http request + }) + ).subscribe(); + this.subscriptions.push(timerSubscription); }, err => { err = err && err.error?err.error:err; @@ -517,8 +542,34 @@ export class ClaimInsertComponent { } if(this.defaultColors){ buttonClass+=" linksbaskettitles uk-padding-small "; - } + } return buttonClass + "uk-text-center "; } + + getStatus(){ + + console.log("TODO status fetch") + this.subscriptions.push(this.claimService.getStatus(this.claimsJob.id, this.properties.claimsAPIURL).subscribe(data => { + console.log(data); + this.claimsJob = data.data; + if(this.claimsJob.status == "COMPLETE" || data.data.status == "ERROR"){ + this.insertedClaims = this.claimsJob.insertedIds; + this.errorInClaims = this.claimsJob.errorInClaims; + + if (this.claims2Insert != this.insertedClaims.length) { + let error: ClaimsErrorMessage = new ClaimsErrorMessage(); + error.type = "claimServiceFail2Insert"; + error.inserted = this.insertedClaims.length; + error.failed = this.errorInClaims.length; + this.createErrorMessagesPerEntity((this.insertedClaims.length == 0)); + this.errors.push(error); + } + this.afterclaimsInsertion(); + + + } + + })); + } } From 3b6822c71f32512533793e6b57f6ff017f4c874e Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Tue, 1 Aug 2023 11:40:55 +0300 Subject: [PATCH 02/48] Update material to version 15 --- .../claimEntitiesSelection.component.ts | 39 ------------------- .../claimProjectSearchForm.module.ts | 20 ++++------ dashboard/divId/divIds.module.ts | 12 +----- .../page-help-content-form.module.ts | 13 +++---- landingPages/feedback/feedback.module.ts | 3 +- .../citeThis/citeThis.component.ts | 12 +----- .../landing-utils/citeThis/citeThis.module.ts | 3 +- .../organization/organization.module.ts | 2 - landingPages/project/project.module.ts | 3 +- landingPages/result/resultLanding.module.ts | 4 +- .../notify-form/notify-form.module.ts | 3 +- .../advancedSearchForm.component.html | 11 +----- .../searchUtils/advancedSearchForm.module.ts | 3 +- .../searchUtils/entitiesSelection.module.ts | 3 +- .../searchUtils/quick-selections.component.ts | 1 - .../searchUtils/searchFilter.module.ts | 20 ++++------ .../searchUtils/searchSorting.module.ts | 14 +++---- .../search-input/search-input.module.ts | 3 +- utils/entities/entities.module.ts | 23 ----------- 19 files changed, 39 insertions(+), 153 deletions(-) delete mode 100644 claims/claim-utils/claimEntitiesSelection.component.ts delete mode 100644 utils/entities/entities.module.ts diff --git a/claims/claim-utils/claimEntitiesSelection.component.ts b/claims/claim-utils/claimEntitiesSelection.component.ts deleted file mode 100644 index 66c70f71..00000000 --- a/claims/claim-utils/claimEntitiesSelection.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {Component, Input, ViewChild} from '@angular/core'; -import {ShowOptions} from './claimHelper.class'; -import {MatSelect} from "@angular/material/select"; -import {OpenaireEntities} from "../../utils/properties/searchFields"; - - -@Component({ - selector: 'claim-enities-selection', - - template:` - - - - {{openaireEntities.RESULTS}} - {{openaireEntities.PROJECTS}} - {{openaireEntities.COMMUNITIES}} - - - - - - `, - -}) -export class ClaimEntitiesSelectionComponent{ - @ViewChild(MatSelect) matSelect: MatSelect; - @Input() showOptions:ShowOptions = new ShowOptions(); - public openaireEntities = OpenaireEntities; - - open() { - if (this.matSelect && !this.matSelect.focused) { - this.matSelect.open(); - } - } - -} diff --git a/claims/claim-utils/claimProjectSearchForm.module.ts b/claims/claim-utils/claimProjectSearchForm.module.ts index 5293d430..3d2ace89 100644 --- a/claims/claim-utils/claimProjectSearchForm.module.ts +++ b/claims/claim-utils/claimProjectSearchForm.module.ts @@ -1,11 +1,9 @@ -import { NgModule } from '@angular/core'; +import {NgModule} from '@angular/core'; -import { SharedModule } from '../../../openaireLibrary/shared/shared.module'; -import { CommonModule } from '@angular/common'; +import {SharedModule} from '../../shared/shared.module'; +import {CommonModule} from '@angular/common'; import {ClaimProjectsSearchFormComponent} from './claimProjectSearchForm.component'; -// import {LoadingModalModule} from '../../utils/modal/loadingModal.module'; - import {ProjectServiceModule} from '../../landingPages/project/projectService.module'; import {ProjectsServiceModule} from '../../services/projectsService.module'; import {EntitiesAutocompleteModule} from '../../utils/entitiesAutoComplete/entitiesAutoComplete.module'; @@ -13,29 +11,25 @@ import {HelperModule} from '../../utils/helper/helper.module'; import {ClaimResultsModule} from './claimResults.module'; -import {PagingModule } from '../../utils/paging.module'; +import {PagingModule} from '../../utils/paging.module'; import {SearchFilterModule} from '../../searchPages/searchUtils/searchFilter.module'; import {RangeFilterModule} from "../../utils/rangeFilter/rangeFilter.module"; -import {ClaimEntitiesSelectionComponent} from "./claimEntitiesSelection.component"; -import {MatSelectModule} from "@angular/material/select"; import {AdvancedSearchInputModule} from "../../sharedComponents/advanced-search-input/advanced-search-input.module"; import {InputModule} from "../../sharedComponents/input/input.module"; import {DropdownFilterModule} from "../../utils/dropdown-filter/dropdown-filter.module"; - @NgModule({ +@NgModule({ imports: [ SharedModule, CommonModule, // LoadingModalModule, ProjectServiceModule, ProjectsServiceModule, EntitiesAutocompleteModule, HelperModule, - PagingModule, SearchFilterModule, ClaimResultsModule, RangeFilterModule, MatSelectModule, AdvancedSearchInputModule, InputModule, DropdownFilterModule + PagingModule, SearchFilterModule, ClaimResultsModule, RangeFilterModule, AdvancedSearchInputModule, InputModule, DropdownFilterModule ], providers:[ ], declarations: [ ClaimProjectsSearchFormComponent, - ClaimEntitiesSelectionComponent - ], - exports: [ClaimProjectsSearchFormComponent, ClaimEntitiesSelectionComponent] + exports: [ClaimProjectsSearchFormComponent] }) export class ClaimProjectsSearchFormModule { } diff --git a/dashboard/divId/divIds.module.ts b/dashboard/divId/divIds.module.ts index e379ded4..eca996e6 100644 --- a/dashboard/divId/divIds.module.ts +++ b/dashboard/divId/divIds.module.ts @@ -6,14 +6,6 @@ import {AlertModalModule} from '../../utils/modal/alertModal.module'; import {DivIdsComponent} from './divIds.component'; import {AdminToolServiceModule} from "../../services/adminToolService.module"; import {InputModule} from "../../sharedComponents/input/input.module"; - - -import {MatAutocompleteModule} from '@angular/material/autocomplete'; -import { MatCheckboxModule } from "@angular/material/checkbox"; -import { MatFormFieldModule } from "@angular/material/form-field"; - - -import {MatChipsModule} from '@angular/material/chips'; import {AdminTabsModule} from "../sharedComponents/admin-tabs/admin-tabs.module"; import {PageContentModule} from "../sharedComponents/page-content/page-content.module"; import {ClassesRoutingModule} from "./classes-routing.module"; @@ -24,8 +16,8 @@ import {LoadingModule} from "../../utils/loading/loading.module"; @NgModule({ imports: [ CommonModule, RouterModule, FormsModule, - AlertModalModule, ReactiveFormsModule, AdminToolServiceModule, InputModule, MatAutocompleteModule, MatFormFieldModule, MatChipsModule, - MatCheckboxModule, AdminTabsModule, PageContentModule, ClassesRoutingModule, SearchInputModule, IconsModule, LoadingModule + AlertModalModule, ReactiveFormsModule, AdminToolServiceModule, InputModule, + AdminTabsModule, PageContentModule, ClassesRoutingModule, SearchInputModule, IconsModule, LoadingModule ], declarations: [DivIdsComponent], exports: [DivIdsComponent] diff --git a/dashboard/helpTexts/page-help-content-form.module.ts b/dashboard/helpTexts/page-help-content-form.module.ts index 243a11d7..27e34ae4 100644 --- a/dashboard/helpTexts/page-help-content-form.module.ts +++ b/dashboard/helpTexts/page-help-content-form.module.ts @@ -8,21 +8,20 @@ import {PageContentFormComponent} from './page-help-content-form.component'; import {PageHelpContentFormRoutingModule} from './page-help-content-form-routing.module'; import {AdminToolServiceModule} from '../../services/adminToolService.module'; import {InputModule} from '../../sharedComponents/input/input.module'; -import {MatSlideToggleModule} from '@angular/material/slide-toggle'; import {IconsModule} from '../../utils/icons/icons.module'; import {PageContentModule} from '../sharedComponents/page-content/page-content.module'; import {RouterModule} from '@angular/router'; import {LoadingModule} from '../../utils/loading/loading.module'; +import {MatSlideToggleModule} from "@angular/material/slide-toggle"; @NgModule({ imports: [ CommonModule, FormsModule, RouterModule, SafeHtmlPipeModule, CKEditorModule, - AlertModalModule, ReactiveFormsModule, PageHelpContentFormRoutingModule, AdminToolServiceModule, InputModule, MatSlideToggleModule, IconsModule, PageContentModule, LoadingModule + AlertModalModule, ReactiveFormsModule, PageHelpContentFormRoutingModule, AdminToolServiceModule, InputModule, IconsModule, PageContentModule, LoadingModule, MatSlideToggleModule ], - declarations: [ - PageContentFormComponent - ], - exports: [PageContentFormComponent] + declarations: [PageContentFormComponent], + exports: [PageContentFormComponent] }) -export class PageHelpContentFormModule {} +export class PageHelpContentFormModule { +} diff --git a/landingPages/feedback/feedback.module.ts b/landingPages/feedback/feedback.module.ts index 23ba3680..69f72d67 100644 --- a/landingPages/feedback/feedback.module.ts +++ b/landingPages/feedback/feedback.module.ts @@ -3,7 +3,6 @@ import {CommonModule} from "@angular/common"; import {FeedbackComponent} from "./feedback.component"; import {LandingHeaderModule} from "../landing-utils/landing-header/landing-header.module"; import {ReactiveFormsModule} from "@angular/forms"; -import {MatSelectModule} from "@angular/material/select"; import {AlertModalModule} from "../../utils/modal/alertModal.module"; import {EmailService} from "../../utils/email/email.service"; import {RecaptchaModule} from "ng-recaptcha"; @@ -11,7 +10,7 @@ import {IconsModule} from "../../utils/icons/icons.module"; import {InputModule} from "../../sharedComponents/input/input.module"; @NgModule({ - imports: [CommonModule, LandingHeaderModule, ReactiveFormsModule, MatSelectModule, AlertModalModule, RecaptchaModule, IconsModule, InputModule], + imports: [CommonModule, LandingHeaderModule, ReactiveFormsModule, AlertModalModule, RecaptchaModule, IconsModule, InputModule], declarations: [FeedbackComponent], providers: [EmailService], exports: [FeedbackComponent] diff --git a/landingPages/landing-utils/citeThis/citeThis.component.ts b/landingPages/landing-utils/citeThis/citeThis.component.ts index 0334d387..660a4b0d 100644 --- a/landingPages/landing-utils/citeThis/citeThis.component.ts +++ b/landingPages/landing-utils/citeThis/citeThis.component.ts @@ -1,19 +1,9 @@ -import { - Component, - Inject, - Input, - OnDestroy, - OnInit, - RendererFactory2, - ViewEncapsulation -} from '@angular/core'; +import {Component, Inject, Input, OnDestroy, OnInit, RendererFactory2, ViewEncapsulation} from '@angular/core'; import {Citation, CitationData} from './citation.class'; -import {ResultLandingInfo} from "../../../utils/entities/resultLandingInfo"; import {DOCUMENT} from "@angular/common"; import {EnvProperties} from "../../../utils/properties/env-properties"; import {properties} from "../../../../../environments/environment"; import {PiwikService} from "../../../utils/piwik/piwik.service"; -import {ResultPreview} from "../../../utils/result-preview/result-preview"; declare var Cite: any; // Based on https://citation.js.org/api/tutorial-getting_started.html browser release diff --git a/landingPages/landing-utils/citeThis/citeThis.module.ts b/landingPages/landing-utils/citeThis/citeThis.module.ts index 53583b5f..8de1bdbc 100644 --- a/landingPages/landing-utils/citeThis/citeThis.module.ts +++ b/landingPages/landing-utils/citeThis/citeThis.module.ts @@ -3,13 +3,12 @@ import {CommonModule} from '@angular/common'; import {FormsModule} from '@angular/forms'; import {CiteThisComponent} from './citeThis.component'; -import { MatSelectModule } from "@angular/material/select"; import {InputModule} from "../../../sharedComponents/input/input.module"; @NgModule({ imports: [ - CommonModule, FormsModule, MatSelectModule, InputModule + CommonModule, FormsModule, InputModule ], declarations: [ CiteThisComponent diff --git a/landingPages/organization/organization.module.ts b/landingPages/organization/organization.module.ts index a367d897..9a4a2737 100644 --- a/landingPages/organization/organization.module.ts +++ b/landingPages/organization/organization.module.ts @@ -23,7 +23,6 @@ import {HelperModule} from "../../utils/helper/helper.module"; import {OrganizationsDeletedByInferenceModule} from "./deletedByInference/deletedByInference.module"; import {LandingHeaderModule} from "../landing-utils/landing-header/landing-header.module"; import {FeedbackModule} from "../feedback/feedback.module"; -import {MatSelectModule} from "@angular/material/select"; import {TabsModule} from "../../utils/tabs/tabs.module"; import {SearchTabModule} from "../../utils/tabs/contents/search-tab.module"; import {LoadingModule} from '../../utils/loading/loading.module'; @@ -50,7 +49,6 @@ import {EntityActionsModule} from "../../utils/entity-actions/entity-actions.mod ProjectsServiceModule, Schema2jsonldModule, SEOServiceModule, HelperModule, OrganizationsDeletedByInferenceModule, LandingHeaderModule, FeedbackModule, - MatSelectModule, TabsModule, SearchTabModule, LoadingModule, IconsModule, InputModule, FullScreenModalModule, EGIDataTransferModule, EntityActionsModule ], declarations: [ diff --git a/landingPages/project/project.module.ts b/landingPages/project/project.module.ts index e17a294c..713c9f1f 100644 --- a/landingPages/project/project.module.ts +++ b/landingPages/project/project.module.ts @@ -2,7 +2,6 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {FormsModule} from '@angular/forms'; import {RouterModule} from '@angular/router'; -import {MatSelectModule} from "@angular/material/select"; import {ProjectComponent} from './project.component'; import {ProjectServiceModule} from './projectService.module'; @@ -41,7 +40,7 @@ import {EntityActionsModule} from "../../utils/entity-actions/entity-actions.mod IFrameModule, ReportsServiceModule, SearchResearchResultsServiceModule, ProjectServiceModule, Schema2jsonldModule, SEOServiceModule, HelperModule, - LandingHeaderModule, MatSelectModule, FeedbackModule, AltMetricsModule, + LandingHeaderModule, FeedbackModule, AltMetricsModule, TabsModule, SearchTabModule, LoadingModule, IconsModule, InputModule, FullScreenModalModule, SafeHtmlPipeModule, EGIDataTransferModule, EntityActionsModule ], diff --git a/landingPages/result/resultLanding.module.ts b/landingPages/result/resultLanding.module.ts index 67a0a8fb..3803a598 100644 --- a/landingPages/result/resultLanding.module.ts +++ b/landingPages/result/resultLanding.module.ts @@ -28,8 +28,6 @@ import {FeedbackModule} from "../feedback/feedback.module"; import {TabsModule} from "../../utils/tabs/tabs.module"; import {LoadingModule} from "../../utils/loading/loading.module"; import {OrcidModule} from "../../orcid/orcid.module"; -import {MatFormFieldModule} from "@angular/material/form-field"; -import {MatSelectModule} from "@angular/material/select"; import {IconsModule} from "../../utils/icons/icons.module"; import {IconsService} from "../../utils/icons/icons.service"; import {cite, fire, graph, landmark, link, link_to, quotes, rocket, versions} from "../../utils/icons/icons"; @@ -48,7 +46,7 @@ import {EntityActionsModule} from "../../utils/entity-actions/entity-actions.mod AltMetricsModule, Schema2jsonldModule, SEOServiceModule, DeletedByInferenceModule, ShowAuthorsModule, HelperModule, ResultLandingUtilsModule, AlertModalModule, AnnotationModule, LandingHeaderModule, NoLoadPaging, ResultPreviewModule, FeedbackModule, TabsModule, LoadingModule, - OrcidModule, MatFormFieldModule, MatSelectModule, IconsModule, InputModule, EGIDataTransferModule, RecaptchaModule, + OrcidModule, IconsModule, InputModule, EGIDataTransferModule, RecaptchaModule, SdgFosSuggestModule, FullScreenModalModule, SafeHtmlPipeModule, EntityActionsModule ], declarations: [ diff --git a/notifications/notify-form/notify-form.module.ts b/notifications/notify-form/notify-form.module.ts index ea7dda32..84e4ede3 100644 --- a/notifications/notify-form/notify-form.module.ts +++ b/notifications/notify-form/notify-form.module.ts @@ -1,13 +1,12 @@ import {NgModule} from "@angular/core"; import {NotifyFormComponent} from "./notify-form.component"; import {CommonModule} from "@angular/common"; -import {MatCheckboxModule} from "@angular/material/checkbox"; import {ReactiveFormsModule} from "@angular/forms"; import {InputModule} from "../../sharedComponents/input/input.module"; import {NotificationUserModule} from "../notification-user/notification-user.module"; @NgModule({ - imports: [CommonModule, MatCheckboxModule, ReactiveFormsModule, InputModule, NotificationUserModule], + imports: [CommonModule, ReactiveFormsModule, InputModule, NotificationUserModule], declarations: [NotifyFormComponent], exports: [NotifyFormComponent] }) diff --git a/searchPages/searchUtils/advancedSearchForm.component.html b/searchPages/searchUtils/advancedSearchForm.component.html index f0d88901..846c6bc2 100644 --- a/searchPages/searchUtils/advancedSearchForm.component.html +++ b/searchPages/searchUtils/advancedSearchForm.component.html @@ -33,10 +33,7 @@ [options]="fieldIdsOptions" (valueChange)="fieldIdsChanged(i,selectedField.id)" type="select">

- + >
-
Please check your from date diff --git a/searchPages/searchUtils/advancedSearchForm.module.ts b/searchPages/searchUtils/advancedSearchForm.module.ts index d9cf2b9b..5d5c17a0 100644 --- a/searchPages/searchUtils/advancedSearchForm.module.ts +++ b/searchPages/searchUtils/advancedSearchForm.module.ts @@ -10,7 +10,6 @@ import {DateFilterModule} from './dateFilter.module'; import {SearchFormModule} from './searchForm.module'; import {QuickSelectionsModule} from "./quick-selections.module"; import {EntitiesSelectionModule} from "./entitiesSelection.module"; -import {MatSelectModule} from "@angular/material/select"; import {IconsModule} from "../../utils/icons/icons.module"; import {SearchInputModule} from "../../sharedComponents/search-input/search-input.module"; import {AdvancedSearchInputModule} from "../../sharedComponents/advanced-search-input/advanced-search-input.module"; @@ -22,7 +21,7 @@ import {filters} from "../../utils/icons/icons"; @NgModule({ imports: [ CommonModule, FormsModule, RouterModule, EntitiesAutocompleteModule, StaticAutocompleteModule, DateFilterModule, - SearchFormModule, QuickSelectionsModule, EntitiesSelectionModule, MatSelectModule, IconsModule, SearchInputModule, AdvancedSearchInputModule, InputModule + SearchFormModule, QuickSelectionsModule, EntitiesSelectionModule, IconsModule, SearchInputModule, AdvancedSearchInputModule, InputModule ], declarations: [ AdvancedSearchFormComponent, diff --git a/searchPages/searchUtils/entitiesSelection.module.ts b/searchPages/searchUtils/entitiesSelection.module.ts index 9a281ee4..e10a0e9d 100644 --- a/searchPages/searchUtils/entitiesSelection.module.ts +++ b/searchPages/searchUtils/entitiesSelection.module.ts @@ -3,13 +3,12 @@ import {CommonModule} from '@angular/common'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {RouterModule} from '@angular/router'; import {EntitiesSelectionComponent} from "./entitiesSelection.component"; -import { MatSelectModule } from "@angular/material/select"; import {InputModule} from "../../sharedComponents/input/input.module"; @NgModule({ imports: [ CommonModule, FormsModule, - RouterModule, ReactiveFormsModule, MatSelectModule, InputModule + RouterModule, ReactiveFormsModule, InputModule ], declarations: [ EntitiesSelectionComponent, diff --git a/searchPages/searchUtils/quick-selections.component.ts b/searchPages/searchUtils/quick-selections.component.ts index ee9dd8b4..0931a405 100644 --- a/searchPages/searchUtils/quick-selections.component.ts +++ b/searchPages/searchUtils/quick-selections.component.ts @@ -6,7 +6,6 @@ import {ConfigurationService} from "../../utils/configuration/configuration.serv import {Subscription} from "rxjs"; import {ActivatedRoute, Router} from "@angular/router"; import {properties} from "../../../../environments/environment"; -import {OpenaireEntities} from "../../utils/properties/searchFields"; @Component({ selector: 'quick-selections', diff --git a/searchPages/searchUtils/searchFilter.module.ts b/searchPages/searchUtils/searchFilter.module.ts index 382884c1..6f8bc92b 100644 --- a/searchPages/searchUtils/searchFilter.module.ts +++ b/searchPages/searchUtils/searchFilter.module.ts @@ -1,29 +1,25 @@ -import { NgModule} from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {FormsModule} from '@angular/forms'; import {SearchFilterComponent} from './searchFilter.component'; -import{SearchFilterModalComponent} from './searchFilterModal.component'; +import {SearchFilterModalComponent} from './searchFilterModal.component'; import {ModalModule} from '../../utils/modal/modal.module'; -import { MatSelectModule } from "@angular/material/select"; import {RouterModule} from "@angular/router"; import {InputModule} from '../../sharedComponents/input/input.module'; import {IconsModule} from "../../utils/icons/icons.module"; @NgModule({ imports: [ - CommonModule, FormsModule, ModalModule, MatSelectModule, RouterModule, + CommonModule, FormsModule, ModalModule, RouterModule, InputModule, IconsModule ], declarations: [ SearchFilterComponent, SearchFilterModalComponent -], - - providers:[ ], exports: [ SearchFilterComponent, SearchFilterModalComponent - - ] + ] }) -export class SearchFilterModule { } +export class SearchFilterModule { +} diff --git a/searchPages/searchUtils/searchSorting.module.ts b/searchPages/searchUtils/searchSorting.module.ts index 140dc0fa..51992dfd 100644 --- a/searchPages/searchUtils/searchSorting.module.ts +++ b/searchPages/searchUtils/searchSorting.module.ts @@ -1,16 +1,14 @@ -import { NgModule} from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {RouterModule} from '@angular/router'; -import{SearchSortingComponent} from './searchSorting.component'; -import { MatSelectModule } from "@angular/material/select"; +import {SearchSortingComponent} from './searchSorting.component'; import {InputModule} from '../../sharedComponents/input/input.module'; @NgModule({ imports: [ - CommonModule, FormsModule, RouterModule, MatSelectModule, - InputModule + CommonModule, FormsModule, RouterModule, InputModule ], declarations: [ SearchSortingComponent diff --git a/sharedComponents/search-input/search-input.module.ts b/sharedComponents/search-input/search-input.module.ts index 158b7815..7e5c2de4 100644 --- a/sharedComponents/search-input/search-input.module.ts +++ b/sharedComponents/search-input/search-input.module.ts @@ -1,13 +1,12 @@ import {NgModule} from '@angular/core'; import {SharedModule} from '../../../openaireLibrary/shared/shared.module'; import {SearchInputComponent} from './search-input.component'; -import {MatAutocompleteModule} from '@angular/material/autocomplete'; import {IconsModule} from '../../utils/icons/icons.module'; import {InputModule} from "../input/input.module"; import {ClickModule} from "../../utils/click/click.module"; @NgModule({ - imports: [SharedModule, MatAutocompleteModule, IconsModule, InputModule, ClickModule], + imports: [SharedModule, IconsModule, InputModule, ClickModule], declarations: [SearchInputComponent], exports: [SearchInputComponent] }) diff --git a/utils/entities/entities.module.ts b/utils/entities/entities.module.ts deleted file mode 100644 index 76e06335..00000000 --- a/utils/entities/entities.module.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; - - -//Entities -import {OrganizationInfo} from './organizationInfo'; -import {ProjectInfo} from './projectInfo'; -import {Publication} from './publication'; -import {ResultLandingInfo} from './resultLandingInfo'; -import {SearchResult} from './searchResult'; -import {DataProviderInfo} from './dataProviderInfo'; -import {Metrics} from './metrics'; - -@NgModule({ - imports: [ CommonModule, FormsModule ], - declarations: [ - - ], - exports: [ - ] -}) -export class EntitiesModule { } From 51e39801796e6c49c97cc721c7b1e797a45a8880 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Tue, 1 Aug 2023 14:29:27 +0300 Subject: [PATCH 03/48] Update angular to version 16 --- .../communityGuard/connectAdminLoginGuard.guard.ts | 11 ++--------- connect/communityGuard/isCommunity.guard.ts | 12 +++--------- error/isRouteEnabled.guard.ts | 4 ++-- http-interceptor.service.ts | 2 +- login/adminLoginGuard.guard.ts | 11 ++--------- login/freeGuard.guard.ts | 10 ++-------- login/loginGuard.guard.ts | 13 ++----------- utils/can-exit.guard.ts | 4 ++-- utils/piwik/previousRouteRecorder.guard.ts | 4 ++-- 9 files changed, 18 insertions(+), 53 deletions(-) diff --git a/connect/communityGuard/connectAdminLoginGuard.guard.ts b/connect/communityGuard/connectAdminLoginGuard.guard.ts index 2b55ede5..86359230 100644 --- a/connect/communityGuard/connectAdminLoginGuard.guard.ts +++ b/connect/communityGuard/connectAdminLoginGuard.guard.ts @@ -1,20 +1,13 @@ import {filter, map, mergeMap, take} from 'rxjs/operators'; import {Injectable} from '@angular/core'; -import { - ActivatedRouteSnapshot, - CanActivate, - CanActivateChild, - Router, - RouterStateSnapshot, - UrlTree -} from '@angular/router'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import {Observable, of} from 'rxjs'; import {Session} from '../../login/utils/helper.class'; import {LoginErrorCodes} from '../../login/utils/guardHelper.class'; import {UserManagementService} from "../../services/user-management.service"; @Injectable() -export class ConnectAdminLoginGuard implements CanActivate, CanActivateChild { +export class ConnectAdminLoginGuard { constructor(private router: Router, private userManagementService: UserManagementService) { diff --git a/connect/communityGuard/isCommunity.guard.ts b/connect/communityGuard/isCommunity.guard.ts index 41b737c2..9dd04798 100644 --- a/connect/communityGuard/isCommunity.guard.ts +++ b/connect/communityGuard/isCommunity.guard.ts @@ -1,11 +1,5 @@ -import { Injectable } from '@angular/core'; -import { - Router, - CanActivate, - ActivatedRouteSnapshot, - RouterStateSnapshot, - CanLoad, Route, UrlSegment, CanActivateChild, UrlTree -} from '@angular/router'; +import {Injectable} from '@angular/core'; +import {ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; import {Observable} from 'rxjs'; import {ConnectHelper} from '../connectHelper'; @@ -14,7 +8,7 @@ import {CommunityService} from "../community/community.service"; import {map} from "rxjs/operators"; @Injectable() -export class IsCommunity implements CanActivate, CanActivateChild { +export class IsCommunity { constructor(private router: Router, private communityService: CommunityService) { diff --git a/error/isRouteEnabled.guard.ts b/error/isRouteEnabled.guard.ts index 2c8d704c..2dca90fb 100644 --- a/error/isRouteEnabled.guard.ts +++ b/error/isRouteEnabled.guard.ts @@ -1,13 +1,13 @@ import {Observable} from 'rxjs'; import {take, tap} from 'rxjs/operators'; import {Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivate, Data, Router, RouterStateSnapshot, UrlTree} from '@angular/router'; +import { ActivatedRouteSnapshot, Data, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import {ConfigurationService} from '../utils/configuration/configuration.service'; import {ConnectHelper} from '../connect/connectHelper'; import {properties} from "../../../environments/environment"; @Injectable() -export class IsRouteEnabled implements CanActivate { +export class IsRouteEnabled { constructor(private router: Router, private config: ConfigurationService) { diff --git a/http-interceptor.service.ts b/http-interceptor.service.ts index 7b0a4104..03b66510 100644 --- a/http-interceptor.service.ts +++ b/http-interceptor.service.ts @@ -2,7 +2,7 @@ import {Injectable, Inject, PLATFORM_ID, Optional} from '@angular/core'; import {HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpHeaders} from '@angular/common/http'; import {Observable, of} from 'rxjs'; import {tap} from 'rxjs/operators'; -import {TransferState, makeStateKey, StateKey} from '@angular/platform-browser'; +import {TransferState, makeStateKey, StateKey} from '@angular/core'; import {isPlatformServer} from '@angular/common'; import {properties} from "../../environments/environment"; import {REQUEST} from "./utils/tokens"; diff --git a/login/adminLoginGuard.guard.ts b/login/adminLoginGuard.guard.ts index 53b2ec94..ddb800ab 100644 --- a/login/adminLoginGuard.guard.ts +++ b/login/adminLoginGuard.guard.ts @@ -1,12 +1,5 @@ import {Injectable} from '@angular/core'; -import { - ActivatedRouteSnapshot, - CanActivate, - CanActivateChild, Data, - Router, - RouterStateSnapshot, - UrlTree -} from '@angular/router'; +import { ActivatedRouteSnapshot, Data, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import {Observable} from 'rxjs'; import {Session} from './utils/helper.class'; import {LoginErrorCodes} from './utils/guardHelper.class'; @@ -16,7 +9,7 @@ import {map, tap} from "rxjs/operators"; @Injectable({ providedIn: 'root' }) -export class AdminLoginGuard implements CanActivate, CanActivateChild { +export class AdminLoginGuard { constructor(private router: Router, private userManagementService: UserManagementService) { diff --git a/login/freeGuard.guard.ts b/login/freeGuard.guard.ts index c2141a70..13d5941e 100644 --- a/login/freeGuard.guard.ts +++ b/login/freeGuard.guard.ts @@ -1,16 +1,10 @@ import { Injectable } from '@angular/core'; -import { - Router, - CanActivate, - ActivatedRouteSnapshot, - RouterStateSnapshot, - UrlTree -} from '@angular/router'; +import { Router, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; import {Observable} from 'rxjs'; import {LoginErrorCodes} from './utils/guardHelper.class'; @Injectable() -export class FreeGuard implements CanActivate { +export class FreeGuard { constructor(private router: Router) { } diff --git a/login/loginGuard.guard.ts b/login/loginGuard.guard.ts index 0c9f737c..297f52df 100644 --- a/login/loginGuard.guard.ts +++ b/login/loginGuard.guard.ts @@ -1,14 +1,5 @@ import {Injectable} from '@angular/core'; -import { - ActivatedRouteSnapshot, - CanActivate, - CanActivateChild, - CanLoad, - Route, - Router, - RouterStateSnapshot, - UrlTree -} from '@angular/router'; +import { ActivatedRouteSnapshot, Route, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import {Observable} from 'rxjs'; import {LoginErrorCodes} from './utils/guardHelper.class'; import {map, tap} from "rxjs/operators"; @@ -17,7 +8,7 @@ import {UserManagementService} from "../services/user-management.service"; @Injectable({ providedIn: 'root' }) -export class LoginGuard implements CanActivate, CanLoad, CanActivateChild { +export class LoginGuard { constructor(private router: Router, private userManagementService: UserManagementService) { diff --git a/utils/can-exit.guard.ts b/utils/can-exit.guard.ts index 61947899..692a970f 100644 --- a/utils/can-exit.guard.ts +++ b/utils/can-exit.guard.ts @@ -1,6 +1,6 @@ import {Observable} from 'rxjs'; import {Injectable} from '@angular/core'; -import {CanDeactivate} from '@angular/router'; + export interface IDeactivateComponent { canExit: () => Observable | Promise | boolean; @@ -9,7 +9,7 @@ export interface IDeactivateComponent { @Injectable({ providedIn: 'root' }) -export class CanExitGuard implements CanDeactivate { +export class CanExitGuard { constructor() { } diff --git a/utils/piwik/previousRouteRecorder.guard.ts b/utils/piwik/previousRouteRecorder.guard.ts index e4961b7a..4d050618 100644 --- a/utils/piwik/previousRouteRecorder.guard.ts +++ b/utils/piwik/previousRouteRecorder.guard.ts @@ -1,13 +1,13 @@ import { Injectable } from '@angular/core'; -import { CanDeactivate, Router} from '@angular/router'; +import { Router } from '@angular/router'; import {Observable} from 'rxjs'; import {properties} from "../../../../environments/environment"; @Injectable({ providedIn: 'root' }) // do not forget to register this class as a provider -export class PreviousRouteRecorder implements CanDeactivate { +export class PreviousRouteRecorder { constructor(private router: Router) { } canDeactivate(component: any): Observable | boolean { From e7429881ca7788fb218cc0063a0bc1e3bdb626f4 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Wed, 2 Aug 2023 14:08:04 +0300 Subject: [PATCH 04/48] Delete a > in advanced form --- searchPages/searchUtils/advancedSearchForm.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searchPages/searchUtils/advancedSearchForm.component.html b/searchPages/searchUtils/advancedSearchForm.component.html index 846c6bc2..8019c91c 100644 --- a/searchPages/searchUtils/advancedSearchForm.component.html +++ b/searchPages/searchUtils/advancedSearchForm.component.html @@ -33,7 +33,7 @@ [options]="fieldIdsOptions" (valueChange)="fieldIdsChanged(i,selectedField.id)" type="select">
- > +
Date: Thu, 3 Aug 2023 12:45:39 +0300 Subject: [PATCH 05/48] show status also for direct index records job on reload keep job ids and continue show progres until they finish change get auth options method --- claims/claim-utils/service/claims.service.ts | 53 +----- claims/claimsByToken/claimsByToken.service.ts | 18 +-- .../insertClaim/insertClaim.component.ts | 153 ++++++++++++------ claims/linking/linkingGeneric.component.ts | 5 + .../selected/metadataPreview.component.html | 8 + services/servicesUtils/customOptions.class.ts | 9 +- 6 files changed, 122 insertions(+), 124 deletions(-) diff --git a/claims/claim-utils/service/claims.service.ts b/claims/claim-utils/service/claims.service.ts index 26a3d4d3..d7d46218 100644 --- a/claims/claim-utils/service/claims.service.ts +++ b/claims/claim-utils/service/claims.service.ts @@ -10,7 +10,7 @@ export class ClaimsService { } private getClaimRequest(size : number, page : number, url :string, fromCache:boolean):any { - return this.http.get(url, CustomOptions.getAuthOptions()); + return this.http.get(url, CustomOptions.getAuthOptionsWithBody()); } getClaims( size : number, page : number, keyword:string, sortby: string, descending: boolean, types: string, apiUrl:string):any { let url = apiUrl +"claims"+"?offset="+(size*(page-1) + "&limit="+size)+"&keyword="+keyword+"&sortby="+sortby+"&descending="+descending+(types.length>0?"&"+types:types); @@ -44,10 +44,7 @@ export class ClaimsService { deleteClaimById(claimId:string , apiUrl:string):any{ //console.warn('Trying to delete claim with id : '+claimId); let url = apiUrl +"claims/"+claimId; - // let headers = new Headers({ 'Content-Type': 'application/json' }); - // let options = new RequestOptions({ headers: headers }); return this.http.delete( url, CustomOptions.getAuthOptionsWithBody())//.map(request => request.json()) - // .do(request => console.info("After delete" )) .pipe(catchError(this.handleError)); } @@ -61,10 +58,7 @@ export class ClaimsService { } url= apiUrl +"claims/bulk?"+url; - // let headers = new Headers({ 'Content-Type': 'application/json' }); - // let options = new RequestOptions({ headers: headers }); - return this.http.delete( url, CustomOptions.getAuthOptions())//.map(request => request.json()) - // .do(request => console.info("After delete" )) + return this.http.delete( url, CustomOptions.getAuthOptionsWithBody())//.map(request => request.json()) .pipe(catchError(this.handleError)); } @@ -72,37 +66,16 @@ export class ClaimsService { // console.warn('Trying toinsert claims : '+claims); let url = apiUrl +"claims/bulk"; let body = JSON.stringify( claims ); - //console.warn('Json body: : '+body); - // let headers = new Headers({ 'Content-Type': 'application/json' }); - // let options = new RequestOptions({ headers: headers }); return this.http.post(url, body, CustomOptions.getAuthOptionsWithBody()) - //.map(res => res.json()) - //.do(request => console.info("Insert Response:"+request.status) ) .pipe(catchError(this.handleError)); } - insertClaim(claim, apiUrl:string):any{ - //console.warn('Trying toinsert claim : '+claim); - let url = apiUrl +"claims"; - let body = JSON.stringify( claim ); - // let headers = new Headers({ 'Content-Type': 'application/json' }); - // let options = new RequestOptions({ headers: headers }); - return this.http.post(url, body, CustomOptions.getAuthOptionsWithBody()) - //.map(res => res.json()) - //.do(request => console.info("Insert Response:"+request.status) ) - .pipe(catchError(this.handleError)); - } insertDirectRecords(records, apiUrl:string):any{ //console.warn('Trying to feedrecords : '+records); let url = apiUrl +"feed/bulk"; let body = JSON.stringify( records ); - //console.warn('Json body: : '+body); - // let headers = new Headers({ 'Content-Type': 'application/json' }); - // let options = new RequestOptions({ headers: headers }); return this.http.post(url, body, CustomOptions.getAuthOptionsWithBody()) - //.map(res => res.json()) - //.do(request => console.info("Insert Response:"+request) ) .pipe(catchError(this.handleError)); } @@ -110,9 +83,7 @@ export class ClaimsService { let url = apiUrl +"jobStatus/" + jobId; - return this.http.get(url,CustomOptions.getAuthOptions()) - //.map(res => res.json()) - //.do(request => console.info("Insert Response:"+request) ) + return this.http.get(url,CustomOptions.getAuthOptionsWithBody()) .pipe(catchError(this.handleError)); } @@ -123,22 +94,4 @@ export class ClaimsService { return observableThrowError(error || 'Server error'); } - // getClaim(id:string, apiUrl:string):any { - // let url = apiUrl+"claims/"+id; - // return new Promise((resolve, reject) => { - // this.http.get(url) - // //.map(res => res.json()) - // .subscribe( - // data => { - // resolve(data['data']); - // }, - // err => { - // reject(err); - // } - // ) - // ; - // }); - // } - - } diff --git a/claims/claimsByToken/claimsByToken.service.ts b/claims/claimsByToken/claimsByToken.service.ts index 4414065e..21b882da 100644 --- a/claims/claimsByToken/claimsByToken.service.ts +++ b/claims/claimsByToken/claimsByToken.service.ts @@ -18,26 +18,12 @@ export class ClaimsByTokenService { let key = url; - return this.http.get(url, CustomOptions.getAuthOptions()); - //.map(res => res.text()) - //.map(request => request.json()); + return this.http.get(url, CustomOptions.getAuthOptionsWithBody()); + } -/* - getClaims(email: string, token: string, user_token: string):any { - let url = OpenaireProperties.getClaimsAPIURL(); // What else? - let body = JSON.stringify( {"email": email, "token": token} ); - console.warn('Json body: : '+body); - let headers = new Headers({ 'Content-Type': 'application/json' }); - let options = new RequestOptions({ headers: headers }); - return this.http.post(url, body, options) - .map(res => res.json()) - .do(request => console.info("Insert Response:"+request.status) ) - .catch(this.handleError); - } -*/ updateClaimsCuration( selectedRight: Set, selectedWrong: Set, apiURL:string) { let url = apiURL + "curate/bulk"; diff --git a/claims/linking/insertClaim/insertClaim.component.ts b/claims/linking/insertClaim/insertClaim.component.ts index 2d1a276f..068f033c 100644 --- a/claims/linking/insertClaim/insertClaim.component.ts +++ b/claims/linking/insertClaim/insertClaim.component.ts @@ -37,8 +37,9 @@ import {map} from "rxjs/operators";
Initiating process....
-{{claimsJob.insertedIds.length}} out of {{claims2Insert}} links created.
-
{{feedRecordsJob.length}} records added in the index...
+ {{claimsJob.insertedIds.length}} out of {{claims2Insert}} links created.
+
+ {{feedRecordsJob.insertedIds.length}} out of {{records2Insert}} records added in the index...
Please don't close the window, process is ongoing...
@@ -72,6 +73,32 @@ export class ClaimInsertComponent { this.subscriptions.push(this.route.queryParams.subscribe(params => { this.params = params; })); + if(localStorage.getItem(this.localStoragePrefix + "claimsJob")){ + this.claimsJob = JSON.parse(localStorage.getItem(this.localStoragePrefix + "claimsJob")); + this.feedRecordsJob = JSON.parse(localStorage.getItem(this.localStoragePrefix + "feedRecordsJob")); + if(this.claimsJob.status != "COMPLETE"){ + this.claiming = true; + let loadingTimerSubscription = timer(0, 1000).pipe( + map(() => { + if(this.loading) { + this.loading.open(); + loadingTimerSubscription.unsubscribe(); + } + }) + ).subscribe(); + this.subscriptions.push(loadingTimerSubscription); + + let timerSubscription = timer(0, 10000).pipe( + map(() => { + this.getStatus(); // load data contains the http request + }) + ).subscribe(); + this.subscriptions.push(timerSubscription); + }else{ + this.claimsJob = null; + } + } + } params = {}; @@ -99,14 +126,12 @@ export class ClaimInsertComponent { public claimsJob; public feedRecordsJob; public claims2Insert; + public records2Insert public insert() { this.confirmOpen(); } saveAndNavigate(){ - localStorage.setItem(this.localStoragePrefix + "results", JSON.stringify(this.results)); - if (this.sources != null) { - localStorage.setItem(this.localStoragePrefix + "sources", JSON.stringify(this.sources)); - } + this.saveLocalStorage(); this._router.navigate(['/user-info'], { queryParams: { "errorCode": LoginErrorCodes.NOT_VALID, @@ -191,9 +216,12 @@ export class ClaimInsertComponent { //first call direct index service - when call is done (success or error) call isertBulkClaims method to insert claims in DB // console.log("directclaims"); // console.log(directclaims); - if (directclaims.length > 0 && this.properties.environment != "development"){ + if (directclaims.length > 0/* && this.properties.environment != "development"*/){ this.subscriptions.push(this.claimService.insertDirectRecords(directclaims, this.properties.claimsAPIURL).subscribe( data => { + this.feedRecordsJob = data.data; + this.records2Insert = directclaims.length; + console.log(data); // this.insertedRecords = data.insertedIds; // // this.errorInRecords = data.errorInClaims; @@ -204,12 +232,12 @@ export class ClaimInsertComponent { if (err.code && err.code == 403) { this.saveAndNavigate(); } - if (err.insertedIds && err.insertedIds.length > 0) { + /* if (err.insertedIds && err.insertedIds.length > 0) { this.insertedRecords = err.insertedIds; } if (err.errorInClaims && err.errorInClaims.length > 0) { this.errorInRecords = err.errorInClaims; - } + }*/ this.isertBulkClaims(claims); ClaimInsertComponent.handleError("Error inserting direct records: " + JSON.stringify(directclaims), err); @@ -223,35 +251,12 @@ export class ClaimInsertComponent { } private isertBulkClaims(claims: ClaimRecord2Insert[]) { - console.log("claims"); - console.log(claims); this.errors.splice(0, this.errors.length); this.subscriptions.push(this.claimService.insertBulkClaims(claims, this.properties.claimsAPIURL).subscribe( data => { - /*this.insertedClaims = data.insertedIds; - this.errorInClaims = data.errorInClaims; - //TODO remove - testing having errors in claims - // this.insertedClaims.pop(); - // this.insertedClaims.pop(); - // this.errorInClaims.push(claims[1]); - // this.insertedClaims.splice(0,this.insertedClaims.length); - // this.errorInClaims = claims; - //remove till here - - if (claims.length != this.insertedClaims.length) { - let error: ClaimsErrorMessage = new ClaimsErrorMessage(); - error.type = "claimServiceFail2Insert"; - error.inserted = this.insertedClaims.length; - error.failed = this.errorInClaims.length; - this.createErrorMessagesPerEntity((this.insertedClaims.length == 0)); - this.errors.push(error); - } - this.afterclaimsInsertion(); - - */ - console.log(data) this.claims2Insert = claims.length; this.claimsJob = data.data; + this.saveLocalStorage(); let timerSubscription = timer(0, 10000).pipe( map(() => { this.getStatus(); // load data contains the http request @@ -360,13 +365,16 @@ export class ClaimInsertComponent { } private afterclaimsInsertion() { - - this.loading.close(); this.claiming = false; + this.loading.close(); + if (this.errorInClaims.length == 0 && this.insertedClaims.length > 0) { localStorage.removeItem(this.localStoragePrefix + "sources"); localStorage.removeItem(this.localStoragePrefix + "results"); + localStorage.removeItem(this.localStoragePrefix + "claimsJob"); + localStorage.removeItem(this.localStoragePrefix + "feedRecordsJob"); + this._router.navigate(['/myclaims'], {queryParams: this.params}); } } @@ -547,29 +555,72 @@ export class ClaimInsertComponent { } getStatus(){ - - console.log("TODO status fetch") - this.subscriptions.push(this.claimService.getStatus(this.claimsJob.id, this.properties.claimsAPIURL).subscribe(data => { - console.log(data); - this.claimsJob = data.data; - if(this.claimsJob.status == "COMPLETE" || data.data.status == "ERROR"){ - this.insertedClaims = this.claimsJob.insertedIds; - this.errorInClaims = this.claimsJob.errorInClaims; - - if (this.claims2Insert != this.insertedClaims.length) { + if(this.feedRecordsJob && ! (this.feedRecordsJob.status == "COMPLETE" || this.feedRecordsJob.status == "ERROR") ) { + this.subscriptions.push(this.claimService.getStatus(this.feedRecordsJob.id, this.properties.claimsAPIURL).subscribe(data => { + console.log("feed", data); + this.feedRecordsJob = data.data; + if (this.feedRecordsJob.status == "COMPLETE" || data.data.status == "ERROR") { + this.insertedRecords = this.feedRecordsJob.insertedIds; + this.errorInRecords = this.feedRecordsJob.errorInClaims; + } + }, err => { let error: ClaimsErrorMessage = new ClaimsErrorMessage(); - error.type = "claimServiceFail2Insert"; - error.inserted = this.insertedClaims.length; - error.failed = this.errorInClaims.length; + error.type = "jobError"; this.createErrorMessagesPerEntity((this.insertedClaims.length == 0)); this.errors.push(error); + this.afterclaimsInsertion(); + this.feedRecordsJob = null; + localStorage.removeItem(this.localStoragePrefix + "feedRecordsJob"); } - this.afterclaimsInsertion(); + + )); + } + if(this.claimsJob) { + this.subscriptions.push(this.claimService.getStatus(this.claimsJob.id, this.properties.claimsAPIURL).subscribe(data => { + console.log("claim", data); + this.claimsJob = data.data; + if (this.claimsJob.status == "COMPLETE" || data.data.status == "ERROR" && (!(this.feedRecordsJob.status == "COMPLETE" || data.data.status == "ERROR")) || !this.feedRecordsJob) { + this.insertedClaims = this.claimsJob.insertedIds; + this.errorInClaims = this.claimsJob.errorInClaims; + + if (this.claims2Insert != this.insertedClaims.length) { + let error: ClaimsErrorMessage = new ClaimsErrorMessage(); + error.type = "claimServiceFail2Insert"; + error.inserted = this.insertedClaims.length; + error.failed = this.errorInClaims.length; + this.createErrorMessagesPerEntity((this.insertedClaims.length == 0)); + this.errors.push(error); + } + this.afterclaimsInsertion(); - } + } + }, err => { + let error: ClaimsErrorMessage = new ClaimsErrorMessage(); + error.type = "jobError"; + this.createErrorMessagesPerEntity((this.insertedClaims.length == 0)); + this.errors.push(error); + this.afterclaimsInsertion(); + this.claimsJob = null; + localStorage.removeItem(this.localStoragePrefix + "claimsJob"); - })); + } + )); + } + } + saveLocalStorage(){ + if (this.results != null) { + localStorage.setItem(this.localStoragePrefix + "results", JSON.stringify(this.results)); + } + if (this.sources != null) { + localStorage.setItem(this.localStoragePrefix + "sources", JSON.stringify(this.sources)); + } + if (this.claimsJob != null) { + localStorage.setItem(this.localStoragePrefix + "claimsJob", JSON.stringify(this.claimsJob)); + } + if (this.feedRecordsJob != null) { + localStorage.setItem(this.localStoragePrefix + "feedRecordsJob", JSON.stringify(this.feedRecordsJob)); + } } } diff --git a/claims/linking/linkingGeneric.component.ts b/claims/linking/linkingGeneric.component.ts index 524f3566..31efc1b5 100644 --- a/claims/linking/linkingGeneric.component.ts +++ b/claims/linking/linkingGeneric.component.ts @@ -94,6 +94,11 @@ export class LinkingGenericComponent { if(localStorage.getItem(this.localStoragePrefix + "sources")){ this.sources = JSON.parse(localStorage.getItem(this.localStoragePrefix + "sources")); } + if(localStorage.getItem(this.localStoragePrefix + "claimsJob")){ + let job = JSON.parse(localStorage.getItem(this.localStoragePrefix + "claimsJob")); + if(job.status != "COMPLETE"){ + this.showOptions.show = 'claim'; } + } } } diff --git a/claims/linking/selected/metadataPreview.component.html b/claims/linking/selected/metadataPreview.component.html index 2a8f38c3..a0723118 100644 --- a/claims/linking/selected/metadataPreview.component.html +++ b/claims/linking/selected/metadataPreview.component.html @@ -250,6 +250,14 @@ None of the links saved. +
+ +
+ The saving status of your links can't be fetched. Please check your links or start over. +
+
Manage your links here +
+
Date: Thu, 3 Aug 2023 13:02:59 +0300 Subject: [PATCH 06/48] replace session.isLoggedIn() method --- .../claimContextSearchForm.component.ts | 38 +------------------ claims/linking/linkingGeneric.component.ts | 27 ++++++++++++- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/claims/claim-utils/claimContextSearchForm.component.ts b/claims/claim-utils/claimContextSearchForm.component.ts index 95289d97..463c9d93 100644 --- a/claims/claim-utils/claimContextSearchForm.component.ts +++ b/claims/claim-utils/claimContextSearchForm.component.ts @@ -2,12 +2,9 @@ import {Component, Input, ViewChild} from '@angular/core'; import {Router} from '@angular/router'; import {ContextsService} from './service/contexts.service'; import {ClaimEntity, ShowOptions} from './claimHelper.class'; -import {Session} from '../../login/utils/helper.class'; -import {LoginErrorCodes} from '../../login/utils/guardHelper.class'; import {EnvProperties} from '../../utils/properties/env-properties'; import {Subscriber} from "rxjs"; import {OpenaireEntities} from "../../utils/properties/searchFields"; -import {CommunityService} from "../../connect/community/community.service"; import {CommunitiesService} from "../../connect/communities/communities.service"; declare var UIkit: any; @@ -167,10 +164,6 @@ export class ClaimContextSearchFormComponent { } getCommunities() { - if (!Session.isLoggedIn()) { - this.saveStateAndRedirectLogin(); - - } else { this.loading = true; this.subscriptions.push(this._contextService.getPublicCommunitiesByState().subscribe( data => { @@ -203,18 +196,14 @@ export class ClaimContextSearchFormComponent { this.error = true; } )); - } + } getCategories() { this.loading = true; // this.categories=[]; if (this.selectedCommunityId != '0') { - if (!Session.isLoggedIn()) { - this.saveStateAndRedirectLogin(); - - } else { if (this.categories[this.selectedCommunityId]) { this.loading = false; if(this.categories[this.selectedCommunityId].length > 0){ @@ -242,7 +231,7 @@ export class ClaimContextSearchFormComponent { } )); } - } + } displaySubcategory(id) { @@ -255,9 +244,6 @@ export class ClaimContextSearchFormComponent { } browseConcepts(categoryId) { - if (!Session.isLoggedIn()) { - this.saveStateAndRedirectLogin(); - } else { if (this.conceptsClass[categoryId] != null) { this.conceptsClassDisplay[categoryId] = !this.conceptsClassDisplay[categoryId]; return; @@ -285,8 +271,6 @@ export class ClaimContextSearchFormComponent { this.conceptsCategoryLoading[categoryId] = false; } )); - } - } browseSubConcepts(categoryId, conceptId) { @@ -308,24 +292,6 @@ export class ClaimContextSearchFormComponent { } - - saveStateAndRedirectLogin() { - - if (this.results != null) { - localStorage.setItem(this.localStoragePrefix + "results", JSON.stringify(this.results)); - } - if (this.sources != null) { - localStorage.setItem(this.localStoragePrefix + "sources", JSON.stringify(this.sources)); - } - - this.router.navigate(['/user-info'], { - queryParams: { - "errorCode": LoginErrorCodes.NOT_VALID, - "redirectUrl": this.router.url - } - }); - } - private static handleError(message: string, error) { console.error("Claim context search form (component): " + message, error); } diff --git a/claims/linking/linkingGeneric.component.ts b/claims/linking/linkingGeneric.component.ts index 31efc1b5..e2149bb0 100644 --- a/claims/linking/linkingGeneric.component.ts +++ b/claims/linking/linkingGeneric.component.ts @@ -16,6 +16,8 @@ import {OpenaireEntities} from "../../utils/properties/searchFields"; import {StringUtils} from "../../utils/string-utils.class"; import {RouterHelper} from "../../utils/routerHelper.class"; import { Location } from '@angular/common'; +import {LoginErrorCodes} from "../../login/utils/guardHelper.class"; +import {UserManagementService} from "../../services/user-management.service"; @Component({ selector: 'linking-generic', @@ -51,12 +53,17 @@ export class LinkingGenericComponent { constructor (private _router: Router, private route: ActivatedRoute, private entitySearch:EntitiesSearchService, private _meta: Meta, private _title: Title, private _piwikService:PiwikService, private seoService: SEOService, private helper: HelperService, private cdr: ChangeDetectorRef, - private location: Location) { + private location: Location, private userManagementService: UserManagementService) { } subscriptions = []; ngOnInit() { - if(this.breadcrumbs.length === 0) { + this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + if (!user) { + this.saveStateAndRedirectLogin(); + } + })); + if(this.breadcrumbs.length === 0) { this.breadcrumbs.push({name: 'home', route: '/'}); this.breadcrumbs.push({name: "Link", route: null}); } @@ -211,4 +218,20 @@ export class LinkingGenericComponent { this.location.back(); } } + saveStateAndRedirectLogin() { + + if (this.results != null) { + localStorage.setItem(this.localStoragePrefix + "results", JSON.stringify(this.results)); + } + if (this.sources != null) { + localStorage.setItem(this.localStoragePrefix + "sources", JSON.stringify(this.sources)); + } + + this._router.navigate(['/user-info'], { + queryParams: { + "errorCode": LoginErrorCodes.NOT_VALID, + "redirectUrl": this._router.url + } + }); + } } From 758f2f10ffe6623fb55606ca2e9c29abf89b9b4f Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Fri, 4 Aug 2023 14:45:41 +0300 Subject: [PATCH 07/48] Import transferState from angular/core --- cache-interceptor.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cache-interceptor.service.ts b/cache-interceptor.service.ts index a3a67d32..14e0f648 100644 --- a/cache-interceptor.service.ts +++ b/cache-interceptor.service.ts @@ -1,6 +1,5 @@ -import { Injectable, Inject, PLATFORM_ID } from '@angular/core'; +import {Injectable, Inject, PLATFORM_ID, TransferState} from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http'; -import { TransferState } from '@angular/platform-browser'; import { properties } from "../../environments/environment"; @Injectable({ From d9a828363002336b3d333174560796706c7c1bce Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Tue, 22 Aug 2023 17:08:46 +0300 Subject: [PATCH 08/48] Update to Uikit 3.16.24 --- claims/linking/linkingGeneric.component.html | 4 +- .../page-content/page-content.component.ts | 39 +------------------ .../sidebar/sideBar.component.html | 3 +- fos/fos.component.html | 2 +- .../dataProvider/dataProvider.component.html | 6 +-- .../organization/organization.component.html | 6 +-- landingPages/project/project.component.html | 6 +-- .../result/resultLanding.component.html | 8 ++-- login/userMini.component.ts | 7 ++-- monitor/methodology/terminology.component.ts | 4 +- sharedComponents/input/input.component.ts | 4 +- sharedComponents/navigationBar.component.html | 28 ++++++------- .../search-input/search-input.component.ts | 1 + .../entitiesAutoComplete.component.ts | 2 +- .../staticAutoComplete.component.ts | 2 +- utils/theme/theme.component.html | 8 ++-- 16 files changed, 45 insertions(+), 85 deletions(-) diff --git a/claims/linking/linkingGeneric.component.html b/claims/linking/linkingGeneric.component.html index 9d0b1379..7f394dd2 100644 --- a/claims/linking/linkingGeneric.component.html +++ b/claims/linking/linkingGeneric.component.html @@ -45,7 +45,7 @@
-
+
-
+
diff --git a/dashboard/sharedComponents/page-content/page-content.component.ts b/dashboard/sharedComponents/page-content/page-content.component.ts index 54261153..654f82e6 100644 --- a/dashboard/sharedComponents/page-content/page-content.component.ts +++ b/dashboard/sharedComponents/page-content/page-content.component.ts @@ -80,9 +80,6 @@ export class PageContentComponent implements OnInit, AfterViewInit, OnDestroy { } ngOnInit() { - if(this.isBrowser) { - this.stickyBugWorkaround(); - } this.subscriptions.push(this.layoutService.isMobile.subscribe(isMobile => { this.isMobile = isMobile; if(this.isBrowser) { @@ -132,41 +129,7 @@ export class PageContentComponent implements OnInit, AfterViewInit, OnDestroy { initFooter() { let footer_offset = this.calcStickyFooterOffset(this.sticky_footer.nativeElement); - this.sticky.footer = UIkit.sticky(this.sticky_footer.nativeElement, {bottom: true, offset: footer_offset}); - } - - /** - * Workaround for sticky not update bug when sidebar is toggled. - * TODO when UIKit will be updated => remove - * - * */ - stickyBugWorkaround() { - let sidebarOffset = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--dashboard-sidebar-width')) - - Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--dashboard-sidebar-mini-width')); - let transitionDelay = Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--dashboard-transition-delay')) * 1000; - this.subscriptions.push(this.layoutService.isOpen.subscribe(isOpen => { - if (this.sticky.header) { - if (isOpen) { - this.sticky.header.$el.style.width = Number.parseInt(this.sticky.header.$el.style.width) - sidebarOffset + 'px'; - } else { - this.sticky.header.$el.style.width = Number.parseInt(this.sticky.header.$el.style.width) + sidebarOffset + 'px'; - } - setTimeout(() => { - this.sticky.header.$emit(); - }, transitionDelay); - } - if (this.sticky.footer) { - if (isOpen) { - this.sticky.footer.$el.style.width = Number.parseInt(this.sticky.footer.$el.style.width) - sidebarOffset + 'px'; - } else { - this.sticky.footer.$el.style.width = Number.parseInt(this.sticky.footer.$el.style.width) + sidebarOffset + 'px'; - } - setTimeout(() => { - this.sticky.footer.$emit(); - }, transitionDelay); - } - this.cdr.detectChanges(); - })); + this.sticky.footer = UIkit.sticky(this.sticky_footer.nativeElement, {end: true, offset: footer_offset}); } /** diff --git a/dashboard/sharedComponents/sidebar/sideBar.component.html b/dashboard/sharedComponents/sidebar/sideBar.component.html index 9bba0b18..35e20996 100644 --- a/dashboard/sharedComponents/sidebar/sideBar.component.html +++ b/dashboard/sharedComponents/sidebar/sideBar.component.html @@ -29,7 +29,7 @@
{{item.title}} +
  • -
    +
    • diff --git a/landingPages/dataProvider/dataProvider.component.html b/landingPages/dataProvider/dataProvider.component.html index 376cbb87..4ec30f37 100644 --- a/landingPages/dataProvider/dataProvider.component.html +++ b/landingPages/dataProvider/dataProvider.component.html @@ -17,7 +17,7 @@
      + uk-sticky="end: true; media: @m" [attr.offset]="offset">
      - {{menu.title}} - {{menu.title}} - {{menu.title}} + [fragment]="menu.fragment">{{menu.title}} + + {{menu.title}}
      • {{menu.title}} -
        -
        -
        -
      • diff --git a/sharedComponents/search-input/search-input.component.ts b/sharedComponents/search-input/search-input.component.ts index ea7e87df..0b2a3e02 100644 --- a/sharedComponents/search-input/search-input.component.ts +++ b/sharedComponents/search-input/search-input.component.ts @@ -90,6 +90,7 @@ export class SearchInputComponent implements OnInit, AfterViewInit { ngAfterViewInit() { if(typeof document !== 'undefined') { this.ratio = Number.parseFloat(getComputedStyle(this.searchInput.nativeElement).getPropertyValue('--search-input-icon-ratio')); + this.cdr.detectChanges() } } diff --git a/utils/entitiesAutoComplete/entitiesAutoComplete.component.ts b/utils/entitiesAutoComplete/entitiesAutoComplete.component.ts index 9a3faee1..080f3dab 100644 --- a/utils/entitiesAutoComplete/entitiesAutoComplete.component.ts +++ b/utils/entitiesAutoComplete/entitiesAutoComplete.component.ts @@ -31,7 +31,7 @@ import {AutoCompleteValue} from "../../searchPages/searchUtils/searchHelperClass
        + uk-dropdown="pos: bottom-left; mode: none; stretch: true; offset: 15; flip: false; shift: false" [attr.target]="'#' + id" [attr.boundary]="'#' + id">
        -
        diff --git a/searchPages/searchUtils/newSearchPage.component.html b/searchPages/searchUtils/newSearchPage.component.html index 68893685..2f0e4d78 100644 --- a/searchPages/searchUtils/newSearchPage.component.html +++ b/searchPages/searchUtils/newSearchPage.component.html @@ -182,7 +182,7 @@ (stickyForm?'':' ') : (+ (stickyForm?'':' uk-section') +' uk-padding-remove-bottom uk-padding-remove-top ' + ((usedBy == 'deposit' || usedBy == 'orcid') ? ' uk-padding-remove-top ' : ' '))" - [attr.uk-sticky]="((stickyForm || (simpleView && mobile))?'{offset:100;top:90;cls-active:uk-active uk-sticky-below;cls-inactive:uk-sticky '+ + [attr.uk-sticky]="((stickyForm || (simpleView && mobile))?'{offset:100;start:90;cls-active:uk-active uk-sticky-below;cls-inactive:uk-sticky '+ (usedBy != 'deposit' && usedBy != 'orcid' && (!customFilter || customFilter.queryFieldName != 'communityId') ? ' uk-position-relative ' :(' uk-section ' ))+'}':null)">
        From befbfd071b96dec562ac39b0d4b65e869fd094ce Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Wed, 23 Aug 2023 11:43:07 +0300 Subject: [PATCH 10/48] Add xsmall padding in graph_elements instread of margins because of uikit upgrade --- .../dataProvider/dataProvider.component.html | 4 ++- .../organization/organization.component.html | 26 ++++++++++--------- landingPages/project/project.component.html | 4 ++- .../result/resultLanding.component.html | 4 ++- monitor/methodology/terminology.component.ts | 8 +++--- 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/landingPages/dataProvider/dataProvider.component.html b/landingPages/dataProvider/dataProvider.component.html index 4ec30f37..5246c1b0 100644 --- a/landingPages/dataProvider/dataProvider.component.html +++ b/landingPages/dataProvider/dataProvider.component.html @@ -48,7 +48,8 @@
        -
        -
        - - - Powered by OpenAIRE graph - - +
        +
        + + + Powered by OpenAIRE graph + + Last update of records in OpenAIRE: {{indexUpdateDate | date: 'MMM dd, yyyy'}} - -
        - Found an issue? - Give us feedback -
        -
        + +
        + Found an issue? + Give us feedback +
        +
        +
        -
        -
        -
        -
        +
        Powered by OpenAIRE graph @@ -83,9 +83,9 @@ declare var ResizeObserver;
        -
        -
        +
        Powered by OpenAIRE graph From 7906b056c919a95d9edbe6bcc00e364f2af4dab7 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Wed, 23 Aug 2023 16:42:52 +0300 Subject: [PATCH 11/48] Fix drops and dropdowns in avalableOn, result-preview and showAuthors --- .../landing-utils/availableOn.component.ts | 7 +++-- utils/authors/showAuthors.component.ts | 2 +- .../result-preview.component.html | 30 ++++++++++--------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/landingPages/landing-utils/availableOn.component.ts b/landingPages/landing-utils/availableOn.component.ts index 9a1d217c..b3d47e2b 100644 --- a/landingPages/landing-utils/availableOn.component.ts +++ b/landingPages/landing-utils/availableOn.component.ts @@ -26,9 +26,10 @@ import {RouterHelper} from "../../utils/routerHelper.class"; -
        - +
        +
        + +
        diff --git a/utils/authors/showAuthors.component.ts b/utils/authors/showAuthors.component.ts index a9987197..12a8784a 100644 --- a/utils/authors/showAuthors.component.ts +++ b/utils/authors/showAuthors.component.ts @@ -26,7 +26,7 @@ import {properties} from "../../../../environments/environment"; {{author.fullName + ";"}} -
        +
        diff --git a/utils/result-preview/result-preview.component.html b/utils/result-preview/result-preview.component.html index d8608abb..5bc75d3f 100644 --- a/utils/result-preview/result-preview.component.html +++ b/utils/result-preview/result-preview.component.html @@ -216,20 +216,22 @@ [name]="result.measure.bip[0].icon"> {{result.measure.bip[0].value}} -
        - - - - - - -
        - - {{metric.name}}{{metric.value}}
        -
        - BIP! - Powered by BIP! +
        +
        + + + + + + +
        + + {{metric.name}}{{metric.value}}
        +
        + BIP! + Powered by BIP! +
        From 55a995a34890318b024d458cdd978a9d044d8b47 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Tue, 29 Aug 2023 10:53:51 +0300 Subject: [PATCH 12/48] Input: Date add calendar in mobile dropdown --- sharedComponents/input/input.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sharedComponents/input/input.component.ts b/sharedComponents/input/input.component.ts index d3a6722b..ad0bd9a4 100644 --- a/sharedComponents/input/input.component.ts +++ b/sharedComponents/input/input.component.ts @@ -199,6 +199,9 @@ declare var UIkit; uk-dropdown="pos: bottom-left; mode: none; flip: false ; shift: false" [attr.target]="'#' + id" [attr.boundary]="'#' + id" (click)="$event.stopPropagation()">
        + + +
          From 1be082c2f37c0a7da1c06de23e7588718b8b647c Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Wed, 30 Aug 2023 11:11:23 +0300 Subject: [PATCH 13/48] Add hasStickyHeaderOnMobile in layoutService --- .../sharedComponents/sidebar/layout.service.ts | 18 ++++++++++++++++++ sharedComponents/navigationBar.component.html | 2 +- sharedComponents/navigationBar.component.ts | 5 +++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/dashboard/sharedComponents/sidebar/layout.service.ts b/dashboard/sharedComponents/sidebar/layout.service.ts index 39a2eaa8..38ae65c7 100644 --- a/dashboard/sharedComponents/sidebar/layout.service.ts +++ b/dashboard/sharedComponents/sidebar/layout.service.ts @@ -79,6 +79,10 @@ export class LayoutService { * Add hasMenuSearchBar: false/ nothing on data of route config, if the search bar in the menu should not appear, otherwise true. */ private hasMenuSearchBarSubject: BehaviorSubject = new BehaviorSubject(false); + /** + * Add hasStickyHeaderOnMobile: true in order to activate uk-sticky in header of mobile/tablet devices. + * */ + private hasStickyHeaderOnMobileSubject: BehaviorSubject = new BehaviorSubject(false); private subscriptions: any[] = []; @@ -177,6 +181,12 @@ export class LayoutService { } else { this.setHasMenuSearchBar(false); } + if (data['hasStickyHeaderOnMobile'] !== undefined && + data['hasStickyHeaderOnMobile'] === true) { + this.setHasStickyHeaderOnMobile(true); + } else { + this.setHasStickyHeaderOnMobile(false); + } } })); this.setObserver(); @@ -311,4 +321,12 @@ export class LayoutService { setHasMenuSearchBar(value: boolean) { this.hasMenuSearchBarSubject.next(value); } + + get hasStickyHeaderOnMobile(): Observable { + return this.hasStickyHeaderOnMobileSubject.asObservable(); + } + + setHasStickyHeaderOnMobile(value: boolean) { + this.hasStickyHeaderOnMobileSubject.next(value); + } } diff --git a/sharedComponents/navigationBar.component.html b/sharedComponents/navigationBar.component.html index 83f49b47..873c0cd9 100644 --- a/sharedComponents/navigationBar.component.html +++ b/sharedComponents/navigationBar.component.html @@ -1,5 +1,5 @@
          -
        @@ -123,7 +123,7 @@ From dbbd7ec541d96a5062a769dea2255ad9ccb36b13 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Thu, 12 Oct 2023 13:10:02 +0300 Subject: [PATCH 24/48] Input: Make error text-small --- sharedComponents/input/input.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sharedComponents/input/input.component.ts b/sharedComponents/input/input.component.ts index ad0bd9a4..cfcae9d5 100644 --- a/sharedComponents/input/input.component.ts +++ b/sharedComponents/input/input.component.ts @@ -231,10 +231,10 @@ declare var UIkit;
        - - {{errors?.error}} - Please provide a valid URL (e.g. https://example.com) - + + {{errors?.error}} + Please provide a valid URL (e.g. https://example.com) + From 8d6522270c26451c7474630fc71e7fa3e31c5c0e Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Mon, 16 Oct 2023 14:34:48 +0300 Subject: [PATCH 25/48] Slider container: Fix error about observer --- .../slider-container.component.ts | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/sharedComponents/slider-utils/slider-container.component.ts b/sharedComponents/slider-utils/slider-container.component.ts index 1fd18467..4eea093d 100644 --- a/sharedComponents/slider-utils/slider-container.component.ts +++ b/sharedComponents/slider-utils/slider-container.component.ts @@ -60,7 +60,7 @@ export class SliderContainerComponent implements OnInit, OnDestroy, AfterContent } ngOnDestroy() { - if(this.observer) { + if(this.observer && typeof IntersectionObserver !== 'undefined') { this.observer.disconnect(); } } @@ -78,24 +78,26 @@ export class SliderContainerComponent implements OnInit, OnDestroy, AfterContent } setObserver() { - let options = { - root: null, - rootMargin: '0px', - threshold: 0.1 - }; - this.observer = new IntersectionObserver(entries => { - entries.forEach(entry => { - if (entry.isIntersecting && !this.initialized) { - this.stopped = false; - this.start(0); - this.initialized = true; - } else { - this.initialized = false; - this.stopped = true; - } - }); - }, options); - this.observer.observe(this.element.nativeElement); + if(typeof IntersectionObserver !== 'undefined') { + let options = { + root: null, + rootMargin: '0px', + threshold: 0.1 + }; + this.observer = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (entry.isIntersecting && !this.initialized) { + this.stopped = false; + this.start(0); + this.initialized = true; + } else { + this.initialized = false; + this.stopped = true; + } + }); + }, options); + this.observer.observe(this.element.nativeElement); + } } start(time: number) { From 717de55f6e5ee051d3c2618a45bc21f75ca96120 Mon Sep 17 00:00:00 2001 From: "konstantina.galouni" Date: Wed, 18 Oct 2023 14:18:04 +0300 Subject: [PATCH 26/48] [Library | angular-16]: [Bug fix] Name of bip metrics accidentally removed in branches merge. --- utils/result-preview/result-preview.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/result-preview/result-preview.component.html b/utils/result-preview/result-preview.component.html index 6323db7c..cec5a36c 100644 --- a/utils/result-preview/result-preview.component.html +++ b/utils/result-preview/result-preview.component.html @@ -224,6 +224,7 @@ + {{metric.name}} {{metric.value | number}} {{metric.value}} From f6e927f4c2e9fee5518182d3b770548ccd2c8d37 Mon Sep 17 00:00:00 2001 From: "konstantina.galouni" Date: Thu, 19 Oct 2023 14:49:14 +0300 Subject: [PATCH 27/48] [angular-16 | WIP | REMOVED] Removed files environment.all.ts, environment.beta.ts, environment.prod.ts, environment.test.ts and define and export all properties from environment.ts. --- .../environments/environment.all.ts | 25 ------------ .../environments/environment.beta.ts | 6 --- .../environments/environment.prod.ts | 6 --- .../environments/environment.test.ts | 6 --- utils/properties/environments/environment.ts | 39 +++++++++++++++++++ 5 files changed, 39 insertions(+), 43 deletions(-) delete mode 100644 utils/properties/environments/environment.all.ts delete mode 100644 utils/properties/environments/environment.beta.ts delete mode 100644 utils/properties/environments/environment.prod.ts delete mode 100644 utils/properties/environments/environment.test.ts diff --git a/utils/properties/environments/environment.all.ts b/utils/properties/environments/environment.all.ts deleted file mode 100644 index 020b7e89..00000000 --- a/utils/properties/environments/environment.all.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {EnvProperties} from "../env-properties"; - -export let common: EnvProperties = { - swhURL: "https://archive.softwareheritage.org/", - // searchCrossrefAPIURL: "https://api.crossref.org/works", - // searchDataciteAPIURL: "https://api.datacite.org/works", - // searchOrcidURL: "https://pub.orcid.org/v2.1/", - // orcidURL: "https://orcid.org/", - // doiURL: "https://doi.org/", - // pmcURL: "http://europepmc.org/articles/", - // pmidURL: "https://www.ncbi.nlm.nih.gov/pubmed/", - // handleURL: "http://hdl.handle.net/", - // cordisURL: "http://cordis.europa.eu/projects/", - // openDoarURL: "http://v2.sherpa.ac.uk/id/repository/", - // r3DataURL: "http://service.re3data.org/repository/", - // fairSharingURL: "https://fairsharing.org/", - // sherpaURL: "http://sherpa.ac.uk/romeo/issn/", - // sherpaURLSuffix: "/", - // zenodo: "https://zenodo.org/", - // openAccess: "https://www.openaire.eu/support/faq#article-id-234", - // openAccessRepo: "https://www.openaire.eu/support/faq#article-id-310", - // fp7Guidlines: "https://www.openaire.eu/open-access-in-fp7-seventh-research-framework-programme", - // h2020Guidlines: "https://www.openaire.eu/oa-publications/h2020/open-access-in-horizon-2020", - // ercGuidlines: "http://erc.europa.eu/sites/default/files/document/file/ERC_Open_Access_Guidelines-revised_2014.pdf", -} \ No newline at end of file diff --git a/utils/properties/environments/environment.beta.ts b/utils/properties/environments/environment.beta.ts deleted file mode 100644 index 520b88e2..00000000 --- a/utils/properties/environments/environment.beta.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {EnvProperties} from "../env-properties"; - -export let commonBeta: EnvProperties = { - // searchAPIURLLAst: "http://beta.services.openaire.eu/search/v2/api/", - // searchResourcesAPIURL: "https://beta.services.openaire.eu/search/v2/api/resources", -} \ No newline at end of file diff --git a/utils/properties/environments/environment.prod.ts b/utils/properties/environments/environment.prod.ts deleted file mode 100644 index a2aa22f8..00000000 --- a/utils/properties/environments/environment.prod.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {EnvProperties} from "../env-properties"; - -export let commonProd: EnvProperties = { - // searchAPIURLLAst: "https://services.openaire.eu/search/v2/api/", - // searchResourcesAPIURL: "https://services.openaire.eu/search/v2/api/resources", -} \ No newline at end of file diff --git a/utils/properties/environments/environment.test.ts b/utils/properties/environments/environment.test.ts deleted file mode 100644 index 81abb978..00000000 --- a/utils/properties/environments/environment.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {EnvProperties} from "../env-properties"; - -export let commonTest: EnvProperties = { - // searchAPIURLLAst: "https://services.openaire.eu/shadowSearch/v2/api/", - // searchResourcesAPIURL: "https://services.openaire.eu/shadowSearch/v2/api/resources", -} \ No newline at end of file diff --git a/utils/properties/environments/environment.ts b/utils/properties/environments/environment.ts index 69e7ceb1..6faf2d7f 100644 --- a/utils/properties/environments/environment.ts +++ b/utils/properties/environments/environment.ts @@ -1,6 +1,45 @@ import {EnvProperties} from "../env-properties"; +export let common: EnvProperties = { + swhURL: "https://archive.softwareheritage.org/", + // searchCrossrefAPIURL: "https://api.crossref.org/works", + // searchDataciteAPIURL: "https://api.datacite.org/works", + // searchOrcidURL: "https://pub.orcid.org/v2.1/", + // orcidURL: "https://orcid.org/", + // doiURL: "https://doi.org/", + // pmcURL: "http://europepmc.org/articles/", + // pmidURL: "https://www.ncbi.nlm.nih.gov/pubmed/", + // handleURL: "http://hdl.handle.net/", + // cordisURL: "http://cordis.europa.eu/projects/", + // openDoarURL: "http://v2.sherpa.ac.uk/id/repository/", + // r3DataURL: "http://service.re3data.org/repository/", + // fairSharingURL: "https://fairsharing.org/", + // sherpaURL: "http://sherpa.ac.uk/romeo/issn/", + // sherpaURLSuffix: "/", + // zenodo: "https://zenodo.org/", + // openAccess: "https://www.openaire.eu/support/faq#article-id-234", + // openAccessRepo: "https://www.openaire.eu/support/faq#article-id-310", + // fp7Guidlines: "https://www.openaire.eu/open-access-in-fp7-seventh-research-framework-programme", + // h2020Guidlines: "https://www.openaire.eu/oa-publications/h2020/open-access-in-horizon-2020", + // ercGuidlines: "http://erc.europa.eu/sites/default/files/document/file/ERC_Open_Access_Guidelines-revised_2014.pdf", +} + export let commonDev: EnvProperties = { // searchAPIURLLAst: "http://beta.services.openaire.eu/search/v2/api/", // searchResourcesAPIURL: "https://beta.services.openaire.eu/search/v2/api/resources", +} + +export let commonTest: EnvProperties = { + // searchAPIURLLAst: "https://services.openaire.eu/shadowSearch/v2/api/", + // searchResourcesAPIURL: "https://services.openaire.eu/shadowSearch/v2/api/resources", +} + +export let commonBeta: EnvProperties = { + // searchAPIURLLAst: "http://beta.services.openaire.eu/search/v2/api/", + // searchResourcesAPIURL: "https://beta.services.openaire.eu/search/v2/api/resources", +} + +export let commonProd: EnvProperties = { + // searchAPIURLLAst: "https://services.openaire.eu/search/v2/api/", + // searchResourcesAPIURL: "https://services.openaire.eu/search/v2/api/resources", } \ No newline at end of file From de3fbe8d69ebab14da17913704db3f6a0e35983a Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Thu, 19 Oct 2023 16:33:04 +0300 Subject: [PATCH 28/48] Move topics and cache indicators in library. --- monitor-admin/topic/indicators.component.html | 490 ++++++ monitor-admin/topic/indicators.component.less | 53 + monitor-admin/topic/indicators.component.ts | 1494 +++++++++++++++++ monitor-admin/topic/topic-routing.module.ts | 19 + monitor-admin/topic/topic.component.html | 380 +++++ monitor-admin/topic/topic.component.ts | 795 +++++++++ monitor-admin/topic/topic.module.ts | 49 + monitor-admin/utils/adminDashboard.guard.ts | 43 + .../cache-indicators.component.less | 9 + .../cache-indicators.component.ts | 78 + .../cache-indicators.module.ts | 11 + .../cache-indicators.service.ts | 24 + .../cache-indicators/cache-indicators.ts | 261 +++ monitor-admin/utils/indicator-utils.ts | 1005 +++++++++++ .../utils/services/statistics.service.ts | 24 + .../utils/services/stats-profiles.service.ts | 19 + services/servicesUtils/customOptions.class.ts | 1 - 17 files changed, 4754 insertions(+), 1 deletion(-) create mode 100644 monitor-admin/topic/indicators.component.html create mode 100644 monitor-admin/topic/indicators.component.less create mode 100644 monitor-admin/topic/indicators.component.ts create mode 100644 monitor-admin/topic/topic-routing.module.ts create mode 100644 monitor-admin/topic/topic.component.html create mode 100644 monitor-admin/topic/topic.component.ts create mode 100644 monitor-admin/topic/topic.module.ts create mode 100644 monitor-admin/utils/adminDashboard.guard.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.component.less create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.component.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.module.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.service.ts create mode 100644 monitor-admin/utils/cache-indicators/cache-indicators.ts create mode 100644 monitor-admin/utils/indicator-utils.ts create mode 100644 monitor-admin/utils/services/statistics.service.ts create mode 100644 monitor-admin/utils/services/stats-profiles.service.ts diff --git a/monitor-admin/topic/indicators.component.html b/monitor-admin/topic/indicators.component.html new file mode 100644 index 00000000..ecdc0cdb --- /dev/null +++ b/monitor-admin/topic/indicators.component.html @@ -0,0 +1,490 @@ +
        +
        +
        Number Indicators
        +
        +
        +
        +
        +
        + + + + + + + + + + + + + + + + +
        +
        +
        +
        +
        +
        + +
        +
        +
        + + + + +
        + +
        +
        +
        {{indicator.name}}
        +
        + + -- +
        +
        +
        +
        +
        + +
        +
        +
        + +
        +
        +
        +
        +
        Chart Indicators
        +
        +
        +
        +
        +
        + + + + + + + + + + + + + + + + +
        +
        +
        +
        +
        +
        + +
        +
        +
        + + + + +
        + +
        +
        +
        +
        + {{indicator.name}} +
        + +
        + +
        +
        + +
        + +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        + Create a custom indicator +
        +
        + Use our advance tool to create a custom Indicator that suit the needs of your funding + KPI's. +
        +
        + +
        +
        +
        +
        +
        +
        +
        +
        + +
        +
        +
        +
        + +
        + +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        {{urlParameterizedMessage}}
        +
        +
        + +
        +
        +
        +
        + There are schema enhancements that can be applied in this query.Apply + now +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        + JSON Path +
        +
        +
        + This JSON path is not valid or the result has not been calculated yet. + Please press calculate on box below to see the result. +
        +
        +
        +
        +
        + + + + + + +
        +
        + +
        +
        +
        +
        +
        + + + + + -- + + + +
        +
        +
        +
        +
        +
        +
        +
        +
        + +
        + +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        {{urlParameterizedMessage}}
        +
        +
        + +
        +
        +
        +
        + There are schema enhancements that can be applied in this query. Apply + now +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        + +
        +
        +
        +
        +
        +
        + +
        +
        + +
        + You are about to delete + "{{indicator.name ? indicator.name : (indicator.indicatorPaths[0]?.parameters?.title?indicator.indicatorPaths[0].parameters.title:'')}}" indicator permanently. +
        + Indicators of all profiles based on this default indicator, will be deleted as well. +
        + + + + Are you sure you want to proceed? +
        +
        +
        + + +
        +
        + +
        + You are about to delete this section and its indicators permanently. +
        + Sections of all profiles based on this default section and their contents, will be deleted as well. +
        + + + + Are you sure you want to proceed? +
        +
        + + +
        +
        + +
        +
        +
        diff --git a/monitor-admin/topic/indicators.component.less b/monitor-admin/topic/indicators.component.less new file mode 100644 index 00000000..627ab18a --- /dev/null +++ b/monitor-admin/topic/indicators.component.less @@ -0,0 +1,53 @@ +@import (reference) "~src/assets/openaire-theme/less/_import-variables"; + +.number-preview { + border: @global-border-width solid @global-border; + background: transparent; + border-radius: @global-border-radius; + min-width: 100px; + min-height: 70px; +} + +.refresh-indicator { + background-color: @global-overlay-background; + border-radius: @global-border-radius; + position: absolute; + color: @global-inverse-color; + z-index: 1; +} + +.section { + padding: 60px 45px; + border-radius: @global-border-radius; + border: @global-border-width solid @global-border; + position: relative; + background: @global-inverse-color; + border-left: 5px @global-primary-background solid; + + .tools { + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, -100%); + max-width: 50px; + padding: 5px 10px; + background-image: @global-primary-gradient; + color: @global-inverse-color; + -webkit-clip-path: polygon(20% 5%, 80% 5%, 100% 100%, 0% 100%); + clip-path: polygon(20% 5%, 80% 5%, 100% 100%, 0% 100%); + display: none; + } + + &:hover { + .tools { + display: block; + + a { + color: currentColor; + &:hover { + text-decoration: none; + } + } + } + } +} diff --git a/monitor-admin/topic/indicators.component.ts b/monitor-admin/topic/indicators.component.ts new file mode 100644 index 00000000..d97f5f82 --- /dev/null +++ b/monitor-admin/topic/indicators.component.ts @@ -0,0 +1,1494 @@ +import { + AfterViewInit, + ChangeDetectorRef, + Component, + HostListener, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + ViewChild +} from "@angular/core"; +import { + Format, + Indicator, + IndicatorPath, + IndicatorSize, + IndicatorType, + Section, + Stakeholder, + Visibility +} from "../../monitor/entities/stakeholder"; +import {IndicatorUtils, StakeholderUtils} from "../utils/indicator-utils"; +import { + AbstractControl, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validators +} from "@angular/forms"; +import {AlertModal} from "../../utils/modal/alert"; +import {StatisticsService} from "../utils/services/statistics.service"; +import {HelperFunctions} from "../../utils/HelperFunctions.class"; +import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser"; +import {Reorder, StakeholderService} from "../../monitor/services/stakeholder.service"; +import {EnvProperties} from "../../utils/properties/env-properties"; +import {Observable, Subscriber} from "rxjs"; +import {LayoutService} from "../../dashboard/sharedComponents/sidebar/layout.service"; +import {Router} from "@angular/router"; +import {Role, Session, User} from "../../login/utils/helper.class"; +import {StringUtils} from "../../utils/string-utils.class"; +import {Notification} from "../../notifications/notifications"; +import {NotificationUtils} from "../../notifications/notification-utils"; +import {NotifyFormComponent} from "../../notifications/notify-form/notify-form.component"; +import {NotificationService} from "../../notifications/notification.service"; +import {properties} from "src/environments/environment"; +import {NotificationHandler} from "../../utils/notification-handler"; + +declare var UIkit; + +@Component({ + selector: 'indicators', + templateUrl: './indicators.component.html', + styleUrls: ['indicators.component.less'] +}) +export class IndicatorsComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { + filesToUpload: Array; + errorMessage = ""; + public properties: EnvProperties = properties; + @Input() + public topicIndex: number = 0; + @Input() + public categoryIndex: number = 0; + @Input() + public subcategoryIndex: number = 0; + @Input() + public stakeholder: Stakeholder = null; + @Input() + public changed: Observable; + @Input() + public user: User = null; + public preview: string; + public indicatorUtils: IndicatorUtils = new IndicatorUtils(); + public stakeholderUtils: StakeholderUtils = new StakeholderUtils(); + public numberIndicatorFb: UntypedFormGroup; + public chartIndicatorFb: UntypedFormGroup; + public chartSections: UntypedFormArray; + public numberSections: UntypedFormArray; + /** + * Editable indicator + */ + public section: Section; + public indicator: Indicator; + public index: number = -1; + public editing: boolean = false; + public dragging: boolean = false; + /** Caches */ + public safeUrls: Map = new Map([]); + public numberResponses: Map = new Map(); + public numberResults: Map = new Map(); + /** Import / Export Indicators */ + importLoading: boolean = false; + @ViewChild('editChartModal', {static: true}) editChartModal: AlertModal; + @ViewChild('editNumberModal', {static: true}) editNumberModal: AlertModal; + @ViewChild('deleteModal', {static: true}) deleteModal: AlertModal; + @ViewChild('deleteSectionModal', {static: true}) deleteSectionModal: AlertModal; + public sectionTypeToDelete: string; + public sectionChildrenActionOnDelete: string; + public indicatorChildrenActionOnDelete: string; + urlParameterizedMessage = null; + showCheckForSchemaEnhancements: boolean = false; + private notification: Notification; + @ViewChild('editNumberNotify', {static: true}) editNumberNotify: NotifyFormComponent; + @ViewChild('editChartNotify', {static: true}) editChartNotify: NotifyFormComponent; + @ViewChild('deleteNotify', {static: true}) deleteNotify: NotifyFormComponent; + + public isFullscreen: boolean = false; + + @HostListener('fullscreenchange', ['$event']) + @HostListener('webkitfullscreenchange', ['$event']) + @HostListener('mozfullscreenchange', ['$event']) + @HostListener('MSFullscreenChange', ['$event']) + screenChange(event) { + this.isFullscreen = !this.isFullscreen; + } + + /** + * Subscriptions + **/ + private subscriptions: any[] = []; + private urlSubscriptions: any[] = []; + private numberSubscription: any[] = []; + + constructor(private layoutService: LayoutService, + private stakeholderService: StakeholderService, + private statisticsService: StatisticsService, + private notificationService: NotificationService, + private fb: UntypedFormBuilder, + private router: Router, + private cdr: ChangeDetectorRef, + private sanitizer: DomSanitizer) { + this.filesToUpload = []; + } + + ngOnInit(): void { + if (this.stakeholder) { + this.setCharts(); + this.setNumbers(); + } + this.changed.subscribe(() => { + this.setCharts(); + this.setNumbers(); + this.initReorder(); + }) + } + + ngOnDestroy(): void { + this.subscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } else if (value instanceof Function) { + value(); + } + }); + this.urlSubscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.numberSubscription.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + } + + ngAfterViewInit(): void { + this.initReorder(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (this.canEdit) { + if (changes.topicIndex || changes.categoryIndex || changes.subcategoryIndex) { + this.initReorder(); + this.setCharts(); + this.setNumbers(); + } + } + } + + initReorder() { + this.subscriptions.forEach(value => { + if (value instanceof Function) { + value(); + } + }); + if (document !== undefined) { + let callback = (list, type: IndicatorType, action: 'moved' | 'added' | 'removed'): void => { + let items: HTMLCollection = list.current.children; + let reordered = []; + for (let i = 0; i < items.length; i++) { + if (items.item(i).id) { + reordered.push(items.item(i).id); + } + } + let reorder: Reorder = { + action: action, + target: list.detail[1].id, + ids: reordered + } + this.reorderIndicators(list.current.id.toString().split('-')[1], type, reorder); + }; + this.numbers.forEach((section) => { + this.subscriptions.push(UIkit.util.on(document, 'start', '#number-' + section._id, (): void => { + this.dragging = true; + })); + this.subscriptions.push(UIkit.util.on(document, 'stop', '#number-' + section._id, (): void => { + this.dragging = false; + })); + this.subscriptions.push(UIkit.util.on(document, 'moved', '#number-' + section._id, (list): void => { + callback(list, "number", 'moved'); + })); + this.subscriptions.push(UIkit.util.on(document, 'added', '#number-' + section._id, (list): void => { + callback(list, "number", 'added'); + })); + this.subscriptions.push(UIkit.util.on(document, 'removed', '#number-' + section._id, (list): void => { + callback(list, "number", 'removed'); + })); + }); + this.charts.forEach((section) => { + this.subscriptions.push(UIkit.util.on(document, 'moved', '#chart-' + section._id, (list): void => { + callback(list, "chart", 'moved'); + })); + this.subscriptions.push(UIkit.util.on(document, 'added', '#chart-' + section._id, (list): void => { + callback(list, "chart", 'added'); + })); + this.subscriptions.push(UIkit.util.on(document, 'removed', '#chart-' + section._id, (list): void => { + callback(list, "chart", 'removed'); + })); + }); + } + } + + hide(element: any) { + UIkit.dropdown(element).hide(); + } + + setCharts() { + this.chartSections = this.fb.array([]); + this.charts.forEach(section => { + this.chartSections.push(this.fb.group({ + _id: this.fb.control(section._id), + title: this.fb.control(section.title), + creationDate: this.fb.control(section.creationDate), + stakeholderAlias: this.fb.control(section.stakeholderAlias), + defaultId: this.fb.control(section.defaultId), + type: this.fb.control(section.type), + indicators: this.fb.control(section.indicators) + })); + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + let url = this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath); + if (!this.safeUrls.get('url')) { + indicatorPath.safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + this.safeUrls.set(url, indicatorPath.safeResourceUrl); + } + }); + }) + }); + } + + setNumbers() { + this.numberSections = this.fb.array([]); + this.numberResults.clear(); + let urls: Map = new Map(); + this.numbers.forEach((section, i) => { + this.numberSections.push(this.fb.group({ + _id: this.fb.control(section._id), + title: this.fb.control(section.title), + creationDate: this.fb.control(section.creationDate), + stakeholderAlias: this.fb.control(section.stakeholderAlias), + defaultId: this.fb.control(section.defaultId), + type: this.fb.control(section.type), + indicators: this.fb.control(section.indicators) + })); + section.indicators.forEach((number, j) => { + let url = this.indicatorUtils.getFullUrl(this.stakeholder, number.indicatorPaths[0]); + const pair = JSON.stringify([number.indicatorPaths[0].source, url]); + const indexes = urls.get(pair) ? urls.get(pair) : []; + indexes.push([i, j]); + urls.set(pair, indexes); + }); + }); + this.numberSubscription.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + urls.forEach((indexes, pair) => { + let parsed = JSON.parse(pair); + let response = this.numberResponses.get(pair); + if (response) { + this.calculateResults(response, indexes); + } else { + this.numberSubscription.push(this.statisticsService.getNumbers(this.indicatorUtils.getSourceType(parsed[0]), parsed[1]).subscribe(response => { + this.calculateResults(response, indexes); + this.numberResponses.set(pair, response); + })); + } + }); + } + + private calculateResults(response: any, indexes: [number, number][]) { + indexes.forEach(([i, j]) => { + let result = JSON.parse(JSON.stringify(response)); + this.numbers[i].indicators[j].indicatorPaths[0].jsonPath.forEach(jsonPath => { + if (result) { + result = result[jsonPath]; + } + }); + if (typeof result === 'string' || typeof result === 'number') { + result = Number(result); + if (result === Number.NaN) { + result = 0; + } + } else { + result = 0; + } + this.numberResults.set(i + '-' + j, result); + }); + } + + get charts(): Section[] { + if (this.stakeholder.topics[this.topicIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]) { + return this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].charts; + } else { + return []; + } + } + + get numbers(): Section[] { + if (this.stakeholder.topics[this.topicIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]) { + return this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].numbers; + } else { + return []; + } + } + + set numbers(sections: Section[]) { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex].numbers = sections; + } + + get open(): boolean { + return this.layoutService.open; + } + + get canEdit() { + return this.stakeholder && + this.stakeholder.topics[this.topicIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex] && + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]; + } + + public get numberIndicatorPaths(): UntypedFormArray { + return this.numberIndicatorFb.get('indicatorPaths') as UntypedFormArray; + } + + public get chartIndicatorPaths(): UntypedFormArray { + return this.chartIndicatorFb.get('indicatorPaths') as UntypedFormArray; + } + + public getNumberClassBySize(size: IndicatorSize) { + if (size === 'small') { + return 'uk-width-medium@m uk-width-1-1'; + } else if (size === 'medium') { + return 'uk-width-1-4@l uk-width-1-2@m uk-width-1-1'; + } else { + return 'uk-width-1-2@l uk-width-1-1@m uk-width-1-1'; + } + } + + public getChartClassBySize(size: IndicatorSize) { + if (size === 'small') { + return 'uk-width-1-3@xl uk-width-1-2@m uk-width-1-1'; + } else if (size === 'medium') { + return 'uk-width-1-2@l uk-width-1-1'; + } else { + return 'uk-width-1-1'; + } + } + + public addJsonPath(index: number) { + if (index == 0 && this.getJsonPath(index).getRawValue()[index].indexOf(",") != -1) { + //if in the first path there are more than one paths comma separated, split them and autogenerate the forms + let paths = this.getJsonPath(index).getRawValue()[index].split(","); + for (let i = 0; i < paths.length; i++) { + if (i != 0) { + this.getJsonPath(index).push(this.fb.control('', Validators.required)); + } + } + this.getJsonPath(index).setValue(paths) + } else { + this.getJsonPath(index).push(this.fb.control('', Validators.required)); + } + } + + public removeJsonPath(i: number, j: number) { + if (this.getJsonPath(i).enabled) { + this.getJsonPath(i).removeAt(j); + } + } + + public validateJsonPath(index: number, dirty: boolean = false) { + let indicatorPath: UntypedFormGroup = this.numberIndicatorPaths.at(index); + if (this.indicator.defaultId === null) { + this.getJsonPath(index).disable(); + } + indicatorPath.get('result').setErrors({validating: true}); + this.subscriptions.push(this.statisticsService.getNumbers(null, indicatorPath.get('url').value).subscribe(response => { + let result = JSON.parse(JSON.stringify(response)); + this.getJsonPath(index).controls.forEach(jsonPath => { + if (result) { + result = result[jsonPath.value]; + } + }); + setTimeout(() => { + if (this.indicator.defaultId === null) { + this.getJsonPath(index).enable(); + if (dirty) { + this.getJsonPath(index).markAsDirty(); + } + } + indicatorPath.get('result').setErrors(null); + if (typeof result === 'string' || typeof result === 'number') { + result = Number(result); + if (result !== Number.NaN) { + indicatorPath.get('result').setValue(result); + } else { + indicatorPath.get('result').setValue(0); + } + } else { + indicatorPath.get('result').setValue(0); + } + }, 500); + }, error => { + setTimeout(() => { + if (this.indicator.defaultId === null) { + this.getJsonPath(index).enable(); + if (dirty) { + this.getJsonPath(index).markAsDirty(); + } + } + indicatorPath.get('result').setErrors(null); + indicatorPath.get('result').setValue(0); + }, 500); + })); + } + + public getJsonPath(index: number): UntypedFormArray { + return this.numberIndicatorPaths.at(index).get('jsonPath') as UntypedFormArray; + } + + public getCurrentJsonPath(index: number): string[] { + return this.section.indicators[this.index].indicatorPaths[index].jsonPath; + } + + public getParameters(index: number): UntypedFormArray { + return this.chartIndicatorPaths.at(index).get('parameters') as UntypedFormArray; + } + + public getParameter(index: number, key: string): UntypedFormControl { + return this.getParameters(index).controls.filter(control => control.value.key === key)[0] as UntypedFormControl; + } + + private getSecureUrlByStakeHolder(indicatorPath: IndicatorPath) { + return this.sanitizer.bypassSecurityTrustResourceUrl( + this.indicatorUtils.getChartUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath))); + } + + private getUrlByStakeHolder(indicatorPath: IndicatorPath) { + return this.indicatorUtils.getChartUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath)); + } + + public addNumberIndicatorPath(url: string = '', parameters: UntypedFormArray = new UntypedFormArray([]), source: string = 'stats-tool', jsonPath: UntypedFormArray = new UntypedFormArray([]), format: Format = "NUMBER") { + if (jsonPath.length === 0) { + jsonPath.push(this.fb.control('', Validators.required)); + } + this.numberIndicatorPaths.push(this.fb.group({ + url: this.fb.control(url, [Validators.required, StringUtils.urlValidator()]), + jsonPath: jsonPath, + result: this.fb.control(0, Validators.required), + source: this.fb.control(source, Validators.required), + format: this.fb.control(format, Validators.required) + } + )); + let index = this.numberIndicatorPaths.length - 1; + if (this.numberIndicatorPaths.at(index).get('url').valid) { + this.validateJsonPath(index); + this.checkForSchemaEnhancements(this.numberIndicatorPaths.at(index).get('url').value); + } + if (this.indicator.defaultId === null) { + this.subscriptions.push(this.numberIndicatorPaths.at(index).get('url').valueChanges.subscribe(value => { + this.numberIndicatorPaths.at(index).get('result').setValue(null); + if (this.numberIndicatorPaths.at(index).get('url').valid) { + let indicatorPath: IndicatorPath = this.indicatorUtils.generateIndicatorByNumberUrl(this.indicatorUtils.getNumberSource(value), value, this.stakeholder, this.numberIndicatorPaths.at(index).get('jsonPath').value, this.indicatorUtils.numberSources.get(this.indicatorUtils.getNumberSource(value))); + if (!this.isStakeholderParametersValid(indicatorPath)) { + // default profile + if (this.stakeholder.defaultId == null) { + this.urlParameterizedMessage = "This indicator couldn't be generated properly. Stakeholders based on this profile may not inherit the data correctly." + } else { + this.urlParameterizedMessage = "This indicator couldn't be generated properly. Please make sure chart data is for the current stakeholder." + } + } else { + this.urlParameterizedMessage = null; + } + this.checkForSchemaEnhancements(this.numberIndicatorPaths.at(index).get('url').value); + if (this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index] = indicatorPath; + } else { + this.indicator.indicatorPaths.push(indicatorPath); + } + if (indicatorPath.source) { + this.numberIndicatorPaths.at(index).get('source').setValue(indicatorPath.source); + } + if (indicatorPath.jsonPath.length > 1 && this.getJsonPath(index).length == 1) { + let paths = indicatorPath.jsonPath; + for (let i = 0; i < paths.length; i++) { + if (i == this.getJsonPath(index).length) { + this.getJsonPath(index).push(this.fb.control('', Validators.required)); + } + } + this.getJsonPath(index).setValue(paths) + } + } else { + this.urlParameterizedMessage = null; + } + }) + ); + + this.subscriptions.push(this.numberIndicatorPaths.at(index).get('jsonPath').valueChanges.subscribe(value => { + if (this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index].jsonPath = value; + } + this.numberIndicatorPaths.at(index).get('result').setValue(null); + }) + ); + this.subscriptions.push(this.numberIndicatorPaths.at(index).get('source').valueChanges.subscribe(value => { + if (this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index].source = value; + } + }) + ); + } else { + this.numberIndicatorPaths.at(index).get('url').disable(); + this.numberIndicatorPaths.at(index).get('jsonPath').disable(); + this.numberIndicatorPaths.at(index).get('source').disable(); + } + } + + public addChartIndicatorPath(value: string = '', parameters: UntypedFormArray = new UntypedFormArray([]), disableUrl: boolean = false, type: string = null) { + this.chartIndicatorPaths.push(this.fb.group({ + url: this.fb.control(value, [Validators.required, StringUtils.urlValidator()]), + parameters: parameters, + type: this.fb.control(type) + } + )); + let index = this.chartIndicatorPaths.length - 1; + if (disableUrl) { + this.chartIndicatorPaths.at(index).get('url').disable(); + } else { + this.checkForSchemaEnhancements(this.chartIndicatorPaths.at(index).get('url').value); + this.urlSubscriptions.push(this.chartIndicatorPaths.at(index).get('url').valueChanges.subscribe(value => { + if (this.chartIndicatorPaths.at(index).get('url').valid) { + let indicatorPath: IndicatorPath = this.indicatorUtils.generateIndicatorByChartUrl(this.indicatorUtils.getChartSource(value), value, this.chartIndicatorPaths.at(index).get('type').value, this.stakeholder); + if (!this.isStakeholderParametersValid(indicatorPath)) { + // default profile + if (this.stakeholder.defaultId == null) { + this.urlParameterizedMessage = "This chart couldn't be generated properly. Stakeholders based on this profile may not inherit the data correctly." + } else { + this.urlParameterizedMessage = "This chart couldn't be generated properly. Please make sure chart data is for the current stakeholder." + } + } else { + this.urlParameterizedMessage = null; + } + this.checkForSchemaEnhancements(this.chartIndicatorPaths.at(index).get('url').value); + (this.chartIndicatorPaths.at(index) as UntypedFormGroup).get('type').setValue(indicatorPath.type); + let parameters = this.getParametersAsFormArray(indicatorPath); + (this.chartIndicatorPaths.at(index) as UntypedFormGroup).setControl('parameters', parameters); + if (!this.indicator.indicatorPaths[index]) { + this.indicator.indicatorPaths[index] = indicatorPath; + this.indicator.indicatorPaths[index].safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + } else { + indicatorPath.safeResourceUrl = this.indicator.indicatorPaths[index].safeResourceUrl; + this.indicator.indicatorPaths[index] = indicatorPath; + } + } else { + this.urlParameterizedMessage = null; + } + })); + } + } + + private isStakeholderParametersValid(indicatorPath: IndicatorPath) { + return !((indicatorPath.chartObject && Object.keys(indicatorPath.parameters).indexOf("index_id") == -1 && Object.keys(indicatorPath.parameters).indexOf("index_name") == -1 && Object.keys(indicatorPath.parameters).indexOf("index_shortName") == -1) + || (!indicatorPath.chartObject && indicatorPath.url.indexOf("index_id") == -1 && indicatorPath.url.indexOf("index_name") == -1 && (indicatorPath.url).indexOf("index_shortName") == -1)); + + } + + private getJsonPathAsFormArray(indicatorPath: IndicatorPath): UntypedFormArray { + let jsonPath = this.fb.array([]); + if (indicatorPath.jsonPath) { + indicatorPath.jsonPath.forEach(path => { + jsonPath.push(this.fb.control(path, Validators.required)); + }); + } + return jsonPath; + } + + private getParametersAsFormArray(indicatorPath: IndicatorPath): UntypedFormArray { + let parameters = this.fb.array([]); + if (indicatorPath.parameters) { + Object.keys(indicatorPath.parameters).forEach(key => { + if (this.indicatorUtils.ignoredParameters.indexOf(key) === -1) { + if (this.indicatorUtils.parametersValidators.has(key)) { + parameters.push(this.fb.group({ + key: this.fb.control(key), + value: this.fb.control(indicatorPath.parameters[key], this.indicatorUtils.parametersValidators.get(key)) + })); + } else { + parameters.push(this.fb.group({ + key: this.fb.control(key), + value: this.fb.control(indicatorPath.parameters[key]) + })); + } + } + }); + } + return parameters; + } + + public editNumberIndicatorOpen(section: Section, id = null) { + this.urlParameterizedMessage = null; + this.section = section; + this.index = (id) ? section.indicators.findIndex(value => value._id === id) : -1; + if (this.index !== -1) { + this.indicator = HelperFunctions.copy(this.section.indicators[this.index]); + this.numberIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name, Validators.required), + description: this.fb.control(this.indicator.description), + creationDate: this.fb.control(this.indicator.creationDate), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([], Validators.required), + type: this.fb.control(this.indicator.type), + width: this.fb.control(this.indicator.width), + height: this.fb.control(this.indicator.height), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.indicator.indicatorPaths.forEach(indicatorPath => { + this.addNumberIndicatorPath(this.indicatorUtils.getNumberUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath)), indicatorPath.parameters, indicatorPath.source, this.getJsonPathAsFormArray(indicatorPath), indicatorPath.format); + }); + } else { + this.indicator = new Indicator('', '', '', 'number', 'small', 'small', "PUBLIC", []); + this.numberIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name, Validators.required), + description: this.fb.control(this.indicator.description), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([], Validators.required), + type: this.fb.control(this.indicator.type), + width: this.fb.control(this.indicator.width), + height: this.fb.control(this.indicator.height), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.addNumberIndicatorPath(); + } + if (this.indicator.defaultId) { + setTimeout(() => { + this.numberIndicatorFb.get('description').disable(); + }, 0); + } + this.editNumberModal.cancelButtonText = 'Cancel'; + this.editNumberModal.okButtonLeft = false; + this.editNumberModal.alertMessage = false; + if (this.index === -1) { + this.editNumberModal.alertTitle = 'Create a new number indicator'; + this.editNumberModal.okButtonText = 'Save'; + this.notification = NotificationUtils.createIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.editNumberNotify.reset(this.notification.message); + } else { + this.editNumberModal.alertTitle = 'Edit number indicator\'s information'; + this.editNumberModal.okButtonText = 'Save Changes'; + this.notification = NotificationUtils.editIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.editNumberNotify.reset(this.notification.message); + } + this.editNumberModal.stayOpen = true; + this.editNumberModal.open(); + } + + public editChartIndicatorOpen(section: Section, id = null) { + this.urlParameterizedMessage = null; + this.urlSubscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } + }); + this.section = section; + this.index = (id) ? section.indicators.findIndex(value => value._id === id) : -1; + if (this.index !== -1) { + this.indicator = HelperFunctions.copy(this.section.indicators[this.index]); + this.chartIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name), + creationDate: this.fb.control(this.indicator.creationDate), + description: this.fb.control(this.indicator.description), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([]), + width: this.fb.control(this.indicator.width), + height: this.fb.control(this.indicator.height), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.indicator.indicatorPaths.forEach(indicatorPath => { + this.addChartIndicatorPath(this.getUrlByStakeHolder(indicatorPath), + this.getParametersAsFormArray(indicatorPath), this.indicator.defaultId !== null, indicatorPath.type); + indicatorPath.safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + }); + } else { + this.indicator = new Indicator('', '', '', 'chart', 'medium', 'medium', "PUBLIC", []); + this.chartIndicatorFb = this.fb.group({ + _id: this.fb.control(this.indicator._id), + name: this.fb.control(this.indicator.name), + description: this.fb.control(this.indicator.description), + additionalDescription: this.fb.control(this.indicator.additionalDescription), + visibility: this.fb.control(this.indicator.visibility), + indicatorPaths: this.fb.array([]), + width: this.fb.control(this.indicator.width, Validators.required), + height: this.fb.control(this.indicator.height, Validators.required), + defaultId: this.fb.control(this.indicator.defaultId) + }); + this.addChartIndicatorPath(); + } + if (this.indicator.defaultId) { + setTimeout(() => { + this.chartIndicatorFb.get('description').disable(); + }, 0); + } + this.editChartModal.cancelButtonText = 'Cancel'; + this.editChartModal.okButtonLeft = false; + this.editChartModal.alertMessage = false; + if (this.index === -1) { + this.editChartModal.alertTitle = 'Create a new chart indicator'; + this.editChartModal.okButtonText = 'Save'; + this.notification = NotificationUtils.createIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.editChartNotify.reset(this.notification.message); + } else { + this.editChartModal.alertTitle = 'Edit chart indicator\'s information'; + this.editChartModal.okButtonText = 'Save Changes'; + this.notification = NotificationUtils.editIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + ; + this.editChartNotify.reset(this.notification.message); + } + this.editChartModal.stayOpen = true; + this.editChartModal.open(); + } + + saveIndicator() { + this.editing = true; + if (this.indicator.type === 'chart') { + this.chartIndicatorFb.get('description').enable(); + this.indicator = this.indicatorUtils.generateIndicatorByForm(this.chartIndicatorFb.value, this.indicator.indicatorPaths, this.indicator.type, true); + this.section = this.charts.find(section => section._id === this.section._id); + } else { + this.numberIndicatorFb.get('description').enable(); + this.indicator = this.indicatorUtils.generateIndicatorByForm(this.numberIndicatorFb.value, this.indicator.indicatorPaths, this.indicator.type, false); + this.section = this.numbers.find(section => section._id === this.section._id); + } + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + this.section._id + ]; + this.subscriptions.push(this.stakeholderService.saveElement(this.properties.monitorServiceAPIURL, this.indicator, path).subscribe(indicator => { + if (this.index !== -1) { + this.section.indicators[this.index] = indicator; + } else { + this.section.indicators.push(indicator); + } + this.notification.entity = indicator._id; + this.notification.stakeholder = this.stakeholder.alias; + this.notification.stakeholderType = this.stakeholder.type; + this.notification.groups = [Role.curator(this.stakeholder.type)]; + if (this.stakeholder.defaultId) { + this.notification.groups.push(Role.manager(this.stakeholder.type, this.stakeholder.alias)); + if (this.indicator.type === "chart") { + this.setCharts(); + this.chartIndicatorFb = null; + this.editChartNotify.sendNotification(this.notification); + } else { + this.setNumbers(); + this.numberIndicatorFb = null; + this.editNumberNotify.sendNotification(this.notification); + } + } else { + this.stakeholderService.getStakeholders(this.properties.monitorServiceAPIURL, null, this.stakeholder._id).subscribe(stakeholders => { + stakeholders.forEach(value => { + this.notification.groups.push(Role.manager(value.type, value.alias)) + }); + if (this.indicator.type === "chart") { + this.setCharts(); + this.chartIndicatorFb = null; + this.editChartNotify.sendNotification(this.notification); + } else { + this.setNumbers(); + this.numberIndicatorFb = null; + this.editNumberNotify.sendNotification(this.notification); + } + }); + } + UIkit.notification('Indicator has been successfully saved', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + if (this.indicator.type === "chart") { + this.editChartModal.cancel(); + } else { + this.editNumberModal.cancel(); + } + }, error => { + this.chartIndicatorFb = null; + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + if (this.indicator.type === "chart") { + this.editChartModal.cancel(); + } else { + this.editNumberModal.cancel(); + } + })); + } + + saveIndicators(sections) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id + ]; + this.subscriptions.push(this.stakeholderService.saveBulkElements(this.properties.monitorServiceAPIURL, sections, path).subscribe(stakeholder => { + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].charts = stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].charts; + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].numbers = stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].numbers; + this.setCharts(); + this.setNumbers(); + this.initReorder(); + this.notification = NotificationUtils.importIndicators(this.user.fullname, this.stakeholder.alias); + this.notification.entity = this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index]._id; + this.notification.name = this.user.firstname; + this.notification.surname = this.user.lastname; + this.notification.stakeholder = this.stakeholder.alias; + this.notification.stakeholderType = this.stakeholder.type; + this.notification.groups = [Role.curator(this.stakeholder.type)]; + if (this.stakeholder.defaultId) { + this.notification.groups.push(Role.manager(this.stakeholder.type, this.stakeholder.alias)); + this.notificationService.sendNotification(this.notification).subscribe(notification => { + UIkit.notification('A notification has been sent successfully', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + }, error => { + UIkit.notification('An error has occurred. Please try again later', { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + }); + } else { + this.stakeholderService.getStakeholders(this.properties.monitorServiceAPIURL, null, this.stakeholder._id).subscribe(stakeholders => { + stakeholders.forEach(value => { + this.notification.groups.push(Role.manager(value.type, value.alias)) + }); + this.notificationService.sendNotification(this.notification).subscribe(notification => { + NotificationHandler.rise('A notification has been sent successfully'); + }, error => { + NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); + }); + }); + } + this.editing = false; + this.importLoading = false; + NotificationHandler.rise('Indicators have been imported successfully!'); + }, error => { + this.chartIndicatorFb = null; + NotificationHandler.rise('An error has occurred. Please try again later', 'danger'); + this.editing = false; + this.importLoading = false; + })); + + + } + + reorderIndicators(sectionId: string, type: IndicatorType, reorder: Reorder) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + sectionId + ]; + this.subscriptions.push(this.stakeholderService.reorderIndicators(this.properties.monitorServiceAPIURL, path, reorder, type).subscribe(indicators => { + if (type === 'chart') { + this.charts.find(section => section._id === sectionId).indicators = indicators; + this.setCharts(); + } else { + this.numbers.find(section => section._id === sectionId).indicators = indicators; + this.setNumbers(); + } + this.editing = false; + })); + } + + hasDifference(index: number): boolean { + let hasDifference = false; + this.chartIndicatorPaths.at(index).value.parameters.forEach((parameter) => { + if (parameter.value !== this.indicator.indicatorPaths[index].parameters[parameter.key]) { + hasDifference = true; + return; + } + }); + return hasDifference || this.indicator.indicatorPaths[index].safeResourceUrl.toString() !== + this.getSecureUrlByStakeHolder(this.indicator.indicatorPaths[index]).toString(); + } + + public get isAdministrator(): boolean { + return Session.isPortalAdministrator(this.user); + } + + public get isCurator(): boolean { + return this.isAdministrator || Session.isCurator(this.stakeholder.type, this.user); + } + + refreshIndicator() { + this.indicator = this.indicatorUtils.generateIndicatorByForm(this.chartIndicatorFb.value, this.indicator.indicatorPaths, 'chart', true); + this.indicator.indicatorPaths.forEach(indicatorPath => { + indicatorPath.safeResourceUrl = this.getSecureUrlByStakeHolder(indicatorPath); + }); + } + + deleteIndicatorOpen(section: Section, indicatorId: string, type: string, childrenAction: string = null) { + this.indicatorChildrenActionOnDelete = null; + if (childrenAction == "delete") { + this.indicatorChildrenActionOnDelete = childrenAction; + } else if (childrenAction == "disconnect") { + this.indicatorChildrenActionOnDelete = childrenAction; + } + + this.section = section; + if (type === 'chart') { + this.index = this.charts.find(value => value._id == section._id).indicators.findIndex(value => value._id == indicatorId); + } else { + this.index = this.numbers.find(value => value._id == section._id).indicators.findIndex(value => value._id == indicatorId); + } + this.indicator = section.indicators.find(value => value._id == indicatorId); + this.deleteModal.alertTitle = 'Delete indicator'; + this.deleteModal.cancelButtonText = 'No'; + this.deleteModal.okButtonText = 'Yes'; + this.notification = NotificationUtils.deleteIndicator(this.user.firstname + ' ' + this.user.lastname, this.stakeholder.name); + this.deleteNotify.reset(this.notification.message); + this.deleteModal.stayOpen = true; + this.deleteModal.open(); + } + + deleteIndicator() { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + this.section._id, + this.indicator._id + ]; + this.subscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, path, this.indicatorChildrenActionOnDelete).subscribe(() => { + if (this.indicator.type === 'chart') { + this.charts.find(section => section._id === this.section._id).indicators.splice(this.index, 1); + this.setCharts(); + } else { + this.numbers.find(section => section._id === this.section._id).indicators.splice(this.index, 1); + this.setNumbers(); + } + UIkit.notification('Indicator has been successfully deleted', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.notification.entity = this.indicator._id; + this.notification.stakeholder = this.stakeholder.alias; + this.notification.stakeholderType = this.stakeholder.type; + this.notification.groups = [Role.curator(this.stakeholder.type)]; + if (this.stakeholder.defaultId) { + this.notification.groups.push(Role.manager(this.stakeholder.type, this.stakeholder.alias)); + this.deleteNotify.sendNotification(this.notification); + } else { + this.stakeholderService.getStakeholders(this.properties.monitorServiceAPIURL, null, this.stakeholder._id).subscribe(stakeholders => { + stakeholders.forEach(value => { + this.notification.groups.push(Role.manager(value.type, value.alias)) + }); + this.deleteNotify.sendNotification(this.notification); + }); + } + this.editing = false; + this.deleteModal.cancel(); + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.deleteModal.cancel(); + })); + } + + changeIndicatorStatus(sectionId: string, indicator: Indicator, visibility: Visibility) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + sectionId, + indicator._id + ]; + this.subscriptions.push(this.stakeholderService.changeVisibility(this.properties.monitorServiceAPIURL, path, visibility).subscribe(returnedElement => { + indicator.visibility = returnedElement.visibility; + UIkit.notification('Indicator has been successfully changed to ' + indicator.visibility.toLowerCase(), { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + }, error => { + UIkit.notification('An error has been occurred. Try again later', { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + })); + } + + saveSection(focused: boolean, sectionControl: AbstractControl, index: number, type: IndicatorType = "chart") { + if (!focused && sectionControl.dirty) { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id + ]; + this.subscriptions.push(this.stakeholderService.saveSection(this.properties.monitorServiceAPIURL, sectionControl.value, path, index).subscribe(section => { + if (type === 'chart') { + this.charts[index] = section; + this.setCharts(); + } else { + this.numbers[index] = section; + this.setNumbers(); + } + this.initReorder(); + UIkit.notification('Section has been successfully saved', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + })); + } + } + + createSection(index = -1, type: IndicatorType = 'chart') { + this.editing = true; + this.section = new Section(type, null, null, this.stakeholder.alias); + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id + ]; + this.subscriptions.push(this.stakeholderService.saveSection(this.properties.monitorServiceAPIURL, this.section, path, index).subscribe(section => { + if (type === 'chart') { + if (index !== -1) { + this.charts.splice(index, 0, section); + } else { + this.charts.push(section); + } + this.setCharts(); + } else { + if (index !== -1) { + this.numbers.splice(index, 0, section); + } else { + this.numbers.push(section); + } + this.setNumbers(); + } + this.initReorder(); + UIkit.notification('Section has been successfully created', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + })); + } + + // deleteNumberSectionOpen(section: Section, index: number) { + // this.section = section; + // this.index = index; + // this.deleteNumberSectionModal.alertTitle = 'Delete Section'; + // this.deleteNumberSectionModal.cancelButtonText = 'No'; + // this.deleteNumberSectionModal.okButtonText = 'Yes'; + // this.deleteNumberSectionModal.open(); + // } + // + // deleteChartSectionOpen(section: Section, index: number) { + // this.section = section; + // this.index = index; + // this.deleteChartSectionModal.alertTitle = 'Delete Section'; + // this.deleteChartSectionModal.cancelButtonText = 'No'; + // this.deleteChartSectionModal.okButtonText = 'Yes'; + // this.deleteChartSectionModal.open(); + // } + + deleteSectionOpen(section: Section, index: number, type: IndicatorType, childrenAction: string = null) { + if (!this.editing && !section.defaultId) { + this.sectionTypeToDelete = type; + this.sectionChildrenActionOnDelete = null; + if (childrenAction == "delete") { + this.sectionChildrenActionOnDelete = childrenAction; + } else if (childrenAction == "disconnect") { + this.sectionChildrenActionOnDelete = childrenAction; + } + + this.section = section; + this.index = index; + this.deleteSectionModal.alertTitle = 'Delete Section'; + this.deleteSectionModal.cancelButtonText = 'No'; + this.deleteSectionModal.okButtonText = 'Yes'; + this.deleteSectionModal.stayOpen = true; + this.deleteSectionModal.open(); + } + } + + deleteSection() { + this.editing = true; + let path = [ + this.stakeholder._id, + this.stakeholder.topics[this.topicIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex]._id, + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.subcategoryIndex]._id, + this.section._id + ]; + this.subscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, path, this.sectionChildrenActionOnDelete).subscribe(() => { + if (this.sectionTypeToDelete === "chart") { + this.charts.splice(this.index, 1); + this.setCharts(); + } else { + this.numbers.splice(this.index, 1); + this.setNumbers(); + } + this.initReorder(); + UIkit.notification('Section has been successfully deleted', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.deleteSectionModal.cancel(); + }, error => { + UIkit.notification(error.error.message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.deleteSectionModal.cancel(); + })); + } + + private checkForSchemaEnhancements(url: string) { + this.showCheckForSchemaEnhancements = this.isAdministrator && url && !this.properties.useOldStatisticsSchema && this.indicatorUtils.checkForSchemaEnhancements(url); + } + + migrateFromOldImportJsonFile(charts) { + // first section contains numbers + // second contains charts + let hasNumbers = false; + for (let chart of charts) { + if (chart['type'] == 'number') { + hasNumbers = true; + break; + } + } + let chartsSection = (hasNumbers ? 1 : 0); // no numbers: first sections contains charts + for (let chart of charts) { + if (chart['sectionIndex'] == null) { + chart['sectionIndex'] = chart['type'] == 'chart' ? chartsSection : 0; + } + } + return charts; + } + + importIndicatorsAndSave(charts: any[]) { + let sectionsToSave: Section[] = []; + let countIndicators = 0; + // name description additionalDescription, height, width, visibility + let noValidParams = 0; + let duplicates = 0; + charts = this.migrateFromOldImportJsonFile(charts); + for (let chart of charts) { + if (!sectionsToSave[chart['sectionIndex']]) { + let sectionToSave = new Section(chart['sectionType'] ? chart['sectionType'] : chart['type'], chart['sectionTitle']); + sectionToSave.indicators = []; + sectionsToSave[chart['sectionIndex']] = sectionToSave; + } + let exists = false; + let indicatorPath; + // validate indicators' schema from file + let invalid_file_message; + if (!chart.type) { + invalid_file_message = "No indicator type is specified. Type should be chart or number."; + } else if (chart.type != "chart" && chart.type != "number") { + invalid_file_message = "Invalid indicator type. Type should be chart or number."; + } else if (chart.type == "number" && !chart.jsonPath) { + invalid_file_message = "No jsonPath is specified for number indicator." + } else if (!chart.url) { + invalid_file_message = "No indicator url is specified."; + } + + if (invalid_file_message) { + UIkit.notification(invalid_file_message, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + break; + } + + if (chart.type == "chart") { + indicatorPath = this.indicatorUtils.generateIndicatorByChartUrl(this.indicatorUtils.getChartSource(chart.url), chart.url, chart.type, this.stakeholder); + for (let section of this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].charts) { + for (let chart of section.indicators) { + if (JSON.stringify(chart.indicatorPaths[0].chartObject) == JSON.stringify(indicatorPath.chartObject)) { + duplicates++; + exists = true; + } + } + + } + } else if (chart.type == "number") { + indicatorPath = this.indicatorUtils.generateIndicatorByNumberUrl(this.indicatorUtils.getNumberSource(chart.url), chart.url, this.stakeholder, + chart.jsonPath, this.indicatorUtils.numberSources.get(this.indicatorUtils.getNumberSource(chart.url))); + for (let section of this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[this.index].numbers) { + for (let chart of section.indicators) { + if (JSON.stringify(chart.indicatorPaths[0].chartObject) == JSON.stringify(indicatorPath.chartObject)) { + duplicates++; + exists = true; + } + } + + } + } + if (!this.isStakeholderParametersValid(indicatorPath)) { + noValidParams++; + } + if (!exists) { + let i: Indicator = new Indicator(chart.name, chart.description, chart.additionalDescription, chart.type, chart.width, chart.height, "RESTRICTED", [indicatorPath]); + sectionsToSave[chart['sectionIndex']].indicators.push(i); + countIndicators++; + } + + } + + if (duplicates > 0) { + UIkit.notification(duplicates + " urls already exist and will not be imported!", { + status: 'warning', + timeout: 6000, + pos: 'bottom-right' + }); + } + if (noValidParams > 0) { + let noValidMessage = "Some indicators couldn't be generated properly. Please make sure chart data is for the current stakeholder."; + if (this.stakeholder.defaultId == null) { + noValidMessage = "Some indicators couldn't be generated properly. Stakeholders based on this profile may not inherit the data correctly."; + } + UIkit.notification(noValidMessage, { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + } else if (sectionsToSave.length > 0 && countIndicators > 0) { + this.saveIndicators(sectionsToSave.filter(section => !!section)); + } + if (sectionsToSave.length == 0 || countIndicators == 0) { + UIkit.notification(" No urls imported!", { + status: 'warning', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + } + } + + public exportIndicators(subcategoryIndex) { + this.editing = true; + let indicators = []; + let index: number = 0; + let indexIndicator: number = 0; + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[subcategoryIndex].numbers.forEach(section => { + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + indicators[indexIndicator] = { + "type": indicator.type, "name": indicator.name, "jsonPath": indicatorPath.jsonPath, + "description": indicator.description, "additionalDescription": indicator.additionalDescription, + "visibility": indicator.visibility, "width": indicator.width, "height": indicator.height, + "url": this.indicatorUtils.getNumberUrl(indicatorPath.source, this.indicatorUtils.getFullUrl(this.stakeholder, indicatorPath)), + "sectionTitle": section.title, + "sectionType": section.type, + "sectionIndex": index + }; + indexIndicator++; + }); + }); + index++; + }); + + this.stakeholder.topics[this.topicIndex].categories[this.categoryIndex].subCategories[subcategoryIndex].charts.forEach(section => { + section.indicators.forEach(indicator => { + indicator.indicatorPaths.forEach(indicatorPath => { + indicators[indexIndicator] = { + "type": indicator.type, "name": indicator.name, + "description": indicator.description, "additionalDescription": indicator.additionalDescription, + "visibility": indicator.visibility, "width": indicator.width, "height": indicator.height, + "url": this.getUrlByStakeHolder(indicatorPath), + "sectionTitle": section.title, + "sectionType": section.type, + "sectionIndex": index + }; + indexIndicator++; + }); + }); + index++; + + }); + + let topic = this.stakeholder ? this.stakeholder.topics[this.topicIndex] : null; + let category = topic ? topic.categories[this.categoryIndex] : null; + let subCategory = category ? category.subCategories[this.subcategoryIndex] : null; + + var jsonFileUrl = window.URL.createObjectURL(new Blob([JSON.stringify(indicators)], {type: 'application/json'})); + var a = window.document.createElement('a'); + window.document.body.appendChild(a); + a.setAttribute('style', 'display: none'); + a.href = jsonFileUrl; + a.download = this.stakeholder.alias + "_" + topic.alias + "_" + category.alias + "_" + subCategory.alias + ".json"; + a.click(); + window.URL.revokeObjectURL(jsonFileUrl); + a.remove(); // remove the element + + this.editing = false; + } + + fileChangeEvent(fileInput: any, index) { + this.index = index; + this.editing = true; + this.importLoading = true; + this.filesToUpload = >fileInput.target.files; + this.upload(); + } + + upload() { + if (this.filesToUpload.length == 0) { + console.error("There is no selected file to upload."); + UIkit.notification("There is no selected file to upload.", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + return; + } else { + if (this.filesToUpload[0].name.indexOf(".json") == -1 || (this.filesToUpload[0].type != "application/json")) { + console.error("No valid file type. The required type is JSON"); + UIkit.notification("No valid file type. The required type is JSON", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + return; + } + } + + this.makeFileRequest(this.properties.utilsService + '/upload?type=json', [], this.filesToUpload).then(async (result: string) => { + + let json_result = JSON.parse(result); + + // validate file + if (!json_result || json_result.length == 0) { + UIkit.notification("Importing file is empty", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + } else { + this.importIndicatorsAndSave(json_result); + } + }, (error) => { + console.error("Error importing files", error); + UIkit.notification("Error importing files", { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.editing = false; + this.importLoading = false; + }); + } + + makeFileRequest(url: string, params: Array, files: Array) { + return new Promise((resolve, reject) => { + const formData: any = new FormData(); + const xhr = new XMLHttpRequest(); + for (let i = 0; i < files.length; i++) { + formData.append("uploads[]", files[i], files[i].name); + } + xhr.onreadystatechange = function () { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + resolve(xhr.response); + } else { + reject(xhr.response); + } + } + }; + xhr.open("POST", url, true); + xhr.send(formData); + }); + } + + copyToClipboard(value) { + const tempBox = document.createElement('textarea'); + tempBox.style.position = 'fixed'; + tempBox.style.left = '0'; + tempBox.style.top = '0'; + tempBox.style.opacity = '0'; + tempBox.value = value; + document.body.appendChild(tempBox); + tempBox.focus(); + tempBox.select(); + tempBox.setSelectionRange(0, 99999); + document.execCommand('copy'); + document.body.removeChild(tempBox); + NotificationHandler.rise('Copied to clipboard'); + } +} diff --git a/monitor-admin/topic/topic-routing.module.ts b/monitor-admin/topic/topic-routing.module.ts new file mode 100644 index 00000000..bccc637d --- /dev/null +++ b/monitor-admin/topic/topic-routing.module.ts @@ -0,0 +1,19 @@ +import {NgModule} from '@angular/core'; +import {RouterModule} from '@angular/router'; +import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard'; +import {TopicComponent} from "./topic.component"; +import {CanExitGuard} from "../../utils/can-exit.guard"; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + component: TopicComponent, + canDeactivate: [PreviousRouteRecorder, CanExitGuard] + } + ]) + ] +}) +export class TopicRoutingModule { +} diff --git a/monitor-admin/topic/topic.component.html b/monitor-admin/topic/topic.component.html new file mode 100644 index 00000000..2e5c7ab1 --- /dev/null +++ b/monitor-admin/topic/topic.component.html @@ -0,0 +1,380 @@ + + + +
        +
        + +
        + You are about to delete "{{element.name}}" {{type}} permanently. +
        + {{getPluralTypeName()}} of all profiles based on this default {{type}}, will be deleted as well. +
        + Are you sure you want to proceed? +
        +
        + +
        + +
        +
        +
        +
        +
        +
        +
        +
        + +
        + You have the option to change the visibility status of your {{type}}, with or without applying the changed status to + its contents. +
        +
        + +
        +
        + +
        +
        +

        Note:

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

        Note:

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

        " + contactForm.message + "

        " + + "
        "; + email.recipients = admins; + return email; + } + public static composeEmailForUsageCounts(contactForm: any, admins: any): Email { let email: Email = new Email(); From 6355a731464721f90dba6fc91132b57a3fb14a65 Mon Sep 17 00:00:00 2001 From: argirok Date: Mon, 23 Oct 2023 14:32:46 +0300 Subject: [PATCH 32/48] [angular-16-update-zenodo-api | DONE] : update zenodo community API and parsing --- .../zenodo-communities.service.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/connect/zenodoCommunities/zenodo-communities.service.ts b/connect/zenodoCommunities/zenodo-communities.service.ts index 5268a079..296f41bb 100644 --- a/connect/zenodoCommunities/zenodo-communities.service.ts +++ b/connect/zenodoCommunities/zenodo-communities.service.ts @@ -16,7 +16,8 @@ export class ZenodoCommunitiesService { //.map(res => res.json()) .pipe(map(res => [this.parseZenodoCommunities(res['hits'].hits),res['hits'].total])); } - getZenodoCommunityById(properties:EnvProperties, url: string) { + getZenodoCommunityById(properties:EnvProperties, id: string) { + let url = properties.zenodoCommunities + "/" + id; return this.http.get((properties.useLongCache)? (properties.cacheUrl+encodeURIComponent(url)) : url) //.map(res => res.json()) .pipe(map(res => { @@ -39,14 +40,14 @@ export class ZenodoCommunitiesService { parseZenodoCommunity(resData:any):ZenodoCommunityInfo { var result: ZenodoCommunityInfo = new ZenodoCommunityInfo(); - - result['title'] = resData.title; + let metadata = resData["metadata"]; + result['title'] = metadata.title; result['id'] = resData.id; - result['description'] = resData.description; - result['link'] = resData.links.html; - result['logoUrl'] = resData.logo_url; + result['description'] = metadata.description; + result['link'] = resData.links.self_html; + result['logoUrl'] = resData.links.logo; result['date'] = resData.updated; - result['page'] = resData.page; + result['page'] = metadata.page; return result; } From b67c242fe70de58057b19620d3c80276c4473a8f Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Tue, 24 Oct 2023 11:51:55 +0300 Subject: [PATCH 33/48] [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, From 54bff7cd4a26f948d402973162cfd6a87abb1974 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Wed, 25 Oct 2023 10:37:39 +0300 Subject: [PATCH 34/48] [angular-16 | DONE | CHANGED]: Change email composer for Develop project (Add name, surname, email in mail body). --- utils/email/composer.ts | 43 ++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/utils/email/composer.ts b/utils/email/composer.ts index 216a1d59..4a2d35fd 100644 --- a/utils/email/composer.ts +++ b/utils/email/composer.ts @@ -1,6 +1,7 @@ import {Email} from "./email"; import {Body} from "./body"; import {properties} from "../../../../environments/environment"; +import {User} from "../../login/utils/helper.class"; export class Composer { private static noteBodySize = "14px"; @@ -48,7 +49,7 @@ export class Composer { public static composeEmailForNewCommunity(contactForm: any, admins: any): Email { let email: Email = new Email(); - + email.subject = "OpenAIRE - Connect"; email.body = "
        " + "Name: " + contactForm.name + "
        " @@ -61,10 +62,10 @@ export class Composer { email.recipients = admins; return email; } - + public static composeEmailForMonitor(contactForm: any, admins: string[]): Email { let email: Email = new Email(); - + email.subject = "OpenAIRE - Monitor"; email.body = "
        " + "Name: " + contactForm.name + "
        " @@ -93,21 +94,23 @@ export class Composer { return email; } - public static composeEmailForDevelop(contactForm: any, admins: any): Email { + public static composeEmailForDevelop(contactForm: any, admins: any, user: User): Email { let email: Email = new Email(); - email.subject = "OpenAIRE - Develop [" + properties.environment.toUpperCase() + "]"; email.body = "
        " - + "Subject: " + (contactForm.subject ? contactForm.subject : '-') + "
        " - + "

        " + contactForm.message + "

        " - + "
        "; + + "Name: " + user.firstname + "
        " + + "Surname: " + user.lastname + "
        " + + "Email: " + user.email + "
        " + + "Subject: " + (contactForm.subject ? contactForm.subject : '-') + "
        " + + "

        " + contactForm.message + "

        " + + "
        "; email.recipients = admins; return email; } public static composeEmailForUsageCounts(contactForm: any, admins: any): Email { let email: Email = new Email(); - + email.subject = "OpenAIRE - UsageCounts"; email.body = "
        " + "Name: " + contactForm.name + "
        " @@ -119,10 +122,10 @@ export class Composer { email.recipients = admins; return email; } - + public static composeEmailForGraph(contactForm: any, admins: any): Email { let email: Email = new Email(); - + email.subject = "OpenAIRE - Research Graph"; email.body = "
        " + "Name: " + contactForm.name + "
        " @@ -134,7 +137,7 @@ export class Composer { email.recipients = admins; return email; } - + public static composeEmailForFeedback(info: {name: string, url: string, email: string, issues: any[]}, recipients: string[]): Email { let email: Email = new Email(); email.subject = 'Feedback report for ' + info.name; @@ -173,7 +176,7 @@ export class Composer { email.recipients = recipients; return email; } - + public static composeEmailForUserAfterFeedback(recipients: string[]): Email { let email: Email = new Email(); email.subject = 'Feedback report received'; @@ -184,7 +187,7 @@ export class Composer { email.recipients = recipients; return email; } - + public static composeEmailToInformOldManagersForTheNewOnes(communityName: string, communityId: string) : Email { let email: Email = new Email(); @@ -233,7 +236,7 @@ export class Composer { + "
        "; return email; } - + public static formatEmailBodyForInvitation(body: Body): string { let fromMessageAndName = ""; @@ -250,7 +253,7 @@ export class Composer { return formattedEmail; } - + // TODO remove this after adding this on admin public static initializeInvitationsBody(communityId: string, communityTitle: string, fullname: string): Body { let defaultMainBody = '

        You are invited to join ' @@ -281,7 +284,7 @@ export class Composer { return {body: "", subject: this.subjectPrefix + communityTitle, recipients: []}; } - + public static composeEmailForMonitorDashboard(name: string, recipient: string, role: "manager" | "member") { let email: Email = new Email(); email.subject = 'OpenAIRE Monitor Dashboard | ' + name; @@ -289,7 +292,7 @@ export class Composer { email.recipient = recipient; return email; } - + public static composeMessageForMonitorDashboard(name: string, role: "manager" | "member", user: string = null, invitation: any = null) { let pdppLink = 'http://catalogue.openaire.eu/files/Personal%20Data%20Protection%20Policy%20for%20OpenAIRE%20Services%20April%202022.pdf'; let message = '

        Dear ((__user__)),

        ' + @@ -313,7 +316,7 @@ export class Composer { } return message; } - + public static composeEmailForCommunityDashboard(name: string, role: "manager" | "member", recipient: string) { let email: Email = new Email(); email.subject = 'OpenAIRE Connect | ' + name; @@ -321,7 +324,7 @@ export class Composer { email.recipient = recipient; return email; } - + public static composeMessageForCommunityDashboard(name: string, role: "manager" | "member", user: string = null, invitation: any = null) { let message = '

        Dear ((__user__)),

        ' + '

        You have been invited to be a ' + role + ' of the OpenAIRE Research Community Dashboard for the ' + name + '.

        ' + From 4e20a39578d66da9595bc40f22c80dff3e2bd3bc Mon Sep 17 00:00:00 2001 From: "konstantina.galouni" Date: Wed, 25 Oct 2023 11:51:44 +0300 Subject: [PATCH 35/48] [angular-16 | DONE | CHANGED]: parsingFunctions.class.ts: In bip indicators parsing, parse citations from measure with id 'influence_alt' (old) or 'citation_count' (renamed). --- landingPages/landing-utils/parsingFunctions.class.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/landingPages/landing-utils/parsingFunctions.class.ts b/landingPages/landing-utils/parsingFunctions.class.ts index 8c40bed9..cad44497 100644 --- a/landingPages/landing-utils/parsingFunctions.class.ts +++ b/landingPages/landing-utils/parsingFunctions.class.ts @@ -888,7 +888,7 @@ export class ParsingFunctions { counts.push({name: 'downloads', icon: 'download', value: element.count, order: 1}); measure.downloads = element.count; } - if (element.id == 'influence_alt') { + if (element.id == 'influence_alt' || element.id == 'citation_count') { bip.push({name: 'citations', icon: 'cite', value: element.score, order: 2}); measure.citations = element.score; } From a953195dc81caae9e03c0e4a654ab3f73ba31678 Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Wed, 25 Oct 2023 15:16:13 +0300 Subject: [PATCH 36/48] [angular-16 | DONE | CHANGED]: Make dashboard-sidebar sticky instead of fixed. --- .../sidebar/sideBar.component.html | 4 +- .../sidebar/sideBar.component.ts | 4 + monitor-admin/topic/topic.component.html | 175 +++++++++--------- monitor-admin/topic/topic.component.ts | 4 + 4 files changed, 99 insertions(+), 88 deletions(-) diff --git a/dashboard/sharedComponents/sidebar/sideBar.component.html b/dashboard/sharedComponents/sidebar/sideBar.component.html index 35e20996..9e772ea8 100644 --- a/dashboard/sharedComponents/sidebar/sideBar.component.html +++ b/dashboard/sharedComponents/sidebar/sideBar.component.html @@ -1,8 +1,8 @@ - +
      -
    • -
    • - -
      -
      - + Create new topic
      - Create new topic -
      -
      -
    • -
    + +
  • +
+
-
- - +
diff --git a/monitor-admin/topic/topic.component.ts b/monitor-admin/topic/topic.component.ts index c9d6f7ff..57f65930 100644 --- a/monitor-admin/topic/topic.component.ts +++ b/monitor-admin/topic/topic.component.ts @@ -38,6 +38,7 @@ export class TopicComponent implements OnInit, OnDestroy, AfterViewInit, IDeacti private topicSubscriptions: any[] = []; private subscriptions: any[] = []; public properties: EnvProperties = properties; + public offset: number; public stakeholderUtils: StakeholderUtils = new StakeholderUtils(); public loading: boolean = false; public stakeholder: Stakeholder; @@ -95,6 +96,9 @@ export class TopicComponent implements OnInit, OnDestroy, AfterViewInit, IDeacti } public ngOnInit() { + if (typeof document !== "undefined") { + this.offset = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--header-height')); + } let subscription: Subscription; this.subscriptions.push(this.topicIndexSubject.asObservable().subscribe(index => { this.topicChanged(() => { From 065d73bb3eb787e48a33112451dd4441de9d1658 Mon Sep 17 00:00:00 2001 From: argirok Date: Thu, 26 Oct 2023 09:48:11 +0300 Subject: [PATCH 37/48] [angular-16-community-projects-availableSince | DONE | UPDATE] : add method to get last date that db is loaded --- utils/indexInfo.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utils/indexInfo.service.ts b/utils/indexInfo.service.ts index 0835e04f..1570a7e2 100644 --- a/utils/indexInfo.service.ts +++ b/utils/indexInfo.service.ts @@ -25,6 +25,10 @@ export class IndexInfoService { let url = properties.indexInfoAPI; return this.http.get((properties.useLongCache)? (properties.cacheUrl+encodeURIComponent(url)): url).pipe(map(res => res['orcid_update_date'])).pipe(catchError(err => {return of(null)})); } + getDBLoadLastDate(properties: EnvProperties): Observable { + let url = properties.indexInfoAPI; + return this.http.get((properties.useLongCache)? (properties.cacheUrl+encodeURIComponent(url)): url).pipe(map(res => res['db_load_date'])).pipe(catchError(err => {return of(null)})); + } } From fcc130b530a506f856986bdd80322b8c11b43636 Mon Sep 17 00:00:00 2001 From: argirok Date: Thu, 26 Oct 2023 16:29:05 +0300 Subject: [PATCH 38/48] [angular-16-community-projects-availableSince | DONE | UPDATE] : don't show router link and result actions (for the community projects case where the id is set to '-1') --- utils/result-preview/result-preview.component.html | 4 ++-- utils/result-preview/result-preview.component.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/utils/result-preview/result-preview.component.html b/utils/result-preview/result-preview.component.html index cec5a36c..53415d44 100644 --- a/utils/result-preview/result-preview.component.html +++ b/utils/result-preview/result-preview.component.html @@ -23,7 +23,7 @@

+ [routerLink]="url" class="uk-link uk-text-decoration-none uk-width-expand" [class.uk-disabled]="result.id == '-1'">
{{result.acronym}} @@ -180,7 +180,7 @@
-
+
Date: Fri, 27 Oct 2023 17:49:31 +0300 Subject: [PATCH 39/48] [angular-16 | DONE | FIXED]: Create group was hidden for curators because of new dynamic types. --- login/utils/helper.class.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/login/utils/helper.class.ts b/login/utils/helper.class.ts index 7ade96d7..8fdd6229 100644 --- a/login/utils/helper.class.ts +++ b/login/utils/helper.class.ts @@ -112,7 +112,7 @@ export class Session { } public static isCurator(type: string, user: User): boolean { - return stakeholderTypes.find(stakeholderType => stakeholderType.value == type) && this.isTypeCurator(type, user); + return (type === 'community' || stakeholderTypes.find(stakeholderType => stakeholderType.value == type)) && this.isTypeCurator(type, user); } public static isPortalAdministrator(user: User): boolean { From 35b893e05e7f409767668eb9e362ef521b322251 Mon Sep 17 00:00:00 2001 From: "konstantina.galouni" Date: Mon, 30 Oct 2023 10:53:19 +0200 Subject: [PATCH 40/48] [angular-16 | DONE | FIXED]: In queries of results by pid, do not include pidtype restriction (some handle pids are resolved by doi regex, but no results returned for pidtype exact "doi"). --- landingPages/dataProvider/dataProvider.component.ts | 2 +- landingPages/dataProvider/dataProvider.service.ts | 2 +- landingPages/result/resultLanding.component.ts | 2 +- landingPages/result/resultLanding.service.ts | 2 +- orcid/my-orcid-links/myOrcidLinks.component.ts | 2 +- searchPages/searchUtils/newSearchPage.component.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/landingPages/dataProvider/dataProvider.component.ts b/landingPages/dataProvider/dataProvider.component.ts index ec9c4ad2..6c4b8be0 100644 --- a/landingPages/dataProvider/dataProvider.component.ts +++ b/landingPages/dataProvider/dataProvider.component.ts @@ -434,7 +434,7 @@ export class DataProviderComponent { }, err => { //console.log(err); - this.handleError("Error getting " + this.type + " for " + (this.datasourceId ? ("id: " + this.datasourceId) : ("pid: " + this.identifier.id + " ("+this.identifier.class+")")), err); + this.handleError("Error getting " + this.type + " for " + (this.datasourceId ? ("id: " + this.datasourceId) : ("pid: " + this.identifier.id)), err); if (err.status == 404) { this._router.navigate([this.properties.errorLink], { queryParams: { diff --git a/landingPages/dataProvider/dataProvider.service.ts b/landingPages/dataProvider/dataProvider.service.ts index 49d3e911..a80645e8 100644 --- a/landingPages/dataProvider/dataProvider.service.ts +++ b/landingPages/dataProvider/dataProvider.service.ts @@ -24,7 +24,7 @@ export class DataProviderService { if (id) { return properties.searchAPIURLLAst + typePathParam + "/" + id + '?format=json'; } else if (identifier) { - return properties.searchAPIURLLAst + "resources2?pid="+encodeURIComponent(identifier.id) + "&pidtype=" + identifier.class + "&type="+typePathParam+"&format=json"; + return properties.searchAPIURLLAst + "resources2?query=(pid exact \""+encodeURIComponent(identifier.id) + "\")&type="+typePathParam+"&format=json"; } } diff --git a/landingPages/result/resultLanding.component.ts b/landingPages/result/resultLanding.component.ts index 9ba1309b..9e1c8714 100644 --- a/landingPages/result/resultLanding.component.ts +++ b/landingPages/result/resultLanding.component.ts @@ -604,7 +604,7 @@ export class ResultLandingComponent { } }, err => { - this.handleError("Error getting " + this.type + " for " + (this.id ? ("id: " + this.id) : ("pid: " + this.identifier.id + " ("+this.identifier.class+")")), err); + this.handleError("Error getting " + this.type + " for " + (this.id ? ("id: " + this.id) : ("pid: " + this.identifier.id)), err); if (err.status == 404) { this._router.navigate([this.properties.errorLink], {queryParams: {"page": this._location.path(true), "page_type": this.type}}); }else if(err.name == "TimeoutError"){ diff --git a/landingPages/result/resultLanding.service.ts b/landingPages/result/resultLanding.service.ts index 49c24532..7ed58bb0 100644 --- a/landingPages/result/resultLanding.service.ts +++ b/landingPages/result/resultLanding.service.ts @@ -39,7 +39,7 @@ export class ResultLandingService { } else if (identifier) { // pid = "10.3389/fphys.2014.00466"; let url = properties.searchAPIURLLAst + "resources2"; - url += "?pid=" + encodeURIComponent(identifier.id) + "&pidtype=" + identifier.class + "&type="; + url += "?query=(pid exact \"" + encodeURIComponent(identifier.id) + "\")&type="; if (type === 'publication') { url += 'publications'; } else if (type === 'dataset') { diff --git a/orcid/my-orcid-links/myOrcidLinks.component.ts b/orcid/my-orcid-links/myOrcidLinks.component.ts index 5fbe58b8..29d66592 100644 --- a/orcid/my-orcid-links/myOrcidLinks.component.ts +++ b/orcid/my-orcid-links/myOrcidLinks.component.ts @@ -302,7 +302,7 @@ export class MyOrcidLinksComponent { for(let work of works) { for(let pid of work['pids']) { let identifier: Identifier = Identifier.getIdentifierFromString(pid, false); - this.orcidQuery += (this.orcidQuery ? " or " : "") + ('(pidclassid exact "'+identifier.class+'" and pid="'+StringUtils.URIEncode(identifier.id)+'")'); + this.orcidQuery += (this.orcidQuery ? " or " : "") + ('(pid="'+StringUtils.URIEncode(identifier.id)+'")'); } } this.showLoading = false; diff --git a/searchPages/searchUtils/newSearchPage.component.ts b/searchPages/searchUtils/newSearchPage.component.ts index a61b99e6..96aa3e20 100644 --- a/searchPages/searchUtils/newSearchPage.component.ts +++ b/searchPages/searchUtils/newSearchPage.component.ts @@ -982,7 +982,7 @@ export class NewSearchPageComponent implements OnInit, OnDestroy, OnChanges { doisParams += (doisParams.length > 0 ? " or " : "") + '(authorid="' + StringUtils.URIEncode(identifier.id) + '")'; // doisParams += (doisParams.length > 0 ? " or " : "") +'(authorid="' + StringUtils.URIEncode(identifier.id) + '" and (authoridtype exact "orcid"))'; } else { - doisParams += (doisParams.length > 0 ? " or " : "") + '(pidclassid exact "' + identifier.class + '" and pid="' + StringUtils.URIEncode(identifier.id) + '")'; + doisParams += (doisParams.length > 0 ? " or " : "") + '(pid="' + StringUtils.URIEncode(identifier.id) + '")'; // doisParams += (doisParams.length > 0 ? " or " : "") +'(pidclassid exact "' + identifier.class + '" and pid="' + identifier.id + '")'; } } From 5184ffad194e23864411f989f9403cd984afb4a3 Mon Sep 17 00:00:00 2001 From: "konstantina.galouni" Date: Mon, 30 Oct 2023 11:22:20 +0200 Subject: [PATCH 41/48] [angular-16 | DONE | ADDED] env-properties.ts: Added "faircore4eosc" in type Dashboard and type PortalType. --- utils/properties/env-properties.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/properties/env-properties.ts b/utils/properties/env-properties.ts index 4a2ccd68..65931eaa 100644 --- a/utils/properties/env-properties.ts +++ b/utils/properties/env-properties.ts @@ -1,6 +1,6 @@ export type Environment = "development" | "test" | "beta" | "production"; -export type Dashboard = "explore" | "connect" | "monitor" | "aggregator" | "eosc" | "developers"; -export type PortalType = "explore" | "connect" | "community" | "monitor" | "funder" | "ri" | "project" | "organization" | "aggregator" | "eosc"; +export type Dashboard = "explore" | "connect" | "monitor" | "aggregator" | "eosc" | "developers" | "faircore4eosc"; +export type PortalType = "explore" | "connect" | "community" | "monitor" | "funder" | "ri" | "project" | "organization" | "aggregator" | "eosc" | "faircore4eosc"; export interface EnvProperties { environment?: Environment; From 5af67f738570647ee61fb4d9ccb211bfae2890ae Mon Sep 17 00:00:00 2001 From: "k.triantafyllou" Date: Mon, 30 Oct 2023 17:58:54 +0200 Subject: [PATCH 42/48] [develop | FIXED]: Fixed width of indicators page in admin. --- monitor-admin/topic/topic.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor-admin/topic/topic.component.html b/monitor-admin/topic/topic.component.html index 8197b19a..e11e98c5 100644 --- a/monitor-admin/topic/topic.component.html +++ b/monitor-admin/topic/topic.component.html @@ -190,7 +190,7 @@
-
+