From fc0fdd2e5105d7256a229fd36b7455d9edfc466f Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Tue, 8 Feb 2022 14:16:04 +0100 Subject: [PATCH] bugfix, Permission Link display correctly --- .../add-permission-line.component.ts | 2 +- .../filter-complete.pipe.ts | 4 +- .../path-auto-complete.component.ts | 5 +- .../display-path.pipe.ts | 2 +- .../permission-edit-line.component.ts | 2 +- .../permissions-management.component.ts | 5 +- .../permission-editor.component.ts | 13 +-- .../ApiInformation/ApiInformationCache.ts | 43 +++++++ .../ApiInformation/GetObjectIdHelper.ts | 106 ++++++++++++++++++ src/app/services/ApiInformation/IApiData.ts | 6 + .../services/ApiInformation/IExtraParams.ts | 4 + .../ApiInformation/IGenericApiObject.ts | 4 + .../api-information.service.spec.ts | 0 .../api-information.service.ts | 93 ++++----------- 14 files changed, 202 insertions(+), 87 deletions(-) create mode 100644 src/app/services/ApiInformation/ApiInformationCache.ts create mode 100644 src/app/services/ApiInformation/GetObjectIdHelper.ts create mode 100644 src/app/services/ApiInformation/IApiData.ts create mode 100644 src/app/services/ApiInformation/IExtraParams.ts create mode 100644 src/app/services/ApiInformation/IGenericApiObject.ts rename src/app/services/{ => ApiInformation}/api-information.service.spec.ts (100%) rename src/app/services/{ => ApiInformation}/api-information.service.ts (70%) diff --git a/src/app/components/permissions-management/add-permission-line/add-permission-line.component.ts b/src/app/components/permissions-management/add-permission-line/add-permission-line.component.ts index 77e48c6d..aeeffbc2 100644 --- a/src/app/components/permissions-management/add-permission-line/add-permission-line.component.ts +++ b/src/app/components/permissions-management/add-permission-line/add-permission-line.component.ts @@ -12,7 +12,7 @@ */ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {Server} from "@models/server"; -import {ApiInformationService} from "@services/api-information.service"; +import {ApiInformationService} from "@services/ApiInformation/api-information.service"; import {Methods, Permission, PermissionActions} from "@models/api/permission"; import {PermissionsService} from "@services/permissions.service"; import {ToasterService} from "@services/toaster.service"; diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe.ts b/src/app/components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe.ts index d42c242f..e9c1f509 100644 --- a/src/app/components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe.ts +++ b/src/app/components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe.ts @@ -11,14 +11,14 @@ * Author: Sylvain MATHIEU, Elise LEBEAU */ import { Pipe, PipeTransform } from '@angular/core'; -import {IFormatedList} from "@services/api-information.service"; +import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject"; @Pipe({ name: 'filterComplete' }) export class FilterCompletePipe implements PipeTransform { - transform(value: IFormatedList[], searchText: string): IFormatedList[] { + transform(value: IGenericApiObject[], searchText: string): IGenericApiObject[] { if (!searchText || searchText === '') { return value; } return value.filter((v) => { diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.ts b/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.ts index 8ef82aad..2b145c5c 100644 --- a/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.ts +++ b/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.ts @@ -11,10 +11,11 @@ * Author: Sylvain MATHIEU, Elise LEBEAU */ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {ApiInformationService, IFormatedList} from "@services/api-information.service"; +import {ApiInformationService} from "@services/ApiInformation/api-information.service"; import {Server} from "@models/server"; import {PermissionPath} from "@components/permissions-management/add-permission-line/path-auto-complete/PermissionPath"; import {SubPath} from "@components/permissions-management/add-permission-line/path-auto-complete/SubPath"; +import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject"; @Component({ selector: 'app-path-auto-complete', @@ -28,7 +29,7 @@ export class PathAutoCompleteComponent implements OnInit { @Input() server: Server; path: PermissionPath = new PermissionPath(); values: string[] = []; - private completeData: { data: IFormatedList[]; key: string }; + private completeData: { data: IGenericApiObject[]; key: string }; public mode: 'SELECT' | 'COMPLETE' | undefined; completeField: string; diff --git a/src/app/components/permissions-management/display-path.pipe.ts b/src/app/components/permissions-management/display-path.pipe.ts index 4d660782..49260340 100644 --- a/src/app/components/permissions-management/display-path.pipe.ts +++ b/src/app/components/permissions-management/display-path.pipe.ts @@ -13,7 +13,7 @@ import {Pipe, PipeTransform} from '@angular/core'; import {map, switchMap} from "rxjs/operators"; import {forkJoin, Observable, of} from "rxjs"; -import {ApiInformationService} from "@services/api-information.service"; +import {ApiInformationService} from "@services/ApiInformation/api-information.service"; import {Server} from "@models/server"; @Pipe({ diff --git a/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.ts b/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.ts index ed9e7e55..c30839e6 100644 --- a/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.ts +++ b/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.ts @@ -13,7 +13,7 @@ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {Methods, Permission} from "@models/api/permission"; import {Server} from '@models/server'; -import {ApiInformationService} from "@services/api-information.service"; +import {ApiInformationService} from "@services/ApiInformation/api-information.service"; import {PermissionsService} from "@services/permissions.service"; import {ToasterService} from "@services/toaster.service"; import {MatDialog} from "@angular/material/dialog"; diff --git a/src/app/components/permissions-management/permissions-management.component.ts b/src/app/components/permissions-management/permissions-management.component.ts index d228a90d..f48e44e6 100644 --- a/src/app/components/permissions-management/permissions-management.component.ts +++ b/src/app/components/permissions-management/permissions-management.component.ts @@ -19,7 +19,8 @@ import {Permission} from "@models/api/permission"; import {AddPermissionLineComponent} from "@components/permissions-management/add-permission-line/add-permission-line.component"; import {ServerService} from "@services/server.service"; import {PageEvent} from "@angular/material/paginator"; -import {ApiInformationService, IFormatedList} from "@services/api-information.service"; +import {ApiInformationService} from "@services/ApiInformation/api-information.service"; +import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject"; @Component({ selector: 'app-permissions-management', @@ -33,7 +34,7 @@ export class PermissionsManagementComponent implements OnInit { newPermissionEdit = false; searchPermissions: any; pageEvent: PageEvent | undefined; - filteredOptions: IFormatedList[]; + filteredOptions: IGenericApiObject[]; options: string[] = []; @ViewChild('dynamic', { diff --git a/src/app/components/role-management/role-detail/permission-editor/permission-editor.component.ts b/src/app/components/role-management/role-detail/permission-editor/permission-editor.component.ts index 11fc9357..ffbe254a 100644 --- a/src/app/components/role-management/role-detail/permission-editor/permission-editor.component.ts +++ b/src/app/components/role-management/role-detail/permission-editor/permission-editor.component.ts @@ -10,15 +10,14 @@ * * Author: Sylvain MATHIEU, Elise LEBEAU */ -import {Component, Inject, Input, OnInit, Output} from '@angular/core'; -import {Role} from "@models/api/role"; +import {Component, Input, OnInit, Output, EventEmitter} from '@angular/core'; import {Server} from "@models/server"; import {Permission} from "@models/api/permission"; import {MatDialog} from "@angular/material/dialog"; import {PermissionEditorValidateDialogComponent} from "@components/role-management/role-detail/permission-editor/permission-editor-validate-dialog/permission-editor-validate-dialog.component"; -import { EventEmitter } from '@angular/core'; -import {ApiInformationService, IFormatedList} from "@services/api-information.service"; +import {ApiInformationService } from "@services/ApiInformation/api-information.service"; import {PageEvent} from "@angular/material/paginator"; +import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject"; @Component({ selector: 'app-permission-editor', @@ -30,7 +29,7 @@ export class PermissionEditorComponent implements OnInit { owned: Set; available: Set; searchPermissions: any; - filteredOptions: IFormatedList[]; + filteredOptions: IGenericApiObject[]; pageEventOwned: PageEvent | undefined; pageEventAvailable: PageEvent | undefined; @@ -106,10 +105,10 @@ export class PermissionEditorComponent implements OnInit { } get ownedArray() { - return Array.from(this.owned.values()) + return Array.from(this.owned.values()); } get availableArray() { - return Array.from(this.available.values()) + return Array.from(this.available.values()); } } diff --git a/src/app/services/ApiInformation/ApiInformationCache.ts b/src/app/services/ApiInformation/ApiInformationCache.ts new file mode 100644 index 00000000..b4f83d3e --- /dev/null +++ b/src/app/services/ApiInformation/ApiInformationCache.ts @@ -0,0 +1,43 @@ +import {IApiData} from "./IApiData"; +import {Server} from "../../models/server"; +import {IExtraParams} from "./IExtraParams"; +import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject"; + +export class ApiInformationCache { + + private cache = new Map(); + + + public update(server: Server, key: string, value: string, extraParams: IExtraParams[], data: IGenericApiObject[]) { + const dataKey = this.generateKey(server, key, value, extraParams); + this.cache.set(dataKey, {expired: Date.now() + 10000, data}); + } + + public get(server: Server, key: string, value: string, extraParams: IExtraParams[]): IGenericApiObject[] | undefined { + const dataKey = this.generateKey(server, key, value, extraParams); + const data = this.cache.get(dataKey); + if (data) { + if (data.expired > Date.now()) { + return data.data; + } + } + + } + + private generateKey(server: Server, key: string, value: string, extraParams: IExtraParams[]) { + return `${server.id}-${key}-${value}-${extraParams.map(param => `${param.key}+${param.value}`).join('.')}`; + } + + searchByName(name: string) { + const result: IGenericApiObject[] = []; + this.cache.forEach((apiData: IApiData) => { + apiData.data.forEach((value: IGenericApiObject) => { + if (value.name.includes(name)) { + result.push(value); + } + }); + }); + + return result; + } +} diff --git a/src/app/services/ApiInformation/GetObjectIdHelper.ts b/src/app/services/ApiInformation/GetObjectIdHelper.ts new file mode 100644 index 00000000..8b5c0129 --- /dev/null +++ b/src/app/services/ApiInformation/GetObjectIdHelper.ts @@ -0,0 +1,106 @@ +import {ApiInformationService, IApiObject} from "@services/ApiInformation/api-information.service"; +import {Server} from "@models/server"; +import {IExtraParams} from "@services/ApiInformation/IExtraParams"; +import {forkJoin, Observable, of} from "rxjs"; +import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject"; +import {map} from "rxjs/operators"; + +export class GetObjectIdHelper { + + public static getIdNameFromKey(key: string): string { + return /{([^)]+)}/.exec(key)[1]; + } + + public static findElementInObjectListFn(key): (data: IApiObject[]) => IApiObject { + return function findElement(data: IApiObject[]): IApiObject { + const elem = data.find(d => d.name === key); + if (!elem) { + throw new Error('entry not found'); + } + return elem; + }; + } + + public static buildRequestURL(server: Server, value: string, extraParams: IExtraParams[]): (elem) => string { + return (elem): string => { + let url = `${server.protocol}//${server.host}:${server.port}${elem.path}`; + if (extraParams) { + extraParams.forEach((param) => { + url = url.replace(param.key, param.value); + }); + } + + if (value) { + url = `${url}/${value}`; + } + return url; + }; + } + + public static createResponseObject(key: string, + extraParams: IExtraParams[], + service: ApiInformationService, + server: Server + ): (response) => Observable { + + const idName = key ? GetObjectIdHelper.getIdNameFromKey(key) : undefined; + return (response): Observable => { + + if (!(response instanceof Array)) { + response = [response]; + } + if (response.length === 0) { + return of([]); + } + + /* + specific treatment for link_id + */ + if (key === '{link_id}') { + return GetObjectIdHelper.setLinkObjectInformation(response, extraParams, service, server); + } else { + return GetObjectIdHelper.setGenericObjectInformation(response, idName); + } + }; + } + + private static setGenericObjectInformation(response: any[], idName: string): Observable { + const keys = Object.keys(response[0]); + const idKey = keys.find(k => k.match(/_id$|filename/)); + const nameKey = keys.find(k => k.match(/name/)); + response = response.map(o => { + return { + id: o[idName] || o[idKey], + name: o[nameKey] + }; + }); + return of(response); + } + + private static setLinkObjectInformation(links: any[], + extraParams: IExtraParams[], + service: ApiInformationService, + server: Server + ): Observable { + + return forkJoin(links.map(link => GetObjectIdHelper.getLinkInformation(link, extraParams, service, server))); + } + + private static getLinkInformation(link: any, + extraParams: IExtraParams[], + service: ApiInformationService, + server: Server + ): Observable { + + const nodesDataObs = link.nodes.map(node => service.getListByObjectId(server, '{node_id}', node.node_id, extraParams)); + return forkJoin(nodesDataObs) + .pipe(map((nodes: [any]) => { + const name = nodes + .reduce((acc, val) => acc.concat(val), []) + .map(node => node.name) + .join(' <-> '); + + return {id: link.link_id, name}; + })); + } +} diff --git a/src/app/services/ApiInformation/IApiData.ts b/src/app/services/ApiInformation/IApiData.ts new file mode 100644 index 00000000..b4b2505e --- /dev/null +++ b/src/app/services/ApiInformation/IApiData.ts @@ -0,0 +1,6 @@ +import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject"; + +export interface IApiData { + expired: number; + data: IGenericApiObject[]; +} diff --git a/src/app/services/ApiInformation/IExtraParams.ts b/src/app/services/ApiInformation/IExtraParams.ts new file mode 100644 index 00000000..9d0a12d1 --- /dev/null +++ b/src/app/services/ApiInformation/IExtraParams.ts @@ -0,0 +1,4 @@ +export interface IExtraParams { + key: string; + value: string; +} diff --git a/src/app/services/ApiInformation/IGenericApiObject.ts b/src/app/services/ApiInformation/IGenericApiObject.ts new file mode 100644 index 00000000..5ae03d34 --- /dev/null +++ b/src/app/services/ApiInformation/IGenericApiObject.ts @@ -0,0 +1,4 @@ +export interface IGenericApiObject { + id: string; + name?: string; +} diff --git a/src/app/services/api-information.service.spec.ts b/src/app/services/ApiInformation/api-information.service.spec.ts similarity index 100% rename from src/app/services/api-information.service.spec.ts rename to src/app/services/ApiInformation/api-information.service.spec.ts diff --git a/src/app/services/api-information.service.ts b/src/app/services/ApiInformation/api-information.service.ts similarity index 70% rename from src/app/services/api-information.service.ts rename to src/app/services/ApiInformation/api-information.service.ts index d5b8030e..95cfbc8c 100644 --- a/src/app/services/api-information.service.ts +++ b/src/app/services/ApiInformation/api-information.service.ts @@ -13,10 +13,14 @@ import {Injectable} from '@angular/core'; import {HttpClient} from "@angular/common/http"; import {Observable, of, ReplaySubject} from "rxjs"; -import {map, switchMap, take} from "rxjs/operators"; -import {Methods} from "@models/api/permission"; -import {HttpServer} from "@services/http-server.service"; -import {Server} from "@models/server"; +import {map, switchMap, take, tap} from "rxjs/operators"; +import {Methods} from "app/models/api/permission"; +import {HttpServer} from "app/services/http-server.service"; +import {Server} from "app/models/server"; +import {GetObjectIdHelper} from "@services/ApiInformation/GetObjectIdHelper"; +import {IExtraParams} from "@services/ApiInformation/IExtraParams"; +import {ApiInformationCache} from "@services/ApiInformation/ApiInformationCache"; +import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject"; export interface IPathDict { methods: ('POST' | 'GET' | 'PUT' | 'DELETE' | 'HEAD' | 'PATH')[]; @@ -35,18 +39,12 @@ export interface IQueryObject { text: string[]; } -export interface IFormatedList { - id: string; - name?: string; -} - - @Injectable({ providedIn: 'root' }) export class ApiInformationService { - private cache_permissions: { [key: string]: IFormatedList; } = {}; + private cache = new ApiInformationCache(); private allowed = ['projects', 'images', 'templates', 'computes', 'symbols', 'notifications']; private data: ReplaySubject = new ReplaySubject(1); private objs: ReplaySubject = new ReplaySubject(1); @@ -206,71 +204,24 @@ export class ApiInformationService { })); } - getListByObjectId(server: Server, key: string, value?: string, extraParams?: { key: string; value: string }[]) { - const idName = /{([^)]+)}/.exec(key)[1]; - function findElement(data: IApiObject[]): IApiObject { - const elem = data.find(d => d.name === key); - if (!elem) { - throw new Error('entry not found'); - } - return elem; + getListByObjectId(server: Server, key: string, value?: string, extraParams?: IExtraParams[]) { + + const cachedData = this.cache.get(server, key, value, extraParams); + if (cachedData) { + return of(cachedData); } return this.objs.pipe( - map(findElement), - switchMap(elem => { - let url = `${server.protocol}//${server.host}:${server.port}${elem.path}`; - if (extraParams) { - extraParams.forEach((param) => { - url = url.replace(param.key, param.value); - }); - } - - if (value) { - url = `${url}/${value}`; - } - - return this.httpClient.get(url, {headers: {Authorization: `Bearer ${server.authToken}`}}); - } - ), - switchMap(response => { - - if (response instanceof Array) { - if (response.length === 0) { - return of([]); - } - const keys = Object.keys(response[0]); - const idKey = keys.find(k => k.match(/_id$|filename/)); - const nameKey = keys.find(k => k.match(/name/)); - response = response.map(o => { - return { - id: o[idName] || o[idKey], - name: o[nameKey] - }; - }); - response.forEach(elt => { - this.cache_permissions[elt.id] = elt; - }); - return of(response); - } else { - const keys = Object.keys(response); - const idKey = keys.find(k => k.match(/_id$|filename/)); - const nameKey = keys.find(k => k.match(/name/)); - const ret = {id: response[idName] || response[idKey], name: response[nameKey]}; - this.cache_permissions[ret.id] = ret; - return of([ret]); - } - }), take(1)); + map(GetObjectIdHelper.findElementInObjectListFn(key)), + map(GetObjectIdHelper.buildRequestURL(server, value, extraParams)), + switchMap(url => this.httpClient.get(url, {headers: {Authorization: `Bearer ${server.authToken}`}})), + switchMap(GetObjectIdHelper.createResponseObject(key, extraParams, this, server)), + tap(data => this.cache.update(server, key, value, extraParams, data)), + take(1)); } - getIdByObjNameFromCache(name: string): IFormatedList[] { - const ret: IFormatedList[] = []; - for (let [key, value] of Object.entries(this.cache_permissions)) { - if (value.name.includes(name)) { - ret.push(value); - } - } - return ret; + getIdByObjNameFromCache(name: string): IGenericApiObject[] { + return this.cache.searchByName(name); } }