Skip to content

Commit

Permalink
fix(UVE): unable to execute sub-action on pages (#29932)
Browse files Browse the repository at this point in the history
### 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
  • Loading branch information
rjvelazco authored Sep 10, 2024
1 parent c50d421 commit 4e84677
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<dot-workflow-actions
(actionFired)="actionFired.emit($event)"
[actions]="actions"
[groupAction]="true" />
[groupActions]="true" />
</div>
</p-toolbar>
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});

Expand All @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
<p-splitButton
(onClick)="action[0].command({ originalEvent: $event })"
[disabled]="loading"
(onClick)="mainAction?.command({ originalEvent: $event })"
[disabled]="loading()"
[styleClass]="sizeClass"
[model]="action | slice: 1"
[model]="subActions"
[outlined]="!$first"
[label]="action[0].label" />
[label]="mainAction.label" />
} @else {
<p-button
(onClick)="action[0].command({ originalEvent: $event })"
[loading]="loading"
(onClick)="mainAction?.command({ originalEvent: $event })"
[loading]="loading()"
[styleClass]="sizeClass + (!$first ? ' p-button-outlined' : '')"
[label]="action[0].label" />
[label]="mainAction.label" />
}
} @empty {
<p-button
[loading]="loading"
[disabled]="!loading"
[loading]="loading()"
[disabled]="!loading()"
[styleClass]="sizeClass"
[label]="(loading ? 'Loading' : 'edit.ema.page.no.workflow.action') | dm"
[label]="(loading() ? 'Loading' : 'edit.ema.page.no.workflow.action') | dm"
data-testId="empty-button" />
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('DotWorkflowActionsComponent', () => {
spectator = createComponent({
props: {
actions: WORKFLOW_ACTIONS_MOCK,
groupAction: true,
groupActions: true,
loading: false,
size: 'normal'
}
Expand Down Expand Up @@ -146,7 +146,7 @@ describe('DotWorkflowActionsComponent', () => {

describe('not group action', () => {
beforeEach(() => {
spectator.setInput('groupAction', false);
spectator.setInput('groupActions', false);
spectator.detectComponentChanges();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
input,
OnChanges,
Output,
output,
signal
} from '@angular/core';

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';

Expand All @@ -25,6 +24,11 @@ const InplaceButtonSizePrimeNg: Record<ButtonSize, string> = {
large: 'p-button-lg'
};

interface WorkflowActionsGroup {
mainAction: MenuItem;
subActions: MenuItem[];
}

@Component({
selector: 'dot-workflow-actions',
standalone: true,
Expand All @@ -34,71 +38,121 @@ const InplaceButtonSizePrimeNg: Record<ButtonSize, string> = {
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<DotCMSWorkflowAction>();
/**
* List of actions to display
*
* @type {DotCMSWorkflowAction[]}
* @memberof DotWorkflowActionsComponent
*/
actions = input.required<DotCMSWorkflowAction[]>();
/**
* Show a loading button spinner
*
* @memberof DotWorkflowActionsComponent
*/
loading = input<boolean>(false);
/**
* Group the actions by separator
*
* @memberof DotWorkflowActionsComponent
*/
groupActions = input<boolean>(false);
/**
* Button size
*
* @type {ButtonSize}
* @memberof DotWorkflowActionsComponent
*/
size = input<ButtonSize>('normal');
/**
* Emits when an action is selected
*
* @memberof DotWorkflowActionsComponent
*/
actionFired = output<DotCMSWorkflowAction>();

protected groupedActions = signal<MenuItem[][]>([]);
protected sizeClass: string;
protected $groupedActions = signal<WorkflowActionsGroup[]>([]);
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<DotCMSWorkflowAction[][]>(
(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)
};
}
}

0 comments on commit 4e84677

Please sign in to comment.