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/components/workspace_list/index.tsx b/src/plugins/workspace/public/components/workspace_list/index.tsx index bc92a01f8f58..aac721c236a6 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 { useApplications } from '././../../hooks'; +import { getSelectedFeatureQuantities } from '../../utils'; const WORKSPACE_LIST_PAGE_DESCRIPTIOIN = i18n.translate('workspace.list.description', { defaultMessage: @@ -47,6 +49,7 @@ export const WorkspaceList = () => { pageSizeOptions: [5, 10, 20], }); const [deletedWorkspace, setDeletedWorkspace] = useState(null); + const applications = useApplications(application); const handleSwitchWorkspace = useCallback( (id: string) => { @@ -106,6 +109,10 @@ export const WorkspaceList = () => { name: 'Features', isExpander: true, hasActions: true, + render: (features: string[]) => { + const { total, selected } = getSelectedFeatureQuantities(features, applications); + return `${selected}/${total}`; + }, }, { name: 'Actions', diff --git a/src/plugins/workspace/public/hooks.ts b/src/plugins/workspace/public/hooks.ts index e84ee46507ef..a63dc8f83d3d 100644 --- a/src/plugins/workspace/public/hooks.ts +++ b/src/plugins/workspace/public/hooks.ts @@ -3,15 +3,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ApplicationStart, PublicAppInfo } from 'opensearch-dashboards/public'; import { useObservable } from 'react-use'; import { useMemo } from 'react'; +import { of } from 'rxjs'; +import { ApplicationStart, PublicAppInfo } from '../../../core/public'; -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) => { + 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..5ce89d9fffc6 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,48 @@ 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 and exclude management category', () => { + const { total, selected } = getSelectedFeatureQuantities(['*'], defaultApplications); + expect(total).toBe(1); + expect(selected).toBe(1); + }); + + it('should get quantity normally and exclude management category', () => { + const { total, selected } = getSelectedFeatureQuantities( + ['dev_tools', '!@management'], + defaultApplications + ); + 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 444b3aadadf3..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 } from '../../../core/public'; +import { + AppCategory, + PublicAppInfo, + AppNavLinkStatus, + DEFAULT_APP_CATEGORIES, +} from '../../../core/public'; /** * Checks if a given feature matches the provided feature configuration. @@ -55,3 +60,26 @@ 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 = getAllExcludingManagementApps(applications); + const featureFilter = featureMatchesConfig(featuresConfig); + const selectedApplications = visibleApplications.filter((app) => featureFilter(app)); + return { + total: visibleApplications.length, + selected: selectedApplications.length, + }; +};