From 4e846778482f13082eee74722b9db76637974220 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Tue, 10 Sep 2024 10:57:00 -0400 Subject: [PATCH] fix(UVE): unable to execute sub-action on pages (#29932) ### Proposed Changes * Let users execute `sub-actions` on pages ### Checklist - [x] Tests ### Why was this error happening? It seems like an incompatibility between Angular's `SlicePipe` and PrimeNg v17 A general guess, `SlicePipe` is not a Pure Pipe and has Angular's own logic implemented inside that doesn't work like a native slice, which makes it inconsistent with what one would expect it to work like by @zJaaal #### Test the `Slice` pipe with PrimeNg `v15` and `v17` - PrimeNG v15 - slice [demo](https://stackblitz.com/edit/xad6jf?file=src%2Fapp%2Fdemo%2Fsplit-button-basic-demo.ts,src%2Fapp%2Fdemo%2Fsplit-button-basic-demo.html) - PrimeNG v17 - slice [demo](https://stackblitz.com/edit/ixjqxa?file=src%2Fapp%2Fsplit-button-basic-demo.ts) ### Video https://github.com/user-attachments/assets/2ae68864-155c-4c53-903f-7f233d8021af --- .../dot-edit-content-toolbar.component.html | 2 +- ...dot-edit-content-toolbar.component.spec.ts | 6 +- ...dit-ema-workflow-actions.component.spec.ts | 8 +- .../dot-workflow-actions.component.html | 26 ++-- .../dot-workflow-actions.component.spec.ts | 4 +- .../dot-workflow-actions.component.ts | 138 ++++++++++++------ 6 files changed, 120 insertions(+), 64 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-toolbar/dot-edit-content-toolbar.component.html b/core-web/libs/edit-content/src/lib/components/dot-edit-content-toolbar/dot-edit-content-toolbar.component.html index 62b26396e014..85a2b99f479e 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-toolbar/dot-edit-content-toolbar.component.html +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-toolbar/dot-edit-content-toolbar.component.html @@ -4,6 +4,6 @@ + [groupActions]="true" /> diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-toolbar/dot-edit-content-toolbar.component.spec.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-toolbar/dot-edit-content-toolbar.component.spec.ts index a1baa59fa7f1..1a1e14b5a15c 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-toolbar/dot-edit-content-toolbar.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-toolbar/dot-edit-content-toolbar.component.spec.ts @@ -30,9 +30,9 @@ describe('DotEditContentToolbarComponent', () => { it('should dot-workflow-actions component with the correct input', () => { const component = spectator.query(DotWorkflowActionsComponent); expect(component).toBeTruthy(); - expect(component.actions).toEqual(WORKFLOW_ACTIONS_MOCK); - expect(component.groupAction).toBeTruthy(); - expect(component.size).toBe('normal'); + expect(component.actions()).toEqual(WORKFLOW_ACTIONS_MOCK); + expect(component.groupActions()).toBeTruthy(); + expect(component.size()).toBe('normal'); }); it('should emit the action dot-workflow-actions emits the fired action', () => { 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-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.spec.ts index 0a8d53a5be43..58d02b6a3d42 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-edit-ema-workflow-actions/dot-edit-ema-workflow-actions.component.spec.ts @@ -135,9 +135,9 @@ describe('DotEditEmaWorkflowActionsComponent', () => { 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'); + expect(dotWorkflowActionsComponent.actions()).toEqual([]); + expect(dotWorkflowActionsComponent.loading()).toBeTruthy(); + expect(dotWorkflowActionsComponent.size()).toBe('small'); }); }); @@ -155,7 +155,7 @@ describe('DotEditEmaWorkflowActionsComponent', () => { const dotWorkflowActionsComponent = spectator.query(DotWorkflowActionsComponent); expect(dotWorkflowsActionsService.getByInode).toHaveBeenCalledWith('123'); - expect(dotWorkflowActionsComponent.actions).toEqual(mockWorkflowsActions); + expect(dotWorkflowActionsComponent.actions()).toEqual(mockWorkflowsActions); }); it('should fire workflow actions when it does not have inputs', () => { 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 7bae7ce6fe0b..ed2afd75d23d 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 @@ -1,24 +1,26 @@ -@for (action of groupedActions(); track $index) { - @if (action.length > 1) { +@for (action of $groupedActions(); track $index) { + @let mainAction = action.mainAction; + @let subActions = action.subActions; + @if (subActions.length) { + [label]="mainAction.label" /> } @else { + [label]="mainAction.label" /> } } @empty { } 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 728fb8c12d61..2db6fd164fd9 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 @@ -82,7 +82,7 @@ describe('DotWorkflowActionsComponent', () => { spectator = createComponent({ props: { actions: WORKFLOW_ACTIONS_MOCK, - groupAction: true, + groupActions: true, loading: false, size: 'normal' } @@ -146,7 +146,7 @@ describe('DotWorkflowActionsComponent', () => { describe('not group action', () => { beforeEach(() => { - spectator.setInput('groupAction', false); + spectator.setInput('groupActions', false); spectator.detectComponentChanges(); }); 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 c65b85ca9a0b..9d07764d0d75 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 @@ -2,10 +2,9 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, - EventEmitter, - Input, + input, OnChanges, - Output, + output, signal } from '@angular/core'; @@ -13,7 +12,7 @@ import { MenuItem } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { SplitButtonModule } from 'primeng/splitbutton'; -import { CustomMenuItem, DotCMSActionSubtype, DotCMSWorkflowAction } from '@dotcms/dotcms-models'; +import { DotCMSActionSubtype, DotCMSWorkflowAction } from '@dotcms/dotcms-models'; import { DotMessagePipe } from '../../dot-message/dot-message.pipe'; @@ -25,6 +24,11 @@ const InplaceButtonSizePrimeNg: Record = { large: 'p-button-lg' }; +interface WorkflowActionsGroup { + mainAction: MenuItem; + subActions: MenuItem[]; +} + @Component({ selector: 'dot-workflow-actions', standalone: true, @@ -34,71 +38,121 @@ const InplaceButtonSizePrimeNg: Record = { changeDetection: ChangeDetectionStrategy.OnPush }) export class DotWorkflowActionsComponent implements OnChanges { - @Input({ required: true }) actions: DotCMSWorkflowAction[]; - @Input() loading = false; - @Input() groupAction = false; - @Input() size: ButtonSize = 'normal'; - @Output() actionFired = new EventEmitter(); + /** + * List of actions to display + * + * @type {DotCMSWorkflowAction[]} + * @memberof DotWorkflowActionsComponent + */ + actions = input.required(); + /** + * Show a loading button spinner + * + * @memberof DotWorkflowActionsComponent + */ + loading = input(false); + /** + * Group the actions by separator + * + * @memberof DotWorkflowActionsComponent + */ + groupActions = input(false); + /** + * Button size + * + * @type {ButtonSize} + * @memberof DotWorkflowActionsComponent + */ + size = input('normal'); + /** + * Emits when an action is selected + * + * @memberof DotWorkflowActionsComponent + */ + actionFired = output(); - protected groupedActions = signal([]); - protected sizeClass: string; + protected $groupedActions = signal([]); + protected sizeClass!: string; ngOnChanges(): void { - this.groupedActions.set( - this.groupAction ? this.groupActions(this.actions) : this.formatActions(this.actions) - ); - this.sizeClass = InplaceButtonSizePrimeNg[this.size]; + this.sizeClass = InplaceButtonSizePrimeNg[this.size()]; + + if (!this.actions().length) { + this.$groupedActions.set([]); + + return; + } + + this.setActions(); + } + + /** + * Set the actions to display + * + * @private + * @memberof DotWorkflowActionsComponent + */ + private setActions(): void { + const groups = this.createGroups(this.actions()); + const actions = groups.map((group) => { + const [first, ...rest] = group; + const mainAction = this.createActions(first); + const subActions = rest.map((action) => this.createActions(action)); + + return { mainAction, subActions }; + }); + + this.$groupedActions.set(actions); } /** - * Group actions by separator + * Create the groups of actions + * Each group is create when a separator is found * * @private - * @param {DotCMSWorkflowAction[]} [actions=[]] - * @return {*} {MenuItem[][]} + * @param {DotCMSWorkflowAction[]} actions + * @return {*} {DotCMSWorkflowAction[][]} * @memberof DotWorkflowActionsComponent */ - private groupActions(actions: DotCMSWorkflowAction[] = []): MenuItem[][] { + private createGroups(actions: DotCMSWorkflowAction[]): DotCMSWorkflowAction[][] { + if (!this.groupActions()) { + // Remove the separator from the actions and return the actions grouped + const formatActions = actions.filter( + (action) => action?.metadata?.subtype !== DotCMSActionSubtype.SEPARATOR + ); + + return [formatActions].filter((group) => !!group.length); + } + + // Create a new group every time we find a separator return actions - ?.reduce( + .reduce( (acc, action) => { if (action?.metadata?.subtype === DotCMSActionSubtype.SEPARATOR) { acc.push([]); } else { - acc[acc.length - 1].push({ - label: action.name, - command: () => this.actionFired.emit(action) - }); + acc[acc.length - 1].push(action); } return acc; }, [[]] ) - .filter((group) => group.length); + .filter((group) => !!group.length); } /** - * Remove the separator from the actions and return the actions grouped - * in a single group. + * Create the action menu item * * @private - * @param {DotCMSWorkflowAction[]} [actions=[]] - * @return {*} {MenuItem[][]} + * @param {DotCMSWorkflowAction} action + * @return {*} {MenuItem} * @memberof DotWorkflowActionsComponent */ - private formatActions(actions: DotCMSWorkflowAction[] = []): CustomMenuItem[][] { - const formatedActions = actions?.reduce((acc, action) => { - if (action?.metadata?.subtype !== DotCMSActionSubtype.SEPARATOR) { - acc.push({ - label: action.name, - command: () => this.actionFired.emit(action) - }); - } - - return acc; - }, []); - - return [formatedActions].filter((group) => group.length); + private createActions(action: DotCMSWorkflowAction): MenuItem { + return { + label: action.name, + command: () => this.actionFired.emit(action) + }; } }