From ed560b995bd03f62666736ad048b0d68868117c4 Mon Sep 17 00:00:00 2001 From: Yuye Zhu Date: Mon, 24 Jul 2023 10:25:24 +0800 Subject: [PATCH] Refactor navigation links from left menu hard code to workspace plugin register (#55) * feature: add public/workspaces service Signed-off-by: SuZhoue-Joe * Exit workspace from left menu Signed-off-by: yuye-aws * Show exit workspace button with small window size Signed-off-by: yuye-aws * Remove recently viewed and workspace overview on left menu Signed-off-by: yuye-aws * Add buttons for outside, inside workspace case Signed-off-by: yuye-aws * Implement home button and workspace over view button on left menu Signed-off-by: yuye-aws * Implement workspace dropdown list in left menu Signed-off-by: yuye-aws * Add props on recently accessed and custom nav link Signed-off-by: yuye-aws * Reuse method getWorkspaceUrl Signed-off-by: yuye-aws * Remove recently accessed and custom nav props in test Signed-off-by: yuye-aws * Revert "Remove recently accessed and custom nav props in test" This reverts commit 7895e5c5dcde9e134f26b2d6a3df54a2d62e9274. * Wrap title with i18n Signed-off-by: yuye-aws * Add redirect for workspace app Signed-off-by: yuye-aws * Enable users to go to workspace lists page via see more under workspaces in left menu Signed-off-by: yuye-aws * Fix build error and part of test error (#42) * fix: fix build error and some ut Signed-off-by: tygao * chore: remove saved object client test diff Signed-off-by: tygao --------- Signed-off-by: tygao * Comment Alerts and Favorites in left menu Signed-off-by: yuye-aws * Recover recently viewed items in left menu Signed-off-by: yuye-aws * Move exit workspace from left menu to update page Signed-off-by: yuye-aws * Remove unused import Signed-off-by: yuye-aws * Add workspace category info Signed-off-by: yuye-aws * Remove workspace nav link Signed-off-by: yuye-aws * Remove unused import Signed-off-by: yuye-aws * Add FilteredNavLinks props to chrome service mock Signed-off-by: yuye-aws * Remove workspace related constans from chrome Signed-off-by: yuye-aws * Remove workspace related props from chrome and core Signed-off-by: yuye-aws * Remove workspace related props from header Signed-off-by: yuye-aws * Shorten import path for workspace updater Signed-off-by: yuye-aws * Add euiIconType for workspace left menu category Signed-off-by: yuye-aws * Remove workspace related props for collapsible nav Signed-off-by: yuye-aws * Remove workspace related props for collapsible nav Signed-off-by: yuye-aws * Implement navigation for delete and exit workspace Signed-off-by: yuye-aws * Navigate external links through url change Signed-off-by: yuye-aws * Implement filteredNavLinks and sort ChromeNavLinks in nav link service Signed-off-by: yuye-aws * Add workspace list, see more, admin and overview into chromenavlinks Signed-off-by: yuye-aws * fix: unit test failure (#50) Signed-off-by: SuZhou-Joe * Fix osd bootstrap error Signed-off-by: yuye-aws * Check workspace enabled for left menu Signed-off-by: yuye-aws * Add home nav link to left menu when outside workspace Signed-off-by: yuye-aws * Fix unit test for collapsible nav Signed-off-by: yuye-aws * Fix unit test for header Signed-off-by: yuye-aws * Fix unit test for collapsible nav Signed-off-by: yuye-aws * Fix unit test for collapsible nav Signed-off-by: yuye-aws * Update snapshot for unit tests Signed-off-by: yuye-aws * fix osd bootstrap error Signed-off-by: yuye-aws * fix combinelatest import error Signed-off-by: yuye-aws * update snapshot for unit tests Signed-off-by: yuye-aws * variable rename Signed-off-by: yuye-aws * move custom nav link to mock props Signed-off-by: yuye-aws * move default filtered nav link to core Signed-off-by: yuye-aws * change navigation method in workspace updater Signed-off-by: yuye-aws * Update src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx Co-authored-by: SuZhou-Joe * revert some unncessary changes Signed-off-by: yuye-aws * fix navigation url bug Signed-off-by: yuye-aws * move default filtered nav link value setting from core to workspace plugin Signed-off-by: yuye-aws * move filter nav link to a new function Signed-off-by: yuye-aws * process filter nav links when workspace is disabled Signed-off-by: yuye-aws * change navigation method Signed-off-by: yuye-aws --------- Signed-off-by: SuZhoue-Joe Signed-off-by: yuye-aws Signed-off-by: tygao Signed-off-by: SuZhou-Joe Co-authored-by: SuZhoue-Joe Co-authored-by: raintygao --- src/core/public/chrome/chrome_service.mock.ts | 2 + src/core/public/chrome/chrome_service.tsx | 48 +- src/core/public/chrome/constants.ts | 6 - src/core/public/chrome/nav_links/nav_link.ts | 5 + .../chrome/nav_links/nav_links_service.ts | 26 + .../collapsible_nav.test.tsx.snap | 8267 +++++++---------- .../header/__snapshots__/header.test.tsx.snap | 998 +- .../chrome/ui/header/collapsible_nav.test.tsx | 5 - .../chrome/ui/header/collapsible_nav.tsx | 339 +- .../public/chrome/ui/header/header.test.tsx | 5 - src/core/public/chrome/ui/header/header.tsx | 13 +- src/core/public/chrome/ui/header/nav_link.tsx | 41 +- src/core/public/core_system.ts | 3 +- src/plugins/workspace/common/constants.ts | 12 + .../workspace_updater/workspace_updater.tsx | 52 +- src/plugins/workspace/public/plugin.ts | 156 +- 16 files changed, 4078 insertions(+), 5900 deletions(-) diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index 8e5205e6f9bf..c52d206b84a4 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -37,6 +37,8 @@ const createStartContractMock = () => { getHeaderComponent: jest.fn(), navLinks: { getNavLinks$: jest.fn(), + getFilteredNavLinks$: jest.fn(), + setFilteredNavLinks: jest.fn(), has: jest.fn(), get: jest.fn(), getAll: jest.fn(), diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index a0afe640ec64..e3ccfdb6d1d2 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -34,7 +34,6 @@ import { FormattedMessage } from '@osd/i18n/react'; import { BehaviorSubject, combineLatest, merge, Observable, of, ReplaySubject } from 'rxjs'; import { flatMap, map, takeUntil } from 'rxjs/operators'; import { EuiLink } from '@elastic/eui'; -import { i18n } from '@osd/i18n'; import { mountReactNode } from '../utils/mount'; import { InternalApplicationStart } from '../application'; import { DocLinksStart } from '../doc_links'; @@ -42,14 +41,14 @@ import { HttpStart } from '../http'; import { InjectedMetadataStart } from '../injected_metadata'; import { NotificationsStart } from '../notifications'; import { IUiSettingsClient } from '../ui_settings'; -import { OPENSEARCH_DASHBOARDS_ASK_OPENSEARCH_LINK, WORKSPACE_APP_ID } from './constants'; +import { OPENSEARCH_DASHBOARDS_ASK_OPENSEARCH_LINK } from './constants'; import { ChromeDocTitle, DocTitleService } from './doc_title'; import { ChromeNavControls, NavControlsService } from './nav_controls'; import { ChromeNavLinks, NavLinksService, ChromeNavLink } from './nav_links'; import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed'; import { Header } from './ui'; import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu'; -import { Branding, WorkspacesStart } from '../'; +import { Branding } from '../'; export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; const IS_LOCKED_KEY = 'core.chrome.isLocked'; @@ -100,7 +99,6 @@ interface StartDeps { injectedMetadata: InjectedMetadataStart; notifications: NotificationsStart; uiSettings: IUiSettingsClient; - workspaces: WorkspacesStart; } /** @internal */ @@ -154,7 +152,6 @@ export class ChromeService { injectedMetadata, notifications, uiSettings, - workspaces, }: StartDeps): Promise { this.initVisibility(application); @@ -181,41 +178,6 @@ export class ChromeService { docTitle.reset(); }); - const getWorkspaceUrl = (id: string) => { - return workspaces.formatUrlWithWorkspaceId( - application.getUrlForApp(WORKSPACE_APP_ID, { - path: '/', - absolute: true, - }), - id - ); - }; - - const exitWorkspace = async () => { - let result; - try { - result = await workspaces.client.exitWorkspace(); - } catch (error) { - notifications?.toasts.addDanger({ - title: i18n.translate('workspace.exit.failed', { - defaultMessage: 'Failed to exit workspace', - }), - text: error instanceof Error ? error.message : JSON.stringify(error), - }); - return; - } - if (!result?.success) { - notifications?.toasts.addDanger({ - title: i18n.translate('workspace.exit.failed', { - defaultMessage: 'Failed to exit workspace', - }), - text: result?.error, - }); - return; - } - await application.navigateToApp('home'); - }; - const setIsNavDrawerLocked = (isLocked: boolean) => { isNavDrawerLocked$.next(isLocked); localStorage.setItem(IS_LOCKED_KEY, `${isLocked}`); @@ -288,7 +250,7 @@ export class ChromeService { homeHref={http.basePath.prepend('/app/home')} isVisible$={this.isVisible$} opensearchDashboardsVersion={injectedMetadata.getOpenSearchDashboardsVersion()} - navLinks$={navLinks.getNavLinks$()} + navLinks$={navLinks.getFilteredNavLinks$()} customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))} recentlyAccessed$={recentlyAccessed.get$()} navControlsLeft$={navControls.getLeft$()} @@ -297,13 +259,9 @@ export class ChromeService { navControlsExpandedCenter$={navControls.getExpandedCenter$()} navControlsExpandedRight$={navControls.getExpandedRight$()} onIsLockedUpdate={setIsNavDrawerLocked} - exitWorkspace={exitWorkspace} - getWorkspaceUrl={getWorkspaceUrl} isLocked$={getIsNavDrawerLocked$} branding={injectedMetadata.getBranding()} survey={injectedMetadata.getSurvey()} - currentWorkspace$={workspaces.client.currentWorkspace$} - workspaceList$={workspaces.client.workspaceList$} /> ), diff --git a/src/core/public/chrome/constants.ts b/src/core/public/chrome/constants.ts index 6de7c01f1d13..5008f8b4a69a 100644 --- a/src/core/public/chrome/constants.ts +++ b/src/core/public/chrome/constants.ts @@ -31,9 +31,3 @@ export const OPENSEARCH_DASHBOARDS_ASK_OPENSEARCH_LINK = 'https://forum.opensearch.org/'; export const GITHUB_CREATE_ISSUE_LINK = 'https://github.com/opensearch-project/OpenSearch-Dashboards/issues/new/choose'; - -export const WORKSPACE_APP_ID = 'workspace'; - -export const PATHS = { - list: '/list', -}; diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts index cddd45234514..8479c8468b74 100644 --- a/src/core/public/chrome/nav_links/nav_link.ts +++ b/src/core/public/chrome/nav_links/nav_link.ts @@ -102,6 +102,11 @@ export interface ChromeNavLink { * Hides a link from the navigation. */ readonly hidden?: boolean; + + /** + * Links can be navigated through url. + */ + readonly externalLink?: boolean; } /** @public */ diff --git a/src/core/public/chrome/nav_links/nav_links_service.ts b/src/core/public/chrome/nav_links/nav_links_service.ts index 93c138eac62c..1d3dc7994445 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -53,6 +53,16 @@ export interface ChromeNavLinks { */ getNavLinks$(): Observable>>; + /** + * Set an observable for a sorted list of filtered navlinks. + */ + getFilteredNavLinks$(): Observable>>; + + /** + * Set filtered navlinks. + */ + setFilteredNavLinks(filteredNavLinks: ReadonlyMap): void; + /** * Get the state of a navlink at this point in time. * @param id @@ -116,6 +126,7 @@ type LinksUpdater = (navLinks: Map) => Map>(new Map()); public start({ application, http }: StartDeps): ChromeNavLinks { const appLinks$ = application.applications$.pipe( @@ -151,6 +162,14 @@ export class NavLinksService { return navLinks$.pipe(map(sortNavLinks), takeUntil(this.stop$)); }, + setFilteredNavLinks: (filteredNavLinks: ReadonlyMap) => { + this.filteredNavLinks$.next(filteredNavLinks); + }, + + getFilteredNavLinks$: () => { + return this.filteredNavLinks$.pipe(map(sortChromeNavLinks), takeUntil(this.stop$)); + }, + get(id: string) { const link = navLinks$.value.get(id); return link && link.properties; @@ -215,3 +234,10 @@ function sortNavLinks(navLinks: ReadonlyMap) { 'order' ); } + +function sortChromeNavLinks(chromeNavLinks: ReadonlyMap) { + return sortBy( + [...chromeNavLinks.values()].map((link) => link as Readonly), + 'order' + ); +} diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index d6b28013bcf9..4e85a75ba963 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -72,92 +72,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` } } closeNav={[Function]} - currentWorkspace$={ - BehaviorSubject { - "_isScalar": false, - "_value": null, - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [ - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - ], - "thrownError": null, - } - } customNavLink$={ BehaviorSubject { "_isScalar": false, @@ -177,8 +91,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "thrownError": null, } } - exitWorkspace={[Function]} - getWorkspaceUrl={[Function]} homeHref="/" id="collapsibe-nav" isLocked={false} @@ -358,25 +270,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "closed": false, "hasError": false, "isStopped": false, - "observers": Array [], - "thrownError": null, - } - } - storage={ - StubBrowserStorage { - "keys": Array [], - "size": 0, - "sizeLimit": 5000000, - "values": Array [], - } - } - workspaceList$={ - BehaviorSubject { - "_isScalar": false, - "_value": Array [], - "closed": false, - "hasError": false, - "isStopped": false, "observers": Array [ Subscriber { "_parentOrParents": null, @@ -415,47 +308,18 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "syncErrorThrown": false, "syncErrorValue": null, }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, ], "thrownError": null, } } + storage={ + StubBrowserStorage { + "keys": Array [], + "size": 0, + "sizeLimit": 5000000, + "values": Array [], + } + } > -
-
- -
- -
- - - -
-
- -
- -

- Home -

-
-
-
-
-
-
-
-
- -
-
- -
- -
- - - -
-
- -
- -

- Alerts -

-
-
-
-
-
-
-
-
- - - - - Favorites + Recently viewed } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" + data-test-subj="collapsibleNavGroup-recentlyViewed" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
- -
- - - -
-
- Favorites + Recently viewed
@@ -757,68 +485,113 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
- -
- -
-

- No Favorites -

-
-
-
-
- -
- + + recent 1 + + + + + -
-

- SEE MORE -

-
-
-
-
+ + + recent 2 + + + + + +
@@ -828,10 +601,14 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
@@ -858,21 +635,27 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Library } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="/defaultModeLogo" + data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
@@ -939,7 +722,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Library
@@ -966,70 +749,145 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
- -
- -
-

- No Workspaces -

-
-
-
-
- -
- + + discover + + + + + -
-

- SEE MORE -

-
-
-
-
+ + + visualize + + + + + +
  • + + + dashboard + + +
  • +
    + +
    @@ -1039,1126 +897,489 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
    -
    -
    + -
    - + + + -
    - - - -
    -
    - -
    - -

    - Admin -

    -
    -
    -
    -
    + Observability + + + -
    -
    -
    - -
    - -
      - - - - - Dock navigation - - , - } - } - color="subdued" - data-test-subj="collapsible-nav-lock" - iconType="lockOpen" - label="Dock navigation" - onClick={[Function]} - size="xs" + -
    • + + + + +
      -
      + + +
      - Dock navigation - - -
    • -
      -
      -
    -
    -
    -
    -
    - - - - -
    - -`; - -exports[`CollapsibleNav renders the default nav 1`] = ` - - - -`; - -exports[`CollapsibleNav renders the default nav 2`] = ` - - - -`; - -exports[`CollapsibleNav renders the default nav 3`] = ` - - - - + + + +`; + +exports[`CollapsibleNav renders the default nav 1`] = ` + + + +`; + +exports[`CollapsibleNav renders the default nav 2`] = ` + + + +`; + +exports[`CollapsibleNav renders the default nav 3`] = ` + + + + @@ -2730,198 +2674,17 @@ exports[`CollapsibleNav renders the default nav 3`] = ` `; - -exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] = ` - -
    -
    + -
    - + -
    - - - -
    -
    - -
    - -

    - Home -

    -
    -
    -
    -
    + Recently viewed + + + -
    -
    -
    - -
    - -
    - -
    + + + + + - - - -
    -
    - + +
    + +

    + Recently viewed +

    +
    +
    +
    +
    +
    + + +
    +
    + +
    - -

    - Alerts -

    -
    + + +
    - -
    - +
    + + - +
    @@ -3438,21 +3186,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] = className="euiCollapsibleNavGroup__title" id="mockId__title" > - Favorites + Library } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="/darkModeLogo" + data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    @@ -3519,7 +3273,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] = className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Favorites + Library
    @@ -3546,68 +3300,67 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] =
    - -
    - -
    -

    - No Favorites -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    + + + discover + + + + + +
    @@ -3617,10 +3370,14 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] =
    @@ -3647,21 +3404,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] = className="euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Observability } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="logoObservability" + data-test-subj="collapsibleNavGroup-observability" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    @@ -3728,7 +3491,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] = className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Observability
    @@ -3751,429 +3514,187 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 1`] =
    -
    - -
    - -
    -

    - No Workspaces -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    - - - -
    -
    - -
    - -

    - Admin -

    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
      - - - - - Dock navigation - - , - } - } - color="subdued" - data-test-subj="collapsible-nav-lock" - iconType="lockOpen" - label="Dock navigation" - onClick={[Function]} - size="xs" - > -
    • +
      - -
    • -
      -
      -
    -
    + +
  • + + + discover + + +
  • +
    + + +
    +
    + + + - +
    - - - - -
    -
    -`; - -exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = ` - + +
    +
    + +
      + + + + Dock navigation + + , + } + } + color="subdued" + data-test-subj="collapsible-nav-lock" + iconType="lockOpen" + label="Dock navigation" + onClick={[Function]} + size="xs" + > +
    • + +
    • +
      +
    +
    +
    +
    +
    + + + + + + +
    +`; + +exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = ` + -
    -
    + -
    - + -
    - - - -
    -
    - -
    - -

    - Home -

    -
    -
    -
    -
    + Recently viewed + + + -
    -
    -
    - -
    - -
    - -
    + + + + + - - - -
    -
    - + +
    + +

    + Recently viewed +

    +
    +
    +
    +
    +
    + + +
    +
    + +
    - -

    - Alerts -

    -
    + + +
    - -
    - +
    + + - +
    @@ -4690,21 +4195,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = className="euiCollapsibleNavGroup__title" id="mockId__title" > - Favorites + Library } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="/defaultModeLogo" + data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    @@ -4771,7 +4282,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Favorites + Library
    @@ -4798,68 +4309,67 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] =
    - -
    - -
    -

    - No Favorites -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    + + + discover + + + + + +
    @@ -4869,10 +4379,14 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] =
    @@ -4899,21 +4413,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = className="euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Observability } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="logoObservability" + data-test-subj="collapsibleNavGroup-observability" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    @@ -4980,7 +4500,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] = className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Observability
    @@ -5007,70 +4527,67 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] =
    - -
    - -
    -

    - No Workspaces -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    + + + discover + + + + + +
    @@ -5079,86 +4596,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 2`] =
    - -
    -
    - -
    - -
    - - - -
    -
    - -
    - -

    - Admin -

    -
    -
    -
    -
    -
    -
    -
    -
    - -
    + +
    - -
      - - - Dock navigation - - , - } - } - color="subdued" - data-test-subj="collapsible-nav-lock" - iconType="lockOpen" - label="Dock navigation" - onClick={[Function]} - size="xs" - > -
    • - -
    • -
      -
      -
    -
    -
    -
    -
    - - - - -
    -
    -`; - -exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = ` - + + Dock navigation + + , + } + } + color="subdued" + data-test-subj="collapsible-nav-lock" + iconType="lockOpen" + label="Dock navigation" + onClick={[Function]} + size="xs" + > +
  • + +
  • + + + + + + + + + + + + +
    +`; + +exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = ` + -
    -
    + -
    - -
    - - - -
    -
    - -
    + +

    - -

    - Home -

    -
    -

    -
    -
    + Recently viewed + + + -
    -
    -
    - -
    - -
    - -
    + + + + + - - - -
    -
    - + +
    + +

    + Recently viewed +

    +
    +
    +
    +
    +
    + + +
    +
    + +
    - -

    - Alerts -

    -
    + + +
    - -
    - +
    + + - +
    @@ -5940,21 +5202,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = className="euiCollapsibleNavGroup__title" id="mockId__title" > - Favorites + Library } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="undefined/opensearch_mark_default_mode.svg" + data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    @@ -6021,7 +5289,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Favorites + Library
    @@ -6048,68 +5316,67 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] =
    - -
    - -
    -

    - No Favorites -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    + + + discover + + + + + +
    @@ -6119,10 +5386,14 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] =
    @@ -6149,21 +5420,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = className="euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Observability } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="logoObservability" + data-test-subj="collapsibleNavGroup-observability" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    @@ -6230,7 +5507,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] = className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Observability
    @@ -6257,70 +5534,67 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] =
    - -
    - -
    -

    - No Workspaces -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    + + + discover + + + + + +
    @@ -6329,86 +5603,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in dark mode 3`] =
    - -
    -
    - -
    - -
    - - - -
    -
    - -
    - -

    - Admin -

    -
    -
    -
    -
    -
    -
    -
    -
    - -
    +
    - -
      - - - -
    -
    + + + +
    - -
    + + @@ -6558,133 +5773,18 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`] } } closeNav={[Function]} - currentWorkspace$={ - BehaviorSubject { - "_isScalar": false, - "_value": null, - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [ - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - ], - "thrownError": null, - } - } customNavLink$={ BehaviorSubject { "_isScalar": false, - "_value": undefined, + "_value": Object { + "baseUrl": "/", + "category": undefined, + "data-test-subj": "Custom link", + "href": "Custom link", + "id": "Custom link", + "isActive": true, + "title": "Custom link", + }, "closed": false, "hasError": false, "isStopped": false, @@ -6692,8 +5792,6 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`] "thrownError": null, } } - exitWorkspace={[Function]} - getWorkspaceUrl={[Function]} homeHref="/" id="collapsibe-nav" isLocked={false} @@ -6791,25 +5889,6 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`] "closed": false, "hasError": false, "isStopped": false, - "observers": Array [], - "thrownError": null, - } - } - storage={ - StubBrowserStorage { - "keys": Array [], - "size": 0, - "sizeLimit": 5000000, - "values": Array [], - } - } - workspaceList$={ - BehaviorSubject { - "_isScalar": false, - "_value": Array [], - "closed": false, - "hasError": false, - "isStopped": false, "observers": Array [ Subscriber { "_parentOrParents": null, @@ -6848,84 +5927,18 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`] "syncErrorThrown": false, "syncErrorValue": null, }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, ], "thrownError": null, } } + storage={ + StubBrowserStorage { + "keys": Array [], + "size": 0, + "sizeLimit": 5000000, + "values": Array [], + } + } > -
    -
    + -
    - -
    - - - -
    -
    - -
    + +

    - -

    - Home -

    -
    -

    -
    -
    + Recently viewed + + + -
    -
    -
    - -
    - -
    - -
    + + + + + - - - -
    -
    - + +
    + +

    + Recently viewed +

    +
    +
    +
    +
    +
    + + +
    +
    + +
    - -

    - Alerts -

    -
    + + +
    - -
    - +
    + + - +
    @@ -7119,21 +6212,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`] className="euiCollapsibleNavGroup__title" id="mockId__title" > - Favorites + Library } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="/defaultModeLogo" + data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    @@ -7200,7 +6299,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`] className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Favorites + Library
    @@ -7227,68 +6326,67 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`]
    - -
    - -
    -

    - No Favorites -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    + + + discover + + + + + +
    @@ -7298,10 +6396,14 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`]
    @@ -7328,21 +6430,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`] className="euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Observability } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="logoObservability" + data-test-subj="collapsibleNavGroup-observability" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    @@ -7409,7 +6517,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`] className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Observability
    @@ -7436,70 +6544,67 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`]
    - -
    - -
    -

    - No Workspaces -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    + + + discover + + + + + +
    @@ -7508,86 +6613,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 1`]
    - -
    -
    - -
    - -
    - - - -
    -
    - -
    - -

    - Admin -

    -
    -
    -
    -
    -
    -
    -
    -
    - -
    +
    - -
      - - - -
    -
    + + + +
    - -
    + + @@ -7734,133 +6780,18 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] } } closeNav={[Function]} - currentWorkspace$={ - BehaviorSubject { - "_isScalar": false, - "_value": null, - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [ - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - ], - "thrownError": null, - } - } customNavLink$={ BehaviorSubject { "_isScalar": false, - "_value": undefined, + "_value": Object { + "baseUrl": "/", + "category": undefined, + "data-test-subj": "Custom link", + "href": "Custom link", + "id": "Custom link", + "isActive": true, + "title": "Custom link", + }, "closed": false, "hasError": false, "isStopped": false, @@ -7868,8 +6799,6 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] "thrownError": null, } } - exitWorkspace={[Function]} - getWorkspaceUrl={[Function]} homeHref="/" id="collapsibe-nav" isLocked={false} @@ -7967,25 +6896,6 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] "closed": false, "hasError": false, "isStopped": false, - "observers": Array [], - "thrownError": null, - } - } - storage={ - StubBrowserStorage { - "keys": Array [], - "size": 0, - "sizeLimit": 5000000, - "values": Array [], - } - } - workspaceList$={ - BehaviorSubject { - "_isScalar": false, - "_value": Array [], - "closed": false, - "hasError": false, - "isStopped": false, "observers": Array [ Subscriber { "_parentOrParents": null, @@ -8024,84 +6934,18 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] "syncErrorThrown": false, "syncErrorValue": null, }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, ], "thrownError": null, } } + storage={ + StubBrowserStorage { + "keys": Array [], + "size": 0, + "sizeLimit": 5000000, + "values": Array [], + } + } > -
    -
    + -
    - + -
    - - - -
    -
    - -
    - -

    - Home -

    -
    -
    -
    -
    + Recently viewed + + + -
    -
    -
    - -
    - -
    - -
    + + + + + - - - -
    -
    - + +
    + +

    + Recently viewed +

    +
    +
    +
    +
    +
    + + +
    +
    + +
    - -

    - Alerts -

    -
    + + +
    - -
    - +
    + + - +
    @@ -8295,21 +7219,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] className="euiCollapsibleNavGroup__title" id="mockId__title" > - Favorites + Library } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="undefined/opensearch_mark_default_mode.svg" + data-test-subj="collapsibleNavGroup-opensearchDashboards" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    @@ -8376,7 +7306,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Favorites + Library
    @@ -8403,68 +7333,67 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`]
    - -
    - -
    -

    - No Favorites -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    + + + discover + + + + + +
    @@ -8474,10 +7403,14 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`]
    @@ -8504,21 +7437,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] className="euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Observability } className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + data-test-opensearch-logo="logoObservability" + data-test-subj="collapsibleNavGroup-observability" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    @@ -8585,7 +7524,7 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`] className="euiTitle euiTitle--xxsmall euiCollapsibleNavGroup__title" id="mockId__title" > - Workspaces + Observability
    @@ -8612,70 +7551,67 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`]
    - -
    - -
    -

    - No Workspaces -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    + + + discover + + + + + +
    @@ -8684,86 +7620,27 @@ exports[`CollapsibleNav renders the nav bar with custom logo in default mode 2`]
    - -
    -
    - -
    - -
    - - - -
    -
    - -
    - -

    - Admin -

    -
    -
    -
    -
    -
    -
    -
    -
    - -
    +
    - -
      - - -
    -
    + + +
    - -
    + + diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 81dd48e741a1..db75e2ee08ae 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -314,55 +314,6 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } - currentWorkspace$={ - BehaviorSubject { - "_isScalar": false, - "_value": null, - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [ - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - ], - "thrownError": null, - } - } customNavLink$={ BehaviorSubject { "_isScalar": false, @@ -379,7 +330,6 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } - exitWorkspace={[Function]} forceAppSwitcherNavigation$={ BehaviorSubject { "_isScalar": false, @@ -466,7 +416,6 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } - getWorkspaceUrl={[Function]} helpExtension$={ BehaviorSubject { "_isScalar": false, @@ -1843,18 +1792,6 @@ exports[`Header handles visibility and lock changes 1`] = ` "closed": false, "hasError": false, "isStopped": false, - "observers": Array [], - "thrownError": null, - } - } - survey="/" - workspaceList$={ - BehaviorSubject { - "_isScalar": false, - "_value": Array [], - "closed": false, - "hasError": false, - "isStopped": false, "observers": Array [ Subscriber { "_parentOrParents": null, @@ -1897,6 +1834,7 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } + survey="/" >
    -
    -
    - -
    - -
    - - - -
    -
    - -
    - -

    - Home -

    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    - - - -
    -
    - -
    - -

    - Alerts -

    -
    -
    -
    -
    -
    -
    -
    -
    - - - - - Favorites + Recently viewed } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" + className="euiCollapsibleNavGroup euiCollapsibleNavGroup--light euiCollapsibleNavGroup--withHeading" + data-test-subj="collapsibleNavGroup-recentlyViewed" id="mockId" initialIsOpen={true} isLoading={false} isLoadingMessage={false} + onToggle={[Function]} paddingSize="none" >
    - -
    - - - -
    -
    - Favorites + Recently viewed
    @@ -6236,68 +5976,71 @@ exports[`Header handles visibility and lock changes 1`] = `
    - -
    - -
    -

    - No Favorites -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    + + + dashboard + + + + + +
    @@ -6307,296 +6050,77 @@ exports[`Header handles visibility and lock changes 1`] = `
    - - - - - - -

    - Workspaces -

    -
    -
    - - } - className="euiCollapsibleNavGroup euiCollapsibleNavGroup--withHeading" +
    -
    - -
    -
    - -
    -
    -
    - -
    - -
    -

    - No Workspaces -

    -
    -
    -
    -
    - -
    - -
    -

    - SEE MORE -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - -
    -
    - -
    - -
    - - -
    -
    - -
    - -

    - Admin -

    -
    -
    -
    -
    -
    + opensearchDashboards + + + + + +
    - -
    + +
    - -
      - - -
    -
    + + +
    -
    -
    + + @@ -6987,92 +6511,6 @@ exports[`Header renders condensed header 1`] = ` "thrownError": null, } } - currentWorkspace$={ - BehaviorSubject { - "_isScalar": false, - "_value": null, - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [ - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - ], - "thrownError": null, - } - } customNavLink$={ BehaviorSubject { "_isScalar": false, @@ -7084,7 +6522,6 @@ exports[`Header renders condensed header 1`] = ` "thrownError": null, } } - exitWorkspace={[Function]} forceAppSwitcherNavigation$={ BehaviorSubject { "_isScalar": false, @@ -7134,7 +6571,6 @@ exports[`Header renders condensed header 1`] = ` "thrownError": null, } } - getWorkspaceUrl={[Function]} helpExtension$={ BehaviorSubject { "_isScalar": false, @@ -8379,18 +7815,6 @@ exports[`Header renders condensed header 1`] = ` opensearchDashboardsDocLink="/docs" opensearchDashboardsVersion="1.0.0" recentlyAccessed$={ - BehaviorSubject { - "_isScalar": false, - "_value": Array [], - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [], - "thrownError": null, - } - } - survey="/" - workspaceList$={ BehaviorSubject { "_isScalar": false, "_value": Array [], @@ -8435,47 +7859,11 @@ exports[`Header renders condensed header 1`] = ` "syncErrorThrown": false, "syncErrorValue": null, }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, ], "thrownError": null, } } + survey="/" >
    ({ htmlIdGenerator: () => () => 'mockId', @@ -80,11 +79,7 @@ function mockProps() { closeNav: () => {}, navigateToApp: () => Promise.resolve(), navigateToUrl: () => Promise.resolve(), - exitWorkspace: () => {}, - getWorkspaceUrl: (id: string) => '', customNavLink$: new BehaviorSubject(undefined), - currentWorkspace$: workspacesServiceMock.createStartContract().client.currentWorkspace$, - workspaceList$: workspacesServiceMock.createStartContract().client.workspaceList$, branding: { darkMode: false, mark: { diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index d7d15694b235..f5ca8a5af97a 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -48,10 +48,8 @@ import { AppCategory } from '../../../../types'; import { InternalApplicationStart } from '../../../application'; import { HttpStart } from '../../../http'; import { OnIsLockedUpdate } from './'; -import { createEuiListItem, isModifiedOrPrevented, createWorkspaceNavLink } from './nav_link'; +import { createEuiListItem, isModifiedOrPrevented, createRecentNavLink } from './nav_link'; import { ChromeBranding } from '../../chrome_service'; -import { WorkspaceAttribute } from '../../../workspace'; -import { WORKSPACE_APP_ID, PATHS } from '../../constants'; function getAllCategories(allCategorizedLinks: Record) { const allCategories = {} as Record; @@ -103,10 +101,6 @@ interface Props { navigateToUrl: InternalApplicationStart['navigateToUrl']; customNavLink$: Rx.Observable; branding: ChromeBranding; - exitWorkspace: () => void; - getWorkspaceUrl: (id: string) => string; - currentWorkspace$: Rx.BehaviorSubject; - workspaceList$: Rx.BehaviorSubject; } export function CollapsibleNav({ @@ -117,8 +111,6 @@ export function CollapsibleNav({ homeHref, storage = window.localStorage, onIsLockedUpdate, - exitWorkspace, - getWorkspaceUrl, closeNav, navigateToApp, navigateToUrl, @@ -126,12 +118,10 @@ export function CollapsibleNav({ ...observables }: Props) { const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); + const recentlyAccessed = useObservable(observables.recentlyAccessed$, []); const appId = useObservable(observables.appId$, ''); - const currentWorkspace = useObservable(observables.currentWorkspace$); - const workspaceList = useObservable(observables.workspaceList$, []).slice(0, 5); const lockRef = useRef(null); - const filteredLinks = getFilterLinks(currentWorkspace, navLinks); - const groupedNavLinks = groupBy(filteredLinks, (link) => link?.category?.id); + const groupedNavLinks = groupBy(navLinks, (link) => link?.category?.id); const { undefined: unknowns = [], ...allCategorizedLinks } = groupedNavLinks; const categoryDictionary = getAllCategories(allCategorizedLinks); const orderedCategories = getOrderedCategories(allCategorizedLinks, categoryDictionary); @@ -154,17 +144,6 @@ export function CollapsibleNav({ const markDefault = branding.mark?.defaultUrl; const markDarkMode = branding.mark?.darkModeUrl; - function getFilterLinks( - workspace: WorkspaceAttribute | null | undefined, - allNavLinks: ChromeNavLink[] - ) { - if (!workspace) return allNavLinks; - - const features = workspace.features ?? []; - const links = allNavLinks.filter((item) => features.indexOf(item.id) > -1); - return links; - } - /** * Use branding configurations to check which URL to use for rendering * side menu opensearch logo in default mode @@ -207,19 +186,64 @@ export function CollapsibleNav({ outsideClickCloses={false} > - {/* Home, Alerts, Favorites, Projects and Admin outside workspace */} - {!currentWorkspace && ( - <> - { - closeNav(); - await navigateToApp('home'); - }} - iconType={'logoOpenSearch'} - title={i18n.translate('core.ui.primaryNavSection.home', { - defaultMessage: 'Home', + {/* Recently viewed */} + + setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage) + } + data-test-subj="collapsibleNavGroup-recentlyViewed" + > + {recentlyAccessed.length > 0 ? ( + { + // TODO #64541 + // Can remove icon from recent links completely + const { iconType, onClick, ...hydratedLink } = createRecentNavLink( + link, + navLinks, + basePath, + navigateToUrl + ); + + return { + ...hydratedLink, + 'data-test-subj': 'collapsibleNavAppLink--recent', + onClick: (event) => { + if (!isModifiedOrPrevented(event)) { + closeNav(); + onClick(event); + } + }, + }; })} + maxWidth="none" + color="subdued" + gutterSize="none" + size="s" + className="osdCollapsibleNav__recentsListGroup" /> + ) : ( + +

    + {i18n.translate('core.ui.EmptyRecentlyViewed', { + defaultMessage: 'No recently viewed items', + })} +

    +
    + )} +
    + + {/* Alerts and Favorites */} + {/* ( + <> + + ) */} + + {/* OpenSearchDashboards, Observability, Security, and Management sections */} + {orderedCategories.map((categoryName) => { + const category = categoryDictionary[categoryName]!; + const opensearchLinkLogo = + category.id === 'opensearchDashboards' ? customSideMenuLogo() : category.euiIconType; + + return ( setIsCategoryOpen(category.id, isCategoryOpen, storage)} + data-test-subj={`collapsibleNavGroup-${category.id}`} + data-test-opensearch-logo={opensearchLinkLogo} > - {workspaceList?.length > 0 ? ( - { - const href = getWorkspaceUrl(workspace.id); - const hydratedLink = createWorkspaceNavLink(href, workspace, navLinks); - return { - href, - ...hydratedLink, - 'data-test-subj': 'collapsibleNavAppLink--workspace', - onClick: async (event) => { - if (!isModifiedOrPrevented(event)) { - closeNav(); - } - }, - }; - })} - maxWidth="none" - color="subdued" - gutterSize="none" - size="s" - /> - ) : ( - -

    - {i18n.translate('core.ui.EmptyWorkspaceList', { - defaultMessage: 'No Workspaces', - })} -

    -
    - )} - readyForEUI(link))} + maxWidth="none" color="subdued" - style={{ padding: '0 8px 8px' }} - onClick={async () => { - await navigateToApp(WORKSPACE_APP_ID, { - path: PATHS.list, - }); - }} - > -

    - {i18n.translate('core.ui.SeeMoreWorkspace', { - defaultMessage: 'SEE MORE', - })} -

    -
    + gutterSize="none" + size="s" + />
    - - - )} - - {/* Workspace name and Overview inside workspace */} - {currentWorkspace && ( - <> - - { - window.location.href = getWorkspaceUrl(currentWorkspace.id); - }} - iconType={'grid'} - title={i18n.translate('core.ui.primaryNavSection.overview', { - defaultMessage: 'Overview', - })} - /> - - )} + ); + })} - {/* OpenSearchDashboards, Observability, Security, and Management sections inside workspace */} - {currentWorkspace && - orderedCategories.map((categoryName) => { - const category = categoryDictionary[categoryName]!; - const opensearchLinkLogo = - category.id === 'opensearchDashboards' ? customSideMenuLogo() : category.euiIconType; + {/* Things with no category (largely for custom plugins) */} + {unknowns.map((link, i) => ( + + + + + + ))} - return ( - - setIsCategoryOpen(category.id, isCategoryOpen, storage) - } - data-test-subj={`collapsibleNavGroup-${category.id}`} - data-test-opensearch-logo={opensearchLinkLogo} - > - readyForEUI(link))} - maxWidth="none" - color="subdued" - gutterSize="none" - size="s" - /> - - ); - })} - - {/* Things with no category (largely for custom plugins) inside workspace */} - {currentWorkspace && - unknowns.map((link, i) => ( - - - - - - ))} - - - - {/* Exit workspace button only within a workspace*/} - {currentWorkspace && ( + {/* Docking button only for larger screens that can support it*/} + + + - )} - {/* Docking button only for larger screens that can support it*/} - { - - { + onIsLockedUpdate(!isLocked); + if (lockRef.current) { + lockRef.current.focus(); } - onClick={() => { - onIsLockedUpdate(!isLocked); - if (lockRef.current) { - lockRef.current.focus(); - } - }} - iconType={isLocked ? 'lock' : 'lockOpen'} - /> - - } - - + }} + iconType={isLocked ? 'lock' : 'lockOpen'} + /> + + +
    ); diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index 12b31d53795f..319dea4c394b 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -36,7 +36,6 @@ import { httpServiceMock } from '../../../http/http_service.mock'; import { applicationServiceMock } from '../../../mocks'; import { Header } from './header'; import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; -import { workspacesServiceMock } from '../../../workspace/workspaces_service.mock'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => 'mockId', @@ -70,8 +69,6 @@ function mockProps() { isLocked$: new BehaviorSubject(false), loadingCount$: new BehaviorSubject(0), onIsLockedUpdate: () => {}, - exitWorkspace: () => {}, - getWorkspaceUrl: (id: string) => '', branding: { darkMode: false, logo: { defaultUrl: '/' }, @@ -79,8 +76,6 @@ function mockProps() { applicationTitle: 'OpenSearch Dashboards', }, survey: '/', - currentWorkspace$: workspacesServiceMock.createStartContract().client.currentWorkspace$, - workspaceList$: workspacesServiceMock.createStartContract().client.workspaceList$, }; } diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index af92ead3e27c..9496b76b9980 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -43,7 +43,7 @@ import { i18n } from '@osd/i18n'; import classnames from 'classnames'; import React, { createRef, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; -import { Observable, BehaviorSubject } from 'rxjs'; +import { Observable } from 'rxjs'; import { LoadingIndicator } from '../'; import { ChromeBadge, @@ -64,7 +64,6 @@ import { HomeLoader } from './home_loader'; import { HeaderNavControls } from './header_nav_controls'; import { HeaderActionMenu } from './header_action_menu'; import { HeaderLogo } from './header_logo'; -import { WorkspaceAttribute } from '../../../workspace'; export interface HeaderProps { opensearchDashboardsVersion: string; @@ -90,12 +89,8 @@ export interface HeaderProps { isLocked$: Observable; loadingCount$: ReturnType; onIsLockedUpdate: OnIsLockedUpdate; - exitWorkspace: () => void; - getWorkspaceUrl: (id: string) => string; branding: ChromeBranding; survey: string | undefined; - currentWorkspace$: BehaviorSubject; - workspaceList$: BehaviorSubject; } export function Header({ @@ -104,8 +99,6 @@ export function Header({ application, basePath, onIsLockedUpdate, - exitWorkspace, - getWorkspaceUrl, homeHref, branding, survey, @@ -259,8 +252,6 @@ export function Header({ navigateToApp={application.navigateToApp} navigateToUrl={application.navigateToUrl} onIsLockedUpdate={onIsLockedUpdate} - exitWorkspace={exitWorkspace} - getWorkspaceUrl={getWorkspaceUrl} closeNav={() => { setIsNavOpen(false); if (toggleCollapsibleNavRef.current) { @@ -269,8 +260,6 @@ export function Header({ }} customNavLink$={observables.customNavLink$} branding={branding} - currentWorkspace$={observables.currentWorkspace$} - workspaceList$={observables.workspaceList$} />
    diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 8281b1ee2f96..832708122d5e 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -31,12 +31,7 @@ import { EuiIcon } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import React from 'react'; -import { - ChromeNavLink, - ChromeRecentlyAccessedHistoryItem, - CoreStart, - WorkspaceAttribute, -} from '../../..'; +import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../../..'; import { HttpStart } from '../../../http'; import { InternalApplicationStart } from '../../../application/types'; import { relativeToAbsolute } from '../../nav_links/to_nav_link'; @@ -65,7 +60,6 @@ export function createEuiListItem({ onClick = () => {}, navigateToApp, dataTestSubj, - externalLink = false, }: Props) { const { href, id, title, disabled, euiIconType, icon, tooltip } = link; @@ -79,7 +73,7 @@ export function createEuiListItem({ } if ( - !externalLink && // ignore external links + !link.externalLink && // ignore external links event.button === 0 && // ignore everything but left clicks !isModifiedOrPrevented(event) ) { @@ -153,34 +147,3 @@ export function createRecentNavLink( }, }; } - -export interface WorkspaceNavLink { - label: string; - title: string; - 'aria-label': string; -} - -export function createWorkspaceNavLink( - href: string, - workspace: WorkspaceAttribute, - navLinks: ChromeNavLink[] -): WorkspaceNavLink { - const label = workspace.name; - let titleAndAriaLabel = label; - const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl)); - if (navLink) { - titleAndAriaLabel = i18n.translate('core.ui.workspaceLinks.linkItem.screenReaderLabel', { - defaultMessage: '{workspaceItemLinkName}, type: {pageType}', - values: { - workspaceItemLinkName: label, - pageType: navLink.title, - }, - }); - } - - return { - label, - title: titleAndAriaLabel, - 'aria-label': titleAndAriaLabel, - }; -} diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 1e756ddcf8c9..0f1d1c267899 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -31,7 +31,7 @@ import { pick } from '@osd/std'; import { CoreId } from '../server'; import { PackageInfo, EnvironmentMode } from '../server/types'; -import { CoreSetup, CoreStart } from '.'; +import { ChromeNavLink, CoreSetup, CoreStart } from '.'; import { ChromeService } from './chrome'; import { FatalErrorsService, FatalErrorsSetup } from './fatal_errors'; import { HttpService } from './http'; @@ -233,7 +233,6 @@ export class CoreSystem { injectedMetadata, notifications, uiSettings, - workspaces, }); this.coreApp.start({ application, http, notifications, uiSettings }); diff --git a/src/plugins/workspace/common/constants.ts b/src/plugins/workspace/common/constants.ts index 557b889d6111..878a0ca9441e 100644 --- a/src/plugins/workspace/common/constants.ts +++ b/src/plugins/workspace/common/constants.ts @@ -3,6 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { i18n } from '@osd/i18n'; +import { AppCategory } from '../../../core/types'; + export const WORKSPACE_APP_ID = 'workspace'; export const WORKSPACE_APP_NAME = 'Workspace'; @@ -14,3 +17,12 @@ export const PATHS = { }; export const WORKSPACE_OP_TYPE_CREATE = 'create'; export const WORKSPACE_OP_TYPE_UPDATE = 'update'; + +export const WORKSPACE_NAV_CATEGORY: AppCategory = { + id: 'workspace', + label: i18n.translate('core.ui.workspaceNavList.label', { + defaultMessage: 'Workspaces', + }), + euiIconType: 'folderClosed', + order: 2000, +}; diff --git a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx b/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx index a3dc973ee095..278ed962966f 100644 --- a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx +++ b/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx @@ -15,10 +15,8 @@ import { import { useObservable } from 'react-use'; import { i18n } from '@osd/i18n'; import { of } from 'rxjs'; - import { WorkspaceAttribute } from 'opensearch-dashboards/public'; -import { useOpenSearchDashboards } from '../../../../../../src/plugins/opensearch_dashboards_react/public'; - +import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { PATHS } from '../../../common/constants'; import { WorkspaceForm, WorkspaceFormData } from '../workspace_creator/workspace_form'; import { WORKSPACE_APP_ID, WORKSPACE_OP_TYPE_UPDATE } from '../../../common/constants'; @@ -27,7 +25,7 @@ import { DeleteWorkspaceModal } from '../delete_workspace_modal'; export const WorkspaceUpdater = () => { const { - services: { application, workspaces, notifications }, + services: { application, workspaces, notifications, http }, } = useOpenSearchDashboards<{ application: ApplicationStart }>(); const currentWorkspace = useObservable( @@ -129,7 +127,50 @@ export const WorkspaceUpdater = () => { } } setDeleteWorkspaceModalVisible(false); - await application.navigateToApp('home'); + if (http) { + const homeUrl = application.getUrlForApp('home', { + path: '/', + absolute: false, + }); + const targetUrl = http.basePath.prepend(http.basePath.remove(homeUrl), { + withoutWorkspace: true, + }); + await application.navigateToUrl(targetUrl); + } + }; + + const exitWorkspace = async () => { + let result; + try { + result = await workspaces?.client.exitWorkspace(); + } catch (error) { + notifications?.toasts.addDanger({ + title: i18n.translate('workspace.exit.failed', { + defaultMessage: 'Failed to exit workspace', + }), + text: error instanceof Error ? error.message : JSON.stringify(error), + }); + return; + } + if (!result?.success) { + notifications?.toasts.addDanger({ + title: i18n.translate('workspace.exit.failed', { + defaultMessage: 'Failed to exit workspace', + }), + text: result?.error, + }); + return; + } + if (http) { + const homeUrl = application.getUrlForApp('home', { + path: '/', + absolute: false, + }); + const targetUrl = http.basePath.prepend(http.basePath.remove(homeUrl), { + withoutWorkspace: true, + }); + await application.navigateToUrl(targetUrl); + } }; return ( @@ -139,6 +180,7 @@ export const WorkspaceUpdater = () => { restrictWidth pageTitle="Update Workspace" rightSideItems={[ + Exit, setDeleteWorkspaceModalVisible(true)}> Delete , diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index f79eb6d9bf50..e154796fe783 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -5,14 +5,19 @@ import { i18n } from '@osd/i18n'; import type { Subscription } from 'rxjs'; +import { combineLatest } from 'rxjs'; import { + ApplicationStart, + AppMountParameters, + AppNavLinkStatus, + ChromeNavLink, CoreSetup, CoreStart, Plugin, - AppMountParameters, - AppNavLinkStatus, + WorkspaceAttribute, + WorkspacesStart, } from '../../../core/public'; -import { WORKSPACE_APP_ID } from '../common/constants'; +import { PATHS, WORKSPACE_APP_ID, WORKSPACE_NAV_CATEGORY } from '../common/constants'; import { mountDropdownList } from './mount'; import { SavedObjectsManagementPluginSetup } from '../../saved_objects_management/public'; import { getWorkspaceColumn } from './components/utils/workspace_column'; @@ -99,7 +104,35 @@ export class WorkspacesPlugin implements Plugin<{}, {}, WorkspacesPluginSetupDep return {}; } - private _changeSavedObjectCurrentWorkspace() { + private workspaceToChromeNavLink( + workspace: WorkspaceAttribute, + workspacesStart: WorkspacesStart, + application: ApplicationStart + ): ChromeNavLink { + const id = WORKSPACE_APP_ID + '/' + workspace.id; + const url = workspacesStart?.formatUrlWithWorkspaceId( + application.getUrlForApp(WORKSPACE_APP_ID, { + path: '/', + absolute: true, + }), + workspace.id + ); + return { + id, + url, + hidden: false, + disabled: false, + baseUrl: url, + href: url, + category: WORKSPACE_NAV_CATEGORY, + title: i18n.translate('core.ui.workspaceNavList.workspaceName', { + defaultMessage: workspace.name, + }), + externalLink: true, + }; + } + + private async _changeSavedObjectCurrentWorkspace() { if (this.coreStart) { return this.coreStart.workspaces.client.currentWorkspaceId$.subscribe( (currentWorkspaceId) => { @@ -109,9 +142,121 @@ export class WorkspacesPlugin implements Plugin<{}, {}, WorkspacesPluginSetupDep } } + private filterByWorkspace( + workspace: WorkspaceAttribute | null | undefined, + allNavLinks: ChromeNavLink[] + ) { + if (!workspace) return allNavLinks; + const features = workspace.features ?? []; + return allNavLinks.filter((item) => features.includes(item.id)); + } + + private filterNavLinks(core: CoreStart, workspaceEnabled: boolean) { + const navLinksService = core.chrome.navLinks; + const chromeNavLinks$ = navLinksService.getNavLinks$(); + if (workspaceEnabled) { + const workspaceList$ = core.workspaces.client.workspaceList$; + const currentWorkspace$ = core.workspaces.client.currentWorkspace$; + combineLatest([workspaceList$, chromeNavLinks$, currentWorkspace$]).subscribe( + ([workspaceList, chromeNavLinks, currentWorkspace]) => { + const filteredNavLinks = new Map(); + chromeNavLinks = this.filterByWorkspace(currentWorkspace, chromeNavLinks); + chromeNavLinks.forEach((chromeNavLink) => { + if (chromeNavLink.id === 'home') { + // set hidden, icon and order for home nav link + const homeNavLink: ChromeNavLink = { + ...chromeNavLink, + hidden: currentWorkspace !== null, + euiIconType: 'logoOpenSearch', + order: 1000, + }; + filteredNavLinks.set(chromeNavLink.id, homeNavLink); + } else { + filteredNavLinks.set(chromeNavLink.id, chromeNavLink); + } + }); + workspaceList + .filter((value, index) => !currentWorkspace && index < 5) + .map((workspace) => + this.workspaceToChromeNavLink(workspace, core.workspaces, core.application) + ) + .forEach((workspaceNavLink) => + filteredNavLinks.set(workspaceNavLink.id, workspaceNavLink) + ); + // See more + const seeMoreId = WORKSPACE_APP_ID + PATHS.list; + const seeMoreUrl = WORKSPACE_APP_ID + PATHS.list; + const seeMoreNavLink: ChromeNavLink = { + id: seeMoreId, + title: i18n.translate('core.ui.workspaceNavList.seeMore', { + defaultMessage: 'SEE MORE', + }), + hidden: currentWorkspace !== null, + disabled: false, + baseUrl: seeMoreUrl, + href: seeMoreUrl, + category: WORKSPACE_NAV_CATEGORY, + }; + filteredNavLinks.set(seeMoreId, seeMoreNavLink); + // Admin + const adminId = 'admin'; + const adminUrl = '/app/admin'; + const adminNavLink: ChromeNavLink = { + id: adminId, + title: i18n.translate('core.ui.workspaceNavList.admin', { + defaultMessage: 'Admin', + }), + hidden: currentWorkspace !== null, + disabled: true, + baseUrl: adminUrl, + href: adminUrl, + euiIconType: 'managementApp', + order: 9000, + }; + filteredNavLinks.set(adminId, adminNavLink); + // Overview only inside workspace + if (currentWorkspace) { + const overviewId = WORKSPACE_APP_ID + PATHS.update; + const overviewUrl = core.workspaces.formatUrlWithWorkspaceId( + core.application.getUrlForApp(WORKSPACE_APP_ID, { + path: PATHS.update, + absolute: true, + }), + currentWorkspace.id + ); + const overviewNavLink: ChromeNavLink = { + id: overviewId, + title: i18n.translate('core.ui.workspaceNavList.overview', { + defaultMessage: 'Overview', + }), + hidden: false, + disabled: false, + baseUrl: overviewUrl, + href: overviewUrl, + euiIconType: 'grid', + order: 1000, + }; + filteredNavLinks.set(overviewId, overviewNavLink); + } + navLinksService.setFilteredNavLinks(filteredNavLinks); + } + ); + } else { + chromeNavLinks$.subscribe((chromeNavLinks) => { + const filteredNavLinks = new Map(); + chromeNavLinks.forEach((chromeNavLink) => + filteredNavLinks.set(chromeNavLink.id, chromeNavLink) + ); + navLinksService.setFilteredNavLinks(filteredNavLinks); + }); + } + } + public start(core: CoreStart) { // If workspace feature is disabled, it will not load the workspace plugin if (core.uiSettings.get('workspace:enabled') === false) { + // set default value for filtered nav links + this.filterNavLinks(core, false); return {}; } @@ -123,6 +268,9 @@ export class WorkspacesPlugin implements Plugin<{}, {}, WorkspacesPluginSetupDep chrome: core.chrome, }); this.currentWorkspaceSubscription = this._changeSavedObjectCurrentWorkspace(); + if (core) { + this.filterNavLinks(core, true); + } return {}; }