From 5ef55ddc0297eefd9be43f5b05e2f8dfe6dc2a7d Mon Sep 17 00:00:00 2001 From: pweick Date: Fri, 28 Jun 2024 14:04:42 +0200 Subject: [PATCH 1/3] feat(registry): [xxx] registry reload --- .../modules/page/parts/core/parts.facade.ts | 12 +++++ .../parts/model/partReloadOperation.enum.ts | 23 +++++++++ .../parts/presentation/parts.component.html | 2 + .../parts/presentation/parts.component.ts | 48 +++++++++++++++++++ .../parts-table/parts-table.component.html | 21 ++++++-- .../parts-table/parts-table.component.spec.ts | 35 ++++++++++++++ .../parts-table/parts-table.component.ts | 27 ++++++++++- .../modules/shared/service/parts.service.ts | 12 +++++ frontend/src/assets/locales/de/common.json | 10 ++++ frontend/src/assets/locales/en/common.json | 10 ++++ 10 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts diff --git a/frontend/src/app/modules/page/parts/core/parts.facade.ts b/frontend/src/app/modules/page/parts/core/parts.facade.ts index 7033a8beb9..4745fb6629 100644 --- a/frontend/src/app/modules/page/parts/core/parts.facade.ts +++ b/frontend/src/app/modules/page/parts/core/parts.facade.ts @@ -51,6 +51,18 @@ export class PartsFacade { return this.partsState.partsAsPlanned$; } + public reloadRegistry() { + return this.partsService.reloadRegistry(); + } + + public syncPartsAsBuilt(assetIds: string[]) { + return this.partsService.syncPartsAsBuilt(assetIds); + } + + public syncPartsAsPlanned(assetIds: string[]) { + return this.partsService.syncPartsAsPlanned(assetIds); + } + public setPartsAsBuilt(page = 0, pageSize = 50, sorting: TableHeaderSort[] = [], assetAsBuiltFilter?: AssetAsBuiltFilter, isOrSearch?: boolean): void { this.partsAsBuiltSubscription?.unsubscribe(); this.partsAsBuiltSubscription = this.partsService.getPartsAsBuilt(page, pageSize, sorting, assetAsBuiltFilter, isOrSearch).subscribe({ diff --git a/frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts b/frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts new file mode 100644 index 0000000000..b524419683 --- /dev/null +++ b/frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +export enum PartReloadOperation { + RELOAD_REGISTRY = 'RELOAD_REGISTRY', + SYNC_PARTS_AS_BUILT = 'SYNC_PARTS_AS_BUILT', + SYNC_PARTS_AS_PLANNED = 'SYNC_PARTS_AS_PLANNED' +} diff --git a/frontend/src/app/modules/page/parts/presentation/parts.component.html b/frontend/src/app/modules/page/parts/presentation/parts.component.html index 2379a9d9df..c08a95667c 100644 --- a/frontend/src/app/modules/page/parts/presentation/parts.component.html +++ b/frontend/src/app/modules/page/parts/presentation/parts.component.html @@ -55,6 +55,7 @@ part.id)); + break; + case PartReloadOperation.SYNC_PARTS_AS_PLANNED: + this.syncPartsAsPlanned(this.currentSelectedAsPlannedItems$.value.map(part => part.id)); + break; + } + } + + private reloadRegistry() { + this.partsFacade.reloadRegistry().subscribe({ + next: data => { + this.toastService.success('registryReload.success'); + }, + error: error => { + this.toastService.error(error?.getMessage()); + }, + }); + } + + private syncPartsAsBuilt(assetIds: string[]) { + this.partsFacade.syncPartsAsBuilt(assetIds).subscribe({ + next: data => { + this.toastService.success('partSynchronization.success'); + }, + error: error => { + this.toastService.error(error?.getMessage()); + }, + }); + } + + private syncPartsAsPlanned(assetIds: string[]) { + this.partsFacade.syncPartsAsPlanned(assetIds).subscribe({ + next: data => { + this.toastService.success('partSynchronization.success'); + }, + error: error => { + this.toastService.error(error?.getMessage()); + }, + }); + } + private resetFilterAndShowToast(resetOwner?: boolean) { let filterIsSet = resetMultiSelectionAutoCompleteComponent(this.partsTableComponents, false); if (resetOwner) { diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html index 80c4249da0..e15034bd85 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.html @@ -79,24 +79,39 @@
+ [matTooltipDisabled]="isAdmin() && atLeastOneSelected() && !isIllegalSelectionToPublish()">
+
+ +
+

{ expect(componentInstance.selection.selected).toEqual([]); }); + it('should emit reload registry event on part reload button click when no part selected', async () => { + const { fixture } = await renderPartsTableComponent(1, TableType.AS_BUILT_OWN); + const { componentInstance } = fixture; + + const emitPartReloadSpy = spyOn(componentInstance.partReloadClickedEvent, 'emit'); + componentInstance.partReloadIconClicked(); + + expect(emitPartReloadSpy).toHaveBeenCalledWith(PartReloadOperation.RELOAD_REGISTRY); + }); + + it('should emit sync parts as built event on part reload button click when part selected', async () => { + const { fixture } = await renderPartsTableComponent(1, TableType.AS_BUILT_OWN); + const { componentInstance } = fixture; + componentInstance.mainAspectType = MainAspectType.AS_BUILT; + const emitPartReloadSpy = spyOn(componentInstance.partReloadClickedEvent, 'emit'); + + componentInstance.selection.select({ id: 1 }); + componentInstance.partReloadIconClicked(); + + expect(emitPartReloadSpy).toHaveBeenCalledWith(PartReloadOperation.SYNC_PARTS_AS_BUILT); + }); + + it('should emit sync parts as planned event on part reload button click when part selected', async () => { + const { fixture } = await renderPartsTableComponent(1, TableType.AS_PLANNED_OWN); + const { componentInstance } = fixture; + componentInstance.mainAspectType = MainAspectType.AS_PLANNED; + const emitPartReloadSpy = spyOn(componentInstance.partReloadClickedEvent, 'emit'); + + componentInstance.selection.select({ id: 1 }); + componentInstance.partReloadIconClicked(); + + expect(emitPartReloadSpy).toHaveBeenCalledWith(PartReloadOperation.SYNC_PARTS_AS_PLANNED); + }); }); diff --git a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts index 673ab802c7..e2830cd84b 100644 --- a/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts +++ b/frontend/src/app/modules/shared/components/parts-table/parts-table.component.ts @@ -42,6 +42,7 @@ import { UserService } from '@core/user/user.service'; import { MainAspectType } from '@page/parts/model/mainAspectType.enum'; import { Owner } from '@page/parts/model/owner.enum'; import { ImportState, Part } from '@page/parts/model/parts.model'; +import { PartReloadOperation } from '@page/parts/model/partReloadOperation.enum'; import { MultiSelectAutocompleteComponent } from '@shared/components/multi-select-autocomplete/multi-select-autocomplete.component'; import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; import { PartsTableConfigUtils } from '@shared/components/parts-table/parts-table-config.utils'; @@ -72,6 +73,7 @@ export class PartsTableComponent implements OnInit { @ViewChildren(MultiSelectAutocompleteComponent) multiSelectAutocompleteComponents: QueryList; @Output() publishIconClickedEvent = new EventEmitter(); + @Output() partReloadClickedEvent = new EventEmitter(); @Input() labelId: string; @Input() noShadow = false; @Input() showHover = true; @@ -184,7 +186,7 @@ export class PartsTableComponent implements OnInit { this.createQualityNotificationClickedEvent.emit(this.notificationType); } - public isAllowedToPublish(): boolean { + public isAdmin(): boolean { return this.roleService.hasAccess([ 'admin' ]); } @@ -200,10 +202,31 @@ export class PartsTableComponent implements OnInit { } } - public publishIconClicked(): void { + handleKeyDownPartReloadIconClicked(event: KeyboardEvent): void { + if (event.key === 'Enter') { + this.partReloadIconClicked(); + } + } + + public publishIconClicked() { this.publishIconClickedEvent.emit(); } + public partReloadIconClicked() { + if (!this.atLeastOneSelected()) { + this.partReloadClickedEvent.emit(PartReloadOperation.RELOAD_REGISTRY); + } else { + switch (this.mainAspectType) { + case MainAspectType.AS_BUILT: + this.partReloadClickedEvent.emit(PartReloadOperation.SYNC_PARTS_AS_BUILT); + break; + case MainAspectType.AS_PLANNED: + this.partReloadClickedEvent.emit(PartReloadOperation.SYNC_PARTS_AS_PLANNED); + break; + } + } + } + public readonly dataSource = new MatTableDataSource(); public readonly selection = new SelectionModel(true, []); diff --git a/frontend/src/app/modules/shared/service/parts.service.ts b/frontend/src/app/modules/shared/service/parts.service.ts index a0e347f26d..5771522f99 100644 --- a/frontend/src/app/modules/shared/service/parts.service.ts +++ b/frontend/src/app/modules/shared/service/parts.service.ts @@ -47,6 +47,18 @@ export class PartsService { constructor(private readonly apiService: ApiService) { } + public reloadRegistry() { + return this.apiService.get(`${ this.url }/registry/reload`); + } + + public syncPartsAsBuilt(assetIds: string[]) { + return this.apiService.post(`${ this.url }/assets/as-built/sync`, { assetIds }); + } + + public syncPartsAsPlanned(assetIds: string[]) { + return this.apiService.post(`${ this.url }/assets/as-planned/sync`, { assetIds }); + } + public getPartsAsBuilt(page: number, pageSize: number, sorting: TableHeaderSort[], assetAsBuiltFilter?: AssetAsBuiltFilter, isOrSearch?: boolean): Observable> { let sort = sorting.map(sortingItem => PartsAssembler.mapSortToApiSort(sortingItem)); diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index 9128f41455..39b812e918 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -19,6 +19,8 @@ "noChildPartsForInvestigation": "Diese Funktion ist für Produkte ohne Bauteile nicht verfügbar.", "noCustomerAsPlannedParts": "Produkte von Kunden im Lebenszyklus \"AsPlanned\" sind nicht verfügbar.", "illegalAssetStateToPublish": "Ein oder mehrere ausgewählte Produkte befinden sich nicht im erforderlichen Importstatus \"TRANSIENT\" oder \"ERROR\".", + "syncSelectedParts" : "Synchronisierung der ausgewählten Parts", + "refreshAllParts" : "Synchronisierung aller Parts", "adminContract": "Verträge", "createIncident": "Mitteilung erstellen", "startInvestigation": "Qualitätsuntersuchung für Bauteile erstellen", @@ -294,6 +296,14 @@ "success": "Anfrage zur Veröffentlichung der Produkte war erfolgreich", "error": "" }, + "registryReload": { + "success": "Aktualisierung aller Parts erfolgreich.", + "error": "" + }, + "partSynchronization": { + "success": "Synchronisierung ausgewählter Parts erfolgreich.", + "error": "" + }, "pagination": { "itemsPerPageLabel": "Artikel pro Seite:", "nextPageLabel": "Nächsten Seite", diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index db45a21c05..151cd7c250 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -18,6 +18,8 @@ "noChildPartsForInvestigation": "This function is not available for Parts without components.", "noCustomerAsPlannedParts": "Customer parts in lifecycle \"AsPlanned\" are not available.", "illegalAssetStateToPublish": "One or more selected parts are not in the required import state \"TRANSIENT\" or \"ERROR\".", + "syncSelectedParts" : "Synchronize selected parts", + "refreshAllParts" : "Synchronize all parts", "adminContract": "Contracts", "createIncident": "Create quality notification", "startInvestigation": "Start Quality investigation on subcomponents", @@ -293,6 +295,14 @@ "success": "Request to publish assets was successful", "error": "" }, + "registryReload": { + "success": "Successfully reloaded all parts.", + "error": "" + }, + "partSynchronization": { + "success": "Successfully synchronized selected parts.", + "error": "" + }, "pagination": { "itemsPerPageLabel": "Items per page:", "nextPageLabel": "Next page", From 1672be821494ce45f58162a6cb10c4f1083652d5 Mon Sep 17 00:00:00 2001 From: ds-pweick <162321742+ds-pweick@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:24:15 +0200 Subject: [PATCH 2/3] chore(legal): updated copyright header in newly added frontend file Co-authored-by: Maximilian Wesener <124587888+ds-mwesener@users.noreply.github.com> --- .../app/modules/page/parts/model/partReloadOperation.enum.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts b/frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts index b524419683..72ff74f6e6 100644 --- a/frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts +++ b/frontend/src/app/modules/page/parts/model/partReloadOperation.enum.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. From 838235830d4b7b20cf3e8feb878ca957c935e4a4 Mon Sep 17 00:00:00 2001 From: pweick Date: Fri, 28 Jun 2024 15:27:08 +0200 Subject: [PATCH 3/3] chore(docs): updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 811d801b6a..afa4b65b99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ _**For better traceability add the corresponding GitHub issue number in each cha - XXX Added interceptor to EdcRestTemplates to log requests - #915 Added section to documentation: EDC-BPN configuration - #1037 Added link from contracts view to the corresponding filtered part table view +- #786 Added icons on part table to let admin reload registry / sync assets via IRS ### Removed