From a8c88c935d7c9c55305d0ca760ff1959c74e5c7f Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Tue, 27 Jun 2023 17:04:58 +0200 Subject: [PATCH 01/13] feature(view):[TRACEFOSS-1549] create alert detailed view --- .../core/investigation-detail.facade.ts | 7 +++++++ .../detail/investigation-detail.component.html | 13 ++++++++++++- .../detail/investigation-detail.component.ts | 2 +- frontend/src/assets/locales/de/common.json | 3 ++- frontend/src/assets/locales/en/common.json | 3 ++- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/modules/page/investigations/core/investigation-detail.facade.ts b/frontend/src/app/modules/page/investigations/core/investigation-detail.facade.ts index 5d7dd8100c..8deb2d16f3 100644 --- a/frontend/src/app/modules/page/investigations/core/investigation-detail.facade.ts +++ b/frontend/src/app/modules/page/investigations/core/investigation-detail.facade.ts @@ -21,6 +21,8 @@ import { TitleCasePipe } from '@angular/common'; import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { getRoute, INVESTIGATION_BASE_ROUTE } from '@core/known-route'; import { InvestigationDetailState } from '@page/investigations/core/investigation-detail.state'; import { Part, SemanticDataModel } from '@page/parts/model/parts.model'; import { Notification } from '@shared/model/notification.model'; @@ -39,6 +41,7 @@ export class InvestigationDetailFacade { private readonly partsService: PartsService, private readonly investigationDetailState: InvestigationDetailState, private readonly titleCasePipe: TitleCasePipe, + private readonly router: Router, ) { } @@ -121,6 +124,10 @@ export class InvestigationDetailFacade { this.investigationDetailState.supplierPartsInformation = { data: [ ...sortedData ] }; } + public navigateBackToInvestigations(pageIndex: number): void { + const { link } = getRoute(INVESTIGATION_BASE_ROUTE); + this.router.navigate([`/${link}`]).then(); + } public unsubscribeSubscriptions(): void { this.notificationPartsInformationDescription?.unsubscribe(); this.supplierPartsSubscription?.unsubscribe(); diff --git a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html index 17f6d13d4f..11fd0076d8 100644 --- a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html +++ b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html @@ -20,7 +20,18 @@ -->
-
+
+ +
+ arrow_back + {{ 'actions.goBack' | i18n }} +
+
+
+
Date: Wed, 28 Jun 2023 17:56:29 +0200 Subject: [PATCH 02/13] feature(view):[TRACEFOSS-1549] update investigation detailed view --- .../core/investigation-detail.facade.ts | 7 ------ .../investigation-detail.component.html | 2 +- .../detail/investigation-detail.component.ts | 20 ++++++++++++++- .../presentation/investigations.component.ts | 25 ++++++++++++++++--- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/modules/page/investigations/core/investigation-detail.facade.ts b/frontend/src/app/modules/page/investigations/core/investigation-detail.facade.ts index 8deb2d16f3..5d7dd8100c 100644 --- a/frontend/src/app/modules/page/investigations/core/investigation-detail.facade.ts +++ b/frontend/src/app/modules/page/investigations/core/investigation-detail.facade.ts @@ -21,8 +21,6 @@ import { TitleCasePipe } from '@angular/common'; import { Injectable } from '@angular/core'; -import { Router } from '@angular/router'; -import { getRoute, INVESTIGATION_BASE_ROUTE } from '@core/known-route'; import { InvestigationDetailState } from '@page/investigations/core/investigation-detail.state'; import { Part, SemanticDataModel } from '@page/parts/model/parts.model'; import { Notification } from '@shared/model/notification.model'; @@ -41,7 +39,6 @@ export class InvestigationDetailFacade { private readonly partsService: PartsService, private readonly investigationDetailState: InvestigationDetailState, private readonly titleCasePipe: TitleCasePipe, - private readonly router: Router, ) { } @@ -124,10 +121,6 @@ export class InvestigationDetailFacade { this.investigationDetailState.supplierPartsInformation = { data: [ ...sortedData ] }; } - public navigateBackToInvestigations(pageIndex: number): void { - const { link } = getRoute(INVESTIGATION_BASE_ROUTE); - this.router.navigate([`/${link}`]).then(); - } public unsubscribeSubscriptions(): void { this.notificationPartsInformationDescription?.unsubscribe(); this.supplierPartsSubscription?.unsubscribe(); diff --git a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html index 11fd0076d8..ecc11991c0 100644 --- a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html +++ b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.html @@ -23,7 +23,7 @@
arrow_back diff --git a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.ts b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.ts index 3073bbae87..1fc6da59ea 100644 --- a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.ts +++ b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.ts @@ -20,7 +20,8 @@ ********************************************************************************/ import { AfterViewInit, Component, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; +import { getRoute, INVESTIGATION_BASE_ROUTE } from '@core/known-route'; import { InvestigationDetailFacade } from '@page/investigations/core/investigation-detail.facade'; import { InvestigationHelperService } from '@page/investigations/core/investigation-helper.service'; import { InvestigationsFacade } from '@page/investigations/core/investigations.facade'; @@ -71,16 +72,21 @@ export class InvestigationDetailComponent implements AfterViewInit, OnDestroy { public notificationPartsTableConfig: TableConfig; public supplierPartsTableConfig: TableConfig; public isReceived: boolean; + private originPageNumber: number; + private originTabIndex: number; private subscription: Subscription; private selectedInvestigationTmpStore: Notification; public selectedInvestigation: Notification; + private paramSubscription: Subscription + constructor( public readonly helperService: InvestigationHelperService, public readonly investigationDetailFacade: InvestigationDetailFacade, private readonly staticIdService: StaticIdService, private readonly investigationsFacade: InvestigationsFacade, + private router: Router, private readonly route: ActivatedRoute, private readonly ctaSnackbarService: CtaSnackbarService, ) { @@ -88,6 +94,12 @@ export class InvestigationDetailComponent implements AfterViewInit, OnDestroy { this.supplierPartsDetailInformation$ = this.investigationDetailFacade.supplierPartsInformation$; this.selected$ = this.investigationDetailFacade.selected$; + + this.paramSubscription = this.route.queryParams.subscribe(params => { + this.originPageNumber = params.pageNumber; + this.originTabIndex = params?.tabIndex; + }) + } public ngAfterViewInit(): void { @@ -109,6 +121,7 @@ export class InvestigationDetailComponent implements AfterViewInit, OnDestroy { public ngOnDestroy(): void { this.subscription?.unsubscribe(); this.investigationDetailFacade.unsubscribeSubscriptions(); + this.paramSubscription?.unsubscribe(); } public onNotificationPartsSort({ sorting }: TableEventConfig): void { @@ -147,6 +160,11 @@ export class InvestigationDetailComponent implements AfterViewInit, OnDestroy { navigator.clipboard.writeText(semanticModelId).then(_ => this.ctaSnackbarService.show(text)); } + public navigateBackToInvestigations(): void { + const { link } = getRoute(INVESTIGATION_BASE_ROUTE); + this.router.navigate([`/${link}`], {queryParams: {tabIndex: this.originTabIndex, pageNumber: this.originPageNumber}}); + } + public handleConfirmActionCompletedEvent(): void { this.investigationDetailFacade.selected = { loader: true }; this.subscription?.unsubscribe(); diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts b/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts index 199bf0f836..dea04a7e47 100644 --- a/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts @@ -20,7 +20,7 @@ ********************************************************************************/ import { AfterContentInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { getRoute, INVESTIGATION_BASE_ROUTE } from '@core/known-route'; import { InvestigationDetailFacade } from '@page/investigations/core/investigation-detail.facade'; import { InvestigationHelperService } from '@page/investigations/core/investigation-helper.service'; @@ -33,6 +33,7 @@ import { ApproveNotificationModalComponent } from '@shared/modules/notification/ import { CancelNotificationModalComponent } from '@shared/modules/notification/modal/cancel/cancel-notification-modal.component'; import { CloseNotificationModalComponent } from '@shared/modules/notification/modal/close/close-notification-modal.component'; import { DeclineNotificationModalComponent } from '@shared/modules/notification/modal/decline/decline-notification-modal.component'; +import { Subscription } from 'rxjs'; import { InvestigationsFacade } from '../core/investigations.facade'; @Component({ @@ -53,6 +54,8 @@ export class InvestigationsComponent implements OnInit, OnDestroy, AfterContentI public menuActionsConfig: MenuActionConfig[]; + private paramSubscription: Subscription; + private pagination: TableEventConfig = { page: 0, pageSize: 50, sorting: ['createdDate' , 'desc'] }; constructor( @@ -60,14 +63,22 @@ export class InvestigationsComponent implements OnInit, OnDestroy, AfterContentI private readonly investigationsFacade: InvestigationsFacade, private readonly investigationDetailFacade: InvestigationDetailFacade, private readonly router: Router, + private readonly route: ActivatedRoute ) { this.investigationsReceived$ = this.investigationsFacade.investigationsReceived$; this.investigationsQueuedAndRequested$ = this.investigationsFacade.investigationsQueuedAndRequested$; } public ngOnInit(): void { - this.investigationsFacade.setReceivedInvestigation(this.pagination.page, this.pagination.pageSize, this.pagination.sorting); - this.investigationsFacade.setQueuedAndRequestedInvestigations(this.pagination.page, this.pagination.pageSize, this.pagination.sorting); + this.paramSubscription = this.route.queryParams.subscribe(params => { + if(params.pageNumber) { + this.pagination.page = params.pageNumber; + + } else { + this.investigationsFacade.setReceivedInvestigation(0, this.pagination.pageSize, this.pagination.sorting); + this.investigationsFacade.setQueuedAndRequestedInvestigations(0, this.pagination.pageSize, this.pagination.sorting); + } + }) } public ngAfterContentInit(): void { @@ -113,6 +124,7 @@ export class InvestigationsComponent implements OnInit, OnDestroy, AfterContentI public ngOnDestroy(): void { this.investigationsFacade.stopInvestigations(); + this.paramSubscription?.unsubscribe(); } public onReceivedTableConfigChanged(pagination: TableEventConfig) { @@ -128,7 +140,12 @@ export class InvestigationsComponent implements OnInit, OnDestroy, AfterContentI public openDetailPage(notification: Notification): void { this.investigationDetailFacade.selected = { data: notification }; const { link } = getRoute(INVESTIGATION_BASE_ROUTE); - this.router.navigate([`/${link}/${notification.id}`]).then(); + let params: any = { + pageNumber: this.pagination.page + } + const tabIndex = this.route.snapshot.queryParamMap.get('tabIndex'); + params = {tabIndex: tabIndex, ...params} + this.router.navigate([`/${link}/${notification.id}`], { queryParams: params }); } public handleConfirmActionCompletedEvent() { From 6a4872ddfa214af6e67497c0109bad1badf70361 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Thu, 29 Jun 2023 16:20:06 +0200 Subject: [PATCH 03/13] feature(view):[TRACEFOSS-1549] delete label in all notification modals --- .../accept/accept-notification-modal.component.html | 2 -- .../accept-notification-modal.component.spec.ts | 2 -- .../acknowledge-notification-modal.component.html | 1 - .../acknowledge-notification-modal.component.spec.ts | 2 -- .../approve-notification-modal.component.html | 1 - .../approve-notification-modal.component.spec.ts | 2 -- .../cancel/cancel-notification-modal.component.html | 1 - .../cancel-notification-modal.component.spec.ts | 2 -- .../close/close-notification-modal.component.html | 1 - .../close/close-notification-modal.component.spec.ts | 2 -- .../decline-notification-modal.component.html | 1 - .../decline-notification-modal.component.spec.ts | 2 -- frontend/src/assets/locales/de/common.json | 12 ------------ frontend/src/assets/locales/en/common.json | 12 ------------ 14 files changed, 43 deletions(-) diff --git a/frontend/src/app/modules/shared/modules/notification/modal/accept/accept-notification-modal.component.html b/frontend/src/app/modules/shared/modules/notification/modal/accept/accept-notification-modal.component.html index b67e97565b..cc20a09582 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/accept/accept-notification-modal.component.html +++ b/frontend/src/app/modules/shared/modules/notification/modal/accept/accept-notification-modal.component.html @@ -20,9 +20,7 @@ --> -

{{ translationContext + '.modal.acceptDescription' | i18n }}

-
{{ translationContext + '.modal.acceptReasonHint' | i18n }} diff --git a/frontend/src/app/modules/shared/modules/notification/modal/accept/accept-notification-modal.component.spec.ts b/frontend/src/app/modules/shared/modules/notification/modal/accept/accept-notification-modal.component.spec.ts index 1a4d9b681f..6c1786c378 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/accept/accept-notification-modal.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/notification/modal/accept/accept-notification-modal.component.spec.ts @@ -29,13 +29,11 @@ describe('AcceptNotificationModalComponent', () => { it('should create accept modal', async () => { await renderAcceptModal(NotificationStatus.ACKNOWLEDGED); const title = await waitFor(() => screen.getByText('commonInvestigation.modal.acceptTitle')); - const hint = await waitFor(() => screen.getByText('commonInvestigation.modal.acceptDescription')); const hint2 = await waitFor(() => screen.getByText('commonInvestigation.modal.acceptReasonHint')); const buttonL = await waitFor(() => screen.getByText('actions.cancel')); const buttonR = await waitFor(() => screen.getByText('actions.accept')); expect(title).toBeInTheDocument(); - expect(hint).toBeInTheDocument(); expect(hint2).toBeInTheDocument(); expect(buttonL).toBeInTheDocument(); expect(buttonR).toBeInTheDocument(); diff --git a/frontend/src/app/modules/shared/modules/notification/modal/acknowledge/acknowledge-notification-modal.component.html b/frontend/src/app/modules/shared/modules/notification/modal/acknowledge/acknowledge-notification-modal.component.html index d711c2ecda..2c315d6465 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/acknowledge/acknowledge-notification-modal.component.html +++ b/frontend/src/app/modules/shared/modules/notification/modal/acknowledge/acknowledge-notification-modal.component.html @@ -20,6 +20,5 @@ --> -

{{ translationContext + '.modal.acknowledgeDescription' | i18n }}

diff --git a/frontend/src/app/modules/shared/modules/notification/modal/acknowledge/acknowledge-notification-modal.component.spec.ts b/frontend/src/app/modules/shared/modules/notification/modal/acknowledge/acknowledge-notification-modal.component.spec.ts index 2a64e07286..25ab6bdc6f 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/acknowledge/acknowledge-notification-modal.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/notification/modal/acknowledge/acknowledge-notification-modal.component.spec.ts @@ -28,12 +28,10 @@ describe('AcknowledgeNotificationModalComponent', () => { it('should create acknowledge modal', async () => { await renderAcknowledgeModal(NotificationStatus.RECEIVED); const title = await waitFor(() => screen.getByText('commonInvestigation.modal.acknowledgeTitle')); - const hint = await waitFor(() => screen.getByText('commonInvestigation.modal.acknowledgeDescription')); const buttonL = await waitFor(() => screen.getByText('actions.cancel')); const buttonR = await waitFor(() => screen.getByText('actions.acknowledge')); expect(title).toBeInTheDocument(); - expect(hint).toBeInTheDocument(); expect(buttonL).toBeInTheDocument(); expect(buttonR).toBeInTheDocument(); }); diff --git a/frontend/src/app/modules/shared/modules/notification/modal/approve/approve-notification-modal.component.html b/frontend/src/app/modules/shared/modules/notification/modal/approve/approve-notification-modal.component.html index a201e94faa..2c315d6465 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/approve/approve-notification-modal.component.html +++ b/frontend/src/app/modules/shared/modules/notification/modal/approve/approve-notification-modal.component.html @@ -20,6 +20,5 @@ --> -

{{ translationContext + '.modal.approvalDescription' | i18n }}

diff --git a/frontend/src/app/modules/shared/modules/notification/modal/approve/approve-notification-modal.component.spec.ts b/frontend/src/app/modules/shared/modules/notification/modal/approve/approve-notification-modal.component.spec.ts index 17634849bf..5cc0d6e3e3 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/approve/approve-notification-modal.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/notification/modal/approve/approve-notification-modal.component.spec.ts @@ -28,12 +28,10 @@ describe('ApproveNotificationModalComponent', () => { it('should create approve modal', async () => { await renderApproveModal(NotificationStatus.CREATED); const title = await waitFor(() => screen.getByText('commonInvestigation.modal.approvalTitle')); - const hint = await waitFor(() => screen.getByText('commonInvestigation.modal.approvalDescription')); const buttonL = await waitFor(() => screen.getByText('actions.cancel')); const buttonR = await waitFor(() => screen.getByText('actions.confirm')); expect(title).toBeInTheDocument(); - expect(hint).toBeInTheDocument(); expect(buttonL).toBeInTheDocument(); expect(buttonR).toBeInTheDocument(); }); diff --git a/frontend/src/app/modules/shared/modules/notification/modal/cancel/cancel-notification-modal.component.html b/frontend/src/app/modules/shared/modules/notification/modal/cancel/cancel-notification-modal.component.html index 2ce788d8b1..7eb45b0aef 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/cancel/cancel-notification-modal.component.html +++ b/frontend/src/app/modules/shared/modules/notification/modal/cancel/cancel-notification-modal.component.html @@ -20,7 +20,6 @@ --> -

{{ translationContext + '.modal.cancellationDescription' | i18n }}

diff --git a/frontend/src/app/modules/shared/modules/notification/modal/cancel/cancel-notification-modal.component.spec.ts b/frontend/src/app/modules/shared/modules/notification/modal/cancel/cancel-notification-modal.component.spec.ts index 6498ad5486..8184440405 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/cancel/cancel-notification-modal.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/notification/modal/cancel/cancel-notification-modal.component.spec.ts @@ -28,13 +28,11 @@ describe('CancelNotificationModalComponent', () => { it('should create cancel modal', async () => { await renderCancelModal(NotificationStatus.CREATED); const title = await waitFor(() => screen.getByText('commonInvestigation.modal.cancellationTitle')); - const hint = await waitFor(() => screen.getByText('commonInvestigation.modal.cancellationDescription')); const hint2 = await waitFor(() => screen.getByText('commonInvestigation.modal.cancellationHint')); const buttonL = await waitFor(() => screen.getByText('actions.cancel')); const buttonR = await waitFor(() => screen.getByText('actions.cancellationConfirm')); expect(title).toBeInTheDocument(); - expect(hint).toBeInTheDocument(); expect(hint2).toBeInTheDocument(); expect(buttonL).toBeInTheDocument(); expect(buttonR).toBeInTheDocument(); diff --git a/frontend/src/app/modules/shared/modules/notification/modal/close/close-notification-modal.component.html b/frontend/src/app/modules/shared/modules/notification/modal/close/close-notification-modal.component.html index ebf91f8bc9..6b53b86df4 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/close/close-notification-modal.component.html +++ b/frontend/src/app/modules/shared/modules/notification/modal/close/close-notification-modal.component.html @@ -20,7 +20,6 @@ --> -

{{ translationContext + '.modal.closeDescription' | i18n }}

diff --git a/frontend/src/app/modules/shared/modules/notification/modal/close/close-notification-modal.component.spec.ts b/frontend/src/app/modules/shared/modules/notification/modal/close/close-notification-modal.component.spec.ts index b64595dc21..8a7af8d4db 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/close/close-notification-modal.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/notification/modal/close/close-notification-modal.component.spec.ts @@ -29,13 +29,11 @@ describe('CloseNotificationModalComponent', () => { it('should create close modal', async () => { await renderCloseModal(NotificationStatus.SENT); const title = await waitFor(() => screen.getByText('commonInvestigation.modal.closeTitle')); - const hint = await waitFor(() => screen.getByText('commonInvestigation.modal.closeDescription')); const hint2 = await waitFor(() => screen.getByText('commonInvestigation.modal.closeReasonHint')); const buttonL = await waitFor(() => screen.getByText('actions.cancel')); const buttonR = await waitFor(() => screen.getByText('actions.close')); expect(title).toBeInTheDocument(); - expect(hint).toBeInTheDocument(); expect(hint2).toBeInTheDocument(); expect(buttonL).toBeInTheDocument(); expect(buttonR).toBeInTheDocument(); diff --git a/frontend/src/app/modules/shared/modules/notification/modal/decline/decline-notification-modal.component.html b/frontend/src/app/modules/shared/modules/notification/modal/decline/decline-notification-modal.component.html index 27e22470e2..64e86996c0 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/decline/decline-notification-modal.component.html +++ b/frontend/src/app/modules/shared/modules/notification/modal/decline/decline-notification-modal.component.html @@ -20,7 +20,6 @@ --> -

{{ translationContext + '.modal.declineDescription' | i18n }}

diff --git a/frontend/src/app/modules/shared/modules/notification/modal/decline/decline-notification-modal.component.spec.ts b/frontend/src/app/modules/shared/modules/notification/modal/decline/decline-notification-modal.component.spec.ts index 489d8b06fd..b3f28ed69c 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/decline/decline-notification-modal.component.spec.ts +++ b/frontend/src/app/modules/shared/modules/notification/modal/decline/decline-notification-modal.component.spec.ts @@ -28,13 +28,11 @@ describe('DeclineNotificationModalComponent', () => { it('should create close modal', async () => { await renderDeclineModal(NotificationStatus.ACKNOWLEDGED); const title = await waitFor(() => screen.getByText('commonInvestigation.modal.declineTitle')); - const hint = await waitFor(() => screen.getByText('commonInvestigation.modal.declineDescription')); const hint2 = await waitFor(() => screen.getByText('commonInvestigation.modal.declineReasonHint')); const buttonL = await waitFor(() => screen.getByText('actions.cancel')); const buttonR = await waitFor(() => screen.getByText('actions.decline')); expect(title).toBeInTheDocument(); - expect(hint).toBeInTheDocument(); expect(hint2).toBeInTheDocument(); expect(buttonL).toBeInTheDocument(); expect(buttonR).toBeInTheDocument(); diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index e25d7c3e38..24b05d682e 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -175,20 +175,14 @@ }, "modal": { "acceptTitle": "Annehmen der Untersuchung", - "acceptDescription": "Sind Sie sicher, dass Sie diese Untersuchung annehmen wollen?", "acceptReasonHint": "Geben Sie den Grund für die Annahme dieser Untersuchung ein.", "acknowledgeTitle": "Bestätigung der Untersuchung", - "acknowledgeDescription": "Sind Sie sicher, dass Sie diese Untersuchung bestätigen wollen?", "approvalTitle": "Genehmigung der Untersuchung", - "approvalDescription": "Sind Sie sicher, dass Sie diese Untersuchung genehmigen wollen?", "closeTitle": "Schließen der Untersuchung", - "closeDescription": "Sind Sie sicher, dass Sie diese Untersuchung schließen wollen?", "closeReasonHint": "Geben Sie den Grund für das Schließen ein.", "cancellationTitle": "Abbruch der Untersuchung", - "cancellationDescription": "Sind Sie sicher, dass Sie diese Untersuchung abbrechen wollen?", "cancellationHint": "Geben Sie die ID der Untersuchung ein, um Ihre Abbruchanfrage zu bestätigen.", "declineTitle": "Ablehnung der Untersuchung", - "declineDescription": "Sind Sie sicher, dass Sie diese Untersuchung ablehnen wollen?", "declineReasonHint": "Geben Sie den Grund für die Ablehnung dieser Untersuchung ein.", "successfullyAccepted": "Untersuchung wurde erfolgreich angenommen.", "successfullyAcknowledged": "Untersuchung wurde erfolgreich bestätigt", @@ -222,20 +216,14 @@ }, "modal": { "acceptTitle": "Annehmen der Qualitätswarnung", - "acceptDescription": "Sind Sie sicher, dass Sie diese Qualitätswarnung annehmen wollen?", "acceptReasonHint": "Geben Sie den Grund für die Annahme dieser Qualitätswarnung ein.", "acknowledgeTitle": "Bestätigung der Qualitätswarnung", - "acknowledgeDescription": "Sind Sie sicher, dass Sie diese Qualitätswarnung bestätigen wollen?", "approvalTitle": "Genehmigung der Qualitätswarnung", - "approvalDescription": "Sind Sie sicher, dass Sie diese Qualitätswarnung genehmigen wollen?", "closeTitle": "Schließen der Qualitätswarnung", - "closeDescription": "Sind Sie sicher, dass Sie diese Qualitätswarnung schließen wollen?", "closeReasonHint": "Geben Sie den Grund für das Schließen ein.", "cancellationTitle": "Abbruch der Qualitätswarnung", - "cancellationDescription": "Sind Sie sicher, dass Sie diese Qualitätswarnung abbrechen wollen?", "cancellationHint": "Geben Sie die ID der Qualitätswarnung ein, um Ihre Abbruchanfrage zu bestätigen.", "declineTitle": "Ablehnung der Qualitätswarnung", - "declineDescription": "Sind Sie sicher, dass Sie diese Qualitätswarnung ablehnen wollen?", "declineReasonHint": "Geben Sie den Grund für die Ablehnung dieser Qualitätswarnung ein.", "successfullyAccepted": "Qualitätswarnung wurde erfolgreich angenommen.", "successfullyAcknowledged": "Qualitätswarnung wurde erfolgreich bestätigt", diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index 30466b9dd4..fc8ed6b3e7 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -178,20 +178,14 @@ }, "modal": { "acceptTitle": "Accept of investigation", - "acceptDescription": "Are you sure you want to accept this investigation?", "acceptReasonHint": "Enter the reason for accepting this investigation.", "acknowledgeTitle": "Acknowledgment of investigation", - "acknowledgeDescription": "Are you sure you want to acknowledge this investigation?", "approvalTitle": "Approval of investigation", - "approvalDescription": "Are you sure you want to approve this investigation?", "closeTitle": "Close of investigation", - "closeDescription": "Are you sure you want to close this investigation?", "closeReasonHint": "Enter the reason for close action.", "cancellationTitle": "Cancellation of investigation", - "cancellationDescription": "Are you sure you want to cancel this investigation?", "cancellationHint": "Enter the ID of the investigation to confirm your cancellation.", "declineTitle": "Decline of investigation", - "declineDescription": "Are you sure you want to decline this investigation?", "declineReasonHint": "Enter the reason for declining this investigation.", "successfullyAccepted": "Investigation was accepted successfully.", "successfullyAcknowledged": "Investigation was acknowledged successfully.", @@ -225,20 +219,14 @@ }, "modal": { "acceptTitle": "Accept of alert", - "acceptDescription": "Are you sure you want to accept this alert?", "acceptReasonHint": "Enter the reason for accepting this alert.", "acknowledgeTitle": "Acknowledgment of alert", - "acknowledgeDescription": "Are you sure you want to acknowledge this alert?", "approvalTitle": "Approval of alert", - "approvalDescription": "Are you sure you want to approve this alert?", "closeTitle": "Close of alert", - "closeDescription": "Are you sure you want to close this alert?", "closeReasonHint": "Enter the reason for close action.", "cancellationTitle": "Cancellation of alert", - "cancellationDescription": "Are you sure you want to cancel this alert?", "cancellationHint": "Enter the ID of the alert to confirm your cancellation.", "declineTitle": "Decline of alert", - "declineDescription": "Are you sure you want to decline this alert?", "declineReasonHint": "Enter the reason for declining this alert.", "successfullyAccepted": "Alert was accepted successfully.", "successfullyAcknowledged": "Alert was acknowledged successfully.", From 69d46c4b0930d21e8c6450b4fa13439f365b642f Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Thu, 29 Jun 2023 16:41:55 +0200 Subject: [PATCH 04/13] feature(view):[TRACEFOSS-1549] fix tests --- .../detail/investigation-detail.component.spec.ts | 3 +++ .../presentation/investigations.component.spec.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.spec.ts b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.spec.ts index 815d09b477..b7c73fe319 100644 --- a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.spec.ts +++ b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.spec.ts @@ -25,9 +25,11 @@ import { InvestigationsModule } from '@page/investigations/investigations.module import { InvestigationsService } from '@shared/service/investigations.service'; import { fireEvent, screen, waitFor } from '@testing-library/angular'; import { renderComponent } from '@tests/test-render.utils'; +import { of } from 'rxjs'; import { MOCK_part_1 } from '../../../../mocks/services/parts-mock/parts.test.model'; describe('InvestigationDetailComponent', () => { + const renderInvestigationDetail = async (id?: string) => { return await renderComponent(InvestigationDetailComponent, { imports: [InvestigationsModule], @@ -41,6 +43,7 @@ describe('InvestigationDetailComponent', () => { get: () => id || 'id-2', }, }, + queryParams: of({ pageNumber: 0, tabIndex: 0 }) }, }, ], diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts b/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts index 173c11909c..a5f0f2cd7b 100644 --- a/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts @@ -42,7 +42,7 @@ describe('InvestigationsComponent', () => { spy.and.returnValue(new Promise(null)); fireEvent.click(await waitFor(() => screen.getByTestId('table-menu-button--actions.viewDetails'))); - expect(spy).toHaveBeenCalledWith(['/investigations/id-84']); + expect(spy).toHaveBeenCalledWith(['/investigations/id-84'], Object({ queryParams: Object({ tabIndex: null, pageNumber: 0 }) }) ); }); it('should call change pagination of received investigations', async () => { From 6fb52e3627dab1ebee9731e690fafc1e74e753db Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Thu, 29 Jun 2023 16:45:20 +0200 Subject: [PATCH 05/13] feature(view):[TRACEFOSS-1549] fix tests --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e50bc66d5a..cdef0ad468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] ### Added +- Added back button in notification detailed view ### Changed +- Changed Layout in notification detailed view ### Removed From 70adf3f42f54843abab7906b68abc72f31f73de5 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Thu, 29 Jun 2023 16:58:45 +0200 Subject: [PATCH 06/13] feature(view):[TRACEFOSS-1549] added test for back button --- .../detail/investigation-detail.component.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.spec.ts b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.spec.ts index b7c73fe319..c216649c9a 100644 --- a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.spec.ts +++ b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.spec.ts @@ -61,6 +61,11 @@ describe('InvestigationDetailComponent', () => { await waitFor(() => expect(screen.getByText('pageInvestigation.subHeadline.supplierParts')).toBeInTheDocument()); }); + it('should render specific text for back button', async () => { + await renderInvestigationDetail('id-1'); + await waitFor(() => expect(screen.getByText('actions.goBack')).toBeInTheDocument()); + }); + it('should render copy data to clipboard', async () => { await renderInvestigationDetail('id-1'); await waitFor(() => expect(screen.getByText('pageInvestigation.subHeadline.supplierParts')).toBeInTheDocument()); From f21484385779a5ce03b23ba25d654b14571da992 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Fri, 30 Jun 2023 10:08:48 +0200 Subject: [PATCH 07/13] feature(view):[TRACEFOSS-1549] added test for back button --- .../investigations.component.spec.ts | 4 +++- .../presentation/investigations.component.ts | 18 +++++---------- .../model/notification-tab-information.ts | 23 +++++++++++++++++++ 3 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 frontend/src/app/modules/shared/model/notification-tab-information.ts diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts b/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts index a5f0f2cd7b..16a313ed93 100644 --- a/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts @@ -21,6 +21,7 @@ import { InvestigationsModule } from '@page/investigations/investigations.module'; import { InvestigationsComponent } from '@page/investigations/presentation/investigations.component'; +import { NotificationTabInformation } from '@shared/model/notification-tab-information'; import { InvestigationsService } from '@shared/service/investigations.service'; import { fireEvent, screen, waitFor } from '@testing-library/angular'; import { renderComponent } from '@tests/test-render.utils'; @@ -42,7 +43,8 @@ describe('InvestigationsComponent', () => { spy.and.returnValue(new Promise(null)); fireEvent.click(await waitFor(() => screen.getByTestId('table-menu-button--actions.viewDetails'))); - expect(spy).toHaveBeenCalledWith(['/investigations/id-84'], Object({ queryParams: Object({ tabIndex: null, pageNumber: 0 }) }) ); + const tabInformation: NotificationTabInformation = { tabIndex: NaN, pageNumber: undefined} + expect(spy).toHaveBeenCalledWith(['/investigations/id-84'], { queryParams: tabInformation } ); }); it('should call change pagination of received investigations', async () => { diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts b/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts index dea04a7e47..bce9a430cf 100644 --- a/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts @@ -25,6 +25,7 @@ import { getRoute, INVESTIGATION_BASE_ROUTE } from '@core/known-route'; import { InvestigationDetailFacade } from '@page/investigations/core/investigation-detail.facade'; import { InvestigationHelperService } from '@page/investigations/core/investigation-helper.service'; import { MenuActionConfig, TableEventConfig } from '@shared/components/table/table.model'; +import { NotificationTabInformation } from '@shared/model/notification-tab-information'; import { Notification } from '@shared/model/notification.model'; import { TranslationContext } from '@shared/model/translation-context.model'; import { AcceptNotificationModalComponent } from '@shared/modules/notification/modal/accept/accept-notification-modal.component'; @@ -71,14 +72,10 @@ export class InvestigationsComponent implements OnInit, OnDestroy, AfterContentI public ngOnInit(): void { this.paramSubscription = this.route.queryParams.subscribe(params => { - if(params.pageNumber) { - this.pagination.page = params.pageNumber; - - } else { - this.investigationsFacade.setReceivedInvestigation(0, this.pagination.pageSize, this.pagination.sorting); - this.investigationsFacade.setQueuedAndRequestedInvestigations(0, this.pagination.pageSize, this.pagination.sorting); - } + this.pagination.page = params?.pageNumber; }) + this.investigationsFacade.setReceivedInvestigation(this.pagination.page, this.pagination.pageSize, this.pagination.sorting); + this.investigationsFacade.setQueuedAndRequestedInvestigations(this.pagination.page, this.pagination.pageSize, this.pagination.sorting); } public ngAfterContentInit(): void { @@ -140,12 +137,9 @@ export class InvestigationsComponent implements OnInit, OnDestroy, AfterContentI public openDetailPage(notification: Notification): void { this.investigationDetailFacade.selected = { data: notification }; const { link } = getRoute(INVESTIGATION_BASE_ROUTE); - let params: any = { - pageNumber: this.pagination.page - } const tabIndex = this.route.snapshot.queryParamMap.get('tabIndex'); - params = {tabIndex: tabIndex, ...params} - this.router.navigate([`/${link}/${notification.id}`], { queryParams: params }); + const tabInformation: NotificationTabInformation = {tabIndex: parseInt(tabIndex), pageNumber: this.pagination.page} + this.router.navigate([`/${link}/${notification.id}`], { queryParams: tabInformation }); } public handleConfirmActionCompletedEvent() { diff --git a/frontend/src/app/modules/shared/model/notification-tab-information.ts b/frontend/src/app/modules/shared/model/notification-tab-information.ts new file mode 100644 index 0000000000..3fb1f8cda0 --- /dev/null +++ b/frontend/src/app/modules/shared/model/notification-tab-information.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 type NotificationTabInformation = { + tabIndex: number | null, + pageNumber?: number | null +} From 5e3fc8ea14200451d376a59fa1275d77af2e773f Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Fri, 30 Jun 2023 10:34:28 +0200 Subject: [PATCH 08/13] feature(view):[TRACEFOSS-1549] added E2E-A testenvironemnt --- frontend/angular.json | 11 +++++++ frontend/package.json | 1 + .../src/environments/environment.authE2eA.ts | 30 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 frontend/src/environments/environment.authE2eA.ts diff --git a/frontend/angular.json b/frontend/angular.json index 25bf03c776..b2527f9485 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -80,6 +80,14 @@ } ] }, + "authE2eA": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.authE2eA.ts" + } + ] + }, "authLocal": { "fileReplacements": [ { @@ -161,6 +169,9 @@ "authTest": { "browserTarget": "trace-x:build:dev,authTest" }, + "authE2eA": { + "browserTarget": "trace-x:build:dev,authE2eA" + }, "authLocal": { "browserTarget": "trace-x:build:dev,authLocal" }, diff --git a/frontend/package.json b/frontend/package.json index 190794601a..b5a5a0cabb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ "start" : "ng serve", "start:auth" : "ng serve --configuration=dev,auth", "start:auth:test": "ng serve --configuration=dev,authTest", + "start:auth:e2ea": "ng serve --configuration=dev,authE2eA", "start:auth:mock" : "ng serve --configuration=authMock", "start:auth:local" : "ng serve --configuration=authLocal", "start:auth:localBe" : "ng serve --configuration=localBackend", diff --git a/frontend/src/environments/environment.authE2eA.ts b/frontend/src/environments/environment.authE2eA.ts new file mode 100644 index 0000000000..9f1d85def9 --- /dev/null +++ b/frontend/src/environments/environment.authE2eA.ts @@ -0,0 +1,30 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +import { _environment } from './_environment.base'; + +export const environment = { + ..._environment, + mockService: false, + authDisabled: false, + apiUrl: 'https://traceability-e2e-a.dev.demo.catena-x.net/api', + keycloakUrl: 'https://centralidp.dev.demo.catena-x.net/auth', + clientId: 'Cl17-CX-Part', + api: '', +}; From b9414151ae174bd9d5709b65d1a7ddcaf3215bb4 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Fri, 30 Jun 2023 15:23:31 +0200 Subject: [PATCH 09/13] feature(view):[TRACEFOSS-1549] added alert detailed view --- CHANGELOG.md | 1 + .../app/modules/page/alerts/alerts.module.ts | 11 +- .../app/modules/page/alerts/alerts.routing.ts | 11 +- .../page/alerts/core/alert-detail.facade.ts | 131 +++++++++ .../page/alerts/core/alert-detail.state.ts | 73 +++++ .../modules/page/alerts/core/alerts.facade.ts | 6 +- .../alerts/detail/alert-detail.component.html | 264 ++++++++++++++++++ .../alerts/detail/alert-detail.component.scss | 52 ++++ .../detail/alert-detail.component.spec.ts | 76 +++++ .../alerts/detail/alert-detail.component.ts | 214 ++++++++++++++ .../alerts/presentation/alerts.component.html | 1 + .../presentation/alerts.component.spec.ts | 15 +- .../alerts/presentation/alerts.component.ts | 31 +- .../detail/investigation-detail.component.ts | 1 - .../investigations.component.spec.ts | 2 +- .../presentation/investigations.component.ts | 11 +- .../model/notification-tab-information.ts | 2 +- .../notification-tab.component.ts | 2 +- .../modules/shared/service/alerts.service.ts | 9 +- .../src/assets/locales/de/page.alert.json | 14 + .../src/assets/locales/de/page.alerts.json | 2 - .../src/assets/locales/en/page.alert.json | 14 + .../src/assets/locales/en/page.alerts.json | 2 - 23 files changed, 924 insertions(+), 21 deletions(-) create mode 100644 frontend/src/app/modules/page/alerts/core/alert-detail.facade.ts create mode 100644 frontend/src/app/modules/page/alerts/core/alert-detail.state.ts create mode 100644 frontend/src/app/modules/page/alerts/detail/alert-detail.component.html create mode 100644 frontend/src/app/modules/page/alerts/detail/alert-detail.component.scss create mode 100644 frontend/src/app/modules/page/alerts/detail/alert-detail.component.spec.ts create mode 100644 frontend/src/app/modules/page/alerts/detail/alert-detail.component.ts create mode 100644 frontend/src/assets/locales/de/page.alert.json delete mode 100644 frontend/src/assets/locales/de/page.alerts.json create mode 100644 frontend/src/assets/locales/en/page.alert.json delete mode 100644 frontend/src/assets/locales/en/page.alerts.json diff --git a/CHANGELOG.md b/CHANGELOG.md index cdef0ad468..b680f9f21f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - Added back button in notification detailed view +- Added alert detail view ### Changed - Changed Layout in notification detailed view diff --git a/frontend/src/app/modules/page/alerts/alerts.module.ts b/frontend/src/app/modules/page/alerts/alerts.module.ts index 72903f71b3..f2fd3f3b25 100644 --- a/frontend/src/app/modules/page/alerts/alerts.module.ts +++ b/frontend/src/app/modules/page/alerts/alerts.module.ts @@ -21,9 +21,13 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { getI18nPageProvider } from '@core/i18n'; import { AlertsRoutingModule } from '@page/alerts/alerts.routing'; +import { AlertDetailFacade } from '@page/alerts/core/alert-detail.facade'; +import { AlertDetailState } from '@page/alerts/core/alert-detail.state'; import { AlertHelperService } from '@page/alerts/core/alert-helper.service'; import { AlertsFacade } from '@page/alerts/core/alerts.facade'; import { AlertsState } from '@page/alerts/core/alerts.state'; +import { AlertDetailComponent } from '@page/alerts/detail/alert-detail.component'; +import { PartsModule } from '@page/parts/parts.module'; import { NotificationModule } from '@shared/modules/notification/notification.module'; import { SharedModule } from '@shared/shared.module'; import { TemplateModule } from '@shared/template.module'; @@ -32,7 +36,7 @@ import { AlertsComponent } from './presentation/alerts.component'; @NgModule({ declarations: [ - AlertsComponent + AlertsComponent, AlertDetailComponent ], imports: [ CommonModule, @@ -40,12 +44,15 @@ import { AlertsComponent } from './presentation/alerts.component'; SharedModule, AlertsRoutingModule, NotificationModule, + PartsModule ], providers: [ AlertsFacade, AlertsState, + AlertDetailFacade, + AlertDetailState, AlertHelperService, - ...getI18nPageProvider('page.alerts'), + ...getI18nPageProvider('page.alert'), ] }) export class AlertsModule { } diff --git a/frontend/src/app/modules/page/alerts/alerts.routing.ts b/frontend/src/app/modules/page/alerts/alerts.routing.ts index 3adf277430..7089d8a22d 100644 --- a/frontend/src/app/modules/page/alerts/alerts.routing.ts +++ b/frontend/src/app/modules/page/alerts/alerts.routing.ts @@ -19,6 +19,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { AlertDetailComponent } from '@page/alerts/detail/alert-detail.component'; import { AlertsComponent } from '@page/alerts/presentation/alerts.component'; import { I18NEXT_NAMESPACE_RESOLVER } from 'angular-i18next'; @@ -28,7 +29,14 @@ const ALERTS_ROUTING: Routes = [ path: '', pathMatch: 'full', component: AlertsComponent, - data: { i18nextNamespaces: ['page.alerts'] }, + data: { i18nextNamespaces: ['page.alert'] }, + resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, + }, + { + path: ':alertId', + pathMatch: 'full', + component: AlertDetailComponent, + data: { i18nextNamespaces: ['page.alert'] }, resolve: { i18next: I18NEXT_NAMESPACE_RESOLVER }, }, ]; @@ -38,3 +46,4 @@ const ALERTS_ROUTING: Routes = [ exports: [RouterModule], }) export class AlertsRoutingModule {} + diff --git a/frontend/src/app/modules/page/alerts/core/alert-detail.facade.ts b/frontend/src/app/modules/page/alerts/core/alert-detail.facade.ts new file mode 100644 index 0000000000..9019de3dca --- /dev/null +++ b/frontend/src/app/modules/page/alerts/core/alert-detail.facade.ts @@ -0,0 +1,131 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +import { TitleCasePipe } from '@angular/common'; +import { Injectable } from '@angular/core'; +import { AlertDetailState } from '@page/alerts/core/alert-detail.state'; +import { Part, SemanticDataModel } from '@page/parts/model/parts.model'; +import { Notification } from '@shared/model/notification.model'; +import { View } from '@shared/model/view.model'; +import { PartsService } from '@shared/service/parts.service'; +import { Observable, of, Subscription } from 'rxjs'; +import { filter, map, switchMap } from 'rxjs/operators'; +import { SortDirection } from '../../../../mocks/services/pagination.helper'; + +@Injectable() +export class AlertDetailFacade { + private notificationPartsInformationDescription: Subscription; + private supplierPartsSubscription: Subscription; + + constructor( + private readonly partsService: PartsService, + private readonly alertDetailState: AlertDetailState, + private readonly titleCasePipe: TitleCasePipe, + ) { + } + + public get notificationPartsInformation$(): Observable> { + return this.alertDetailState.alertPartsInformation$; + } + + public get supplierPartsInformation$(): Observable> { + return this.alertDetailState.supplierPartsInformation$; + } + + public get selected$(): Observable> { + return this.alertDetailState.selected$; + } + + public set selected(selectedAlert: View) { + this.alertDetailState.selected = selectedAlert; + } + + public get selected(): View { + return this.alertDetailState.selected; + } + + public setAlertPartsInformation(notification: Notification): void { + this.notificationPartsInformationDescription?.unsubscribe(); + this.alertDetailState.alertPartsInformation = { loader: true }; + + if (!notification.assetIds.length) { + this.alertDetailState.alertPartsInformation = { data: [] }; + return; + } + + this.notificationPartsInformationDescription = this.partsService + .getPartDetailOfIds(notification.assetIds) + .subscribe({ + next: data => { + data.forEach(part => { + part.semanticDataModel = this.titleCasePipe.transform(part.semanticDataModel); + }) + this.alertDetailState.alertPartsInformation = { data }; + }, + error: error => (this.alertDetailState.alertPartsInformation = { error }), + }); + } + + public setAndSupplierPartsInformation(): void { + this.supplierPartsSubscription?.unsubscribe(); + this.alertDetailState.supplierPartsInformation = { loader: true }; + + this.supplierPartsSubscription = this.alertDetailState.alertPartsInformation$ + .pipe( + filter(view => !!view.data), + map(({ data }) => this.getIdsFromPartList(data)), + switchMap(partIds => (!!partIds && !!partIds.length ? this.partsService.getPartDetailOfIds(partIds) : of([]))), + ) + .subscribe({ + next: data => { + data.forEach(part => { + part.semanticDataModel = this.titleCasePipe.transform(part.semanticDataModel); + }) + this.alertDetailState.supplierPartsInformation = { data }; + }, + error: error => (this.alertDetailState.supplierPartsInformation = { error }), + }); + } + + public sortNotificationParts(key: string, direction: SortDirection): void { + const { data } = this.alertDetailState.alertPartsInformation; + if (!data) return; + + const sortedData = this.partsService.sortParts(data, key, direction); + this.alertDetailState.alertPartsInformation = { data: [ ...sortedData ] }; + } + + public sortSupplierParts(key: string, direction: SortDirection): void { + const { data } = this.alertDetailState.supplierPartsInformation; + if (!data) return; + + const sortedData = this.partsService.sortParts(data, key, direction); + this.alertDetailState.supplierPartsInformation = { data: [ ...sortedData ] }; + } + + public unsubscribeSubscriptions(): void { + this.notificationPartsInformationDescription?.unsubscribe(); + this.supplierPartsSubscription?.unsubscribe(); + } + + private getIdsFromPartList(parts: Part[]): string[] { + const childIds = parts.map(part => part.children).reduce((p, c) => [ ...p, ...c ], []); + return [ ...new Set(childIds) ]; + } +} diff --git a/frontend/src/app/modules/page/alerts/core/alert-detail.state.ts b/frontend/src/app/modules/page/alerts/core/alert-detail.state.ts new file mode 100644 index 0000000000..a68047d419 --- /dev/null +++ b/frontend/src/app/modules/page/alerts/core/alert-detail.state.ts @@ -0,0 +1,73 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +import { Injectable } from '@angular/core'; +import { Part } from '@page/parts/model/parts.model'; +import { Notification } from '@shared/model/notification.model'; +import { State } from '@shared/model/state'; +import { View } from '@shared/model/view.model'; +import { Observable } from 'rxjs'; + +@Injectable() +export class AlertDetailState { + private readonly _alertPartsInformation$ = new State>({ loader: true }); + private readonly _supplierPartsInformation$ = new State>({ loader: true }); + + private readonly _selected$ = new State>({ loader: true }); + + // Detailed information for parts assigned to an alert + public get alertPartsInformation$(): Observable> { + return this._alertPartsInformation$.observable; + } + + public get alertPartsInformation(): View { + return this._alertPartsInformation$.snapshot; + } + + public set alertPartsInformation(view: View) { + this._alertPartsInformation$.update(view); + } + + // Detailed information for child parts assigned to an alert + public get supplierPartsInformation$(): Observable> { + return this._supplierPartsInformation$.observable; + } + + public get supplierPartsInformation(): View { + return this._supplierPartsInformation$.snapshot; + } + + public set supplierPartsInformation(view: View) { + this._supplierPartsInformation$.update(view); + } + + // Selected Notification + public get selected$(): Observable> { + return this._selected$.observable; + } + + public set selected({ data, loader, error }: View) { + const view: View = { data, loader, error }; + this._selected$.update(view); + } + + get selected(): View { + return this._selected$.snapshot; + } +} diff --git a/frontend/src/app/modules/page/alerts/core/alerts.facade.ts b/frontend/src/app/modules/page/alerts/core/alerts.facade.ts index 5b244a8a28..74f2f2a346 100644 --- a/frontend/src/app/modules/page/alerts/core/alerts.facade.ts +++ b/frontend/src/app/modules/page/alerts/core/alerts.facade.ts @@ -20,7 +20,7 @@ import { Injectable } from '@angular/core'; import { AlertsState } from '@page/alerts/core/alerts.state'; import { TableHeaderSort } from '@shared/components/table/table.model'; -import { Notifications, NotificationStatus } from '@shared/model/notification.model'; +import { Notification, Notifications, NotificationStatus } from '@shared/model/notification.model'; import { View } from '@shared/model/view.model'; import { AlertsService } from '@shared/service/alerts.service'; import { Observable, Subscription } from 'rxjs'; @@ -43,6 +43,10 @@ export class AlertsFacade { return this.alertsState.alertsQueuedAndRequested$; } + public getAlert(id: string): Observable { + return this.alertsService.getAlert(id); + } + public setReceivedAlerts(page = 0, pageSize = 50, sorting: TableHeaderSort = null): void { this.alertReceivedSubscription?.unsubscribe(); this.alertReceivedSubscription = this.alertsService diff --git a/frontend/src/app/modules/page/alerts/detail/alert-detail.component.html b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.html new file mode 100644 index 0000000000..88d4f95d75 --- /dev/null +++ b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.html @@ -0,0 +1,264 @@ + + +
+
+ +
+ arrow_back + {{ 'actions.goBack' | i18n }} +
+
+
+
+ +
+ share + {{ 'actions.approve' | i18n }} +
+
+ +
+ cancel + {{ 'actions.cancel' | i18n }} +
+
+ +
+ close + {{ 'actions.close' | i18n }} +
+
+ + +
+ assignment_turned_in + {{ 'actions.accept' | i18n }} +
+
+ + +
+ work + {{ 'actions.acknowledge' | i18n }} +
+
+ + +
+ assignment_late + {{ 'actions.decline' | i18n }} +
+
+
+
+ + + + + + + + + + + +
+ + +

{{ 'pageAlert.info' | i18n }}

+
+ + + +
+ + + +

+ {{ 'pageAlert.subHeadline.' + (isReceived ? 'affectedParts' : 'supplierParts') | i18n }} +

+
+ + + +
+
+ +
+ + +

{{ 'pageAlert.reason' | i18n }}

+
+ + + +
+ + + +

{{ 'pageAlert.subHeadline.supplierParts' | i18n }}

+
+ + + +
+
+ + + +
+ {{ row.semanticModelId }} + filter_none +
+
+
+ + + + + + + + + + + + + + + + + + +

{{ 'dataLoading.error' | i18n }}

+

{{ view.error | json }}

+
+ + + + + + + + diff --git a/frontend/src/app/modules/page/alerts/detail/alert-detail.component.scss b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.scss new file mode 100644 index 0000000000..6b7995eba2 --- /dev/null +++ b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.scss @@ -0,0 +1,52 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +.detail--header { + display: flex; + justify-content: space-between; +} + +.detail--wrapper { + display: grid; + grid-template-columns: 30% 70%; + + @media (max-width: 1024px) { + grid-template-columns: 100%; + } +} + +.detail--wrapper__supplier { + margin-top: 1.5rem; +} + +.detail--table_wrapper--notification { + margin-left: 1.5rem; + + @media (max-width: 1024px) { + margin-left: 0; + margin-top: 1.5rem; + } +} + +.alert--semantic-model-id__icon { + width: 14px; + height: 14px; + font-size: 14px; + margin-left: 0.5rem; +} diff --git a/frontend/src/app/modules/page/alerts/detail/alert-detail.component.spec.ts b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.spec.ts new file mode 100644 index 0000000000..baf41ba5b5 --- /dev/null +++ b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.spec.ts @@ -0,0 +1,76 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +import { ActivatedRoute } from '@angular/router'; +import { AlertsModule } from '@page/alerts/alerts.module'; +import { AlertDetailComponent } from '@page/alerts/detail/alert-detail.component'; +import { AlertsService } from '@shared/service/alerts.service'; +import { fireEvent, screen, waitFor } from '@testing-library/angular'; +import { renderComponent } from '@tests/test-render.utils'; +import { of } from 'rxjs'; +import { MOCK_part_1 } from '../../../../mocks/services/parts-mock/parts.test.model'; + +describe('AlertDetailComponent', () => { + + const renderAlertDetail = async (id?: string) => { + return await renderComponent(AlertDetailComponent, { + imports: [AlertsModule], + providers: [ + AlertsService, + { + provide: ActivatedRoute, + useValue: { + snapshot: { + paramMap: { + get: () => id || 'id-2', + }, + }, + queryParams: of({ pageNumber: 0, tabIndex: 0 }) + }, + }, + ], + }); + }; + + it('should render specific text and additional table for received alert', async () => { + await renderAlertDetail(); + await waitFor(() => expect(screen.getByText('pageAlert.subHeadline.affectedParts')).toBeInTheDocument()); + await waitFor(() => expect(screen.getByText('pageAlert.subHeadline.supplierParts')).toBeInTheDocument()); + }); + + it('should render specific text for queued or requested alerts', async () => { + await renderAlertDetail('id-1'); + await waitFor(() => expect(screen.getByText('pageAlert.subHeadline.supplierParts')).toBeInTheDocument()); + }); + + it('should render specific text for back button', async () => { + await renderAlertDetail('id-1'); + await waitFor(() => expect(screen.getByText('actions.goBack')).toBeInTheDocument()); + }); + + it('should render copy data to clipboard', async () => { + await renderAlertDetail('id-1'); + await waitFor(() => expect(screen.getByText('pageAlert.subHeadline.supplierParts')).toBeInTheDocument()); + + const spy = spyOn(navigator.clipboard, 'writeText').and.returnValue(new Promise(null)); + fireEvent.click(await waitFor(() => screen.getByTestId('copy-button--' + MOCK_part_1.id))); + + expect(spy).toHaveBeenCalledWith(MOCK_part_1.semanticModelId); + }); +}); diff --git a/frontend/src/app/modules/page/alerts/detail/alert-detail.component.ts b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.ts new file mode 100644 index 0000000000..2b5d5b7b93 --- /dev/null +++ b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.ts @@ -0,0 +1,214 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +import { AfterViewInit, Component, OnDestroy, TemplateRef, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ALERT_BASE_ROUTE, getRoute } from '@core/known-route'; +import { AlertDetailFacade } from '@page/alerts/core/alert-detail.facade'; +import { AlertHelperService } from '@page/alerts/core/alert-helper.service'; +import { AlertsFacade } from '@page/alerts/core/alerts.facade'; +import { Part } from '@page/parts/model/parts.model'; +import { CtaSnackbarService } from '@shared/components/call-to-action-snackbar/cta-snackbar.service'; +import { CreateHeaderFromColumns, TableConfig, TableEventConfig } from '@shared/components/table/table.model'; +import { Notification } from '@shared/model/notification.model'; +import { TranslationContext } from '@shared/model/translation-context.model'; +import { View } from '@shared/model/view.model'; +import { AcceptNotificationModalComponent } from '@shared/modules/notification/modal/accept/accept-notification-modal.component'; +import { AcknowledgeNotificationModalComponent } from '@shared/modules/notification/modal/acknowledge/acknowledge-notification-modal.component'; +import { ApproveNotificationModalComponent } from '@shared/modules/notification/modal/approve/approve-notification-modal.component'; +import { CancelNotificationModalComponent } from '@shared/modules/notification/modal/cancel/cancel-notification-modal.component'; +import { CloseNotificationModalComponent } from '@shared/modules/notification/modal/close/close-notification-modal.component'; +import { DeclineNotificationModalComponent } from '@shared/modules/notification/modal/decline/decline-notification-modal.component'; +import { StaticIdService } from '@shared/service/staticId.service'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; +import { filter, first, tap } from 'rxjs/operators'; + +@Component({ + selector: 'app-alert-detail', + templateUrl: './alert-detail.component.html', + styleUrls: ['./alert-detail.component.scss'], +}) +export class AlertDetailComponent implements AfterViewInit, OnDestroy { + @ViewChild(ApproveNotificationModalComponent) approveModal: ApproveNotificationModalComponent; + @ViewChild(CloseNotificationModalComponent) closeModal: CloseNotificationModalComponent; + @ViewChild(CancelNotificationModalComponent) cancelModal: CancelNotificationModalComponent; + + @ViewChild(AcceptNotificationModalComponent) acceptModal: AcceptNotificationModalComponent; + @ViewChild(AcknowledgeNotificationModalComponent) acknowledgeModal: AcknowledgeNotificationModalComponent; + @ViewChild(DeclineNotificationModalComponent) declineModal: DeclineNotificationModalComponent; + + @ViewChild('semanticModelIdTmp') semanticModelIdTmp: TemplateRef; + + public readonly alertPartsInformation$: Observable>; + public readonly supplierPartsDetailInformation$: Observable>; + public readonly selected$: Observable>; + + public readonly isAlertOpen$ = new BehaviorSubject(false); + public readonly selectedItems$ = new BehaviorSubject([]); + public readonly deselectPartTrigger$ = new Subject(); + public readonly addPartTrigger$ = new Subject(); + + public readonly notificationPartsTableId = this.staticIdService.generateId('AlertDetail'); + public readonly supplierPartsTableId = this.staticIdService.generateId('AlertDetail'); + + public notificationPartsTableConfig: TableConfig; + public supplierPartsTableConfig: TableConfig; + public isReceived: boolean; + private originPageNumber: number; + private originTabIndex: number; + + private subscription: Subscription; + private selectedAlertTmpStore: Notification; + public selectedAlert: Notification; + + private paramSubscription: Subscription + + constructor( + public readonly helperService: AlertHelperService, + public readonly alertDetailFacade: AlertDetailFacade, + private readonly staticIdService: StaticIdService, + private readonly alertsFacade: AlertsFacade, + private router: Router, + private readonly route: ActivatedRoute, + private readonly ctaSnackbarService: CtaSnackbarService, + ) { + this.alertPartsInformation$ = this.alertDetailFacade.notificationPartsInformation$; + this.supplierPartsDetailInformation$ = this.alertDetailFacade.supplierPartsInformation$; + + this.selected$ = this.alertDetailFacade.selected$; + + this.paramSubscription = this.route.queryParams.subscribe(params => { + this.originPageNumber = params.pageNumber; + this.originTabIndex = params?.tabIndex; + }) + + } + + public ngAfterViewInit(): void { + if (!this.alertDetailFacade.selected?.data) { + this.selectedNotificationBasedOnUrl(); + } + + this.subscription = this.selected$ + .pipe( + filter(({ data }) => !!data), + tap(({ data }) => { + this.setTableConfigs(data); + this.selectedAlert = data; + }), + ) + .subscribe(); + } + + public ngOnDestroy(): void { + this.subscription?.unsubscribe(); + this.alertDetailFacade.unsubscribeSubscriptions(); + this.paramSubscription?.unsubscribe(); + } + + public onNotificationPartsSort({ sorting }: TableEventConfig): void { + const [name, direction] = sorting || ['', '']; + this.alertDetailFacade.sortNotificationParts(name, direction); + } + + public onSupplierPartsSort({ sorting }: TableEventConfig): void { + const [name, direction] = sorting || ['', '']; + this.alertDetailFacade.sortSupplierParts(name, direction); + } + + public onMultiSelect(event: unknown[]): void { + this.selectedAlertTmpStore = Object.assign(this.alertDetailFacade.selected); + this.selectedItems$.next(event as Part[]); + } + + public removeItemFromSelection(part: Part): void { + this.deselectPartTrigger$.next([part]); + this.selectedItems$.next(this.selectedItems$.getValue().filter(({ id }) => id !== part.id)); + } + + public clearSelected(): void { + this.deselectPartTrigger$.next(this.selectedItems$.getValue()); + this.selectedItems$.next([]); + } + + public addItemToSelection(part: Part): void { + this.addPartTrigger$.next(part); + this.selectedItems$.next([...this.selectedItems$.getValue(), part]); + } + + public copyToClipboard(semanticModelId: string): void { + const text = { id: 'clipboard', values: { value: semanticModelId } }; + navigator.clipboard.writeText(semanticModelId).then(_ => this.ctaSnackbarService.show(text)); + } + + public navigateBackToAlerts(): void { + const { link } = getRoute(ALERT_BASE_ROUTE); + this.router.navigate([`/${link}`], {queryParams: {tabIndex: this.originTabIndex, pageNumber: this.originPageNumber}}); + } + + public handleConfirmActionCompletedEvent(): void { + this.alertDetailFacade.selected = { loader: true }; + this.subscription?.unsubscribe(); + this.ngAfterViewInit(); + } + + private setTableConfigs(data: Notification): void { + this.isReceived = !data.isFromSender; + + const displayedColumns = ['id', 'semanticDataModel', 'name', 'semanticModelId']; + const sortableColumns = { id: true, semanticDataModel: true, name: true, semanticModelId: true }; + + const tableConfig = { + displayedColumns, + header: CreateHeaderFromColumns(displayedColumns, 'table.column'), + sortableColumns: sortableColumns, + hasPagination: false, + cellRenderers: { + semanticModelId: this.semanticModelIdTmp, + }, + }; + + this.alertDetailFacade.setAlertPartsInformation(data); + this.notificationPartsTableConfig = { ...tableConfig }; + + if (!this.isReceived) { + return; + } + + this.alertDetailFacade.setAndSupplierPartsInformation(); + this.supplierPartsTableConfig = { + ...tableConfig, + displayedColumns: ['select', ...displayedColumns], + header: CreateHeaderFromColumns(['select', ...displayedColumns], 'table.column'), + }; + } + + private selectedNotificationBasedOnUrl(): void { + const alertId = this.route.snapshot.paramMap.get('alertId'); + this.alertsFacade + .getAlert(alertId) + .pipe( + first(), + tap(notification => (this.alertDetailFacade.selected = { data: notification })), + ) + .subscribe(); + } + + protected readonly TranslationContext = TranslationContext; +} diff --git a/frontend/src/app/modules/page/alerts/presentation/alerts.component.html b/frontend/src/app/modules/page/alerts/presentation/alerts.component.html index f3c70c9c10..f63e57e653 100644 --- a/frontend/src/app/modules/page/alerts/presentation/alerts.component.html +++ b/frontend/src/app/modules/page/alerts/presentation/alerts.component.html @@ -61,5 +61,6 @@ [queuedAndRequestedSortableColumns]="{status: true, createdDate: true, severity: true, sendTo: true}" (onReceivedTableConfigChanged)="onReceivedTableConfigChange($event)" (onQueuedAndRequestedTableConfigChanged)="onQueuedAndRequestedTableConfigChange($event)" + (selected)="openDetailPage($event)" [menuActionsConfig]="menuActionsConfig" > diff --git a/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts b/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts index afdf7e7b16..a687e4a04a 100644 --- a/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts +++ b/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts @@ -18,6 +18,8 @@ ********************************************************************************/ import { AlertsModule } from '@page/alerts/alerts.module'; +import { NotificationTabInformation } from '@shared/model/notification-tab-information'; +import { AlertsService } from '@shared/service/alerts.service'; import { fireEvent, screen, waitFor } from '@testing-library/angular'; import { renderComponent } from '@tests/test-render.utils'; @@ -28,11 +30,22 @@ describe('AlertsComponent', () => { const renderAlerts = async () => { return await renderComponent(AlertsComponent, { imports: [AlertsModule], - providers: [], + providers: [AlertsService], translations: ['page.alert'], }); }; + it('should call detail page with correct ID', async () => { + const { fixture } = await renderAlerts(); + fireEvent.click((await waitFor(() => screen.getAllByTestId('table-menu-button')))[0]); + + const spy = spyOn((fixture.componentInstance as any).router, 'navigate'); + spy.and.returnValue(new Promise(null)); + + fireEvent.click(await waitFor(() => screen.getByTestId('table-menu-button--actions.viewDetails'))); + const tabInformation: NotificationTabInformation = { tabIndex: null, pageNumber: undefined} + expect(spy).toHaveBeenCalledWith(['/alerts/id-84'], { queryParams: tabInformation } ); + }); it('should call change pagination of received alerts', async () => { await renderAlerts(); diff --git a/frontend/src/app/modules/page/alerts/presentation/alerts.component.ts b/frontend/src/app/modules/page/alerts/presentation/alerts.component.ts index 8371c15de3..c357eeb1a8 100644 --- a/frontend/src/app/modules/page/alerts/presentation/alerts.component.ts +++ b/frontend/src/app/modules/page/alerts/presentation/alerts.component.ts @@ -18,9 +18,13 @@ ********************************************************************************/ import { Component, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ALERT_BASE_ROUTE, getRoute } from '@core/known-route'; +import { AlertDetailFacade } from '@page/alerts/core/alert-detail.facade'; import { AlertHelperService } from '@page/alerts/core/alert-helper.service'; import { AlertsFacade } from '@page/alerts/core/alerts.facade'; import { MenuActionConfig, TableEventConfig } from '@shared/components/table/table.model'; +import { NotificationTabInformation } from '@shared/model/notification-tab-information'; import { Notification } from '@shared/model/notification.model'; import { TranslationContext } from '@shared/model/translation-context.model'; import { AcceptNotificationModalComponent } from '@shared/modules/notification/modal/accept/accept-notification-modal.component'; @@ -29,6 +33,7 @@ import { ApproveNotificationModalComponent } from '@shared/modules/notification/ import { CancelNotificationModalComponent } from '@shared/modules/notification/modal/cancel/cancel-notification-modal.component'; import { CloseNotificationModalComponent } from '@shared/modules/notification/modal/close/close-notification-modal.component'; import { DeclineNotificationModalComponent } from '@shared/modules/notification/modal/decline/decline-notification-modal.component'; +import { Subscription } from 'rxjs'; @Component({ selector: 'app-alerts', @@ -49,19 +54,30 @@ export class AlertsComponent { public menuActionsConfig: MenuActionConfig[]; + private paramSubscription: Subscription; + private pagination: TableEventConfig = { page: 0, pageSize: 50, sorting: ['createdDate' , 'desc'] }; constructor( public readonly helperService: AlertHelperService, - private readonly alertsFacade: AlertsFacade + private readonly alertsFacade: AlertsFacade, + private readonly alertDetailFacade: AlertDetailFacade, + private readonly router: Router, + private readonly route: ActivatedRoute ) { this.alertsReceived$ = this.alertsFacade.alertsReceived$; this.alertsQueuedAndRequested$ = this.alertsFacade.alertsQueuedAndRequested$; } public ngOnInit(): void { - this.alertsFacade.setReceivedAlerts(this.pagination.page, this.pagination.pageSize, this.pagination.sorting); - this.alertsFacade.setQueuedAndRequestedAlerts(this.pagination.page, this.pagination.pageSize, this.pagination.sorting); + this.paramSubscription = this.route.queryParams.subscribe(params => { + if(params.pageNumber) { + this.pagination.page = params.pageNumber; + } else { + this.alertsFacade.setReceivedAlerts(0, this.pagination.pageSize, this.pagination.sorting); + this.alertsFacade.setQueuedAndRequestedAlerts(0, this.pagination.pageSize, this.pagination.sorting); + } + }) } public ngAfterContentInit(): void { @@ -107,6 +123,7 @@ export class AlertsComponent { public ngOnDestroy(): void { this.alertsFacade.stopAlerts(); + this.paramSubscription?.unsubscribe(); } public onReceivedTableConfigChange(pagination: TableEventConfig) { @@ -119,6 +136,14 @@ export class AlertsComponent { this.alertsFacade.setQueuedAndRequestedAlerts(this.pagination.page, this.pagination.pageSize, this.pagination.sorting); } + public openDetailPage(notification: Notification): void { + this.alertDetailFacade.selected = { data: notification }; + const { link } = getRoute(ALERT_BASE_ROUTE); + const tabIndex = this.route.snapshot.queryParamMap.get('tabIndex'); + const tabInformation: NotificationTabInformation = {tabIndex: tabIndex, pageNumber: this.pagination.page} + this.router.navigate([`/${link}/${notification.id}`], { queryParams: tabInformation }); + } + public handleConfirmActionCompletedEvent() { this.ngOnInit(); } diff --git a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.ts b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.ts index 1fc6da59ea..991e1362c8 100644 --- a/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.ts +++ b/frontend/src/app/modules/page/investigations/detail/investigation-detail.component.ts @@ -136,7 +136,6 @@ export class InvestigationDetailComponent implements AfterViewInit, OnDestroy { public onMultiSelect(event: unknown[]): void { this.selectedInvestigationTmpStore = Object.assign(this.investigationDetailFacade.selected); - this.selectedItems$.next(event as Part[]); } diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts b/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts index 16a313ed93..5995f55b3f 100644 --- a/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.spec.ts @@ -43,7 +43,7 @@ describe('InvestigationsComponent', () => { spy.and.returnValue(new Promise(null)); fireEvent.click(await waitFor(() => screen.getByTestId('table-menu-button--actions.viewDetails'))); - const tabInformation: NotificationTabInformation = { tabIndex: NaN, pageNumber: undefined} + const tabInformation: NotificationTabInformation = { tabIndex: null, pageNumber: 0} expect(spy).toHaveBeenCalledWith(['/investigations/id-84'], { queryParams: tabInformation } ); }); diff --git a/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts b/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts index bce9a430cf..a7e5a2ca9b 100644 --- a/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts +++ b/frontend/src/app/modules/page/investigations/presentation/investigations.component.ts @@ -72,10 +72,13 @@ export class InvestigationsComponent implements OnInit, OnDestroy, AfterContentI public ngOnInit(): void { this.paramSubscription = this.route.queryParams.subscribe(params => { - this.pagination.page = params?.pageNumber; + if(params.pageNumber) { + this.pagination.page = params.pageNumber; + } else { + this.investigationsFacade.setReceivedInvestigation(0, this.pagination.pageSize, this.pagination.sorting); + this.investigationsFacade.setQueuedAndRequestedInvestigations(0, this.pagination.pageSize, this.pagination.sorting); + } }) - this.investigationsFacade.setReceivedInvestigation(this.pagination.page, this.pagination.pageSize, this.pagination.sorting); - this.investigationsFacade.setQueuedAndRequestedInvestigations(this.pagination.page, this.pagination.pageSize, this.pagination.sorting); } public ngAfterContentInit(): void { @@ -138,7 +141,7 @@ export class InvestigationsComponent implements OnInit, OnDestroy, AfterContentI this.investigationDetailFacade.selected = { data: notification }; const { link } = getRoute(INVESTIGATION_BASE_ROUTE); const tabIndex = this.route.snapshot.queryParamMap.get('tabIndex'); - const tabInformation: NotificationTabInformation = {tabIndex: parseInt(tabIndex), pageNumber: this.pagination.page} + const tabInformation: NotificationTabInformation = {tabIndex: tabIndex, pageNumber: this.pagination.page} this.router.navigate([`/${link}/${notification.id}`], { queryParams: tabInformation }); } diff --git a/frontend/src/app/modules/shared/model/notification-tab-information.ts b/frontend/src/app/modules/shared/model/notification-tab-information.ts index 3fb1f8cda0..228b90a149 100644 --- a/frontend/src/app/modules/shared/model/notification-tab-information.ts +++ b/frontend/src/app/modules/shared/model/notification-tab-information.ts @@ -18,6 +18,6 @@ ********************************************************************************/ export type NotificationTabInformation = { - tabIndex: number | null, + tabIndex: string | null, pageNumber?: number | null } diff --git a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts index 80d63bcd4b..45ff4035e6 100644 --- a/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts +++ b/frontend/src/app/modules/shared/modules/notification/notification-tab/notification-tab.component.ts @@ -40,7 +40,7 @@ export class NotificationTabComponent implements AfterViewInit { @Input() notificationsView$: Observable>; @Input() labelId: string; @Input() hasPagination = true; - @Input() translationContext: 'commonInvestigation' | 'pageAlerts'; + @Input() translationContext: 'commonInvestigation' | 'commonAlert'; @Input() menuActionsConfig: MenuActionConfig[]; @Input() optionalColumns: Array<'targetDate' | 'severity' | 'createdBy' | 'sendTo'> = []; @Input() sortableColumns: Record = {}; diff --git a/frontend/src/app/modules/shared/service/alerts.service.ts b/frontend/src/app/modules/shared/service/alerts.service.ts index d9249040bb..229df2a27c 100644 --- a/frontend/src/app/modules/shared/service/alerts.service.ts +++ b/frontend/src/app/modules/shared/service/alerts.service.ts @@ -29,7 +29,8 @@ import { Severity } from '@shared/model/severity.model'; import type { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { - NotificationCreateResponse, + Notification, + NotificationCreateResponse, NotificationResponse, Notifications, NotificationsResponse, NotificationStatus, @@ -62,6 +63,12 @@ export class AlertsService { .pipe(map(alerts => NotificationAssembler.assembleNotifications(alerts))); } + public getAlert(id: string): Observable { + return this.apiService + .get(`${this.url}/alerts/${id}`) + .pipe(map(notification => NotificationAssembler.assembleNotification(notification))); + } + public postAlert(partIds: string[], description: string, severity: Severity, bpn: string): Observable { const body = { partIds, description, severity, bpn }; diff --git a/frontend/src/assets/locales/de/page.alert.json b/frontend/src/assets/locales/de/page.alert.json new file mode 100644 index 0000000000..7956fece37 --- /dev/null +++ b/frontend/src/assets/locales/de/page.alert.json @@ -0,0 +1,14 @@ +{ + "pageAlert": { + "info": "Übersicht", + "reason": "Nachrichtenverlauf", + "selectedParts_one": "{{count}} Produkt für diese Seite ausgewählt.", + "selectedParts_other": "{{count}} Produkte für diese Seite ausgewählt.", + "selectPartsAction": "Warnung starten", + + "subHeadline": { + "affectedParts": "Betroffene Produkte", + "supplierParts": "Produkte von Lieferanten" + } + } +} diff --git a/frontend/src/assets/locales/de/page.alerts.json b/frontend/src/assets/locales/de/page.alerts.json deleted file mode 100644 index 2c63c08510..0000000000 --- a/frontend/src/assets/locales/de/page.alerts.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} diff --git a/frontend/src/assets/locales/en/page.alert.json b/frontend/src/assets/locales/en/page.alert.json new file mode 100644 index 0000000000..d3fd5f0318 --- /dev/null +++ b/frontend/src/assets/locales/en/page.alert.json @@ -0,0 +1,14 @@ +{ + "pageAlert": { + "info": "Overview", + "reason": "Message history", + "selectedParts_one": "{{count}} Part selected for this page.", + "selectedParts_other": "{{count}} Parts selected for this page.", + "selectPartsAction": "Start alert", + + "subHeadline": { + "affectedParts": "Affected parts", + "supplierParts": "Supplier parts" + } + } +} diff --git a/frontend/src/assets/locales/en/page.alerts.json b/frontend/src/assets/locales/en/page.alerts.json deleted file mode 100644 index 2c63c08510..0000000000 --- a/frontend/src/assets/locales/en/page.alerts.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} From 412e870f10bf2e91953dd2a95ac91b04a79c1028 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Fri, 30 Jun 2023 15:30:32 +0200 Subject: [PATCH 10/13] feature(view):[TRACEFOSS-1549] added alert detailed view --- .../modules/page/alerts/presentation/alerts.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts b/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts index a687e4a04a..2d05a2ee0f 100644 --- a/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts +++ b/frontend/src/app/modules/page/alerts/presentation/alerts.component.spec.ts @@ -43,7 +43,7 @@ describe('AlertsComponent', () => { spy.and.returnValue(new Promise(null)); fireEvent.click(await waitFor(() => screen.getByTestId('table-menu-button--actions.viewDetails'))); - const tabInformation: NotificationTabInformation = { tabIndex: null, pageNumber: undefined} + const tabInformation: NotificationTabInformation = { tabIndex: null, pageNumber: 0} expect(spy).toHaveBeenCalledWith(['/alerts/id-84'], { queryParams: tabInformation } ); }); From e0cfd96c990b084333f7177ab9e1d5c063001f4e Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Mon, 3 Jul 2023 13:25:43 +0200 Subject: [PATCH 11/13] feature(view):[TRACEFOSS-1549] added test --- .../alerts/core/alert-detail.facade.spec.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 frontend/src/app/modules/page/alerts/core/alert-detail.facade.spec.ts diff --git a/frontend/src/app/modules/page/alerts/core/alert-detail.facade.spec.ts b/frontend/src/app/modules/page/alerts/core/alert-detail.facade.spec.ts new file mode 100644 index 0000000000..a313125c7c --- /dev/null +++ b/frontend/src/app/modules/page/alerts/core/alert-detail.facade.spec.ts @@ -0,0 +1,58 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +import { TestBed } from '@angular/core/testing'; +import { AlertDetailFacade } from '@page/alerts/core/alert-detail.facade'; +import { AlertDetailState } from '@page/alerts/core/alert-detail.state'; +import { PartsService } from '@shared/service/parts.service'; +import { TitleCasePipe } from '@angular/common'; + +describe('AlertDetailFacade', () => { + let componentFacade: AlertDetailFacade; + let alertDetailStateMock: jasmine.SpyObj; + let partsService: jasmine.SpyObj; + let titleCasePipe: jasmine.SpyObj; + + beforeEach(() => { + alertDetailStateMock = jasmine.createSpyObj('AlertDetailState', [ + 'getIdsFromPartList', + 'setAlertPartsInformation', + 'getAlertPartsInformation', + 'setSupplierPartsInformation', + 'getSupplierPartsInformation' + ]); + partsService = jasmine.createSpyObj('PartsService', [ 'getPartDetailOfIds' ]); + titleCasePipe = jasmine.createSpyObj('TitleCasePipe', [ 'transform' ]); + + TestBed.configureTestingModule({ + providers: [ + AlertDetailFacade, + { provide: AlertDetailState, useValue: alertDetailStateMock }, + { provide: PartsService, useValue: partsService }, + { provide: TitleCasePipe, useValue: titleCasePipe } + ] + }); + + componentFacade = TestBed.inject(AlertDetailFacade); + }); + + it('should create the component facade', () => { + expect(componentFacade).toBeTruthy(); + }); +}); From 2234d609ce496c8b5bba7e5f501a840dabfb9d88 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Mon, 3 Jul 2023 16:27:10 +0200 Subject: [PATCH 12/13] feature(view):[TRACEFOSS-1549] added test --- .../src/app/mocks/services/alerts-mock/alerts.test.model.ts | 2 +- .../modules/page/alerts/detail/alert-detail.component.spec.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/mocks/services/alerts-mock/alerts.test.model.ts b/frontend/src/app/mocks/services/alerts-mock/alerts.test.model.ts index 7d22d3d339..a46465edb7 100644 --- a/frontend/src/app/mocks/services/alerts-mock/alerts.test.model.ts +++ b/frontend/src/app/mocks/services/alerts-mock/alerts.test.model.ts @@ -56,7 +56,7 @@ export const buildMockAlerts = ( }; }); -const MockEmptyAlert: NotificationResponse = { +export const MockEmptyAlert: NotificationResponse = { id: `${AlertIdPrefix}000`, description: `Alert No 000`, status: NotificationStatus.CREATED, diff --git a/frontend/src/app/modules/page/alerts/detail/alert-detail.component.spec.ts b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.spec.ts index baf41ba5b5..b2c2957289 100644 --- a/frontend/src/app/modules/page/alerts/detail/alert-detail.component.spec.ts +++ b/frontend/src/app/modules/page/alerts/detail/alert-detail.component.spec.ts @@ -73,4 +73,5 @@ describe('AlertDetailComponent', () => { expect(spy).toHaveBeenCalledWith(MOCK_part_1.semanticModelId); }); + }); From d8cce3e1d468857a073960292138a70fa58ad643 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Mon, 3 Jul 2023 16:28:08 +0200 Subject: [PATCH 13/13] feature(view):[TRACEFOSS-1549] added test --- .../alerts/core/alert-detail.facade.spec.ts | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 frontend/src/app/modules/page/alerts/core/alert-detail.facade.spec.ts diff --git a/frontend/src/app/modules/page/alerts/core/alert-detail.facade.spec.ts b/frontend/src/app/modules/page/alerts/core/alert-detail.facade.spec.ts new file mode 100644 index 0000000000..908d1b73f2 --- /dev/null +++ b/frontend/src/app/modules/page/alerts/core/alert-detail.facade.spec.ts @@ -0,0 +1,87 @@ +/******************************************************************************** + * 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 + ********************************************************************************/ + +import { TitleCasePipe } from '@angular/common'; +import { TestBed } from '@angular/core/testing'; +import { CalendarDateModel } from '@core/model/calendar-date.model'; +import { AlertDetailFacade } from '@page/alerts/core/alert-detail.facade'; +import { AlertDetailState } from '@page/alerts/core/alert-detail.state'; +import { Notification } from '@shared/model/notification.model'; +import { Severity } from '@shared/model/severity.model'; +import { PartsService } from '@shared/service/parts.service'; + +describe('AlertDetailFacade', () => { + let alertDetailFacade: AlertDetailFacade; + let alertDetailState: AlertDetailState; + let partsService: jasmine.SpyObj; + let titleCasePipe: jasmine.SpyObj; + + let testNotification: Notification = { + id: 'id-1', + description: 'Alert No 1', + createdBy: { name: 'OEM xxxxxxxxxxxxxxx A', bpn: 'BPN10000000OEM0A' }, + sendTo: { name: 'OEM xxxxxxxxxxxxxxx B', bpn: 'BPN20000000OEM0B' }, + reason: { close: '', accept: '', decline: '' }, + isFromSender: true, + assetIds: [], + status: null, + severity: Severity.MINOR, + createdDate: new CalendarDateModel('2022-05-01T10:34:12.000Z'), + } + + beforeEach(() => { + /* + alertDetailState = jasmine.createSpyObj('AlertDetailState', [ + 'getIdsFromPartList', + 'setAlertPartsInformation', + 'getAlertPartsInformation', + 'setSupplierPartsInformation', + 'getSupplierPartsInformation' + ]); + */ + alertDetailState = new AlertDetailState(); + partsService = jasmine.createSpyObj('PartsService', [ 'getPartDetailOfIds' ]); + titleCasePipe = jasmine.createSpyObj('TitleCasePipe', [ 'transform' ]); + + TestBed.configureTestingModule({ + providers: [ + AlertDetailFacade, + { provide: AlertDetailState, useValue: alertDetailState }, + { provide: PartsService, useValue: partsService }, + { provide: TitleCasePipe, useValue: titleCasePipe } + ] + }); + + alertDetailFacade = TestBed.inject(AlertDetailFacade); + }); + + it('should create the component facade', () => { + expect(alertDetailFacade).toBeTruthy(); + }); + + it('should set empty data if no asset ids are provided', () => { + + let callSpy = spyOn(alertDetailFacade, 'setAlertPartsInformation'); + let setSpy = spyOnProperty(alertDetailState, 'alertPartsInformation', 'set').and.callThrough(); + + alertDetailFacade.setAlertPartsInformation(testNotification); + expect(callSpy).toHaveBeenCalledWith(testNotification); + + }) +});