Skip to content

Commit d74bc4c

Browse files
rjvelazcozJaaal
andauthored
feat(uve): request page using custom query (dotCMS#31958)
This pull request introduces several updates to enhance the handling of workflow actions, streamline state management, and improve error handling in the `edit-ema` portlet. The changes primarily focus on leveraging `@ngrx/signals` hooks for lifecycle management, simplifying logic in the `withLoad` and `withWorkflow` features, and improving type safety and error handling. ### Workflow and State Management Enhancements: * **Refactored `getWorkflowActions` to require a `string` parameter**: This change ensures that the workflow actions are explicitly tied to a specific page inode, improving type safety and reducing ambiguity. * **Introduced `withHooks` for lifecycle management**: Added an `onInit` hook to automatically fetch workflow actions when the page data is available, centralizing initialization logic. ### Improvements to `withLoad` Feature: * **Simplified page loading logic**: Removed redundant calls to services like `DotLicenseService` and `LoginService` from the main `switchMap` pipeline and moved them to `withHooks`. This improves readability and separates concerns. [[1]](diffhunk://#diff-0cbc04b5047422551dde0970e4ea1d0e1fcce3d9d8f4e0b4612fbc30b1658487L40-L42) [[2]](diffhunk://#diff-0cbc04b5047422551dde0970e4ea1d0e1fcce3d9d8f4e0b4612fbc30b1658487R170-R219) * **Enhanced state updates**: Centralized state updates for `pageAPIResponse`, `languages`, and other properties using `patchState` within streamlined effects, making the code more maintainable. [[1]](diffhunk://#diff-0cbc04b5047422551dde0970e4ea1d0e1fcce3d9d8f4e0b4612fbc30b1658487L72-R111) [[2]](diffhunk://#diff-0cbc04b5047422551dde0970e4ea1d0e1fcce3d9d8f4e0b4612fbc30b1658487L188-R157) ### Error Handling and Type Safety: * **Centralized error handling**: Improved error handling by consolidating `catchError` logic, ensuring consistent error state updates across the application. * **Added null checks for optional properties**: Updated utility functions like `computeCanEditPage` to safely handle optional properties, preventing potential runtime errors. ### Dependency Updates: * **Introduced `effect` from `@angular/core`**: Used the `effect` API to manage side effects, such as updating state based on computed properties, within `withHooks`. [[1]](diffhunk://#diff-0cbc04b5047422551dde0970e4ea1d0e1fcce3d9d8f4e0b4612fbc30b1658487R170-R219) [[2]](diffhunk://#diff-cec02f183ef67c00ba5b9e441d0fdd5875782f6fa1d7cd900679438c7297de44L73-R94) * **Removed unused dependencies**: Cleaned up imports by removing unused services and operators like `forkJoin` and `LoginService`. ### Page API Example https://github.com/user-attachments/assets/a211884c-d754-4886-81ac-9273c38427fe ### GraphQL Example https://github.com/user-attachments/assets/8d47ffa2-baff-41d3-afd0-fa784a039f9c --------- Co-authored-by: Jalinson Diaz <[email protected]>
1 parent fa9281d commit d74bc4c

22 files changed

+398
-363
lines changed

core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,11 @@ describe('DotEmaShellComponent', () => {
259259
{
260260
provide: DotPageApiService,
261261
useValue: {
262-
get({ language_id }) {
262+
get({ language_id = 1 }) {
263263
return PAGE_RESPONSE_BY_LANGUAGE_ID[language_id] || of({});
264264
},
265-
getClientPage({ language_id }, _clientConfig) {
266-
return PAGE_RESPONSE_BY_LANGUAGE_ID[language_id] || of({});
265+
getGraphQLPage() {
266+
return of({});
267267
},
268268
save() {
269269
return of({});
@@ -922,9 +922,9 @@ describe('DotEmaShellComponent', () => {
922922
});
923923

924924
it('should trigger a store reload if the url is the same', () => {
925+
spectator.detectChanges();
925926
const spyReload = jest.spyOn(store, 'reloadCurrentPage');
926927
const spyLocation = jest.spyOn(location, 'go');
927-
spectator.detectChanges();
928928

929929
spectator.triggerEventHandler(
930930
DotEmaDialogComponent,

core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ export class DotEmaShellComponent implements OnInit {
131131
handleNgEvent({ event }: DialogAction) {
132132
switch (event.detail.name) {
133133
case NG_CUSTOM_EVENTS.UPDATE_WORKFLOW_ACTION: {
134-
this.uveStore.getWorkflowActions();
134+
const pageAPIResponse = this.uveStore.pageAPIResponse();
135+
this.uveStore.getWorkflowActions(pageAPIResponse.page.inode);
135136
break;
136137
}
137138

core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
data-testId="progress-bar"
5252
mode="indeterminate" />
5353
}
54-
@if ($editorProps().contentletTools; as contentletTools) {
54+
@if ($editorProps()?.contentletTools; as contentletTools) {
5555
<dot-ema-contentlet-tools
5656
(edit)="handleEditContentlet($event)"
5757
(editVTL)="handleEditVTL($event)"

core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.spec.ts

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { ConfirmationService, MessageService } from 'primeng/api';
1818
import { ConfirmDialogModule } from 'primeng/confirmdialog';
1919
import { DialogService } from 'primeng/dynamicdialog';
2020

21+
import { map } from 'rxjs/operators';
22+
2123
import { CLIENT_ACTIONS } from '@dotcms/client';
2224
import {
2325
DotAlertConfirmService,
@@ -282,13 +284,21 @@ const createRouting = () =>
282284
{
283285
provide: DotPageApiService,
284286
useValue: {
285-
get({ language_id }) {
286-
// We use the language_id to determine the response, use this to test different behaviors
287-
return UVE_PAGE_RESPONSE_MAP[language_id];
287+
get(data) {
288+
const { language_id = 1 } = data;
289+
290+
return UVE_PAGE_RESPONSE_MAP[language_id].pipe(
291+
map((page = {}) => ({
292+
// Update page to "fake" a new page and avoid reference issues
293+
...(page as object)
294+
}))
295+
);
288296
},
289-
getClientPage({ language_id }, _clientConfig) {
290-
// We use the language_id to determine the response, use this to test different behaviors
291-
return UVE_PAGE_RESPONSE_MAP[language_id];
297+
getGraphQLPage({ language_id = 1 }) {
298+
return of({
299+
page: UVE_PAGE_RESPONSE_MAP[language_id],
300+
content: {}
301+
});
292302
},
293303
save() {
294304
return of({});
@@ -599,6 +609,9 @@ describe('EditEmaEditorComponent', () => {
599609
});
600610

601611
describe('edit', () => {
612+
beforeEach(() => {
613+
store.setIsClientReady(true);
614+
});
602615
const baseContentletPayload = {
603616
x: 100,
604617
y: 100,
@@ -608,8 +621,8 @@ describe('EditEmaEditorComponent', () => {
608621
};
609622

610623
it('should edit urlContentMap page', () => {
624+
spectator.detectChanges();
611625
const dialog = spectator.query(DotEmaDialogComponent);
612-
613626
jest.spyOn(dialog, 'editUrlContentMapContentlet');
614627

615628
spectator.triggerEventHandler(DotUveToolbarComponent, 'editUrlContentMap', {
@@ -628,7 +641,6 @@ describe('EditEmaEditorComponent', () => {
628641

629642
it('should open a dialog and save after backend emit', (done) => {
630643
spectator.detectChanges();
631-
632644
const dialog = spectator.debugElement.query(
633645
By.css('[data-testId="ema-dialog"]')
634646
);
@@ -2950,15 +2962,12 @@ describe('EditEmaEditorComponent', () => {
29502962
describe('CUSTOMER ACTIONS', () => {
29512963
describe('CLIENT_READY', () => {
29522964
it('should set client GraphQL configuration and call the reload', () => {
2953-
const setClientConfigurationSpy = jest.spyOn(
2954-
store,
2955-
'setClientConfiguration'
2956-
);
2965+
const setClientConfigurationSpy = jest.spyOn(store, 'setCustomGraphQL');
29572966
const reloadSpy = jest.spyOn(store, 'reloadCurrentPage');
29582967

29592968
const config = {
2960-
params: {},
2961-
query: '{ query: { hello } }'
2969+
query: '{ query: { hello } }',
2970+
variables: undefined
29622971
};
29632972

29642973
window.dispatchEvent(
@@ -2971,23 +2980,15 @@ describe('EditEmaEditorComponent', () => {
29712980
})
29722981
);
29732982

2974-
expect(setClientConfigurationSpy).toHaveBeenCalledWith(config);
2983+
expect(setClientConfigurationSpy).toHaveBeenCalledWith(config, true);
29752984
expect(reloadSpy).toHaveBeenCalled();
29762985
});
29772986

2978-
it('should set client PAGEAPI configuration and call the reload', () => {
2979-
const setClientConfigurationSpy = jest.spyOn(
2980-
store,
2981-
'setClientConfiguration'
2982-
);
2987+
it('should set call reloadCurrentPage when client is ready', () => {
2988+
const setCustomGraphQLSpy = jest.spyOn(store, 'setCustomGraphQL');
29832989
const reloadSpy = jest.spyOn(store, 'reloadCurrentPage');
29842990

2985-
const config = {
2986-
params: {
2987-
depth: '1'
2988-
},
2989-
query: ''
2990-
};
2991+
const config = { params: { depth: '1' } };
29912992

29922993
window.dispatchEvent(
29932994
new MessageEvent('message', {
@@ -2999,7 +3000,7 @@ describe('EditEmaEditorComponent', () => {
29993000
})
30003001
);
30013002

3002-
expect(setClientConfigurationSpy).toHaveBeenCalledWith(config);
3003+
expect(setCustomGraphQLSpy).not.toHaveBeenCalled();
30033004
expect(reloadSpy).toHaveBeenCalled();
30043005
});
30053006
});
@@ -3015,6 +3016,8 @@ describe('EditEmaEditorComponent', () => {
30153016
});
30163017

30173018
const loadPageAssetSpy = jest.spyOn(store, 'loadPageAsset');
3019+
3020+
spectator.detectChanges();
30183021
const dialog = spectator.debugElement.query(
30193022
By.css('[data-testId="ema-dialog"]')
30203023
);
@@ -3056,12 +3059,11 @@ describe('EditEmaEditorComponent', () => {
30563059
});
30573060

30583061
const loadPageAssetSpy = jest.spyOn(store, 'loadPageAsset');
3062+
spectator.detectChanges();
3063+
30593064
const dialog = spectator.debugElement.query(
30603065
By.css('[data-testId="ema-dialog"]')
30613066
);
3062-
3063-
spectator.detectComponentChanges();
3064-
30653067
triggerCustomEvent(dialog, 'action', {
30663068
event: new CustomEvent('ng-event', {
30673069
detail: {

core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.ts

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ import {
8383
DotPage
8484
} from '../shared/models';
8585
import { UVEStore } from '../store/dot-uve.store';
86-
import { ClientRequestProps } from '../store/features/client/withClient';
8786
import {
8887
SDK_EDITOR_SCRIPT_SOURCE,
8988
TEMPORAL_DRAG_ITEM,
@@ -93,7 +92,8 @@ import {
9392
getDragItemData,
9493
insertContentletInContainer,
9594
getTargetUrl,
96-
shouldNavigate
95+
shouldNavigate,
96+
convertClientParamsToPageParams
9797
} from '../utils';
9898

9999
@Component({
@@ -180,7 +180,8 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy {
180180
* We should not depend on this `$reloadEditorContent` computed to `resetEditorProperties` or `resetDialog`
181181
* This depends on the `code` with each the page renders code. This reset should be done in `widthLoad` signal feature but we can't do it yet
182182
*/
183-
const { isTraditionalPage, isClientReady } = this.uveStore.$reloadEditorContent();
183+
const { isTraditionalPage } = this.uveStore.$reloadEditorContent();
184+
const isClientReady = untracked(() => this.uveStore.isClientReady());
184185

185186
untracked(() => {
186187
this.uveStore.resetEditorProperties();
@@ -731,7 +732,13 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy {
731732
return;
732733
}
733734

734-
if (clientAction === CLIENT_ACTIONS.EDIT_CONTENTLET) {
735+
const graphql = this.uveStore.graphql();
736+
737+
if (!actionPayload || graphql) {
738+
this.uveStore.reloadCurrentPage();
739+
740+
return;
741+
} else if (clientAction === CLIENT_ACTIONS.EDIT_CONTENTLET) {
735742
this.contentWindow?.postMessage(
736743
{
737744
name: __DOTCMS_UVE_EVENT__.UVE_RELOAD_PAGE
@@ -740,12 +747,6 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy {
740747
);
741748
}
742749

743-
if (!actionPayload) {
744-
this.uveStore.reloadCurrentPage();
745-
746-
return;
747-
}
748-
749750
const { pageContainers, didInsert } = insertContentletInContainer({
750751
...actionPayload,
751752
newContentletId: contentletIdentifier
@@ -1002,18 +1003,25 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy {
10021003
)
10031004
.subscribe(() => this.uveStore.reloadCurrentPage());
10041005
},
1005-
[CLIENT_ACTIONS.CLIENT_READY]: (clientConfig: ClientRequestProps) => {
1006-
const { query, params } = clientConfig || {};
1006+
[CLIENT_ACTIONS.CLIENT_READY]: (devConfig) => {
10071007
const isClientReady = this.uveStore.isClientReady();
10081008

1009-
// Frameworks Navigation triggers the client ready event, so we need to prevent it
1010-
// Until we manually trigger the reload
1009+
// console.log('isClientReady', isClientReady);
10111010
if (isClientReady) {
10121011
return;
10131012
}
10141013

1015-
this.uveStore.setClientConfiguration({ query, params });
1016-
this.uveStore.reloadCurrentPage();
1014+
const { graphql, params, query: rawQuery } = devConfig || {};
1015+
const { query = rawQuery, variables } = graphql || {};
1016+
const legacyGraphqlResponse = !!rawQuery;
1017+
1018+
if (query || rawQuery) {
1019+
this.uveStore.setCustomGraphQL({ query, variables }, legacyGraphqlResponse);
1020+
}
1021+
1022+
const pageParams = convertClientParamsToPageParams(params);
1023+
this.uveStore.reloadCurrentPage(pageParams);
1024+
this.uveStore.setIsClientReady(true);
10171025
},
10181026
[CLIENT_ACTIONS.EDIT_CONTENTLET]: (contentlet: DotCMSContentlet) => {
10191027
this.dialog.editContentlet({ ...contentlet, clientAction: action });
@@ -1051,7 +1059,7 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy {
10511059
this.iframe?.nativeElement?.contentWindow?.postMessage(
10521060
{
10531061
name: __DOTCMS_UVE_EVENT__.UVE_SET_PAGE_DATA,
1054-
payload: this.uveStore.pageAPIResponse()
1062+
payload: this.#clientPayload()
10551063
},
10561064
this.host
10571065
);
@@ -1455,4 +1463,17 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy {
14551463
this.uveStore.setOGTagResults(results);
14561464
});
14571465
}
1466+
1467+
#clientPayload() {
1468+
const graphqlResponse = this.uveStore.$customGraphqlResponse();
1469+
1470+
if (graphqlResponse) {
1471+
return graphqlResponse;
1472+
}
1473+
1474+
return {
1475+
...this.uveStore.pageAPIResponse(),
1476+
params: this.uveStore.pageParams()
1477+
};
1478+
}
14581479
}

core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-layout/edit-ema-layout.component.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ describe('EditEmaLayoutComponent', () => {
106106
save: jest.fn(() => of(PAGE_RESPONSE))
107107
}),
108108
mockProvider(DotPageApiService, {
109-
get: jest.fn(() => of(PAGE_RESPONSE)),
110-
getClientPage: jest.fn(() => of(PAGE_RESPONSE))
109+
get: jest.fn(() => of(PAGE_RESPONSE))
111110
}),
112111
mockProvider(DotWorkflowsActionsService, {
113112
getByInode: jest.fn(() => of([]))

core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-layout/edit-ema-layout.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ export class EditEmaLayoutComponent implements OnInit, OnDestroy {
170170
summary: 'Success',
171171
detail: this.dotMessageService.get('dot.common.message.saved')
172172
});
173-
this.uveStore.reloadCurrentPage({ isClientReady: false });
173+
this.uveStore.reloadCurrentPage();
174+
this.uveStore.setIsClientReady(false);
174175
}
175176

176177
/**

core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.spec.ts

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -119,18 +119,6 @@ describe('DotPageApiService', () => {
119119
);
120120
});
121121

122-
it('should get the page using graphql', () => {
123-
const query = 'query { ... }';
124-
spectator.service.getGraphQLPage(query).subscribe();
125-
126-
const { request } = spectator.expectOne('/api/v1/graphql', HttpMethod.POST);
127-
const requestHeaders = request.headers;
128-
129-
expect(request.body).toEqual({ query });
130-
expect(requestHeaders.get('dotcachettl')).toBe('0');
131-
expect(requestHeaders.get('Content-Type')).toEqual('application/json');
132-
});
133-
134122
describe('editMode', () => {
135123
const BASE_URL =
136124
'/api/v1/page/render/test-url?language_id=en&com.dotmarketing.persona.id=modes.persona.no.persona';
@@ -189,39 +177,17 @@ describe('DotPageApiService', () => {
189177
});
190178
});
191179

192-
describe('getClientPage', () => {
193-
const baseParams = {
194-
url: '///test-url',
195-
mode: UVE_MODE.EDIT,
196-
language_id: 'en',
197-
[PERSONA_KEY]: 'modes.persona.no.persona'
198-
};
199-
180+
describe('getGraphQLPage', () => {
200181
it('should get the page using graphql if the client send a query', () => {
201182
const query = 'query { ... }';
202-
spectator.service.getClientPage(baseParams, { query }).subscribe();
183+
spectator.service.getGraphQLPage({ query, variables: {} }).subscribe();
203184

204185
const { request } = spectator.expectOne('/api/v1/graphql', HttpMethod.POST);
205186
const requestHeaders = request.headers;
206187

207-
expect(request.body).toEqual({ query });
188+
expect(request.body).toEqual({ query, variables: {} });
208189
expect(requestHeaders.get('dotcachettl')).toBe('0');
209190
expect(requestHeaders.get('Content-Type')).toEqual('application/json');
210191
});
211-
212-
it('should get the page using the page api if the client does not send a query', () => {
213-
spectator.service
214-
.getClientPage(baseParams, {
215-
params: {
216-
depth: '1'
217-
}
218-
})
219-
.subscribe();
220-
221-
spectator.expectOne(
222-
'/api/v1/page/render/test-url?depth=1&mode=EDIT_MODE&language_id=en&com.dotmarketing.persona.id=modes.persona.no.persona',
223-
HttpMethod.GET
224-
);
225-
});
226192
});
227193
});

0 commit comments

Comments
 (0)