From c1bb4d8b67741a1805e7688c5242404dac2a1d4a Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Mon, 1 Jul 2024 17:11:21 +0200 Subject: [PATCH 1/6] bug(retry): 1009 implement retry request logic --- .../src/app/modules/core/api/api.service.ts | 12 ++++++ .../core/user/table-settings.service.spec.ts | 8 +++- .../detail/notification-detail.component.html | 2 +- .../detail/notification-detail.component.ts | 14 +++++++ .../presentation/notifications.component.ts | 15 ++++++++ .../notification-reason.component.spec.ts | 7 ++-- .../notification-reason.component.ts | 38 +++++++------------ .../toast-message/toast-message.model.ts | 6 +++ .../shared/components/toasts/toast.service.ts | 11 +++++- .../shared/components/toasts/toast.spec.ts | 18 +++++---- .../notification-modal-content.component.html | 2 +- .../shared/service/notification.service.ts | 4 ++ frontend/src/assets/locales/de/common.json | 4 +- frontend/src/assets/locales/en/common.json | 4 +- 14 files changed, 103 insertions(+), 42 deletions(-) diff --git a/frontend/src/app/modules/core/api/api.service.ts b/frontend/src/app/modules/core/api/api.service.ts index 22b2196943..36c35bf9e4 100644 --- a/frontend/src/app/modules/core/api/api.service.ts +++ b/frontend/src/app/modules/core/api/api.service.ts @@ -21,6 +21,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { RetryRequestContext } from '@shared/components/toasts/toast-message/toast-message.model'; import { Observable } from 'rxjs'; import { AuthService } from '../auth/auth.service'; @@ -28,6 +29,7 @@ import { AuthService } from '../auth/auth.service'; providedIn: 'root', }) export class ApiService { + lastRequest: RetryRequestContext | null = null; constructor(private readonly httpClient: HttpClient, private readonly authService: AuthService) { } @@ -111,6 +113,16 @@ export class ApiService { }); } + /** + * set the public class property 'lastRequest' from where you made the request + * before retrying + */ + public retryLastRequest(): Observable | null { + if (this.lastRequest) { + return this.httpClient.request(this.lastRequest.method, this.lastRequest.url, this.lastRequest.options); + } + } + private buildHeaders(): HttpHeaders { return new HttpHeaders({ Access: 'application/json', diff --git a/frontend/src/app/modules/core/user/table-settings.service.spec.ts b/frontend/src/app/modules/core/user/table-settings.service.spec.ts index bafd29d9a1..e7021fe92d 100644 --- a/frontend/src/app/modules/core/user/table-settings.service.spec.ts +++ b/frontend/src/app/modules/core/user/table-settings.service.spec.ts @@ -17,18 +17,22 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; +import { MockedKeycloakService } from '@core/auth/mocked-keycloak.service'; import { TableSettingsService } from '@core/user/table-settings.service'; import { TableType } from '@shared/components/multi-select-autocomplete/table-type.model'; import { TableViewConfig } from '@shared/components/parts-table/table-view-config.model'; +import { ToastService } from '@shared/components/toasts/toast.service'; +import { KeycloakService } from 'keycloak-angular'; describe('TableSettingsService', () => { let service: TableSettingsService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [], - providers: [ TableSettingsService ], + imports: [ HttpClientTestingModule ], + providers: [ TableSettingsService, ToastService, { provide: KeycloakService, useValue: MockedKeycloakService } ], }); service = TestBed.inject(TableSettingsService); }); diff --git a/frontend/src/app/modules/page/notifications/detail/notification-detail.component.html b/frontend/src/app/modules/page/notifications/detail/notification-detail.component.html index 7dbf617f20..60680a8679 100644 --- a/frontend/src/app/modules/page/notifications/detail/notification-detail.component.html +++ b/frontend/src/app/modules/page/notifications/detail/notification-detail.component.html @@ -290,7 +290,7 @@ - + diff --git a/frontend/src/app/modules/page/notifications/detail/notification-detail.component.ts b/frontend/src/app/modules/page/notifications/detail/notification-detail.component.ts index 53548623e9..521e3629d0 100644 --- a/frontend/src/app/modules/page/notifications/detail/notification-detail.component.ts +++ b/frontend/src/app/modules/page/notifications/detail/notification-detail.component.ts @@ -67,6 +67,7 @@ export class NotificationDetailComponent implements AfterViewInit, OnDestroy { public selectedNotification: Notification; private paramSubscription: Subscription; + private toastActionSubscription: Subscription; constructor( public readonly helperService: NotificationHelperService, @@ -81,6 +82,18 @@ export class NotificationDetailComponent implements AfterViewInit, OnDestroy { this.notificationPartsInformation$ = this.notificationDetailFacade.notificationPartsInformation$; this.supplierPartsDetailInformation$ = this.notificationDetailFacade.supplierPartsInformation$; + this.toastService.retryAction.subscribe({ + next: result => { + const context = this.selectedNotification?.type === NotificationType.ALERT ? 'Alert' : 'Investigation'; + if (result?.success) { + this.toastService.success(`common${ context }.modal.successfullyApproved`); + } else if (result?.error) { + this.toastService.error(`common${ context }.modal.failedApprove`, 15000, true); + } + this.ngAfterViewInit(); + }, + }); + this.selected$ = this.notificationDetailFacade.selected$; this.paramSubscription = this.route.queryParams.subscribe(params => { @@ -111,6 +124,7 @@ export class NotificationDetailComponent implements AfterViewInit, OnDestroy { this.subscription?.unsubscribe(); this.notificationDetailFacade.unsubscribeSubscriptions(); this.paramSubscription?.unsubscribe(); + this.toastActionSubscription?.unsubscribe(); } public navigateToEditView() { diff --git a/frontend/src/app/modules/page/notifications/presentation/notifications.component.ts b/frontend/src/app/modules/page/notifications/presentation/notifications.component.ts index b6516c3c8f..1e29db5696 100644 --- a/frontend/src/app/modules/page/notifications/presentation/notifications.component.ts +++ b/frontend/src/app/modules/page/notifications/presentation/notifications.component.ts @@ -28,6 +28,7 @@ import { NotificationChannel } from '@shared/components/multi-select-autocomplet import { NotificationCommonModalComponent } from '@shared/components/notification-common-modal/notification-common-modal.component'; import { TableSortingUtil } from '@shared/components/table/table-sorting.util'; import { MenuActionConfig, TableEventConfig, TableHeaderSort } from '@shared/components/table/table.model'; +import { ToastService } from '@shared/components/toasts/toast.service'; import { createDeeplinkNotificationFilter } from '@shared/helper/notification-helper'; import { setMultiSorting } from '@shared/helper/table-helper'; import { NotificationTabInformation } from '@shared/model/notification-tab-information'; @@ -58,6 +59,7 @@ export class NotificationsComponent { private ctrlKeyState: boolean = false; private paramSubscription: Subscription; + private toastActionSubscription: Subscription; receivedFilter: NotificationFilter; requestedFilter: NotificationFilter; @@ -71,6 +73,7 @@ export class NotificationsComponent { private readonly router: Router, private readonly route: ActivatedRoute, private readonly cd: ChangeDetectorRef, + private readonly toastService: ToastService, ) { this.notificationsReceived$ = this.notificationsFacade.notificationsReceived$; this.notificationsQueuedAndRequested$ = this.notificationsFacade.notificationsQueuedAndRequested$; @@ -81,6 +84,17 @@ export class NotificationsComponent { window.addEventListener('keyup', (event) => { this.ctrlKeyState = setMultiSorting(event); }); + + this.toastActionSubscription = this.toastService.retryAction.subscribe({ + next: result => { + if (result?.success) { + this.toastService.success('requestNotification.successfullyApproved'); + } else if (result?.error) { + this.toastService.error('requestNotification.failedApprove', 15000, true); + } + this.handleConfirmActionCompletedEvent(); + }, + }); } public ngOnInit(): void { @@ -106,6 +120,7 @@ export class NotificationsComponent { public ngOnDestroy(): void { this.notificationsFacade.stopNotifications(); this.paramSubscription?.unsubscribe(); + this.toastActionSubscription?.unsubscribe(); } public onReceivedTableConfigChange(pagination: TableEventConfig) { diff --git a/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.spec.ts b/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.spec.ts index c2990cae6d..9ec1ce13ee 100644 --- a/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.spec.ts +++ b/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.spec.ts @@ -72,7 +72,8 @@ describe('NotificationReasonComponent', () => { }, ] }; - component.notification = notification; + component.notificationMessages = notification.messages; + component.ngOnInit(); expect(component.textMessages.length).toBe(2); expect(component.textMessages[0].message).toEqual('Hello'); expect(component.textMessages[1].direction).toEqual('left'); @@ -102,7 +103,7 @@ describe('NotificationReasonComponent', () => { isFromSender: true, messages: [] }; - component.notification = notification; + component.notificationMessages = notification.messages; expect(component.textMessages.length).toBe(0); }); @@ -122,7 +123,7 @@ describe('NotificationReasonComponent', () => { isFromSender: true, messages: [] }; - component.notification = notification; + component.notificationMessages = notification.messages; // Since date is invalid, sorting and processing might behave unexpectedly, expecting no error thrown and no messages processed expect(component.textMessages.length).toBe(0); }); diff --git a/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts b/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts index 9d87c707d9..2fa5fcb57f 100644 --- a/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts +++ b/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts @@ -21,7 +21,7 @@ import { Component, Input } from '@angular/core'; import { environment } from '@env'; -import { Notification, NotificationStatus } from '@shared/model/notification.model'; +import { NotificationMessage, NotificationStatus } from '@shared/model/notification.model'; type TextMessageDirection = 'left' | 'right'; @@ -42,31 +42,21 @@ interface TextMessage { }) export class NotificationReasonComponent { public textMessages: TextMessage[] = []; + @Input() notificationMessages: NotificationMessage[]; - @Input() set notification({ - description, - status, - isFromSender, - createdDate, - createdBy, - createdByName, - sendTo, - sendToName, - messages, - }: Notification) { + ngOnInit() { - const sortedMessagesAfterDates = messages.sort((a, b) => new Date(a.messageDate).valueOf() - new Date(b.messageDate).valueOf()); - - sortedMessagesAfterDates.forEach(message => { - this.textMessages.push({ - message: message.message, - direction: environment.bpn === message.sentBy ? 'right' : 'left', - user: message.sentByName, - bpn: message.sentBy, - status: message.status, - date: message.messageDate, - errorMessage: message.errorMessage, - }); + const sortedMessagesAfterDates = this.notificationMessages?.sort((a, b) => new Date(a.messageDate).valueOf() - new Date(b.messageDate).valueOf()); + sortedMessagesAfterDates?.forEach(message => { + this.textMessages.push({ + message: message.message, + direction: environment.bpn === message.sentBy ? 'right' : 'left', + user: message.sentByName, + bpn: message.sentBy, + status: message.status, + date: message.messageDate, + errorMessage: message.errorMessage, + }); }); } diff --git a/frontend/src/app/modules/shared/components/toasts/toast-message/toast-message.model.ts b/frontend/src/app/modules/shared/components/toasts/toast-message/toast-message.model.ts index d1c7f7431b..65bcec9532 100644 --- a/frontend/src/app/modules/shared/components/toasts/toast-message/toast-message.model.ts +++ b/frontend/src/app/modules/shared/components/toasts/toast-message/toast-message.model.ts @@ -42,6 +42,12 @@ export interface CallAction { linkQueryParams?: Record; } +export interface RetryRequestContext { + method: 'GET' | 'POST' | 'PUT' | 'DELETE'; + url: string; + options?: any; +} + export class ToastMessage { public isSliderON = true; diff --git a/frontend/src/app/modules/shared/components/toasts/toast.service.ts b/frontend/src/app/modules/shared/components/toasts/toast.service.ts index 456152ffae..dd70380b31 100644 --- a/frontend/src/app/modules/shared/components/toasts/toast.service.ts +++ b/frontend/src/app/modules/shared/components/toasts/toast.service.ts @@ -20,6 +20,7 @@ ********************************************************************************/ import { EventEmitter, Injectable } from '@angular/core'; +import { ApiService } from '@core/api/api.service'; import { I18nMessage } from '@shared/model/i18n-message'; import { Observable, Subject } from 'rxjs'; import { CallAction, ToastMessage, ToastStatus } from './toast-message/toast-message.model'; @@ -32,6 +33,9 @@ export class ToastService { private idx = 0; retryAction = new EventEmitter(); + constructor(private readonly apiService: ApiService) { + } + public getCurrentToast$(): Observable { return this.toastStore.asObservable(); } @@ -56,7 +60,10 @@ export class ToastService { this.toastStore.next(new ToastMessage(this.idx++, message, ToastStatus.Warning, timeout)); } - public emitClick(event?: any) { - this.retryAction.emit(); + public emitClick(): void { + this.apiService.retryLastRequest()?.subscribe({ + next: (next) => this.retryAction.emit({ success: next }), + error: (err) => this.retryAction.emit({ error: err }), + }); }; } diff --git a/frontend/src/app/modules/shared/components/toasts/toast.spec.ts b/frontend/src/app/modules/shared/components/toasts/toast.spec.ts index 0ef692871f..89ff281a4c 100644 --- a/frontend/src/app/modules/shared/components/toasts/toast.spec.ts +++ b/frontend/src/app/modules/shared/components/toasts/toast.spec.ts @@ -19,15 +19,19 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -import {TestBed} from '@angular/core/testing'; -import {SharedModule} from '@shared/shared.module'; -import {screen} from '@testing-library/angular'; -import {renderComponent} from '@tests/test-render.utils'; -import {ToastService} from './toast.service'; +import { TestBed } from '@angular/core/testing'; +import { ApiService } from '@core/api/api.service'; +import { SharedModule } from '@shared/shared.module'; +import { screen } from '@testing-library/angular'; +import { renderComponent } from '@tests/test-render.utils'; +import { ToastService } from './toast.service'; describe('toasts', () => { const renderToastLayout = async () => { - await renderComponent(``, { imports: [ SharedModule ] }); + await renderComponent(``, { + imports: [ SharedModule ], + providers: [ ApiService ], + }); return TestBed.inject(ToastService); }; @@ -68,7 +72,7 @@ describe('toasts', () => { it('should emit click action on toast', async () => { const toastService = await renderToastLayout(); - const toastActionSpy = spyOn(toastService.retryAction, 'emit') + const toastActionSpy = spyOn(toastService['apiService'], 'retryLastRequest'); toastService.emitClick(); expect(toastActionSpy).toHaveBeenCalled(); }); diff --git a/frontend/src/app/modules/shared/modules/notification/modal/content/notification-modal-content.component.html b/frontend/src/app/modules/shared/modules/notification/modal/content/notification-modal-content.component.html index 830742e95f..62b05594c9 100644 --- a/frontend/src/app/modules/shared/modules/notification/modal/content/notification-modal-content.component.html +++ b/frontend/src/app/modules/shared/modules/notification/modal/content/notification-modal-content.component.html @@ -27,7 +27,7 @@ - + diff --git a/frontend/src/app/modules/shared/service/notification.service.ts b/frontend/src/app/modules/shared/service/notification.service.ts index 692fccd9b7..323a6f8f10 100644 --- a/frontend/src/app/modules/shared/service/notification.service.ts +++ b/frontend/src/app/modules/shared/service/notification.service.ts @@ -97,6 +97,10 @@ export class NotificationService { public approveNotification(id: string): Observable { const requestUrl = this.notificationUrl(); + this.apiService.lastRequest = { + url: `${ requestUrl }/${ id }/approve`, + method: 'POST', + }; return this.apiService.post(`${ requestUrl }/${ id }/approve`); } diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index 1f0f5f2726..12ef7acc56 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -290,7 +290,9 @@ "saveError": "Bei der Erstellung des Qualitätsthemas ist ein Fehler aufgetreten.", "saveEditSuccess": "Qualitätsthema wurde erfolgreich aktualisiert.", "saveEditError": "Bei der Aktualisierung des Qualitätsthemas ist ein Fehler aufgetreten.", - "save": "Qualitätsthema speichern" + "save" : "Qualitätsthema speichern", + "successfullyApproved" : "Qualitätsthema wurde erfolgreich genehmigt.", + "failedApprove" : "Qualitätsthema konnte nicht genehmigt werden, bitte versuchen Sie es erneut." }, "editNotification": { "saveButton": "Speichern", diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index 9d48fdbd4f..19c80cfe76 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -289,7 +289,9 @@ "saveError": "An error occurred while creating the quality topic.", "saveEditSuccess": "Quality topic was updated successfully.", "saveEditError": "An error occurred while updating the quality topic.", - "save": "Save quality topic" + "save" : "Save quality topic", + "successfullyApproved" : "Notification was approved successfully.", + "failedApprove" : "Notification failed to approve, please try again." }, "editNotification": { "saveButton": "Save", From 659457234cc31b812b7b9bb315689c76b618aecd Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Mon, 1 Jul 2024 17:14:52 +0200 Subject: [PATCH 2/6] bug(retry): 1009 implement retry request logic --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12b8ec201..00d96e3142 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ _**For better traceability add the corresponding GitHub issue number in each cha - #985 Added function to filter notifications for contractAgreementIds - #786 Added authorization as admin for submodel api & registry api - #884 Upgraded tractionBatteryCode from 1.0.0 to 2.0.0 +- #1009 reimplemented retry request logic for notification approval ### Added - #832 added policymanagement list view, creator and editor From bfae645f84ccf34865c8b8d4886d8369d377f77a Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Tue, 2 Jul 2024 10:43:09 +0200 Subject: [PATCH 3/6] bug(retry): 1009 add tests --- .../notification-detail.component.spec.ts | 21 +++++++++++++++++++ .../notifications.component.spec.ts | 17 +++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/frontend/src/app/modules/page/notifications/detail/notification-detail.component.spec.ts b/frontend/src/app/modules/page/notifications/detail/notification-detail.component.spec.ts index 18f3c54819..390174a3c1 100644 --- a/frontend/src/app/modules/page/notifications/detail/notification-detail.component.spec.ts +++ b/frontend/src/app/modules/page/notifications/detail/notification-detail.component.spec.ts @@ -20,10 +20,12 @@ import { ActivatedRoute } from '@angular/router'; import { NotificationDetailComponent } from '@page/notifications/detail/notification-detail.component'; import { NotificationsModule } from '@page/notifications/notifications.module'; +import { NotificationAssembler } from '@shared/assembler/notification.assembler'; import { NotificationService } from '@shared/service/notification.service'; import { screen, waitFor } from '@testing-library/angular'; import { renderComponent } from '@tests/test-render.utils'; import { of } from 'rxjs'; +import { MockEmptyAlert } from '../../../../mocks/services/alerts-mock/alerts.test.model'; describe('NotificationDetailComponent', () => { @@ -58,4 +60,23 @@ describe('NotificationDetailComponent', () => { await waitFor(() => expect(screen.getByText('actions.goBack')).toBeInTheDocument()); }); + it('should correctly behave on toast retry action', async () => { + const { fixture } = await renderNotificationDetail('id-1'); + const { componentInstance } = fixture; + const ngSpy = spyOn(componentInstance, 'ngAfterViewInit').and.returnValue(null); + const toastSuccessSpy = spyOn(componentInstance['toastService'], 'success'); + const toastErrorSpy = spyOn(componentInstance['toastService'], 'error'); + + componentInstance.selectedNotification = NotificationAssembler.assembleNotification(MockEmptyAlert); + componentInstance['toastService'].retryAction.emit({ success: true }); + expect(toastSuccessSpy).toHaveBeenCalled(); + + componentInstance['toastService'].retryAction.emit({ error: true }); + expect(toastErrorSpy).toHaveBeenCalled(); + + expect(ngSpy).toHaveBeenCalled(); + + }); + + }); diff --git a/frontend/src/app/modules/page/notifications/presentation/notifications.component.spec.ts b/frontend/src/app/modules/page/notifications/presentation/notifications.component.spec.ts index 3964e2636f..e77282b1d9 100644 --- a/frontend/src/app/modules/page/notifications/presentation/notifications.component.spec.ts +++ b/frontend/src/app/modules/page/notifications/presentation/notifications.component.spec.ts @@ -167,4 +167,21 @@ describe('NotificationsComponent', () => { expect(notificationsComponent['notificationReceivedSortList']).toEqual([]); }); + it('should correctly behave on toast retry action', async () => { + const { fixture } = await renderNotifications(); + const { componentInstance } = fixture; + const handleConfirmSpy = spyOn(componentInstance, 'handleConfirmActionCompletedEvent'); + const toastSuccessSpy = spyOn(componentInstance['toastService'], 'success'); + const toastErrorSpy = spyOn(componentInstance['toastService'], 'error'); + + componentInstance['toastService'].retryAction.emit({ success: true }); + expect(toastSuccessSpy).toHaveBeenCalled(); + + componentInstance['toastService'].retryAction.emit({ error: true }); + expect(toastErrorSpy).toHaveBeenCalled(); + + expect(handleConfirmSpy).toHaveBeenCalled(); + + }); + }); From 1da3e955d79d684d32ee4cb7d70c6ad965c651f5 Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Tue, 2 Jul 2024 10:55:16 +0200 Subject: [PATCH 4/6] bug(retry): 1009 fix issue --- .../notification-reason/notification-reason.component.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts b/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts index 2fa5fcb57f..7813c7d4a4 100644 --- a/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts +++ b/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts @@ -45,8 +45,13 @@ export class NotificationReasonComponent { @Input() notificationMessages: NotificationMessage[]; ngOnInit() { + if (!this.notificationMessages) { + return; + } + let sortedMessagesAfterDates; + + sortedMessagesAfterDates = this.notificationMessages?.sort((a, b) => new Date(a.messageDate).valueOf() - new Date(b.messageDate).valueOf()); - const sortedMessagesAfterDates = this.notificationMessages?.sort((a, b) => new Date(a.messageDate).valueOf() - new Date(b.messageDate).valueOf()); sortedMessagesAfterDates?.forEach(message => { this.textMessages.push({ message: message.message, From e484f95e60bf7cb9bdced0b094d5555a1db1295c Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Tue, 2 Jul 2024 11:02:41 +0200 Subject: [PATCH 5/6] bug(retry): 1009 fix issue --- .../notification-reason/notification-reason.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts b/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts index 7813c7d4a4..031e01ce2b 100644 --- a/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts +++ b/frontend/src/app/modules/shared/components/notification-reason/notification-reason.component.ts @@ -48,9 +48,9 @@ export class NotificationReasonComponent { if (!this.notificationMessages) { return; } - let sortedMessagesAfterDates; + let sortedMessagesAfterDates = [ ...this.notificationMessages ]; - sortedMessagesAfterDates = this.notificationMessages?.sort((a, b) => new Date(a.messageDate).valueOf() - new Date(b.messageDate).valueOf()); + sortedMessagesAfterDates.sort((a, b) => new Date(a.messageDate).valueOf() - new Date(b.messageDate).valueOf()); sortedMessagesAfterDates?.forEach(message => { this.textMessages.push({ From e94d3a8853a355d3c4688ae1bd256cdeef3a9b2d Mon Sep 17 00:00:00 2001 From: Martin Maul Date: Wed, 3 Jul 2024 12:58:57 +0200 Subject: [PATCH 6/6] chore(retry): 1009 try again for all notification actions --- .../src/app/modules/core/api/api.service.ts | 7 ++--- .../detail/notification-detail.component.ts | 8 +++--- .../presentation/notifications.component.ts | 5 ++-- .../toast-message/toast-message.model.ts | 6 ---- .../shared/components/toasts/toast.service.ts | 4 +-- .../shared/service/notification.service.ts | 28 +++++++++++++++---- frontend/src/assets/locales/de/common.json | 2 +- frontend/src/assets/locales/en/common.json | 12 +++++++- 8 files changed, 46 insertions(+), 26 deletions(-) diff --git a/frontend/src/app/modules/core/api/api.service.ts b/frontend/src/app/modules/core/api/api.service.ts index 36c35bf9e4..51551800a7 100644 --- a/frontend/src/app/modules/core/api/api.service.ts +++ b/frontend/src/app/modules/core/api/api.service.ts @@ -21,7 +21,6 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { RetryRequestContext } from '@shared/components/toasts/toast-message/toast-message.model'; import { Observable } from 'rxjs'; import { AuthService } from '../auth/auth.service'; @@ -29,7 +28,7 @@ import { AuthService } from '../auth/auth.service'; providedIn: 'root', }) export class ApiService { - lastRequest: RetryRequestContext | null = null; + lastRequest: { execute?: () => Observable, context?: any }; constructor(private readonly httpClient: HttpClient, private readonly authService: AuthService) { } @@ -118,8 +117,8 @@ export class ApiService { * before retrying */ public retryLastRequest(): Observable | null { - if (this.lastRequest) { - return this.httpClient.request(this.lastRequest.method, this.lastRequest.url, this.lastRequest.options); + if (this.lastRequest.execute) { + return this.lastRequest.execute(); } } diff --git a/frontend/src/app/modules/page/notifications/detail/notification-detail.component.ts b/frontend/src/app/modules/page/notifications/detail/notification-detail.component.ts index 521e3629d0..da17a6aba8 100644 --- a/frontend/src/app/modules/page/notifications/detail/notification-detail.component.ts +++ b/frontend/src/app/modules/page/notifications/detail/notification-detail.component.ts @@ -82,13 +82,13 @@ export class NotificationDetailComponent implements AfterViewInit, OnDestroy { this.notificationPartsInformation$ = this.notificationDetailFacade.notificationPartsInformation$; this.supplierPartsDetailInformation$ = this.notificationDetailFacade.supplierPartsInformation$; - this.toastService.retryAction.subscribe({ + this.toastActionSubscription = this.toastService.retryAction.subscribe({ next: result => { - const context = this.selectedNotification?.type === NotificationType.ALERT ? 'Alert' : 'Investigation'; + const formattedStatus = result?.context?.charAt(0)?.toUpperCase() + result?.context?.slice(1)?.toLowerCase(); if (result?.success) { - this.toastService.success(`common${ context }.modal.successfullyApproved`); + this.toastService.success(`requestNotification.successfully${ formattedStatus }`); } else if (result?.error) { - this.toastService.error(`common${ context }.modal.failedApprove`, 15000, true); + this.toastService.error(`requestNotification.failed${ formattedStatus }`, 15000, true); } this.ngAfterViewInit(); }, diff --git a/frontend/src/app/modules/page/notifications/presentation/notifications.component.ts b/frontend/src/app/modules/page/notifications/presentation/notifications.component.ts index 1e29db5696..252a610543 100644 --- a/frontend/src/app/modules/page/notifications/presentation/notifications.component.ts +++ b/frontend/src/app/modules/page/notifications/presentation/notifications.component.ts @@ -87,10 +87,11 @@ export class NotificationsComponent { this.toastActionSubscription = this.toastService.retryAction.subscribe({ next: result => { + const formatted = result?.context?.charAt(0)?.toUpperCase() + result?.context?.slice(1)?.toLowerCase(); if (result?.success) { - this.toastService.success('requestNotification.successfullyApproved'); + this.toastService.success(`requestNotification.successfully${ formatted }`); } else if (result?.error) { - this.toastService.error('requestNotification.failedApprove', 15000, true); + this.toastService.error(`requestNotification.failed${ formatted }`, 15000, true); } this.handleConfirmActionCompletedEvent(); }, diff --git a/frontend/src/app/modules/shared/components/toasts/toast-message/toast-message.model.ts b/frontend/src/app/modules/shared/components/toasts/toast-message/toast-message.model.ts index 65bcec9532..d1c7f7431b 100644 --- a/frontend/src/app/modules/shared/components/toasts/toast-message/toast-message.model.ts +++ b/frontend/src/app/modules/shared/components/toasts/toast-message/toast-message.model.ts @@ -42,12 +42,6 @@ export interface CallAction { linkQueryParams?: Record; } -export interface RetryRequestContext { - method: 'GET' | 'POST' | 'PUT' | 'DELETE'; - url: string; - options?: any; -} - export class ToastMessage { public isSliderON = true; diff --git a/frontend/src/app/modules/shared/components/toasts/toast.service.ts b/frontend/src/app/modules/shared/components/toasts/toast.service.ts index dd70380b31..aa5b153128 100644 --- a/frontend/src/app/modules/shared/components/toasts/toast.service.ts +++ b/frontend/src/app/modules/shared/components/toasts/toast.service.ts @@ -62,8 +62,8 @@ export class ToastService { public emitClick(): void { this.apiService.retryLastRequest()?.subscribe({ - next: (next) => this.retryAction.emit({ success: next }), - error: (err) => this.retryAction.emit({ error: err }), + next: (next) => this.retryAction.emit({ success: next, context: this.apiService.lastRequest?.context }), + error: (err) => this.retryAction.emit({ error: err, context: this.apiService.lastRequest?.context }), }); }; } diff --git a/frontend/src/app/modules/shared/service/notification.service.ts b/frontend/src/app/modules/shared/service/notification.service.ts index 323a6f8f10..b809b7930d 100644 --- a/frontend/src/app/modules/shared/service/notification.service.ts +++ b/frontend/src/app/modules/shared/service/notification.service.ts @@ -92,21 +92,32 @@ export class NotificationService { public closeNotification(id: string, reason: string): Observable { const requestUrl = this.notificationUrl(); const body = { reason }; - return this.apiService.post(`${ requestUrl }/${ id }/close`, body); + const request = () => this.apiService.post(`${ requestUrl }/${ id }/close`, body); + this.apiService.lastRequest = { + context: NotificationStatus.CLOSED, + execute: request, + }; + return request(); } public approveNotification(id: string): Observable { const requestUrl = this.notificationUrl(); + const request = () => this.apiService.post(`${ requestUrl }/${ id }/approve`); this.apiService.lastRequest = { - url: `${ requestUrl }/${ id }/approve`, - method: 'POST', + context: NotificationStatus.APPROVED, + execute: request, }; - return this.apiService.post(`${ requestUrl }/${ id }/approve`); + return request(); } public cancelNotification(id: string): Observable { const requestUrl = this.notificationUrl(); - return this.apiService.post(`${ requestUrl }/${ id }/cancel`); + const request = () => this.apiService.post(`${ requestUrl }/${ id }/cancel`); + this.apiService.lastRequest = { + context: NotificationStatus.CANCELED, + execute: request, + }; + return request(); } public updateNotification( @@ -116,7 +127,12 @@ export class NotificationService { ): Observable { const requestUrl = this.notificationUrl(); const body = { reason, status }; - return this.apiService.post(`${ requestUrl }/${ id }/update`, body); + const request = () => this.apiService.post(`${ requestUrl }/${ id }/update`, body); + this.apiService.lastRequest = { + context: status, + execute: request, + }; + return request(); } public editNotification(notificationId: string, title: string, receiverBpn: string, severity: string, targetDate: string, description: string, affectedPartIds: string[]): Observable { diff --git a/frontend/src/assets/locales/de/common.json b/frontend/src/assets/locales/de/common.json index 7eb41447a2..97777d7963 100644 --- a/frontend/src/assets/locales/de/common.json +++ b/frontend/src/assets/locales/de/common.json @@ -292,7 +292,7 @@ "saveEditError": "Bei der Aktualisierung des Qualitätsthemas ist ein Fehler aufgetreten.", "save" : "Qualitätsthema speichern", "successfullyApproved" : "Qualitätsthema wurde erfolgreich genehmigt.", - "failedApprove" : "Qualitätsthema konnte nicht genehmigt werden, bitte versuchen Sie es erneut." + "failedApprove" : "Qualitätsthema konnte nicht genehmigt werden, bitte versuchen Sie es erneut.", "noChanges" : "Erfordert eine Änderung am Qualitätsthema" }, "editNotification": { diff --git a/frontend/src/assets/locales/en/common.json b/frontend/src/assets/locales/en/common.json index 6b0cda6108..b697c13aa2 100644 --- a/frontend/src/assets/locales/en/common.json +++ b/frontend/src/assets/locales/en/common.json @@ -290,8 +290,18 @@ "saveEditSuccess": "Quality topic was updated successfully.", "saveEditError": "An error occurred while updating the quality topic.", "save" : "Save quality topic", + "successfullyAccepted" : "Notification was accepted successfully.", + "successfullyAcknowledged" : "Notification was acknowledged successfully.", "successfullyApproved" : "Notification was approved successfully.", - "failedApprove" : "Notification failed to approve, please try again." + "successfullyCanceled" : "Notification was canceled successfully.", + "successfullyClosed" : "Notification was closed successfully.", + "successfullyDeclined" : "Notification was declined successfully.", + "failedAccepted" : "Notification failed to accept, please try again.", + "failedAcknowledged" : "Notification failed to acknowledge, please try again.", + "failedApproved" : "Notification failed to approve, please try again.", + "failedCanceled" : "Notification failed to cancel, please try again.", + "failedClosed" : "Notification failed to close, please try again.", + "failedDeclined" : "Notification failed to decline, please try again.", "noChanges" : "Requires a change on the quality topic" }, "editNotification": {