diff --git a/changelogs/fragments/7817.yml b/changelogs/fragments/7817.yml new file mode 100644 index 00000000000..1b2a2607f26 --- /dev/null +++ b/changelogs/fragments/7817.yml @@ -0,0 +1,2 @@ +fix: +- [Workspace] maximum call stack error in use case service ([#7817](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7817)) \ No newline at end of file diff --git a/src/plugins/workspace/public/types.ts b/src/plugins/workspace/public/types.ts index 2fc342c0f8c..8e5076a6cca 100644 --- a/src/plugins/workspace/public/types.ts +++ b/src/plugins/workspace/public/types.ts @@ -14,11 +14,16 @@ export type Services = CoreStart & { navigationUI?: NavigationPublicPluginStart['ui']; }; +export interface WorkspaceUseCaseFeature { + id: string; + title?: string; +} + export interface WorkspaceUseCase { id: string; title: string; description: string; - features: Array<{ id: string; title?: string }>; + features: WorkspaceUseCaseFeature[]; systematic?: boolean; order?: number; } diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts index 6f599084f9d..03aa0e20822 100644 --- a/src/plugins/workspace/public/utils.test.ts +++ b/src/plugins/workspace/public/utils.test.ts @@ -558,6 +558,38 @@ describe('workspace utils: isEqualWorkspaceUseCase', () => { }) ).toEqual(false); }); + it('should return false for duplicate features', () => { + expect( + isEqualWorkspaceUseCase( + { ...useCaseMock, features: [useCaseMock.features[0], useCaseMock.features[0]] }, + { + ...useCaseMock, + features: [ + useCaseMock.features[0], + { + id: 'another', + title: 'Another', + }, + ], + } + ) + ).toEqual(false); + }); + it('should return true for multi same features', () => { + const anotherFeature = { + id: 'another', + title: 'Another', + }; + expect( + isEqualWorkspaceUseCase( + { ...useCaseMock, features: [useCaseMock.features[0], anotherFeature] }, + { + ...useCaseMock, + features: [useCaseMock.features[0], anotherFeature], + } + ) + ).toEqual(true); + }); it('should return true when all properties equal', () => { expect( isEqualWorkspaceUseCase(useCaseMock, { diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index 2bc4f7a8015..bbadd528d73 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -24,7 +24,7 @@ import { WorkspaceAvailability, } from '../../../core/public'; import { DEFAULT_SELECTED_FEATURES_IDS, WORKSPACE_DETAIL_APP_ID } from '../common/constants'; -import { WorkspaceUseCase } from './types'; +import { WorkspaceUseCase, WorkspaceUseCaseFeature } from './types'; import { formatUrlWithWorkspaceId } from '../../../core/public/utils'; import { SigV4ServiceName } from '../../../plugins/data_source/common/data_sources'; @@ -260,6 +260,18 @@ export const convertNavGroupToWorkspaceUseCase = ({ order, }); +const compareFeatures = ( + features1: WorkspaceUseCaseFeature[], + features2: WorkspaceUseCaseFeature[] +) => { + const featuresSerializer = (features: WorkspaceUseCaseFeature[]) => + features + .map(({ id, title }) => `${id}-${title}`) + .sort() + .join(); + return featuresSerializer(features1) === featuresSerializer(features2); +}; + export const isEqualWorkspaceUseCase = (a: WorkspaceUseCase, b: WorkspaceUseCase) => { if (a.id !== b.id) { return false; @@ -276,14 +288,7 @@ export const isEqualWorkspaceUseCase = (a: WorkspaceUseCase, b: WorkspaceUseCase if (a.order !== b.order) { return false; } - if ( - a.features.length !== b.features.length || - a.features.some((aFeature) => - b.features.some( - (bFeature) => aFeature.id !== bFeature.id || aFeature.title !== bFeature.title - ) - ) - ) { + if (a.features.length !== b.features.length || !compareFeatures(a.features, b.features)) { return false; } return true;