diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts index bab367e5d557..1c4ee37ba0ae 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts @@ -21,6 +21,7 @@ import { DotMessageService, DotPropertiesService, DotWorkflowActionsFireService, + DotWorkflowsActionsService, PushPublishService } from '@dotcms/data-access'; import { @@ -208,6 +209,12 @@ describe('DotEmaShellComponent', () => { DotWorkflowActionsFireService, Router, Location, + { + provide: DotWorkflowsActionsService, + useValue: { + getByInode: () => of([]) + } + }, { provide: DotPropertiesService, useValue: dotPropertiesServiceMock @@ -559,6 +566,23 @@ describe('DotEmaShellComponent', () => { expect(spyloadPageAsset).toHaveBeenCalledWith({ url: '/my-awesome-page' }); }); + it('should get the workflow action when an `UPDATE_WORKFLOW_ACTION` event is received', () => { + const spyGetWorkflowActions = jest.spyOn(store, 'getWorkflowActions'); + + spectator.detectChanges(); + + spectator.triggerEventHandler( + DotEmaDialogComponent, + 'action', + DIALOG_ACTION_EVENT({ + name: NG_CUSTOM_EVENTS.UPDATE_WORKFLOW_ACTION + }) + ); + spectator.detectChanges(); + + expect(spyGetWorkflowActions).toHaveBeenCalled(); + }); + it('should trigger a store reload if the url is the same', () => { const spyReload = jest.spyOn(store, 'reloadCurrentPage'); const spyLocation = jest.spyOn(location, 'go'); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts index c07053bcc3ee..fc10066c55c1 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts @@ -19,7 +19,8 @@ import { DotPageLayoutService, DotPageRenderService, DotSeoMetaTagsService, - DotSeoMetaTagsUtilService + DotSeoMetaTagsUtilService, + DotWorkflowsActionsService } from '@dotcms/data-access'; import { SiteService } from '@dotcms/dotcms-js'; import { DotPageToolsSeoComponent } from '@dotcms/portlets/dot-ema/ui'; @@ -58,6 +59,7 @@ import { DotPageRenderService, DotSeoMetaTagsService, DotSeoMetaTagsUtilService, + DotWorkflowsActionsService, { provide: WINDOW, useValue: window @@ -111,6 +113,7 @@ export class DotEmaShellComponent implements OnInit, OnDestroy { ...(pageParams ?? {}), ...(viewParams ?? {}) }; + this.#updateLocation(queryParams); }); @@ -131,6 +134,11 @@ export class DotEmaShellComponent implements OnInit, OnDestroy { handleNgEvent({ event }: DialogAction) { switch (event.detail.name) { + case NG_CUSTOM_EVENTS.UPDATE_WORKFLOW_ACTION: { + this.uveStore.getWorkflowActions(); + break; + } + case NG_CUSTOM_EVENTS.SAVE_PAGE: { this.handleSavePageEvent(event); break; diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-ema-info-display/dot-ema-info-display.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-ema-info-display/dot-ema-info-display.component.spec.ts index a0c08e9bcb7e..ae6d037f6ccf 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-ema-info-display/dot-ema-info-display.component.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-ema-info-display/dot-ema-info-display.component.spec.ts @@ -21,7 +21,8 @@ import { DotExperimentsService, DotLanguagesService, DotLicenseService, - DotMessageService + DotMessageService, + DotWorkflowsActionsService } from '@dotcms/data-access'; import { LoginService } from '@dotcms/dotcms-js'; import { DEFAULT_VARIANT_NAME } from '@dotcms/dotcms-models'; @@ -53,6 +54,12 @@ describe('DotEmaInfoDisplayComponent', () => { MessageService, mockProvider(Router), mockProvider(ActivatedRoute), + { + provide: DotWorkflowsActionsService, + useValue: { + getByInode: () => of([]) + } + }, { provide: DotLanguagesService, useValue: new DotLanguagesServiceMock() diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html index 1f2c2c1b48cd..e5e086766816 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html @@ -51,7 +51,7 @@ data-testId="uve-toolbar-persona-selector" /> @if (!preview) { - Workflows + } diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts index 3ada5bd626fd..291efdc4c758 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts @@ -13,7 +13,8 @@ import { DotExperimentsService, DotLanguagesService, DotLicenseService, - DotPersonalizeService + DotPersonalizeService, + DotWorkflowsActionsService } from '@dotcms/data-access'; import { LoginService } from '@dotcms/dotcms-js'; import { @@ -41,6 +42,7 @@ import { } from '../../../utils'; import { DotEmaBookmarksComponent } from '../dot-ema-bookmarks/dot-ema-bookmarks.component'; import { DotEmaRunningExperimentComponent } from '../dot-ema-running-experiment/dot-ema-running-experiment.component'; +import { DotUveWorkflowActionsComponent } from '../dot-uve-workflow-actions/dot-uve-workflow-actions.component'; import { EditEmaLanguageSelectorComponent } from '../edit-ema-language-selector/edit-ema-language-selector.component'; import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/edit-ema-persona-selector.component'; @@ -107,7 +109,8 @@ describe('DotUveToolbarComponent', () => { HttpClientTestingModule, MockComponent(DotEmaBookmarksComponent), MockComponent(DotEmaRunningExperimentComponent), - MockComponent(EditEmaPersonaSelectorComponent) + MockComponent(EditEmaPersonaSelectorComponent), + MockComponent(DotUveWorkflowActionsComponent) ], providers: [ UVEStore, @@ -115,6 +118,10 @@ describe('DotUveToolbarComponent', () => { mockProvider(ConfirmationService, { confirm: jest.fn() }), + + mockProvider(DotWorkflowsActionsService, { + getByInode: () => of([]) + }), { provide: DotLanguagesService, useValue: new DotLanguagesServiceMock() @@ -181,6 +188,12 @@ describe('DotUveToolbarComponent', () => { }); }); + it('should have a dot-uve-workflow-actions component', () => { + const workflowActions = spectator.query(DotUveWorkflowActionsComponent); + + expect(workflowActions).toBeTruthy(); + }); + describe('copy-url', () => { let button: DebugElement; @@ -359,10 +372,6 @@ describe('DotUveToolbarComponent', () => { it('should have persona selector', () => { expect(spectator.query(byTestId('uve-toolbar-persona-selector'))).toBeTruthy(); }); - - it('should have workflows button', () => { - expect(spectator.query(byTestId('uve-toolbar-workflow-actions'))).toBeTruthy(); - }); }); describe('preview', () => { @@ -411,8 +420,10 @@ describe('DotUveToolbarComponent', () => { expect(spectator.query(byTestId('uve-toolbar-running-experiment'))).toBeFalsy(); }); - it('should not have workflow actions', () => { - expect(spectator.query(byTestId('uve-toolbar-workflow-actions'))).toBeFalsy(); + it('should not have a dot-uve-workflow-actions component', () => { + const workflowActions = spectator.query(DotUveWorkflowActionsComponent); + + expect(workflowActions).toBeNull(); }); }); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts index bfc47d0fe26a..0f649288747d 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts @@ -27,6 +27,7 @@ import { UVEStore } from '../../../store/dot-uve.store'; import { DotEmaBookmarksComponent } from '../dot-ema-bookmarks/dot-ema-bookmarks.component'; import { DotEmaInfoDisplayComponent } from '../dot-ema-info-display/dot-ema-info-display.component'; import { DotEmaRunningExperimentComponent } from '../dot-ema-running-experiment/dot-ema-running-experiment.component'; +import { DotUveWorkflowActionsComponent } from '../dot-uve-workflow-actions/dot-uve-workflow-actions.component'; import { EditEmaLanguageSelectorComponent } from '../edit-ema-language-selector/edit-ema-language-selector.component'; import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/edit-ema-persona-selector.component'; @@ -46,10 +47,10 @@ import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/ed SplitButtonModule, FormsModule, ReactiveFormsModule, - ChipModule, EditEmaPersonaSelectorComponent, EditEmaLanguageSelectorComponent, - ClipboardModule + DotUveWorkflowActionsComponent, + ChipModule ], providers: [DotPersonalizeService], templateUrl: './dot-uve-toolbar.component.html', @@ -59,8 +60,10 @@ import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/ed export class DotUveToolbarComponent { $personaSelector = viewChild('personaSelector'); $languageSelector = viewChild('languageSelector'); - #store = inject(UVEStore); + @Output() translatePage = new EventEmitter<{ page: DotPage; newLanguage: number }>(); + + readonly #store = inject(UVEStore); readonly #messageService = inject(MessageService); readonly #dotMessageService = inject(DotMessageService); readonly #confirmationService = inject(ConfirmationService); @@ -71,8 +74,6 @@ export class DotUveToolbarComponent { readonly $apiURL = this.#store.$apiURL; readonly $personaSelectorProps = this.#store.$personaSelector; - @Output() translatePage = new EventEmitter<{ page: DotPage; newLanguage: number }>(); - readonly $styleToolbarClass = computed(() => { if (!this.$isPreviewMode()) { return 'uve-toolbar'; @@ -81,6 +82,13 @@ export class DotUveToolbarComponent { return 'uve-toolbar uve-toolbar-preview'; }); + readonly $pageInode = computed(() => { + return this.#store.pageAPIResponse()?.page.inode; + }); + + readonly $actions = this.#store.workflowLoading; + readonly $workflowLoding = this.#store.workflowLoading; + protected readonly date = new Date(); /** diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.css b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.css similarity index 100% rename from core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.css rename to core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.css diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.html b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.html similarity index 83% rename from core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.html rename to core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.html index 5a2e1336efb4..ad072ffdf11d 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.html +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.html @@ -2,4 +2,5 @@ (actionFired)="handleActionTrigger($event)" [size]="'small'" [loading]="loading()" + [disabled]="!canEdit()" [actions]="actions()" /> diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.spec.ts similarity index 73% rename from core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.spec.ts rename to core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.spec.ts index 58d02b6a3d42..2855f2b42294 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.spec.ts @@ -3,6 +3,7 @@ import { Spectator, createComponentFactory, mockProvider } from '@ngneat/spectat import { Subject, of } from 'rxjs'; import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { signal } from '@angular/core'; import { MessageService } from 'primeng/api'; @@ -19,7 +20,6 @@ import { DotWizardService, DotWorkflowActionsFireService, DotWorkflowEventHandlerService, - DotWorkflowsActionsService, PushPublishService } from '@dotcms/data-access'; import { CoreWebService, LoginService } from '@dotcms/dotcms-js'; @@ -32,7 +32,10 @@ import { mockWorkflowsActions } from '@dotcms/utils-testing'; -import { DotEditEmaWorkflowActionsComponent } from './dot-edit-ema-workflow-actions.component'; +import { DotUveWorkflowActionsComponent } from './dot-uve-workflow-actions.component'; + +import { MOCK_RESPONSE_VTL } from '../../../shared/mocks'; +import { UVEStore } from '../../../store/dot-uve.store'; const DOT_WORKFLOW_PAYLOAD_MOCK: DotWorkflowPayload = { assign: '654b0931-1027-41f7-ad4d-173115ed8ec1', @@ -68,6 +71,8 @@ const workflowActionMock = { ] }; +const expectedInode = MOCK_RESPONSE_VTL.page.inode; + const messageServiceMock = new MockDotMessageService({ 'Workflow-Action': 'Workflow Action', 'edit.content.fire.action.success': 'Success', @@ -76,23 +81,40 @@ const messageServiceMock = new MockDotMessageService({ Loading: 'loading' }); -describe('DotEditEmaWorkflowActionsComponent', () => { - let spectator: Spectator; +const pageParams = { + url: 'test-url', + language_id: '1' +}; + +const uveStoreMock = { + pageAPIResponse: signal(MOCK_RESPONSE_VTL), + workflowActions: signal([]), + workflowLoading: signal(false), + canEditPage: signal(true), + pageParams: signal(pageParams), + loadPageAsset: jest.fn(), + reloadCurrentPage: jest.fn(), + setWorkflowActionLoading: jest.fn() +}; + +describe('DotUveWorkflowActionsComponent', () => { + let spectator: Spectator; let dotWizardService: DotWizardService; - let dotWorkflowsActionsService: DotWorkflowsActionsService; let dotWorkflowEventHandlerService: DotWorkflowEventHandlerService; let dotWorkflowActionsFireService: DotWorkflowActionsFireService; let messageService: MessageService; + let store: InstanceType; + const createComponent = createComponentFactory({ - component: DotEditEmaWorkflowActionsComponent, + component: DotUveWorkflowActionsComponent, imports: [HttpClientTestingModule], componentProviders: [ DotWizardService, - DotWorkflowsActionsService, DotWorkflowEventHandlerService, DotWorkflowActionsFireService, MessageService, + mockProvider(UVEStore, uveStoreMock), mockProvider(DotAlertConfirmService), mockProvider(DotMessageDisplayService), mockProvider(DotHttpErrorManagerService), @@ -116,57 +138,56 @@ describe('DotEditEmaWorkflowActionsComponent', () => { detectChanges: false }); + store = spectator.inject(UVEStore, true); dotWizardService = spectator.inject(DotWizardService, true); - dotWorkflowsActionsService = spectator.inject(DotWorkflowsActionsService, true); dotWorkflowEventHandlerService = spectator.inject(DotWorkflowEventHandlerService, true); dotWorkflowActionsFireService = spectator.inject(DotWorkflowActionsFireService, true); messageService = spectator.inject(MessageService, true); }); - it('should create', () => { - expect(spectator.component).toBeTruthy(); - }); - describe('Without Workflow Actions', () => { - beforeEach(() => { - spectator.setInput('inode', '123'); + it('should set action as an empty array and loading to true', () => { + uveStoreMock.workflowLoading.set(true); spectator.detectChanges(); - }); - it('should set action as an empty array and loading to true', () => { const dotWorkflowActionsComponent = spectator.query(DotWorkflowActionsComponent); expect(dotWorkflowActionsComponent.actions()).toEqual([]); expect(dotWorkflowActionsComponent.loading()).toBeTruthy(); expect(dotWorkflowActionsComponent.size()).toBe('small'); }); + + it("should be disabled if user can't edit", () => { + uveStoreMock.canEditPage.set(false); + spectator.detectChanges(); + + const dotWorkflowActionsComponent = spectator.query(DotWorkflowActionsComponent); + expect(dotWorkflowActionsComponent.disabled()).toBeTruthy(); + }); }); describe('With Workflow Actions', () => { beforeEach(() => { - jest.spyOn(dotWorkflowsActionsService, 'getByInode').mockReturnValue( - of(mockWorkflowsActions) - ); - - spectator.setInput('inode', '123'); + uveStoreMock.workflowLoading.set(false); + uveStoreMock.canEditPage.set(true); + uveStoreMock.workflowActions.set(mockWorkflowsActions); spectator.detectChanges(); }); it('should load workflow actions', () => { const dotWorkflowActionsComponent = spectator.query(DotWorkflowActionsComponent); - expect(dotWorkflowsActionsService.getByInode).toHaveBeenCalledWith('123'); expect(dotWorkflowActionsComponent.actions()).toEqual(mockWorkflowsActions); + expect(dotWorkflowActionsComponent.loading()).toBeFalsy(); + expect(dotWorkflowActionsComponent.disabled()).toBeFalsy(); }); - it('should fire workflow actions when it does not have inputs', () => { - jest.spyOn(dotWorkflowEventHandlerService, 'containsPushPublish').mockReturnValue( - false - ); + it('should fire workflow actions and loadPageAssets', () => { + const spySetWorkflowActionLoading = jest.spyOn(store, 'setWorkflowActionLoading'); + const spyLoadPageAsset = jest.spyOn(store, 'loadPageAsset'); const dotWorkflowActionsComponent = spectator.query(DotWorkflowActionsComponent); const spy = jest .spyOn(dotWorkflowActionsFireService, 'fireTo') .mockReturnValue(of(dotcmsContentletMock)); - const spyNewPage = jest.spyOn(spectator.component.newPage, 'emit'); const spyMessage = jest.spyOn(messageService, 'add'); dotWorkflowActionsComponent.actionFired.emit({ @@ -175,16 +196,16 @@ describe('DotEditEmaWorkflowActionsComponent', () => { }); expect(spy).toHaveBeenCalledWith({ - inode: '123', + inode: expectedInode, actionId: mockWorkflowsActions[0].id, data: undefined }); - expect(spyNewPage).toHaveBeenCalledWith(dotcmsContentletMock); - expect(dotWorkflowsActionsService.getByInode).toHaveBeenCalledWith( - dotcmsContentletMock.inode - ); - + expect(spySetWorkflowActionLoading).toHaveBeenCalledWith(true); + expect(spyLoadPageAsset).toHaveBeenCalledWith({ + language_id: dotcmsContentletMock.languageId.toString(), + url: dotcmsContentletMock.url + }); expect(spyMessage).toHaveBeenCalledTimes(2); // Check the first message @@ -203,6 +224,29 @@ describe('DotEditEmaWorkflowActionsComponent', () => { }); }); + it('should fire workflow actions and reloadPage', () => { + const spySetWorkflowActionLoading = jest.spyOn(store, 'setWorkflowActionLoading'); + const spyReloadCurrentPage = jest.spyOn(store, 'reloadCurrentPage'); + const dotWorkflowActionsComponent = spectator.query(DotWorkflowActionsComponent); + const spy = jest + .spyOn(dotWorkflowActionsFireService, 'fireTo') + .mockReturnValue(of({ ...dotcmsContentletMock, ...pageParams })); + + dotWorkflowActionsComponent.actionFired.emit({ + ...mockWorkflowsActions[0], + actionInputs: [] + }); + + expect(spy).toHaveBeenCalledWith({ + inode: expectedInode, + actionId: mockWorkflowsActions[0].id, + data: undefined + }); + + expect(spySetWorkflowActionLoading).toHaveBeenCalledWith(true); + expect(spyReloadCurrentPage).toHaveBeenCalledWith(); + }); + it('should open Wizard if it has inputs ', () => { const output$ = new Subject(); @@ -211,9 +255,6 @@ describe('DotEditEmaWorkflowActionsComponent', () => { title: 'title' }; - jest.spyOn(dotWorkflowEventHandlerService, 'containsPushPublish').mockReturnValue( - false - ); jest.spyOn(dotWorkflowEventHandlerService, 'setWizardInput').mockReturnValue( wizardInputMock ); @@ -240,7 +281,7 @@ describe('DotEditEmaWorkflowActionsComponent', () => { workflowActionMock.actionInputs ); expect(spyFireTo).toHaveBeenCalledWith({ - inode: '123', + inode: expectedInode, actionId: workflowActionMock.id, data: DOT_PROCESSED_WORKFLOW_PAYLOAD_MOCK }); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.ts similarity index 69% rename from core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.ts rename to core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.ts index a990fdd27543..2a23808dc504 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-workflow-actions/dot-uve-workflow-actions.component.ts @@ -1,13 +1,4 @@ -import { - Component, - EventEmitter, - Input, - OnChanges, - Output, - SimpleChanges, - inject, - signal -} from '@angular/core'; +import { Component, computed, inject } from '@angular/core'; import { MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; @@ -19,39 +10,39 @@ import { DotMessageService, DotWizardService, DotWorkflowActionsFireService, - DotWorkflowsActionsService, DotWorkflowEventHandlerService } from '@dotcms/data-access'; import { DotCMSContentlet, DotCMSWorkflowAction, DotWorkflowPayload } from '@dotcms/dotcms-models'; -import { DotMessagePipe, DotWorkflowActionsComponent } from '@dotcms/ui'; +import { DotWorkflowActionsComponent } from '@dotcms/ui'; + +import { UVEStore } from '../../../store/dot-uve.store'; +import { compareUrlPaths, getPageURI } from '../../../utils'; @Component({ - selector: 'dot-edit-ema-workflow-actions', + selector: 'dot-uve-workflow-actions', standalone: true, - imports: [DotWorkflowActionsComponent, ButtonModule, DotMessagePipe], + imports: [DotWorkflowActionsComponent, ButtonModule], providers: [ DotWorkflowActionsFireService, DotWorkflowEventHandlerService, - DotWorkflowsActionsService, DotHttpErrorManagerService ], - templateUrl: './dot-edit-ema-workflow-actions.component.html', - styleUrl: './dot-edit-ema-workflow-actions.component.css' + templateUrl: './dot-uve-workflow-actions.component.html', + styleUrl: './dot-uve-workflow-actions.component.css' }) -export class DotEditEmaWorkflowActionsComponent implements OnChanges { - @Input({ required: true }) inode: string; - @Output() newPage: EventEmitter = new EventEmitter(); - - protected actions = signal([]); - protected loading = signal(true); - +export class DotUveWorkflowActionsComponent { private readonly dotWorkflowActionsFireService = inject(DotWorkflowActionsFireService); - private readonly dotWorkflowsActionsService = inject(DotWorkflowsActionsService); private readonly dotMessageService = inject(DotMessageService); private readonly httpErrorManagerService = inject(DotHttpErrorManagerService); private readonly dotWizardService = inject(DotWizardService); private readonly dotWorkflowEventHandlerService = inject(DotWorkflowEventHandlerService); private readonly messageService = inject(MessageService); + readonly #uveStore = inject(UVEStore); + + inode = computed(() => this.#uveStore.pageAPIResponse()?.page.inode); + actions = this.#uveStore.workflowActions; + loading = this.#uveStore.workflowLoading; + canEdit = this.#uveStore.canEditPage; private readonly successMessage = { severity: 'info', @@ -67,12 +58,6 @@ export class DotEditEmaWorkflowActionsComponent implements OnChanges { life: 2000 }; - ngOnChanges(changes: SimpleChanges) { - if (changes.inode) { - this.loadWorkflowActions(this.inode); - } - } - handleActionTrigger(workflow: DotCMSWorkflowAction): void { const { actionInputs = [] } = workflow; const isPushPublish = this.dotWorkflowEventHandlerService.containsPushPublish(actionInputs); @@ -99,21 +84,6 @@ export class DotEditEmaWorkflowActionsComponent implements OnChanges { }); } - private loadWorkflowActions(inode: string): void { - this.loading.set(true); - this.dotWorkflowsActionsService - .getByInode(inode) - .pipe( - map((newWorkflows: DotCMSWorkflowAction[]) => { - return newWorkflows || []; - }) - ) - .subscribe((newWorkflows: DotCMSWorkflowAction[]) => { - this.loading.set(false); - this.actions.set(newWorkflows); - }); - } - private openWizard(workflow: DotCMSWorkflowAction): void { this.dotWizardService .open( @@ -138,7 +108,7 @@ export class DotEditEmaWorkflowActionsComponent implements OnChanges { workflow: DotCMSWorkflowAction, data?: T ): void { - this.loading.set(true); + this.#uveStore.setWorkflowActionLoading(true); this.messageService.add({ ...this.successMessage, detail: this.dotMessageService.get('edit.ema.page.executing.workflow.action'), @@ -147,7 +117,7 @@ export class DotEditEmaWorkflowActionsComponent implements OnChanges { this.dotWorkflowActionsFireService .fireTo({ - inode: this.inode, + inode: this.inode(), actionId: workflow.id, data }) @@ -162,17 +132,40 @@ export class DotEditEmaWorkflowActionsComponent implements OnChanges { }) ) .subscribe((contentlet: DotCMSContentlet) => { - this.loading.set(false); - if (!contentlet) { return; } - const { inode } = contentlet; - this.newPage.emit(contentlet); - this.inode = inode; - this.loadWorkflowActions(inode); + this.handleNewContent(contentlet); this.messageService.add(this.successMessage); }); } + + /** + * Handle a new page event. This event is triggered when the page changes for a Workflow Action + * Update the query params if the url or the language id changed + * + * @param {DotCMSContentlet} page + * @memberof EditEmaToolbarComponent + */ + protected handleNewContent(pageAsset: DotCMSContentlet): void { + const currentParams = this.#uveStore.pageParams(); + + const url = getPageURI(pageAsset); + const language_id = pageAsset.languageId?.toString(); + + const urlChanged = !compareUrlPaths(url, currentParams.url); + const languageChanged = language_id !== currentParams.language_id; + + if (urlChanged || languageChanged) { + this.#uveStore.loadPageAsset({ + url, + language_id + }); + + return; + } + + this.#uveStore.reloadCurrentPage(); + } } diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/edit-ema-toolbar/edit-ema-toolbar.component.html b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/edit-ema-toolbar/edit-ema-toolbar.component.html index 252310595753..3c88816de67f 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/edit-ema-toolbar/edit-ema-toolbar.component.html +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/edit-ema-toolbar/edit-ema-toolbar.component.html @@ -64,9 +64,8 @@ #personaSelector data-testId="persona-selector" /> - @if ($toolbarProps().workflowActionsInode; as inode) { - - } + + @if ($toolbarProps().unlockButton; as unlockButton) { { let spectator: Spectator; let store: SpyObject>; let messageService: MessageService; - let router: Router; let confirmationService: ConfirmationService; const createComponent = createComponentFactory({ component: EditEmaToolbarComponent, imports: [ MockComponent(DotDeviceSelectorSeoComponent), - MockComponent(DotEditEmaWorkflowActionsComponent), + MockComponent(DotUveWorkflowActionsComponent), MockComponent(DotEmaBookmarksComponent), MockComponent(DotEmaInfoDisplayComponent), MockComponent(DotEmaRunningExperimentComponent), @@ -191,7 +189,6 @@ describe('EditEmaToolbarComponent', () => { store = spectator.inject(UVEStore); messageService = spectator.inject(MessageService); - router = spectator.inject(Router); confirmationService = spectator.inject(ConfirmationService); }); @@ -407,50 +404,11 @@ describe('EditEmaToolbarComponent', () => { rejectLabel: 'Reject' }); }); - - xit('should dpersonalize - call service', () => { - expect(true).toBe(true); - }); }); - describe('dot-edit-ema-workflow-actions', () => { - it('should have attr', () => { - const workflowActions = spectator.query(DotEditEmaWorkflowActionsComponent); - - expect(workflowActions.inode).toBe('123-i'); - }); - - it('should update page', () => { - const spyloadPageAsset = jest.spyOn(store, 'loadPageAsset'); - spectator.triggerEventHandler(DotEditEmaWorkflowActionsComponent, 'newPage', { - pageURI: '/path-and-stuff', - url: 'path', - languageId: 1 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any); - - spectator.detectChanges(); - - expect(spyloadPageAsset).toHaveBeenCalledWith({ - url: '/path-and-stuff', - language_id: '1' - }); - }); - - it('should trigger a store reload if the URL from urlContentMap is the same as the current URL', () => { - jest.spyOn(store, 'pageAPIResponse').mockReturnValue(PAGE_RESPONSE_URL_CONTENT_MAP); - - spectator.triggerEventHandler(DotEditEmaWorkflowActionsComponent, 'newPage', { - pageURI: '/test-url', - url: '/test-url', - languageId: 1 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any); - - spectator.detectChanges(); - expect(store.reloadCurrentPage).toHaveBeenCalled(); - expect(router.navigate).not.toHaveBeenCalled(); - }); + it('should have a dot-uve-workflow-actions component', () => { + const workflowActions = spectator.query(DotUveWorkflowActionsComponent); + expect(workflowActions).toBeTruthy(); }); describe('dot-ema-info-display', () => { @@ -501,7 +459,6 @@ describe('EditEmaToolbarComponent', () => { }); store = spectator.inject(UVEStore); messageService = spectator.inject(MessageService); - router = spectator.inject(Router); confirmationService = spectator.inject(ConfirmationService); }); it('should show when showInfoDisplay is true in the store', () => { diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/edit-ema-toolbar/edit-ema-toolbar.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/edit-ema-toolbar/edit-ema-toolbar.component.ts index 62220f7221ca..6f059eb73aa1 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/edit-ema-toolbar/edit-ema-toolbar.component.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/edit-ema-toolbar/edit-ema-toolbar.component.ts @@ -29,10 +29,10 @@ import { DEFAULT_PERSONA } from '../../../shared/consts'; import { DotPage } from '../../../shared/models'; import { UVEStore } from '../../../store/dot-uve.store'; import { compareUrlPaths } from '../../../utils'; -import { DotEditEmaWorkflowActionsComponent } from '../dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component'; import { DotEmaBookmarksComponent } from '../dot-ema-bookmarks/dot-ema-bookmarks.component'; import { DotEmaInfoDisplayComponent } from '../dot-ema-info-display/dot-ema-info-display.component'; import { DotEmaRunningExperimentComponent } from '../dot-ema-running-experiment/dot-ema-running-experiment.component'; +import { DotUveWorkflowActionsComponent } from '../dot-uve-workflow-actions/dot-uve-workflow-actions.component'; import { EditEmaLanguageSelectorComponent } from '../edit-ema-language-selector/edit-ema-language-selector.component'; import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/edit-ema-persona-selector.component'; @@ -50,7 +50,7 @@ import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/ed EditEmaPersonaSelectorComponent, EditEmaLanguageSelectorComponent, DotEmaInfoDisplayComponent, - DotEditEmaWorkflowActionsComponent, + DotUveWorkflowActionsComponent, ClipboardModule ], providers: [DotPersonalizeService], diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.spec.ts index 450bc1f9e114..d40d3308e568 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.spec.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from '@jest/globals'; -import { SpectatorRouting, createRoutingFactory, byTestId } from '@ngneat/spectator/jest'; +import { + SpectatorRouting, + createRoutingFactory, + byTestId, + mockProvider +} from '@ngneat/spectator/jest'; import { MockComponent } from 'ng-mocks'; import { Observable, of, throwError } from 'rxjs'; @@ -25,18 +30,22 @@ import { DotESContentService, DotExperimentsService, DotFavoritePageService, + DotGlobalMessageService, DotHttpErrorManagerService, DotIframeService, DotLanguagesService, DotLicenseService, + DotMessageDisplayService, DotMessageService, DotPersonalizeService, DotPropertiesService, + DotRouterService, DotSeoMetaTagsService, DotSeoMetaTagsUtilService, DotSessionStorageService, DotTempFileUploadService, DotWorkflowActionsFireService, + DotWorkflowsActionsService, PushPublishService } from '@dotcms/data-access'; import { @@ -67,9 +76,9 @@ import { MockDotHttpErrorManagerService } from '@dotcms/utils-testing'; -import { DotEditEmaWorkflowActionsComponent } from './components/dot-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component'; import { DotEmaRunningExperimentComponent } from './components/dot-ema-running-experiment/dot-ema-running-experiment.component'; import { DotUveToolbarComponent } from './components/dot-uve-toolbar/dot-uve-toolbar.component'; +import { DotUveWorkflowActionsComponent } from './components/dot-uve-workflow-actions/dot-uve-workflow-actions.component'; import { CONTENT_TYPE_MOCK } from './components/edit-ema-palette/components/edit-ema-palette-content-type/edit-ema-palette-content-type.component.spec'; import { CONTENTLETS_MOCK } from './components/edit-ema-palette/edit-ema-palette.component.spec'; import { EditEmaToolbarComponent } from './components/edit-ema-toolbar/edit-ema-toolbar.component'; @@ -136,7 +145,7 @@ const createRouting = () => component: EditEmaEditorComponent, imports: [RouterTestingModule, HttpClientTestingModule, SafeUrlPipe, ConfirmDialogModule], declarations: [ - MockComponent(DotEditEmaWorkflowActionsComponent), + MockComponent(DotUveWorkflowActionsComponent), MockComponent(DotResultsSeoToolComponent), MockComponent(DotEmaRunningExperimentComponent), MockComponent(EditEmaToolbarComponent) @@ -149,6 +158,15 @@ const createRouting = () => DotFavoritePageService, DotESContentService, DotSessionStorageService, + mockProvider(DotMessageDisplayService), + mockProvider(DotRouterService), + mockProvider(DotGlobalMessageService), + { + provide: DotWorkflowsActionsService, + useValue: { + getByInode: () => of([]) + } + }, { provide: DotPropertiesService, useValue: { @@ -440,6 +458,7 @@ describe('EditEmaEditorComponent', () => { store.setFlags({ FEATURE_FLAG_UVE_PREVIEW_MODE: true }); + spectator.detectChanges(); const toolbar = spectator.query(DotUveToolbarComponent); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts index 4d817ba4af5a..a26c028c30cf 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts @@ -159,7 +159,7 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { readonly host = '*'; readonly $ogTags: WritableSignal = signal(undefined); readonly $editorProps = this.uveStore.$editorProps; - // This on is the FF + readonly $previewMode = this.uveStore.$previewMode; readonly $isPreviewMode = this.uveStore.$isPreviewMode; diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-layout/edit-ema-layout.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-layout/edit-ema-layout.component.spec.ts index b3c345499578..cd080b9a58b9 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-layout/edit-ema-layout.component.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-layout/edit-ema-layout.component.spec.ts @@ -19,7 +19,8 @@ import { DotLicenseService, DotMessageService, DotPageLayoutService, - DotRouterService + DotRouterService, + DotWorkflowsActionsService } from '@dotcms/data-access'; import { CoreWebService, LoginService } from '@dotcms/dotcms-js'; import { TemplateBuilderComponent, TemplateBuilderModule } from '@dotcms/template-builder'; @@ -93,6 +94,9 @@ describe('EditEmaLayoutComponent', () => { get: jest.fn(() => of(PAGE_RESPONSE)), getClientPage: jest.fn(() => of(PAGE_RESPONSE)) }), + mockProvider(DotWorkflowsActionsService, { + getByInode: jest.fn(() => of([])) + }), MockProvider(DotExperimentsService, DotExperimentsServiceMock, 'useValue'), MockProvider(DotRouterService, new MockDotRouterJestService(jest), 'useValue'), MockProvider(DotLanguagesService, new DotLanguagesServiceMock(), 'useValue'), diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/enums.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/enums.ts index 35c542dbc821..d6e2cd6f9c3a 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/enums.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/enums.ts @@ -12,7 +12,8 @@ export enum NG_CUSTOM_EVENTS { OPEN_WIZARD = 'workflow-wizard', DIALOG_CLOSED = 'dialog-closed', EDIT_CONTENTLET_UPDATED = 'edit-contentlet-data-updated', - LANGUAGE_IS_CHANGED = 'language-is-changed' + LANGUAGE_IS_CHANGED = 'language-is-changed', + UPDATE_WORKFLOW_ACTION = 'update-workflow-action' } // Status of the whole UVE diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts index 45de140e732c..4755c10f8e5e 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts @@ -17,7 +17,8 @@ import { DotLanguagesService, DotLicenseService, DotMessageService, - DotPropertiesService + DotPropertiesService, + DotWorkflowsActionsService } from '@dotcms/data-access'; import { LoginService } from '@dotcms/dotcms-js'; import { @@ -67,6 +68,12 @@ describe('UVEStore', () => { MessageService, mockProvider(Router), mockProvider(ActivatedRoute), + { + provide: DotWorkflowsActionsService, + useValue: { + getByInode: () => of({}) + } + }, { provide: DotPropertiesService, useValue: dotPropertiesServiceMock diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts index d43e88e037d2..1bd25f53c8fd 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts @@ -109,7 +109,6 @@ export function withUVEToolbar() { ? (pageAPIResponse?.urlContentMap ?? null) : null, runningExperiment: isExperimentRunning ? experiment : null, - workflowActionsInode: store.canEditPage() ? pageAPIResponse?.page.inode : null, unlockButton: shouldShowUnlock ? unlockButton : null, showInfoDisplay: shouldShowInfoDisplay }; diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.spec.ts index 30c8d52ea39c..14dc9b20de07 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.spec.ts @@ -540,7 +540,6 @@ describe('withEditor', () => { currentLanguage: MOCK_RESPONSE_HEADLESS.viewAs.language, urlContentMap: null, runningExperiment: null, - workflowActionsInode: MOCK_RESPONSE_HEADLESS.page.inode, unlockButton: null, showInfoDisplay: false }); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.spec.ts index 9a49c45296de..f5a2d088e64f 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.spec.ts @@ -14,7 +14,8 @@ import { DotExperimentsService, DotLanguagesService, DotLicenseService, - DotMessageService + DotMessageService, + DotWorkflowsActionsService } from '@dotcms/data-access'; import { LoginService } from '@dotcms/dotcms-js'; import { @@ -80,6 +81,7 @@ describe('withLoad', () => { let spectator: SpectatorService>; let store: InstanceType; let dotPageApiService: SpyObject; + let dotWorkflowsActionsService: SpyObject; let router: Router; const createService = createServiceFactory({ @@ -87,15 +89,21 @@ describe('withLoad', () => { providers: [ mockProvider(Router), mockProvider(ActivatedRoute), + { + provide: DotWorkflowsActionsService, + useValue: { + getByInode: () => of([]) + } + }, { provide: DotPageApiService, useValue: { get() { return of({}); }, - getClientPage() { - return of({}); - }, + getClientPage: jest + .fn() + .mockImplementation(buildPageAPIResponseFromMock(MOCK_RESPONSE_HEADLESS)), save: jest.fn() } }, @@ -143,6 +151,7 @@ describe('withLoad', () => { router = spectator.inject(Router); dotPageApiService = spectator.inject(DotPageApiService); + dotWorkflowsActionsService = spectator.inject(DotWorkflowsActionsService); jest.spyOn(dotPageApiService, 'get').mockImplementation( buildPageAPIResponseFromMock(MOCK_RESPONSE_HEADLESS) ); @@ -183,6 +192,14 @@ describe('withLoad', () => { expect(store.isClientReady()).toBe(true); }); + it('should call workflow action service on loadPageAsset', () => { + const getWorkflowActionsSpy = jest.spyOn(dotWorkflowsActionsService, 'getByInode'); + store.loadPageAsset(HEADLESS_BASE_QUERY_PARAMS); + expect(getWorkflowActionsSpy).toHaveBeenCalledWith( + MOCK_RESPONSE_HEADLESS.page.inode + ); + }); + it('should update the pageParams with the vanity URL on permanent redirect', () => { const permanentRedirect = getVanityUrl( VTL_BASE_QUERY_PARAMS.url, @@ -198,10 +215,7 @@ describe('withLoad', () => { store.loadPageAsset(VTL_BASE_QUERY_PARAMS); expect(router.navigate).toHaveBeenCalledWith([], { - queryParams: { - ...VTL_BASE_QUERY_PARAMS, - url: forwardTo - }, + queryParams: { url: forwardTo }, queryParamsHandling: 'merge' }); }); @@ -221,10 +235,7 @@ describe('withLoad', () => { store.loadPageAsset(VTL_BASE_QUERY_PARAMS); expect(router.navigate).toHaveBeenCalledWith([], { - queryParams: { - ...VTL_BASE_QUERY_PARAMS, - url: forwardTo - }, + queryParams: { url: forwardTo }, queryParamsHandling: 'merge' }); }); @@ -237,12 +248,20 @@ describe('withLoad', () => { expect(getPageSpy).toHaveBeenCalledWith(pageParams, { params: null, query: '' }); }); - }); - it('should reload the store with a specific property value', () => { - store.reloadCurrentPage({ isClientReady: false }); + it('should reload the store with a specific property value', () => { + store.reloadCurrentPage({ isClientReady: false }); - expect(store.isClientReady()).toBe(false); + expect(store.isClientReady()).toBe(false); + }); + + it('should call workflow action service on reloadCurrentPage', () => { + const getWorkflowActionsSpy = jest.spyOn(dotWorkflowsActionsService, 'getByInode'); + store.reloadCurrentPage(); + expect(getWorkflowActionsSpy).toHaveBeenCalledWith( + MOCK_RESPONSE_HEADLESS.page.inode + ); + }); }); }); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts index 0c1b479186c8..fb532018d950 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts @@ -1,4 +1,3 @@ -import { tapResponse } from '@ngrx/operators'; import { patchState, signalStoreFeature, type, withMethods } from '@ngrx/signals'; import { rxMethod } from '@ngrx/signals/rxjs-interop'; import { EMPTY, forkJoin, of, pipe } from 'rxjs'; @@ -7,7 +6,7 @@ import { HttpErrorResponse } from '@angular/common/http'; import { inject } from '@angular/core'; import { Router } from '@angular/router'; -import { map, shareReplay, switchMap, take, tap } from 'rxjs/operators'; +import { catchError, map, shareReplay, switchMap, take, tap } from 'rxjs/operators'; import { DotExperimentsService, DotLanguagesService, DotLicenseService } from '@dotcms/data-access'; import { LoginService } from '@dotcms/dotcms-js'; @@ -18,6 +17,7 @@ import { UVE_STATUS } from '../../../shared/enums'; import { computeCanEditPage, computePageIsLocked, isForwardOrPage } from '../../../utils'; import { UVEState } from '../../models'; import { withClient } from '../client/withClient'; +import { withWorkflow } from '../workflow/withWorkflow'; /** * Add load and reload method to the store @@ -31,6 +31,7 @@ export function withLoad() { state: type() }, withClient(), + withWorkflow(), withMethods((store) => { const router = inject(Router); const dotPageApiService = inject(DotPageApiService); @@ -74,20 +75,15 @@ export function withLoad() { switchMap((pageAsset) => { const { vanityUrl } = pageAsset; - // If there is no vanity and is not a redirect we just return the pageAPI response + // If there is not vanity and is not a redirect we just return the pageAPI response if (isForwardOrPage(vanityUrl)) { return of(pageAsset); } - const queryParams = { - ...pageParams, - url: vanityUrl.forwardTo.replace('/', '') - }; - - // Will trigger full editor page Reload + const url = vanityUrl.forwardTo.replace('/', ''); router.navigate([], { - queryParams, - queryParamsHandling: 'merge' + queryParamsHandling: 'merge', + queryParams: { url } }); // EMPTY is a simple Observable that only emits the complete notification. @@ -101,13 +97,16 @@ export function withLoad() { .pipe(take(1), shareReplay()), currentUser: loginService.getCurrentUser() }).pipe( - tap({ - error: ({ status: errorStatus }: HttpErrorResponse) => { - patchState(store, { - errorCode: errorStatus, - status: UVE_STATUS.ERROR - }); - } + tap(({ pageAsset }) => + store.getWorkflowActions(pageAsset.page.inode) + ), + catchError(({ status: errorStatus }: HttpErrorResponse) => { + patchState(store, { + errorCode: errorStatus, + status: UVE_STATUS.ERROR + }); + + return EMPTY; }), switchMap(({ pageAsset, isEnterprise, currentUser }) => { const experimentId = @@ -121,40 +120,42 @@ export function withLoad() { pageAsset.page.identifier ) }).pipe( - tap({ - next: ({ experiment, languages }) => { - const canEditPage = computeCanEditPage( - pageAsset?.page, - currentUser, - experiment - ); - - const pageIsLocked = computePageIsLocked( - pageAsset?.page, - currentUser - ); - - const isTraditionalPage = !pageParams.clientHost; // If we don't send the clientHost we are using as VTL page - - patchState(store, { - pageAPIResponse: pageAsset, - isEnterprise, - currentUser, - experiment, - languages, - canEditPage, - pageIsLocked, - isTraditionalPage, - isClientReady: isTraditionalPage, // If is a traditional page we are ready - status: UVE_STATUS.LOADED - }); - }, - error: ({ status: errorStatus }: HttpErrorResponse) => { - patchState(store, { - errorCode: errorStatus, - status: UVE_STATUS.ERROR - }); - } + catchError(({ status: errorStatus }: HttpErrorResponse) => { + patchState(store, { + errorCode: errorStatus, + status: UVE_STATUS.ERROR + }); + + return EMPTY; + }), + tap(({ experiment, languages }) => { + const canEditPage = computeCanEditPage( + pageAsset?.page, + currentUser, + experiment + ); + + const pageIsLocked = computePageIsLocked( + pageAsset?.page, + currentUser + ); + + const isPreview = pageParams.preview === 'true'; + const isTraditionalPage = !pageParams.clientHost; + const isClientReady = isTraditionalPage || isPreview; + + patchState(store, { + pageAPIResponse: pageAsset, + isEnterprise, + currentUser, + experiment, + languages, + canEditPage, + pageIsLocked, + isClientReady, + isTraditionalPage, + status: UVE_STATUS.LOADED + }); }) ); }) @@ -180,44 +181,45 @@ export function withLoad() { return dotPageApiService .getClientPage(store.pageParams(), store.clientRequestProps()) .pipe( - switchMap((pageAPIResponse) => - dotLanguagesService - .getLanguagesUsedPage(pageAPIResponse.page.identifier) - .pipe( - map((languages) => ({ - pageAPIResponse, - languages - })) + tap((pageAsset) => { + store.getWorkflowActions(pageAsset.page.inode); + }), + switchMap((pageAPIResponse) => { + return forkJoin({ + pageAPIResponse: of(pageAPIResponse), + languages: dotLanguagesService.getLanguagesUsedPage( + pageAPIResponse.page.identifier ) - ), - tapResponse({ - next: ({ pageAPIResponse, languages }) => { - const canEditPage = computeCanEditPage( - pageAPIResponse?.page, - store.currentUser(), - store.experiment() - ); + }); + }), + catchError(({ status: errorStatus }: HttpErrorResponse) => { + patchState(store, { + errorCode: errorStatus, + status: UVE_STATUS.ERROR + }); - const pageIsLocked = computePageIsLocked( - pageAPIResponse?.page, - store.currentUser() - ); + return EMPTY; + }), + tap(({ pageAPIResponse, languages }) => { + const canEditPage = computeCanEditPage( + pageAPIResponse?.page, + store.currentUser(), + store.experiment() + ); + + const pageIsLocked = computePageIsLocked( + pageAPIResponse?.page, + store.currentUser() + ); - patchState(store, { - pageAPIResponse, - languages, - canEditPage, - pageIsLocked, - status: UVE_STATUS.LOADED, - isClientReady: partialState?.isClientReady ?? true - }); - }, - error: ({ status: errorStatus }: HttpErrorResponse) => { - patchState(store, { - errorCode: errorStatus, - status: UVE_STATUS.ERROR - }); - } + patchState(store, { + pageAPIResponse, + languages, + canEditPage, + pageIsLocked, + status: UVE_STATUS.LOADED, + isClientReady: partialState?.isClientReady ?? true + }); }) ); }) diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/workflow/withWorkflow.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/workflow/withWorkflow.spec.ts new file mode 100644 index 000000000000..f1bafa1e07a7 --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/workflow/withWorkflow.spec.ts @@ -0,0 +1,93 @@ +import { describe, expect } from '@jest/globals'; +import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; +import { signalStore, withState } from '@ngrx/signals'; +import { of } from 'rxjs'; + +import { DotWorkflowsActionsService } from '@dotcms/data-access'; +import { mockWorkflowsActions } from '@dotcms/utils-testing'; + +import { withWorkflow } from './withWorkflow'; + +import { DotPageApiParams } from '../../../services/dot-page-api.service'; +import { UVE_STATUS } from '../../../shared/enums'; +import { MOCK_RESPONSE_HEADLESS } from '../../../shared/mocks'; +import { UVEState } from '../../models'; + +const pageParams: DotPageApiParams = { + url: 'new-url', + language_id: '1', + 'com.dotmarketing.persona.id': '2' +}; + +const initialState: UVEState = { + isEnterprise: false, + languages: [], + pageAPIResponse: MOCK_RESPONSE_HEADLESS, + currentUser: null, + experiment: null, + errorCode: null, + pageParams, + status: UVE_STATUS.LOADING, + isTraditionalPage: true, + canEditPage: false, + pageIsLocked: true, + isClientReady: false +}; + +export const uveStoreMock = signalStore(withState(initialState), withWorkflow()); + +describe('withLoad', () => { + let spectator: SpectatorService>; + let store: InstanceType; + let dotWorkflowsActionsService: SpyObject; + + const createService = createServiceFactory({ + service: uveStoreMock, + providers: [ + { + provide: DotWorkflowsActionsService, + useValue: { + getByInode: () => of(mockWorkflowsActions) + } + } + ] + }); + + beforeEach(() => { + spectator = createService(); + store = spectator.service; + dotWorkflowsActionsService = spectator.inject(DotWorkflowsActionsService); + }); + + it('should start with the initial state', () => { + expect(store.workflowActions()).toEqual([]); + expect(store.workflowLoading()).toBe(true); + }); + + describe('withMethods', () => { + describe('getWorkflowActions', () => { + it('should call get workflow actions using store page inode', () => { + const spyWorkflowActions = jest.spyOn(dotWorkflowsActionsService, 'getByInode'); + store.getWorkflowActions(); + expect(store.workflowLoading()).toBe(false); + expect(store.workflowActions()).toEqual(mockWorkflowsActions); + expect(spyWorkflowActions).toHaveBeenCalledWith(MOCK_RESPONSE_HEADLESS.page.inode); + }); + + it('should call get workflow actions using the provided inode', () => { + const spyWorkflowActions = jest.spyOn(dotWorkflowsActionsService, 'getByInode'); + store.getWorkflowActions('123'); + expect(store.workflowLoading()).toBe(false); + expect(store.workflowActions()).toEqual(mockWorkflowsActions); + expect(spyWorkflowActions).toHaveBeenCalledWith('123'); + }); + }); + + it('should set workflowLoading to true', () => { + store.setWorkflowActionLoading(true); + expect(store.workflowLoading()).toBe(true); + }); + }); + + afterEach(() => jest.clearAllMocks()); +}); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/workflow/withWorkflow.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/workflow/withWorkflow.ts new file mode 100644 index 000000000000..1827f86a0423 --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/workflow/withWorkflow.ts @@ -0,0 +1,81 @@ +import { tapResponse } from '@ngrx/operators'; +import { patchState, signalStoreFeature, type, withMethods, withState } from '@ngrx/signals'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; +import { pipe } from 'rxjs'; + +import { HttpErrorResponse } from '@angular/common/http'; +import { inject } from '@angular/core'; + +import { switchMap, tap } from 'rxjs/operators'; + +import { DotWorkflowsActionsService } from '@dotcms/data-access'; +import { DotCMSWorkflowAction } from '@dotcms/dotcms-models'; + +import { UVE_STATUS } from '../../../shared/enums'; +import { UVEState } from '../../models'; + +interface WithWorkflowState { + workflowActions: DotCMSWorkflowAction[]; + workflowLoading: boolean; +} + +/** + * Add load and reload method to the store + * + * @export + * @return {*} + */ +export function withWorkflow() { + return signalStoreFeature( + { + state: type() + }, + withState({ + workflowActions: [], + workflowLoading: true + }), + withMethods((store) => { + const dotWorkflowsActionsService = inject(DotWorkflowsActionsService); + + return { + /** + * Load workflow actions + */ + getWorkflowActions: rxMethod( + pipe( + tap(() => { + patchState(store, { + workflowLoading: true + }); + }), + switchMap((inode) => { + const pageInode = inode || store.pageAPIResponse()?.page.inode; + + return dotWorkflowsActionsService.getByInode(pageInode).pipe( + tapResponse({ + next: (workflowActions = []) => { + patchState(store, { + workflowActions, + workflowLoading: false + }); + }, + error: ({ status: errorStatus }: HttpErrorResponse) => { + patchState(store, { + errorCode: errorStatus, + status: UVE_STATUS.ERROR + }); + } + }) + ); + }) + ) + ), + setWorkflowActionLoading: (loading: boolean) => { + patchState(store, { + workflowLoading: loading + }); + } + }; + }) + ); +} diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/models.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/models.ts index bb7eeb96926a..113a528e5bcc 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/models.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/models.ts @@ -1,5 +1,10 @@ import { CurrentUser } from '@dotcms/dotcms-js'; -import { DotExperiment, DotLanguage, DotPageToolUrlParams } from '@dotcms/dotcms-models'; +import { + DotCMSWorkflowAction, + DotExperiment, + DotLanguage, + DotPageToolUrlParams +} from '@dotcms/dotcms-models'; import { InfoPage } from '@dotcms/ui'; import { DotPageApiParams, DotPageApiResponse } from '../services/dot-page-api.service'; @@ -20,6 +25,7 @@ export interface UVEState { canEditPage: boolean; pageIsLocked: boolean; isClientReady: boolean; + workflowActions?: DotCMSWorkflowAction[]; } export interface ShellProps { diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts index 5f643314740a..fa42f0370dfe 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/utils/index.ts @@ -633,3 +633,19 @@ export function shouldNavigate(targetUrl: string | undefined, currentUrl: string // Navigate if the target URL is defined and different from the current URL return targetUrl !== undefined && !compareUrlPaths(targetUrl, currentUrl); } + +/** + * Get the page URI from the contentlet + * + * If the URL_MAP_FOR_CONTENT is present, it will be used as the page URI. + * + * @param {DotCMSContentlet} { urlContentMap, pageURI, url} + * @return {*} {string} + */ +export const getPageURI = ({ urlContentMap, pageURI, url }: DotCMSContentlet): string => { + const contentMapUrl = urlContentMap?.URL_MAP_FOR_CONTENT; + const pageURIUrl = pageURI ?? url; + const newUrl = contentMapUrl ?? pageURIUrl; + + return sanitizeURL(newUrl); +}; diff --git a/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.html b/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.html index ed2afd75d23d..675cf6e66225 100644 --- a/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.html +++ b/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.html @@ -4,7 +4,7 @@ @if (subActions.length) { } diff --git a/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.spec.ts index 2db6fd164fd9..af3a5a59f419 100644 --- a/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.spec.ts @@ -189,6 +189,37 @@ describe('DotWorkflowActionsComponent', () => { }); }); + describe('disabled', () => { + beforeEach(() => { + spectator.setInput('actions', [ + ...WORKFLOW_ACTIONS_MOCK, + WORKFLOW_ACTIONS_SEPARATOR_MOCK, + WORKFLOW_ACTIONS_MOCK[0] + ]); + spectator.detectChanges(); + }); + + it('should disable the button', () => { + const button = spectator.query(Button); + expect(button.disabled).toBeFalsy(); + + spectator.setInput('disabled', true); + spectator.detectChanges(); + + expect(button.disabled).toBeTruthy(); + }); + + it('should disabled split buttons ', () => { + const splitButton = spectator.query(SplitButton); + expect(splitButton.disabled).toBeFalsy(); + + spectator.setInput('disabled', true); + spectator.detectChanges(); + + expect(splitButton.disabled).toBeTruthy(); + }); + }); + describe('size', () => { beforeEach(() => { spectator.setInput('actions', [ diff --git a/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.ts b/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.ts index 9d07764d0d75..3f1d20265c4a 100644 --- a/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-workflow-actions/dot-workflow-actions.component.ts @@ -51,6 +51,12 @@ export class DotWorkflowActionsComponent implements OnChanges { * @memberof DotWorkflowActionsComponent */ loading = input(false); + /** + * Disable the actions + * + * @memberof DotWorkflowActionsComponent + */ + disabled = input(false); /** * Group the actions by separator * diff --git a/dotCMS/src/main/webapp/html/portlet/ext/contentlet/edit_contentlet_js_inc.jsp b/dotCMS/src/main/webapp/html/portlet/ext/contentlet/edit_contentlet_js_inc.jsp index 36f3146084b9..26ba7e0d63a7 100644 --- a/dotCMS/src/main/webapp/html/portlet/ext/contentlet/edit_contentlet_js_inc.jsp +++ b/dotCMS/src/main/webapp/html/portlet/ext/contentlet/edit_contentlet_js_inc.jsp @@ -26,6 +26,14 @@ let variantNameParam = "<%=variantNameParam%>"; let contentletVariantId = "<%=contentlet.getVariantId()%>"; + /* + * Ajax Methods don't wait until the reindex in completed. + * We need to wait for the reindex when we edit in order to reload the page + * Maybe we can avoid this after this is merged: https://github.com/dotCMS/core/pull/30110 + * More info: https://github.com/dotCMS/core/issues/30218 + */ + const AjaxWFReindexDelay = 500; + // If the contentlet variantName is not default, it doesn't matter if we are in a variant or not, // we use the contentlet variantName to keep the consistency in the actions (false && short-circuit) @@ -65,12 +73,6 @@ } } - - - - - - var myForm = document.getElementById('fm'); var copyAsset = false; @@ -106,9 +108,6 @@ } }; dojo.xhrGet(xhrArgs); - - - } } function selectVersion(objId) { @@ -169,9 +168,6 @@ } } - - - //Structure change function structureSelected() { @@ -233,14 +229,10 @@ return loc; } - - function addTab(tabid){ tabsArray.push(tabid); } - - function submitParent(param) { if (copyAsset) { disableButtons(myForm); @@ -270,15 +262,12 @@ } } - - <% if(Config.getIntProperty("CONTENT_AUTOSAVE_INTERVAL",0) > 0){%> // http://jira.dotmarketing.net/browse/DOTCMS-2273 var autoSaveInterval = <%= Config.getIntProperty("CONTENT_AUTOSAVE_INTERVAL",0) %>; setInterval("saveContent(true)",autoSaveInterval); <%}%> - function getFormData(formId,nameValueSeparator){ // Returns form data as name value pairs with nameValueSeparator. var formData = new Array(); @@ -349,10 +338,8 @@ } // Categories selected in the Category Dialog - var catCount = <%=UtilMethods.isSet(catCount)?Integer.parseInt(catCount):0 %>; - for(var i=1; i 0) ? currentContentletInode @@ -940,16 +910,22 @@ // END: PUSH PUBLISHING ACTIONLET saveContent(false); - } var contentAdmin = new dotcms.dijit.contentlet.ContentAdmin('<%= contentlet.getIdentifier() %>','<%= contentlet.getInode() %>','<%= contentlet.getLanguageId() %>'); - function makeEditable(contentletInode){ + function dispatchCustomEvent(detail) { + setTimeout(() => { + var customEvent = document.createEvent('CustomEvent'); + customEvent.initCustomEvent('ng-event', false, false, detail); + document.dispatchEvent(customEvent); + }, AjaxWFReindexDelay); + } + + function makeEditable(contentletInode){ ContentletAjax.lockContent(contentletInode, checkoutContentletCallback); dojo.empty("contentletActionsHanger"); dojo.byId("contentletActionsHanger").innerHTML="
"; - } function checkoutContentletCallback(data){ @@ -959,8 +935,11 @@ } + const eventData = { + name: 'update-workflow-action' + }; + dispatchCustomEvent(eventData) refreshActionPanel(data["lockedIdent"]); - } @@ -975,23 +954,24 @@ return; } + const eventData = { + name: 'update-workflow-action' + }; + dispatchCustomEvent(eventData) refreshActionPanel(data["lockedIdent"]); - } - - function unlockContent(contentletInode){ - window.onbeforeunload=true; + const eventData = { + name: 'update-workflow-action' + }; + dispatchCustomEvent(eventData) ContentletAjax.unlockContent(contentletInode, unlockContentCallback); - //dojo.empty("contentletActionsHanger"); - //dojo.byId("contentletActionsHanger").innerHTML="
"; - } @@ -1003,8 +983,6 @@ } refreshActionPanel(data["lockedIdent"]); - - } @@ -1065,6 +1043,4 @@ } } - -