Skip to content

Commit

Permalink
issue-29621: Excluding Hidden content types from the Widgets displayed (
Browse files Browse the repository at this point in the history
#30517)

This pull request introduces several changes to the `DotPaletteStore`
and related test files to enhance the handling of content types and
widgets, including the filtering of hidden content types. The most
important changes include adding the `DotPropertiesService` for
configuration management and refactoring the content type loading logic.

Enhancements to `DotPaletteStore`:

* Added `DotPropertiesService` to manage configuration settings and
retrieve hidden content types (`CONTENT_PALETTE_HIDDEN_CONTENT_TYPES`).
(`[[1]](diffhunk://#diff-8262303c46e34e159efdc68dec99a47ddf453302794be94483b1abee94c13556R9)`,
`[[2]](diffhunk://#diff-5e9e57d14e6a9185a0988e2062be4028f862924c1b068ac9eddab73da058e94fR13-R20)`,
`[[3]](diffhunk://#diff-5e9e57d14e6a9185a0988e2062be4028f862924c1b068ac9eddab73da058e94fL166-R169)`)
* Refactored the `getContenttypesData` method to use `forkJoin` with
multiple observables, including hidden content types, and improved the
filtering logic to exclude hidden content types.
(`[core-web/apps/dotcms-ui/src/app/portlets/dot-edit-page/components/dot-palette/store/dot-palette.store.tsL252-L282](diffhunk://#diff-5e9e57d14e6a9185a0988e2062be4028f862924c1b068ac9eddab73da058e94fL252-L282)`)

Updates to tests:

* Updated test cases to include `DotPropertiesService` and verify the
filtering of hidden content types in `dot-palette.store.spec.ts` and
`edit-ema-palette.store.spec.ts`.
(`[[1]](diffhunk://#diff-8262303c46e34e159efdc68dec99a47ddf453302794be94483b1abee94c13556R114-R136)`,
`[[2]](diffhunk://#diff-8262303c46e34e159efdc68dec99a47ddf453302794be94483b1abee94c13556R147)`,
`[[3]](diffhunk://#diff-8262303c46e34e159efdc68dec99a47ddf453302794be94483b1abee94c13556L185-R233)`,
`[[4]](diffhunk://#diff-1440c725f71c6ba67d7a4847e983e63b1e7a3a4fe17042aafe9553a89ad19f22L46-R82)`,
`[[5]](diffhunk://#diff-1440c725f71c6ba67d7a4847e983e63b1e7a3a4fe17042aafe9553a89ad19f22R119-R131)`,
`[[6]](diffhunk://#diff-1440c725f71c6ba67d7a4847e983e63b1e7a3a4fe17042aafe9553a89ad19f22R141-R160)`)

Refactoring:

* Replaced inline sorting and filtering logic with pre-defined constants
and helper methods to improve code readability and maintainability.
(`[[1]](diffhunk://#diff-8262303c46e34e159efdc68dec99a47ddf453302794be94483b1abee94c13556R114-R136)`,
`[[2]](diffhunk://#diff-8262303c46e34e159efdc68dec99a47ddf453302794be94483b1abee94c13556L185-R233)`,
`[[3]](diffhunk://#diff-e60190c318aa61ddbc495b1c20398bb42e7baa8429b8241aa6964267f3ddd445L235-L259)`)

These changes improve the overall functionality and maintainability of
the `DotPaletteStore` by incorporating configuration management and
enhancing the content type filtering logic.

### Video


https://github.com/user-attachments/assets/a376dd9d-861f-4250-a67d-1dd0dd5474da

---------

Co-authored-by: Rafael Velazco <[email protected]>
  • Loading branch information
Neehakethi and rjvelazco authored Dec 26, 2024
1 parent b6cb29a commit 4b4b2da
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import {
DotContentTypeService,
DotESContentService,
DotPropertiesService,
DotSessionStorageService,
PaginatorService
} from '@dotcms/data-access';
Expand Down Expand Up @@ -110,18 +111,29 @@ class MockContentTypeService {
}
}

const SORTED_CONTENT_TYPE_MOCK = contentTypeDataMock.sort((a, b) =>
a.name.localeCompare(b.name)
) as DotCMSContentType[];

describe('DotPaletteStore', () => {
let dotPaletteStore: DotPaletteStore;
let paginatorService: PaginatorService;
let dotContentTypeService: DotContentTypeService;
let dotESContentService: DotESContentService;
let dotSessionStorageService: DotSessionStorageService;
let dotPropertiesService: DotPropertiesService;

beforeEach(() => {
TestBed.configureTestingModule({
providers: [
DotPaletteStore,
DotSessionStorageService,
{
provide: DotPropertiesService,
useValue: {
getKeyAsList: () => of([])
}
},
{ provide: PaginatorService, useClass: MockPaginatorService },
{ provide: DotContentTypeService, useClass: MockContentTypeService },
{ provide: DotESContentService, useClass: MockESPaginatorService }
Expand All @@ -132,6 +144,7 @@ describe('DotPaletteStore', () => {
dotContentTypeService = TestBed.inject(DotContentTypeService);
dotESContentService = TestBed.inject(DotESContentService);
dotSessionStorageService = TestBed.inject(DotSessionStorageService);
dotPropertiesService = TestBed.inject(DotPropertiesService);
});

// Updaters
Expand Down Expand Up @@ -182,27 +195,42 @@ describe('DotPaletteStore', () => {

// Effects
it('should load contentTypes to store', (done) => {
const sortedDataMock = contentTypeDataMock.sort((a, b) => a.name.localeCompare(b.name));
spyOn(dotContentTypeService, 'filterContentTypes').and.returnValues(
of(sortedDataMock as DotCMSContentType[])
of(SORTED_CONTENT_TYPE_MOCK)
);
spyOn(dotContentTypeService, 'getContentTypes').and.returnValues(of([]));

dotPaletteStore.loadContentTypes(['blog', 'banner']);
dotPaletteStore.vm$.subscribe((data) => {
expect(data.contentTypes).toEqual(SORTED_CONTENT_TYPE_MOCK);
done();
});
});

it("should load contentTypes and remove the hidden is the CONTENT_PALETTE_HIDDEN_CONTENT_TYPES is setted'", (done) => {
spyOn(dotContentTypeService, 'filterContentTypes').and.returnValues(
of(SORTED_CONTENT_TYPE_MOCK)
);
spyOn(dotContentTypeService, 'getContentTypes').and.returnValues(of([]));
spyOn(dotPropertiesService, 'getKeyAsList').and.returnValue(of(['Form']));

const expectedData = SORTED_CONTENT_TYPE_MOCK.filter((item) => item.variable !== 'Form');

dotPaletteStore.loadContentTypes(['blog', 'banner']);
dotPaletteStore.vm$.subscribe((data) => {
expect(data.contentTypes).toEqual(sortedDataMock as DotCMSContentType[]);
expect(data.contentTypes).toEqual(expectedData);
done();
});
});

it('should load inly widgets to store if allowedContent is empty', (done) => {
const sortedDataMock = contentTypeDataMock.sort((a, b) => a.name.localeCompare(b.name));
it('should load only widgets to store if allowedContent is empty', (done) => {
spyOn(dotContentTypeService, 'filterContentTypes').and.returnValues(of([]));
spyOn(dotContentTypeService, 'getContentTypes').and.returnValues(
of(sortedDataMock as DotCMSContentType[])
of(SORTED_CONTENT_TYPE_MOCK)
);
dotPaletteStore.loadContentTypes([]);
dotPaletteStore.vm$.subscribe((data) => {
expect(data.contentTypes).toEqual(sortedDataMock as DotCMSContentType[]);
expect(data.contentTypes).toEqual(SORTED_CONTENT_TYPE_MOCK);
done();
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ComponentStore } from '@ngrx/component-store';
import { forkJoin, Observable } from 'rxjs';
import { forkJoin, Observable, of } from 'rxjs';

import { Injectable } from '@angular/core';

Expand All @@ -10,14 +10,17 @@ import { debounceTime, map, take } from 'rxjs/operators';
import {
DotContentTypeService,
DotESContentService,
DotPropertiesService,
DotSessionStorageService,
PaginatorService
} from '@dotcms/data-access';
import {
ComponentStatus,
DEFAULT_VARIANT_ID,
DotCMSBaseTypesContentTypes,
DotCMSContentlet,
DotCMSContentType,
DotConfigurationVariables,
ESContent
} from '@dotcms/dotcms-models';

Expand Down Expand Up @@ -163,7 +166,8 @@ export class DotPaletteStore extends ComponentStore<DotPaletteState> {
private dotContentTypeService: DotContentTypeService,
private paginatorESService: DotESContentService,
private paginationService: PaginatorService,
private dotSessionStorageService: DotSessionStorageService
private dotSessionStorageService: DotSessionStorageService,
private dotConfigurationService: DotPropertiesService
) {
super({
contentlets: null,
Expand Down Expand Up @@ -249,37 +253,41 @@ export class DotPaletteStore extends ComponentStore<DotPaletteState> {
*/
getContenttypesData(): void {
this.setLoading();
this.state$.pipe(take(1)).subscribe(({ filter, allowedContent }) => {
if (allowedContent && allowedContent.length) {
forkJoin([
this.dotContentTypeService.filterContentTypes(filter, allowedContent.join(',')),
this.dotContentTypeService.getContentTypes({ filter, page: 40, type: 'WIDGET' })
])
.pipe(take(1))
.subscribe((results: DotCMSContentType[][]) => {
const [allowContent, widgets] = results;

// Some pages bring widgets in the CONTENT_PALETTE_HIDDEN_CONTENT_TYPES, and others don't.
// However, all pages allow widgets, so we make a request just to get them.
// Full comment here: https://github.com/dotCMS/core/pull/22573#discussion_r921263060
// This filter is used to prevent widgets from being repeated.
const contentLets = allowContent.filter(
(item) => item.baseType !== 'WIDGET'
);

// Merge both array and order them by name
const contentTypes = [...contentLets, ...widgets]
.sort((a, b) => a.name.localeCompare(b.name))
.slice(0, 40);

this.loadContentypes(contentTypes);
});
} else {
this.dotContentTypeService
.getContentTypes({ filter, page: 40, type: 'WIDGET' })
.pipe(take(1))
.subscribe((data: DotCMSContentType[]) => this.loadContentypes(data));
}
this.state$.pipe(take(1)).subscribe(({ filter, allowedContent = [] }) => {
// Note: This store needs to be refactored
const hasAllowedContent = allowedContent && allowedContent.length > 0;
const contentTypes$ = hasAllowedContent
? this.dotContentTypeService.filterContentTypes(filter, allowedContent.join(','))
: of([]);

forkJoin({
contentTypes: contentTypes$,
widgets: this.dotContentTypeService.getContentTypes({
filter,
page: 40,
type: 'WIDGET'
}),
hiddenContentTypes: this.dotConfigurationService.getKeyAsList(
DotConfigurationVariables.CONTENT_PALETTE_HIDDEN_CONTENT_TYPES
)
})
.pipe(take(1))
.subscribe(({ contentTypes, widgets, hiddenContentTypes }) => {
/**
* This filter is used to prevent widgets from being repeated.
* More information here: https://github.com/dotCMS/core/pull/22573#discussion_r921263060
*/
const filteredContentTypes = contentTypes.filter(
(item) => item.baseType !== DotCMSBaseTypesContentTypes.WIDGET
);
const mergedContentAndWidgets = [...filteredContentTypes, ...widgets];
const data = mergedContentAndWidgets
.filter((item) => !hiddenContentTypes.includes(item.variable))
.sort((a, b) => a.name.localeCompare(b.name))
.slice(0, 40);

this.loadContentypes(data);
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
DEFAULT_VARIANT_NAME,
DotCMSContentlet,
DotCMSContentType,
DotConfigurationVariables,
DotContainerStructure,
DotContentletEventAddContentType,
DotExperiment,
Expand Down Expand Up @@ -377,9 +378,8 @@ browse from the page internal links
}

private setAllowedContent(pageState: DotPageRenderState): void {
const CONTENT_HIDDEN_KEY = 'CONTENT_PALETTE_HIDDEN_CONTENT_TYPES';
this.dotConfigurationService
.getKeyAsList(CONTENT_HIDDEN_KEY)
.getKeyAsList(DotConfigurationVariables.CONTENT_PALETTE_HIDDEN_CONTENT_TYPES)
.pipe(take(1))
.subscribe((results) => {
this.allowedContent = this.filterAllowedContentTypes(results, pageState) || [];
Expand Down
5 changes: 5 additions & 0 deletions core-web/libs/dotcms-models/src/lib/shared-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export const enum FeaturedFlags {
FEATURE_FLAG_UVE_PREVIEW_MODE = 'FEATURE_FLAG_UVE_PREVIEW_MODE'
}

export const enum DotConfigurationVariables {
CONTENT_PALETTE_HIDDEN_CONTENT_TYPES = 'CONTENT_PALETTE_HIDDEN_CONTENT_TYPES',
WYSIWYG_IMAGE_URL_PATTERN = 'WYSIWYG_IMAGE_URL_PATTERN'
}

export const FEATURE_FLAG_NOT_FOUND = 'NOT_FOUND';

export type DotDropdownGroupSelectOption<T> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
DotESContentService,
DotPropertiesService
} from '@dotcms/data-access';
import { DotCMSContentType } from '@dotcms/dotcms-models';
import { DotConfigurationVariables } from '@dotcms/dotcms-models';

import {
DotPaletteStore,
Expand All @@ -17,8 +17,23 @@ import {
PALETTE_TYPES
} from './edit-ema-palette.store';

const WIDGET_MOCK = {
baseType: 'WIDGET',
id: 'widgetTest1',
name: 'widgetTest1',
variable: 'widgetTest1'
};

const VALID_CONTENT_TYPE_MOCK = {
baseType: 'someContent',
id: 'contentTypeTest1',
name: 'contentTypeTest1',
variable: 'contentTypeTest1'
};

describe('EditEmaPaletteStore', () => {
let spectator: SpectatorService<DotPaletteStore>;
let dotPropertiesService: DotPropertiesService;
const createService = createServiceFactory({
service: DotPaletteStore,
providers: [
Expand All @@ -43,32 +58,29 @@ describe('EditEmaPaletteStore', () => {
useValue: {
filterContentTypes: () =>
of([
{
baseType: 'someContent',
id: 'contentTypeTest1',
name: 'contentTypeTest1'
},
VALID_CONTENT_TYPE_MOCK,
{
baseType: 'WIDGET',
id: 'contentTypeTest2',
name: 'contentTypeTest2'
name: 'contentTypeTest2',
variable: 'contentTypeTest2'
}
]),
getContentTypes: () =>
of([
{
baseType: 'WIDGET',
id: 'widgetTest1',
name: 'widgetTest1'
}
])
getContentTypes: () => of([WIDGET_MOCK])
}
},
{
provide: DotPropertiesService,
useValue: {
getKeyAsList: () => of([])
}
}
]
});

beforeEach(() => {
spectator = createService();
dotPropertiesService = spectator.inject(DotPropertiesService);
});

describe('updaters', () => {
Expand Down Expand Up @@ -105,26 +117,19 @@ describe('EditEmaPaletteStore', () => {
spectator.inject(DotContentTypeService),
'getContentTypes'
);

const getKeyAsListSpy = jest.spyOn(dotPropertiesService, 'getKeyAsList');
spectator.service.loadContentTypes({
filter: '',
allowedContent: ['contentTypeTest1']
});
const expected = [
{
baseType: 'someContent',
id: 'contentTypeTest1',
name: 'contentTypeTest1'
},
{
baseType: 'WIDGET',
id: 'widgetTest1',
name: 'widgetTest1'
}
];
spectator.service.vm$.subscribe((state) => {
expect(state.contenttypes.items).toEqual(expected as DotCMSContentType[]);
expect(state.contenttypes.items).toEqual([VALID_CONTENT_TYPE_MOCK, WIDGET_MOCK]);
expect(patchStateSpy).toHaveBeenCalled();
expect(filterContentTypesSpy).toHaveBeenCalledWith('', 'contentTypeTest1');
expect(getKeyAsListSpy).toHaveBeenCalledWith(
DotConfigurationVariables.CONTENT_PALETTE_HIDDEN_CONTENT_TYPES
);
expect(getContentTypesSpy).toHaveBeenCalledWith({
filter: '',
page: 40,
Expand All @@ -134,6 +139,26 @@ describe('EditEmaPaletteStore', () => {
});
});

it('should load content types with filter hidden content types', (done) => {
const getKeyAsListSpy = jest
.spyOn(dotPropertiesService, 'getKeyAsList')
.mockReturnValue(of(['contentTypeTest1']));

const payload = {
filter: '',
allowedContent: ['contentTypeTest1']
};

spectator.service.loadContentTypes(payload);
spectator.service.vm$.subscribe((state) => {
expect(state.contenttypes.items).toEqual([WIDGET_MOCK]);
expect(getKeyAsListSpy).toHaveBeenCalledWith(
DotConfigurationVariables.CONTENT_PALETTE_HIDDEN_CONTENT_TYPES
);
done();
});
});

it('should load contentlets', (done) => {
const contentServiceSpy = jest.spyOn(spectator.inject(DotESContentService), 'get');
const patchStateSpy = jest.spyOn(spectator.service, 'patchState');
Expand Down
Loading

0 comments on commit 4b4b2da

Please sign in to comment.