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 46b5dbf33..d7fa8d4e1 100644 Binary files a/imxweb/imx-modules/imx-api-qbm.tgz and b/imxweb/imx-modules/imx-api-qbm.tgz differ diff --git a/imxweb/imx-modules/imx-api-rms.tgz b/imxweb/imx-modules/imx-api-rms.tgz index 9a025df64..f2e53b93e 100644 Binary files a/imxweb/imx-modules/imx-api-rms.tgz and b/imxweb/imx-modules/imx-api-rms.tgz differ 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(); + } }