From 0ebeb21839d1dbb137f0e02219901039733aba18 Mon Sep 17 00:00:00 2001 From: Martina-Graeber-One-Identity <151505816+Martina-Graeber-One-Identity@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:18:29 +0200 Subject: [PATCH] Sync repo from v92 to v92 20241008054030 (#192) * Automated sync from source branch v92 * Readme updated and unchanged files reset * Update README.md --------- Co-authored-by: imx-sync-bot Co-authored-by: Martina Graeber (mgraeber) Co-authored-by: Hanno Bunjes --- .commit | 2 +- .sync-history | 1 - README.md | 6 + imxweb/imx-modules/imx-api-qbm.tgz | Bin 70381 -> 70432 bytes imxweb/imx-modules/imx-api-rms.tgz | Bin 22920 -> 21498 bytes .../application-navigation.component.ts | 4 +- .../lib/applications/applications.service.ts | 11 +- .../aob/src/lib/shops/shops.service.ts | 2 +- .../src/lib/software/software.component.ts | 21 +- .../apc/src/lib/software/software.service.ts | 16 +- .../attestation-history.component.ts | 7 +- .../attestation-history.service.ts | 34 ++- .../lib/decision/attestation-cases.service.ts | 43 ++- .../attestation-decision.component.ts | 98 +++--- .../pick-category-sidesheet.component.html | 21 +- .../pick-category-sidesheet.component.ts | 58 ++-- .../pick-category.component.html | 2 +- .../pick-category/pick-category.component.ts | 110 +++---- .../pick-category/pick-category.service.ts | 114 ++++--- .../policy-list/policy-list.component.ts | 49 ++- .../att/src/lib/policies/policy.service.ts | 20 +- .../policy-group-list.component.ts | 1 + .../lib/policy-group/policy-group.service.ts | 112 ++++--- .../lib/runs/runs-grid/runs-grid.component.ts | 151 ++++++---- .../projects/att/src/lib/runs/runs.service.ts | 22 ++ .../rules-violations.component.html | 92 ++---- .../rules-violations.component.ts | 16 +- .../rules-violations.service.ts | 34 ++- .../violations-per-rule.component.html | 54 ++-- .../violations-per-rule.component.ts | 17 +- .../cpl/src/lib/rules/rules.component.html | 43 ++- .../cpl/src/lib/rules/rules.component.ts | 31 +- .../cpl/src/lib/rules/rules.service.ts | 39 ++- .../src/lib/policies/policies.component.html | 2 +- .../src/lib/policies/policies.component.ts | 23 +- .../pol/src/lib/policies/policies.service.ts | 9 +- .../policy-violations.component.ts | 29 +- .../policy-violations.service.ts | 67 +++-- .../src/lib/cdr/edit-fk/edit-fk.component.ts | 89 +++--- .../projects/qbm/src/lib/cdr/editor-base.ts | 2 +- .../fk-selector.component.ts | 29 +- .../lib/addressbook/addressbook.component.ts | 15 +- .../lib/addressbook/addressbook.service.ts | 28 +- .../lib/delegation/delegation.component.ts | 23 +- .../lib/identities/identities.component.ts | 37 +-- .../src/lib/identities/identities.service.ts | 94 +++--- .../itshop-pattern-sidesheet.component.html | 2 +- .../itshop-pattern-sidesheet.component.ts | 114 +++---- .../itshop-pattern.component.html | 5 +- .../itshop-pattern.component.scss | 25 +- .../itshop-pattern.component.ts | 135 +++++---- .../itshop-pattern/itshop-pattern.service.ts | 114 ++++--- .../approvals-table.component.ts | 3 +- .../qer/src/lib/person/person.service.ts | 6 +- .../request-history.service.ts | 28 +- .../request-table.component.ts | 3 +- .../src/lib/resources/resources.service.ts | 281 +++++++++++------- .../src/lib/role-management/role.service.ts | 44 ++- .../roles-overview.component.ts | 25 +- .../service-items/service-items.service.ts | 111 ++++--- .../serviceitem-list.component.ts | 17 +- .../cart-item-edit/cart-item-fk.service.ts | 55 ++-- .../view-devices.component.ts | 103 +++---- .../lib/view-devices/view-devices.service.ts | 27 +- imxweb/projects/rmb/src/lib/init.service.ts | 62 ++-- imxweb/projects/rms/src/lib/init.service.ts | 34 ++- .../src/lib/accounts/accounts.component.ts | 50 ++-- .../tsb/src/lib/accounts/accounts.service.ts | 33 +- .../tsb/src/lib/groups/groups.component.ts | 56 ++-- .../tsb/src/lib/groups/groups.service.ts | 67 +++-- 70 files changed, 1787 insertions(+), 1291 deletions(-) diff --git a/.commit b/.commit index 107b89649..605ee0a5e 100644 --- a/.commit +++ b/.commit @@ -1 +1 @@ -c2f226527c85033e5dd81f1ba2d4d90a4ced7e8b +93cd074f727575c0538b48b2cc9a89350235f847 diff --git a/.sync-history b/.sync-history index 716180a3a..e69de29bb 100644 --- a/.sync-history +++ b/.sync-history @@ -1 +0,0 @@ -31a43b96f 2024-08-23 Merged PR 70361: 271561-cdr-editor-for-bitmask-properties \ No newline at end of file diff --git a/README.md b/README.md index ff6faf67d..2d5ea3342 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ ## Change log +### October 8, 2024 +- 465213: Fixes an infinite loop if the an invalid value is set on a basic CDR. +- 466404: Fixes an issue with setting today's date as the start date of a delegation. +- 464909: Fixes an issue with the removal of IT shop assignments from newly created applications. +- 466217: Fixes timing issues that could cause the search result to become out of sync with the search term entered by the user. + ### September 9, 2024 - 463113: Fixes an issue, regarding the "Unsubscribed as from" property, that doesn't work as expected. - 462249: Fixes the "Property not found: UID_UNSRoot" error, when switching to tab "Child System Entitlements" on the Data Explorer's system Entitlements page. diff --git a/imxweb/imx-modules/imx-api-qbm.tgz b/imxweb/imx-modules/imx-api-qbm.tgz index 46b5dbf33864bcd1ed0c0f63b08e8e7a1b59bc48..d7fa8d4e1bc8e673f61eaa04f28f46013f021a8a 100644 GIT binary patch delta 17617 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 E05J-WmH+?% delta 17522 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 N_y0kQ@ofNf6aaf|bUOe5 diff --git a/imxweb/imx-modules/imx-api-rms.tgz b/imxweb/imx-modules/imx-api-rms.tgz index 9a025df64393af20e7a5a8f6e92f3e84b719546d..f2e53b93ef6270034fc02ce2a72d282958cd81ac 100644 GIT binary patch delta 21419 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 literal 22920 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 diff --git a/imxweb/projects/aob/src/lib/applications/application-navigation/application-navigation.component.ts b/imxweb/projects/aob/src/lib/applications/application-navigation/application-navigation.component.ts index 0cbfa7985..2b9fd5dc9 100644 --- a/imxweb/projects/aob/src/lib/applications/application-navigation/application-navigation.component.ts +++ b/imxweb/projects/aob/src/lib/applications/application-navigation/application-navigation.component.ts @@ -33,7 +33,6 @@ import { PortalApplication, PortalApplicationNew } from 'imx-api-aob'; import { CollectionLoadParameters, TypedEntityCollectionData, TypedEntity } from 'imx-qbm-dbts'; import { BusyService, ClassloggerService, DataSourceToolbarSettings, DataTileBadge, DataTilesComponent, SettingsService } from 'qbm'; import { ApplicationsService } from '../applications.service'; -import { UserModelService } from 'qer'; import { AobPermissionsService } from '../../permissions/aob-permissions.service'; @@ -90,7 +89,6 @@ export class ApplicationNavigationComponent implements OnInit { private logger: ClassloggerService, private readonly appService: ApplicationsService, private readonly settingsService: SettingsService, - private readonly userService: UserModelService, private readonly route: ActivatedRoute, private readonly applicationsProvider: ApplicationsService, private readonly aobPermissionsService: AobPermissionsService, @@ -178,6 +176,8 @@ export class ApplicationNavigationComponent implements OnInit { public async onSearch(keywords: string): Promise { this.logger.debug(this, `Searching for: ${keywords}`); + + this.appService.abortCall(); this.navigationState.StartIndex = 0; if (keywords == null || keywords.length === 0) { diff --git a/imxweb/projects/aob/src/lib/applications/applications.service.ts b/imxweb/projects/aob/src/lib/applications/applications.service.ts index 4199479a6..08a18a903 100644 --- a/imxweb/projects/aob/src/lib/applications/applications.service.ts +++ b/imxweb/projects/aob/src/lib/applications/applications.service.ts @@ -48,6 +48,8 @@ export class ApplicationsService { public readonly onApplicationDeleted = new Subject(); public applicationRefresh: BehaviorSubject = new BehaviorSubject(false); + public abortController = new AbortController(); + private badgePublished: DataTileBadge; private badgeKpiErrors: DataTileBadge; private badgeNew: DataTileBadge; @@ -93,7 +95,9 @@ export class ApplicationsService { if (this.aobClient.typedClient == null) { return new Promise>((resolve) => resolve(null)); } - return this.apiProvider.request(() => this.aobClient.typedClient.PortalApplication.Get(parameters)); + return this.apiProvider.request(() => + this.aobClient.typedClient.PortalApplication.Get(parameters, { signal: this.abortController.signal }) + ); } public async reload(uidApplication: string): Promise { @@ -220,6 +224,11 @@ export class ApplicationsService { this.onApplicationDeleted.next(uid); } + + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } private isPublished(application: PortalApplication): boolean { if (application.IsInActive == null) { diff --git a/imxweb/projects/aob/src/lib/shops/shops.service.ts b/imxweb/projects/aob/src/lib/shops/shops.service.ts index 5159a4639..e0aacf978 100644 --- a/imxweb/projects/aob/src/lib/shops/shops.service.ts +++ b/imxweb/projects/aob/src/lib/shops/shops.service.ts @@ -96,7 +96,7 @@ export class ShopsService { await this.apiProvider.request(async () => { for (const shop of shops) { await this.aobClient.client - .portal_applicationinshop_delete(application.UID_AOBApplication.value, shop.UID_ITShopOrg.value, undefined); + .portal_applicationinshop_delete(application.UID_AOBApplication.value, shop.UID_ITShopOrg.value,''); count++; } }); diff --git a/imxweb/projects/apc/src/lib/software/software.component.ts b/imxweb/projects/apc/src/lib/software/software.component.ts index cb659972d..7be896640 100644 --- a/imxweb/projects/apc/src/lib/software/software.component.ts +++ b/imxweb/projects/apc/src/lib/software/software.component.ts @@ -63,7 +63,7 @@ export class SoftwareComponent implements OnInit, SideNavigationComponent { private readonly metadata: MetadataService, private readonly sidesheet: EuiSidesheetService, private readonly ldsReplace: LdsReplacePipe, - private readonly translate: TranslateService, + private readonly translate: TranslateService ) {} public async ngOnInit(): Promise { @@ -122,14 +122,17 @@ export class SoftwareComponent implements OnInit, SideNavigationComponent { private async navigate(): Promise { const isBusy = this.busyService.beginBusy(); try { - this.dstSettings = { - dataSource: await this.resourceProvider.get(this.navigationState), - entitySchema: this.entitySchema, - navigationState: this.navigationState, - displayedColumns: this.displayColumns, - filters: this.dataModel.Filters, - dataModel: this.dataModel, - }; + const dataSource = await this.resourceProvider.get(this.navigationState); + if (dataSource) { + this.dstSettings = { + dataSource: dataSource, + entitySchema: this.entitySchema, + navigationState: this.navigationState, + displayedColumns: this.displayColumns, + filters: this.dataModel.Filters, + dataModel: this.dataModel, + }; + } } finally { isBusy.endBusy(); } diff --git a/imxweb/projects/apc/src/lib/software/software.service.ts b/imxweb/projects/apc/src/lib/software/software.service.ts index 2c187fec1..26b36b057 100644 --- a/imxweb/projects/apc/src/lib/software/software.service.ts +++ b/imxweb/projects/apc/src/lib/software/software.service.ts @@ -53,6 +53,8 @@ export class SoftwareService { protected config: QerProjectConfig & ProjectConfig; + private abortController = new AbortController(); + constructor( protected readonly project: ProjectConfigurationService, private readonly api: ApcApiService, @@ -65,7 +67,13 @@ export class SoftwareService { accProduct: this.api.typedClient.PortalResourcesApplicationServiceitem, resp: { type: PortalRespApplication, - get: async (parameter: any) => this.api.client.portal_resp_application_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_resp_application_get(parameter, { signal: this.abortController.signal }); + }, schema: this.api.typedClient.PortalRespApplication.GetSchema(), dataModel: async (filter?: FilterData[]) => this.api.client.portal_resp_application_datamodel_get({ filter }), interactive: this.api.typedClient.PortalRespApplicationInteractive, @@ -138,4 +146,10 @@ export class SoftwareService { public async unsubscribeMembership(item: TypedEntity): Promise { await this.qerClient.client.portal_itshop_unsubscribe_post({ UidPwo: [item.GetEntity().GetColumn('UID_PersonWantsOrg').GetValue()] }); } + + + private abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/att/src/lib/attestation-history/attestation-history.component.ts b/imxweb/projects/att/src/lib/attestation-history/attestation-history.component.ts index 24f7ddb05..f053107ae 100644 --- a/imxweb/projects/att/src/lib/attestation-history/attestation-history.component.ts +++ b/imxweb/projects/att/src/lib/attestation-history/attestation-history.component.ts @@ -74,7 +74,7 @@ export class AttestationHistoryComponent implements OnInit, OnDestroy { @Input() public parameters: { objecttable: string; objectuid: string; filter?: FilterData[] }; @Input() public itemStatus: DataSourceItemStatus = { enabled: (__) => true }; @Input() public withAssignmentAnalysis: boolean = false; - @Input() public selectable : boolean = true; + @Input() public selectable: boolean = true; @ViewChild('attestorFilter', { static: false }) public attestorFilter: AttestationHistoryFilterComponent; @@ -197,6 +197,7 @@ export class AttestationHistoryComponent implements OnInit, OnDestroy { } public async onSearch(search: string): Promise { + this.historyService.abortCall(); return this.getData({ search }); } @@ -251,8 +252,6 @@ export class AttestationHistoryComponent implements OnInit, OnDestroy { viewConfig: this.viewConfig, exportMethod, }; - } else { - this.dstSettings = undefined; } } finally { isBusy.endBusy(); @@ -314,10 +313,8 @@ export class AttestationHistoryComponent implements OnInit, OnDestroy { } public async viewAssignmentAnalysis(event: Event, attestationCase: AttestationHistoryCase): Promise { - event.stopPropagation(); const uidPerson = attestationCase.UID_Person.value; - const objectKey = DbObjectKey.FromXml(attestationCase.ObjectKeyBase.value); const data: SourceDetectiveSidesheetData = { diff --git a/imxweb/projects/att/src/lib/attestation-history/attestation-history.service.ts b/imxweb/projects/att/src/lib/attestation-history/attestation-history.service.ts index 09e5f0ffc..0d3739698 100644 --- a/imxweb/projects/att/src/lib/attestation-history/attestation-history.service.ts +++ b/imxweb/projects/att/src/lib/attestation-history/attestation-history.service.ts @@ -48,28 +48,33 @@ import { DataSourceToolbarExportMethod } from 'qbm'; providedIn: 'root', }) export class AttestationHistoryService { + public abortController = new AbortController(); + constructor(private readonly attClient: ApiService, private readonly parameterDataService: ParameterDataService) {} public async get( parameters: AttestationCaseLoadParameters ): Promise> { - return this.attClient.typedClient.PortalAttestationCase.Get(parameters); + return this.attClient.typedClient.PortalAttestationCase.Get(parameters, { signal: this.abortController.signal }); } - public async getAttestations(loadParameters?: AttestationCaseLoadParameters): Promise> { + public async getAttestations(loadParameters?: AttestationCaseLoadParameters): Promise | undefined> { const collection = await this.get(loadParameters); + if (!collection) { + return undefined; + } return { - tableName: collection.tableName, - totalCount: collection.totalCount, - Data: collection.Data.map((item: PortalAttestationCase, index: number) => { + tableName: collection?.tableName, + totalCount: collection?.totalCount, + Data: collection?.Data.map((item: PortalAttestationCase, index: number) => { const parameterDataContainer = this.parameterDataService.createContainer( item.GetEntity(), - { ...collection.extendedData, ...{ index } }, + { ...collection?.extendedData, ...{ index } }, (parameters) => this.getParameterCandidates(parameters), (treefilterparameter) => this.getFilterTree(treefilterparameter) ); - return new AttestationHistoryCase(item, parameterDataContainer, { ...collection.extendedData, ...{ index } }); + return new AttestationHistoryCase(item, parameterDataContainer, { ...collection?.extendedData, ...{ index } }); }), }; } @@ -80,13 +85,13 @@ export class AttestationHistoryService { getMethod: (withProperties: string, PageSize?: number) => { let method: MethodDescriptor; if (PageSize) { - method = factory.portal_attestation_case_get({...loadParameters, withProperties, PageSize, StartIndex: 0}) + method = factory.portal_attestation_case_get({ ...loadParameters, withProperties, PageSize, StartIndex: 0 }); } else { - method = factory.portal_attestation_case_get({...loadParameters, withProperties}) + method = factory.portal_attestation_case_get({ ...loadParameters, withProperties }); } return new MethodDefinition(method); - } - } + }, + }; } public async getDataModel(objecttable?: string, objectuid?: string, groupFilter?: FilterData[]): Promise { @@ -99,13 +104,18 @@ export class AttestationHistoryService { public getGroupInfo(parameters: AttestationCaseLoadParameters = {}): Promise { // remove groupFilter from parameters - const {withProperties, groupFilter, search, OrderBy, ...paramsWithoutGroupFilter } = parameters; + const { withProperties, groupFilter, search, OrderBy, ...paramsWithoutGroupFilter } = parameters; return this.attClient.client.portal_attestation_case_group_get({ ...paramsWithoutGroupFilter, ...{ withcount: true, filter: parameters.groupFilter }, }); } + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } + private async getParameterCandidates(parameters: ParameterDataLoadParameters): Promise { return this.attClient.client.portal_attestation_case_parameter_candidates_post( parameters.columnName, diff --git a/imxweb/projects/att/src/lib/decision/attestation-cases.service.ts b/imxweb/projects/att/src/lib/decision/attestation-cases.service.ts index 3f7d4a8f3..b98602e51 100644 --- a/imxweb/projects/att/src/lib/decision/attestation-cases.service.ts +++ b/imxweb/projects/att/src/lib/decision/attestation-cases.service.ts @@ -68,6 +68,8 @@ import { AttestationCaseLoadParameters } from '../attestation-history/attestatio }) export class AttestationCasesService { public isChiefApproval: boolean; + public abortController = new AbortController(); + private readonly historyBuilder = new TypedEntityBuilder(PortalAttestationCaseHistory); private readonly apiClientMethodFactory = new ApiClientMethodFactory(); @@ -86,23 +88,33 @@ export class AttestationCasesService { return this.attClient.typedClient.PortalAttestationCase.GetSchema(); } - public async get(attDecisionParameters?: AttestationDecisionLoadParameters, isUserEscalationApprover= false): Promise> { + public async get( + attDecisionParameters?: AttestationDecisionLoadParameters, + isUserEscalationApprover = false + ): Promise | undefined> { + const navigationState = { + ...attDecisionParameters, + Escalation: (attDecisionParameters.uid_attestationcase !== '' && isUserEscalationApprover) || attDecisionParameters.Escalation, + }; - const navigationState = { ...attDecisionParameters, Escalation: (attDecisionParameters.uid_attestationcase !== '' && isUserEscalationApprover) || attDecisionParameters.Escalation }; - - const collection = await this.attClient.typedClient.PortalAttestationApprove.Get(navigationState); + const collection = await this.attClient.typedClient.PortalAttestationApprove.Get(navigationState, { + signal: this.abortController.signal, + }); + if (!collection) { + return undefined; + } return { - tableName: collection.tableName, - totalCount: collection.totalCount, - Data: collection.Data.map((item: PortalAttestationApprove, index: number) => { + tableName: collection?.tableName, + totalCount: collection?.totalCount, + Data: collection?.Data.map((item: PortalAttestationApprove, index: number) => { const parameterDataContainer = this.parameterDataService.createContainer( item.GetEntity(), - { ...collection.extendedData, ...{ index } }, + { ...collection?.extendedData, ...{ index } }, (parameters) => this.getParameterCandidates(parameters), (treefilterparameter) => this.getFilterTree(treefilterparameter) ); - return new AttestationCase(item, this.isChiefApproval, parameterDataContainer, { ...collection.extendedData, ...{ index } }); + return new AttestationCase(item, this.isChiefApproval, parameterDataContainer, { ...collection?.extendedData, ...{ index } }); }), }; } @@ -113,13 +125,13 @@ export class AttestationCasesService { getMethod: (withProperties: string, PageSize?: number) => { let method: MethodDescriptor; if (PageSize) { - method = factory.portal_attestation_approve_get({...attDecisionParameters, withProperties, PageSize, StartIndex: 0}) + method = factory.portal_attestation_approve_get({ ...attDecisionParameters, withProperties, PageSize, StartIndex: 0 }); } else { - method = factory.portal_attestation_approve_get({...attDecisionParameters, withProperties}) + method = factory.portal_attestation_approve_get({ ...attDecisionParameters, withProperties }); } return new MethodDefinition(method); - } - } + }, + }; } public async getNumberOfPending(parameters: AttestationCaseLoadParameters): Promise { @@ -279,6 +291,11 @@ export class AttestationCasesService { return this.attClient.client.portal_attestation_denydecision_post(this.getKey(attestationCase), input); } + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } + private getKey(attestationCase: TypedEntity): string { return attestationCase.GetEntity().GetKeys()[0]; } diff --git a/imxweb/projects/att/src/lib/decision/attestation-decision.component.ts b/imxweb/projects/att/src/lib/decision/attestation-decision.component.ts index 0c8d00108..d509ed2ba 100644 --- a/imxweb/projects/att/src/lib/decision/attestation-decision.component.ts +++ b/imxweb/projects/att/src/lib/decision/attestation-decision.component.ts @@ -70,7 +70,7 @@ export class AttestationDecisionComponent implements OnInit, OnDestroy { public selectedCases: AttestationCase[] = []; public userUid: string; - public hideToolbar:boolean = false; + public hideToolbar: boolean = false; public recApprove = RecommendationEnum.Approve; public recDeny = RecommendationEnum.Deny; @@ -287,6 +287,7 @@ export class AttestationDecisionComponent implements OnInit, OnDestroy { } public async search(search: string): Promise { + this.attestationCases.abortCall(); return this.getData({ ...this.navigationState, ...{ search } }); } @@ -302,38 +303,40 @@ export class AttestationDecisionComponent implements OnInit, OnDestroy { Escalation: this.attestationCases.isChiefApproval, ...this.navigationState, }; - const dataSource = await this.attestationCases.get(params,this.isUserEscalationApprover); - const exportMethod = this.attestationCases.exportData(params); - this.dstSettings = { - dataSource, - entitySchema: this.entitySchema, - navigationState: this.navigationState, - filters: this.filterOptions, - dataModel: this.dataModel, - groupData: this.groupData, - displayedColumns: [ - this.entitySchema.Columns.UiText, - { - ColumnName: 'badges', - Type: ValType.String, - untranslatedDisplay: '#LDS#Badges', - }, - { - ColumnName: 'decision', - Type: ValType.String, - afterAdditionals: true, - untranslatedDisplay: '#LDS#Decision', - }, - { - ColumnName: 'recommendations', - Type: ValType.String, - afterAdditionals: true, - untranslatedDisplay: '#LDS#Recommendation', - }, - ], - exportMethod, - viewConfig: this.viewConfig, - }; + const dataSource = await this.attestationCases.get(params, this.isUserEscalationApprover); + if (dataSource) { + const exportMethod = this.attestationCases.exportData(params); + this.dstSettings = { + dataSource, + entitySchema: this.entitySchema, + navigationState: this.navigationState, + filters: this.filterOptions, + dataModel: this.dataModel, + groupData: this.groupData, + displayedColumns: [ + this.entitySchema.Columns.UiText, + { + ColumnName: 'badges', + Type: ValType.String, + untranslatedDisplay: '#LDS#Badges', + }, + { + ColumnName: 'decision', + Type: ValType.String, + afterAdditionals: true, + untranslatedDisplay: '#LDS#Decision', + }, + { + ColumnName: 'recommendations', + Type: ValType.String, + afterAdditionals: true, + untranslatedDisplay: '#LDS#Recommendation', + }, + ], + exportMethod, + viewConfig: this.viewConfig, + }; + } } finally { isBusy.endBusy(); } @@ -345,7 +348,7 @@ export class AttestationDecisionComponent implements OnInit, OnDestroy { try { const groupedData = this.groupedData[groupKey]; const navigationState = { ...groupedData.navigationState, Escalation: this.viewEscalation }; - groupedData.data = await this.attestationCases.get(navigationState,this.isUserEscalationApprover); + groupedData.data = await this.attestationCases.get(navigationState, this.isUserEscalationApprover); groupedData.settings = { displayedColumns: this.dstSettings.displayedColumns, dataModel: this.dstSettings.dataModel, @@ -371,18 +374,21 @@ export class AttestationDecisionComponent implements OnInit, OnDestroy { try { attestationCaseWithPolicy = ( - await this.attestationCases.get({ - Escalation: this.viewEscalation, - uidpolicy: attestationCase.UID_AttestationPolicy.value, - filter: [ - { - ColumnName: 'UID_AttestationCase', - Type: FilterType.Compare, - CompareOp: CompareOperator.Equal, - Value1: attestationCase.GetEntity().GetKeys()[0], - }, - ], - },this.isUserEscalationApprover) + await this.attestationCases.get( + { + Escalation: this.viewEscalation, + uidpolicy: attestationCase.UID_AttestationPolicy.value, + filter: [ + { + ColumnName: 'UID_AttestationCase', + Type: FilterType.Compare, + CompareOp: CompareOperator.Equal, + Value1: attestationCase.GetEntity().GetKeys()[0], + }, + ], + }, + this.isUserEscalationApprover + ) ).Data[0]; // Add additional violation data to this case attestationCaseWithPolicy.data.CanSeeComplianceViolations = attestationCase.data.CanSeeComplianceViolations; diff --git a/imxweb/projects/att/src/lib/pick-category/pick-category-sidesheet/pick-category-sidesheet.component.html b/imxweb/projects/att/src/lib/pick-category/pick-category-sidesheet/pick-category-sidesheet.component.html index 7ea95b1a8..540ee38f8 100644 --- a/imxweb/projects/att/src/lib/pick-category/pick-category-sidesheet/pick-category-sidesheet.component.html +++ b/imxweb/projects/att/src/lib/pick-category/pick-category-sidesheet/pick-category-sidesheet.component.html @@ -4,13 +4,12 @@ {{'#LDS#Assigned identities' | translate}} - + - -
+
-
+ (click)="saveChanges()" [disabled]="form.invalid || form.pristine"> + {{ '#LDS#Save' | translate}} + +
\ No newline at end of file diff --git a/imxweb/projects/att/src/lib/pick-category/pick-category-sidesheet/pick-category-sidesheet.component.ts b/imxweb/projects/att/src/lib/pick-category/pick-category-sidesheet/pick-category-sidesheet.component.ts index fe345c3de..d809e588b 100644 --- a/imxweb/projects/att/src/lib/pick-category/pick-category-sidesheet/pick-category-sidesheet.component.ts +++ b/imxweb/projects/att/src/lib/pick-category/pick-category-sidesheet/pick-category-sidesheet.component.ts @@ -47,10 +47,9 @@ import { UntypedFormGroup } from '@angular/forms'; @Component({ selector: 'imx-pick-category-sidesheet', templateUrl: './pick-category-sidesheet.component.html', - styleUrls: ['./pick-category-sidesheet.component.scss'] + styleUrls: ['./pick-category-sidesheet.component.scss'], }) export class PickCategorySidesheetComponent implements OnInit { - public readonly dstWrapper: DataSourceWrapper; public readonly form = new UntypedFormGroup({}); public dstSettings: DataSourceToolbarSettings; @@ -62,8 +61,9 @@ export class PickCategorySidesheetComponent implements OnInit { private uidPickCategory: string; constructor( - @Inject(EUI_SIDESHEET_DATA) public data: { - pickCategory: PortalPickcategory + @Inject(EUI_SIDESHEET_DATA) + public data: { + pickCategory: PortalPickcategory; }, private readonly sidesheet: EuiSidesheetService, private readonly snackBar: SnackBarService, @@ -75,7 +75,7 @@ export class PickCategorySidesheetComponent implements OnInit { const entitySchema = this.pickCategoryService.pickcategoryItemsSchema; this.dstWrapper = new DataSourceWrapper( - state => this.pickCategoryService.getPickCategoryItems(this.uidPickCategory, state), + (state, requestOpts) => this.pickCategoryService.getPickCategoryItems(this.uidPickCategory, state, requestOpts), [entitySchema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME]], entitySchema ); @@ -91,16 +91,22 @@ export class PickCategorySidesheetComponent implements OnInit { public async getData(newState?: CollectionLoadParameters): Promise { this.pickCategoryService.handleOpenLoader(); try { - this.dstSettings = await this.dstWrapper.getDstSettings(newState); + const dstSettings = await this.dstWrapper.getDstSettings(newState, { signal: this.pickCategoryService.abortController.signal }); + if (dstSettings) { + this.dstSettings = dstSettings; + } } finally { this.pickCategoryService.handleCloseLoader(); } } public selectedItemsCanBeDeleted(): boolean { - return this.selectedPickedItems != null && - this.selectedPickedItems.length > 0 && - this.data.pickCategory.IsManual.value; + return this.selectedPickedItems != null && this.selectedPickedItems.length > 0 && this.data.pickCategory.IsManual.value; + } + + public onSearch(keywords: string): Promise { + this.pickCategoryService.abortCall(); + return this.getData({ search: keywords }); } public onSelectionChanged(items: PortalPickcategoryItems[]): void { @@ -109,15 +115,18 @@ export class PickCategorySidesheetComponent implements OnInit { } public async assignPickedItems(): Promise { - const selection = await this.sidesheet.open(PickCategorySelectIdentitiesComponent, { - title: await this.translate.get('#LDS#Heading Assign Identities').toPromise(), - subTitle: this.data.pickCategory.GetEntity().GetDisplay(), - padding: '0px', - width: '700px', - disableClose: false, - testId: 'pick-category-select-identities', - data: this.dstSettings.dataSource.Data - }).afterClosed().toPromise(); + const selection = await this.sidesheet + .open(PickCategorySelectIdentitiesComponent, { + title: await this.translate.get('#LDS#Heading Assign Identities').toPromise(), + subTitle: this.data.pickCategory.GetEntity().GetDisplay(), + padding: '0px', + width: '700px', + disableClose: false, + testId: 'pick-category-select-identities', + data: this.dstSettings.dataSource.Data, + }) + .afterClosed() + .toPromise(); if (selection && (await this.pickCategoryService.createPickedItems(selection, this.uidPickCategory)) > 0) { await this.getData(); @@ -125,11 +134,13 @@ export class PickCategorySidesheetComponent implements OnInit { } public async removePickedItems(): Promise { - if (await this.confirmationService.confirm({ - Title: '#LDS#Heading Remove Identities', - Message: '#LDS#Are you sure you want to remove the selected identities?' - })) { - if (await this.pickCategoryService.deletePickedItems(this.uidPickCategory, this.selectedPickedItems) > 0) { + if ( + await this.confirmationService.confirm({ + Title: '#LDS#Heading Remove Identities', + Message: '#LDS#Are you sure you want to remove the selected identities?', + }) + ) { + if ((await this.pickCategoryService.deletePickedItems(this.uidPickCategory, this.selectedPickedItems)) > 0) { await this.getData(); this.table?.clearSelection(); } @@ -149,5 +160,4 @@ export class PickCategorySidesheetComponent implements OnInit { } } } - } diff --git a/imxweb/projects/att/src/lib/pick-category/pick-category.component.html b/imxweb/projects/att/src/lib/pick-category/pick-category.component.html index 02f0b87b9..5eec59d42 100644 --- a/imxweb/projects/att/src/lib/pick-category/pick-category.component.html +++ b/imxweb/projects/att/src/lib/pick-category/pick-category.component.html @@ -11,7 +11,7 @@

#dst [options]="['search']" [settings]="dstSettings" - (search)="getData({ search: $event })" + (search)="onSearch($event)" (navigationStateChanged)="getData($event)" data-imx-identifier="pick-category-dst" > diff --git a/imxweb/projects/att/src/lib/pick-category/pick-category.component.ts b/imxweb/projects/att/src/lib/pick-category/pick-category.component.ts index 0c14cb8a3..f434bd555 100644 --- a/imxweb/projects/att/src/lib/pick-category/pick-category.component.ts +++ b/imxweb/projects/att/src/lib/pick-category/pick-category.component.ts @@ -40,7 +40,7 @@ import { ConfirmationService, HelpContextualComponent, HelpContextualService, - HELP_CONTEXTUAL + HELP_CONTEXTUAL, } from 'qbm'; import { PickCategoryCreateComponent } from './pick-category-create/pick-category-create.component'; import { PickCategorySidesheetComponent } from './pick-category-sidesheet/pick-category-sidesheet.component'; @@ -49,10 +49,9 @@ import { PickCategoryService } from './pick-category.service'; @Component({ selector: 'imx-pick-category', templateUrl: './pick-category.component.html', - styleUrls: ['./pick-category.component.scss'] + styleUrls: ['./pick-category.component.scss'], }) export class PickCategoryComponent implements OnInit, OnDestroy { - public readonly dstWrapper: DataSourceWrapper; public dstSettings: DataSourceToolbarSettings; public selectedPickCategoryItems: PortalPickcategory[] = []; @@ -68,15 +67,11 @@ export class PickCategoryComponent implements OnInit, OnDestroy { private readonly logger: ClassloggerService, private readonly helpContextualService: HelpContextualService ) { - const entitySchema = this.pickCategoryService.pickcategorySchema; this.dstWrapper = new DataSourceWrapper( - state => this.pickCategoryService.getPickCategories(state), - [ - entitySchema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME], - entitySchema.Columns.IsManual - ], + (state, requestOpts) => this.pickCategoryService.getPickCategories(state, requestOpts), + [entitySchema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME], entitySchema.Columns.IsManual], entitySchema ); } @@ -94,16 +89,26 @@ export class PickCategoryComponent implements OnInit, OnDestroy { this.selectedPickCategoryItems = items; } + public async onSearch(keywords: string): Promise { + this.pickCategoryService.abortCall(); + return this.getData({ search: keywords }); + } + public selectedItemsCanBeDeleted(): boolean { - return this.selectedPickCategoryItems != null && + return ( + this.selectedPickCategoryItems != null && this.selectedPickCategoryItems.length > 0 && - this.selectedPickCategoryItems.every(item => item.IsManual.value); + this.selectedPickCategoryItems.every((item) => item.IsManual.value) + ); } public async getData(newState?: CollectionLoadParameters): Promise { this.pickCategoryService.handleOpenLoader(); try { - this.dstSettings = await this.dstWrapper.getDstSettings(newState); + const dstSettings = await this.dstWrapper.getDstSettings(newState, { signal: this.pickCategoryService.abortController.signal }); + if (dstSettings) { + this.dstSettings = dstSettings; + } } finally { this.pickCategoryService.handleCloseLoader(); } @@ -111,37 +116,41 @@ export class PickCategoryComponent implements OnInit, OnDestroy { public async viewDetails(pickCategory: PortalPickcategory): Promise { if (pickCategory) { - this.helpContextualService.setHelpContextId(HELP_CONTEXTUAL.AttestationPreselectionEdit) - const result = await this.sideSheet.open(PickCategorySidesheetComponent, { - title: await this.translate.get('#LDS#Heading Edit Sample').toPromise(), - subTitle: pickCategory.GetEntity().GetDisplay(), - panelClass: 'imx-sidesheet', - padding: '0', - width: '600px', - testId: 'pickCategory-details-sidesheet', - data: { - pickCategory - }, - headerComponent: HelpContextualComponent - }).afterClosed().toPromise(); + this.helpContextualService.setHelpContextId(HELP_CONTEXTUAL.AttestationPreselectionEdit); + const result = await this.sideSheet + .open(PickCategorySidesheetComponent, { + title: await this.translate.get('#LDS#Heading Edit Sample').toPromise(), + subTitle: pickCategory.GetEntity().GetDisplay(), + panelClass: 'imx-sidesheet', + padding: '0', + width: '600px', + testId: 'pickCategory-details-sidesheet', + data: { + pickCategory, + }, + headerComponent: HelpContextualComponent, + }) + .afterClosed() + .toPromise(); if (result) { this.getData(); } - } - else { + } else { this.messageService.subject.next({ - text: '#LDS#You cannot edit the sample. The sample does not exist (anymore). Please reload the page.' + text: '#LDS#You cannot edit the sample. The sample does not exist (anymore). Please reload the page.', }); } } public async delete(): Promise { - if (await this.confirmationService.confirm({ - Title: '#LDS#Heading Delete Sample', - Message: '#LDS#Are you sure you want to delete the sample?' - })) { - if (await this.pickCategoryService.deletePickCategories(this.selectedPickCategoryItems) > 0) { + if ( + await this.confirmationService.confirm({ + Title: '#LDS#Heading Delete Sample', + Message: '#LDS#Are you sure you want to delete the sample?', + }) + ) { + if ((await this.pickCategoryService.deletePickCategories(this.selectedPickCategoryItems)) > 0) { await this.getData(); this.table?.clearSelection(); } @@ -153,29 +162,30 @@ export class PickCategoryComponent implements OnInit, OnDestroy { this.logger.trace(this, 'new pick category created', newPickCategory); if (newPickCategory) { - - this.helpContextualService.setHelpContextId(HELP_CONTEXTUAL.AttestationPreselectionCreate) - const result = await this.sideSheet.open(PickCategoryCreateComponent, { - title: await this.translate.get('#LDS#Heading Create Sample').toPromise(), - panelClass: 'imx-sidesheet', - padding: '0', - width: '700px', - disableClose: true, - testId: 'pickCategory-create-sidesheet', - data: { - pickCategory: newPickCategory - }, - headerComponent: HelpContextualComponent - }).afterClosed().toPromise(); + this.helpContextualService.setHelpContextId(HELP_CONTEXTUAL.AttestationPreselectionCreate); + const result = await this.sideSheet + .open(PickCategoryCreateComponent, { + title: await this.translate.get('#LDS#Heading Create Sample').toPromise(), + panelClass: 'imx-sidesheet', + padding: '0', + width: '700px', + disableClose: true, + testId: 'pickCategory-create-sidesheet', + data: { + pickCategory: newPickCategory, + }, + headerComponent: HelpContextualComponent, + }) + .afterClosed() + .toPromise(); if (result?.create) { await this.pickCategoryService.saveNewPickCategoryAndItems(result.pickCategory, result.pickedItems); this.getData(); } - } - else { + } else { this.messageService.subject.next({ - text: '#LDS#The sample could not be created. Please reload the page and try again.' + text: '#LDS#The sample could not be created. Please reload the page and try again.', }); } } diff --git a/imxweb/projects/att/src/lib/pick-category/pick-category.service.ts b/imxweb/projects/att/src/lib/pick-category/pick-category.service.ts index 11c83673d..3cdb5edc2 100644 --- a/imxweb/projects/att/src/lib/pick-category/pick-category.service.ts +++ b/imxweb/projects/att/src/lib/pick-category/pick-category.service.ts @@ -29,6 +29,7 @@ import { OverlayRef } from '@angular/cdk/overlay'; import { EuiLoadingService } from '@elemental-ui/core'; import { + ApiRequestOptions, CollectionLoadParameters, EntityCollectionData, EntitySchema, @@ -39,11 +40,11 @@ import { PortalPersonAll, PortalPickcategory, PortalPickcategoryItems } from 'im import { QerApiService } from 'qer'; import { ClassloggerService, SnackBarService } from 'qbm'; - @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PickCategoryService { + public abortController = new AbortController(); private busyIndicator: OverlayRef; @@ -52,8 +53,8 @@ export class PickCategoryService { private readonly busyService: EuiLoadingService, private readonly snackbar: SnackBarService, private readonly logger: ClassloggerService, - private readonly errorHandler: ErrorHandler, - ) { } + private readonly errorHandler: ErrorHandler + ) {} public get pickcategorySchema(): EntitySchema { return this.qerClient.typedClient.PortalPickcategory.GetSchema(); @@ -63,9 +64,11 @@ export class PickCategoryService { return this.qerClient.typedClient.PortalPickcategoryItems.GetSchema(); } - public async getPickCategories(navigationState: CollectionLoadParameters): - Promise> { - return this.qerClient.typedClient.PortalPickcategory.Get(navigationState); + public async getPickCategories( + navigationState: CollectionLoadParameters, + requestOpts?: ApiRequestOptions + ): Promise> { + return this.qerClient.typedClient.PortalPickcategory.Get(navigationState, requestOpts); } /** @@ -84,10 +87,13 @@ export class PickCategoryService { try { deletedObjects = (await this.bulkDeletePickCategories(pickCategories)).length; if (deletedObjects > 0) { - this.snackbar.open({ - key: '#LDS#{0} samples have been successfully deleted.', - parameters: [deletedObjects] - }, '#LDS#Close'); + this.snackbar.open( + { + key: '#LDS#{0} samples have been successfully deleted.', + parameters: [deletedObjects], + }, + '#LDS#Close' + ); } } finally { this.handleCloseLoader(); @@ -99,17 +105,23 @@ export class PickCategoryService { return this.qerClient.typedClient.PortalPickcategory.createEntity(); } - public async getPickCategoryItems(uidQERPickCategory: string, navigationState: CollectionLoadParameters): - Promise> { - return this.qerClient.typedClient.PortalPickcategoryItems.Get(uidQERPickCategory, navigationState); + public async getPickCategoryItems( + uidQERPickCategory: string, + navigationState: CollectionLoadParameters, + requestOpts?: ApiRequestOptions + ): Promise> { + return this.qerClient.typedClient.PortalPickcategoryItems.Get(uidQERPickCategory, navigationState, requestOpts); } - public async deletePickCategoryItems(uidPickCategory: string, pickCategoriesItems: PortalPickcategoryItems[]) - : Promise { + public async deletePickCategoryItems( + uidPickCategory: string, + pickCategoriesItems: PortalPickcategoryItems[] + ): Promise { return this.handlePromiseLoader( Promise.all( - pickCategoriesItems.map(pickedItem => - this.qerClient.client.portal_pickcategory_items_delete(uidPickCategory, pickedItem.GetEntity().GetKeys().join(','))) + pickCategoriesItems.map((pickedItem) => + this.qerClient.client.portal_pickcategory_items_delete(uidPickCategory, pickedItem.GetEntity().GetKeys().join(',')) + ) ) ); } @@ -119,7 +131,7 @@ export class PickCategoryService { try { await this.qerClient.client.portal_pickcategory_items_post(uidPickCategory, { Reload: true, - Objects: pickCategoriesItems.map(item => item.EntityWriteDataSingle) + Objects: pickCategoriesItems.map((item) => item.EntityWriteDataSingle), }); assignedObjectsCounter++; this.logger.trace(this, `${pickCategoriesItems.length} pick category items assigned`); @@ -137,10 +149,13 @@ export class PickCategoryService { try { deletedObjects = (await this.deletePickCategoryItems(uidPickCategory, selectedPickedItems)).length; if (deletedObjects > 0) { - this.snackbar.open({ - key: '#LDS#{0} identities have been successfully removed.', - parameters: [deletedObjects] - }, '#LDS#Close'); + this.snackbar.open( + { + key: '#LDS#{0} identities have been successfully removed.', + parameters: [deletedObjects], + }, + '#LDS#Close' + ); } } finally { this.handleCloseLoader(); @@ -149,29 +164,33 @@ export class PickCategoryService { } public async createPickedItems(selection: any, uidPickCategory: string, showResultInSnackbar: boolean = true): Promise { - - const newAssignedObjects = (await this.handlePromiseLoader( - Promise.all( - selection.map(async (selectedItem: { XObjectKey: { value: string; }; }) => { - const pickedItem = this.qerClient.typedClient.PortalPickcategoryItems.createEntity(); - pickedItem.UID_QERPickCategory.value = uidPickCategory; - pickedItem.ObjectKeyItem.value = selectedItem.XObjectKey.value; - await pickedItem.GetEntity().Commit(true); - }) + const newAssignedObjects = ( + await this.handlePromiseLoader( + Promise.all( + selection.map(async (selectedItem: { XObjectKey: { value: string } }) => { + const pickedItem = this.qerClient.typedClient.PortalPickcategoryItems.createEntity(); + pickedItem.UID_QERPickCategory.value = uidPickCategory; + pickedItem.ObjectKeyItem.value = selectedItem.XObjectKey.value; + await pickedItem.GetEntity().Commit(true); + }) + ) ) - )).length; + ).length; if (newAssignedObjects > 0 && showResultInSnackbar) { - this.snackbar.open({ - key: '#LDS#{0} identities have been successfully assigned.', - parameters: [newAssignedObjects] - }, '#LDS#Close'); + this.snackbar.open( + { + key: '#LDS#{0} identities have been successfully assigned.', + parameters: [newAssignedObjects], + }, + '#LDS#Close' + ); } return newAssignedObjects; } public handleOpenLoader(): void { if (!this.busyIndicator) { - setTimeout(() => this.busyIndicator = this.busyService.show()); + setTimeout(() => (this.busyIndicator = this.busyService.show())); } } @@ -185,7 +204,6 @@ export class PickCategoryService { } public async saveNewPickCategoryAndItems(pickCategory: PortalPickcategory, pickedItems: PortalPersonAll[]): Promise { - this.handleOpenLoader(); try { const uidPickCategory = await this.postPickCategory(pickCategory); @@ -194,22 +212,28 @@ export class PickCategoryService { if (pickedItems && pickedItems.length > 0) { await this.createPickedItems(pickedItems, uidPickCategory, false); } - } finally { this.handleCloseLoader(); } - this.snackbar.open({ - key: '#LDS#The sample has been successfully created.', - parameters: [pickCategory.GetEntity().GetDisplay()] - }, '#LDS#Close'); + this.snackbar.open( + { + key: '#LDS#The sample has been successfully created.', + parameters: [pickCategory.GetEntity().GetDisplay()], + }, + '#LDS#Close' + ); + } + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); } private async bulkDeletePickCategories(pickCategories: PortalPickcategory[]): Promise { return this.handlePromiseLoader( Promise.all( - pickCategories.map(pickCategory => this.qerClient.client.portal_pickcategory_delete(pickCategory.GetEntity().GetKeys().join(','))) + pickCategories.map((pickCategory) => this.qerClient.client.portal_pickcategory_delete(pickCategory.GetEntity().GetKeys().join(','))) ) ); } diff --git a/imxweb/projects/att/src/lib/policies/policy-list/policy-list.component.ts b/imxweb/projects/att/src/lib/policies/policy-list/policy-list.component.ts index 98fa200f5..cd3c0eb43 100644 --- a/imxweb/projects/att/src/lib/policies/policy-list/policy-list.component.ts +++ b/imxweb/projects/att/src/lib/policies/policy-list/policy-list.component.ts @@ -45,15 +45,7 @@ import { DataSourceToolbarViewConfig, BusyService, } from 'qbm'; -import { - DisplayColumns, - ValType, - ExtendedTypedEntityCollection, - EntitySchema, - FilterType, - CompareOperator, - DataModel, -} from 'imx-qbm-dbts'; +import { DisplayColumns, ValType, ExtendedTypedEntityCollection, EntitySchema, FilterType, CompareOperator, DataModel } from 'imx-qbm-dbts'; import { PolicyFilterData, PortalAttestationPolicy, PortalAttestationPolicyEdit } from 'imx-api-att'; import { UserModelService, ViewConfigService } from 'qer'; import { PolicyService } from '../policy.service'; @@ -101,8 +93,8 @@ export class PolicyListComponent implements OnInit { private readonly userService: UserModelService, private readonly systemInfoService: SystemInfoService, private readonly settingsService: SettingsService, - private readonly changeDetector : ChangeDetectorRef, - private readonly logger: ClassloggerService, + private readonly changeDetector: ChangeDetectorRef, + private readonly logger: ClassloggerService ) { this.navigationState = { PageSize: this.settingsService.DefaultPageSize, StartIndex: 0 }; this.entitySchemaPolicy = policyService.AttestationPolicySchema; @@ -155,6 +147,7 @@ export class PolicyListComponent implements OnInit { } public async onSearch(keywords: string): Promise { + this.policyService.abortCall(); this.navigationState = { ...this.navigationState, ...{ @@ -321,7 +314,7 @@ export class PolicyListComponent implements OnInit { subtitle: policy.GetEntity().GetDisplay(), }; } finally { - setTimeout(() => this.elementalBusyService.hide(overlayRef)); + setTimeout(() => this.elementalBusyService.hide(overlayRef)); } if (data) { @@ -379,20 +372,22 @@ export class PolicyListComponent implements OnInit { try { const policies = await this.policyService.getPolicies(this.navigationState); - const exportMethod = this.policyService.exportPolicies(this.navigationState); - this.logger.trace(this, 'interactive policy loaded', policies); - - this.dstSettings = { - displayedColumns: this.displayedColumns, - dataSource: policies, - filters: this.filterOptions, - groupData: this.groupData, - entitySchema: this.entitySchemaPolicy, - navigationState: this.navigationState, - dataModel: this.dataModel, - viewConfig: this.viewConfig, - exportMethod, - }; + if (policies) { + const exportMethod = this.policyService.exportPolicies(this.navigationState); + this.logger.trace(this, 'interactive policy loaded', policies); + + this.dstSettings = { + displayedColumns: this.displayedColumns, + dataSource: policies, + filters: this.filterOptions, + groupData: this.groupData, + entitySchema: this.entitySchemaPolicy, + navigationState: this.navigationState, + dataModel: this.dataModel, + viewConfig: this.viewConfig, + exportMethod, + }; + } } finally { isBusy.endBusy(); } @@ -412,7 +407,7 @@ export class PolicyListComponent implements OnInit { width: 'max(600px, 80%)', disableClose: true, data: { policy, filterData, isNew, isComplienceFrameworkEnabled: this.isComplienceFrameworkEnabled, showSampleDataWarning }, - testId: 'policy-list-show-policy-sidesheet' + testId: 'policy-list-show-policy-sidesheet', }); const shouldReload = await sidesheetRef.afterClosed().toPromise(); diff --git a/imxweb/projects/att/src/lib/policies/policy.service.ts b/imxweb/projects/att/src/lib/policies/policy.service.ts index e6a3e8118..9a7f5b746 100644 --- a/imxweb/projects/att/src/lib/policies/policy.service.ts +++ b/imxweb/projects/att/src/lib/policies/policy.service.ts @@ -61,6 +61,8 @@ import { PolicyCopyData } from './policy.interface'; providedIn: 'root', }) export class PolicyService { + public abortController = new AbortController(); + private readonly apiClientMethodFactory = new V2ApiClientMethodFactory(); constructor( @@ -83,12 +85,15 @@ export class PolicyService { return this.api.typedClient.PortalAttestationPolicyEditInteractive.GetSchema(); } - public async getPolicies(parameters: PolicyLoadParameters): Promise> { - const collection = await this.api.typedClient.PortalAttestationPolicy.Get(parameters); + public async getPolicies(parameters: PolicyLoadParameters): Promise | undefined> { + const collection = await this.api.typedClient.PortalAttestationPolicy.Get(parameters, { signal: this.abortController.signal }); + if (!collection) { + return undefined; + } return { - tableName: collection.tableName, - totalCount: collection.totalCount, - Data: collection.Data.map((element, index) => new AttestationPolicy(element.GetEntity())), + tableName: collection?.tableName, + totalCount: collection?.totalCount, + Data: collection?.Data.map((element, index) => new AttestationPolicy(element.GetEntity())), }; } @@ -237,6 +242,11 @@ export class PolicyService { return element.totalCount; } + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } + private async copyPropertiesFrom( entity: PortalAttestationPolicyEdit, reference: PortalAttestationPolicyEdit, diff --git a/imxweb/projects/att/src/lib/policy-group/policy-group-list/policy-group-list.component.ts b/imxweb/projects/att/src/lib/policy-group/policy-group-list/policy-group-list.component.ts index 5bf6ae3cb..07677513e 100644 --- a/imxweb/projects/att/src/lib/policy-group/policy-group-list/policy-group-list.component.ts +++ b/imxweb/projects/att/src/lib/policy-group/policy-group-list/policy-group-list.component.ts @@ -121,6 +121,7 @@ export class PolicyGroupListComponent { } } public async onSearch(keywords: string): Promise { + this.policyGroupService.abortCall(); this.navigationState = { ...this.navigationState, ...{ diff --git a/imxweb/projects/att/src/lib/policy-group/policy-group.service.ts b/imxweb/projects/att/src/lib/policy-group/policy-group.service.ts index 9251d81cd..679c1e74d 100644 --- a/imxweb/projects/att/src/lib/policy-group/policy-group.service.ts +++ b/imxweb/projects/att/src/lib/policy-group/policy-group.service.ts @@ -25,103 +25,88 @@ */ import { Injectable } from '@angular/core'; -import { - CollectionLoadParameters, - CompareOperator, - DataModel, - EntityCollectionData, - EntitySchema, - ExtendedEntityCollectionData, - ExtendedTypedEntityCollection, - FilterType, - GroupInfo, - MethodDefinition, -} from 'imx-qbm-dbts'; -import { - PortalAttestationPolicygroups, - PolicyFilter -} from 'imx-api-att'; +import { CollectionLoadParameters, EntityCollectionData, EntitySchema, ExtendedTypedEntityCollection } from 'imx-qbm-dbts'; +import { PortalAttestationPolicygroups, PolicyFilter } from 'imx-api-att'; import { ApiService } from '../api.service'; import { EuiLoadingService } from '@elemental-ui/core'; import { OverlayRef } from '@angular/cdk/overlay'; import { TranslateService } from '@ngx-translate/core'; -import { AppConfigService, ClassloggerService } from 'qbm'; +import { ClassloggerService } from 'qbm'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PolicyGroupService { + public abortController = new AbortController(); + private busyIndicator: OverlayRef; + constructor( private api: ApiService, - private busyService: EuiLoadingService, + private busyService: EuiLoadingService, private readonly translator: TranslateService, - private readonly config: AppConfigService, private readonly logger: ClassloggerService - ) { } + ) {} public get AttestationPolicyGroupSchema(): EntitySchema { return this.api.typedClient.PortalAttestationPolicygroups.GetSchema(); } - public async get(parameters: CollectionLoadParameters): Promise> { - return this.api.typedClient.PortalAttestationPolicygroups.Get(parameters); + return this.api.typedClient.PortalAttestationPolicygroups.Get(parameters, { signal: this.abortController.signal }); } - - public async deleteAttestationPolicyGroup(uidAttestationpolicygroup: string): Promise { + public async deleteAttestationPolicyGroup(uidAttestationpolicygroup: string): Promise { return this.api.client.portal_attestation_policygroups_delete(uidAttestationpolicygroup); } - public async getPolicyGroupEdit(uid: string): - Promise> { + public async getPolicyGroupEdit(uid: string): Promise> { return this.api.typedClient.PortalAttestationPolicygroupsInteractive.Get_byid(uid); } - public async buildNewEntity(reference?: PortalAttestationPolicygroups, filter?: PolicyFilter): - Promise { - const entities = await this.api.typedClient.PortalAttestationPolicygroupsInteractive.Get(); - if (reference == null) { + public async buildNewEntity(reference?: PortalAttestationPolicygroups, filter?: PolicyFilter): Promise { + const entities = await this.api.typedClient.PortalAttestationPolicygroupsInteractive.Get(); + if (reference == null) { + return entities.Data[0]; + } + await this.copyPropertiesFrom(entities.Data[0], reference, filter); return entities.Data[0]; } - await this.copyPropertiesFrom(entities.Data[0], reference, filter); - return entities.Data[0]; -} -public async copyPropertiesFrom( - entity: PortalAttestationPolicygroups, - reference: PortalAttestationPolicygroups, filter: PolicyFilter): Promise { - - for (const key in this.api.typedClient.PortalAttestationPolicyEditInteractive.GetSchema().Columns) { - if (!key.startsWith('__') && entity[key].GetMetadata().CanEdit()) { - await entity[key].Column.PutValueStruct({ - DataValue: reference[key].value, - DisplayValue: reference[key].Column.GetDisplayValue() - }); + public async copyPropertiesFrom( + entity: PortalAttestationPolicygroups, + reference: PortalAttestationPolicygroups, + filter: PolicyFilter + ): Promise { + for (const key in this.api.typedClient.PortalAttestationPolicyEditInteractive.GetSchema().Columns) { + if (!key.startsWith('__') && entity[key].GetMetadata().CanEdit()) { + await entity[key].Column.PutValueStruct({ + DataValue: reference[key].value, + DisplayValue: reference[key].Column.GetDisplayValue(), + }); + } } - } - entity.Ident_AttestationPolicyGroup.value = - `${reference.Ident_AttestationPolicyGroup.value} (${(await this.translator.get('#LDS#New').toPromise())})`; + entity.Ident_AttestationPolicyGroup.value = `${reference.Ident_AttestationPolicyGroup.value} (${await this.translator + .get('#LDS#New') + .toPromise()})`; - this.logger.trace(this, 'properties copied from policy', reference, filter); -} + this.logger.trace(this, 'properties copied from policy', reference, filter); + } -public async getPolicyGroups(parameters: CollectionLoadParameters): -Promise> { -const collection = await this.api.typedClient.PortalAttestationPolicygroups.Get(parameters); -return { - tableName: collection.tableName, - totalCount: collection.totalCount, - Data: collection.Data.map((element, index) => - new PortalAttestationPolicygroups(element.GetEntity()) - ) -}; -} + public async getPolicyGroups( + parameters: CollectionLoadParameters + ): Promise> { + const collection = await this.api.typedClient.PortalAttestationPolicygroups.Get(parameters); + return { + tableName: collection.tableName, + totalCount: collection.totalCount, + Data: collection.Data.map((element, index) => new PortalAttestationPolicygroups(element.GetEntity())), + }; + } public handleOpenLoader(): void { if (!this.busyIndicator) { - setTimeout(() => this.busyIndicator = this.busyService.show()); + setTimeout(() => (this.busyIndicator = this.busyService.show())); } } @@ -133,4 +118,9 @@ return { }); } } + + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/att/src/lib/runs/runs-grid/runs-grid.component.ts b/imxweb/projects/att/src/lib/runs/runs-grid/runs-grid.component.ts index 064067643..ba96cfdd1 100644 --- a/imxweb/projects/att/src/lib/runs/runs-grid/runs-grid.component.ts +++ b/imxweb/projects/att/src/lib/runs/runs-grid/runs-grid.component.ts @@ -29,10 +29,27 @@ import { Component, Input, OnInit } from '@angular/core'; import { EuiLoadingService, EuiSidesheetService } from '@elemental-ui/core'; import { TranslateService } from '@ngx-translate/core'; -import { CollectionLoadParameters, CompareOperator, DataModel, EntityCollectionData, EntitySchema, FilterData, FilterType, MethodDefinition, MethodDescriptor } from 'imx-qbm-dbts'; +import { + CollectionLoadParameters, + CompareOperator, + DataModel, + EntityCollectionData, + EntitySchema, + FilterData, + FilterType, + MethodDefinition, + MethodDescriptor, +} from 'imx-qbm-dbts'; import { PortalAttestationRun, RunStatisticsConfig, V2ApiClientMethodFactory } from 'imx-api-att'; -import { DataSourceToolbarExportMethod, DataSourceToolbarFilter, DataSourceToolbarGroupData, DataSourceToolbarSettings, DataSourceToolbarViewConfig, DataTableGroupedData, SettingsService } from 'qbm'; -import { ApiService } from '../../api.service'; +import { + DataSourceToolbarExportMethod, + DataSourceToolbarFilter, + DataSourceToolbarGroupData, + DataSourceToolbarSettings, + DataSourceToolbarViewConfig, + DataTableGroupedData, + SettingsService, +} from 'qbm'; import { createGroupData } from '../../datamodel/datamodel-helper'; import { RunSidesheetComponent } from '../run-sidesheet.component'; import { RunsService } from '../runs.service'; @@ -43,10 +60,9 @@ import { ViewConfigService } from 'qer'; @Component({ selector: 'imx-runs-grid', templateUrl: './runs-grid.component.html', - styleUrls: ['./runs-grid.component.scss'] + styleUrls: ['./runs-grid.component.scss'], }) export class RunsGridComponent implements OnInit { - /** * Settings needed by the DataSourceToolbarComponent */ @@ -54,7 +70,7 @@ export class RunsGridComponent implements OnInit { public readonly categoryBadgeColor = { Bad: 'red', Mediocre: 'orange', - Good: 'white' + Good: 'white', }; public entitySchema: EntitySchema; @@ -87,32 +103,35 @@ export class RunsGridComponent implements OnInit { private viewConfigService: ViewConfigService, private busyService: EuiLoadingService, private sideSheet: EuiSidesheetService, - private readonly attService: ApiService, private readonly settingsService: SettingsService, private readonly permissions: PermissionsService, private translate: TranslateService ) { - this.entitySchema = this.attService.typedClient.PortalAttestationRun.GetSchema(); - + this.entitySchema = this.runsService.AttestationRunSchema; } public async ngOnInit(): Promise { this.filter = { - filter: this.uidAttestationPolicy == null ? [] : [{ - CompareOp: CompareOperator.Equal, - Type: FilterType.Compare, - ColumnName: 'UID_AttestationPolicy', - Value1: this.uidAttestationPolicy - }] + filter: + this.uidAttestationPolicy == null + ? [] + : [ + { + CompareOp: CompareOperator.Equal, + Type: FilterType.Compare, + ColumnName: 'UID_AttestationPolicy', + Value1: this.uidAttestationPolicy, + }, + ], }; this.navigationState = { ...{ PageSize: this.settingsService.DefaultPageSize, StartIndex: 0 }, ...this.filter }; - const config = await this.attService.client.portal_attestation_config_get(); + const config = await this.runsService.attestationConfig(); this.attestationRunConfig = config.AttestationRunConfig; this.progressCalcThreshold = config.ProgressCalculationThreshold; this.canSeeAttestationPolicies = await this.permissions.canSeeAttestationPolicies(); let busyIndicator: OverlayRef; - setTimeout(() => busyIndicator = this.busyService.show()); + setTimeout(() => (busyIndicator = this.busyService.show())); try { this.dataModel = await this.runsService.getDataModel(); this.viewConfig = await this.viewConfigService.getInitialDSTExtension(this.dataModel, this.viewConfigPath); @@ -124,7 +143,7 @@ export class RunsGridComponent implements OnInit { this.filterOptions = this.dataModel.Filters; this.groupData = createGroupData( this.dataModel, - parameters => this.runsService.getGroupInfo({ ...{ PageSize: this.navigationState.PageSize, StartIndex: 0 }, ...parameters }), + (parameters) => this.runsService.getGroupInfo({ ...{ PageSize: this.navigationState.PageSize, StartIndex: 0 }, ...parameters }), this.uidAttestationPolicy == null ? [] : ['UID_AttestationPolicy'] ); } finally { @@ -155,32 +174,34 @@ export class RunsGridComponent implements OnInit { } let overlayRef: OverlayRef; - setTimeout(() => overlayRef = this.busyService.show()); + setTimeout(() => (overlayRef = this.busyService.show())); try { - const data = await this.attService.typedClient.PortalAttestationRun.Get(this.navigationState); - this.runs = data.Data; - this.dstSettings = { - displayedColumns: [ - this.entitySchema.Columns.UID_AttestationPolicy, - this.entitySchema.Columns.RunCategory, - this.entitySchema.Columns.PolicyProcessed, - this.entitySchema.Columns.DueDate, - this.entitySchema.Columns.PendingCases, - this.entitySchema.Columns.ClosedCases, - this.entitySchema.Columns.Progress - ], - dataSource: data, - entitySchema:this.entitySchema, - navigationState: this.navigationState, - filters: this.filterOptions, - dataModel: this.dataModel, - groupData: this.groupData, - viewConfig: this.viewConfig, - exportMethod: this.getExportMethod(), - }; + const data = await this.runsService.getAttestationRuns(this.navigationState); + if (data) { + this.runs = data.Data; + this.dstSettings = { + displayedColumns: [ + this.entitySchema.Columns.UID_AttestationPolicy, + this.entitySchema.Columns.RunCategory, + this.entitySchema.Columns.PolicyProcessed, + this.entitySchema.Columns.DueDate, + this.entitySchema.Columns.PendingCases, + this.entitySchema.Columns.ClosedCases, + this.entitySchema.Columns.Progress, + ], + dataSource: data, + entitySchema: this.entitySchema, + navigationState: this.navigationState, + filters: this.filterOptions, + dataModel: this.dataModel, + groupData: this.groupData, + viewConfig: this.viewConfig, + exportMethod: this.getExportMethod(), + }; - this.hasPendingAttestations = this.runs?.some(run => run.PendingCases.value > 0); + this.hasPendingAttestations = this.runs?.some((run) => run.PendingCases.value > 0); + } } finally { setTimeout(() => this.busyService.hide(overlayRef)); } @@ -192,38 +213,42 @@ export class RunsGridComponent implements OnInit { getMethod: (withProperties: string, PageSize?: number) => { let method: MethodDescriptor; if (PageSize) { - method = factory.portal_attestation_run_get({...this.navigationState, withProperties, PageSize, StartIndex: 0}); + method = factory.portal_attestation_run_get({ ...this.navigationState, withProperties, PageSize, StartIndex: 0 }); } else { - method = factory.portal_attestation_run_get({...this.navigationState, withProperties}); + method = factory.portal_attestation_run_get({ ...this.navigationState, withProperties }); } return new MethodDefinition(method); - } - } + }, + }; } public async onSearch(keywords: string): Promise { + this.runsService.abortCall(); return this.getData({ PageSize: this.navigationState.PageSize, StartIndex: 0, - search: keywords + search: keywords, }); } public async onRunChanged(run: PortalAttestationRun): Promise { - await this.sideSheet.open(RunSidesheetComponent, { - title: await this.translate.get('#LDS#Heading View Attestation Run Details').toPromise(), - subTitle: run.GetEntity().GetDisplay(), - padding: '0px', - width: 'max(768px, 60%)', - testId: 'runs-grid-view-attestation-run-details', - data: { - run: await this.runsService.getSingleRun(run.GetEntity().GetKeys()[0]), - attestationRunConfig: this.attestationRunConfig, - canSeeAttestationPolicies: this.canSeeAttestationPolicies, - threshold: this.progressCalcThreshold, - completed: this.isCompleted(run) - } - }).afterClosed().toPromise(); + await this.sideSheet + .open(RunSidesheetComponent, { + title: await this.translate.get('#LDS#Heading View Attestation Run Details').toPromise(), + subTitle: run.GetEntity().GetDisplay(), + padding: '0px', + width: 'max(768px, 60%)', + testId: 'runs-grid-view-attestation-run-details', + data: { + run: await this.runsService.getSingleRun(run.GetEntity().GetKeys()[0]), + attestationRunConfig: this.attestationRunConfig, + canSeeAttestationPolicies: this.canSeeAttestationPolicies, + threshold: this.progressCalcThreshold, + completed: this.isCompleted(run), + }, + }) + .afterClosed() + .toPromise(); await this.getData(); } @@ -233,13 +258,13 @@ export class RunsGridComponent implements OnInit { try { const groupedData = this.groupedData[groupKey]; - groupedData.data = await this.attService.typedClient.PortalAttestationRun.Get(groupedData.navigationState); + groupedData.data = await this.runsService.getAttestationRuns(groupedData.navigationState); groupedData.settings = { displayedColumns: this.dstSettings.displayedColumns, dataModel: this.dstSettings.dataModel, dataSource: groupedData.data, entitySchema: this.dstSettings.entitySchema, - navigationState: groupedData.navigationState + navigationState: groupedData.navigationState, }; } finally { setTimeout(() => this.busyService.hide(overlayRef)); @@ -251,6 +276,6 @@ export class RunsGridComponent implements OnInit { } public isCompleted(run: PortalAttestationRun): boolean { - return (run.ClosedCases.value + run.PendingCases.value) > 0 && run.PendingCases.value === 0; + return run.ClosedCases.value + run.PendingCases.value > 0 && run.PendingCases.value === 0; } } diff --git a/imxweb/projects/att/src/lib/runs/runs.service.ts b/imxweb/projects/att/src/lib/runs/runs.service.ts index 4269f71b8..09b4fff6f 100644 --- a/imxweb/projects/att/src/lib/runs/runs.service.ts +++ b/imxweb/projects/att/src/lib/runs/runs.service.ts @@ -32,6 +32,7 @@ import { TranslateService } from '@ngx-translate/core'; import { AttCaseDataRead, + AttestationConfig, ExtendRunInput, PortalAttestationCase, PortalAttestationRun, @@ -62,6 +63,8 @@ import { SendReminderMailComponent } from './send-reminder-mail.component'; providedIn: 'root', }) export class RunsService { + public abortController = new AbortController(); + private readonly apiClientMethodFactory = new V2ApiClientMethodFactory(); constructor( @@ -179,10 +182,24 @@ export class RunsService { }); } + public async attestationConfig(): Promise { + return await this.attService.client.portal_attestation_config_get(); + } + public getSchemaForCases(): EntitySchema { return this.attService.typedClient.PortalAttestationCase.GetSchema(); } + public get AttestationRunSchema(): EntitySchema { + return this.attService.typedClient.PortalAttestationRun.GetSchema(); + } + + public async getAttestationRuns( + parameters: CollectionLoadParameters + ): Promise> { + return await this.attService.typedClient.PortalAttestationRun.Get(parameters, { signal: this.abortController.signal }); + } + public async getSingleRun(uidRun: string): Promise { const elements = await this.attService.typedClient.PortalAttestationRun.Get({ filter: [ @@ -197,4 +214,9 @@ export class RunsService { return elements.Data[0]; } + + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.component.html b/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.component.html index 04c72193c..f7608b91f 100644 --- a/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.component.html +++ b/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.component.html @@ -7,64 +7,45 @@

- + - - - + + + - + {{ item?.stateBadge?.caption }} - + - + - +
- - @@ -72,31 +53,22 @@

- + +

- -
-
+ \ No newline at end of file diff --git a/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.component.ts b/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.component.ts index b8ccee489..481f0e9d7 100644 --- a/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.component.ts +++ b/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.component.ts @@ -115,7 +115,7 @@ export class RulesViolationsComponent implements OnInit, OnDestroy { this.viewConfig = await this.viewConfigService.getInitialDSTExtension(this.dataModelWrapper.dataModel, this.viewConfigPath); this.dstWrapper = new DataSourceWrapper( - (state) => this.rulesViolationsService.getRulesViolationsApprove(state), + (state, requestOpts) => this.rulesViolationsService.getRulesViolationsApprove(state, requestOpts), [ entitySchema.Columns.UID_Person, entitySchema.Columns.UID_NonCompliance, @@ -159,14 +159,22 @@ export class RulesViolationsComponent implements OnInit, OnDestroy { public async getData(parameter?: CollectionLoadParameters): Promise { const isBusy = this.busyService.beginBusy(); try { - this.dstSettings = await this.dstWrapper.getDstSettings(parameter); - this.dstSettings.exportMethod = this.rulesViolationsService.exportRulesViolations(parameter); - this.dstSettings.viewConfig = this.viewConfig; + const dstSettings = await this.dstWrapper.getDstSettings(parameter, { signal: this.rulesViolationsService.abortController.signal }); + if (dstSettings) { + this.dstSettings = dstSettings; + this.dstSettings.exportMethod = this.rulesViolationsService.exportRulesViolations(parameter); + this.dstSettings.viewConfig = this.viewConfig; + } } finally { isBusy.endBusy(); } } + public onSearch(keywords: string): Promise { + this.rulesViolationsService.abortCall(); + return this.getData({ search: keywords }); + } + public onSelectionChanged(items: RulesViolationsApproval[]): void { this.logger.trace(this, 'selection changed', items); this.selectedRulesViolations = items; diff --git a/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.service.ts b/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.service.ts index 0b66e47a0..f9576997c 100644 --- a/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.service.ts +++ b/imxweb/projects/cpl/src/lib/rules-violations/rules-violations.service.ts @@ -31,6 +31,7 @@ import { TranslateService } from '@ngx-translate/core'; import { ComplianceFeatureConfig, PortalRules, PortalRulesViolations, V2ApiClientMethodFactory } from 'imx-api-cpl'; import { + ApiRequestOptions, CollectionLoadParameters, CompareOperator, DataModel, @@ -56,6 +57,8 @@ import { RulesViolationsLoadParameters } from './rules-violations-load-parameter providedIn: 'root', }) export class RulesViolationsService { + public abortController = new AbortController(); + private busyIndicator: OverlayRef; private busyIndicatorCounter = 0; @@ -104,20 +107,28 @@ export class RulesViolationsService { * @returns a list of {@link PortalRulesViolations|PortalRulesViolationss} */ public async getRulesViolationsApprove( - parameters?: CollectionLoadParameters - ): Promise> { + parameters?: CollectionLoadParameters, + requestOpts?: ApiRequestOptions + ): Promise | undefined> { this.logger.debug(this, `Retrieving all rule violations to approve`); this.logger.trace('Navigation state', parameters); - const collection = await this.cplClient.typedClient.PortalRulesViolations.Get({ - approvable: true, - ...parameters, - }); + const collection = await this.cplClient.typedClient.PortalRulesViolations.Get( + { + approvable: true, + ...parameters, + }, + requestOpts + ); + + if (!collection) { + return undefined; + } const hasRiskIndex = (await this.systemInfoService.get()).PreProps.includes('RISKINDEX'); return { - tableName: collection.tableName, - totalCount: collection.totalCount, - Data: collection.Data.map((item: PortalRulesViolations) => new RulesViolationsApproval(item, hasRiskIndex, this.translate)), + tableName: collection?.tableName, + totalCount: collection?.totalCount, + Data: collection?.Data.map((item: PortalRulesViolations) => new RulesViolationsApproval(item, hasRiskIndex, this.translate)), }; } @@ -169,4 +180,9 @@ export class RulesViolationsService { }); return call.Data[0]; } + + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/cpl/src/lib/rules/rules-sidesheet/violations-per-rule/violations-per-rule.component.html b/imxweb/projects/cpl/src/lib/rules/rules-sidesheet/violations-per-rule/violations-per-rule.component.html index f944ea132..5448bac17 100644 --- a/imxweb/projects/cpl/src/lib/rules/rules-sidesheet/violations-per-rule/violations-per-rule.component.html +++ b/imxweb/projects/cpl/src/lib/rules/rules-sidesheet/violations-per-rule/violations-per-rule.component.html @@ -3,48 +3,28 @@ {{ '#LDS#Here you can get an overview of the violations of the compliance rule.' | translate }} - +
- - + + - - + + - + - + +
- + \ No newline at end of file diff --git a/imxweb/projects/cpl/src/lib/rules/rules-sidesheet/violations-per-rule/violations-per-rule.component.ts b/imxweb/projects/cpl/src/lib/rules/rules-sidesheet/violations-per-rule/violations-per-rule.component.ts index 1010eca0f..11b147588 100644 --- a/imxweb/projects/cpl/src/lib/rules/rules-sidesheet/violations-per-rule/violations-per-rule.component.ts +++ b/imxweb/projects/cpl/src/lib/rules/rules-sidesheet/violations-per-rule/violations-per-rule.component.ts @@ -65,8 +65,11 @@ export class ViolationsPerRuleComponent implements OnInit { }; this.dstWrapper = new DataSourceWrapper( - (state) => - this.rulesViolationsService.getRulesViolationsApprove({ ...state, ...{ uid_compliancerule: this.uidRule, approvable: undefined } }), + (state, requestOpts) => + this.rulesViolationsService.getRulesViolationsApprove( + { ...state, ...{ uid_compliancerule: this.uidRule, approvable: undefined } }, + requestOpts + ), [ this.entitySchema.Columns.UID_Person, this.entitySchema.Columns.State, @@ -132,9 +135,17 @@ export class ViolationsPerRuleComponent implements OnInit { public async getData(parameter?: CollectionLoadParameters): Promise { this.rulesViolationsService.handleOpenLoader(); try { - this.dstSettings = await this.dstWrapper.getDstSettings(parameter); + const dstSettings = await this.dstWrapper.getDstSettings(parameter, { signal: this.rulesViolationsService.abortController.signal }); + if (dstSettings) { + this.dstSettings = dstSettings; + } } finally { this.rulesViolationsService.handleCloseLoader(); } } + + public onSearch(keywords: string): Promise { + this.rulesViolationsService.abortCall(); + return this.getData({ search: keywords }); + } } diff --git a/imxweb/projects/cpl/src/lib/rules/rules.component.html b/imxweb/projects/cpl/src/lib/rules/rules.component.html index ef2f25312..544d2b2c3 100644 --- a/imxweb/projects/cpl/src/lib/rules/rules.component.html +++ b/imxweb/projects/cpl/src/lib/rules/rules.component.html @@ -4,48 +4,45 @@

- +
- - + +
{{ item.GetEntity().GetDisplay() }}
-
{{ item.Description.Column.GetDisplayValue() }}
+
{{ item.Description.Column.GetDisplayValue() + }}
- {{ '#LDS#Deactivated' | translate }} + {{ '#LDS#Deactivated' | translate + }}
- + {{ rule.CountOpen.value + rule.CountClosed.value }} - + - +
-
@@ -55,4 +52,4 @@

-
+
\ No newline at end of file diff --git a/imxweb/projects/cpl/src/lib/rules/rules.component.ts b/imxweb/projects/cpl/src/lib/rules/rules.component.ts index ee27d6cea..de68dea95 100644 --- a/imxweb/projects/cpl/src/lib/rules/rules.component.ts +++ b/imxweb/projects/cpl/src/lib/rules/rules.component.ts @@ -182,20 +182,27 @@ export class RulesComponent implements OnInit { const isBusy = this.busyService.beginBusy(); try { const data = await this.rulesProvider.getRules(this.navigationState); - const exportMethod = this.rulesProvider.exportRules(this.navigationState); - exportMethod.initialColumns = this.displayedColumns.map((col) => col.ColumnName); - this.dstSettings = { - displayedColumns: this.displayedColumns, - dataSource: data, - dataModel: this.dataModel, - entitySchema: this.ruleSchema, - navigationState: this.navigationState, - filters: this.filterOptions, - viewConfig: this.viewConfig, - exportMethod, - }; + if (data) { + const exportMethod = this.rulesProvider.exportRules(this.navigationState); + exportMethod.initialColumns = this.displayedColumns.map((col) => col.ColumnName); + this.dstSettings = { + displayedColumns: this.displayedColumns, + dataSource: data, + dataModel: this.dataModel, + entitySchema: this.ruleSchema, + navigationState: this.navigationState, + filters: this.filterOptions, + viewConfig: this.viewConfig, + exportMethod, + }; + } } finally { isBusy.endBusy(); } } + + public onSearch(keywords: string): Promise { + this.rulesProvider.abortCall(); + return this.navigate({ search: keywords }); + } } diff --git a/imxweb/projects/cpl/src/lib/rules/rules.service.ts b/imxweb/projects/cpl/src/lib/rules/rules.service.ts index 7422cecd2..262587b39 100644 --- a/imxweb/projects/cpl/src/lib/rules/rules.service.ts +++ b/imxweb/projects/cpl/src/lib/rules/rules.service.ts @@ -28,22 +28,28 @@ import { OverlayRef } from '@angular/cdk/overlay'; import { Injectable } from '@angular/core'; import { EuiLoadingService } from '@elemental-ui/core'; import { ComplianceFeatureConfig, PortalRules, V2ApiClientMethodFactory } from 'imx-api-cpl'; -import { } from 'imx-api-cpl'; -import { CollectionLoadParameters, DataModel, EntityCollectionData, EntitySchema, ExtendedTypedEntityCollection, MethodDefinition, MethodDescriptor } from 'imx-qbm-dbts'; +import {} from 'imx-api-cpl'; +import { + CollectionLoadParameters, + DataModel, + EntityCollectionData, + EntitySchema, + ExtendedTypedEntityCollection, + MethodDefinition, + MethodDescriptor, +} from 'imx-qbm-dbts'; import { AppConfigService, DataSourceToolbarExportMethod } from 'qbm'; import { ApiService } from '../api.service'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class RulesService { + public abortController = new AbortController(); + private busyIndicator: OverlayRef; - constructor( - private apiservice: ApiService, - private busyService: EuiLoadingService, - private appConfig: AppConfigService - ) { } + constructor(private apiservice: ApiService, private busyService: EuiLoadingService, private appConfig: AppConfigService) {} public get ruleSchema(): EntitySchema { return this.apiservice.typedClient.PortalRules.GetSchema(); @@ -53,9 +59,8 @@ export class RulesService { return this.apiservice.client.portal_compliance_config_get(); } - public async getRules(parameter?: CollectionLoadParameters) - : Promise> { - return this.apiservice.typedClient.PortalRules.Get(parameter); + public async getRules(parameter?: CollectionLoadParameters): Promise> { + return this.apiservice.typedClient.PortalRules.Get(parameter, { signal: this.abortController.signal }); } public exportRules(parameter: CollectionLoadParameters): DataSourceToolbarExportMethod { @@ -64,13 +69,13 @@ export class RulesService { getMethod: (withProperties: string, PageSize?: number) => { let method: MethodDescriptor; if (PageSize) { - method = factory.portal_rules_get({...parameter, withProperties, PageSize, StartIndex: 0}) + method = factory.portal_rules_get({ ...parameter, withProperties, PageSize, StartIndex: 0 }); } else { - method = factory.portal_rules_get({...parameter, withProperties}) + method = factory.portal_rules_get({ ...parameter, withProperties }); } return new MethodDefinition(method); - } - } + }, + }; } public async getDataModel(): Promise { @@ -101,4 +106,8 @@ export class RulesService { } } + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/pol/src/lib/policies/policies.component.html b/imxweb/projects/pol/src/lib/policies/policies.component.html index 49f9e55e0..1a2adc828 100644 --- a/imxweb/projects/pol/src/lib/policies/policies.component.html +++ b/imxweb/projects/pol/src/lib/policies/policies.component.html @@ -9,7 +9,7 @@

[settings]="dstSettings" [options]="['search', 'filter']" [busyService]="busyService" - (search)="navigate({ search: $event })" + (search)="onSearch($event)" (navigationStateChanged)="navigate($event)" > diff --git a/imxweb/projects/pol/src/lib/policies/policies.component.ts b/imxweb/projects/pol/src/lib/policies/policies.component.ts index fe15744cd..32662e899 100644 --- a/imxweb/projects/pol/src/lib/policies/policies.component.ts +++ b/imxweb/projects/pol/src/lib/policies/policies.component.ts @@ -71,7 +71,7 @@ export class PoliciesComponent implements OnInit { public async ngOnInit(): Promise { this.euiBusysService.show(); try { - this.filterOptions = (await this.policiesProvider.getDataModel()).Filters; + this.filterOptions = (await this.policiesProvider.getDataModel()).Filters || []; this.isMControlPerViolation = (await this.policiesProvider.featureConfig()).MitigatingControlsPerViolation; } finally { this.euiBusysService.hide(); @@ -112,15 +112,22 @@ export class PoliciesComponent implements OnInit { try { const data = await this.policiesProvider.getPolicies(this.navigationState); - this.dstSettings = { - displayedColumns: this.displayedColumns, - dataSource: data, - entitySchema: this.policySchema, - navigationState: this.navigationState, - filters: this.filterOptions, - }; + if (data) { + this.dstSettings = { + displayedColumns: this.displayedColumns, + dataSource: data, + entitySchema: this.policySchema, + navigationState: this.navigationState, + filters: this.filterOptions, + }; + } } finally { isBusy.endBusy(); } } + + public async onSearch(keywords: string): Promise { + this.policiesProvider.abortCall(); + return this.navigate({ search: keywords }); + } } diff --git a/imxweb/projects/pol/src/lib/policies/policies.service.ts b/imxweb/projects/pol/src/lib/policies/policies.service.ts index cc0652ae8..33b65651a 100644 --- a/imxweb/projects/pol/src/lib/policies/policies.service.ts +++ b/imxweb/projects/pol/src/lib/policies/policies.service.ts @@ -34,6 +34,8 @@ import { ApiService } from '../api.service'; providedIn: 'root', }) export class PoliciesService { + public abortController = new AbortController(); + constructor(private apiservice: ApiService, private appConfig: AppConfigService) {} public get policySchema(): EntitySchema { @@ -45,7 +47,7 @@ export class PoliciesService { } public async getPolicies(parameter?: CollectionLoadParameters): Promise> { - return this.apiservice.typedClient.PortalPolicies.Get(parameter); + return this.apiservice.typedClient.PortalPolicies.Get(parameter, { signal: this.abortController.signal }); } public async getDataModel(): Promise { @@ -65,4 +67,9 @@ export class PoliciesService { public async getMitigatingControls(uid: string): Promise> { return this.apiservice.typedClient.PortalPoliciesMitigatingcontrols.Get(uid); } + + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/pol/src/lib/policy-violations/policy-violations.component.ts b/imxweb/projects/pol/src/lib/policy-violations/policy-violations.component.ts index 76d672048..aefa9c4e8 100644 --- a/imxweb/projects/pol/src/lib/policy-violations/policy-violations.component.ts +++ b/imxweb/projects/pol/src/lib/policy-violations/policy-violations.component.ts @@ -173,6 +173,7 @@ export class PolicyViolationsComponent implements OnInit { } public async search(search: string): Promise { + this.policyViolationsService.abortCall(); return this.getData({ ...this.navigationState, ...{ search } }); } @@ -219,19 +220,21 @@ export class PolicyViolationsComponent implements OnInit { this.filterOptions = this.filterOptions.filter((filter) => filter.Name !== 'uid_qerpolicy'); } const dataSource = await this.policyViolationsService.get(this.approveOnly, this.navigationState); - const exportMethod = this.policyViolationsService.exportPolicyViolations(this.navigationState); - exportMethod.initialColumns = this.displayedColumns.map((col) => col.ColumnName); - this.dstSettings = { - dataSource, - entitySchema: this.entitySchema, - navigationState: this.navigationState, - filters: this.filterOptions, - dataModel: this.dataModel, - groupData: this.groupData, - viewConfig: this.viewConfig, - exportMethod, - displayedColumns: this.displayedColumns, - }; + if (dataSource) { + const exportMethod = this.policyViolationsService.exportPolicyViolations(this.navigationState); + exportMethod.initialColumns = this.displayedColumns.map((col) => col.ColumnName); + this.dstSettings = { + dataSource, + entitySchema: this.entitySchema, + navigationState: this.navigationState, + filters: this.filterOptions, + dataModel: this.dataModel, + groupData: this.groupData, + viewConfig: this.viewConfig, + exportMethod, + displayedColumns: this.displayedColumns, + }; + } } finally { isBusy.endBusy(); } diff --git a/imxweb/projects/pol/src/lib/policy-violations/policy-violations.service.ts b/imxweb/projects/pol/src/lib/policy-violations/policy-violations.service.ts index 35720329b..d8b163078 100644 --- a/imxweb/projects/pol/src/lib/policy-violations/policy-violations.service.ts +++ b/imxweb/projects/pol/src/lib/policy-violations/policy-violations.service.ts @@ -33,6 +33,7 @@ import { Subject } from 'rxjs'; import { PolicyConfig, PortalPoliciesViolations, PortalPoliciesViolationslist, V2ApiClientMethodFactory } from 'imx-api-pol'; import { DecisionInput } from 'imx-api-qer'; import { + ApiRequestOptions, CollectionLoadParameters, DataModel, EntityCollectionData, @@ -56,6 +57,7 @@ import { PolicyViolation } from './policy-violation'; providedIn: 'root', }) export class PolicyViolationsService { + public abortController = new AbortController(); public readonly applied = new Subject(); constructor( @@ -77,7 +79,7 @@ export class PolicyViolationsService { return this.api.typedClient.PortalPoliciesViolations.GetSchema(); } - public async getConfig(): Promise{ + public async getConfig(): Promise { return this.api.client.portal_policy_config_get(); } @@ -85,8 +87,21 @@ export class PolicyViolationsService { return this.api.client.portal_policies_violations_datamodel_get(); } - public async get(isApprovable: boolean, polDecisionParameters?: CollectionLoadParameters): Promise> { - const collection = await this.api.typedClient.PortalPoliciesViolationslist.Get_list({approvable: isApprovable, ...polDecisionParameters}); + public async get( + isApprovable: boolean, + polDecisionParameters?: CollectionLoadParameters, + reqestOpts?: ApiRequestOptions + ): Promise | undefined> { + const collection = await this.api.typedClient.PortalPoliciesViolationslist.Get_list( + { + approvable: isApprovable, + ...polDecisionParameters, + }, + reqestOpts + ); + if (!collection) { + return undefined; + } return { tableName: collection.tableName, totalCount: collection.totalCount, @@ -103,20 +118,20 @@ export class PolicyViolationsService { getMethod: (withProperties: string, PageSize?: number) => { let method: MethodDescriptor; if (PageSize) { - method = factory.portal_policies_violations_list_get({...polDecisionParameters, withProperties, PageSize, StartIndex: 0}) + method = factory.portal_policies_violations_list_get({ ...polDecisionParameters, withProperties, PageSize, StartIndex: 0 }); } else { - method = factory.portal_policies_violations_list_get({...polDecisionParameters, withProperties}) + method = factory.portal_policies_violations_list_get({ ...polDecisionParameters, withProperties }); } return new MethodDefinition(method); - } - } + }, + }; } public getGroupInfo(parameters: { by?: string; def?: string } & CollectionLoadParameters = {}): Promise { - const {withProperties, OrderBy, search, ...params }= parameters; + const { withProperties, OrderBy, search, ...params } = parameters; return this.api.client.portal_policies_violations_group_list_get({ - ...params, - withcount: true + ...params, + withcount: true, }); } @@ -203,8 +218,8 @@ export class PolicyViolationsService { return this.api.typedClient.PortalPoliciesViolations.Get(uidObject); } - public async getCandidates(uid:string,data: EntityKeysData,parameter?: CollectionLoadParameters){ - return this.api.client.portal_policies_violations_UID_MitigatingControl_candidates_post(uid,data,parameter); + public async getCandidates(uid: string, data: EntityKeysData, parameter?: CollectionLoadParameters) { + return this.api.client.portal_policies_violations_UID_MitigatingControl_candidates_post(uid, data, parameter); } public createMitigatingControl(uid: string): PortalPoliciesViolations { @@ -225,6 +240,11 @@ export class PolicyViolationsService { } } + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } + private async makeDecision(pwo: PolicyViolation, decision: DecisionInput): Promise { await this.api.client.portal_policies_violations_approve_post(pwo.GetEntity().GetKeys()[0], decision); } @@ -240,16 +260,17 @@ export class PolicyViolationsService { } private async editAction(config: any): Promise { - const result = await this.sideSheet.open(PolicyViolationsActionComponent, { - title: await this.translate.get(config.title).toPromise(), - subTitle: config.data.policyViolations.length === 1 - ? config.data.policyViolations[0].GetEntity().GetDisplay() - : '', - padding: '0', - width: '600px', - testId: `policy-violations-action-${config.data.approve ? 'approve' : 'deny'}`, - data: config.data - }).afterClosed().toPromise(); + const result = await this.sideSheet + .open(PolicyViolationsActionComponent, { + title: await this.translate.get(config.title).toPromise(), + subTitle: config.data.policyViolations.length === 1 ? config.data.policyViolations[0].GetEntity().GetDisplay() : '', + padding: '0', + width: '600px', + testId: `policy-violations-action-${config.data.approve ? 'approve' : 'deny'}`, + data: config.data, + }) + .afterClosed() + .toPromise(); if (result) { let busyIndicator: OverlayRef; @@ -260,7 +281,7 @@ export class PolicyViolationsService { for (const policyViolation of config.data.policyViolations) { await config.apply(policyViolation); } - success = true; + success = true; await this.userService.reloadPendingItems(); } finally { setTimeout(() => this.busyService.hide(busyIndicator)); diff --git a/imxweb/projects/qbm/src/lib/cdr/edit-fk/edit-fk.component.ts b/imxweb/projects/qbm/src/lib/cdr/edit-fk/edit-fk.component.ts index 0a027be34..91a37f358 100644 --- a/imxweb/projects/qbm/src/lib/cdr/edit-fk/edit-fk.component.ts +++ b/imxweb/projects/qbm/src/lib/cdr/edit-fk/edit-fk.component.ts @@ -486,53 +486,56 @@ export class EditFkComponent implements CdrEditor, AfterViewInit, OnDestroy, OnI } this.parameters = { ...this.parameters, ...newState }; const candidateCollection = await this.selectedTable.Get(this.parameters); - this.candidatesTotalCount = candidateCollection?.TotalCount; - - this.isHierarchical = candidateCollection.Hierarchy != null; - - const multipleFkRelations = this.columnContainer.fkRelations && this.columnContainer.fkRelations.length > 1; - const identityRelatedTable = this.selectedTable.TableName === 'Person'; - - const newCandidates = candidateCollection.Entities?.map((entityData) => { - let key: string = ''; - let detailValue: string = entityData.LongDisplay ?? ''; - const defaultEmailColumn = entityData.Columns?.['DefaultEmailAddress']; - /** - * If the candidates data relate to identities (fkRelation Person table) - * then we want to use the email address for the detail line (displayLong) - */ - if (defaultEmailColumn && identityRelatedTable) { - detailValue = defaultEmailColumn.Value; - } - if (multipleFkRelations) { - this.logger.trace(this, 'dynamic foreign key'); - const xObjectKeyColumn = entityData.Columns?.['XObjectKey']; - key = xObjectKeyColumn ? xObjectKeyColumn.Value : undefined; - } else { - this.logger.trace(this, 'foreign key'); - const parentColumn = entityData.Columns ? entityData.Columns[this.columnContainer.fkRelations[0].ColumnName] : undefined; - if (parentColumn != null) { - this.logger.trace(this, 'Use value from explicit parent column'); - key = parentColumn.Value; + if (candidateCollection) { + this.candidatesTotalCount = candidateCollection?.TotalCount; + + this.isHierarchical = candidateCollection.Hierarchy != null; + + const multipleFkRelations = this.columnContainer.fkRelations && this.columnContainer.fkRelations.length > 1; + const identityRelatedTable = this.selectedTable.TableName === 'Person'; + + const newCandidates = candidateCollection.Entities?.map((entityData) => { + let key: string = ''; + let detailValue: string = entityData.LongDisplay ?? ''; + const defaultEmailColumn = entityData.Columns?.['DefaultEmailAddress']; + /** + * If the candidates data relate to identities (fkRelation Person table) + * then we want to use the email address for the detail line (displayLong) + */ + if (defaultEmailColumn && identityRelatedTable) { + detailValue = defaultEmailColumn.Value; + } + if (multipleFkRelations) { + this.logger.trace(this, 'dynamic foreign key'); + const xObjectKeyColumn = entityData.Columns?.['XObjectKey']; + key = xObjectKeyColumn ? xObjectKeyColumn.Value : undefined; } else { - this.logger.trace(this, 'Use the primary key'); - const keys = entityData.Keys; - key = keys && keys.length ? keys[0] : ''; + this.logger.trace(this, 'foreign key'); + + const parentColumn = entityData.Columns ? entityData.Columns[this.columnContainer.fkRelations[0].ColumnName] : undefined; + if (parentColumn != null) { + this.logger.trace(this, 'Use value from explicit parent column'); + key = parentColumn.Value; + } else { + this.logger.trace(this, 'Use the primary key'); + const keys = entityData.Keys; + key = keys && keys.length ? keys[0] : ''; + } } + return { + DataValue: key, + DisplayValue: entityData.Display, + displayLong: detailValue, + }; + }); + if (concatCandidates) { + this.candidates.push(...(newCandidates || [])); + this.savedCandidates = this.candidates; + } else { + this.candidates = newCandidates || []; + this.savedCandidates = this.candidates; } - return { - DataValue: key, - DisplayValue: entityData.Display, - displayLong: detailValue, - }; - }); - if (concatCandidates) { - this.candidates.push(...(newCandidates || [])); - this.savedCandidates = this.candidates; - } else { - this.candidates = newCandidates || []; - this.savedCandidates = this.candidates; } } finally { this.loading = false; diff --git a/imxweb/projects/qbm/src/lib/cdr/editor-base.ts b/imxweb/projects/qbm/src/lib/cdr/editor-base.ts index 47e9483bc..e9abb4243 100644 --- a/imxweb/projects/qbm/src/lib/cdr/editor-base.ts +++ b/imxweb/projects/qbm/src/lib/cdr/editor-base.ts @@ -194,7 +194,7 @@ export abstract class EditorBase implements CdrEditor, OnDestroy { * @param value the new value */ private async writeValue(value: any): Promise { - if (this.control.errors && Object.keys(this.control.errors).some((elem) => elem !== 'generalError')) { + if (this.control.errors) { this.logger.debug(this, 'writeValue - client validation failed'); return; } diff --git a/imxweb/projects/qbm/src/lib/fk-advanced-picker/fk-selector.component.ts b/imxweb/projects/qbm/src/lib/fk-advanced-picker/fk-selector.component.ts index c064a2e17..e3c47a287 100644 --- a/imxweb/projects/qbm/src/lib/fk-advanced-picker/fk-selector.component.ts +++ b/imxweb/projects/qbm/src/lib/fk-advanced-picker/fk-selector.component.ts @@ -79,7 +79,7 @@ export class FkSelectorComponent implements OnInit { constructor( public readonly metadataProvider: MetadataService, private readonly settingsService: SettingsService, - private readonly logger: ClassloggerService, + private readonly logger: ClassloggerService ) {} public async ngOnInit(): Promise { @@ -193,18 +193,21 @@ export class FkSelectorComponent implements OnInit { displayedColumns.push(DisplayColumns.DISPLAY_PROPERTY); - this.settings = { - dataSource: this.builder.buildReadWriteEntities(await this.selectedTable.Get(navigationState), this.entitySchema), - displayedColumns, - entitySchema: this.entitySchema, - filters: this.filters, - dataModel: this.dataModel, - navigationState, - filterTree: { - multiSelect: true, - filterMethode: async (parentKey) => this.selectedTable.GetFilterTree(parentKey), - }, - }; + const data = this.builder.buildReadWriteEntities(await this.selectedTable.Get(navigationState), this.entitySchema); + if (data) { + this.settings = { + dataSource: data, + displayedColumns, + entitySchema: this.entitySchema, + filters: this.filters, + dataModel: this.dataModel, + navigationState, + filterTree: { + multiSelect: true, + filterMethode: async (parentKey) => this.selectedTable.GetFilterTree(parentKey), + }, + }; + } } finally { isBusy.endBusy(); } diff --git a/imxweb/projects/qer/src/lib/addressbook/addressbook.component.ts b/imxweb/projects/qer/src/lib/addressbook/addressbook.component.ts index c94b32d0f..615ed4f0f 100644 --- a/imxweb/projects/qer/src/lib/addressbook/addressbook.component.ts +++ b/imxweb/projects/qer/src/lib/addressbook/addressbook.component.ts @@ -78,7 +78,11 @@ export class AddressbookComponent implements OnInit { 'address-book' ); - this.dstSettings = await this.dstWrapper.getDstSettings({ PageSize: this.settingsService.DefaultPageSize, StartIndex: 0 }); + this.addressbookService.abortCall(); + this.dstSettings = await this.dstWrapper.getDstSettings( + { PageSize: this.settingsService.DefaultPageSize, StartIndex: 0 }, + { signal: this.addressbookService.abortController.signal } + ); } finally { isBusy.endBusy(); } @@ -143,10 +147,17 @@ export class AddressbookComponent implements OnInit { } public async onSearch(search: string): Promise { + this.addressbookService.abortCall(); const isBusy = this.busyService.beginBusy(); try { - this.dstSettings = await this.dstWrapper.getDstSettings({ StartIndex: 0, search }); + const dstSettings = await this.dstWrapper.getDstSettings( + { StartIndex: 0, search }, + { signal: this.addressbookService.abortController.signal } + ); + if (dstSettings) { + this.dstSettings = dstSettings; + } } finally { isBusy.endBusy(); } diff --git a/imxweb/projects/qer/src/lib/addressbook/addressbook.service.ts b/imxweb/projects/qer/src/lib/addressbook/addressbook.service.ts index f506a97ed..85f5b1b77 100644 --- a/imxweb/projects/qer/src/lib/addressbook/addressbook.service.ts +++ b/imxweb/projects/qer/src/lib/addressbook/addressbook.service.ts @@ -33,28 +33,29 @@ import { PersonService } from '../person/person.service'; import { AddressbookDetail } from './addressbook-detail/addressbook-detail.interface'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class AddressbookService { - constructor(private readonly personService: PersonService) { } + public abortController = new AbortController(); - public async createDataSourceWrapper(columnNames: string[], identifier?: string): Promise { + constructor(private readonly personService: PersonService) {} + public async createDataSourceWrapper(columnNames: string[], identifier?: string): Promise { const entitySchema = this.personService.schemaPersonAll; const displayedColumns = columnNames - .filter(columnName => entitySchema.Columns[columnName]) - .map(columnName => entitySchema.Columns[columnName]); + .filter((columnName) => entitySchema.Columns[columnName]) + .map((columnName) => entitySchema.Columns[columnName]); displayedColumns.unshift(entitySchema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME]); return new DataSourceWrapper( - state => this.personService.getAll(state), + (state, requestOpts) => this.personService.getAll(state, requestOpts), displayedColumns, entitySchema, { dataModel: await this.personService.getDataModel(), - getGroupInfo: parameters => this.personService.getGroupInfo(parameters), - groupingFilterOptions: ['withmanager', 'orphaned'] + getGroupInfo: (parameters) => this.personService.getGroupInfo(parameters), + groupingFilterOptions: ['withmanager', 'orphaned'], }, identifier ); @@ -69,9 +70,14 @@ export class AddressbookService { return { columns: columnNames - .filter(columnName => entitySchema.Columns[columnName]) - .map(columnName => personDetailEntity.GetColumn(columnName)), - personUid + .filter((columnName) => entitySchema.Columns[columnName]) + .map((columnName) => personDetailEntity.GetColumn(columnName)), + personUid, }; } + + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/qer/src/lib/delegation/delegation.component.ts b/imxweb/projects/qer/src/lib/delegation/delegation.component.ts index 6da6a3cd5..d51188316 100644 --- a/imxweb/projects/qer/src/lib/delegation/delegation.component.ts +++ b/imxweb/projects/qer/src/lib/delegation/delegation.component.ts @@ -24,29 +24,30 @@ * */ +import { STEPPER_GLOBAL_OPTIONS, StepperSelectionEvent } from '@angular/cdk/stepper'; import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { AbstractControl, UntypedFormControl, UntypedFormGroup, ValidatorFn } from '@angular/forms'; -import { StepperSelectionEvent, STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper'; -import { EuiLoadingService } from '@elemental-ui/core'; import { MatSelectionList, MatSelectionListChange } from '@angular/material/list'; import { PageEvent } from '@angular/material/paginator'; -import { Subscription } from 'rxjs'; -import { distinctUntilChanged, debounceTime } from 'rxjs/operators'; import { MatStep, MatStepper } from '@angular/material/stepper'; +import { EuiLoadingService } from '@elemental-ui/core'; +import { Subscription } from 'rxjs'; +import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; -import { ColumnDependentReference, BaseCdr, EntityService, AuthenticationService, ClassloggerService, BusyService } from 'qbm'; import { - PortalDelegations, - PortalDelegable, - QerProjectConfig, GlobalDelegationInput, + PortalDelegable, + PortalDelegations, PortalDelegationsGlobalRoleclasses, + QerProjectConfig, } from 'imx-api-qer'; +import { AuthenticationService, BaseCdr, BusyService, ClassloggerService, ColumnDependentReference, EntityService } from 'qbm'; import { ProjectConfigurationService } from '../project-configuration/project-configuration.service'; -import { DelegationService } from './delegation.service'; import { UserModelService } from '../user/user-model.service'; +import { DelegationService } from './delegation.service'; import { CollectionLoadParameters } from 'imx-qbm-dbts'; +import moment from 'moment'; import { QerPermissionsService } from '../admin/qer-permissions.service'; /** @@ -453,7 +454,9 @@ export class DelegationComponent implements OnInit, OnDestroy { const schema = this.delegationService.getDelegationSchema(); this.cdrTimeSpan = [schema.Columns.InsertValidFrom, schema.Columns.InsertValidUntil].map((property) => { property.IsReadOnly = false; - return new BaseCdr(this.entityService.createLocalEntityColumn(property, undefined, { ValueConstraint: { MinValue: new Date() } })); + return new BaseCdr( + this.entityService.createLocalEntityColumn(property, undefined, { ValueConstraint: { MinValue: moment().startOf('day') } }) + ); }); } diff --git a/imxweb/projects/qer/src/lib/identities/identities.component.ts b/imxweb/projects/qer/src/lib/identities/identities.component.ts index d30d47d98..a79b6bc76 100644 --- a/imxweb/projects/qer/src/lib/identities/identities.component.ts +++ b/imxweb/projects/qer/src/lib/identities/identities.component.ts @@ -373,23 +373,26 @@ export class DataExplorerIdentitiesComponent implements OnInit, OnDestroy, SideN const data = this.isAdmin ? await this.identitiesService.getAllPersonAdmin(this.navigationState) : await this.identitiesService.getReportsOfManager(this.navigationState); - const exportMethod: DataSourceToolbarExportMethod = this.isAdmin - ? this.identitiesService.exportAdminPerson(this.navigationState) - : this.identitiesService.exportPerson(this.navigationState); - exportMethod.initialColumns = this.displayedColumns.map((col) => col.ColumnName); - - this.dstSettings = { - displayedColumns: this.displayedColumns, - dataSource: data, - entitySchema: this.entitySchemaPersonReports, - navigationState: this.navigationState, - filters: this.filterOptions, - groupData: this.groupingInfo, - dataModel: this.dataModel, - viewConfig: this.viewConfig, - exportMethod, - }; - this.logger.debug(this, `Head at ${data.Data.length + this.navigationState.StartIndex} of ${data.totalCount} item(s)`); + + if (data) { + const exportMethod: DataSourceToolbarExportMethod = this.isAdmin + ? this.identitiesService.exportAdminPerson(this.navigationState) + : this.identitiesService.exportPerson(this.navigationState); + exportMethod.initialColumns = this.displayedColumns.map((col) => col.ColumnName); + + this.dstSettings = { + displayedColumns: this.displayedColumns, + dataSource: data, + entitySchema: this.entitySchemaPersonReports, + navigationState: this.navigationState, + filters: this.filterOptions, + groupData: this.groupingInfo, + dataModel: this.dataModel, + viewConfig: this.viewConfig, + exportMethod, + }; + this.logger.debug(this, `Head at ${data?.Data?.length + this.navigationState?.StartIndex} of ${data.totalCount} item(s)`); + } } finally { isBusy.endBusy(); } diff --git a/imxweb/projects/qer/src/lib/identities/identities.service.ts b/imxweb/projects/qer/src/lib/identities/identities.service.ts index c782f995b..bda909e02 100644 --- a/imxweb/projects/qer/src/lib/identities/identities.service.ts +++ b/imxweb/projects/qer/src/lib/identities/identities.service.ts @@ -42,21 +42,29 @@ import { MethodDefinition, MethodDescriptor, } from 'imx-qbm-dbts'; -import { PortalPersonReports, PortalPersonAll, PortalAdminPerson, PortalPersonUid, ViewConfigData, V2ApiClientMethodFactory } from 'imx-api-qer'; +import { + PortalPersonReports, + PortalPersonAll, + PortalAdminPerson, + PortalPersonUid, + ViewConfigData, + V2ApiClientMethodFactory, +} from 'imx-api-qer'; import { QerApiService } from '../qer-api-client.service'; import { QerPermissionsService } from '../admin/qer-permissions.service'; import { DuplicateCheckParameter } from './create-new-identity/duplicate-check-parameter.interface'; @Injectable() export class IdentitiesService { - public authorityDataDeleted: Subject = new Subject(); + private abortController = new AbortController(); constructor( private readonly qerClient: QerApiService, private readonly logger: ClassloggerService, - private readonly qerPermissions: QerPermissionsService) { } + private readonly qerPermissions: QerPermissionsService + ) {} public get personReportsSchema(): EntitySchema { return this.qerClient.typedClient.PortalPersonReports.GetSchema(); @@ -74,14 +82,13 @@ export class IdentitiesService { return this.qerClient.typedClient.PortalAdminPerson.GetSchema(); } - - public getAttestationHelperAlertDescription(count: { total: number; forUser: number; }): { description: string; value?: any; }[] { + public getAttestationHelperAlertDescription(count: { total: number; forUser: number }): { description: string; value?: any }[] { // #LDS#There are currently no pending attestation cases. return [ { description: '#LDS#Here you can get an overview of all attestations cases for this object.' }, { description: '#LDS#Pending attestation cases: {0}', value: count.total }, - { description: '#LDS#Pending attestation cases you can approve or deny: {0}', value: count.forUser } + { description: '#LDS#Pending attestation cases you can approve or deny: {0}', value: count.forUser }, ]; } @@ -93,15 +100,23 @@ export class IdentitiesService { * @returns Wrapped list of Persons. */ public async getAllPerson(navigationState: CollectionLoadParameters): Promise> { + if (navigationState?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } this.logger.debug(this, `Retrieving person list`); this.logger.trace('Navigation state', navigationState); - return this.qerClient.typedClient.PortalPersonAll.Get(navigationState); + return this.qerClient.typedClient.PortalPersonAll.Get(navigationState, { signal: this.abortController.signal }); } public async getAllPersonAdmin(navigationState: CollectionLoadParameters): Promise> { + if (navigationState?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } this.logger.debug(this, `Retrieving person list`); this.logger.trace('Navigation state', navigationState); - return this.qerClient.typedClient.PortalAdminPerson.Get(navigationState); + return this.qerClient.typedClient.PortalAdminPerson.Get(navigationState, { signal: this.abortController.signal }); } /** @@ -122,13 +137,13 @@ export class IdentitiesService { getMethod: (withProperties: string, PageSize?: number) => { let method: MethodDescriptor; if (PageSize) { - method = factory.portal_admin_person_get({...navigationState, withProperties, PageSize, StartIndex: 0}) + method = factory.portal_admin_person_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); } else { - method = factory.portal_admin_person_get({...navigationState, withProperties}) + method = factory.portal_admin_person_get({ ...navigationState, withProperties }); } return new MethodDefinition(method); - } - } + }, + }; } public exportPerson(navigationState?: CollectionLoadParameters): DataSourceToolbarExportMethod { @@ -137,13 +152,13 @@ export class IdentitiesService { getMethod: (withProperties: string, PageSize?: number) => { let method: MethodDescriptor; if (PageSize) { - method = factory.portal_person_reports_get({...navigationState, withProperties, PageSize, StartIndex: 0 }); + method = factory.portal_person_reports_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); } else { - method = factory.portal_person_reports_get({...navigationState, withProperties}); + method = factory.portal_person_reports_get({ ...navigationState, withProperties }); } return new MethodDefinition(method); - } - } + }, + }; } /** @@ -153,30 +168,31 @@ export class IdentitiesService { * * @returns Wrapped list of Persons. */ - public async getReportsOfManager(navigationState: CollectionLoadParameters): - Promise> { + public async getReportsOfManager(navigationState: CollectionLoadParameters): Promise> { + if (navigationState?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } this.logger.debug(this, `Retrieving reports of the manager`); this.logger.trace('Navigation state', navigationState); - return this.qerClient.typedClient.PortalPersonReports.Get(navigationState); + return this.qerClient.typedClient.PortalPersonReports.Get(navigationState, { signal: this.abortController.signal }); } public async getGroupedAllPerson(columns: string, navigationState: CollectionLoadParameters): Promise { this.logger.debug(this, `Retrieving person list`); this.logger.trace('Navigation state', navigationState); - return this.qerClient.client.portal_admin_person_group_get( - { - by: columns, - def: '', - StartIndex: navigationState.StartIndex, - PageSize: navigationState.PageSize, - withcount: true, - withmanager: '', - orphaned: '', - deletedintarget: '', - isinactive: '' - } - ); + return this.qerClient.client.portal_admin_person_group_get({ + by: columns, + def: '', + StartIndex: navigationState.StartIndex, + PageSize: navigationState.PageSize, + withcount: true, + withmanager: '', + orphaned: '', + deletedintarget: '', + isinactive: '', + }); } public async userIsAdmin(): Promise { @@ -220,8 +236,7 @@ export class IdentitiesService { public buildFilterForduplicates(parameter: DuplicateCheckParameter): FilterData[] { const filter = []; - if (parameter.firstName != null && parameter.firstName !== '' - && parameter.lastName != null && parameter.lastName !== '') { + if (parameter.firstName != null && parameter.firstName !== '' && parameter.lastName != null && parameter.lastName !== '') { filter.push(this.buildFilter('FirstName', parameter.firstName)); filter.push(this.buildFilter('LastName', parameter.lastName)); } @@ -237,9 +252,7 @@ export class IdentitiesService { return filter; } - public async getDuplicates(parameter: CollectionLoadParameters) - : Promise>> { - + public async getDuplicates(parameter: CollectionLoadParameters): Promise>> { if (parameter.filter?.length === 0) { return { Data: [], totalCount: 0 }; } @@ -251,7 +264,12 @@ export class IdentitiesService { CompareOp: CompareOperator.Equal, Type: FilterType.Compare, ColumnName: column, - Value1: value + Value1: value, }; } + + private abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern-sidesheet/itshop-pattern-sidesheet.component.html b/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern-sidesheet/itshop-pattern-sidesheet.component.html index cf465f7f8..a1dd98456 100644 --- a/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern-sidesheet/itshop-pattern-sidesheet.component.html +++ b/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern-sidesheet/itshop-pattern-sidesheet.component.html @@ -28,7 +28,7 @@
; @@ -88,11 +87,12 @@ export class ItshopPatternSidesheetComponent implements OnInit, OnDestroy { constructor( formBuilder: UntypedFormBuilder, - @Inject(EUI_SIDESHEET_DATA) public data: { - pattern: PortalItshopPatternPrivate - isMyPattern: boolean, - adminMode: boolean, - canEditAndDelete: boolean + @Inject(EUI_SIDESHEET_DATA) + public data: { + pattern: PortalItshopPatternPrivate; + isMyPattern: boolean; + adminMode: boolean; + canEditAndDelete: boolean; }, private readonly translate: TranslateService, private readonly patternService: ItshopPatternService, @@ -107,10 +107,9 @@ export class ItshopPatternSidesheetComponent implements OnInit, OnDestroy { this.detailsFormGroup = new UntypedFormGroup({ formArray: formBuilder.array([]) }); this.closeSubscription = this.sideSheetRef.closeClicked().subscribe(async () => { - if (!this.detailsFormGroup.dirty - || await confirmation.confirmLeaveWithUnsavedChanges()) { - this.data.pattern.GetEntity().DiscardChanges(); - this.sideSheetRef.close(); + if (!this.detailsFormGroup.dirty || (await confirmation.confirmLeaveWithUnsavedChanges())) { + this.data.pattern.GetEntity().DiscardChanges(); + this.sideSheetRef.close(); } }); } @@ -144,9 +143,12 @@ export class ItshopPatternSidesheetComponent implements OnInit, OnDestroy { const parameters = { ...parameter, - ...filteredState + ...filteredState, }; - this.dstSettings = await this.dstWrapper.getDstSettings(parameters); + const dstSettings = await this.dstWrapper.getDstSettings(parameters, { signal: this.patternService.abortController.signal }); + if (dstSettings) { + this.dstSettings = dstSettings; + } } finally { isBusy.endBusy(); } @@ -161,6 +163,11 @@ export class ItshopPatternSidesheetComponent implements OnInit, OnDestroy { } } + public onSearch(keywords: string): Promise { + this.patternService.abortCall(); + return this.getData({ search: keywords }); + } + public onSelectionChanged(items: PortalItshopPatternItem[]): void { this.logger.trace(this, 'selection changed', items); this.selectedPatternItems = items; @@ -172,10 +179,12 @@ export class ItshopPatternSidesheetComponent implements OnInit, OnDestroy { } public async delete(): Promise { - if (await this.confirmation.confirm({ - Title: '#LDS#Heading Delete Product Bundle', - Message: '#LDS#Are you sure you want to delete the product bundle?' - })) { + if ( + await this.confirmation.confirm({ + Title: '#LDS#Heading Delete Product Bundle', + Message: '#LDS#Are you sure you want to delete the product bundle?', + }) + ) { if (await this.patternService.delete([this.data.pattern])) { this.sideSheetRef.close(ItShopPatternChangedType.Deleted); } @@ -190,18 +199,20 @@ export class ItshopPatternSidesheetComponent implements OnInit, OnDestroy { } public async addProducts(): Promise { - - const result = await this.sidesheet.open(ItshopPatternAddProductsComponent, { - title: await this.translate.get('#LDS#Heading Add Products To Product Bundle').toPromise(), - subTitle: this.data.pattern.Ident_ShoppingCartPattern.value, - panelClass: 'imx-sidesheet', - padding: '0', - width: 'max(768px, 70%)', - testId: 'pattern-add-products-sidesheet', - data: { - shoppingCartPatternUid: this.shoppingCartPatternUid - } - }).afterClosed().toPromise(); + const result = await this.sidesheet + .open(ItshopPatternAddProductsComponent, { + title: await this.translate.get('#LDS#Heading Add Products To Product Bundle').toPromise(), + subTitle: this.data.pattern.Ident_ShoppingCartPattern.value, + panelClass: 'imx-sidesheet', + padding: '0', + width: 'max(768px, 70%)', + testId: 'pattern-add-products-sidesheet', + data: { + shoppingCartPatternUid: this.shoppingCartPatternUid, + }, + }) + .afterClosed() + .toPromise(); if (result) { const snackBarMessage = '#LDS#The selected products have been successfully added to the product bundle.'; @@ -211,8 +222,7 @@ export class ItshopPatternSidesheetComponent implements OnInit, OnDestroy { } public selectedItemsCanBeDeleted(): boolean { - return this.selectedPatternItems != null - && this.selectedPatternItems.length > 0; + return this.selectedPatternItems != null && this.selectedPatternItems.length > 0; } public async createPrivateCopy(): Promise { @@ -239,7 +249,6 @@ export class ItshopPatternSidesheetComponent implements OnInit, OnDestroy { } public async editPatternItem(selectedItem: PortalItshopPatternItem): Promise { - let projectConfig: QerProjectConfig & ProjectConfig; let serviceItem: PortalShopServiceitems; @@ -251,35 +260,32 @@ export class ItshopPatternSidesheetComponent implements OnInit, OnDestroy { this.patternService.handleCloseLoader(); } - this.sidesheet.open(ItshopPatternItemEditComponent, - { - title: await this.translate.get('#LDS#Heading View Product Details').toPromise(), - subTitle: selectedItem.GetEntity().GetDisplay(), - padding: '0px', - width: '600px', - testId: 'itshop-pattern-item-edit-sidesheet', - data: { - patternItemUid: selectedItem.GetEntity().GetKeys().join(''), - serviceItem, - projectConfig - } - } - ); + this.sidesheet.open(ItshopPatternItemEditComponent, { + title: await this.translate.get('#LDS#Heading View Product Details').toPromise(), + subTitle: selectedItem.GetEntity().GetDisplay(), + padding: '0px', + width: '600px', + testId: 'itshop-pattern-item-edit-sidesheet', + data: { + patternItemUid: selectedItem.GetEntity().GetKeys().join(''), + serviceItem, + projectConfig, + }, + }); } private async setupDetailsTab(): Promise { - if (this.data.canEditAndDelete) { this.cdrList = [ new BaseCdr(this.data.pattern.Ident_ShoppingCartPattern.Column), new BaseCdr(this.data.pattern.Description.Column), - new BaseCdr(this.data.pattern.UID_Person.Column) + new BaseCdr(this.data.pattern.UID_Person.Column), ]; } else { this.cdrList = [ new BaseReadonlyCdr(this.data.pattern.Ident_ShoppingCartPattern.Column), new BaseReadonlyCdr(this.data.pattern.Description.Column), - new BaseReadonlyCdr(this.data.pattern.UID_Person.Column) + new BaseReadonlyCdr(this.data.pattern.UID_Person.Column), ]; } } @@ -287,10 +293,8 @@ export class ItshopPatternSidesheetComponent implements OnInit, OnDestroy { private setupProductsTab(): void { const entitySchema = this.patternService.itshopPatternItemSchema; this.dstWrapper = new DataSourceWrapper( - state => this.patternService.getPatternItems(state), - [ - entitySchema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME] - ], + (state, requestOpts) => this.patternService.getPatternItems(state, requestOpts), + [entitySchema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME]], entitySchema ); } diff --git a/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern.component.html b/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern.component.html index 1b3cd3bfc..8731de8aa 100644 --- a/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern.component.html +++ b/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern.component.html @@ -10,8 +10,9 @@

; public readonly status = { - enabled: (pattern: PortalItshopPatternAdmin): boolean => this.canBeEditedAndDeleted(pattern) + enabled: (pattern: PortalItshopPatternAdmin): boolean => this.canBeEditedAndDeleted(pattern), }; public helpContextId: HelpContextualValues; @@ -98,44 +100,35 @@ export class ItshopPatternComponent implements OnInit, OnDestroy { private readonly confirmationService: ConfirmationService, authentication: AuthenticationService ) { - this.subscriptions.push( - authentication.onSessionResponse.subscribe((sessionState) => - this.currentUserUid = sessionState.UserUid - ) - ); + this.subscriptions.push(authentication.onSessionResponse.subscribe((sessionState) => (this.currentUserUid = sessionState.UserUid))); } public async ngOnInit(): Promise { - this.patternService.handleOpenLoader(); + const isBusy = this.busyService.beginBusy(); try { this.adminMode = await this.qerPermissionService.isShopAdmin(); - this.helpContextId = this.adminMode ? HELP_CONTEXTUAL.RequestTemplates: HELP_CONTEXTUAL.RequestTemplatesUser; + this.helpContextId = this.adminMode ? HELP_CONTEXTUAL.RequestTemplates : HELP_CONTEXTUAL.RequestTemplatesUser; - const entitySchema = this.adminMode - ? this.patternService.itshopPatternAdminSchema - : this.patternService.itshopPatternPrivateSchema; + const entitySchema = this.adminMode ? this.patternService.itshopPatternAdminSchema : this.patternService.itshopPatternPrivateSchema; this.dstWrapper = new DataSourceWrapper( - state => this.adminMode - ? this.patternService.getPublicPatterns(state) - : this.patternService.getPrivatePatterns(state), - [ - entitySchema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME], - entitySchema.Columns.UID_Person, - entitySchema.Columns.IsPublicPattern, - ], + (state, requestOpts) => + this.adminMode + ? this.patternService.getPublicPatterns(state, requestOpts) + : this.patternService.getPrivatePatterns(state, requestOpts), + [entitySchema.Columns[DisplayColumns.DISPLAY_PROPERTYNAME], entitySchema.Columns.UID_Person, entitySchema.Columns.IsPublicPattern], entitySchema, undefined, 'itshop-pattern' ); } finally { - this.patternService.handleCloseLoader(); + isBusy.endBusy(); } await this.getData(); } public ngOnDestroy(): void { - this.subscriptions.forEach(s => s.unsubscribe()); + this.subscriptions.forEach((s) => s.unsubscribe()); } public isMyPattern(pattern: PortalItshopPatternPrivate | PortalItshopPatternAdmin): boolean { @@ -147,10 +140,12 @@ export class ItshopPatternComponent implements OnInit, OnDestroy { } public async delete(selectedPattern?: PortalItshopPatternPrivate | PortalItshopPatternAdmin): Promise { - if (await this.confirmationService.confirm({ - Title: '#LDS#Heading Delete Product Bundles', - Message: '#LDS#Are you sure you want to delete the selected product bundles?' - })) { + if ( + await this.confirmationService.confirm({ + Title: '#LDS#Heading Delete Product Bundles', + Message: '#LDS#Are you sure you want to delete the selected product bundles?', + }) + ) { await this.patternService.delete(selectedPattern ? [selectedPattern] : this.selectedPatterns, this.adminMode); this.getData(); this.table.clearSelection(); @@ -175,35 +170,49 @@ export class ItshopPatternComponent implements OnInit, OnDestroy { } } - public async getData(parameter?: CollectionLoadParameters): Promise { - this.patternService.handleOpenLoader(); + public async getData(parameter?: CollectionLoadParameters): Promise { + const isBusy = this.busyService.beginBusy(); try { const parameters = { ...parameter, - ...{ OrderBy: 'Ident_ShoppingCartPattern asc' } + ...{ OrderBy: 'Ident_ShoppingCartPattern asc' }, }; - this.dstSettings = await this.dstWrapper.getDstSettings(parameters); + const dstSettings = await this.dstWrapper.getDstSettings(parameters, { signal: this.patternService.abortController.signal }); + if (dstSettings) { + this.dstSettings = dstSettings; + } } finally { - this.patternService.handleCloseLoader(); + isBusy.endBusy(); } } + public onSearch(keywords: string): Promise { + this.patternService.abortCall(); + return this.getData({ search: keywords }); + } + public selectedItemsCanBePublished(): boolean { - return this.selectedPatterns != null - && this.selectedPatterns.length > 0 - && this.selectedPatterns.every(item => this.isMyPattern(item) && !item.IsPublicPattern.value); + return ( + this.selectedPatterns != null && + this.selectedPatterns.length > 0 && + this.selectedPatterns.every((item) => this.isMyPattern(item) && !item.IsPublicPattern.value) + ); } public selectedItemsCanBeUnpublished(): boolean { - return this.selectedPatterns != null - && this.selectedPatterns.length > 0 - && this.selectedPatterns.every(item => this.isMyPattern(item) && item.IsPublicPattern.value); + return ( + this.selectedPatterns != null && + this.selectedPatterns.length > 0 && + this.selectedPatterns.every((item) => this.isMyPattern(item) && item.IsPublicPattern.value) + ); } public selectedItemsCanBeDeleted(): boolean { - return this.selectedPatterns != null - && this.selectedPatterns.length > 0 - && this.selectedPatterns.every(item => this.canBeEditedAndDeleted(item)); + return ( + this.selectedPatterns != null && + this.selectedPatterns.length > 0 && + this.selectedPatterns.every((item) => this.canBeEditedAndDeleted(item)) + ); } public onSelectionChanged(items: PortalItshopPatternAdmin[]): void { @@ -221,28 +230,31 @@ export class ItshopPatternComponent implements OnInit, OnDestroy { const pattern = isMyPattern ? await this.patternService.getPrivatePattern(selectedPattern.GetEntity().GetKeys()[0]) : (await this.patternService.getPublicPatterns()).Data.find( - (pattern) => pattern.GetEntity().GetKeys()[0] === selectedPattern.GetEntity().GetKeys()[0], + (pattern) => pattern.GetEntity().GetKeys()[0] === selectedPattern.GetEntity().GetKeys()[0] ); - const title = await this.translate.get(canEditAndDelete - ? '#LDS#Heading Edit Product Bundle' - : '#LDS#Heading View Product Bundle Details').toPromise(); - - const result = await this.sidesheet.open(ItshopPatternSidesheetComponent, { - title, - subTitle: pattern.Ident_ShoppingCartPattern.value, - panelClass: 'imx-sidesheet', - disableClose: true, - padding: '0', - width: '600px', - testId: 'pattern-details-sidesheet', - data: { - pattern, - isMyPattern, - adminMode: this.adminMode, - canEditAndDelete - } - }).afterClosed().toPromise(); + const title = await this.translate + .get(canEditAndDelete ? '#LDS#Heading Edit Product Bundle' : '#LDS#Heading View Product Bundle Details') + .toPromise(); + + const result = await this.sidesheet + .open(ItshopPatternSidesheetComponent, { + title, + subTitle: pattern.Ident_ShoppingCartPattern.value, + panelClass: 'imx-sidesheet', + disableClose: true, + padding: '0', + width: '600px', + testId: 'pattern-details-sidesheet', + data: { + pattern, + isMyPattern, + adminMode: this.adminMode, + canEditAndDelete, + }, + }) + .afterClosed() + .toPromise(); if (result === ItShopPatternChangedType.Saved) { const snackBarMessage = '#LDS#The product bundle has been successfully saved.'; @@ -251,6 +263,5 @@ export class ItshopPatternComponent implements OnInit, OnDestroy { } else if (result) { this.getData(); } - } } diff --git a/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern.service.ts b/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern.service.ts index c31ec87ad..424f61301 100644 --- a/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern.service.ts +++ b/imxweb/projects/qer/src/lib/itshop-pattern/itshop-pattern.service.ts @@ -29,7 +29,19 @@ import { ErrorHandler, Injectable } from '@angular/core'; import { EuiLoadingService } from '@elemental-ui/core'; import { PortalItshopPatternAdmin, PortalItshopPatternItem, PortalItshopPatternPrivate } from 'imx-api-qer'; -import { CollectionLoadParameters, CompareOperator, EntitySchema, ExtendedTypedEntityCollection, FilterType, FkProviderItem, IFkCandidateProvider, InteractiveEntityWriteData, ParameterData, TypedEntity } from 'imx-qbm-dbts'; +import { + ApiRequestOptions, + CollectionLoadParameters, + CompareOperator, + EntitySchema, + ExtendedTypedEntityCollection, + FilterType, + FkProviderItem, + IFkCandidateProvider, + InteractiveEntityWriteData, + ParameterData, + TypedEntity, +} from 'imx-qbm-dbts'; import { ClassloggerService, SnackBarService } from 'qbm'; import { ExtendedEntityWrapper } from '../parameter-data/extended-entity-wrapper.interface'; @@ -37,9 +49,10 @@ import { QerApiService } from '../qer-api-client.service'; import { RequestParametersService } from '../shopping-cart/cart-item-edit/request-parameters.service'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class ItshopPatternService { + public abortController = new AbortController(); private busyIndicator: OverlayRef; private busyIndicatorCounter = 0; @@ -50,7 +63,8 @@ export class ItshopPatternService { private readonly logger: ClassloggerService, private readonly busyService: EuiLoadingService, private readonly errorHandler: ErrorHandler, - private readonly snackBar: SnackBarService) { } + private readonly snackBar: SnackBarService + ) {} public get itshopPatternAdminSchema(): EntitySchema { return this.qerClient.typedClient.PortalItshopPatternAdmin.GetSchema(); @@ -64,17 +78,18 @@ export class ItshopPatternService { return this.qerClient.typedClient.PortalItshopPatternItem.GetSchema(); } - /** * Retrieves all private itshop patterns of a person. * * @returns A list of {@link PortalItshopPatternPrivate} entities. */ - public async getPrivatePatterns(navigationState?: CollectionLoadParameters): - Promise> { + public async getPrivatePatterns( + navigationState?: CollectionLoadParameters, + requestOpts?: ApiRequestOptions + ): Promise> { this.logger.debug(this, `Retrieving private itshop patterns`); this.logger.trace('Navigation state', navigationState); - return this.qerClient.typedClient.PortalItshopPatternPrivate.Get(navigationState); + return this.qerClient.typedClient.PortalItshopPatternPrivate.Get(navigationState, requestOpts); } /** @@ -102,11 +117,13 @@ export class ItshopPatternService { * * @returns A list of {@link PortalItshopPatternAdmin} entities. */ - public async getPatternItems(navigationState?: CollectionLoadParameters): - Promise> { + public async getPatternItems( + navigationState?: CollectionLoadParameters, + requestOpts?: ApiRequestOptions + ): Promise> { this.logger.debug(this, `Retrieving public itshop patterns`); this.logger.trace('Navigation state', navigationState); - return this.qerClient.typedClient.PortalItshopPatternItem.Get(navigationState); + return this.qerClient.typedClient.PortalItshopPatternItem.Get(navigationState, requestOpts); } /** @@ -114,11 +131,13 @@ export class ItshopPatternService { * * @returns A list of {@link PortalItshopPatternAdmin} entities. */ - public async getPublicPatterns(navigationState?: CollectionLoadParameters): - Promise> { + public async getPublicPatterns( + navigationState?: CollectionLoadParameters, + requestOpts?: ApiRequestOptions + ): Promise> { this.logger.debug(this, `Retrieving public itshop patterns`); this.logger.trace('Navigation state', navigationState); - return this.qerClient.typedClient.PortalItshopPatternAdmin.Get(navigationState); + return this.qerClient.typedClient.PortalItshopPatternAdmin.Get(navigationState, requestOpts); } /** @@ -155,9 +174,10 @@ export class ItshopPatternService { } if (deleteCount > 0) { - const message = deleteCount > 1 - ? '#LDS#The selected product bundles have been successfully deleted.' - : '#LDS#The product bundle has been successfully deleted.'; + const message = + deleteCount > 1 + ? '#LDS#The selected product bundles have been successfully deleted.' + : '#LDS#The product bundle has been successfully deleted.'; this.snackBar.open({ key: message }); } } finally { @@ -207,11 +227,11 @@ export class ItshopPatternService { parameterCategoryColumns: this.requestParametersService.createInteractiveParameterCategoryColumns( { Parameters: typedEntity.extendedDataRead?.Parameters, - index + index, }, - parameter => this.getFkProviderItemsInteractive(typedEntity, parameter), + (parameter) => this.getFkProviderItemsInteractive(typedEntity, parameter), typedEntity - ) + ), }; } @@ -219,26 +239,27 @@ export class ItshopPatternService { return entityWrapper.typedEntity.GetEntity().Commit(true); } - public getFkProviderItemsInteractive( interactiveEntity: { InteractiveEntityWriteData: InteractiveEntityWriteData }, parameterData: ParameterData ): IFkCandidateProvider { - const qerClient = this.qerClient; - return new class implements IFkCandidateProvider { + return new (class implements IFkCandidateProvider { getProviderItem(_columnName, fkTableName) { if (parameterData.Property.FkRelation) { - return this.getFkProviderItemInteractive(interactiveEntity, parameterData.Property.ColumnName, parameterData.Property.FkRelation.ParentTableName); + return this.getFkProviderItemInteractive( + interactiveEntity, + parameterData.Property.ColumnName, + parameterData.Property.FkRelation.ParentTableName + ); } if (parameterData.Property.ValidReferencedTables) { - const t = parameterData.Property.ValidReferencedTables.map(parentTableRef => + const t = parameterData.Property.ValidReferencedTables.map((parentTableRef) => this.getFkProviderItemInteractive(interactiveEntity, parameterData.Property.ColumnName, parentTableRef.TableName) - ).filter(t => t.fkTableName == fkTableName); - if (t.length == 1) - return t[0]; + ).filter((t) => t.fkTableName == fkTableName); + if (t.length == 1) return t[0]; return null; } @@ -253,13 +274,7 @@ export class ItshopPatternService { return { columnName, fkTableName, - parameterNames: [ - 'OrderBy', - 'StartIndex', - 'PageSize', - 'filter', - 'search' - ], + parameterNames: ['OrderBy', 'StartIndex', 'PageSize', 'filter', 'search'], load: async (__, parameters?) => { return qerClient.client.portal_itshop_pattern_item_interactive_parameter_candidates_post( columnName, @@ -271,16 +286,17 @@ export class ItshopPatternService { getDataModel: async () => ({}), getFilterTree: async (__, parentkey) => { return qerClient.client.portal_itshop_pattern_item_interactive_parameter_candidates_filtertree_post( - columnName, fkTableName, interactiveEntity.InteractiveEntityWriteData, { parentkey: parentkey } + columnName, + fkTableName, + interactiveEntity.InteractiveEntityWriteData, + { parentkey: parentkey } ); - } + }, }; } - - } + })(); } - /** * Toogle the IsPublicPattern value of a {@link PortalItshopPatternPrivate} and commit the changes to the server * @param uid the uid of itshop pattern that should be toggled. @@ -316,13 +332,12 @@ export class ItshopPatternService { } } const message = shouldBePublic - ? (commitCount === 1 + ? commitCount === 1 ? '#LDS#The product bundle has been shared successfully. The product bundle is now available for all users.' - : '#LDS#The product bundles have been shared successfully. {0} product bundles are now available for all users.') - : (commitCount === 1 - ? '#LDS#Sharing of the product bundle has been successfully undone. The product bundle is now only available for yourself.' - : '#LDS#Sharing of the product bundles has been successfully undone. {0} product bundles are now only available for yourself.' - ); + : '#LDS#The product bundles have been shared successfully. {0} product bundles are now available for all users.' + : commitCount === 1 + ? '#LDS#Sharing of the product bundle has been successfully undone. The product bundle is now only available for yourself.' + : '#LDS#Sharing of the product bundles has been successfully undone. {0} product bundles are now only available for yourself.'; this.snackBar.open({ key: message, parameters: [commitCount] }); } finally { this.handleCloseLoader(); @@ -332,7 +347,7 @@ export class ItshopPatternService { public handleOpenLoader(): void { if (this.busyIndicatorCounter === 0) { - setTimeout(() => this.busyIndicator = this.busyService.show()); + setTimeout(() => (this.busyIndicator = this.busyService.show())); } this.busyIndicatorCounter++; } @@ -347,6 +362,11 @@ export class ItshopPatternService { this.busyIndicatorCounter--; } + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } + private async tryCommit(pattern: PortalItshopPatternPrivate): Promise { try { await pattern.GetEntity().Commit(false); @@ -373,7 +393,7 @@ export class ItshopPatternService { private async tryDeleteProducts(uid: string): Promise { try { - await this.qerClient.typedClient.PortalItshopPatternItem.Delete(uid); + await this.qerClient.typedClient.PortalItshopPatternItem.Delete(uid); return true; } catch (error) { this.errorHandler.handleError(error); diff --git a/imxweb/projects/qer/src/lib/itshopapprove/approvals-table.component.ts b/imxweb/projects/qer/src/lib/itshopapprove/approvals-table.component.ts index dca15aaa9..17069f33c 100644 --- a/imxweb/projects/qer/src/lib/itshopapprove/approvals-table.component.ts +++ b/imxweb/projects/qer/src/lib/itshopapprove/approvals-table.component.ts @@ -333,6 +333,7 @@ export class ApprovalsTableComponent implements OnInit, OnDestroy { } public onSearch(keywords: string): Promise { + this.approvalsService.abortCall(); const navigationState = { ...this.navigationState, ...{ @@ -364,8 +365,6 @@ export class ApprovalsTableComponent implements OnInit, OnDestroy { filters: this.dataModel.Filters, exportMethod }; - } else { - this.dstSettings = undefined; } } diff --git a/imxweb/projects/qer/src/lib/person/person.service.ts b/imxweb/projects/qer/src/lib/person/person.service.ts index 9d1f1d970..6588fdb23 100644 --- a/imxweb/projects/qer/src/lib/person/person.service.ts +++ b/imxweb/projects/qer/src/lib/person/person.service.ts @@ -38,6 +38,7 @@ import { DataModel, GroupInfoData, EntitySchema, + ApiRequestOptions, } from 'imx-qbm-dbts'; import { PortalPersonAll, PortalPersonMasterdata, PortalPersonUid } from 'imx-api-qer'; import { QerApiService } from '../qer-api-client.service'; @@ -47,6 +48,7 @@ import { PersonAllLoadParameters } from './person-all-load-parameters.interface' providedIn: 'root', }) export class PersonService { + public get schemaPersonUid(): EntitySchema { return this.qerClient.typedClient.PortalPersonUid.GetSchema(); } @@ -69,8 +71,8 @@ export class PersonService { return this.qerClient.typedClient.PortalPersonUid.Get(uid, parameters); } - public async getAll(parameters: CollectionLoadParameters = {}): Promise> { - return this.qerClient.typedClient.PortalPersonAll.Get(parameters); + public async getAll(parameters: CollectionLoadParameters = {}, requestOpts: ApiRequestOptions): Promise> { + return this.qerClient.typedClient.PortalPersonAll.Get(parameters, requestOpts); } public async getDataModel(filter?: FilterData[]): Promise { diff --git a/imxweb/projects/qer/src/lib/request-history/request-history.service.ts b/imxweb/projects/qer/src/lib/request-history/request-history.service.ts index aa7215b5d..50986dfef 100644 --- a/imxweb/projects/qer/src/lib/request-history/request-history.service.ts +++ b/imxweb/projects/qer/src/lib/request-history/request-history.service.ts @@ -52,6 +52,8 @@ import { ItshopRequestService } from '../itshop/itshop-request.service'; @Injectable() export class RequestHistoryService { + public abortController = new AbortController(); + constructor(private readonly qerClient: QerApiService, private readonly itshopRequest: ItshopRequestService) {} public get PortalItshopRequestsSchema(): EntitySchema { @@ -62,8 +64,11 @@ export class RequestHistoryService { userUid: string, parameters: RequestHistoryLoadParameters ): Promise> { - const collection = await this.qerClient.typedClient.PortalItshopRequests.Get(parameters); + const collection = await this.qerClient.typedClient.PortalItshopRequests.Get(parameters, { signal: this.abortController.signal }); + if (!collection) { + return undefined; + } return { tableName: collection.tableName, totalCount: collection.totalCount, @@ -97,21 +102,21 @@ export class RequestHistoryService { ): Promise> { const dummy: ArchivedRequestHistoryLoadParameters = {}; recipientId ? (dummy.uidpersonordered = recipientId) : (dummy.uidpersoninserted = userUid); - const collection = await this.qerClient.typedClient.PortalItshopHistoryRequests.Get(new Date(), dummy); + const collection = await this.qerClient.typedClient.PortalItshopHistoryRequests.Get(new Date(), dummy, { signal: this.abortController.signal }); + if (!collection) { + return undefined; + } return { tableName: collection.tableName, totalCount: collection.totalCount, extendedData: collection.extendedData, Data: collection.Data.map((element, index) => { const requestData = new ItshopRequestData({ ...collection.extendedData, ...{ index } }); - const parameterColumns = this.itshopRequest.createParameterColumns( - element.GetEntity(), - requestData.parameters - ); + const parameterColumns = this.itshopRequest.createParameterColumns(element.GetEntity(), requestData.parameters); const request = new ItshopRequest(element.GetEntity(), requestData.pwoData, parameterColumns, userUid); request.isArchived = true; - return request - }) + return request; + }), }; } @@ -186,7 +191,7 @@ export class RequestHistoryService { public async copyRequest(pwo: PortalItshopRequests): Promise { const item = this.qerClient.typedClient.PortalCartitem.createEntity({ Columns: { - UID_AccProduct: {Value:pwo.UID_AccProduct.value} + UID_AccProduct: { Value: pwo.UID_AccProduct.value }, }, }); @@ -197,4 +202,9 @@ export class RequestHistoryService { return item; } + + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/qer/src/lib/request-history/request-table.component.ts b/imxweb/projects/qer/src/lib/request-history/request-table.component.ts index f5b537981..f1cd71eb2 100644 --- a/imxweb/projects/qer/src/lib/request-history/request-table.component.ts +++ b/imxweb/projects/qer/src/lib/request-history/request-table.component.ts @@ -264,6 +264,7 @@ export class RequestTableComponent implements OnInit, OnDestroy, OnChanges { } public onSearch(keywords: string): Promise { + this.requestHistoryService.abortCall(); const navigationState = { ...this.navigationState, ...{ @@ -340,8 +341,6 @@ export class RequestTableComponent implements OnInit, OnDestroy, OnChanges { } else { this.dstSettings = dstSettings; } - } else { - this.dstSettings = undefined; } } finally { busy.endBusy(); diff --git a/imxweb/projects/qer/src/lib/resources/resources.service.ts b/imxweb/projects/qer/src/lib/resources/resources.service.ts index 6550c3988..03b8c758e 100644 --- a/imxweb/projects/qer/src/lib/resources/resources.service.ts +++ b/imxweb/projects/qer/src/lib/resources/resources.service.ts @@ -70,10 +70,17 @@ export class ResourcesService { public static readonly QERReuse = 'QERReuse'; public static readonly QERAssign = 'QERAssign'; - public readonly targets = [ResourcesService.QERResource, ResourcesService.QERReuseUS, ResourcesService.QERReuse, ResourcesService.QERAssign]; + private abortController = new AbortController(); + + public readonly targets = [ + ResourcesService.QERResource, + ResourcesService.QERReuseUS, + ResourcesService.QERReuse, + ResourcesService.QERAssign, + ]; protected config: QerProjectConfig & ProjectConfig; - public editTexts: {[key:string]:string} = {}; + public editTexts: { [key: string]: string } = {}; constructor(protected readonly project: ProjectConfigurationService, private readonly api: QerApiService) { this.registerMap(); @@ -118,12 +125,14 @@ export class ResourcesService { } public async getServiceItem(tablename: string, uidResource: string): Promise { - const filter: FilterData[] = [{ - Type: FilterType.Compare, - CompareOp: CompareOperator.Equal, - ColumnName: 'UID_AccProduct', - Value1: uidResource - }]; + const filter: FilterData[] = [ + { + Type: FilterType.Compare, + CompareOp: CompareOperator.Equal, + ColumnName: 'UID_AccProduct', + Value1: uidResource, + }, + ]; const item = await this.resourceMap.get(tablename).accProduct.Get({ filter }); @@ -135,7 +144,6 @@ export class ResourcesService { this.resourceMap.set(target, { table: target }); }); - const factory = new V2ApiClientMethodFactory(); // QERResource @@ -144,42 +152,56 @@ export class ResourcesService { this.resourceMap.get(ResourcesService.QERResource).accProduct = this.api.typedClient.PortalResourcesQerresourceServiceitem; this.resourceMap.get(ResourcesService.QERResource).admin = { type: PortalAdminResourcesQerresource, - get: async (parameter: any) => this.api.client.portal_admin_resources_qerresource_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_admin_resources_qerresource_get(parameter, { signal: this.abortController.signal }); + }, schema: this.api.typedClient.PortalAdminResourcesQerresource.GetSchema(), dataModel: async (filter: FilterData[]) => this.api.client.portal_admin_resources_qerresource_datamodel_get({ filter }), interactive: this.api.typedClient.PortalAdminResourcesQerresourceInteractive, exportMethod: (navigationState: CollectionLoadParameters) => { - return { getMethod: (withProperties: string, PageSize?: number) => { - let method: MethodDescriptor; - if (PageSize) { - method = factory.portal_admin_resources_qerresource_get({...navigationState, withProperties, PageSize, StartIndex: 0}) - } else { - method = factory.portal_admin_resources_qerresource_get({...navigationState, withProperties}) - } - return new MethodDefinition(method); - } - } - } + return { + getMethod: (withProperties: string, PageSize?: number) => { + let method: MethodDescriptor; + if (PageSize) { + method = factory.portal_admin_resources_qerresource_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); + } else { + method = factory.portal_admin_resources_qerresource_get({ ...navigationState, withProperties }); + } + return new MethodDefinition(method); + }, + }; + }, }; this.resourceMap.get(ResourcesService.QERResource).resp = { type: PortalRespQerresource, - get: async (parameter: any) => this.api.client.portal_resp_qerresource_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_resp_qerresource_get(parameter, { signal: this.abortController.signal }); + }, schema: this.api.typedClient.PortalRespQerresource.GetSchema(), dataModel: async (filter: FilterData[]) => this.api.client.portal_resp_qerresource_datamodel_get({ filter }), interactive: this.api.typedClient.PortalRespQerresourceInteractive, exportMethod: (navigationState: CollectionLoadParameters) => { - return { getMethod: (withProperties: string, PageSize?: number) => { - let method: MethodDescriptor; - if (PageSize) { - method = factory.portal_resp_qerresource_get({...navigationState, withProperties, PageSize, StartIndex: 0}) - } else { - method = factory.portal_resp_qerresource_get({...navigationState, withProperties}) - } - return new MethodDefinition(method); - } - } - } + return { + getMethod: (withProperties: string, PageSize?: number) => { + let method: MethodDescriptor; + if (PageSize) { + method = factory.portal_resp_qerresource_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); + } else { + method = factory.portal_resp_qerresource_get({ ...navigationState, withProperties }); + } + return new MethodDefinition(method); + }, + }; + }, }; // QERReuseUS @@ -189,41 +211,55 @@ export class ResourcesService { this.resourceMap.get(ResourcesService.QERReuseUS).accProduct = this.api.typedClient.PortalResourcesQerreuseusServiceitem; this.resourceMap.get(ResourcesService.QERReuseUS).admin = { type: PortalAdminResourcesQerreuseus, - get: async (parameter: any) => this.api.client.portal_admin_resources_qerreuseus_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_admin_resources_qerreuseus_get(parameter, { signal: this.abortController.signal }); + }, schema: this.api.typedClient.PortalAdminResourcesQerreuseus.GetSchema(), dataModel: async (filter: FilterData[]) => this.api.client.portal_admin_resources_qerreuseus_datamodel_get({ filter }), interactive: this.api.typedClient.PortalAdminResourcesQerreuseusInteractive, exportMethod: (navigationState: CollectionLoadParameters) => { - return { getMethod: (withProperties: string, PageSize?: number) => { - let method: MethodDescriptor; - if (PageSize) { - method = factory.portal_admin_resources_qerreuseus_get({...navigationState, withProperties, PageSize, StartIndex: 0}) - } else { - method = factory.portal_admin_resources_qerreuseus_get({...navigationState, withProperties}) - } - return new MethodDefinition(method); - } - } - } + return { + getMethod: (withProperties: string, PageSize?: number) => { + let method: MethodDescriptor; + if (PageSize) { + method = factory.portal_admin_resources_qerreuseus_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); + } else { + method = factory.portal_admin_resources_qerreuseus_get({ ...navigationState, withProperties }); + } + return new MethodDefinition(method); + }, + }; + }, }; this.resourceMap.get(ResourcesService.QERReuseUS).resp = { type: PortalRespQerreuseus, - get: async (parameter: any) => this.api.client.portal_resp_qerreuseus_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_resp_qerreuseus_get(parameter, { signal: this.abortController.signal }); + }, schema: this.api.typedClient.PortalRespQerreuseus.GetSchema(), dataModel: async (filter: FilterData[]) => this.api.client.portal_resp_qerreuseus_datamodel_get({ filter }), interactive: this.api.typedClient.PortalRespQerreuseusInteractive, exportMethod: (navigationState: CollectionLoadParameters) => { - return { getMethod: (withProperties: string, PageSize?: number) => { - let method: MethodDescriptor; - if (PageSize) { - method = factory.portal_resp_qerreuseus_get({...navigationState, withProperties, PageSize, StartIndex: 0}) - } else { - method = factory.portal_resp_qerreuseus_get({...navigationState, withProperties}) - } - return new MethodDefinition(method); - } - } - } + return { + getMethod: (withProperties: string, PageSize?: number) => { + let method: MethodDescriptor; + if (PageSize) { + method = factory.portal_resp_qerreuseus_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); + } else { + method = factory.portal_resp_qerreuseus_get({ ...navigationState, withProperties }); + } + return new MethodDefinition(method); + }, + }; + }, }; // QERReuse @@ -232,41 +268,55 @@ export class ResourcesService { this.resourceMap.get(ResourcesService.QERReuse).accProduct = this.api.typedClient.PortalResourcesQerreuseServiceitem; this.resourceMap.get(ResourcesService.QERReuse).admin = { type: PortalAdminResourcesQerreuse, - get: async (parameter: any) => this.api.client.portal_admin_resources_qerreuse_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_admin_resources_qerreuse_get(parameter, { signal: this.abortController.signal }); + }, schema: this.api.typedClient.PortalAdminResourcesQerreuse.GetSchema(), dataModel: async (filter: FilterData[]) => this.api.client.portal_admin_resources_qerreuse_datamodel_get({ filter }), interactive: this.api.typedClient.PortalAdminResourcesQerreuseInteractive, exportMethod: (navigationState: CollectionLoadParameters) => { - return { getMethod: (withProperties: string, PageSize?: number) => { - let method: MethodDescriptor; - if (PageSize) { - method = factory.portal_admin_resources_qerreuse_get({...navigationState, withProperties, PageSize, StartIndex: 0}) - } else { - method = factory.portal_admin_resources_qerreuse_get({...navigationState, withProperties}) - } - return new MethodDefinition(method); - } - } - } + return { + getMethod: (withProperties: string, PageSize?: number) => { + let method: MethodDescriptor; + if (PageSize) { + method = factory.portal_admin_resources_qerreuse_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); + } else { + method = factory.portal_admin_resources_qerreuse_get({ ...navigationState, withProperties }); + } + return new MethodDefinition(method); + }, + }; + }, }; this.resourceMap.get(ResourcesService.QERReuse).resp = { type: PortalRespQerreuse, - get: async (parameter: any) => this.api.client.portal_resp_qerreuse_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_resp_qerreuse_get(parameter, { signal: this.abortController.signal }); + }, schema: this.api.typedClient.PortalRespQerreuse.GetSchema(), dataModel: async (filter: FilterData[]) => this.api.client.portal_resp_qerreuse_datamodel_get({ filter }), interactive: this.api.typedClient.PortalRespQerreuseInteractive, exportMethod: (navigationState: CollectionLoadParameters) => { - return { getMethod: (withProperties: string, PageSize?: number) => { - let method: MethodDescriptor; - if (PageSize) { - method = factory.portal_resp_qerreuse_get({...navigationState, withProperties, PageSize, StartIndex: 0}) - } else { - method = factory.portal_resp_qerreuse_get({...navigationState, withProperties}) - } - return new MethodDefinition(method); - } - } - } + return { + getMethod: (withProperties: string, PageSize?: number) => { + let method: MethodDescriptor; + if (PageSize) { + method = factory.portal_resp_qerreuse_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); + } else { + method = factory.portal_resp_qerreuse_get({ ...navigationState, withProperties }); + } + return new MethodDefinition(method); + }, + }; + }, }; // QERAssign @@ -275,47 +325,66 @@ export class ResourcesService { this.resourceMap.get(ResourcesService.QERAssign).accProduct = this.api.typedClient.PortalResourcesQerassignServiceitem; this.resourceMap.get(ResourcesService.QERAssign).admin = { type: PortalAdminResourcesQerassign, - get: async (parameter: any) => this.api.client.portal_admin_resources_qerassign_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_admin_resources_qerassign_get(parameter, { signal: this.abortController.signal }); + }, schema: this.api.typedClient.PortalAdminResourcesQerassign.GetSchema(), dataModel: async (filter: FilterData[]) => this.api.client.portal_admin_resources_qerassign_datamodel_get({ filter }), interactive: this.api.typedClient.PortalAdminResourcesQerassignInteractive, exportMethod: (navigationState: CollectionLoadParameters) => { - return { getMethod: (withProperties: string, PageSize?: number) => { - let method: MethodDescriptor; - if (PageSize) { - method = factory.portal_admin_resources_qerassign_get({...navigationState, withProperties, PageSize, StartIndex: 0}) - } else { - method = factory.portal_admin_resources_qerassign_get({...navigationState, withProperties}) - } - return new MethodDefinition(method); - } - } - } + return { + getMethod: (withProperties: string, PageSize?: number) => { + let method: MethodDescriptor; + if (PageSize) { + method = factory.portal_admin_resources_qerassign_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); + } else { + method = factory.portal_admin_resources_qerassign_get({ ...navigationState, withProperties }); + } + return new MethodDefinition(method); + }, + }; + }, }; this.resourceMap.get(ResourcesService.QERAssign).resp = { type: PortalRespQerassign, - get: async (parameter: any) => this.api.client.portal_resp_qerassign_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_resp_qerassign_get(parameter, { signal: this.abortController.signal }); + }, schema: this.api.typedClient.PortalRespQerassign.GetSchema(), dataModel: async (filter: FilterData[]) => this.api.client.portal_resp_qerassign_datamodel_get({ filter }), interactive: this.api.typedClient.PortalRespQerassignInteractive, exportMethod: (navigationState: CollectionLoadParameters) => { - return { getMethod: (withProperties: string, PageSize?: number) => { - let method: MethodDescriptor; - if (PageSize) { - method = factory.portal_resp_qerassign_get({...navigationState, withProperties, PageSize, StartIndex: 0}) - } else { - method = factory.portal_resp_qerassign_get({...navigationState, withProperties}) - } - return new MethodDefinition(method); - } - } - } + return { + getMethod: (withProperties: string, PageSize?: number) => { + let method: MethodDescriptor; + if (PageSize) { + method = factory.portal_resp_qerassign_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); + } else { + method = factory.portal_resp_qerassign_get({ ...navigationState, withProperties }); + } + return new MethodDefinition(method); + }, + }; + }, }; } public getExportMethod(tableName: string, isAdmin: boolean, navigationState: CollectionLoadParameters): DataSourceToolbarExportMethod { return isAdmin ? this.resourceMap.get(tableName).admin.exportMethod(navigationState) - : this.resourceMap.get(tableName).resp.exportMethod(navigationState) + : this.resourceMap.get(tableName).resp.exportMethod(navigationState); + } + + private abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); } } diff --git a/imxweb/projects/qer/src/lib/role-management/role.service.ts b/imxweb/projects/qer/src/lib/role-management/role.service.ts index 3eb09c50b..999915fb0 100644 --- a/imxweb/projects/qer/src/lib/role-management/role.service.ts +++ b/imxweb/projects/qer/src/lib/role-management/role.service.ts @@ -88,6 +88,8 @@ export class RoleService { private readonly targets = [this.LocalityTag, this.ProfitCenterTag, this.DepartmentTag, this.AERoleTag]; + private abortController = new AbortController(); + constructor( protected readonly api: QerApiService, public readonly session: imx_SessionService, @@ -153,13 +155,31 @@ export class RoleService { // Role Objects for Admin (useable by tree) this.targetMap.get(this.LocalityTag).admin = { - get: async (parameter: any) => this.api.client.portal_admin_role_locality_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_admin_role_locality_get(parameter, { signal: this.abortController.signal }); + }, }; this.targetMap.get(this.ProfitCenterTag).admin = { - get: async (parameter: any) => this.api.client.portal_admin_role_profitcenter_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_admin_role_profitcenter_get(parameter, { signal: this.abortController.signal }); + }, }; this.targetMap.get(this.DepartmentTag).admin = { - get: async (parameter: any) => this.api.client.portal_admin_role_department_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_admin_role_department_get(parameter, { signal: this.abortController.signal }); + }, }; // Entity Schema for Admin @@ -390,7 +410,7 @@ export class RoleService { navigationState?: CollectionLoadParameters ): Promise> { if (this.exists(tableName)) { - return isAdmin ? await this.getEntities(tableName, navigationState) : await this.targetMap.get(tableName).resp.Get(navigationState); + return isAdmin ? await this.getEntities(tableName, navigationState) : await this.getRespEntities(tableName, navigationState); } return null; } @@ -646,6 +666,17 @@ export class RoleService { return builder.buildReadWriteEntities(data, this.targetMap.get(tableName).adminSchema); } + private async getRespEntities( + tableName: string, + navigationState: CollectionLoadParameters + ): Promise> { + if (navigationState?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return await this.targetMap.get(tableName).resp.Get(navigationState, { signal: this.abortController.signal }); + } + public getSplitTargets(): string[] { return [...this.targetMap].filter((m) => m[1].canBeSplitTarget).map((m) => m[0]); } @@ -653,4 +684,9 @@ export class RoleService { public getRoleTranslateKeys(tableName: string): RoleTranslateKeys { return this.targetMap.get(tableName).translateKeys; } + + private abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/qer/src/lib/role-management/roles-overview/roles-overview.component.ts b/imxweb/projects/qer/src/lib/role-management/roles-overview/roles-overview.component.ts index a3b80e323..afa38fa36 100644 --- a/imxweb/projects/qer/src/lib/role-management/roles-overview/roles-overview.component.ts +++ b/imxweb/projects/qer/src/lib/role-management/roles-overview/roles-overview.component.ts @@ -114,7 +114,7 @@ export class RolesOverviewComponent implements OnInit, OnDestroy, SideNavigation private readonly translate: TranslateService, private readonly permission: QerPermissionsService, private readonly errorService: ErrorService, - private readonly userModelService: UserModelService, + private readonly userModelService: UserModelService ) {} public ngOnDestroy(): void { @@ -341,16 +341,19 @@ export class RolesOverviewComponent implements OnInit, OnDestroy, SideNavigation if (this.exportMethod) { this.exportMethod.initialColumns = this.displayColumns.map((col) => col.ColumnName); } - this.dstSettings = { - dataSource: await this.roleService.get(this.ownershipInfo.TableName, this.isAdmin, this.navigationState), - entitySchema: this.entitySchema, - navigationState: this.navigationState, - displayedColumns: this.displayColumns, - filters: this.filterOptions, - dataModel: this.dataModel, - viewConfig: this.viewConfig, - exportMethod: this.exportMethod, - }; + const dataSource = await this.roleService.get(this.ownershipInfo.TableName, this.isAdmin, this.navigationState); + if (dataSource) { + this.dstSettings = { + dataSource: dataSource, + entitySchema: this.entitySchema, + navigationState: this.navigationState, + displayedColumns: this.displayColumns, + filters: this.filterOptions, + dataModel: this.dataModel, + viewConfig: this.viewConfig, + exportMethod: this.exportMethod, + }; + } } public async updateConfig(config: ViewConfigData): Promise { diff --git a/imxweb/projects/qer/src/lib/service-items/service-items.service.ts b/imxweb/projects/qer/src/lib/service-items/service-items.service.ts index f5c71fc82..597635478 100644 --- a/imxweb/projects/qer/src/lib/service-items/service-items.service.ts +++ b/imxweb/projects/qer/src/lib/service-items/service-items.service.ts @@ -26,33 +26,42 @@ import { Injectable } from '@angular/core'; +import { PortalShopServiceitems, RequestableProductForPerson, ServiceItemsExtendedData } from 'imx-api-qer'; import { - PortalShopServiceitems, RequestableProductForPerson, ServiceItemsExtendedData -} from 'imx-api-qer'; -import { - CollectionLoadParameters, ExtendedTypedEntityCollection, FilterType, CompareOperator, ValueStruct, TypedEntity, EntitySchema, DataModel + CollectionLoadParameters, + ExtendedTypedEntityCollection, + FilterType, + CompareOperator, + ValueStruct, + TypedEntity, + EntitySchema, + DataModel, } from 'imx-qbm-dbts'; import { QerApiService } from '../qer-api-client.service'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class ServiceItemsService { - constructor(private readonly qerClient: QerApiService) { } + public abortController = new AbortController(); + + constructor(private readonly qerClient: QerApiService) {} public get PortalShopServiceItemsSchema(): EntitySchema { return this.qerClient.typedClient.PortalShopServiceitems.GetSchema(); } - public async get(parameters: CollectionLoadParameters & { - UID_Person?: string; - UID_AccProductGroup?: string; - IncludeChildCategories?: boolean; - UID_AccProductParent?: string; - UID_PersonReference?: string; - UID_PersonPeerGroup?: string; - }): Promise> { - return this.qerClient.typedClient.PortalShopServiceitems.Get(parameters); + public async get( + parameters: CollectionLoadParameters & { + UID_Person?: string; + UID_AccProductGroup?: string; + IncludeChildCategories?: boolean; + UID_AccProductParent?: string; + UID_PersonReference?: string; + UID_PersonPeerGroup?: string; + } + ): Promise> { + return this.qerClient.typedClient.PortalShopServiceitems.Get(parameters, { signal: this.abortController.signal }); } public async getServiceItem(serviceItemUid: string, isSkippable?: boolean): Promise { @@ -63,9 +72,9 @@ export class ServiceItemsService { ColumnName: 'UID_AccProduct', Type: FilterType.Compare, CompareOp: CompareOperator.Equal, - Value1: serviceItemUid - } - ] + Value1: serviceItemUid, + }, + ], }); if (serviceItemCollection == null || serviceItemCollection.Data == null || serviceItemCollection.Data.length === 0) { @@ -78,7 +87,7 @@ export class ServiceItemsService { return serviceItemCollection.Data[0]; } - public async getDataModel(): Promise{ + public async getDataModel(): Promise { return this.qerClient.client.portal_shop_serviceitems_datamodel_get(undefined); } @@ -86,31 +95,32 @@ export class ServiceItemsService { serviceItems: PortalShopServiceitems[], recipients: ValueStruct[], additionalArgs?: { - uidITShopOrg?: string + uidITShopOrg?: string; } ): RequestableProductForPerson[] { - return serviceItems.map((serviceItem) => { - const key = serviceItem.GetEntity().GetKeys()[0]; - return recipients.map(recipient => { - const requestableProductForPerson: RequestableProductForPerson = { - UidPerson: recipient.DataValue, - UidITShopOrg: additionalArgs?.uidITShopOrg, - UidAccProduct: key, - Display: serviceItem.GetEntity().GetDisplay(), - DisplayRecipient: recipient.DisplayValue - }; - return requestableProductForPerson; - }); - }).reduce((a, b) => a.concat(b), []); + return serviceItems + .map((serviceItem) => { + const key = serviceItem.GetEntity().GetKeys()[0]; + return recipients.map((recipient) => { + const requestableProductForPerson: RequestableProductForPerson = { + UidPerson: recipient.DataValue, + UidITShopOrg: additionalArgs?.uidITShopOrg, + UidAccProduct: key, + Display: serviceItem.GetEntity().GetDisplay(), + DisplayRecipient: recipient.DisplayValue, + }; + return requestableProductForPerson; + }); + }) + .reduce((a, b) => a.concat(b), []); } public async updateServiceCategory(prev: TypedEntity[], current: TypedEntity[], serviceCategoryUid?: string): Promise { if (current?.length > 0) { - const add = prev?.length > 0 ? - current.filter(selectedItem => - prev.find(item => this.getKey(item) === this.getKey(selectedItem)) == null - ) : - current; + const add = + prev?.length > 0 + ? current.filter((selectedItem) => prev.find((item) => this.getKey(item) === this.getKey(selectedItem)) == null) + : current; if (add.length > 0) { await this.setServiceCategory(add, serviceCategoryUid); @@ -118,11 +128,10 @@ export class ServiceItemsService { } if (prev?.length > 0) { - const remove = current?.length > 0 ? - prev.filter(selectedItem => - current.find(item => this.getKey(item) === this.getKey(selectedItem)) == null - ) : - prev; + const remove = + current?.length > 0 + ? prev.filter((selectedItem) => current.find((item) => this.getKey(item) === this.getKey(selectedItem)) == null) + : prev; if (remove.length > 0) { await this.setServiceCategory(remove); @@ -130,14 +139,20 @@ export class ServiceItemsService { } } + public abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } private async setServiceCategory(serviceItems: TypedEntity[], serviceCategoryUid?: string): Promise { return this.qerClient.client.portal_serviceitems_bulk_put({ - Keys: serviceItems.map(item => [this.getKey(item)]), - Data: [{ - Name: "UID_AccProductGroup", - Value: serviceCategoryUid - }] + Keys: serviceItems.map((item) => [this.getKey(item)]), + Data: [ + { + Name: 'UID_AccProductGroup', + Value: serviceCategoryUid, + }, + ], }); } diff --git a/imxweb/projects/qer/src/lib/service-items/serviceitem-list/serviceitem-list.component.ts b/imxweb/projects/qer/src/lib/service-items/serviceitem-list/serviceitem-list.component.ts index 3e97958b7..5dd0655d6 100644 --- a/imxweb/projects/qer/src/lib/service-items/serviceitem-list/serviceitem-list.component.ts +++ b/imxweb/projects/qer/src/lib/service-items/serviceitem-list/serviceitem-list.component.ts @@ -80,11 +80,11 @@ export class ServiceitemListComponent implements AfterViewInit, OnChanges, OnDes @Input() public referenceUserUid: string; @Input() public uidPersonPeerGroup: string; @Input() public dataSourceView = { selected: 'cardlist' }; - @Input() public itemActions: DataTileMenuItem[]; + @Input() public itemActions: DataTileMenuItem[]; @Input() public patternItemsMode: boolean = false; @Output() public selectionChanged = new EventEmitter(); - @Output() public handleAction = new EventEmitter<{ item: PortalShopServiceitems, name: string }>(); + @Output() public handleAction = new EventEmitter<{ item: PortalShopServiceitems; name: string }>(); @Output() public categoryRemoved = new EventEmitter(); @Output() public readonly openCategoryTree = new EventEmitter(); @@ -103,7 +103,7 @@ export class ServiceitemListComponent implements AfterViewInit, OnChanges, OnDes getImagePath: async (prod: PortalShopServiceitems): Promise => this.image.getPath(prod), }; public peerGroupSize: number; - + public busyService = new BusyService(); public get options(): string[] { @@ -122,7 +122,7 @@ export class ServiceitemListComponent implements AfterViewInit, OnChanges, OnDes private readonly dialog: MatDialog, private readonly image: ImageService, private readonly translate: TranslateService, - settingsService: SettingsService, + settingsService: SettingsService ) { this.navigationState = { PageSize: settingsService.DefaultPageSize, StartIndex: 0 }; this.entitySchema = serviceItemsProvider.PortalShopServiceItemsSchema; @@ -132,7 +132,7 @@ export class ServiceitemListComponent implements AfterViewInit, OnChanges, OnDes ColumnName: 'actions', Type: ValType.String, afterAdditionals: true, - untranslatedDisplay: '#LDS#Actions' + untranslatedDisplay: '#LDS#Actions', }, ]; } @@ -169,6 +169,7 @@ export class ServiceitemListComponent implements AfterViewInit, OnChanges, OnDes } public async onSearch(keywords: string): Promise { + this.serviceItemsProvider.abortCall(); const navigationState = { PageSize: this.navigationState.PageSize, StartIndex: 0, @@ -210,7 +211,7 @@ export class ServiceitemListComponent implements AfterViewInit, OnChanges, OnDes displayedColumns: this.displayedColumns, entitySchema: this.entitySchema, navigationState: this.navigationState, - dataModel: this.dataModel + dataModel: this.dataModel, }; this.peerGroupSize = data.extendedData?.PeerGroupSize; @@ -228,8 +229,6 @@ export class ServiceitemListComponent implements AfterViewInit, OnChanges, OnDes } else { this.noDataText = '#LDS#No data'; } - } else { - this.dstSettings = undefined; } } finally { isBusy.endBusy(); @@ -259,7 +258,7 @@ export class ServiceitemListComponent implements AfterViewInit, OnChanges, OnDes } public emitAction(item: DataTileMenuItem, serviceItem?: PortalShopServiceitems): void { - this.handleAction.emit({ item: serviceItem ?? item.typedEntity as PortalShopServiceitems, name: item.name }); + this.handleAction.emit({ item: serviceItem ?? (item.typedEntity as PortalShopServiceitems), name: item.name }); } public async onRemoveChip(): Promise { diff --git a/imxweb/projects/qer/src/lib/shopping-cart/cart-item-edit/cart-item-fk.service.ts b/imxweb/projects/qer/src/lib/shopping-cart/cart-item-edit/cart-item-fk.service.ts index fffdeff71..c224e6bb6 100644 --- a/imxweb/projects/qer/src/lib/shopping-cart/cart-item-edit/cart-item-fk.service.ts +++ b/imxweb/projects/qer/src/lib/shopping-cart/cart-item-edit/cart-item-fk.service.ts @@ -31,36 +31,41 @@ import { FkProviderItem, IFkCandidateProvider, InteractiveEntityWriteData } from import { QerApiService } from '../../qer-api-client.service'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class CartItemFkService { - constructor(private readonly qerClient: QerApiService) { } + constructor(private readonly qerClient: QerApiService) {} public getFkProviderItemsInteractive( interactiveEntity: { InteractiveEntityWriteData: InteractiveEntityWriteData }, parameterData: ParameterData ): IFkCandidateProvider { - const qerClient = this.qerClient; - return new class implements IFkCandidateProvider { + return new (class implements IFkCandidateProvider { + // AbortController + private abortController = new AbortController(); + getProviderItem(_columnName, fkTableName) { if (parameterData.Property.FkRelation) { - return this.getFkProviderItemInteractive(interactiveEntity, parameterData.Property.ColumnName, parameterData.Property.FkRelation.ParentTableName); + return this.getFkProviderItemInteractive( + interactiveEntity, + parameterData.Property.ColumnName, + parameterData.Property.FkRelation.ParentTableName + ); } if (parameterData.Property.ValidReferencedTables) { - const t = parameterData.Property.ValidReferencedTables.map(parentTableRef => + const t = parameterData.Property.ValidReferencedTables.map((parentTableRef) => this.getFkProviderItemInteractive(interactiveEntity, parameterData.Property.ColumnName, parentTableRef.TableName) - ).filter(t => t.fkTableName == fkTableName); - if (t.length == 1) - return t[0]; + ).filter((t) => t.fkTableName == fkTableName); + if (t.length == 1) return t[0]; return null; } return null; } - + private getFkProviderItemInteractive( interactiveEntity: { InteractiveEntityWriteData: InteractiveEntityWriteData }, columnName: string, @@ -69,30 +74,36 @@ export class CartItemFkService { return { columnName, fkTableName, - parameterNames: [ - 'OrderBy', - 'StartIndex', - 'PageSize', - 'filter', - 'search' - ], + parameterNames: ['OrderBy', 'StartIndex', 'PageSize', 'filter', 'search'], load: async (__, parameters?) => { + if (parameters?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } return qerClient.client.portal_cartitem_interactive_parameter_candidates_post( columnName, fkTableName, interactiveEntity.InteractiveEntityWriteData, - parameters + parameters, + { signal: this.abortController.signal } ); }, getDataModel: async () => ({}), getFilterTree: async (__, parentkey) => { return qerClient.client.portal_cartitem_interactive_parameter_candidates_filtertree_post( - columnName, fkTableName, interactiveEntity.InteractiveEntityWriteData, { parentkey: parentkey } + columnName, + fkTableName, + interactiveEntity.InteractiveEntityWriteData, + { parentkey: parentkey } ); - } + }, }; } - }; - } + private abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } + })(); + } } diff --git a/imxweb/projects/qer/src/lib/view-devices/view-devices-home/view-devices.component.ts b/imxweb/projects/qer/src/lib/view-devices/view-devices-home/view-devices.component.ts index c61da9991..32e0397fd 100644 --- a/imxweb/projects/qer/src/lib/view-devices/view-devices-home/view-devices.component.ts +++ b/imxweb/projects/qer/src/lib/view-devices/view-devices-home/view-devices.component.ts @@ -28,13 +28,14 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { ViewDevicesService } from '../view-devices.service'; import { AuthenticationService, - BusyService, DataModelWrapper, + BusyService, + DataModelWrapper, DataSourceToolbarSettings, DataSourceWrapper, HelpContextualComponent, HelpContextualService, HELP_CONTEXTUAL, - SideNavigationComponent + SideNavigationComponent, } from 'qbm'; import { DeviceConfig, PortalCandidatesHardwaretype, PortalDevices } from 'imx-api-qer'; import { CollectionLoadParameters, DisplayColumns, EntitySchema, ValueStruct } from 'imx-qbm-dbts'; @@ -53,7 +54,7 @@ import { CreateNewDeviceComponent } from '../create-new-device/create-new-device templateUrl: './view-devices.component.html', styleUrls: ['./view-devices.component.scss'], }) -export class ViewDevicesComponent implements OnInit, OnDestroy, SideNavigationComponent{ +export class ViewDevicesComponent implements OnInit, OnDestroy, SideNavigationComponent { public dataModelWrapper: DataModelWrapper; public hardwareTypeDataModelWrapper: DataModelWrapper; public dstWrapper: DataSourceWrapper; @@ -64,7 +65,7 @@ export class ViewDevicesComponent implements OnInit, OnDestroy, SideNavigationCo public dstSettingsHardwareType: DataSourceToolbarSettings; public DisplayColumns = DisplayColumns; public deviceModelValueStruct: ValueStruct[]; - public hardwareBasicTypeList: { type: string, basicType: string, key: string }[]; + public hardwareBasicTypeList: { type: string; basicType: string; key: string }[]; public busyService = new BusyService(); public contextId = HELP_CONTEXTUAL.PortalDevices; @@ -86,7 +87,7 @@ export class ViewDevicesComponent implements OnInit, OnDestroy, SideNavigationCo private readonly authService: AuthenticationService, private readonly identitiesService: IdentitiesService, private readonly helpContextualService: HelpContextualService, - qerPermissionService: QerPermissionsService, + qerPermissionService: QerPermissionsService ) { this.entitySchema = this.viewDevicesService.devicesSchema; @@ -94,8 +95,7 @@ export class ViewDevicesComponent implements OnInit, OnDestroy, SideNavigationCo this.sessionResponse$ = this.authService.onSessionResponse.subscribe(async (session) => { if (session.IsLoggedIn) { - this.currentUser = session.UserUid, - this.isManagerForPersons = await qerPermissionService.isPersonManager(); + (this.currentUser = session.UserUid), (this.isManagerForPersons = await qerPermissionService.isPersonManager()); this.isAuditor = await qerPermissionService.isStructStatistics(); } }); @@ -145,9 +145,7 @@ export class ViewDevicesComponent implements OnInit, OnDestroy, SideNavigationCo this.dstWrapperHardwareType = new DataSourceWrapper( (state) => this.viewDevicesService.getPortalCandidatesHardwaretype(state), - [ - this.entitySchemaHardwareType.Columns[DisplayColumns.DISPLAY_PROPERTYNAME], - ], + [this.entitySchemaHardwareType.Columns[DisplayColumns.DISPLAY_PROPERTYNAME]], this.entitySchemaHardwareType, this.hardwareTypeDataModelWrapper, 'hardware-type' @@ -160,14 +158,17 @@ export class ViewDevicesComponent implements OnInit, OnDestroy, SideNavigationCo const isbusy = this.busyService.beginBusy(); try { this.dstSettings = await this.dstWrapper.getDstSettings(newState); - this.dstSettingsHardwareType = await this.dstWrapperHardwareType.getDstSettings(newState); - this.deviceModelValueStruct = this.dstSettingsHardwareType.dataSource.Data.map(d => { - return { + const dstSettingsHardwareType = await this.dstWrapperHardwareType.getDstSettings(newState); + if (dstSettingsHardwareType) { + this.dstSettingsHardwareType = dstSettingsHardwareType; + this.deviceModelValueStruct = this.dstSettingsHardwareType.dataSource.Data.map((d) => { + return { DataValue: d.GetEntity().GetKeys()[0], - DisplayValue: d.GetEntity().GetDisplay() - }; - }); + DisplayValue: d.GetEntity().GetDisplay(), + }; + }); + } } finally { isbusy.endBusy(); } @@ -197,23 +198,23 @@ export class ViewDevicesComponent implements OnInit, OnDestroy, SideNavigationCo return; } - this.helpContextualService.setHelpContextId(HELP_CONTEXTUAL.PortalDevicesEdit); - const result = await this.sideSheet - .open(ViewDevicesSidesheetComponent, { - title: await this.translate.get('#LDS#Heading Edit Device').toPromise(), - subTitle: portalDevices.GetEntity().GetDisplay(), - padding: '0', - width: 'max(600px, 60%)', - disableClose: true, - testId: 'devices-sidesheet', - data: { - device: extendedEntity.Data[0], - deviceEntityConfig: deviceEntityConfig, - }, - headerComponent: HelpContextualComponent - }) - .afterClosed() - .toPromise(); + this.helpContextualService.setHelpContextId(HELP_CONTEXTUAL.PortalDevicesEdit); + const result = await this.sideSheet + .open(ViewDevicesSidesheetComponent, { + title: await this.translate.get('#LDS#Heading Edit Device').toPromise(), + subTitle: portalDevices.GetEntity().GetDisplay(), + padding: '0', + width: 'max(600px, 60%)', + disableClose: true, + testId: 'devices-sidesheet', + data: { + device: extendedEntity.Data[0], + deviceEntityConfig: deviceEntityConfig, + }, + headerComponent: HelpContextualComponent, + }) + .afterClosed() + .toPromise(); if (result) { this.getData(); @@ -223,30 +224,30 @@ export class ViewDevicesComponent implements OnInit, OnDestroy, SideNavigationCo public async createNewDevice(key: string, index: number): Promise { let deviceEntityConfig = this.deviceConfig['VI_Hardware_Fields_Default']; - const hardwareBasicTypeListElement = this.hardwareBasicTypeList.find(hardwareType => hardwareType.key === key); + const hardwareBasicTypeListElement = this.hardwareBasicTypeList.find((hardwareType) => hardwareType.key === key); if (hardwareBasicTypeListElement) { const hardwareBasicType = this.hardwareBasicTypeList[this.hardwareBasicTypeList.indexOf(hardwareBasicTypeListElement)].basicType; deviceEntityConfig = this.deviceConfig[`VI_Hardware_Fields_${hardwareBasicType}`]; } let deviceModelValueStruct = this.deviceModelValueStruct[index]; - this.helpContextualService.setHelpContextId(HELP_CONTEXTUAL.PortalDevicesCreate); - const result = await this.sideSheet - .open(CreateNewDeviceComponent, { - title: await this.translate.get('#LDS#Heading Create Device').toPromise(), - padding: '0px', - width: 'max(650px, 65%)', - disableClose: false, - testId: 'create-new-device-sidesheet', - data: { - newDevice: await this.viewDevicesService.createNewDevice(), - deviceEntityConfig: deviceEntityConfig, - deviceModelValueStruct: deviceModelValueStruct, - }, - headerComponent: HelpContextualComponent - }) - .afterClosed() - .toPromise(); + this.helpContextualService.setHelpContextId(HELP_CONTEXTUAL.PortalDevicesCreate); + const result = await this.sideSheet + .open(CreateNewDeviceComponent, { + title: await this.translate.get('#LDS#Heading Create Device').toPromise(), + padding: '0px', + width: 'max(650px, 65%)', + disableClose: false, + testId: 'create-new-device-sidesheet', + data: { + newDevice: await this.viewDevicesService.createNewDevice(), + deviceEntityConfig: deviceEntityConfig, + deviceModelValueStruct: deviceModelValueStruct, + }, + headerComponent: HelpContextualComponent, + }) + .afterClosed() + .toPromise(); if (result) { this.getData(); diff --git a/imxweb/projects/qer/src/lib/view-devices/view-devices.service.ts b/imxweb/projects/qer/src/lib/view-devices/view-devices.service.ts index d5d930a49..1485c0bed 100644 --- a/imxweb/projects/qer/src/lib/view-devices/view-devices.service.ts +++ b/imxweb/projects/qer/src/lib/view-devices/view-devices.service.ts @@ -28,19 +28,17 @@ import { OverlayRef } from '@angular/cdk/overlay'; import { Injectable } from '@angular/core'; import { QerApiService } from '../qer-api-client.service'; import { EuiLoadingService } from '@elemental-ui/core'; -import { CollectionLoadParameters, DataModel, EntityCollectionData, EntitySchema, ExtendedTypedEntityCollection } from 'imx-qbm-dbts'; -import { PortalCandidatesHardwaretype, PortalCandidatesHardwaretypeWrapper, PortalDevices } from 'imx-api-qer'; +import { CollectionLoadParameters, DataModel, EntitySchema, ExtendedTypedEntityCollection } from 'imx-qbm-dbts'; +import { PortalCandidatesHardwaretype, PortalDevices } from 'imx-api-qer'; @Injectable({ providedIn: 'root', }) export class ViewDevicesService { private busyIndicator: OverlayRef; + private abortController = new AbortController(); - constructor( - private readonly qerClient: QerApiService, - private readonly busyService: EuiLoadingService - ) {} + constructor(private readonly qerClient: QerApiService, private readonly busyService: EuiLoadingService) {} public get devicesSchema(): EntitySchema { return this.qerClient.typedClient.PortalDevices.GetSchema(); @@ -57,7 +55,7 @@ export class ViewDevicesService { } public handleCloseLoader(): void { - if(this.busyIndicator) { + if (this.busyIndicator) { setTimeout(() => { this.busyService.hide(this.busyIndicator); this.busyIndicator = undefined; @@ -85,11 +83,22 @@ export class ViewDevicesService { await this.qerClient.client.portal_devices_delete(uid); } - public async getPortalCandidatesHardwaretype(parameters: CollectionLoadParameters): Promise> { - return await this.qerClient.typedClient.PortalCandidatesHardwaretype.Get(parameters); + public async getPortalCandidatesHardwaretype( + parameters: CollectionLoadParameters + ): Promise> { + if (parameters?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return await this.qerClient.typedClient.PortalCandidatesHardwaretype.Get(parameters, { signal: this.abortController.signal }); } public async getHardwareTypeDataModel(): Promise { return this.qerClient.client.portal_candidates_HardwareType_datamodel_get(); } + + private abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/rmb/src/lib/init.service.ts b/imxweb/projects/rmb/src/lib/init.service.ts index 149e85864..a71deb0ca 100644 --- a/imxweb/projects/rmb/src/lib/init.service.ts +++ b/imxweb/projects/rmb/src/lib/init.service.ts @@ -36,7 +36,6 @@ import { IdentityRoleMembershipsService, MyResponsibilitiesRegistryService, QerApiService, - QerPermissionsService, RoleService, RolesOverviewComponent, isAuditor, @@ -46,7 +45,15 @@ import { import { OrgDataModel } from './org-data-model'; import { OrgMembership } from './org-membership'; import { RmbApiService } from './rmb-api-client.service'; -import { EntitySchema, ExtendedTypedEntityCollection, WriteExtTypedEntity, CollectionLoadParameters, EntityCollectionData, MethodDescriptor, MethodDefinition } from 'imx-qbm-dbts'; +import { + EntitySchema, + ExtendedTypedEntityCollection, + WriteExtTypedEntity, + CollectionLoadParameters, + EntityCollectionData, + MethodDescriptor, + MethodDefinition, +} from 'imx-qbm-dbts'; import { RoleExtendedDataWrite } from 'imx-api-qer'; import { TeamRoleComponent } from './team-role/team-role.component'; import { ProjectConfig } from 'imx-api-qbm'; @@ -54,6 +61,7 @@ import { ProjectConfig } from 'imx-api-qbm'; @Injectable({ providedIn: 'root' }) export class InitService { protected readonly orgTag = 'Org'; + private abortController = new AbortController(); constructor( private readonly router: Router, @@ -67,8 +75,7 @@ export class InitService { private readonly roleService: RoleService, private readonly identityRoleMembershipService: IdentityRoleMembershipsService, private readonly myResponsibilitiesRegistryService: MyResponsibilitiesRegistryService, - private readonly extService: ExtService, - private readonly qerPermissionsService: QerPermissionsService + private readonly extService: ExtService ) {} public onInit(routes: Route[]): void { @@ -117,7 +124,13 @@ export class InitService { adminType: PortalAdminRoleOrg, adminHasHierarchy: true, admin: { - get: async (parameter: any) => this.api.client.portal_admin_role_org_get(parameter), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_admin_role_org_get(parameter, { signal: this.abortController.signal }); + }, }, adminSchema: this.api.typedClient.PortalAdminRoleOrg.GetSchema(), dataModel: new OrgDataModel(this.api), @@ -176,22 +189,24 @@ export class InitService { this.setupMenu(); - this.dataExplorerRegistryService.registerFactory((preProps: string[], features: string[], projectConfig: ProjectConfig, groups: string[]) => { - if (!isRoleAdmin(features) && !isRoleStatistics(features) && !isAuditor(groups)) { - return; + this.dataExplorerRegistryService.registerFactory( + (preProps: string[], features: string[], projectConfig: ProjectConfig, groups: string[]) => { + if (!isRoleAdmin(features) && !isRoleStatistics(features) && !isAuditor(groups)) { + return; + } + return { + instance: RolesOverviewComponent, + data: { + TableName: this.orgTag, + Count: 0, + }, + contextId: HELP_CONTEXTUAL.DataExplorerBusinessRoles, + sortOrder: 7, + name: 'businessroles', + caption: '#LDS#Menu Entry Business roles', + }; } - return { - instance: RolesOverviewComponent, - data: { - TableName: this.orgTag, - Count: 0, - }, - contextId: HELP_CONTEXTUAL.DataExplorerBusinessRoles, - sortOrder: 7, - name: 'businessroles', - caption: '#LDS#Menu Entry Business roles', - }; - }); + ); this.myResponsibilitiesRegistryService.registerFactory((preProps: string[], features: string[]) => ({ instance: RolesOverviewComponent, @@ -202,7 +217,7 @@ export class InitService { TableName: this.orgTag, Count: 0, }, - contextId: HELP_CONTEXTUAL.MyResponsibilitiesBusinessRoles + contextId: HELP_CONTEXTUAL.MyResponsibilitiesBusinessRoles, })); this.extService.register('Dashboard-MediumTiles', { instance: TeamRoleComponent }); } @@ -237,4 +252,9 @@ export class InitService { }); this.router.resetConfig(config); } + + private abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/rms/src/lib/init.service.ts b/imxweb/projects/rms/src/lib/init.service.ts index 27872d642..cc4643560 100644 --- a/imxweb/projects/rms/src/lib/init.service.ts +++ b/imxweb/projects/rms/src/lib/init.service.ts @@ -65,6 +65,7 @@ export interface test { @Injectable({ providedIn: 'root' }) export class InitService { private esetTag = 'ESet'; + private abortController = new AbortController(); constructor( private readonly router: Router, @@ -127,16 +128,24 @@ export class InitService { resp: this.api.typedClient.PortalRespEset, adminType: PortalAdminRoleEset, admin: { - get: async (parameter: any) => - this.api.client.portal_admin_role_eset_get({ - OrderBy: parameter.OrderBy, - StartIndex: parameter.StartIndex, - PageSize: parameter.PageSize, - filter: parameter.filter, - search: parameter.search, - risk: parameter.risk, - esettype: parameter.esettype, - }), + get: async (parameter: any) => { + if (parameter?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.api.client.portal_admin_role_eset_get( + { + OrderBy: parameter.OrderBy, + StartIndex: parameter.StartIndex, + PageSize: parameter.PageSize, + filter: parameter.filter, + search: parameter.search, + risk: parameter.risk, + esettype: parameter.esettype, + }, + { signal: this.abortController.signal } + ); + }, }, adminSchema: this.api.typedClient.PortalAdminRoleEset.GetSchema(), dataModel: new EsetDataModel(this.api), @@ -248,4 +257,9 @@ export class InitService { }); this.router.resetConfig(config); } + + private abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/tsb/src/lib/accounts/accounts.component.ts b/imxweb/projects/tsb/src/lib/accounts/accounts.component.ts index 48cda5a58..4a5321243 100644 --- a/imxweb/projects/tsb/src/lib/accounts/accounts.component.ts +++ b/imxweb/projects/tsb/src/lib/accounts/accounts.component.ts @@ -239,31 +239,33 @@ export class DataExplorerAccountsComponent implements OnInit, OnDestroy, SideNav getParams.container = cUid ? cUid : undefined; const data = await this.accountsService.getAccounts(getParams); - const exportMethod: DataSourceToolbarExportMethod = this.accountsService.exportAccounts(this.navigationState); - exportMethod.initialColumns = this.displayedColumns.map((col) => col.ColumnName); - this.dstSettings = { - displayedColumns: this.displayedColumns, - dataSource: data, - entitySchema: this.entitySchemaUnsAccount, - navigationState: this.navigationState, - filters: this.filterOptions, - filterTree: { - filterMethode: async (parentkey) => { - return this.accountsService.getFilterTree({ - parentkey, - container: getParams.container, - system: getParams.system, - filter: getParams.filter, - }); + if (data) { + const exportMethod: DataSourceToolbarExportMethod = this.accountsService.exportAccounts(this.navigationState); + exportMethod.initialColumns = this.displayedColumns.map((col) => col.ColumnName); + this.dstSettings = { + displayedColumns: this.displayedColumns, + dataSource: data, + entitySchema: this.entitySchemaUnsAccount, + navigationState: this.navigationState, + filters: this.filterOptions, + filterTree: { + filterMethode: async (parentkey) => { + return this.accountsService.getFilterTree({ + parentkey, + container: getParams.container, + system: getParams.system, + filter: getParams.filter, + }); + }, + multiSelect: false, }, - multiSelect: false, - }, - dataModel: this.dataModel, - viewConfig: this.viewConfig, - exportMethod, - }; - this.tableName = data.tableName; - this.logger.debug(this, `Head at ${data.Data.length + this.navigationState.StartIndex} of ${data.totalCount} item(s)`); + dataModel: this.dataModel, + viewConfig: this.viewConfig, + exportMethod, + }; + this.tableName = data.tableName; + this.logger.debug(this, `Head at ${data?.Data.length + this.navigationState?.StartIndex} of ${data.totalCount} item(s)`); + } } finally { isBusy.endBusy(); } diff --git a/imxweb/projects/tsb/src/lib/accounts/accounts.service.ts b/imxweb/projects/tsb/src/lib/accounts/accounts.service.ts index c35c0a7c9..01c3be922 100644 --- a/imxweb/projects/tsb/src/lib/accounts/accounts.service.ts +++ b/imxweb/projects/tsb/src/lib/accounts/accounts.service.ts @@ -35,7 +35,7 @@ import { DataModel, EntityCollectionData, MethodDescriptor, - MethodDefinition + MethodDefinition, } from 'imx-qbm-dbts'; import { TsbApiService } from '../tsb-api-client.service'; import { PortalTargetsystemUnsAccount, V2ApiClientMethodFactory } from 'imx-api-tsb'; @@ -47,10 +47,9 @@ import { DataSourceToolbarExportMethod } from 'qbm'; @Injectable({ providedIn: 'root' }) export class AccountsService { - constructor( - private readonly tsbClient: TsbApiService, - private readonly dynamicMethod: TargetSystemDynamicMethodService - ) { } + private abortController = new AbortController(); + + constructor(private readonly tsbClient: TsbApiService, private readonly dynamicMethod: TargetSystemDynamicMethodService) {} public get accountSchema(): EntitySchema { return this.tsbClient.typedClient.PortalTargetsystemUnsAccount.GetSchema(); @@ -64,7 +63,11 @@ export class AccountsService { * @returns Wrapped list of Accounts. */ public async getAccounts(navigationState: CollectionLoadParameters): Promise> { - return this.tsbClient.typedClient.PortalTargetsystemUnsAccount.Get(navigationState); + if (navigationState?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.tsbClient.typedClient.PortalTargetsystemUnsAccount.Get(navigationState, { signal: this.abortController.signal }); } public exportAccounts(navigationState: CollectionLoadParameters): DataSourceToolbarExportMethod { @@ -73,13 +76,13 @@ export class AccountsService { getMethod: (withProperties: string, PageSize?: number) => { let method: MethodDescriptor; if (PageSize) { - method = factory.portal_targetsystem_uns_account_get({...navigationState, withProperties, PageSize, StartIndex: 0}) + method = factory.portal_targetsystem_uns_account_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); } else { - method = factory.portal_targetsystem_uns_account_get({...navigationState, withProperties}) + method = factory.portal_targetsystem_uns_account_get({ ...navigationState, withProperties }); } return new MethodDefinition(method); - } - } + }, + }; } public async getAccount(dbObjectKey: DbObjectKeyBase, columnName?: string): Promise { @@ -94,12 +97,16 @@ export class AccountsService { return (await this.getDataModel()).Filters; } - public async getDataModel(): Promise{ + public async getDataModel(): Promise { return this.tsbClient.client.portal_targetsystem_uns_account_datamodel_get(undefined); } - - public async getFilterTree(parameter: AccountsFilterTreeParameters):Promise{ + public async getFilterTree(parameter: AccountsFilterTreeParameters): Promise { return this.tsbClient.client.portal_targetsystem_uns_account_filtertree_get(parameter); } + + private abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } } diff --git a/imxweb/projects/tsb/src/lib/groups/groups.component.ts b/imxweb/projects/tsb/src/lib/groups/groups.component.ts index 57fb0622d..87979d723 100644 --- a/imxweb/projects/tsb/src/lib/groups/groups.component.ts +++ b/imxweb/projects/tsb/src/lib/groups/groups.component.ts @@ -386,34 +386,36 @@ export class DataExplorerGroupsComponent implements OnInit, OnDestroy, SideNavig ? await this.groupsService.getGroups(getParams) : await this.groupsService.getGroupsResp(getParams); - const exportMethod = - this.isAdmin || this.unsAccountIdFilter - ? this.groupsService.exportGroups(getParams) - : this.groupsService.exportGroupsResp(getParams); - exportMethod.initialColumns = this.displayedColumns.map((col) => col.ColumnName); - - this.dstSettings = { - displayedColumns: this.displayedColumns, - dataSource: data, - entitySchema: this.entitySchemaUnsGroup, - navigationState: this.navigationState, - filters: this.filterOptions, - filterTree: { - filterMethode: async (parentkey) => { - return this.groupsService.getFilterTree({ - parentkey, - container: getParams.container, - system: getParams.system, - uid_unsaccount: getParams.uid_unsaccount, - }); + if (data) { + const exportMethod = + this.isAdmin || this.unsAccountIdFilter + ? this.groupsService.exportGroups(getParams) + : this.groupsService.exportGroupsResp(getParams); + exportMethod.initialColumns = this.displayedColumns.map((col) => col.ColumnName); + + this.dstSettings = { + displayedColumns: this.displayedColumns, + dataSource: data, + entitySchema: this.entitySchemaUnsGroup, + navigationState: this.navigationState, + filters: this.filterOptions, + filterTree: { + filterMethode: async (parentkey) => { + return this.groupsService.getFilterTree({ + parentkey, + container: getParams.container, + system: getParams.system, + uid_unsaccount: getParams.uid_unsaccount, + }); + }, + multiSelect: false, }, - multiSelect: false, - }, - dataModel: this.dataModel, - viewConfig: this.viewConfig, - exportMethod, - }; - this.logger.debug(this, `Head at ${data.Data.length + this.navigationState.StartIndex} of ${data.totalCount} item(s)`); + dataModel: this.dataModel, + viewConfig: this.viewConfig, + exportMethod, + }; + this.logger.debug(this, `Head at ${data.Data.length + this.navigationState.StartIndex} of ${data.totalCount} item(s)`); + } } finally { isBusy.endBusy(); } diff --git a/imxweb/projects/tsb/src/lib/groups/groups.service.ts b/imxweb/projects/tsb/src/lib/groups/groups.service.ts index a915e274d..4f7519dfe 100644 --- a/imxweb/projects/tsb/src/lib/groups/groups.service.ts +++ b/imxweb/projects/tsb/src/lib/groups/groups.service.ts @@ -37,7 +37,7 @@ import { DataModel, EntityCollectionData, MethodDescriptor, - MethodDefinition + MethodDefinition, } from 'imx-qbm-dbts'; import { PortalTargetsystemUnsGroup, @@ -47,7 +47,7 @@ import { PortalTargetsystemUnsDirectmembers, PortalTargetsystemUnsNestedmembers, PortalRespUnsgroup, - V2ApiClientMethodFactory + V2ApiClientMethodFactory, } from 'imx-api-tsb'; import { GroupsFilterTreeParameters, GetGroupsOptionalParameters } from './groups.models'; import { TsbApiService } from '../tsb-api-client.service'; @@ -57,15 +57,18 @@ import { DbObjectKeyBase } from '../target-system/db-object-key-wrapper.interfac @Injectable({ providedIn: 'root' }) export class GroupsService { + private abortController = new AbortController(); + constructor( private readonly tsbClient: TsbApiService, private readonly logger: ClassloggerService, private readonly dynamicMethod: TargetSystemDynamicMethodService - ) { } + ) {} public unsGroupsSchema(isAdmin: boolean): EntitySchema { - return isAdmin ? this.tsbClient.typedClient.PortalTargetsystemUnsGroup.GetSchema() : - this.tsbClient.typedClient.PortalRespUnsgroup.GetSchema(); + return isAdmin + ? this.tsbClient.typedClient.PortalTargetsystemUnsGroup.GetSchema() + : this.tsbClient.typedClient.PortalRespUnsgroup.GetSchema(); } public get UnsGroupMembersSchema(): EntitySchema { @@ -85,7 +88,11 @@ export class GroupsService { } public async getGroups(navigationState: GetGroupsOptionalParameters): Promise> { - return this.tsbClient.typedClient.PortalTargetsystemUnsGroup.Get(navigationState); + if (navigationState?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.tsbClient.typedClient.PortalTargetsystemUnsGroup.Get(navigationState, { signal: this.abortController.signal }); } public exportGroups(navigationState: GetGroupsOptionalParameters): DataSourceToolbarExportMethod { @@ -94,17 +101,21 @@ export class GroupsService { getMethod: (withProperties: string, PageSize?: number) => { let method: MethodDescriptor; if (PageSize) { - method = factory.portal_targetsystem_uns_group_get({...navigationState, withProperties, PageSize, StartIndex: 0}) + method = factory.portal_targetsystem_uns_group_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); } else { - method = factory.portal_targetsystem_uns_group_get({...navigationState, withProperties}) + method = factory.portal_targetsystem_uns_group_get({ ...navigationState, withProperties }); } return new MethodDefinition(method); - } - } + }, + }; } public async getGroupsResp(navigationState: GetGroupsOptionalParameters): Promise> { - return this.tsbClient.typedClient.PortalRespUnsgroup.Get(navigationState); + if (navigationState?.search !== undefined) { + // abort the request only while searching + this.abortCall(); + } + return this.tsbClient.typedClient.PortalRespUnsgroup.Get(navigationState, { signal: this.abortController.signal }); } public exportGroupsResp(navigationState: GetGroupsOptionalParameters): DataSourceToolbarExportMethod { @@ -113,13 +124,13 @@ export class GroupsService { getMethod: (withProperties: string, PageSize?: number) => { let method: MethodDescriptor; if (PageSize) { - method = factory.portal_resp_unsgroup_get({...navigationState, withProperties, PageSize, StartIndex: 0}) + method = factory.portal_resp_unsgroup_get({ ...navigationState, withProperties, PageSize, StartIndex: 0 }); } else { - method = factory.portal_resp_unsgroup_get({...navigationState, withProperties}) + method = factory.portal_resp_unsgroup_get({ ...navigationState, withProperties }); } return new MethodDefinition(method); - } - } + }, + }; } public async getGroupDetails(dbObjectKey: DbObjectKeyBase): Promise { @@ -171,15 +182,14 @@ export class GroupsService { const groupId = dbObjectKey.Keys[0]; - return Promise.all(uidAccountList.map(accountId => - this.dynamicMethod.delete( - dbObjectKey.TableName, - { + return Promise.all( + uidAccountList.map((accountId) => + this.dynamicMethod.delete(dbObjectKey.TableName, { path: '{groupId}/memberships/{accountId}', - parameters: { groupId, accountId } - } + parameters: { groupId, accountId }, + }) ) - )); + ); } public async getGroupsGroupMembers( @@ -192,16 +202,18 @@ export class GroupsService { } public async getFilterOptions(forAdmin: boolean): Promise { - return forAdmin ? (await this.tsbClient.client.portal_targetsystem_uns_group_datamodel_get(undefined)).Filters + return forAdmin + ? (await this.tsbClient.client.portal_targetsystem_uns_group_datamodel_get(undefined)).Filters : (await this.tsbClient.client.portal_resp_unsgroup_datamodel_get(undefined)).Filters; } public async getDataModel(forAdmin: boolean): Promise { - return forAdmin ? this.tsbClient.client.portal_targetsystem_uns_group_datamodel_get(undefined) + return forAdmin + ? this.tsbClient.client.portal_targetsystem_uns_group_datamodel_get(undefined) : this.tsbClient.client.portal_resp_unsgroup_datamodel_get(undefined); } - public async updateMultipleOwner(uidAccProducts: string[], uidPerson: { uidPerson?: string; uidRole?: string; }): Promise { + public async updateMultipleOwner(uidAccProducts: string[], uidPerson: { uidPerson?: string; uidRole?: string }): Promise { let confirmMessage = '#LDS#The product owner has been successfully assigned.'; try { for (const data of uidAccProducts) { @@ -226,4 +238,9 @@ export class GroupsService { } return confirmMessage; } + + private abortCall(): void { + this.abortController.abort(); + this.abortController = new AbortController(); + } }