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
---
.../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.