+ )}
+ {
+ // This element is used to push icons to the bottom of left navigation when collapsed
+ !isNavOpen ?
', () => {
title: '',
...navLink,
});
- const mockedNavLinks = [
- getMockedNavLink({
- id: 'home',
- title: 'home link',
- }),
- getMockedNavLink({
- id: 'subLink',
- title: 'subLink',
- parentNavLinkId: 'pure',
- }),
- getMockedNavLink({
- id: 'link-in-category',
- title: 'link-in-category',
- category: {
- id: 'category-1',
- label: 'category-1',
- },
- }),
- getMockedNavLink({
- id: 'link-in-category-2',
- title: 'link-in-category-2',
- category: {
- id: 'category-1',
- label: 'category-1',
- },
- }),
- getMockedNavLink({
- id: 'sub-link-in-category',
- title: 'sub-link-in-category',
- parentNavLinkId: 'link-in-category',
- category: {
- id: 'category-1',
- label: 'category-1',
- },
- }),
- ];
const getMockedProps = () => {
return {
- navLinks: mockedNavLinks,
+ homeLink: getMockedNavLink({ id: 'home', title: 'Home', href: '/' }),
navigateToApp: jest.fn(),
logos: getLogos({}, mockBasePath.serverBasePath),
shouldShrinkNavigation: false,
visibleUseCases: [],
+ currentWorkspace$: new BehaviorSubject
(null),
+ setCurrentNavGroup: jest.fn(),
};
};
- it('should render home icon', async () => {
- const { findByTestId } = render();
- await findByTestId('collapsibleNavHome');
- });
- it('should render back icon', async () => {
- const { findByTestId, findByText } = render(
- {
+ const props = {
+ ...getMockedProps(),
+ currentWorkspace$: new BehaviorSubject({ id: 'foo', name: 'foo' }),
+ visibleUseCases: [
+ {
id: 'navGroupFoo',
title: 'navGroupFoo',
description: 'navGroupFoo',
navLinks: [],
- }}
- />
- );
+ },
+ {
+ id: 'navGroupBar',
+ title: 'navGroupBar',
+ description: 'navGroupBar',
+ navLinks: [],
+ },
+ ],
+ currentNavGroup: {
+ id: 'navGroupFoo',
+ title: 'navGroupFoo',
+ description: 'navGroupFoo',
+ navLinks: [],
+ },
+ firstVisibleNavLinkOfAllUseCase: getMockedNavLink({
+ id: 'firstVisibleNavLinkOfAllUseCase',
+ }),
+ };
+ const { findByTestId, findByText, getByTestId } = render();
await findByTestId('collapsibleNavBackButton');
await findByText('Back');
+ fireEvent.click(getByTestId('collapsibleNavBackButton'));
+ expect(props.navigateToApp).toBeCalledWith('firstVisibleNavLinkOfAllUseCase');
+ expect(props.setCurrentNavGroup).toBeCalledWith(ALL_USE_CASE_ID);
});
- it('should render back home icon', async () => {
- const { findByTestId, findByText } = render(
-
- );
- await findByTestId('collapsibleNavBackButton');
- await findByText('Home');
+ it('should render home icon when not in a workspace', async () => {
+ const { findByTestId } = render();
+ await findByTestId('collapsibleNavHome');
});
- it('should render expand icon', async () => {
+ it('should render expand icon when collapsed', async () => {
const { findByTestId } = render(
);
diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx
index 9809e3724454..23e2f7e6108c 100644
--- a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx
+++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx
@@ -4,7 +4,8 @@
*/
import React, { useMemo } from 'react';
-import { Logos } from 'opensearch-dashboards/public';
+import useObservable from 'react-use/lib/useObservable';
+import { Logos, WorkspacesStart } from 'opensearch-dashboards/public';
import {
EuiButtonEmpty,
EuiButtonIcon,
@@ -12,64 +13,53 @@ import {
EuiFlexItem,
EuiIcon,
EuiSpacer,
+ EuiText,
} from '@elastic/eui';
import { InternalApplicationStart } from 'src/core/public/application';
import { i18n } from '@osd/i18n';
import { createEuiListItem } from './nav_link';
-import { NavGroupItemInMap } from '../../nav_group';
+import { ChromeNavGroupServiceStartContract, NavGroupItemInMap } from '../../nav_group';
import { ChromeNavLink } from '../../nav_links';
import { ALL_USE_CASE_ID } from '../../../../../core/utils';
export interface CollapsibleNavTopProps {
- navLinks: ChromeNavLink[];
+ homeLink?: ChromeNavLink;
+ firstVisibleNavLinkOfAllUseCase?: ChromeNavLink;
currentNavGroup?: NavGroupItemInMap;
navigateToApp: InternalApplicationStart['navigateToApp'];
logos: Logos;
- onClickBack?: () => void;
onClickShrink?: () => void;
shouldShrinkNavigation: boolean;
visibleUseCases: NavGroupItemInMap[];
+ currentWorkspace$: WorkspacesStart['currentWorkspace$'];
+ setCurrentNavGroup: ChromeNavGroupServiceStartContract['setCurrentNavGroup'];
}
export const CollapsibleNavTop = ({
- navLinks,
currentNavGroup,
navigateToApp,
logos,
- onClickBack,
onClickShrink,
shouldShrinkNavigation,
visibleUseCases,
+ currentWorkspace$,
+ setCurrentNavGroup,
+ homeLink,
+ firstVisibleNavLinkOfAllUseCase,
}: CollapsibleNavTopProps) => {
- const homeLink = useMemo(() => navLinks.find((link) => link.id === 'home'), [navLinks]);
+ const currentWorkspace = useObservable(currentWorkspace$);
- const isOutsideWorkspace = useMemo(
- () => !visibleUseCases.find((useCase) => useCase.id === currentNavGroup?.id),
- [currentNavGroup, visibleUseCases]
- );
-
- const shouldShowBackButton = useMemo(() => {
- if (!currentNavGroup || currentNavGroup.id === ALL_USE_CASE_ID || shouldShrinkNavigation) {
- return false;
- }
-
- // It means user is in a specific type of workspace
- if (visibleUseCases.length <= 1) {
- return false;
- }
-
- if (isOutsideWorkspace) {
- return true;
- }
-
- return visibleUseCases.length > 1;
- }, [visibleUseCases, currentNavGroup, shouldShrinkNavigation, isOutsideWorkspace]);
-
- const shouldShowHomeLink = useMemo(() => {
- if (!homeLink || shouldShrinkNavigation) return false;
+ /**
+ * We can ensure that left nav is inside second level once all the following conditions are met:
+ * 1. Inside a workspace
+ * 2. The use case type of current workspace is all use case
+ * 3. current nav group is not all use case
+ */
+ const isInsideSecondLevelOfAllWorkspace =
+ visibleUseCases.length > 1 && !!currentWorkspace && currentNavGroup?.id !== ALL_USE_CASE_ID;
- return !shouldShowBackButton;
- }, [shouldShowBackButton, homeLink, shouldShrinkNavigation]);
+ const shouldShowBackButton = !shouldShrinkNavigation && isInsideSecondLevelOfAllWorkspace;
+ const shouldShowHomeLink = !shouldShrinkNavigation && !shouldShowBackButton;
const homeLinkProps = useMemo(() => {
if (homeLink) {
@@ -90,8 +80,8 @@ export const CollapsibleNavTop = ({
}, [homeLink, navigateToApp]);
return (
-
-
+
+
{shouldShowHomeLink ? (
@@ -103,17 +93,18 @@ export const CollapsibleNavTop = ({
{
+ if (firstVisibleNavLinkOfAllUseCase) {
+ navigateToApp(firstVisibleNavLinkOfAllUseCase.id);
+ }
+ setCurrentNavGroup(ALL_USE_CASE_ID);
+ }}
data-test-subj="collapsibleNavBackButton"
>
- {isOutsideWorkspace
- ? i18n.translate('core.ui.primaryNav.homeButtonLabel', {
- defaultMessage: 'Home',
- })
- : i18n.translate('core.ui.primaryNav.backButtonLabel', {
- defaultMessage: 'Back',
- })}
+ {i18n.translate('core.ui.primaryNav.backButtonLabel', {
+ defaultMessage: 'Back',
+ })}
) : null}
@@ -128,7 +119,16 @@ export const CollapsibleNavTop = ({
/>
-
+ {currentNavGroup?.title && (
+ <>
+
+
+
+ {currentNavGroup?.title}
+
+
+ >
+ )}
);
};
diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx
index d4aa686d6ff8..9cc8652c3e41 100644
--- a/src/core/public/chrome/ui/header/header.test.tsx
+++ b/src/core/public/chrome/ui/header/header.test.tsx
@@ -37,6 +37,7 @@ import { StubBrowserStorage } from 'test_utils/stub_browser_storage';
import { httpServiceMock } from '../../../http/http_service.mock';
import { applicationServiceMock, chromeServiceMock } from '../../../mocks';
import { ISidecarConfig, SIDECAR_DOCKED_MODE } from '../../../overlays';
+import { WorkspaceObject } from 'src/core/public/workspace';
import { HeaderVariant } from '../../constants';
import { Header } from './header';
@@ -87,6 +88,7 @@ function mockProps() {
navControlsLeftBottom$: new BehaviorSubject([]),
setCurrentNavGroup: jest.fn(() => {}),
workspaceList$: new BehaviorSubject([]),
+ currentWorkspace$: new BehaviorSubject(null),
useUpdatedHeader: false,
};
}
diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx
index 9bffb2f0c406..4dfe57ecd053 100644
--- a/src/core/public/chrome/ui/header/header.tsx
+++ b/src/core/public/chrome/ui/header/header.tsx
@@ -54,7 +54,7 @@ import {
HeaderVariant,
} from '../..';
import type { Logos } from '../../../../common/types';
-import { WorkspaceObject } from '../../../../public/workspace';
+import { WorkspaceObject, WorkspacesStart } from '../../../../public/workspace';
import { InternalApplicationStart } from '../../../application/types';
import { HttpStart } from '../../../http';
import { getOsdSidecarPaddingStyle, ISidecarConfig } from '../../../overlays';
@@ -115,6 +115,7 @@ export interface HeaderProps {
navGroupsMap$: Observable>;
setCurrentNavGroup: ChromeNavGroupServiceStartContract['setCurrentNavGroup'];
workspaceList$: Observable;
+ currentWorkspace$: WorkspacesStart['currentWorkspace$'];
useUpdatedHeader?: boolean;
}
@@ -497,6 +498,7 @@ export function Header({
currentNavGroup$={observables.currentNavGroup$}
setCurrentNavGroup={setCurrentNavGroup}
capabilities={application.capabilities}
+ currentWorkspace$={observables.currentWorkspace$}
/>
) : (
= Object.freeze
}),
order: 1000,
},
+ manageWorkspace: {
+ id: 'manageWorkspace',
+ label: i18n.translate('core.ui.manageWorkspaceNav.label', {
+ defaultMessage: 'Manage workspace',
+ }),
+ order: 8000,
+ },
});
diff --git a/src/core/utils/default_nav_groups.ts b/src/core/utils/default_nav_groups.ts
index 5565bce3c0f5..64fea126ff3e 100644
--- a/src/core/utils/default_nav_groups.ts
+++ b/src/core/utils/default_nav_groups.ts
@@ -40,7 +40,6 @@ const defaultNavGroups = {
defaultMessage: 'This is a use case contains all the features.',
}),
order: 3000,
- type: NavGroupType.SYSTEM,
},
observability: {
id: 'observability',
diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts
index 73a1ce244113..df8734363920 100644
--- a/src/plugins/advanced_settings/public/plugin.ts
+++ b/src/plugins/advanced_settings/public/plugin.ts
@@ -70,6 +70,9 @@ export class AdvancedSettingsPlugin
? AppNavLinkStatus.visible
: AppNavLinkStatus.hidden,
workspaceAvailability: WorkspaceAvailability.outsideWorkspace,
+ description: i18n.translate('advancedSettings.description', {
+ defaultMessage: 'Customize the appearance and behavior of OpenSearch Dashboards.',
+ }),
mount: async (params: AppMountParameters) => {
const { mountManagementSection } = await import(
'./management_app/mount_management_section'
diff --git a/src/plugins/data_source_management/public/plugin.ts b/src/plugins/data_source_management/public/plugin.ts
index 0288249ae6b2..4caf0fe755d1 100644
--- a/src/plugins/data_source_management/public/plugin.ts
+++ b/src/plugins/data_source_management/public/plugin.ts
@@ -4,6 +4,7 @@
*/
import React from 'react';
+import { i18n } from '@osd/i18n';
import { DataSourcePluginSetup } from 'src/plugins/data_source/public';
import {
AppMountParameters,
@@ -93,6 +94,9 @@ export interface DataSourceManagementPluginStart {
getAuthenticationMethodRegistry: () => IAuthenticationMethodRegistry;
}
+/**
+ * The id is used in src/plugins/workspace/public/plugin.ts and please change that accordingly if you change the id here.
+ */
export const DSM_APP_ID = 'dataSources';
export class DataSourceManagementPlugin
@@ -145,6 +149,8 @@ export class DataSourceManagementPlugin
/**
* The data sources features in observability has the same name as `DSM_APP_ID`
* Add a suffix to avoid duplication
+ *
+ * The id is used in src/plugins/workspace/public/plugin.ts and please change that accordingly if you change the id here.
*/
const DSM_APP_ID_FOR_STANDARD_APPLICATION = `${DSM_APP_ID}_core`;
@@ -153,6 +159,9 @@ export class DataSourceManagementPlugin
id: DSM_APP_ID_FOR_STANDARD_APPLICATION,
title: PLUGIN_NAME,
order: 100,
+ description: i18n.translate('data_source_management.description', {
+ defaultMessage: 'Create and manage data source connections.',
+ }),
mount: async (params: AppMountParameters) => {
const { mountManagementSection } = await import('./management_app');
const [coreStart] = await core.getStartServices();
@@ -181,46 +190,6 @@ export class DataSourceManagementPlugin
},
]);
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.observability, [
- {
- id: DSM_APP_ID_FOR_STANDARD_APPLICATION,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 100,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.search, [
- {
- id: DSM_APP_ID_FOR_STANDARD_APPLICATION,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 100,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS['security-analytics'], [
- {
- id: DSM_APP_ID_FOR_STANDARD_APPLICATION,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 100,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.essentials, [
- {
- id: DSM_APP_ID_FOR_STANDARD_APPLICATION,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 100,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.all, [
- {
- id: DSM_APP_ID_FOR_STANDARD_APPLICATION,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 100,
- },
- ]);
-
// when the feature flag is disabled, we don't need to register any of the mds components
if (!this.featureFlagStatus) {
return undefined;
diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts
index 672f390b6416..fe1099a8e635 100644
--- a/src/plugins/home/public/plugin.ts
+++ b/src/plugins/home/public/plugin.ts
@@ -64,7 +64,7 @@ import { PLUGIN_ID, HOME_APP_BASE_PATH, IMPORT_SAMPLE_DATA_APP_ID } from '../com
import { DataSourcePluginStart } from '../../data_source/public';
import { workWithDataSection } from './application/components/homepage/sections/work_with_data';
import { learnBasicsSection } from './application/components/homepage/sections/learn_basics';
-import { DEFAULT_NAV_GROUPS, DEFAULT_APP_CATEGORIES } from '../../../core/public';
+import { DEFAULT_NAV_GROUPS } from '../../../core/public';
import {
ContentManagementPluginSetup,
ContentManagementPluginStart,
@@ -179,9 +179,7 @@ export class HomePublicPlugin
title: i18n.translate('home.tutorialDirectory.featureCatalogueTitle', {
defaultMessage: 'Add sample data',
}),
- navLinkStatus: core.chrome.navGroup.getNavGroupEnabled()
- ? AppNavLinkStatus.visible
- : AppNavLinkStatus.hidden,
+ navLinkStatus: AppNavLinkStatus.hidden,
mount: async (params: AppMountParameters) => {
const [coreStart] = await core.getStartServices();
setCommonService();
@@ -196,26 +194,6 @@ export class HomePublicPlugin
});
urlForwarding.forwardApp('home', 'home');
- const configurationInfoForImportSampleData = {
- id: IMPORT_SAMPLE_DATA_APP_ID,
- title: i18n.translate('home.nav.sampleData.label', {
- defaultMessage: 'Sample data',
- }),
- order: 400,
- category: DEFAULT_APP_CATEGORIES.manage,
- };
-
- // Register sample data to all of the use cases in 2.16
- [
- DEFAULT_NAV_GROUPS.all,
- DEFAULT_NAV_GROUPS.essentials,
- DEFAULT_NAV_GROUPS['security-analytics'],
- DEFAULT_NAV_GROUPS.observability,
- DEFAULT_NAV_GROUPS.search,
- ].forEach((navGroup) =>
- core.chrome.navGroup.addNavLinksToGroup(navGroup, [configurationInfoForImportSampleData])
- );
-
const featureCatalogue = { ...this.featuresCatalogueRegistry.setup() };
featureCatalogue.register({
diff --git a/src/plugins/index_pattern_management/public/plugin.test.ts b/src/plugins/index_pattern_management/public/plugin.test.ts
index a0525acae2dc..ec9a6137ffcf 100644
--- a/src/plugins/index_pattern_management/public/plugin.test.ts
+++ b/src/plugins/index_pattern_management/public/plugin.test.ts
@@ -25,7 +25,7 @@ describe('DiscoverPlugin', () => {
})
).not.toThrow();
expect(setupMock.application.register).toBeCalledTimes(1);
- expect(setupMock.chrome.navGroup.addNavLinksToGroup).toBeCalledTimes(5);
+ expect(setupMock.chrome.navGroup.addNavLinksToGroup).toBeCalledTimes(1);
});
it('when new navigation is enabled, should navigate to standard IPM app', async () => {
diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts
index b830680e9756..d74cdaffe97e 100644
--- a/src/plugins/index_pattern_management/public/plugin.ts
+++ b/src/plugins/index_pattern_management/public/plugin.ts
@@ -46,7 +46,7 @@ import {
} from './service';
import { ManagementSetup } from '../../management/public';
-import { DEFAULT_NAV_GROUPS, AppStatus, DEFAULT_APP_CATEGORIES } from '../../../core/public';
+import { AppStatus, DEFAULT_NAV_GROUPS } from '../../../core/public';
import { getScopedBreadcrumbs } from '../../opensearch_dashboards_react/public';
import { NavigationPublicPluginStart } from '../../navigation/public';
@@ -70,6 +70,9 @@ const sectionsHeader = i18n.translate('indexPatternManagement.indexPattern.secti
defaultMessage: 'Index patterns',
});
+/**
+ * The id is used in src/plugins/workspace/public/plugin.ts and please change that accordingly if you change the id here.
+ */
const IPM_APP_ID = 'indexPatterns';
export class IndexPatternManagementPlugin
@@ -139,6 +142,9 @@ export class IndexPatternManagementPlugin
core.application.register({
id: IPM_APP_ID,
title: sectionsHeader,
+ description: i18n.translate('indexPatternManagement.indexPattern.description', {
+ defaultMessage: 'Manage index patterns to retrieve data from OpenSearch.',
+ }),
status: core.chrome.navGroup.getNavGroupEnabled()
? AppStatus.accessible
: AppStatus.inaccessible,
@@ -161,43 +167,11 @@ export class IndexPatternManagementPlugin
},
});
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.essentials, [
- {
- id: IPM_APP_ID,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 200,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.observability, [
- {
- id: IPM_APP_ID,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 200,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.search, [
- {
- id: IPM_APP_ID,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 200,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS['security-analytics'], [
- {
- id: IPM_APP_ID,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 200,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.all, [
+ core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.settingsAndSetup, [
{
id: IPM_APP_ID,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 200,
+ title: sectionsHeader,
+ order: 400,
},
]);
diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts
index 6242b87289a1..7b219bffad9e 100644
--- a/src/plugins/management/public/plugin.ts
+++ b/src/plugins/management/public/plugin.ts
@@ -128,6 +128,13 @@ export class ManagementPlugin
}
);
+ const settingsLandingPageTitleForLeftNav = i18n.translate(
+ 'management.settings.landingPage.leftNav.title',
+ {
+ defaultMessage: 'Overview',
+ }
+ );
+
const dataAdministrationLandingPageId = 'data_administration_landing';
const dataAdministrationPageTitle = i18n.translate(
@@ -137,6 +144,13 @@ export class ManagementPlugin
}
);
+ const dataAdministrationPageTitleForLeftNav = i18n.translate(
+ 'management.dataAdministration.landingPage.leftNav.title',
+ {
+ defaultMessage: 'Overview',
+ }
+ );
+
const dataAdministrationPageDescription = i18n.translate(
'management.dataAdministration.landingPage.description',
{
@@ -199,7 +213,7 @@ export class ManagementPlugin
core.application.register({
id: settingsLandingPageId,
- title: settingsLandingPageTitle,
+ title: settingsLandingPageTitleForLeftNav,
order: 100,
navLinkStatus: core.chrome.navGroup.getNavGroupEnabled()
? AppNavLinkStatus.visible
@@ -232,7 +246,7 @@ export class ManagementPlugin
core.application.register({
id: dataAdministrationLandingPageId,
- title: dataAdministrationPageTitle,
+ title: dataAdministrationPageTitleForLeftNav,
order: 100,
navLinkStatus: core.chrome.navGroup.getNavGroupEnabled()
? AppNavLinkStatus.visible
diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts
index d91fb3db2271..65c91d6ff14c 100644
--- a/src/plugins/saved_objects_management/public/plugin.ts
+++ b/src/plugins/saved_objects_management/public/plugin.ts
@@ -63,11 +63,14 @@ import {
} from './services';
import { registerServices } from './register_services';
import { bootstrap } from './ui_actions_bootstrap';
-import { DEFAULT_NAV_GROUPS, DEFAULT_APP_CATEGORIES } from '../../../core/public';
+import { DEFAULT_NAV_GROUPS } from '../../../core/public';
import { RecentWork } from './management_section/recent_work';
import { HOME_CONTENT_AREAS } from '../../../plugins/home/public';
import { getScopedBreadcrumbs } from '../../opensearch_dashboards_react/public';
+/**
+ * The id is used in src/plugins/workspace/public/plugin.ts and please change that accordingly if you change the id here.
+ */
export const APP_ID = 'objects';
export interface SavedObjectsManagementPluginSetup {
@@ -166,6 +169,9 @@ export class SavedObjectsManagementPlugin
title: i18n.translate('savedObjectsManagement.assets.label', {
defaultMessage: 'Assets',
}),
+ description: i18n.translate('savedObjectsManagement.assets.description', {
+ defaultMessage: 'Manage and share your global assets.',
+ }),
mount: async (params: AppMountParameters) => {
const { mountManagementSection } = await import('./management_section');
const [coreStart] = await core.getStartServices();
@@ -194,46 +200,6 @@ export class SavedObjectsManagementPlugin
},
]);
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.observability, [
- {
- id: APP_ID,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 300,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.search, [
- {
- id: APP_ID,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 300,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS['security-analytics'], [
- {
- id: APP_ID,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 300,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.essentials, [
- {
- id: APP_ID,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 300,
- },
- ]);
-
- core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.all, [
- {
- id: APP_ID,
- category: DEFAULT_APP_CATEGORIES.manage,
- order: 300,
- },
- ]);
-
// sets up the context mappings and registers any triggers/actions for the plugin
bootstrap(uiActions);
diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts
index 528f32ae8d94..311fc20ed85d 100644
--- a/src/plugins/visualize/public/plugin.ts
+++ b/src/plugins/visualize/public/plugin.ts
@@ -256,6 +256,13 @@ export class VisualizePlugin
order: 400,
},
]);
+ core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.all, [
+ {
+ id: visualizeAppId,
+ category: undefined,
+ order: 400,
+ },
+ ]);
urlForwarding.forwardApp('visualize', 'visualize');
diff --git a/src/plugins/workspace/public/plugin.test.ts b/src/plugins/workspace/public/plugin.test.ts
index 738679250a06..2fc7d41b50ca 100644
--- a/src/plugins/workspace/public/plugin.test.ts
+++ b/src/plugins/workspace/public/plugin.test.ts
@@ -26,9 +26,7 @@ describe('Workspace plugin', () => {
const mockDependencies: WorkspacePluginStartDeps = {
contentManagement: contentManagementPluginMocks.createStartContract(),
};
- const getSetupMock = () => ({
- ...coreMock.createSetup(),
- });
+ const getSetupMock = () => coreMock.createSetup();
beforeEach(() => {
WorkspaceClientMock.mockClear();
@@ -95,7 +93,7 @@ describe('Workspace plugin', () => {
expect(setupMock.application.register).toBeCalledTimes(4);
expect(WorkspaceClientMock).toBeCalledTimes(1);
expect(workspaceClientMock.enterWorkspace).toBeCalledWith('workspaceId');
- expect(setupMock.getStartServices).toBeCalledTimes(1);
+ expect(setupMock.getStartServices).toBeCalledTimes(2);
await waitFor(
() => {
expect(applicationStartMock.navigateToApp).toBeCalledWith(WORKSPACE_FATAL_ERROR_APP_ID, {
@@ -128,6 +126,7 @@ describe('Workspace plugin', () => {
});
const setupMock = getSetupMock();
const applicationStartMock = applicationServiceMock.createStartContract();
+ const chromeStartMock = chromeServiceMock.createStartContract();
let currentAppIdSubscriber: Subscriber | undefined;
setupMock.getStartServices.mockImplementation(() => {
return Promise.resolve([
@@ -138,6 +137,7 @@ describe('Workspace plugin', () => {
currentAppIdSubscriber = subscriber;
}),
},
+ chrome: chromeStartMock,
},
{},
{},
@@ -282,6 +282,7 @@ describe('Workspace plugin', () => {
expect(coreStart.chrome.navControls.registerLeftBottom).toBeCalledTimes(1);
});
+
it('#start should not update systematic use case features after currentWorkspace set', async () => {
const registeredUseCases$ = new BehaviorSubject([
{
diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts
index 8603c5f5c48f..cdebba4afa99 100644
--- a/src/plugins/workspace/public/plugin.ts
+++ b/src/plugins/workspace/public/plugin.ts
@@ -152,7 +152,7 @@ export class WorkspacePlugin
/**
* The following logic determines whether a navigation group should be hidden or not based on the workspace's feature configurations.
* It checks the following conditions:
- * 1. The navigation group is not a system-level group (system groups are always visible except all use case).
+ * 1. The navigation group is not a system-level group.
* 2. The current workspace has feature configurations set up.
* 3. The current workspace's use case is not "All use case".
* 4. The current navigation group is not included in the feature configurations of the workspace.
@@ -160,7 +160,7 @@ export class WorkspacePlugin
* If all these conditions are true, it means that the navigation group should be hidden.
*/
if (
- (navGroup.type !== NavGroupType.SYSTEM || navGroup.id === ALL_USE_CASE_ID) &&
+ navGroup.type !== NavGroupType.SYSTEM &&
currentWorkspace.features &&
getFirstUseCaseOfFeatureConfigs(currentWorkspace.features) !== ALL_USE_CASE_ID &&
!isNavGroupInFeatureConfigs(navGroup.id, currentWorkspace.features)
@@ -242,6 +242,13 @@ export class WorkspacePlugin
) {
const workspaceClient = new WorkspaceClient(core.http, core.workspaces);
await workspaceClient.init();
+
+ this.useCase.setup({
+ chrome: core.chrome,
+ getStartServices: core.getStartServices,
+ workspaces: core.workspaces,
+ });
+
core.application.registerAppUpdater(this.appUpdater$);
this.unregisterNavGroupUpdater = core.chrome.navGroup.registerNavGroupUpdater(
this.navGroupUpdater$
@@ -363,6 +370,9 @@ export class WorkspacePlugin
navLinkStatus: core.chrome.navGroup.getNavGroupEnabled()
? AppNavLinkStatus.visible
: AppNavLinkStatus.hidden,
+ description: i18n.translate('workspace.workspaceList.description', {
+ defaultMessage: 'Organize collaborative projects in use-case-specific workspaces.',
+ }),
async mount(params: AppMountParameters) {
const { renderListApp } = await import('./application');
return mountWorkspaceApp(params, renderListApp);
@@ -507,5 +517,6 @@ export class WorkspacePlugin
this.unregisterNavGroupUpdater?.();
this.registeredUseCasesUpdaterSubscription?.unsubscribe();
this.workspaceAndUseCasesCombineSubscription?.unsubscribe();
+ this.useCase.stop();
}
}
diff --git a/src/plugins/workspace/public/services/use_case_service.ts b/src/plugins/workspace/public/services/use_case_service.ts
index eed33795a49b..ec0e973b6dac 100644
--- a/src/plugins/workspace/public/services/use_case_service.ts
+++ b/src/plugins/workspace/public/services/use_case_service.ts
@@ -3,16 +3,91 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { Observable } from 'rxjs';
+import { combineLatest, Observable, Subscription } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
-import { ChromeStart, PublicAppInfo } from '../../../../core/public';
+import {
+ ChromeStart,
+ CoreSetup,
+ DEFAULT_APP_CATEGORIES,
+ PublicAppInfo,
+ WorkspacesSetup,
+} from '../../../../core/public';
import { WORKSPACE_USE_CASES } from '../../common/constants';
-import { convertNavGroupToWorkspaceUseCase, isEqualWorkspaceUseCase } from '../utils';
+import {
+ convertNavGroupToWorkspaceUseCase,
+ getFirstUseCaseOfFeatureConfigs,
+ isEqualWorkspaceUseCase,
+} from '../utils';
+
+export interface UseCaseServiceSetupDeps {
+ chrome: CoreSetup['chrome'];
+ workspaces: WorkspacesSetup;
+ getStartServices: CoreSetup['getStartServices'];
+}
export class UseCaseService {
+ private workspaceAndManageWorkspaceCategorySubscription?: Subscription;
constructor() {}
+ /**
+ * Add nav links belong to `manage workspace` to all of the use cases.
+ * @param coreSetup
+ * @param currentWorkspace
+ */
+ private async registerManageWorkspaceCategory(setupDeps: UseCaseServiceSetupDeps) {
+ const [coreStart] = await setupDeps.getStartServices();
+ this.workspaceAndManageWorkspaceCategorySubscription?.unsubscribe();
+ this.workspaceAndManageWorkspaceCategorySubscription = combineLatest([
+ setupDeps.workspaces.currentWorkspace$,
+ coreStart.chrome.navGroup.getNavGroupsMap$(),
+ ])
+ .pipe(
+ map(([currentWorkspace, navGroupMap]) => {
+ const currentUseCase = getFirstUseCaseOfFeatureConfigs(currentWorkspace?.features || []);
+ if (!currentUseCase) {
+ return undefined;
+ }
+
+ return navGroupMap[currentUseCase];
+ })
+ )
+ .pipe(
+ distinctUntilChanged((navGroupInfo, anotherNavGroup) => {
+ return navGroupInfo?.id === anotherNavGroup?.id;
+ })
+ )
+ .subscribe((navGroupInfo) => {
+ if (navGroupInfo) {
+ setupDeps.chrome.navGroup.addNavLinksToGroup(navGroupInfo, [
+ {
+ id: 'dataSources_core',
+ category: DEFAULT_APP_CATEGORIES.manageWorkspace,
+ order: 100,
+ },
+ {
+ id: 'indexPatterns',
+ category: DEFAULT_APP_CATEGORIES.manageWorkspace,
+ order: 200,
+ },
+ {
+ id: 'objects',
+ category: DEFAULT_APP_CATEGORIES.manageWorkspace,
+ order: 300,
+ },
+ ]);
+ }
+ });
+ }
+
+ setup({ chrome, workspaces, getStartServices }: UseCaseServiceSetupDeps) {
+ this.registerManageWorkspaceCategory({
+ chrome,
+ workspaces,
+ getStartServices,
+ });
+ }
+
start({
chrome,
workspaceConfigurableApps$,
@@ -70,4 +145,8 @@ export class UseCaseService {
},
};
}
+
+ stop() {
+ this.workspaceAndManageWorkspaceCategorySubscription?.unsubscribe();
+ }
}
diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts
index dc01e64182eb..02aea8d392c8 100644
--- a/src/plugins/workspace/public/utils.ts
+++ b/src/plugins/workspace/public/utils.ts
@@ -249,7 +249,7 @@ export const convertNavGroupToWorkspaceUseCase = ({
title,
description,
features: navLinks.map((item) => item.id),
- systematic: type === NavGroupType.SYSTEM,
+ systematic: type === NavGroupType.SYSTEM || id === ALL_USE_CASE_ID,
order,
});