From 3e0a2fbb9167268b4b99b72b276d9880fc7bde71 Mon Sep 17 00:00:00 2001 From: tygao Date: Fri, 22 Mar 2024 17:08:05 +0800 Subject: [PATCH 1/7] feat: unite feature set Signed-off-by: tygao --- src/plugins/workspace/public/hooks.ts | 71 ++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/plugins/workspace/public/hooks.ts b/src/plugins/workspace/public/hooks.ts index e84ee46507ef..ea78292085aa 100644 --- a/src/plugins/workspace/public/hooks.ts +++ b/src/plugins/workspace/public/hooks.ts @@ -3,9 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ApplicationStart, PublicAppInfo } from 'opensearch-dashboards/public'; import { useObservable } from 'react-use'; import { useMemo } from 'react'; +import { groupBy } from 'lodash'; +import { i18n } from '@osd/i18n'; +import { WorkspaceFeature, WorkspaceFeatureGroup } from './components/workspace_form/types'; +import { + ApplicationStart, + PublicAppInfo, + DEFAULT_APP_CATEGORIES, + AppNavLinkStatus, +} from '../../../core/public'; +import { isWorkspaceFeatureGroup } from './components/workspace_form/utils'; +import { DEFAULT_CHECKED_FEATURES_IDS } from '../common/constants'; export function useApplications(application: ApplicationStart) { const applications = useObservable(application.applications$); @@ -17,3 +27,62 @@ export function useApplications(application: ApplicationStart) { return apps; }, [applications]); } + +const libraryCategoryLabel = i18n.translate('core.ui.libraryNavList.label', { + defaultMessage: 'Library', +}); + +export function useFeatureOrGroups(applications: PublicAppInfo[]) { + return useMemo(() => { + const transformedApplications = applications.map((app) => { + if (app.category?.id === DEFAULT_APP_CATEGORIES.opensearchDashboards.id) { + return { + ...app, + category: { + ...app.category, + label: libraryCategoryLabel, + }, + }; + } + return app; + }); + const category2Applications = groupBy(transformedApplications, 'category.label'); + return Object.keys(category2Applications).reduce< + Array + >((previousValue, currentKey) => { + const apps = category2Applications[currentKey]; + const features = apps + .filter( + ({ navLinkStatus, chromeless, category }) => + navLinkStatus !== AppNavLinkStatus.hidden && + !chromeless && + category?.id !== DEFAULT_APP_CATEGORIES.management.id + ) + .map(({ id, title }) => ({ + id, + name: title, + })); + if (features.length === 0) { + return previousValue; + } + if (currentKey === 'undefined') { + return [...previousValue, ...features]; + } + return [ + ...previousValue, + { + name: apps[0].category?.label || '', + features, + }, + ]; + }, []); + }, [applications]); +} + +export function useFeaturesQuantities(application: ApplicationStart) { + const featureOrGroups = useFeatureOrGroups(useApplications(application)); + const totalQuantity = featureOrGroups.reduce((acc, cur) => { + return isWorkspaceFeatureGroup(cur) ? acc + cur.features.length : acc + 1; + }, 0); + return totalQuantity + DEFAULT_CHECKED_FEATURES_IDS.length; +} From 1397d48dbc0af5b99f1f09a462bb058c30f3404b Mon Sep 17 00:00:00 2001 From: tygao Date: Mon, 25 Mar 2024 17:27:07 +0800 Subject: [PATCH 2/7] update feature utils Signed-off-by: tygao --- src/plugins/workspace/common/constants.ts | 2 +- .../public/components/workspace_form/utils.ts | 69 ++++++++++++++- .../components/workspace_list/index.tsx | 10 +++ src/plugins/workspace/public/hooks.ts | 88 +++++-------------- 4 files changed, 97 insertions(+), 72 deletions(-) diff --git a/src/plugins/workspace/common/constants.ts b/src/plugins/workspace/common/constants.ts index 2be4a9d121db..1cd0b46a80f2 100644 --- a/src/plugins/workspace/common/constants.ts +++ b/src/plugins/workspace/common/constants.ts @@ -8,7 +8,7 @@ export const WORKSPACE_LIST_APP_ID = 'workspace_list'; export const WORKSPACE_UPDATE_APP_ID = 'workspace_update'; export const WORKSPACE_OVERVIEW_APP_ID = 'workspace_overview'; // These features will be checked and disabled in checkbox on default. -export const DEFAULT_CHECKED_FEATURES_IDS = [WORKSPACE_UPDATE_APP_ID, WORKSPACE_OVERVIEW_APP_ID]; +export const DEFAULT_SELECTED_FEATURES_IDS = [WORKSPACE_UPDATE_APP_ID, WORKSPACE_OVERVIEW_APP_ID]; export const WORKSPACE_FATAL_ERROR_APP_ID = 'workspace_fatal_error'; export const WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID = 'workspace'; export const WORKSPACE_CONFLICT_CONTROL_SAVED_OBJECTS_CLIENT_WRAPPER_ID = diff --git a/src/plugins/workspace/public/components/workspace_form/utils.ts b/src/plugins/workspace/public/components/workspace_form/utils.ts index 133a3bc563de..95a9ed24a85b 100644 --- a/src/plugins/workspace/public/components/workspace_form/utils.ts +++ b/src/plugins/workspace/public/components/workspace_form/utils.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { WorkspacePermissionMode, DEFAULT_CHECKED_FEATURES_IDS } from '../../../common/constants'; +import { WorkspacePermissionMode, DEFAULT_SELECTED_FEATURES_IDS } from '../../../common/constants'; import { WorkspaceFeature, @@ -16,6 +16,11 @@ import { optionIdToWorkspacePermissionModesMap, PermissionModeId, } from './constants'; +import { + AppNavLinkStatus, + DEFAULT_APP_CATEGORIES, + PublicAppInfo, +} from '../../../../../core/public'; export const isWorkspaceFeatureGroup = ( featureOrGroup: WorkspaceFeature | WorkspaceFeatureGroup @@ -30,12 +35,12 @@ export const isValidWorkspacePermissionSetting = ( (setting.type === WorkspacePermissionItemType.Group && !!setting.group)); export const isDefaultCheckedFeatureId = (id: string) => { - return DEFAULT_CHECKED_FEATURES_IDS.indexOf(id) > -1; + return DEFAULT_SELECTED_FEATURES_IDS.indexOf(id) > -1; }; export const appendDefaultFeatureIds = (ids: string[]) => { // concat default checked ids and unique the result - return Array.from(new Set(ids.concat(DEFAULT_CHECKED_FEATURES_IDS))); + return Array.from(new Set(ids.concat(DEFAULT_SELECTED_FEATURES_IDS))); }; export const isValidNameOrDescription = (input?: string) => { @@ -95,3 +100,61 @@ export const getPermissionModeId = (modes: WorkspacePermissionMode[]) => { } return PermissionModeId.Read; }; + +export const convertApplicationsToFeaturesOrGroups = ( + applications: Array< + Pick + > +) => { + const UNDEFINED = 'undefined'; + + // Filter out all hidden applications and management applications and default selected features + const visibleApplications = applications.filter( + ({ navLinkStatus, chromeless, category, id }) => + navLinkStatus !== AppNavLinkStatus.hidden && + !chromeless && + !DEFAULT_SELECTED_FEATURES_IDS.includes(id) && + category?.id !== DEFAULT_APP_CATEGORIES.management.id + ); + + /** + * + * Convert applications to features map, the map use category label as + * map key and group all same category applications in one array after + * transfer application to feature. + * + **/ + const categoryLabel2Features = visibleApplications.reduce<{ + [key: string]: WorkspaceFeature[]; + }>((previousValue, application) => { + const label = application.category?.label || UNDEFINED; + + return { + ...previousValue, + [label]: [...(previousValue[label] || []), { id: application.id, name: application.title }], + }; + }, {}); + + /** + * + * Iterate all keys of categoryLabel2Features map, convert map to features or groups array. + * Features with category label will be converted to feature groups. Features without "undefined" + * category label will be converted to single features. Then append them to the result array. + * + **/ + return Object.keys(categoryLabel2Features).reduce< + Array + >((previousValue, categoryLabel) => { + const features = categoryLabel2Features[categoryLabel]; + if (categoryLabel === UNDEFINED) { + return [...previousValue, ...features]; + } + return [ + ...previousValue, + { + name: categoryLabel, + features, + }, + ]; + }, []); +}; diff --git a/src/plugins/workspace/public/components/workspace_list/index.tsx b/src/plugins/workspace/public/components/workspace_list/index.tsx index bc92a01f8f58..5858860eb11d 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.tsx @@ -26,6 +26,8 @@ import { WORKSPACE_CREATE_APP_ID } from '../../../common/constants'; import { cleanWorkspaceId } from '../../../../../core/public'; import { DeleteWorkspaceModal } from '../delete_workspace_modal'; +import { useAllWorkspaceFeatures, useApplications } from '././../../hooks'; +import { convertApplicationsToFeaturesOrGroups } from '../workspace_form/utils'; const WORKSPACE_LIST_PAGE_DESCRIPTIOIN = i18n.translate('workspace.list.description', { defaultMessage: @@ -47,6 +49,10 @@ export const WorkspaceList = () => { pageSizeOptions: [5, 10, 20], }); const [deletedWorkspace, setDeletedWorkspace] = useState(null); + const workspaceFeaturesOrGroups = convertApplicationsToFeaturesOrGroups( + useApplications(application) + ); + const allFeatureIds = useAllWorkspaceFeatures(workspaceFeaturesOrGroups); const handleSwitchWorkspace = useCallback( (id: string) => { @@ -106,6 +112,10 @@ export const WorkspaceList = () => { name: 'Features', isExpander: true, hasActions: true, + render: (features: string[]) => { + const quantity = allFeatureIds.filter((id) => features.indexOf(id) > -1).length; + return `${quantity}/${allFeatureIds.length}`; + }, }, { name: 'Actions', diff --git a/src/plugins/workspace/public/hooks.ts b/src/plugins/workspace/public/hooks.ts index ea78292085aa..30b07375880d 100644 --- a/src/plugins/workspace/public/hooks.ts +++ b/src/plugins/workspace/public/hooks.ts @@ -5,20 +5,14 @@ import { useObservable } from 'react-use'; import { useMemo } from 'react'; -import { groupBy } from 'lodash'; -import { i18n } from '@osd/i18n'; import { WorkspaceFeature, WorkspaceFeatureGroup } from './components/workspace_form/types'; -import { - ApplicationStart, - PublicAppInfo, - DEFAULT_APP_CATEGORIES, - AppNavLinkStatus, -} from '../../../core/public'; +import { ApplicationStart, PublicAppInfo } from '../../../core/public'; import { isWorkspaceFeatureGroup } from './components/workspace_form/utils'; -import { DEFAULT_CHECKED_FEATURES_IDS } from '../common/constants'; +import { DEFAULT_SELECTED_FEATURES_IDS } from '../common/constants'; +import { of } from 'rxjs'; -export function useApplications(application: ApplicationStart) { - const applications = useObservable(application.applications$); +export function useApplications(application?: ApplicationStart) { + const applications = useObservable(application?.applications$ ?? of(new Map()), new Map()); return useMemo(() => { const apps: PublicAppInfo[] = []; applications?.forEach((app) => { @@ -28,61 +22,19 @@ export function useApplications(application: ApplicationStart) { }, [applications]); } -const libraryCategoryLabel = i18n.translate('core.ui.libraryNavList.label', { - defaultMessage: 'Library', -}); - -export function useFeatureOrGroups(applications: PublicAppInfo[]) { - return useMemo(() => { - const transformedApplications = applications.map((app) => { - if (app.category?.id === DEFAULT_APP_CATEGORIES.opensearchDashboards.id) { - return { - ...app, - category: { - ...app.category, - label: libraryCategoryLabel, - }, - }; - } - return app; - }); - const category2Applications = groupBy(transformedApplications, 'category.label'); - return Object.keys(category2Applications).reduce< - Array - >((previousValue, currentKey) => { - const apps = category2Applications[currentKey]; - const features = apps - .filter( - ({ navLinkStatus, chromeless, category }) => - navLinkStatus !== AppNavLinkStatus.hidden && - !chromeless && - category?.id !== DEFAULT_APP_CATEGORIES.management.id - ) - .map(({ id, title }) => ({ - id, - name: title, - })); - if (features.length === 0) { - return previousValue; - } - if (currentKey === 'undefined') { - return [...previousValue, ...features]; - } - return [ - ...previousValue, - { - name: apps[0].category?.label || '', - features, - }, - ]; - }, []); - }, [applications]); -} - -export function useFeaturesQuantities(application: ApplicationStart) { - const featureOrGroups = useFeatureOrGroups(useApplications(application)); - const totalQuantity = featureOrGroups.reduce((acc, cur) => { - return isWorkspaceFeatureGroup(cur) ? acc + cur.features.length : acc + 1; - }, 0); - return totalQuantity + DEFAULT_CHECKED_FEATURES_IDS.length; +export function useAllWorkspaceFeatures( + workspaceFeatureOrGroup: Array +) { + const registerFeatureIds = workspaceFeatureOrGroup.reduce((acc, cur) => { + if (isWorkspaceFeatureGroup(cur)) { + const ids = cur.features.map((feature) => { + return feature.id; + }); + return acc.concat(ids); + } else { + acc.push(cur.id); + return acc; + } + }, []); + return registerFeatureIds.concat(DEFAULT_SELECTED_FEATURES_IDS); } From e5311c3006eeb497b5c5e94d8b63d68ff36eec8a Mon Sep 17 00:00:00 2001 From: tygao Date: Mon, 25 Mar 2024 17:27:16 +0800 Subject: [PATCH 3/7] update feature utils Signed-off-by: tygao --- src/plugins/workspace/public/hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/workspace/public/hooks.ts b/src/plugins/workspace/public/hooks.ts index 30b07375880d..1cb3b170e3ad 100644 --- a/src/plugins/workspace/public/hooks.ts +++ b/src/plugins/workspace/public/hooks.ts @@ -5,11 +5,11 @@ import { useObservable } from 'react-use'; import { useMemo } from 'react'; +import { of } from 'rxjs'; import { WorkspaceFeature, WorkspaceFeatureGroup } from './components/workspace_form/types'; import { ApplicationStart, PublicAppInfo } from '../../../core/public'; import { isWorkspaceFeatureGroup } from './components/workspace_form/utils'; import { DEFAULT_SELECTED_FEATURES_IDS } from '../common/constants'; -import { of } from 'rxjs'; export function useApplications(application?: ApplicationStart) { const applications = useObservable(application?.applications$ ?? of(new Map()), new Map()); From 6ee592c09a5997c4b88dac8b95aaf497d35bf777 Mon Sep 17 00:00:00 2001 From: tygao Date: Tue, 26 Mar 2024 18:14:13 +0800 Subject: [PATCH 4/7] use featureMatchConfig Signed-off-by: tygao --- .../components/workspace_list/index.tsx | 13 +++++------- src/plugins/workspace/public/hooks.ts | 20 ------------------- src/plugins/workspace/public/utils.ts | 17 +++++++++++++++- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/plugins/workspace/public/components/workspace_list/index.tsx b/src/plugins/workspace/public/components/workspace_list/index.tsx index 5858860eb11d..aac721c236a6 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.tsx @@ -26,8 +26,8 @@ import { WORKSPACE_CREATE_APP_ID } from '../../../common/constants'; import { cleanWorkspaceId } from '../../../../../core/public'; import { DeleteWorkspaceModal } from '../delete_workspace_modal'; -import { useAllWorkspaceFeatures, useApplications } from '././../../hooks'; -import { convertApplicationsToFeaturesOrGroups } from '../workspace_form/utils'; +import { useApplications } from '././../../hooks'; +import { getSelectedFeatureQuantities } from '../../utils'; const WORKSPACE_LIST_PAGE_DESCRIPTIOIN = i18n.translate('workspace.list.description', { defaultMessage: @@ -49,10 +49,7 @@ export const WorkspaceList = () => { pageSizeOptions: [5, 10, 20], }); const [deletedWorkspace, setDeletedWorkspace] = useState(null); - const workspaceFeaturesOrGroups = convertApplicationsToFeaturesOrGroups( - useApplications(application) - ); - const allFeatureIds = useAllWorkspaceFeatures(workspaceFeaturesOrGroups); + const applications = useApplications(application); const handleSwitchWorkspace = useCallback( (id: string) => { @@ -113,8 +110,8 @@ export const WorkspaceList = () => { isExpander: true, hasActions: true, render: (features: string[]) => { - const quantity = allFeatureIds.filter((id) => features.indexOf(id) > -1).length; - return `${quantity}/${allFeatureIds.length}`; + const { total, selected } = getSelectedFeatureQuantities(features, applications); + return `${selected}/${total}`; }, }, { diff --git a/src/plugins/workspace/public/hooks.ts b/src/plugins/workspace/public/hooks.ts index 1cb3b170e3ad..c2c08438121d 100644 --- a/src/plugins/workspace/public/hooks.ts +++ b/src/plugins/workspace/public/hooks.ts @@ -6,10 +6,7 @@ import { useObservable } from 'react-use'; import { useMemo } from 'react'; import { of } from 'rxjs'; -import { WorkspaceFeature, WorkspaceFeatureGroup } from './components/workspace_form/types'; import { ApplicationStart, PublicAppInfo } from '../../../core/public'; -import { isWorkspaceFeatureGroup } from './components/workspace_form/utils'; -import { DEFAULT_SELECTED_FEATURES_IDS } from '../common/constants'; export function useApplications(application?: ApplicationStart) { const applications = useObservable(application?.applications$ ?? of(new Map()), new Map()); @@ -21,20 +18,3 @@ export function useApplications(application?: ApplicationStart) { return apps; }, [applications]); } - -export function useAllWorkspaceFeatures( - workspaceFeatureOrGroup: Array -) { - const registerFeatureIds = workspaceFeatureOrGroup.reduce((acc, cur) => { - if (isWorkspaceFeatureGroup(cur)) { - const ids = cur.features.map((feature) => { - return feature.id; - }); - return acc.concat(ids); - } else { - acc.push(cur.id); - return acc; - } - }, []); - return registerFeatureIds.concat(DEFAULT_SELECTED_FEATURES_IDS); -} diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index 444b3aadadf3..bf99f66c2672 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { AppCategory } from '../../../core/public'; +import { AppCategory, PublicAppInfo, AppNavLinkStatus } from '../../../core/public'; /** * Checks if a given feature matches the provided feature configuration. @@ -55,3 +55,18 @@ export const featureMatchesConfig = (featureConfigs: string[]) => ({ return matched; }; + +export const getSelectedFeatureQuantities = ( + featuresConfig: string[], + applications: PublicAppInfo[] +) => { + const visibleApplications = applications.filter( + ({ navLinkStatus, chromeless }) => navLinkStatus !== AppNavLinkStatus.hidden && !chromeless + ); + const featureFilter = featureMatchesConfig(featuresConfig); + const selectedApplications = visibleApplications.filter((app) => featureFilter(app)); + return { + total: visibleApplications.length, + selected: selectedApplications.length, + }; +}; From 3a5648d9c14e301e191cac9cca4450b4e2c42afc Mon Sep 17 00:00:00 2001 From: tygao Date: Wed, 27 Mar 2024 11:40:26 +0800 Subject: [PATCH 5/7] update Signed-off-by: tygao --- src/plugins/workspace/common/constants.ts | 2 +- .../public/components/workspace_form/utils.ts | 69 +------------------ 2 files changed, 4 insertions(+), 67 deletions(-) diff --git a/src/plugins/workspace/common/constants.ts b/src/plugins/workspace/common/constants.ts index 1cd0b46a80f2..2be4a9d121db 100644 --- a/src/plugins/workspace/common/constants.ts +++ b/src/plugins/workspace/common/constants.ts @@ -8,7 +8,7 @@ export const WORKSPACE_LIST_APP_ID = 'workspace_list'; export const WORKSPACE_UPDATE_APP_ID = 'workspace_update'; export const WORKSPACE_OVERVIEW_APP_ID = 'workspace_overview'; // These features will be checked and disabled in checkbox on default. -export const DEFAULT_SELECTED_FEATURES_IDS = [WORKSPACE_UPDATE_APP_ID, WORKSPACE_OVERVIEW_APP_ID]; +export const DEFAULT_CHECKED_FEATURES_IDS = [WORKSPACE_UPDATE_APP_ID, WORKSPACE_OVERVIEW_APP_ID]; export const WORKSPACE_FATAL_ERROR_APP_ID = 'workspace_fatal_error'; export const WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID = 'workspace'; export const WORKSPACE_CONFLICT_CONTROL_SAVED_OBJECTS_CLIENT_WRAPPER_ID = diff --git a/src/plugins/workspace/public/components/workspace_form/utils.ts b/src/plugins/workspace/public/components/workspace_form/utils.ts index 95a9ed24a85b..133a3bc563de 100644 --- a/src/plugins/workspace/public/components/workspace_form/utils.ts +++ b/src/plugins/workspace/public/components/workspace_form/utils.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { WorkspacePermissionMode, DEFAULT_SELECTED_FEATURES_IDS } from '../../../common/constants'; +import { WorkspacePermissionMode, DEFAULT_CHECKED_FEATURES_IDS } from '../../../common/constants'; import { WorkspaceFeature, @@ -16,11 +16,6 @@ import { optionIdToWorkspacePermissionModesMap, PermissionModeId, } from './constants'; -import { - AppNavLinkStatus, - DEFAULT_APP_CATEGORIES, - PublicAppInfo, -} from '../../../../../core/public'; export const isWorkspaceFeatureGroup = ( featureOrGroup: WorkspaceFeature | WorkspaceFeatureGroup @@ -35,12 +30,12 @@ export const isValidWorkspacePermissionSetting = ( (setting.type === WorkspacePermissionItemType.Group && !!setting.group)); export const isDefaultCheckedFeatureId = (id: string) => { - return DEFAULT_SELECTED_FEATURES_IDS.indexOf(id) > -1; + return DEFAULT_CHECKED_FEATURES_IDS.indexOf(id) > -1; }; export const appendDefaultFeatureIds = (ids: string[]) => { // concat default checked ids and unique the result - return Array.from(new Set(ids.concat(DEFAULT_SELECTED_FEATURES_IDS))); + return Array.from(new Set(ids.concat(DEFAULT_CHECKED_FEATURES_IDS))); }; export const isValidNameOrDescription = (input?: string) => { @@ -100,61 +95,3 @@ export const getPermissionModeId = (modes: WorkspacePermissionMode[]) => { } return PermissionModeId.Read; }; - -export const convertApplicationsToFeaturesOrGroups = ( - applications: Array< - Pick - > -) => { - const UNDEFINED = 'undefined'; - - // Filter out all hidden applications and management applications and default selected features - const visibleApplications = applications.filter( - ({ navLinkStatus, chromeless, category, id }) => - navLinkStatus !== AppNavLinkStatus.hidden && - !chromeless && - !DEFAULT_SELECTED_FEATURES_IDS.includes(id) && - category?.id !== DEFAULT_APP_CATEGORIES.management.id - ); - - /** - * - * Convert applications to features map, the map use category label as - * map key and group all same category applications in one array after - * transfer application to feature. - * - **/ - const categoryLabel2Features = visibleApplications.reduce<{ - [key: string]: WorkspaceFeature[]; - }>((previousValue, application) => { - const label = application.category?.label || UNDEFINED; - - return { - ...previousValue, - [label]: [...(previousValue[label] || []), { id: application.id, name: application.title }], - }; - }, {}); - - /** - * - * Iterate all keys of categoryLabel2Features map, convert map to features or groups array. - * Features with category label will be converted to feature groups. Features without "undefined" - * category label will be converted to single features. Then append them to the result array. - * - **/ - return Object.keys(categoryLabel2Features).reduce< - Array - >((previousValue, categoryLabel) => { - const features = categoryLabel2Features[categoryLabel]; - if (categoryLabel === UNDEFINED) { - return [...previousValue, ...features]; - } - return [ - ...previousValue, - { - name: categoryLabel, - features, - }, - ]; - }, []); -}; From ff16c79f02976f8aa04eb0a29c3df017e3cfcfe0 Mon Sep 17 00:00:00 2001 From: tygao Date: Wed, 27 Mar 2024 16:24:43 +0800 Subject: [PATCH 6/7] add util test Signed-off-by: tygao --- src/plugins/workspace/public/hooks.ts | 2 +- src/plugins/workspace/public/utils.test.ts | 54 +++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/plugins/workspace/public/hooks.ts b/src/plugins/workspace/public/hooks.ts index c2c08438121d..a63dc8f83d3d 100644 --- a/src/plugins/workspace/public/hooks.ts +++ b/src/plugins/workspace/public/hooks.ts @@ -12,7 +12,7 @@ export function useApplications(application?: ApplicationStart) { const applications = useObservable(application?.applications$ ?? of(new Map()), new Map()); return useMemo(() => { const apps: PublicAppInfo[] = []; - applications?.forEach((app) => { + applications.forEach((app) => { apps.push(app); }); return apps; diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts index 510a775cd745..17bfc65ad04a 100644 --- a/src/plugins/workspace/public/utils.test.ts +++ b/src/plugins/workspace/public/utils.test.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { featureMatchesConfig } from './utils'; +import { featureMatchesConfig, getSelectedFeatureQuantities } from './utils'; +import { PublicAppInfo } from '../../../core/public'; describe('workspace utils: featureMatchesConfig', () => { it('feature configured with `*` should match any features', () => { @@ -91,3 +92,54 @@ describe('workspace utils: featureMatchesConfig', () => { ); }); }); + +describe('workspace utils: getSelectedFeatureQuantities', () => { + const defaultApplications = [ + { + appRoute: '/app/dashboards', + id: 'dashboards', + title: 'Dashboards', + category: { + id: 'opensearchDashboards', + label: 'OpenSearch Dashboards', + euiIconType: 'inputOutput', + order: 1000, + }, + status: 0, + navLinkStatus: 1, + }, + { + appRoute: '/app/dev_tools', + id: 'dev_tools', + title: 'Dev Tools', + category: { + id: 'management', + label: 'Management', + order: 5000, + euiIconType: 'managementApp', + }, + status: 0, + navLinkStatus: 1, + }, + ] as PublicAppInfo[]; + it('should support * rules', () => { + const { total, selected } = getSelectedFeatureQuantities(['*'], defaultApplications); + expect(total).toBe(2); + expect(selected).toBe(2); + }); + + it('should support @ and exclude rule', () => { + const { total, selected } = getSelectedFeatureQuantities(['!@management'], defaultApplications); + expect(total).toBe(2); + expect(selected).toBe(0); + }); + + it('should get quantity normally', () => { + const { total, selected } = getSelectedFeatureQuantities( + ['!@management', 'dev_tools'], + defaultApplications + ); + expect(total).toBe(2); + expect(selected).toBe(1); + }); +}); From 4b63b854464b7300254909b9204045791e1fb190 Mon Sep 17 00:00:00 2001 From: tygao Date: Tue, 2 Apr 2024 14:27:03 +0800 Subject: [PATCH 7/7] unite feature set Signed-off-by: tygao --- .../workspace_feature_selector.tsx | 22 +++++-------------- src/plugins/workspace/public/utils.test.ts | 20 ++++++----------- src/plugins/workspace/public/utils.ts | 21 ++++++++++++++---- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_feature_selector.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_feature_selector.tsx index 61181a7a749e..e172b851f1f3 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_feature_selector.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_feature_selector.tsx @@ -16,14 +16,11 @@ import { import { i18n } from '@osd/i18n'; import { groupBy } from 'lodash'; -import { - AppNavLinkStatus, - DEFAULT_APP_CATEGORIES, - PublicAppInfo, -} from '../../../../../core/public'; +import { DEFAULT_APP_CATEGORIES, PublicAppInfo } from '../../../../../core/public'; import { WorkspaceFeature, WorkspaceFeatureGroup } from './types'; import { isDefaultCheckedFeatureId, isWorkspaceFeatureGroup } from './utils'; +import { getAllExcludingManagementApps } from '../../utils'; const libraryCategoryLabel = i18n.translate('core.ui.libraryNavList.label', { defaultMessage: 'Library', @@ -58,17 +55,10 @@ export const WorkspaceFeatureSelector = ({ Array >((previousValue, currentKey) => { const apps = category2Applications[currentKey]; - const features = apps - .filter( - ({ navLinkStatus, chromeless, category }) => - navLinkStatus !== AppNavLinkStatus.hidden && - !chromeless && - category?.id !== DEFAULT_APP_CATEGORIES.management.id - ) - .map(({ id, title }) => ({ - id, - name: title, - })); + const features = getAllExcludingManagementApps(apps).map(({ id, title }) => ({ + id, + name: title, + })); if (features.length === 0) { return previousValue; } diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts index 17bfc65ad04a..5ce89d9fffc6 100644 --- a/src/plugins/workspace/public/utils.test.ts +++ b/src/plugins/workspace/public/utils.test.ts @@ -122,24 +122,18 @@ describe('workspace utils: getSelectedFeatureQuantities', () => { navLinkStatus: 1, }, ] as PublicAppInfo[]; - it('should support * rules', () => { + it('should support * rules and exclude management category', () => { const { total, selected } = getSelectedFeatureQuantities(['*'], defaultApplications); - expect(total).toBe(2); - expect(selected).toBe(2); - }); - - it('should support @ and exclude rule', () => { - const { total, selected } = getSelectedFeatureQuantities(['!@management'], defaultApplications); - expect(total).toBe(2); - expect(selected).toBe(0); + expect(total).toBe(1); + expect(selected).toBe(1); }); - it('should get quantity normally', () => { + it('should get quantity normally and exclude management category', () => { const { total, selected } = getSelectedFeatureQuantities( - ['!@management', 'dev_tools'], + ['dev_tools', '!@management'], defaultApplications ); - expect(total).toBe(2); - expect(selected).toBe(1); + expect(total).toBe(1); + expect(selected).toBe(0); }); }); diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index bf99f66c2672..2c0ad62d7775 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -3,7 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { AppCategory, PublicAppInfo, AppNavLinkStatus } from '../../../core/public'; +import { + AppCategory, + PublicAppInfo, + AppNavLinkStatus, + DEFAULT_APP_CATEGORIES, +} from '../../../core/public'; /** * Checks if a given feature matches the provided feature configuration. @@ -56,13 +61,21 @@ export const featureMatchesConfig = (featureConfigs: string[]) => ({ return matched; }; +// Get all apps excluding management category +export const getAllExcludingManagementApps = (applications: PublicAppInfo[]): PublicAppInfo[] => { + return applications.filter( + ({ navLinkStatus, chromeless, category }) => + navLinkStatus !== AppNavLinkStatus.hidden && + !chromeless && + category?.id !== DEFAULT_APP_CATEGORIES.management.id + ); +}; + export const getSelectedFeatureQuantities = ( featuresConfig: string[], applications: PublicAppInfo[] ) => { - const visibleApplications = applications.filter( - ({ navLinkStatus, chromeless }) => navLinkStatus !== AppNavLinkStatus.hidden && !chromeless - ); + const visibleApplications = getAllExcludingManagementApps(applications); const featureFilter = featureMatchesConfig(featuresConfig); const selectedApplications = visibleApplications.filter((app) => featureFilter(app)); return {