Skip to content

Commit

Permalink
Merge pull request #1130 from ds-pweick/feature/sync-selection
Browse files Browse the repository at this point in the history
feat(registry): registry reload
  • Loading branch information
ds-mwesener authored Jun 28, 2024
2 parents 92d510e + 8382358 commit f7c1298
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 12 additions & 0 deletions frontend/src/app/modules/page/parts/core/parts.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/********************************************************************************
* Copyright (c) 2024 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'
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<app-parts-table class="parts-asBuilt-table"
[paginationData]="parts.data"
(publishIconClickedEvent)="openPublisherSideNav()"
(partReloadClickedEvent)="triggerPartReload($event)"
(createQualityNotificationClickedEvent)="navigateToNotificationCreationView($event)"
[labelId]="titleId"
[selectedPartsInfoLabel]="'page.selectedParts.info'"
Expand Down Expand Up @@ -86,6 +87,7 @@
[paginationData]="parts.data | formatPaginationSemanticDataModelToCamelCase"
[labelId]="titleId"
(publishIconClickedEvent)="openPublisherSideNav()"
(partReloadClickedEvent)="triggerPartReload($event)"
[selectedPartsInfoLabel]="'page.selectedParts.info'"
[selectedPartsActionLabel]="'page.selectedParts.action'"
[deselectTrigger]="deselectPartTrigger$ | async"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { PartsFacade } from '@page/parts/core/parts.facade';
import { resetMultiSelectionAutoCompleteComponent } from '@page/parts/core/parts.helper';
import { MainAspectType } from '@page/parts/model/mainAspectType.enum';
import { Owner } from '@page/parts/model/owner.enum';
import { PartReloadOperation } from '@page/parts/model/partReloadOperation.enum';
import { AssetAsBuiltFilter, AssetAsPlannedFilter, Part } from '@page/parts/model/parts.model';
import { BomLifecycleSize } from '@shared/components/bom-lifecycle-activator/bom-lifecycle-activator.model';
import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model';
Expand Down Expand Up @@ -213,6 +214,53 @@ export class PartsComponent implements OnInit, OnDestroy, AfterViewInit {
}
}

triggerPartReload(operation: PartReloadOperation) {
switch (operation) {
case PartReloadOperation.RELOAD_REGISTRY:
this.reloadRegistry();
break;
case PartReloadOperation.SYNC_PARTS_AS_BUILT:
this.syncPartsAsBuilt(this.currentSelectedItems$.value.map(part => 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,39 @@


<div class="action-button-tile"
matTooltip="{{(isAllowedToPublish() ? (atLeastOneSelected() ? 'routing.illegalAssetStateToPublish' :'requestNotification.modifyNotAllowed') : 'routing.unauthorized') | i18n}}"
matTooltip="{{(isAdmin() ? (atLeastOneSelected() ? 'routing.illegalAssetStateToPublish' :'requestNotification.modifyNotAllowed') : 'routing.unauthorized') | i18n}}"
matTooltipClass="table--header--tooltip"
matTooltipPosition="above"
[class.mdc-tooltip--multiline]="true"
[matTooltipShowDelay]="500"
[matTooltipDisabled]="isAllowedToPublish() && atLeastOneSelected() && !isIllegalSelectionToPublish()">
[matTooltipDisabled]="isAdmin() && atLeastOneSelected() && !isIllegalSelectionToPublish()">
<app-button
iconName="published_with_changes"
class="action-button-tile"
matTooltip="{{'actions.publishAssets'| i18n}}"
[matTooltipShowDelay]="500"
matTooltipPosition="above"
[isDisabled]="!isAllowedToPublish() || !atLeastOneSelected() || isIllegalSelectionToPublish()"
[isDisabled]="!isAdmin() || !atLeastOneSelected() || isIllegalSelectionToPublish()"
(click)="publishIconClicked()"
(keydown)="handleKeyDownPublishIconClicked($event)"
></app-button>
</div>

<div class="action-button-tile"
matTooltip="{{(isAdmin() ? (atLeastOneSelected() ? 'routing.syncSelectedParts' :'routing.refreshAllParts') : 'routing.unauthorized') | i18n}}"
matTooltipClass="table--header--tooltip"
matTooltipPosition="above"
[class.mdc-tooltip--multiline]="true"
[matTooltipShowDelay]="500">
<app-button
iconName="published_with_changes"
class="action-button-tile"
[isDisabled]="!isAdmin()"
(click)="partReloadIconClicked()"
(keydown)="handleKeyDownPartReloadIconClicked($event)"
></app-button>
</div>

</div>
<div class="selected-text-container">
<p *ngIf="selectedPartsInfoLabel"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import { Sort } from '@angular/material/sort';
import { Pagination } from '@core/model/pagination.model';
import { PartsFacade } from '@page/parts/core/parts.facade';
import { MainAspectType } from '@page/parts/model/mainAspectType.enum';
import { PartReloadOperation } from '@page/parts/model/partReloadOperation.enum';
import { PartsTableComponent } from '@shared/components/parts-table/parts-table.component';
import { NotificationType } from '@shared/model/notification.model';
import { FormatPartSemanticDataModelToCamelCasePipe } from '@shared/pipes/format-part-semantic-data-model-to-camelcase.pipe';
Expand Down Expand Up @@ -297,5 +299,38 @@ describe('PartsTableComponent', () => {
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);
});
});

Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -72,6 +73,7 @@ export class PartsTableComponent implements OnInit {
@ViewChildren(MultiSelectAutocompleteComponent) multiSelectAutocompleteComponents: QueryList<MultiSelectAutocompleteComponent>;

@Output() publishIconClickedEvent = new EventEmitter<void>();
@Output() partReloadClickedEvent = new EventEmitter<PartReloadOperation>();
@Input() labelId: string;
@Input() noShadow = false;
@Input() showHover = true;
Expand Down Expand Up @@ -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' ]);
}

Expand All @@ -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<unknown>();
public readonly selection = new SelectionModel<unknown>(true, []);

Expand Down
12 changes: 12 additions & 0 deletions frontend/src/app/modules/shared/service/parts.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pagination<Part>> {

let sort = sorting.map(sortingItem => PartsAssembler.mapSortToApiSort(sortingItem));
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/assets/locales/de/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/assets/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit f7c1298

Please sign in to comment.