Skip to content

Commit

Permalink
chore(uve): Add Persona Selector to New Toolbar (#30856)
Browse files Browse the repository at this point in the history
This pull request introduces several enhancements to the
`dot-uve-toolbar` component, focusing on improving persona management
and adding new services. The most significant changes include the
integration of a persona selector component, updates to handle persona
selection and despersonalization, and modifications to the state
management for persona properties.

This PR also fixes a found issue, where every time we deleted content
from a page for a specific persona, there were cases where the content
was being save for the default persona, messing up with the page for the
default persona.

## Before the fix

https://github.com/user-attachments/assets/ee61e3fe-9b0e-42b4-96ab-2045a8828573

## After the fix

https://github.com/user-attachments/assets/442279c4-1bb0-4e1e-8792-0b944d673c57

Enhancements to persona management:

*
[`core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html`](diffhunk://#diff-9937556e73b051b878ba22ad1ce971a70019a617d7979b3e0bcc814801ad350bL41-R48):
Replaced the static persona selector span with the
`dot-edit-ema-persona-selector` component, adding event bindings for
persona selection and despersonalization.

*
[`core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts`](diffhunk://#diff-217a9e619d6590c4f652e85353b9637ba5e464ddeb0424be35aef39bb8dceb30L2-R16):
Added imports for `DotPersonalizeService` and `DotPersona`, included the
`EditEmaPersonaSelectorComponent` in the component declarations, and
introduced methods `onPersonaSelected` and `onDespersonalize` to handle
persona-related actions.
[[1]](diffhunk://#diff-217a9e619d6590c4f652e85353b9637ba5e464ddeb0424be35aef39bb8dceb30L2-R16)
[[2]](diffhunk://#diff-217a9e619d6590c4f652e85353b9637ba5e464ddeb0424be35aef39bb8dceb30R27-R47)
[[3]](diffhunk://#diff-217a9e619d6590c4f652e85353b9637ba5e464ddeb0424be35aef39bb8dceb30R60-R128)

State management updates:

*
[`core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/models.ts`](diffhunk://#diff-a5d9d93bd678484a7d776d09685fcb638de6d969ac141586a1104ba5e744556bL131-L134):
Removed the `personaSelector` property from `UVEToolbarProps` and
created a new `PersonaSelectorProps` interface.
[[1]](diffhunk://#diff-a5d9d93bd678484a7d776d09685fcb638de6d969ac141586a1104ba5e744556bL131-L134)
[[2]](diffhunk://#diff-a5d9d93bd678484a7d776d09685fcb638de6d969ac141586a1104ba5e744556bR140-R144)

*
[`core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts`](diffhunk://#diff-e6d3fb6319626fa85a4fc6894b57935843713366be593de6dd1dc5ed68bf6afcL25-R25):
Updated the state management to include a computed `$personaSelector`
property that returns the current persona selector properties.
[[1]](diffhunk://#diff-e6d3fb6319626fa85a4fc6894b57935843713366be593de6dd1dc5ed68bf6afcL25-R25)
[[2]](diffhunk://#diff-e6d3fb6319626fa85a4fc6894b57935843713366be593de6dd1dc5ed68bf6afcL112-R123)

*
[`core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.spec.ts`](diffhunk://#diff-7a5de702ac1dc81304f4c31816f5c0363aa56141f8afa583a87856e7a0d8482dR11):
Added a test case to verify the persona selector properties in the
store.
[[1]](diffhunk://#diff-7a5de702ac1dc81304f4c31816f5c0363aa56141f8afa583a87856e7a0d8482dR11)
[[2]](diffhunk://#diff-7a5de702ac1dc81304f4c31816f5c0363aa56141f8afa583a87856e7a0d8482dL50)
[[3]](diffhunk://#diff-7a5de702ac1dc81304f4c31816f5c0363aa56141f8afa583a87856e7a0d8482dR82-R88)
  • Loading branch information
zJaaal authored Dec 6, 2024
1 parent dc55296 commit e32a4d3
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,24 @@
</div>

<div class="p-toolbar-group-end">
<dot-edit-ema-language-selector
#languageSelector
(selected)="onLanguageSelected($event)"
[language]="$toolbar().currentLanguage"
data-testId="uve-toolbar-language-selector" />

@if ($toolbar().runningExperiment; as runningExperiment) {
<dot-ema-running-experiment
[runningExperiment]="runningExperiment"
data-testId="uve-toolbar-running-experiment" />
}
<dot-edit-ema-language-selector
#languageSelector
(selected)="onLanguageSelected($event)"
[language]="$toolbar().currentLanguage"
data-testId="uve-toolbar-language-selector" />

<span data-testId="uve-toolbar-persona-selector">Persona</span>
<dot-edit-ema-persona-selector
(selected)="onPersonaSelected($event)"
(despersonalize)="onDespersonalize($event)"
[pageId]="$personaSelectorProps().pageId"
[value]="$personaSelectorProps().value"
#personaSelector
data-testId="uve-toolbar-persona-selector" />
<span data-testId="uve-toolbar-workflow-actions">Workflows</span>
</div>
</p-toolbar>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, describe } from '@jest/globals';
import { expect, describe, it } from '@jest/globals';
import { byTestId, mockProvider, Spectator, createComponentFactory } from '@ngneat/spectator/jest';
import { MockComponent } from 'ng-mocks';
import { of } from 'rxjs';
Expand All @@ -9,7 +9,12 @@ import { By } from '@angular/platform-browser';

import { ConfirmationService, MessageService } from 'primeng/api';

import { DotExperimentsService, DotLanguagesService, DotLicenseService } from '@dotcms/data-access';
import {
DotExperimentsService,
DotLanguagesService,
DotLicenseService,
DotPersonalizeService
} from '@dotcms/data-access';
import { LoginService } from '@dotcms/dotcms-js';
import {
DotExperimentsServiceMock,
Expand Down Expand Up @@ -37,19 +42,23 @@ import {
import { DotEmaBookmarksComponent } from '../dot-ema-bookmarks/dot-ema-bookmarks.component';
import { DotEmaRunningExperimentComponent } from '../dot-ema-running-experiment/dot-ema-running-experiment.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';

const $apiURL = '/api/v1/page/json/123-xyz-567-xxl?host_id=123-xyz-567-xxl&language_id=1';

describe('DotUveToolbarComponent', () => {
let spectator: Spectator<DotUveToolbarComponent>;
let messageService: MessageService;
let confirmationService: ConfirmationService;
let store: InstanceType<typeof UVEStore>;

const createComponent = createComponentFactory({
component: DotUveToolbarComponent,
imports: [
HttpClientTestingModule,
MockComponent(DotEmaBookmarksComponent),
MockComponent(DotEmaRunningExperimentComponent)
MockComponent(DotEmaRunningExperimentComponent),
MockComponent(EditEmaPersonaSelectorComponent)
],
providers: [
UVEStore,
Expand Down Expand Up @@ -87,6 +96,14 @@ describe('DotUveToolbarComponent', () => {
add: jest.fn()
}
}
],
componentProviders: [
{
provide: DotPersonalizeService,
useValue: {
getPersonalize: jest.fn()
}
}
]
});

Expand Down Expand Up @@ -118,11 +135,7 @@ describe('DotUveToolbarComponent', () => {
runningExperiment: null,
workflowActionsInode: pageAPIResponse?.page.inode,
unlockButton: null,
showInfoDisplay: shouldShowInfoDisplay,
personaSelector: {
pageId: pageAPIResponse?.page.identifier,
value: pageAPIResponse?.viewAs.persona ?? DEFAULT_PERSONA
}
showInfoDisplay: shouldShowInfoDisplay
};

const baseUVEState = {
Expand All @@ -132,6 +145,10 @@ describe('DotUveToolbarComponent', () => {
pageParams: signal(params),
pageAPIResponse: signal(MOCK_RESPONSE_VTL),
$apiURL: signal($apiURL),
$personaSelector: signal({
pageId: pageAPIResponse?.page.identifier,
value: pageAPIResponse?.viewAs.persona ?? DEFAULT_PERSONA
}),
reloadCurrentPage: jest.fn(),
loadPageAsset: jest.fn(),
languages: signal([
Expand All @@ -148,6 +165,8 @@ describe('DotUveToolbarComponent', () => {
});

messageService = spectator.inject(MessageService);
confirmationService = spectator.inject(ConfirmationService);
store = spectator.inject(UVEStore);
});

describe('dot-ema-bookmarks', () => {
Expand Down Expand Up @@ -244,6 +263,96 @@ describe('DotUveToolbarComponent', () => {
expect(btn.getAttribute('href')).toBe($apiURL);
});
});

describe('dot-edit-ema-persona-selector', () => {
it('should have attr', () => {
const personaSelector = spectator.query(EditEmaPersonaSelectorComponent);

expect(personaSelector.pageId).toBe('123');
expect(personaSelector.value).toEqual({
archived: false,
baseType: 'PERSONA',
contentType: 'persona',
folder: 'SYSTEM_FOLDER',
hasLiveVersion: false,
hasTitleImage: false,
host: 'SYSTEM_HOST',
hostFolder: 'SYSTEM_HOST',
hostName: 'System Host',
identifier: 'modes.persona.no.persona',
inode: '',
keyTag: 'dot:persona',
languageId: 1,
live: false,
locked: false,
modDate: '0',
modUser: 'system',
modUserName: 'system user system user',
name: 'Default Visitor',
owner: 'SYSTEM_USER',
personalized: false,
sortOrder: 0,
stInode: 'c938b15f-bcb6-49ef-8651-14d455a97045',
title: 'Default Visitor',
titleImage: 'TITLE_IMAGE_NOT_FOUND',
url: 'demo.dotcms.com',
working: false
});
});

it('should personalize - no confirmation', () => {
const spyloadPageAsset = jest.spyOn(store, 'loadPageAsset');
spectator.triggerEventHandler(EditEmaPersonaSelectorComponent, 'selected', {
identifier: '123',
pageId: '123',
personalized: true
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
spectator.detectChanges();

expect(spyloadPageAsset).toHaveBeenCalledWith({
'com.dotmarketing.persona.id': '123'
});
});

it('should personalize - confirmation', () => {
spectator.triggerEventHandler(EditEmaPersonaSelectorComponent, 'selected', {
identifier: '123',
pageId: '123',
personalized: false
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
spectator.detectChanges();

expect(confirmationService.confirm).toHaveBeenCalledWith({
accept: expect.any(Function),
acceptLabel: 'dot.common.dialog.accept',
header: 'editpage.personalization.confirm.header',
message: 'editpage.personalization.confirm.message',
reject: expect.any(Function),
rejectLabel: 'dot.common.dialog.reject'
});
});

it('should despersonalize', () => {
spectator.triggerEventHandler(EditEmaPersonaSelectorComponent, 'despersonalize', {
identifier: '123',
pageId: '123',
personalized: true
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);

spectator.detectChanges();

expect(confirmationService.confirm).toHaveBeenCalledWith({
accept: expect.any(Function),
acceptLabel: 'dot.common.dialog.accept',
header: 'editpage.personalization.delete.confirm.header',
message: 'editpage.personalization.delete.confirm.message',
rejectLabel: 'dot.common.dialog.reject'
});
});
});
});

describe('State changes', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ import { ConfirmationService, MessageService } from 'primeng/api';
import { ButtonModule } from 'primeng/button';
import { ToolbarModule } from 'primeng/toolbar';

import { DotMessageService } from '@dotcms/data-access';
import { DotLanguage } from '@dotcms/dotcms-models';
import { DotMessageService, DotPersonalizeService } from '@dotcms/data-access';
import { DotPersona, DotLanguage } from '@dotcms/dotcms-models';

import { DEFAULT_PERSONA } from '../../../shared/consts';
import { DotPage } from '../../../shared/models';
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 { EditEmaLanguageSelectorComponent } from '../edit-ema-language-selector/edit-ema-language-selector.component';
import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/edit-ema-persona-selector.component';

@Component({
selector: 'dot-uve-toolbar',
Expand All @@ -31,22 +33,29 @@ import { EditEmaLanguageSelectorComponent } from '../edit-ema-language-selector/
DotEmaBookmarksComponent,
DotEmaInfoDisplayComponent,
DotEmaRunningExperimentComponent,
ClipboardModule,
EditEmaLanguageSelectorComponent
EditEmaPersonaSelectorComponent,
EditEmaLanguageSelectorComponent,
ClipboardModule
],
providers: [DotPersonalizeService],
templateUrl: './dot-uve-toolbar.component.html',
styleUrl: './dot-uve-toolbar.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DotUveToolbarComponent {
languageSelector = viewChild<EditEmaLanguageSelectorComponent>('languageSelector');
$personaSelector = viewChild<EditEmaPersonaSelectorComponent>('personaSelector');

$languageSelector = viewChild<EditEmaLanguageSelectorComponent>('languageSelector');
#store = inject(UVEStore);

readonly #messageService = inject(MessageService);
readonly #dotMessageService = inject(DotMessageService);
readonly #confirmationService = inject(ConfirmationService);
readonly #personalizeService = inject(DotPersonalizeService);

readonly $toolbar = this.#store.$uveToolbar;
readonly $apiURL = this.#store.$apiURL;
readonly $personaSelectorProps = this.#store.$personaSelector;

@Output() translatePage = new EventEmitter<{ page: DotPage; newLanguage: number }>();

Expand Down Expand Up @@ -89,6 +98,75 @@ export class DotUveToolbarComponent {
}

/**
* Handle the persona selection
*
* @param {DotPersona} persona
* @memberof DotEmaComponent
*/
onPersonaSelected(persona: DotPersona & { pageId: string }) {
if (persona.identifier === DEFAULT_PERSONA.identifier || persona.personalized) {
this.#store.loadPageAsset({
'com.dotmarketing.persona.id': persona.identifier
});
} else {
this.#confirmationService.confirm({
header: this.#dotMessageService.get('editpage.personalization.confirm.header'),
message: this.#dotMessageService.get(
'editpage.personalization.confirm.message',
persona.name
),
acceptLabel: this.#dotMessageService.get('dot.common.dialog.accept'),
rejectLabel: this.#dotMessageService.get('dot.common.dialog.reject'),
accept: () => {
this.#personalizeService
.personalized(persona.pageId, persona.keyTag)
.subscribe(() => {
this.#store.loadPageAsset({
'com.dotmarketing.persona.id': persona.identifier
});

this.$personaSelector().fetchPersonas();
}); // This does a take 1 under the hood
},
reject: () => {
this.$personaSelector().resetValue();
}
});
}
}

/**
* Handle the persona despersonalization
*
* @param {(DotPersona & { pageId: string })} persona
* @memberof EditEmaToolbarComponent
*/
onDespersonalize(persona: DotPersona & { pageId: string; selected: boolean }) {
this.#confirmationService.confirm({
header: this.#dotMessageService.get('editpage.personalization.delete.confirm.header'),
message: this.#dotMessageService.get(
'editpage.personalization.delete.confirm.message',
persona.name
),
acceptLabel: this.#dotMessageService.get('dot.common.dialog.accept'),
rejectLabel: this.#dotMessageService.get('dot.common.dialog.reject'),
accept: () => {
this.#personalizeService
.despersonalized(persona.pageId, persona.keyTag)
.subscribe(() => {
this.$personaSelector().fetchPersonas();

if (persona.selected) {
this.#store.loadPageAsset({
'com.dotmarketing.persona.id': DEFAULT_PERSONA.identifier
});
}
}); // This does a take 1 under the hood
}
});
}

/*
* Asks the user for confirmation to create a new translation for a given language.
*
* @param {DotLanguage} language - The language to create a new translation for.
Expand Down Expand Up @@ -116,7 +194,7 @@ export class DotUveToolbarComponent {
},
reject: () => {
// If is rejected, bring back the current language on selector
this.languageSelector().listbox.writeValue(this.$toolbar().currentLanguage);
this.$languageSelector().listbox.writeValue(this.$toolbar().currentLanguage);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ export class EditEmaPersonaSelectorComponent implements AfterViewInit, OnChanges
* @memberof EditEmaPersonaSelectorComponent
*/
resetValue(): void {
this.listbox.value = this.value;
this.listbox.cd.detectChanges();
this.listbox.writeValue(this.value);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
DotPropertiesService,
DotSeoMetaTagsService,
DotSeoMetaTagsUtilService,
DotSessionStorageService,
DotTempFileUploadService,
DotWorkflowActionsFireService,
PushPublishService
Expand Down Expand Up @@ -147,6 +148,7 @@ const createRouting = () =>
UVEStore,
DotFavoritePageService,
DotESContentService,
DotSessionStorageService,
{
provide: DotPropertiesService,
useValue: {
Expand Down
Loading

0 comments on commit e32a4d3

Please sign in to comment.