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({ diff --git a/claims/claim-utils/claimContextSearchForm.component.ts b/claims/claim-utils/claimContextSearchForm.component.ts index d48540e8..bed48c8f 100644 --- a/claims/claim-utils/claimContextSearchForm.component.ts +++ b/claims/claim-utils/claimContextSearchForm.component.ts @@ -2,13 +2,12 @@ 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 {CommunitiesService} from "../../connect/communities/communities.service"; import {UserManagementService} from "../../services/user-management.service"; +import {Session} from "../../login/utils/helper.class"; declare var UIkit: any; @@ -183,10 +182,6 @@ export class ClaimContextSearchFormComponent { } getCommunities() { - if (!Session.isLoggedIn()) { - this.saveStateAndRedirectLogin(); - - } else { this.loading = true; this.subscriptions.push(this._contextService.getCommunitiesByState().subscribe( data => { @@ -220,18 +215,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){ @@ -259,7 +250,7 @@ export class ClaimContextSearchFormComponent { } )); } - } + } displaySubcategory(id) { @@ -272,9 +263,6 @@ export class ClaimContextSearchFormComponent { } browseConcepts(categoryId) { - if (!Session.isLoggedIn()) { - this.saveStateAndRedirectLogin(); - } else { if (this.conceptsClass[categoryId] != null) { this.conceptsClassDisplay[categoryId] = !this.conceptsClassDisplay[categoryId]; return; @@ -302,8 +290,6 @@ export class ClaimContextSearchFormComponent { this.conceptsCategoryLoading[categoryId] = false; } )); - } - } browseSubConcepts(categoryId, conceptId) { @@ -325,24 +311,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/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/claims/claim-utils/service/claims.service.ts b/claims/claim-utils/service/claims.service.ts index b9f32054..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,40 +66,27 @@ 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)); } + getStatus(jobId, apiUrl:string):any{ + + let url = apiUrl +"jobStatus/" + jobId; + + return this.http.get(url,CustomOptions.getAuthOptionsWithBody()) + .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 @@ -113,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 cb1cef70..1a2ec67f 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,19 @@ import {Subscriber} from "rxjs";
CONFIRM LINKING
- + + +
+
+ Initiating process....
+
+ {{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...
+
+

All the links you provided will be published in the OpenAIRE platform.
@@ -60,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 = {}; @@ -84,15 +123,15 @@ export class ClaimInsertComponent { private errorInClaims: ClaimRecord2Insert[] = []; private insertedRecords = []; private errorInRecords = []; - + 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, @@ -177,12 +216,15 @@ 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.insertedRecords = data.insertedIds; - - this.errorInRecords = data.errorInClaims; + this.feedRecordsJob = data.data; + this.records2Insert = directclaims.length; + console.log(data); + // this.insertedRecords = data.insertedIds; + // + // this.errorInRecords = data.errorInClaims; this.isertBulkClaims(claims); }, err => { @@ -190,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); @@ -209,30 +251,18 @@ 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(); + 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 + }) + ).subscribe(); + this.subscriptions.push(timerSubscription); }, err => { err = err && err.error?err.error:err; @@ -335,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}); } } @@ -517,8 +550,77 @@ export class ClaimInsertComponent { } if(this.defaultColors){ buttonClass+=" linksbaskettitles uk-padding-small "; - } + } return buttonClass + "uk-text-center "; } + + getStatus(){ + 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 = "jobError"; + this.createErrorMessagesPerEntity((this.insertedClaims.length == 0)); + this.errors.push(error); + this.afterclaimsInsertion(); + this.feedRecordsJob = null; + localStorage.removeItem(this.localStoragePrefix + "feedRecordsJob"); + } + + )); + } + 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 || !(this.feedRecordsJob.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(); + + + } + }, 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.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/claims/linking/linkingGeneric.component.ts b/claims/linking/linkingGeneric.component.ts index 524f3566..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}); } @@ -94,6 +101,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'; } + } } } @@ -206,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 + } + }); + } } 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 +
+
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; } 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/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/layout.service.ts b/dashboard/sharedComponents/sidebar/layout.service.ts index 39a2eaa8..02754405 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[] = []; @@ -127,7 +131,7 @@ export class LayoutService { data['hasHeader'] === false) { this.setHasHeader(false); if (typeof document !== "undefined") { - document.documentElement.style.setProperty('--header-height', '0'); + document.documentElement.style.setProperty('--header-height', '0px'); } } else { this.setHasHeader(true); @@ -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/dashboard/sharedComponents/sidebar/sideBar.component.html b/dashboard/sharedComponents/sidebar/sideBar.component.html index 9bba0b18..9e772ea8 100644 --- a/dashboard/sharedComponents/sidebar/sideBar.component.html +++ b/dashboard/sharedComponents/sidebar/sideBar.component.html @@ -1,8 +1,8 @@ - +
+ uk-sticky="end: true;" [attr.offset]="graph_offset">
@@ -269,7 +271,7 @@
+ uk-sticky="end: true; media: @m" [attr.offset]="offset">
-
+
@@ -360,11 +362,11 @@ -
-
+
-
    +
    • - {{item.title}} + + {{item.title}} {{item.title}} + [target]="item.target">{{item.title}}
    • diff --git a/login/utils/helper.class.ts b/login/utils/helper.class.ts index 63e15b2f..8fdd6229 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 (type === 'community' || stakeholderTypes.find(stakeholderType => stakeholderType.value == type)) && this.isTypeCurator(type, user); } public static isPortalAdministrator(user: User): boolean { 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..f84f197d --- /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 "../../../monitor/entities/stakeholder"; +import {UntypedFormBuilder, UntypedFormGroup, Validators} from "@angular/forms"; +import {StakeholderUtils} from "../../utils/indicator-utils"; +import {Option} from "../../../sharedComponents/input/input.component"; +import {Subscription} from "rxjs"; +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', + 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..0ebec4d3 --- /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 "../../../sharedComponents/input/input.module"; +import {ReactiveFormsModule} from "@angular/forms"; +import {IconsModule} from "../../../utils/icons/icons.module"; +import {NotifyFormModule} from "../../../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..e0486667 --- /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 '../../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..611032b4 --- /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 "../../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 "src/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..7f4e7064 --- /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 "../../utils/piwik/previousRouteRecorder.guard"; +import {CommonModule} from "@angular/common"; +import {RouterModule} from "@angular/router"; +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 "../../dashboard/sharedComponents/page-content/page-content.module"; +import {LogoUrlPipeModule} from "../../utils/pipes/logoUrlPipe.module"; +import { + SidebarMobileToggleModule +} from "../../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/manageStakeholders/manageStakeholders-routing.module.ts b/monitor-admin/manageStakeholders/manageStakeholders-routing.module.ts new file mode 100644 index 00000000..03c01a41 --- /dev/null +++ b/monitor-admin/manageStakeholders/manageStakeholders-routing.module.ts @@ -0,0 +1,18 @@ +import {NgModule} from '@angular/core'; +import {RouterModule} from '@angular/router'; +import {PreviousRouteRecorder} from '../../utils/piwik/previousRouteRecorder.guard'; +import {ManageStakeholdersComponent} from "./manageStakeholders.component"; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + component: ManageStakeholdersComponent, + canDeactivate: [PreviousRouteRecorder] + } + ]) + ] +}) +export class ManageStakeholdersRoutingModule { +} diff --git a/monitor-admin/manageStakeholders/manageStakeholders.component.html b/monitor-admin/manageStakeholders/manageStakeholders.component.html new file mode 100644 index 00000000..5b981cde --- /dev/null +++ b/monitor-admin/manageStakeholders/manageStakeholders.component.html @@ -0,0 +1,138 @@ +
      +
      + +
      + + + + + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +

      Profile Templates

      +
      + + + +
      + +
      +
      +
      +
      +

      + No profiles to manage yet +

      +
      +
      +

      Profiles

      +
      + + + +
      + +
      +
      +
      +
      +
      +
      +
      + + + + + + +
      +
      + {{text}} +
      +
      + + + +
      +
      +
      +
      + +
      + +
      +
      + +
      +
      + +
      + +
      +
      + This stakeholder will permanently be deleted. Are you sure you want to proceed? +
      +
      diff --git a/monitor-admin/manageStakeholders/manageStakeholders.component.less b/monitor-admin/manageStakeholders/manageStakeholders.component.less new file mode 100644 index 00000000..ee701579 --- /dev/null +++ b/monitor-admin/manageStakeholders/manageStakeholders.component.less @@ -0,0 +1,23 @@ +@import (reference) "~src/assets/openaire-theme/less/color.less"; + +.setType(@color) { + border-bottom: 4px solid fade(@color, 30%); + + & .type { + color: @color; + } +} + +.uk-card { + &.funder { + .setType(@funder-color); + } + + &.ri { + .setType(@ri-color); + } + + &.organization { + .setType(@organization-color); + } +} diff --git a/monitor-admin/manageStakeholders/manageStakeholders.component.ts b/monitor-admin/manageStakeholders/manageStakeholders.component.ts new file mode 100644 index 00000000..5e637b3e --- /dev/null +++ b/monitor-admin/manageStakeholders/manageStakeholders.component.ts @@ -0,0 +1,311 @@ +import {Component, OnDestroy, OnInit, ViewChild} from "@angular/core"; +import {StakeholderService} from "../../monitor/services/stakeholder.service"; +import {EnvProperties} from "../../utils/properties/env-properties"; +import {Stakeholder, StakeholderEntities, Visibility} from "../../monitor/entities/stakeholder"; +import {Subscriber, zip} from "rxjs"; +import {StakeholderUtils} from "../utils/indicator-utils"; +import {UntypedFormBuilder, UntypedFormGroup} from "@angular/forms"; +import {AlertModal} from "../../utils/modal/alert"; +import {Option} from "../../sharedComponents/input/input.component"; +import {Title} from "@angular/platform-browser"; +import {UserManagementService} from "../../services/user-management.service"; +import {Session} from "../../login/utils/helper.class"; +import {EditStakeholderComponent} from "../general/edit-stakeholder/edit-stakeholder.component"; +import {properties} from "src/environments/environment"; +import {ActivatedRoute} from "@angular/router"; +import {CacheIndicatorsService} from "../utils/cache-indicators/cache-indicators.service"; +import {NotificationHandler} from "../../utils/notification-handler"; + +type Tab = 'all' | 'templates'| 'profiles'; + +declare var UIkit; + +@Component({ + selector: 'home', + templateUrl: "./manageStakeholders.component.html", + styleUrls: ["./manageStakeholders.component.less"] +}) +export class ManageStakeholdersComponent implements OnInit, OnDestroy { + + public properties: EnvProperties; + public loading: boolean = true; + public deleteLoading: boolean = false; + public stakeholderUtils: StakeholderUtils = new StakeholderUtils(); + public defaultStakeholders: Stakeholder[]; + public stakeholders: Stakeholder[]; + public alias: string[]; + public stakeholder: Stakeholder; + public index: number; + public user = null; + public tab: Tab = 'all'; + /** + * Filtered Stakeholders + */ + public displayDefaultStakeholders: Stakeholder[]; + public displayStakeholders: Stakeholder[]; + /** + * Top filters + */ + public filters: UntypedFormGroup; + public all: Option = { + value: 'all', + label: 'All' + }; + + public callback: Function; + + /** + * Grid or List View + */ + private subscriptions: any[] = []; + @ViewChild('editStakeholderModal', { static: true }) editStakeholderModal: AlertModal; + @ViewChild('deleteStakeholderModal', { static: true }) deleteStakeholderModal: AlertModal; + @ViewChild('editStakeholderComponent', { static: true }) editStakeholderComponent: EditStakeholderComponent; + + constructor(private stakeholderService: StakeholderService, + private cacheIndicatorsService: CacheIndicatorsService, + private userManagementService: UserManagementService, + private route: ActivatedRoute, + private title: Title, + private fb: UntypedFormBuilder) { + } + + ngOnInit(): void { + this.buildFilters(); + this.properties = properties; + this.title.setTitle('Manage profiles'); + this.subscriptions.push(this.userManagementService.getUserInfo().subscribe(user => { + this.user = user; + })); + let data = zip( + this.stakeholderService.getDefaultStakeholders(this.properties.monitorServiceAPIURL), + this.stakeholderService.getMyStakeholders(this.properties.monitorServiceAPIURL), + this.stakeholderService.getAlias(this.properties.monitorServiceAPIURL) + ); + this.subscriptions.push(data.subscribe(res => { + this.defaultStakeholders = res[0]; + this.stakeholders = res[1]; + this.displayDefaultStakeholders = res[0]; + this.displayStakeholders = res[1]; + this.alias = res[2]; + this.loading = false; + }, error => { + this.loading = false; + })); + } + + ngOnDestroy(): void { + this.subscriptions.forEach(value => { + if (value instanceof Subscriber) { + value.unsubscribe(); + } else if (value instanceof Function) { + value(); + } + }); + } + + hide(element: any) { + UIkit.dropdown(element).hide(); + } + + + private buildFilters() { + this.filters = this.fb.group({ + status: this.fb.control('all'), + keyword: this.fb.control('') + }); + this.subscriptions.push(this.filters.get('status').valueChanges.subscribe(value => { + this.onStatusChange(value); + })); + this.subscriptions.push(this.filters.get('keyword').valueChanges.subscribe(value => { + this.onKeywordChange(value); + })); + } + + onStatusChange(value) { + this.displayDefaultStakeholders = this.filterStatus(this.defaultStakeholders, value); + this.displayStakeholders = this.filterStatus(this.stakeholders, value); + } + + onKeywordChange(value) { + this.displayDefaultStakeholders = this.filterByKeyword(this.defaultStakeholders, value); + this.displayStakeholders = this.filterByKeyword(this.stakeholders, value); + } + + private filterStatus(stakeholders: Stakeholder[], value): Stakeholder[] { + if (value === 'all') { + return stakeholders; + } else { + return stakeholders.filter(stakeholder => stakeholder.visibility == value); + } + } + + private filterByKeyword(stakeholders: Stakeholder[], value): Stakeholder[] { + if (!value) { + return stakeholders; + } else { + return stakeholders.filter(stakeholder => + stakeholder.name && stakeholder.name.toLowerCase().includes(value.toLowerCase()) || + stakeholder.type && stakeholder.type.toLowerCase().includes(value.toLowerCase()) || + stakeholder.index_id && stakeholder.index_id.toLowerCase().includes(value.toLowerCase()) || + stakeholder.index_shortName && stakeholder.index_shortName.toLowerCase().includes(value.toLowerCase()) || + stakeholder.index_name && stakeholder.index_name.toLowerCase().includes(value.toLowerCase()) + ); + } + } + + public editStakeholder(stakeholder: Stakeholder = null, isDefault: boolean = false) { + if (isDefault) { + this.index = (stakeholder) ? this.defaultStakeholders.findIndex(value => value._id === stakeholder._id) : -1; + } else { + this.index = (stakeholder) ? this.stakeholders.findIndex(value => value._id === stakeholder._id) : -1; + } + if (!stakeholder) { + this.stakeholder = new Stakeholder(null, null, null, + null, null, null, null, null); + } else { + this.stakeholder = stakeholder; + } + this.editStakeholderComponent.init(this.stakeholder, this.alias, this.defaultStakeholders, isDefault, this.index === -1); + if (this.index !== -1) { + this.callback = (stakeholder: Stakeholder) => { + let index: number; + if (stakeholder.defaultId == null) { + index = this.alias.findIndex(value => value == this.defaultStakeholders[this.index].alias); + this.defaultStakeholders[this.index] = stakeholder; + } else { + index = this.alias.findIndex(value => value == this.stakeholders[this.index].alias); + this.stakeholders[this.index] = stakeholder; + } + if(index != -1) { + this.alias[index] = stakeholder.alias; + } + this.editStakeholderModal.cancel(); + }; + this.editStakeholderModal.alertTitle = 'Edit ' + this.stakeholder.name; + this.editStakeholderModal.okButtonText = 'Save Changes'; + } else { + this.callback = (stakeholder: Stakeholder) => { + if (stakeholder.defaultId === null) { + this.defaultStakeholders.push(stakeholder); + } else { + this.stakeholders.push(stakeholder); + } + this.alias.push(stakeholder.alias); + this.editStakeholderModal.cancel(); + }; + this.editStakeholderModal.alertTitle = 'Create a new ' + (isDefault?'Default ':'') + 'Profile'; + this.editStakeholderModal.okButtonText = 'Create'; + } + this.editStakeholderModal.cancelButtonText = 'Cancel'; + this.editStakeholderModal.okButtonLeft = false; + this.editStakeholderModal.alertMessage = false; + this.editStakeholderModal.stayOpen = true; + this.editStakeholderModal.open(); + } + + public createReport(stakeholder: Stakeholder) { + this.cacheIndicatorsService.createReport(stakeholder.alias).subscribe(report => { + NotificationHandler.rise('A caching process for ' + stakeholder.name + ' has been started.' ) + }, error => { + console.log(error); + NotificationHandler.rise(error.message(), 'danger'); + }); + } + + public deleteStakeholderOpen(stakeholder: Stakeholder) { + this.stakeholder = stakeholder; + this.deleteStakeholderModal.alertTitle = 'Delete ' + this.stakeholder.index_name; + this.deleteStakeholderModal.cancelButtonText = 'No'; + this.deleteStakeholderModal.okButtonText = 'Yes'; + this.deleteStakeholderModal.alertMessage = false; + this.deleteStakeholderModal.stayOpen = true; + this.deleteStakeholderModal.open(); + } + + public deleteStakeholder() { + this.deleteLoading = true; + if (!this.stakeholder.defaultId) { + this.index = (this.stakeholder) ? this.defaultStakeholders.findIndex(value => value._id === this.stakeholder._id) : -1; + } else { + this.index = (this.stakeholder) ? this.stakeholders.findIndex(value => value._id === this.stakeholder._id) : -1; + } + this.subscriptions.push(this.stakeholderService.deleteElement(this.properties.monitorServiceAPIURL, [this.stakeholder._id]).subscribe(() => { + UIkit.notification(this.stakeholder.name+ ' has been successfully deleted', { + status: 'success', + timeout: 6000, + pos: 'bottom-right' + }); + if (!this.stakeholder.defaultId) { + this.defaultStakeholders.splice(this.index, 1); + } else { + this.stakeholders.splice(this.index, 1); + } + this.alias = this.alias.filter(item => item !== this.stakeholder.alias); + this.deleteLoading = false; + this.deleteStakeholderModal.cancel(); + }, error => { + UIkit.notification('An error has occurred. Please try again later', { + status: 'danger', + timeout: 6000, + pos: 'bottom-right' + }); + this.deleteLoading = false; + this.deleteStakeholderModal.cancel(); + })); + } + + changeStakeholderStatus(stakeholder: Stakeholder, visibility: Visibility) { + let path = [ + stakeholder._id + ]; + this.subscriptions.push(this.stakeholderService.changeVisibility(this.properties.monitorServiceAPIURL, path, visibility).subscribe(returnedElement => { + stakeholder.visibility = returnedElement.visibility; + UIkit.notification(stakeholder.name+ '\'s status has been successfully changed to ' + stakeholder.visibility.toLowerCase(), { + 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' + }); + })); + } + + public isManager(): boolean { + return this.isCurator() || (Session.isKindOfMonitorManager(this.user)); + } + + public isProfileManager(stakeholder: Stakeholder): boolean { + return this.isCurator() || (Session.isManager(stakeholder.type, stakeholder.alias, this.user)); + } + + public isCurator(): boolean { + return this.isAdmin() || Session.isMonitorCurator(this.user); + } + + public isAdmin(): boolean { + return Session.isPortalAdministrator(this.user); + } + + get typesAsString() { + return this.stakeholderUtils.types.slice(0, this.stakeholderUtils.types.length - 1).map(type => type.label).join(', ') + + ' or ' + this.stakeholderUtils.types[this.stakeholderUtils.types.length - 1].label + } + + private isTab(tab: Tab): boolean { + switch (tab) { + case "all": + return true; + case "profiles": + return true; + case "templates": + return true; + default: + return false; + } + } +} diff --git a/monitor-admin/manageStakeholders/manageStakeholders.module.ts b/monitor-admin/manageStakeholders/manageStakeholders.module.ts new file mode 100644 index 00000000..41847068 --- /dev/null +++ b/monitor-admin/manageStakeholders/manageStakeholders.module.ts @@ -0,0 +1,50 @@ +import {NgModule} from "@angular/core"; +import {ManageStakeholdersComponent} from "./manageStakeholders.component"; +import {ManageStakeholdersRoutingModule} from "./manageStakeholders-routing.module"; +import {PreviousRouteRecorder} from "../../utils/piwik/previousRouteRecorder.guard"; +import {CommonModule} from "@angular/common"; +import {RouterModule} from "@angular/router"; +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 {IconsModule} from "../../utils/icons/icons.module"; +import {IconsService} from "../../utils/icons/icons.service"; +import {earth, incognito, restricted} from "../../utils/icons/icons"; +import {PageContentModule} from "../../dashboard/sharedComponents/page-content/page-content.module"; +import {LogoUrlPipeModule} from "../../utils/pipes/logoUrlPipe.module"; +import {SearchInputModule} from "../../sharedComponents/search-input/search-input.module"; +import { + SidebarMobileToggleModule +} from "../../dashboard/sharedComponents/sidebar/sidebar-mobile-toggle/sidebar-mobile-toggle.module"; +import {SliderTabsModule} from "../../sharedComponents/tabs/slider-tabs.module"; +import {EditStakeholderModule} from "../general/edit-stakeholder/edit-stakeholder.module"; + +@NgModule({ + declarations: [ManageStakeholdersComponent], + imports: [ + ManageStakeholdersRoutingModule, + CommonModule, + RouterModule, + InputModule, + LoadingModule, + AlertModalModule, + ReactiveFormsModule, + EditStakeholderModule, + IconsModule, + PageContentModule, + LogoUrlPipeModule, + SearchInputModule, + SidebarMobileToggleModule, + SliderTabsModule + ], + providers: [ + PreviousRouteRecorder, + ], + exports: [ManageStakeholdersComponent] +}) +export class ManageStakeholdersModule { + constructor(private iconsService: IconsService) { + this.iconsService.registerIcons([earth, incognito, restricted]); + } +} 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..e11e98c5 --- /dev/null +++ b/monitor-admin/topic/topic.component.html @@ -0,0 +1,383 @@ + + + +
      +
      + +
      + 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..57f65930 --- /dev/null +++ b/monitor-admin/topic/topic.component.ts @@ -0,0 +1,799 @@ +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 offset: number; + 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() { + 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(() => { + 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..4dae319c --- /dev/null +++ b/monitor-admin/utils/indicator-utils.ts @@ -0,0 +1,1002 @@ +import { + ChartHelper, + FilterType, + Indicator, + IndicatorFilterUtils, + IndicatorPath, + IndicatorPathType, + IndicatorType, + SourceType, + Stakeholder, + StakeholderEntities, + SubCategory, + Topic, stakeholderTypes, + 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[] = [ + ...stakeholderTypes + ]; + + 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/monitor/entities/stakeholder.ts b/monitor/entities/stakeholder.ts index 09ab7022..357f42ab 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: 'organization', label: StakeholderEntities.ORGANIZATION}, + {value: 'project', label: StakeholderEntities.PROJECT} +]; 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, diff --git a/monitor/methodology/terminology.component.ts b/monitor/methodology/terminology.component.ts index 0cf50f76..76908156 100644 --- a/monitor/methodology/terminology.component.ts +++ b/monitor/methodology/terminology.component.ts @@ -39,9 +39,9 @@ declare var ResizeObserver;
-
-
+
Powered by OpenAIRE graph @@ -50,7 +50,7 @@ declare var ResizeObserver;
-
+
@@ -83,9 +83,9 @@ declare var ResizeObserver;
-
-
+
Powered by OpenAIRE graph diff --git a/notifications/notifications-sidebar/notifications-sidebar.component.ts b/notifications/notifications-sidebar/notifications-sidebar.component.ts index 157a9db9..83cc74cb 100644 --- a/notifications/notifications-sidebar/notifications-sidebar.component.ts +++ b/notifications/notifications-sidebar/notifications-sidebar.component.ts @@ -27,12 +27,12 @@ export class NotificationConfiguration { @Component({ selector: 'notification-sidebar', template: ` - +
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/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/advancedSearchForm.component.html b/searchPages/searchUtils/advancedSearchForm.component.html index f0d88901..8019c91c 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/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)">
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 + '")'; } } 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/services/servicesUtils/customOptions.class.ts b/services/servicesUtils/customOptions.class.ts index 3f864990..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' @@ -16,17 +15,12 @@ export class CustomOptions { return { headers: new HttpHeaders({ 'Content-Type': 'application/json', - 'X-XSRF-TOKEN': COOKIE.getCookie(COOKIE.cookieName_id)?COOKIE.getCookie(COOKIE.cookieName_id):'' }), withCredentials: true }; } public static getAuthOptions():{} { - return { - headers: new HttpHeaders({ - 'X-XSRF-TOKEN': (COOKIE.getCookie(COOKIE.cookieName_id)) ? COOKIE.getCookie(COOKIE.cookieName_id) : '' - }), withCredentials: true - }; + return this.getAuthOptionsWithBody(); } -} \ No newline at end of file +} diff --git a/sharedComponents/bottom.component.html b/sharedComponents/bottom.component.html index 17f36d69..c978bf51 100644 --- a/sharedComponents/bottom.component.html +++ b/sharedComponents/bottom.component.html @@ -112,7 +112,7 @@
  • NOADs
  • Guides
  • FAQs
  • -
  • Webinars
  • +
  • Webinars
  • Ask a question
  • @@ -123,7 +123,7 @@ @@ -180,8 +180,8 @@
    - - + + Unless otherwise indicated, all materials created by OpenAIRE are licenced under CC ATTRIBUTION 4.0 INTERNATIONALLICENSE @@ -262,8 +262,8 @@
    - - + + Unless otherwise indicated, all materials created by OpenAIRE are licenced under
    diff --git a/sharedComponents/input/input.component.ts b/sharedComponents/input/input.component.ts index 296622bb..cfcae9d5 100644 --- a/sharedComponents/input/input.component.ts +++ b/sharedComponents/input/input.component.ts @@ -196,11 +196,14 @@ declare var UIkit;
    + uk-dropdown="pos: bottom-left; mode: none; flip: false ; shift: false" [attr.target]="'#' + id" [attr.boundary]="'#' + id" (click)="$event.stopPropagation()">
    + + +
    + uk-dropdown="mode: none; stretch: true; flip: false; shift: false" [attr.boundary]="'#' + id">
    • @@ -228,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) + diff --git a/sharedComponents/navigationBar.component.html b/sharedComponents/navigationBar.component.html index 3ebaf5d2..873c0cd9 100644 --- a/sharedComponents/navigationBar.component.html +++ b/sharedComponents/navigationBar.component.html @@ -1,5 +1,5 @@
    -