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/java/com/dotcms/cache/DynamicTTLCache.java b/dotCMS/src/main/java/com/dotcms/cache/DynamicTTLCache.java
index a9c4f29ade68..3966996dc7b9 100644
--- a/dotCMS/src/main/java/com/dotcms/cache/DynamicTTLCache.java
+++ b/dotCMS/src/main/java/com/dotcms/cache/DynamicTTLCache.java
@@ -47,6 +47,7 @@ public long expireAfterRead(K key, CacheValue value, long currentTime, long curr
return currentDuration;
}
})
+ .recordStats()
.maximumSize(maxCapacity)
.build();
}
diff --git a/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java b/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java
index 09f233bc0120..d6ceb97dd977 100644
--- a/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java
+++ b/dotCMS/src/main/java/com/dotcms/content/elasticsearch/business/ESContentletAPIImpl.java
@@ -686,6 +686,10 @@ public Contentlet findContentletByIdentifier(final String identifier, final long
final Date timeMachineDate, final User user, final boolean respectFrontendRoles)
throws DotDataException, DotSecurityException, DotContentletStateException{
final Contentlet contentlet = contentFactory.findContentletByIdentifier(identifier, languageId, variantId, timeMachineDate);
+ if (contentlet == null) {
+ Logger.debug(this, "Contentlet not found for identifier: " + identifier + " lang:" + languageId + " variant:" + variantId + " date:" + timeMachineDate);
+ return null;
+ }
if (permissionAPI.doesUserHavePermission(contentlet, PermissionAPI.PERMISSION_READ, user, respectFrontendRoles)) {
return contentlet;
} else {
@@ -7671,6 +7675,10 @@ public void validateContentlet(final Contentlet contentlet, final List
// validate unique
if (field.isUnique()) {
try {
+ if (!UtilMethods.isSet(contentlet.getHost())) {
+ populateHost(contentlet);
+ }
+
uniqueFieldValidationStrategyResolver.get().get().validate(contentlet,
LegacyFieldTransformer.from(field));
} catch (final UniqueFieldValueDuplicatedException e) {
diff --git a/dotCMS/src/main/java/com/dotcms/graphql/business/PageAPIGraphQLFieldsProvider.java b/dotCMS/src/main/java/com/dotcms/graphql/business/PageAPIGraphQLFieldsProvider.java
index 8d17d5ff43a5..36fa9bff8e3b 100644
--- a/dotCMS/src/main/java/com/dotcms/graphql/business/PageAPIGraphQLFieldsProvider.java
+++ b/dotCMS/src/main/java/com/dotcms/graphql/business/PageAPIGraphQLFieldsProvider.java
@@ -50,6 +50,10 @@ public Collection getFields() throws DotDataException {
.name("site")
.type(GraphQLString)
.build())
+ .argument(GraphQLArgument.newArgument() //This is time machine
+ .name("publishDate")
+ .type(GraphQLString)
+ .build())
.type(PageAPIGraphQLTypesProvider.INSTANCE.getTypesMap().get(DOT_PAGE))
.dataFetcher(new PageDataFetcher()).build());
}
diff --git a/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/ContainersDataFetcher.java b/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/ContainersDataFetcher.java
index 1ea5b74a62a7..84e6d17e4818 100644
--- a/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/ContainersDataFetcher.java
+++ b/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/ContainersDataFetcher.java
@@ -14,7 +14,6 @@
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.List;
-import javax.servlet.http.HttpServletRequest;
/**
* This DataFetcher returns the {@link TemplateLayout} associated to the requested {@link HTMLPageAsset}.
@@ -31,8 +30,6 @@ public List get(final DataFetchingEnvironment environment) throws
final String languageId = (String) context.getParam("languageId");
final PageMode mode = PageMode.get(pageModeAsString);
- final HttpServletRequest request = context.getHttpServletRequest();
-
final HTMLPageAsset pageAsset = APILocator.getHTMLPageAssetAPI()
.fromContentlet(page);
diff --git a/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/PageDataFetcher.java b/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/PageDataFetcher.java
index 719e99bc1c44..a885e60fb2d5 100644
--- a/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/PageDataFetcher.java
+++ b/dotCMS/src/main/java/com/dotcms/graphql/datafetcher/page/PageDataFetcher.java
@@ -2,6 +2,8 @@
import com.dotcms.graphql.DotGraphQLContext;
import com.dotcms.graphql.exception.PermissionDeniedGraphQLException;
+import com.dotcms.rest.api.v1.page.PageResource;
+import com.dotcms.variant.VariantAPI;
import com.dotmarketing.beans.Host;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.exception.DotSecurityException;
@@ -15,6 +17,7 @@
import com.dotmarketing.portlets.htmlpageasset.model.HTMLPageAsset;
import com.dotmarketing.portlets.rules.business.RulesEngine;
import com.dotmarketing.portlets.rules.model.Rule.FireOn;
+import com.dotmarketing.util.DateUtil;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.PageMode;
import com.dotmarketing.util.UtilMethods;
@@ -22,6 +25,9 @@
import com.liferay.portal.model.User;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
+import io.vavr.control.Try;
+import java.time.Instant;
+import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -53,6 +59,7 @@ public Contentlet get(final DataFetchingEnvironment environment) throws Exceptio
final boolean fireRules = environment.getArgument("fireRules");
final String persona = environment.getArgument("persona");
final String site = environment.getArgument("site");
+ final String publishDate = environment.getArgument("publishDate");
context.addParam("url", url);
context.addParam("languageId", languageId);
@@ -60,6 +67,7 @@ public Contentlet get(final DataFetchingEnvironment environment) throws Exceptio
context.addParam("fireRules", fireRules);
context.addParam("persona", persona);
context.addParam("site", site);
+ context.addParam("publishDate", publishDate);
final PageMode mode = PageMode.get(pageModeAsString);
PageMode.setPageMode(request, mode);
@@ -77,6 +85,22 @@ public Contentlet get(final DataFetchingEnvironment environment) throws Exceptio
request.setAttribute(Host.HOST_VELOCITY_VAR_NAME, site);
}
+ Date publishDateObj = null;
+
+ if(UtilMethods.isSet(publishDate)) {
+ publishDateObj = Try.of(()-> DateUtil.convertDate(publishDate)).getOrElse(() -> {
+ Logger.error(this, "Invalid publish date: " + publishDate);
+ return null;
+ });
+ if(null != publishDateObj) {
+ //We get a valid time machine date
+ final Instant instant = publishDateObj.toInstant();
+ final long epochMilli = instant.toEpochMilli();
+ context.addParam(PageResource.TM_DATE, epochMilli);
+ request.setAttribute(PageResource.TM_DATE, epochMilli);
+ }
+ }
+
Logger.debug(this, ()-> "Fetching page for URL: " + url);
final PageContext pageContext = PageContextBuilder.builder()
diff --git a/dotCMS/src/main/java/com/dotcms/mock/request/MockParameterRequest.java b/dotCMS/src/main/java/com/dotcms/mock/request/MockParameterRequest.java
index a8ffe21b4d0f..1560b22b9701 100644
--- a/dotCMS/src/main/java/com/dotcms/mock/request/MockParameterRequest.java
+++ b/dotCMS/src/main/java/com/dotcms/mock/request/MockParameterRequest.java
@@ -1,17 +1,18 @@
package com.dotcms.mock.request;
-import java.nio.charset.Charset;
+import com.google.common.collect.ImmutableMap;
+import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Vector;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
-import com.dotcms.repackage.com.google.common.collect.ImmutableMap;
/**
* Mock Request Parameter using a Request Wrapper. Part of the work to be
@@ -27,8 +28,7 @@ public MockParameterRequest(HttpServletRequest request) {
public MockParameterRequest(HttpServletRequest request, Map setMe) {
super(request);
HashMap mutable = new HashMap<>();
-
- List additional = URLEncodedUtils.parse(request.getQueryString(), Charset.forName("UTF-8"));
+ List additional = URLEncodedUtils.parse(request.getQueryString(), StandardCharsets.UTF_8);
for(NameValuePair nvp : additional) {
mutable.put(nvp.getName(),nvp.getValue());
}
@@ -40,7 +40,7 @@ public MockParameterRequest(HttpServletRequest request, Map setM
mutable.put(key, request.getParameter(key));
}
mutable.putAll(setMe);
-
+ mutable.values().removeIf(Objects::isNull);
params = ImmutableMap.copyOf(mutable);
}
diff --git a/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/PageRenderUtil.java b/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/PageRenderUtil.java
index 14c2eb885f77..d1daac3664c2 100644
--- a/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/PageRenderUtil.java
+++ b/dotCMS/src/main/java/com/dotcms/rendering/velocity/services/PageRenderUtil.java
@@ -356,27 +356,27 @@ private Optional timeMachineDate(final HttpServletRequest request) {
return Optional.empty();
}
- Optional millis = Optional.empty();
+ Optional