diff --git a/core-web/libs/dotcms-models/src/lib/shared-models.ts b/core-web/libs/dotcms-models/src/lib/shared-models.ts index 665e02e46561..d085547b5bfd 100644 --- a/core-web/libs/dotcms-models/src/lib/shared-models.ts +++ b/core-web/libs/dotcms-models/src/lib/shared-models.ts @@ -27,7 +27,8 @@ export const enum FeaturedFlags { FEATURE_FLAG_CONTENT_EDITOR2_ENABLED = 'CONTENT_EDITOR2_ENABLED', FEATURE_FLAG_CONTENT_EDITOR2_CONTENT_TYPE = 'CONTENT_EDITOR2_CONTENT_TYPE', FEATURE_FLAG_ANNOUNCEMENTS = 'FEATURE_FLAG_ANNOUNCEMENTS', - FEATURE_FLAG_NEW_EDIT_PAGE = 'FEATURE_FLAG_NEW_EDIT_PAGE' + FEATURE_FLAG_NEW_EDIT_PAGE = 'FEATURE_FLAG_NEW_EDIT_PAGE', + FEATURE_FLAG_UVE_PREVIEW_MODE = 'FEATURE_FLAG_UVE_PREVIEW_MODE' } export const FEATURE_FLAG_NOT_FOUND = 'NOT_FOUND'; 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 c07b3966cd5d..97c92b87865e 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 @@ -27,6 +27,7 @@ import { DotLanguagesService, DotLicenseService, DotMessageService, + DotPropertiesService, DotWorkflowActionsFireService, PushPublishService } from '@dotcms/data-access'; @@ -56,6 +57,7 @@ import { DotPageApiService } from '../services/dot-page-api.service'; import { DEFAULT_PERSONA, WINDOW } from '../shared/consts'; import { FormStatus, NG_CUSTOM_EVENTS } from '../shared/enums'; import { + dotPropertiesServiceMock, PAGE_RESPONSE_BY_LANGUAGE_ID, PAGE_RESPONSE_URL_CONTENT_MAP, PAYLOAD_MOCK @@ -103,6 +105,10 @@ describe('DotEmaShellComponent', () => { DotMessageService, DialogService, DotWorkflowActionsFireService, + { + provide: DotPropertiesService, + useValue: dotPropertiesServiceMock + }, { provide: DotcmsConfigService, useValue: new DotcmsConfigServiceMock() 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 new file mode 100644 index 000000000000..8412fff6e6ce --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html @@ -0,0 +1 @@ +

dot-uve-toolbar works!

diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.scss b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.scss new file mode 100644 index 000000000000..e69de29bb2d1 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 new file mode 100644 index 000000000000..333421176be1 --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DotUveToolbarComponent } from './dot-uve-toolbar.component'; + +describe('DotUveToolbarComponent', () => { + let component: DotUveToolbarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DotUveToolbarComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(DotUveToolbarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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 new file mode 100644 index 000000000000..e234bbbea17a --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts @@ -0,0 +1,11 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'dot-uve-toolbar', + standalone: true, + imports: [], + templateUrl: './dot-uve-toolbar.component.html', + styleUrl: './dot-uve-toolbar.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DotUveToolbarComponent {} diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html index 791f9feed1cf..794dd9f784d4 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html @@ -1,4 +1,9 @@ - +@if ($previewMode()) { + +} @else { + +} + @if ($editorProps().seoResults && ogTagsResults$) { UVEStore, DotFavoritePageService, DotESContentService, + { + provide: DotPropertiesService, + useValue: { + ...dotPropertiesServiceMock, + getKeyAsList: () => of([]) + } + }, { provide: DotAlertConfirmService, useValue: { @@ -378,7 +388,6 @@ describe('EditEmaEditorComponent', () => { dotWorkflowActionsFireService = spectator.inject(DotWorkflowActionsFireService, true); router = spectator.inject(Router, true); dotPageApiService = spectator.inject(DotPageApiService, true); - addMessageSpy = jest.spyOn(messageService, 'add'); jest.spyOn(dotLicenseService, 'isEnterprise').mockReturnValue(of(true)); @@ -421,6 +430,23 @@ describe('EditEmaEditorComponent', () => { }); }); + it('should show the old toolbar when FEATURE_FLAG_UVE_PREVIEW_MODE is false', () => { + const toolbar = spectator.query(EditEmaToolbarComponent); + + expect(toolbar).not.toBeNull(); + }); + + it('should show the new toolbar when FEATURE_FLAG_UVE_PREVIEW_MODE is true', () => { + store.setFlags({ + FEATURE_FLAG_UVE_PREVIEW_MODE: true + }); + spectator.detectChanges(); + + const toolbar = spectator.query(DotUveToolbarComponent); + + expect(toolbar).not.toBeNull(); + }); + it('should hide components when the store changes for a variant', () => { const componentsToHide = [ 'palette', 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 5f3ffa77889c..7625cf1cb97e 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 @@ -45,15 +45,10 @@ import { SeoMetaTagsResult } from '@dotcms/dotcms-models'; import { DotResultsSeoToolComponent } from '@dotcms/portlets/dot-ema/ui'; -import { - SafeUrlPipe, - DotSpinnerModule, - DotMessagePipe, - DotCopyContentModalService -} from '@dotcms/ui'; +import { SafeUrlPipe, DotSpinnerModule, DotCopyContentModalService } from '@dotcms/ui'; import { isEqual } from '@dotcms/utils'; -import { DotEmaBookmarksComponent } from './components/dot-ema-bookmarks/dot-ema-bookmarks.component'; +import { DotUveToolbarComponent } from './components/dot-uve-toolbar/dot-uve-toolbar.component'; import { EditEmaPaletteComponent } from './components/edit-ema-palette/edit-ema-palette.component'; import { EditEmaToolbarComponent } from './components/edit-ema-toolbar/edit-ema-toolbar.component'; import { EmaContentletToolsComponent } from './components/ema-contentlet-tools/ema-contentlet-tools.component'; @@ -110,13 +105,12 @@ import { DotEmaDialogComponent, ConfirmDialogModule, EditEmaToolbarComponent, - DotMessagePipe, EmaPageDropzoneComponent, EditEmaPaletteComponent, EmaContentletToolsComponent, - DotEmaBookmarksComponent, ProgressBarModule, DotResultsSeoToolComponent, + DotUveToolbarComponent, DotBlockEditorSidebarComponent ], providers: [ @@ -158,6 +152,7 @@ export class EditEmaEditorComponent implements OnInit, OnDestroy { readonly host = '*'; readonly $ogTags: WritableSignal = signal(undefined); readonly $editorProps = this.uveStore.$editorProps; + readonly $previewMode = this.uveStore.$previewMode; get contentWindow(): Window { return this.iframe.nativeElement.contentWindow; diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/consts.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/consts.ts index 764a21436b0c..5a491fe8f2f0 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/consts.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/consts.ts @@ -1,6 +1,6 @@ import { InjectionToken } from '@angular/core'; -import { DotPersona } from '@dotcms/dotcms-models'; +import { DotPersona, FeaturedFlags } from '@dotcms/dotcms-models'; import { CommonErrors } from './enums'; import { CommonErrorsInfo } from './models'; @@ -67,3 +67,5 @@ export const DEFAULT_PERSONA: DotPersona = { hasLiveVersion: false, modUser: 'system' }; + +export const UVE_FEATURE_FLAGS = [FeaturedFlags.FEATURE_FLAG_UVE_PREVIEW_MODE]; diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/mocks.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/mocks.ts index 0d85461015e7..853e3801d16a 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/mocks.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/shared/mocks.ts @@ -3,7 +3,8 @@ import { of } from 'rxjs'; import { DEFAULT_VARIANT_ID, DotPageContainerStructure, - CONTAINER_SOURCE + CONTAINER_SOURCE, + FeaturedFlags } from '@dotcms/dotcms-models'; import { mockSites, @@ -956,3 +957,10 @@ export const UVE_PAGE_RESPONSE_MAP = { containers: dotPageContainerStructureMock }) }; + +export const dotPropertiesServiceMock = { + getFeatureFlags: () => + of({ + [FeaturedFlags.FEATURE_FLAG_UVE_PREVIEW_MODE]: false + }) +}; 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 c7297b633cd6..89ca17aa9b41 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 @@ -16,7 +16,8 @@ import { DotExperimentsService, DotLanguagesService, DotLicenseService, - DotMessageService + DotMessageService, + DotPropertiesService } from '@dotcms/data-access'; import { LoginService } from '@dotcms/dotcms-js'; import { @@ -37,6 +38,7 @@ import { UVE_STATUS } from '../shared/enums'; import { BASE_SHELL_ITEMS, BASE_SHELL_PROPS_RESPONSE, + dotPropertiesServiceMock, HEADLESS_BASE_QUERY_PARAMS, MOCK_RESPONSE_HEADLESS, MOCK_RESPONSE_VTL, @@ -65,6 +67,10 @@ describe('UVEStore', () => { MessageService, mockProvider(Router), mockProvider(ActivatedRoute), + { + provide: DotPropertiesService, + useValue: dotPropertiesServiceMock + }, { provide: DotPageApiService, useValue: { diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts index 3001d63aaa00..be75b88eef5d 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts @@ -3,11 +3,13 @@ import { patchState, signalStore, withComputed, withMethods, withState } from '@ import { computed } from '@angular/core'; import { withEditor } from './features/editor/withEditor'; +import { withFlags } from './features/flags/withFlags'; import { withLayout } from './features/layout/withLayout'; import { withLoad } from './features/load/withLoad'; import { ShellProps, TranslateProps, UVEState } from './models'; import { DotPageApiResponse } from '../services/dot-page-api.service'; +import { UVE_FEATURE_FLAGS } from '../shared/consts'; import { UVE_STATUS } from '../shared/enums'; import { getErrorPayload, getRequestHostName, sanitizeURL } from '../utils'; @@ -145,5 +147,6 @@ export const UVEStore = signalStore( }), withLoad(), withLayout(), - withEditor() + withEditor(), + withFlags(UVE_FEATURE_FLAGS) ); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/flags/models.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/flags/models.ts new file mode 100644 index 000000000000..42383fa35a25 --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/flags/models.ts @@ -0,0 +1,7 @@ +import { FeaturedFlags } from '@dotcms/dotcms-models'; + +export type UVEFlags = { [key in FeaturedFlags]?: boolean }; + +export interface WithFlagsState { + flags: UVEFlags; +} diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/flags/withFlags.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/flags/withFlags.spec.ts new file mode 100644 index 000000000000..6bd7d6947d4f --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/flags/withFlags.spec.ts @@ -0,0 +1,89 @@ +import { describe } from '@jest/globals'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { signalStore, withState } from '@ngrx/signals'; +import { of } from 'rxjs'; + +import { DotPropertiesService } from '@dotcms/data-access'; +import { FeaturedFlags } from '@dotcms/dotcms-models'; + +import { withFlags } from './withFlags'; + +import { DotPageApiParams } from '../../../services/dot-page-api.service'; +import { UVE_FEATURE_FLAGS } from '../../../shared/consts'; +import { UVE_STATUS } from '../../../shared/enums'; +import { UVEState } from '../../models'; + +const initialState: UVEState = { + isEnterprise: false, + languages: [], + pageAPIResponse: null, + currentUser: null, + experiment: null, + errorCode: null, + params: {} as DotPageApiParams, + status: UVE_STATUS.LOADING, + isTraditionalPage: true, + canEditPage: false, + pageIsLocked: true +}; + +export const uveStoreMock = signalStore( + withState(initialState), + withFlags(UVE_FEATURE_FLAGS) +); + +const MOCK_RESPONSE = UVE_FEATURE_FLAGS.reduce((acc, flag) => { + acc[flag] = true; + + return acc; +}, {}); + +describe('withFlags', () => { + let spectator: SpectatorService>; + let store: InstanceType; + + const createService = createServiceFactory({ + service: uveStoreMock, + providers: [ + { + provide: DotPropertiesService, + useValue: { + getFeatureFlags: jest.fn().mockReturnValue(of(MOCK_RESPONSE)) + } + } + ] + }); + + beforeEach(() => { + spectator = createService(); + store = spectator.service; + }); + + describe('onInit', () => { + it('should call propertiesService.getFeatureFlags with flags', () => { + const propertiesService = spectator.inject(DotPropertiesService); + + expect(propertiesService.getFeatureFlags).toHaveBeenCalledWith(UVE_FEATURE_FLAGS); + }); + + it('should patch state with flags', () => { + expect(store.flags()).toEqual(MOCK_RESPONSE); + }); + }); + describe('methods', () => { + describe('setFlags', () => { + it('should patch state with flags', () => { + store.setFlags(MOCK_RESPONSE); + + expect(store.flags()).toEqual(MOCK_RESPONSE); + }); + }); + }); + describe('computed', () => { + it('should return $previewMode', () => { + expect(store.$previewMode()).toEqual( + MOCK_RESPONSE[FeaturedFlags.FEATURE_FLAG_UVE_PREVIEW_MODE] + ); + }); + }); +}); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/flags/withFlags.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/flags/withFlags.ts new file mode 100644 index 000000000000..91eee9336f04 --- /dev/null +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/flags/withFlags.ts @@ -0,0 +1,62 @@ +import { + patchState, + signalStoreFeature, + type, + withComputed, + withHooks, + withMethods, + withState +} from '@ngrx/signals'; + +import { computed, inject } from '@angular/core'; + +import { take } from 'rxjs/operators'; + +import { DotPropertiesService } from '@dotcms/data-access'; +import { FeaturedFlags } from '@dotcms/dotcms-models'; + +import { UVEFlags, WithFlagsState } from './models'; + +import { UVEState } from '../../models'; + +/** + * + * @description This feature is used to handle the fetch of flags + * @export + * @return {*} + */ +export function withFlags(flags: FeaturedFlags[]) { + return signalStoreFeature( + { + state: type() + }, + withState({ flags: {} }), + withComputed(({ flags }) => { + return { + // You can add here more computed properties if needed + $previewMode: computed(() => { + const currentFlags = flags(); + + return Boolean(currentFlags[FeaturedFlags.FEATURE_FLAG_UVE_PREVIEW_MODE]); + }) + }; + }), + withMethods((store) => ({ + setFlags: (flags: UVEFlags) => { + patchState(store, { flags: { ...flags } }); + } + })), + withHooks({ + onInit: (store) => { + const propertiesService = inject(DotPropertiesService); + + propertiesService + .getFeatureFlags(flags) + .pipe(take(1)) + .subscribe((flags) => { + store.setFlags(flags); + }); + } + }) + ); +} diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java index a02b51632567..05cb7697c0f0 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java @@ -65,7 +65,7 @@ public class ConfigurationResource implements Serializable { new String[] {"EMAIL_SYSTEM_ADDRESS", "WYSIWYG_IMAGE_URL_PATTERN", "CHARSET","CONTENT_PALETTE_HIDDEN_CONTENT_TYPES", "FEATURE_FLAG_EXPERIMENTS", "DOTFAVORITEPAGE_FEATURE_ENABLE", "FEATURE_FLAG_TEMPLATE_BUILDER_2", "SHOW_VIDEO_THUMBNAIL", "EXPERIMENTS_MIN_DURATION", "EXPERIMENTS_MAX_DURATION", "EXPERIMENTS_DEFAULT_DURATION", "FEATURE_FLAG_SEO_IMPROVEMENTS", - "FEATURE_FLAG_SEO_PAGE_TOOLS", "FEATURE_FLAG_EDIT_URL_CONTENT_MAP", "CONTENT_EDITOR2_ENABLED", "CONTENT_EDITOR2_CONTENT_TYPE", "FEATURE_FLAG_NEW_BINARY_FIELD", "FEATURE_FLAG_ANNOUNCEMENTS", "FEATURE_FLAG_NEW_EDIT_PAGE" })); + "FEATURE_FLAG_SEO_PAGE_TOOLS", "FEATURE_FLAG_EDIT_URL_CONTENT_MAP", "CONTENT_EDITOR2_ENABLED", "CONTENT_EDITOR2_CONTENT_TYPE", "FEATURE_FLAG_NEW_BINARY_FIELD", "FEATURE_FLAG_ANNOUNCEMENTS", "FEATURE_FLAG_NEW_EDIT_PAGE", "FEATURE_FLAG_UVE_PREVIEW_MODE" })); private boolean isOnBlackList(final String key) {