From 4548b16bb5933a190b77cc9a5378c674b80d5705 Mon Sep 17 00:00:00 2001 From: Humberto Morera <31667212+hmoreras@users.noreply.github.com> Date: Mon, 11 Nov 2024 08:45:46 -0600 Subject: [PATCH] implementation ( Edit Content): #30212 Display Toast Message on Contentlet Save (#30615) ### Proposed Changes * Add a toast when a workflow is executed. ### Screenshots image --- .../dot-edit-content-form.component.spec.ts | 21 +++++++++++++- .../dot-edit-content-form.component.ts | 16 ++++++++-- ...dot-edit-content-sidebar.component.spec.ts | 2 ++ .../edit-content.layout.component.html | 6 +--- .../store/edit-content.store.spec.ts | 29 ++++++++++++++++--- .../edit-content/store/edit-content.store.ts | 21 ++++++++++++-- ...it-content-wysiwyg-field.component.spec.ts | 3 +- .../WEB-INF/messages/Language.properties | 1 + 8 files changed, 84 insertions(+), 15 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.spec.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.spec.ts index 5a5c28991ce6..e7c6cb202f23 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.spec.ts @@ -5,11 +5,13 @@ import { Spectator, SpyObject } from '@ngneat/spectator/jest'; +import { patchState } from '@ngrx/signals'; import { of } from 'rxjs'; import { Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; +import { MessageService } from 'primeng/api'; import { TabPanel, TabView } from 'primeng/tabview'; import { @@ -21,6 +23,7 @@ import { DotWorkflowsActionsService, DotWorkflowService } from '@dotcms/data-access'; +import { ComponentStatus } from '@dotcms/dotcms-models'; import { DotWorkflowActionsComponent } from '@dotcms/ui'; import { DotFormatDateServiceMock } from '@dotcms/utils-testing'; @@ -40,7 +43,7 @@ import { MockResizeObserver } from '../../utils/mocks'; describe('DotFormComponent', () => { let spectator: Spectator; let component: DotEditContentFormComponent; - let store: SpyObject>; + let store: InstanceType; let dotContentTypeService: SpyObject; let workflowActionsService: SpyObject; let workflowActionsFireService: SpyObject; @@ -62,6 +65,7 @@ describe('DotFormComponent', () => { mockProvider(DotMessageService), mockProvider(Router), mockProvider(DotWorkflowService), + mockProvider(MessageService), { provide: ActivatedRoute, useValue: { @@ -125,6 +129,21 @@ describe('DotFormComponent', () => { expect(component.form.get('modUserName')).toBeFalsy(); expect(component.form.get('publishDate')).toBeFalsy(); }); + + it('should disable the form when loading and enable it when not loading', () => { + spectator.detectChanges(); + + // // Initially, the form should be enabled + expect(component.form.enabled).toBe(true); + + patchState(store, { + state: ComponentStatus.SAVING + }); + + spectator.flushEffects(); + + expect(component.form.enabled).toBe(false); + }); }); describe('New Content', () => { diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.ts index 95ffb3db0895..a2bd529911ef 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/dot-edit-content-form.component.ts @@ -5,6 +5,7 @@ import { Component, computed, DestroyRef, + effect, inject, OnInit, output @@ -142,10 +143,21 @@ export class DotEditContentFormComponent implements OnInit { ngOnInit(): void { if (this.$store.tabs().length) { this.initializeForm(); - this.initializeFormListenter(); + this.initializeFormListener(); } } + constructor() { + effect(() => { + const isLoading = this.$store.isLoading(); + if (isLoading) { + this.form.disable(); + } else { + this.form.enable(); + } + }); + } + /** * Initializes a listener for form value changes. * When the form value changes, it calls the onFormChange method with the new value. @@ -154,7 +166,7 @@ export class DotEditContentFormComponent implements OnInit { * @private * @memberof DotEditContentFormComponent */ - private initializeFormListenter() { + private initializeFormListener() { this.form.valueChanges.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe((value) => { const processedValue = this.processFormValue(value); this.changeValue.emit(processedValue); diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.spec.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.spec.ts index 3efd076d3da1..93b3e44cd666 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-sidebar/dot-edit-content-sidebar.component.spec.ts @@ -10,6 +10,7 @@ import { of } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; +import { MessageService } from 'primeng/api'; import { TabView } from 'primeng/tabview'; import { @@ -49,6 +50,7 @@ describe('DotEditContentSidebarComponent', () => { mockProvider(DotMessageService), mockProvider(Router), mockProvider(DotWorkflowService), + mockProvider(MessageService), { provide: ActivatedRoute, useValue: { diff --git a/core-web/libs/edit-content/src/lib/feature/edit-content/edit-content.layout.component.html b/core-web/libs/edit-content/src/lib/feature/edit-content/edit-content.layout.component.html index 4abfcf04bb4e..6bf2c2894d86 100644 --- a/core-web/libs/edit-content/src/lib/feature/edit-content/edit-content.layout.component.html +++ b/core-web/libs/edit-content/src/lib/feature/edit-content/edit-content.layout.component.html @@ -1,5 +1,5 @@ -@if ($store.isLoaded()) { +@if ($store.isLoaded() || $store.isSaving()) { @let contentType = $store.contentType(); @let variable = contentType.variable; @let showSidebar = $store.showSidebar(); @@ -38,9 +38,5 @@ } } -@if ($store.hasError()) { - {{ 'edit.content.layout.no.content.to.show ' | dm }} -} - diff --git a/core-web/libs/edit-content/src/lib/feature/edit-content/store/edit-content.store.spec.ts b/core-web/libs/edit-content/src/lib/feature/edit-content/store/edit-content.store.spec.ts index 20f95201b190..4c797f4fb8cf 100644 --- a/core-web/libs/edit-content/src/lib/feature/edit-content/store/edit-content.store.spec.ts +++ b/core-web/libs/edit-content/src/lib/feature/edit-content/store/edit-content.store.spec.ts @@ -10,10 +10,13 @@ import { HttpErrorResponse } from '@angular/common/http'; import { fakeAsync, tick } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; +import { MessageService } from 'primeng/api'; + import { DotContentTypeService, DotFireActionOptions, DotHttpErrorManagerService, + DotMessageService, DotRenderMode, DotWorkflowActionsFireService, DotWorkflowsActionsService, @@ -25,13 +28,18 @@ import { DotCMSContentType, DotCMSWorkflowAction } from '@dotcms/dotcms-models'; -import { mockWorkflowsActions } from '@dotcms/utils-testing'; +import { MockDotMessageService, mockWorkflowsActions } from '@dotcms/utils-testing'; import { DotEditContentStore } from './edit-content.store'; import { DotEditContentService } from '../../../services/dot-edit-content.service'; import { CONTENT_TYPE_MOCK } from '../../../utils/mocks'; +const messageServiceMock = new MockDotMessageService({ + 'edit.content.success.workflow.message': 'Your changes have being applied.', + success: 'Success' +}); + describe('DotEditContentStore', () => { let spectator: SpectatorService>; let store: InstanceType; @@ -46,6 +54,7 @@ describe('DotEditContentStore', () => { let workflowActionsService: SpyObject; let workflowActionsFireService: SpyObject; + let messageService: SpyObject; const createService = createServiceFactory({ service: DotEditContentStore, @@ -55,7 +64,8 @@ describe('DotEditContentStore', () => { DotEditContentService, DotHttpErrorManagerService, DotWorkflowsActionsService, - DotWorkflowService + DotWorkflowService, + MessageService ], providers: [ { @@ -69,7 +79,11 @@ describe('DotEditContentStore', () => { mockProvider(Router, { navigate: jest.fn().mockReturnValue(Promise.resolve(true)) - }) + }), + { + provide: DotMessageService, + useValue: messageServiceMock + } ] }); @@ -84,6 +98,7 @@ describe('DotEditContentStore', () => { workflowActionsService = spectator.inject(DotWorkflowsActionsService); workflowActionsFireService = spectator.inject(DotWorkflowActionsFireService); dotEditContentService = spectator.inject(DotEditContentService); + messageService = spectator.inject(MessageService); router = spectator.inject(Router); }); @@ -212,6 +227,12 @@ describe('DotEditContentStore', () => { replaceUrl: true, queryParamsHandling: 'preserve' }); + + expect(messageService.add).toHaveBeenCalledWith({ + severity: 'success', + summary: 'Success', + detail: 'Your changes have being applied.' + }); })); it('should handle error when firing workflow action', fakeAsync(() => { @@ -225,7 +246,7 @@ describe('DotEditContentStore', () => { store.fireWorkflowAction(mockOptions); tick(); - expect(store.state()).toBe(ComponentStatus.ERROR); + expect(store.state()).toBe(ComponentStatus.LOADED); expect(store.error()).toBe('Error firing workflow action'); expect(dotHttpErrorManagerService.handle).toHaveBeenCalled(); })); diff --git a/core-web/libs/edit-content/src/lib/feature/edit-content/store/edit-content.store.ts b/core-web/libs/edit-content/src/lib/feature/edit-content/store/edit-content.store.ts index f5b778732df2..07333a12a52e 100644 --- a/core-web/libs/edit-content/src/lib/feature/edit-content/store/edit-content.store.ts +++ b/core-web/libs/edit-content/src/lib/feature/edit-content/store/edit-content.store.ts @@ -14,6 +14,8 @@ import { HttpErrorResponse } from '@angular/common/http'; import { computed, inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; +import { MessageService } from 'primeng/api'; + import { switchMap, tap } from 'rxjs/operators'; import { DotCMSContentlet } from '@dotcms/angular'; @@ -23,7 +25,8 @@ import { DotHttpErrorManagerService, DotRenderMode, DotWorkflowActionsFireService, - DotWorkflowsActionsService + DotWorkflowsActionsService, + DotMessageService } from '@dotcms/data-access'; import { ComponentStatus, @@ -97,6 +100,11 @@ export const DotEditContentStore = signalStore( */ isLoaded: computed(() => store.state() === ComponentStatus.LOADED), + /** + * Computed property that determines if the store's status is equal to ComponentStatus.SAVING. + */ + isSaving: computed(() => store.state() === ComponentStatus.SAVING), + /** * A computed property that checks if an error exists in the store. * @@ -132,6 +140,8 @@ export const DotEditContentStore = signalStore( dotContentTypeService = inject(DotContentTypeService), dotEditContentService = inject(DotEditContentService), dotHttpErrorManagerService = inject(DotHttpErrorManagerService), + messageService = inject(MessageService), + dotMessageService = inject(DotMessageService), router = inject(Router) ) => ({ @@ -262,10 +272,17 @@ export const DotEditContentStore = signalStore( state: ComponentStatus.LOADED, error: null }); + messageService.add({ + severity: 'success', + summary: dotMessageService.get('success'), + detail: dotMessageService.get( + 'edit.content.success.workflow.message' + ) + }); }, error: (error: HttpErrorResponse) => { patchState(store, { - state: ComponentStatus.ERROR, + state: ComponentStatus.LOADED, error: 'Error firing workflow action' }); dotHttpErrorManagerService.handle(error); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.spec.ts index 158e7343df54..bde2791c4437 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.spec.ts @@ -14,7 +14,7 @@ import { ControlContainer, FormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; -import { ConfirmationService } from 'primeng/api'; +import { ConfirmationService, MessageService } from 'primeng/api'; import { ConfirmDialogModule } from 'primeng/confirmdialog'; import { DropdownModule } from 'primeng/dropdown'; @@ -139,6 +139,7 @@ describe('DotEditContentWYSIWYGFieldComponent', () => { mockProvider(DotHttpErrorManagerService), mockProvider(ActivatedRoute), mockProvider(DotWorkflowService), + mockProvider(MessageService), provideHttpClient(), provideHttpClientTesting(), ConfirmationService, diff --git a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties index ccb22318d995..85af6136f424 100644 --- a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties +++ b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties @@ -5820,6 +5820,7 @@ edit.content.wysiwyg-field.language-variable-tooltip=Start typing to see matchin edit.content.sidebar.information.references-with.pages.tooltip=Used in {0} pages edit.content.sidebar.information.references-with.pages.not.used=Not used on any page yet +edit.content.success.workflow.message=Your changes have being applied. lts.expired.message = This version of dotCMS is no longer supported. Please contact your customer success manager to schedule an upgrade. lts.expires.soon.message = Your dotCMS version will no longer be supported in {0} days. Please contact your customer success manager to schedule an upgrade.