From b935e3612807ce59fdf0267ab8094e5fc3cd0079 Mon Sep 17 00:00:00 2001 From: Yulong Ruan Date: Tue, 10 Oct 2023 18:09:17 +0800 Subject: [PATCH] Refactor workspace context menu register (#207) 1. remove unnecessary workspace menu register 2. expose interface from chrome service to allow customize left nav header --------- Signed-off-by: Yulong Ruan --- src/core/public/chrome/chrome_service.mock.ts | 8 + src/core/public/chrome/chrome_service.tsx | 42 +- src/core/public/chrome/index.ts | 1 + .../collapsible_nav.test.tsx.snap | 3096 ++--------------- .../header/__snapshots__/header.test.tsx.snap | 529 +-- .../chrome/ui/header/collapsible_nav.test.tsx | 2 - .../chrome/ui/header/collapsible_nav.tsx | 33 +- .../ui/header/collapsible_nav_header.tsx | 42 - .../public/chrome/ui/header/header.test.tsx | 3 +- src/core/public/chrome/ui/header/header.tsx | 7 +- src/core/public/core_system.test.ts | 4 - src/core/public/core_system.ts | 4 +- src/core/public/index.ts | 9 +- src/core/public/plugins/plugin_context.ts | 1 + .../public/plugins/plugins_service.test.ts | 1 + src/core/public/workspace/index.ts | 7 +- .../workspace/workspaces_service.mock.ts | 2 - .../workspace/workspaces_service.test.ts | 22 +- .../public/workspace/workspaces_service.ts | 53 +- .../dashboard_listing.test.tsx.snap | 5 - .../dashboard_top_nav.test.tsx.snap | 6 - .../workspace_menu/workspace_menu.tsx | 10 +- src/plugins/workspace/public/plugin.ts | 2 +- .../public/render_workspace_menu.tsx | 11 +- 24 files changed, 409 insertions(+), 3491 deletions(-) delete mode 100644 src/core/public/chrome/ui/header/collapsible_nav_header.tsx diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index b6f130270b8b..f69c13c8fc0e 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -33,6 +33,12 @@ import type { PublicMethodsOf } from '@osd/utility-types'; import { ChromeBadge, ChromeBreadcrumb, ChromeService, InternalChromeStart } from './'; import { getLogosMock } from '../../common/mocks'; +const createSetupContractMock = () => { + return { + registerCollapsibleNavHeader: jest.fn(), + }; +}; + const createStartContractMock = () => { const startContract: DeeplyMockedKeys = { getHeaderComponent: jest.fn(), @@ -97,6 +103,7 @@ const createStartContractMock = () => { type ChromeServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { + setup: jest.fn(), start: jest.fn(), stop: jest.fn(), }; @@ -107,4 +114,5 @@ const createMock = () => { export const chromeServiceMock = { create: createMock, createStartContract: createStartContractMock, + createSetupContract: createSetupContractMock, }; diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 0133047a2932..5f08f8edea10 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -99,6 +99,12 @@ interface StartDeps { workspaces: WorkspacesStart; } +type CollapsibleNavHeaderRender = (context: { + basePath: HttpStart['basePath']; + getUrlForApp: InternalApplicationStart['getUrlForApp']; + workspaces: WorkspacesStart; +}) => JSX.Element | null; + /** @internal */ export class ChromeService { private isVisible$!: Observable; @@ -108,6 +114,7 @@ export class ChromeService { private readonly navLinks = new NavLinksService(); private readonly recentlyAccessed = new RecentlyAccessedService(); private readonly docTitle = new DocTitleService(); + private collapsibleNavHeaderRender?: CollapsibleNavHeaderRender; constructor(private readonly params: ConstructorParams) {} @@ -143,6 +150,14 @@ export class ChromeService { ); } + public setup() { + return { + registerCollapsibleNavHeader: (render: CollapsibleNavHeaderRender) => { + this.collapsibleNavHeaderRender = render; + }, + }; + } + public async start({ application, docLinks, @@ -181,6 +196,15 @@ export class ChromeService { localStorage.setItem(IS_LOCKED_KEY, `${isLocked}`); }; + const collapsibleNavHeaderRender = () => + this.collapsibleNavHeaderRender + ? this.collapsibleNavHeaderRender({ + basePath: http.basePath, + workspaces, + getUrlForApp: application.getUrlForApp, + }) + : null; + const getIsNavDrawerLocked$ = isNavDrawerLocked$.pipe(takeUntil(this.stop$)); const logos = getLogos(injectedMetadata.getBranding(), http.basePath.serverBasePath); @@ -264,7 +288,9 @@ export class ChromeService { branding={injectedMetadata.getBranding()} logos={logos} survey={injectedMetadata.getSurvey()} - workspaces={workspaces} + collapsibleNavHeaderRender={ + this.collapsibleNavHeaderRender ? collapsibleNavHeaderRender : undefined + } /> ), @@ -328,6 +354,20 @@ export class ChromeService { } } +/** + * ChromeSetup allows plugins to customize the global chrome header UI rendering + * before the header UI is mounted. + * + * @example + * Customize the Collapsible Nav's (left nav menu) header section: + * ```ts + * core.chrome.registerCollapsibleNavHeader(() => ) + * ``` + */ +export interface ChromeSetup { + registerCollapsibleNavHeader: (render: CollapsibleNavHeaderRender) => void; +} + /** * ChromeStart allows plugins to customize the global chrome header UI and * enrich the UX with additional information about the current location of the diff --git a/src/core/public/chrome/index.ts b/src/core/public/chrome/index.ts index 4cd43362767c..2f230203c949 100644 --- a/src/core/public/chrome/index.ts +++ b/src/core/public/chrome/index.ts @@ -33,6 +33,7 @@ export { ChromeBreadcrumb, ChromeService, ChromeStart, + ChromeSetup, InternalChromeStart, ChromeHelpExtension, } from './chrome_service'; 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 e5a23b8f3362..b732d2904e22 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 @@ -378,131 +378,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` "values": Array [], } } - workspaces={ - Object { - "currentWorkspace$": BehaviorSubject { - "_isScalar": false, - "_value": null, - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [], - "thrownError": null, - }, - "currentWorkspaceId$": BehaviorSubject { - "_isScalar": false, - "_value": "", - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [], - "thrownError": null, - }, - "initialized$": BehaviorSubject { - "_isScalar": false, - "_value": false, - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [], - "thrownError": null, - }, - "renderWorkspaceMenu": [MockFunction], - "workspaceEnabled$": BehaviorSubject { - "_isScalar": false, - "_value": false, - "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, - }, - "workspaceList$": BehaviorSubject { - "_isScalar": false, - "_value": Array [], - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [], - "thrownError": null, - }, - } - } > - - + +
-
- -
- -
+
+ +
+ - + +
+
+ +
+ +
- - -
- - -
- -
- - - OpenSearch Dashboards - - -
-
-
-
-
- -
+ + + OpenSearch Dashboards + + +
+ +
+ +
+
- - +
+
- - + +
-
- -
- -
+
+ +
+ - + +
+
+ +
+ +
- - -
- - -
- -
- - - OpenSearch Dashboards - - -
-
-
-
-
- -
+ + + OpenSearch Dashboards + + +
+ +
+ +
+
- - +
+
- - + +
-
- -
- -
+
+ +
+ - + +
+
+ +
+ +
- - -
- - -
- -
- - - OpenSearch Dashboards - - -
-
-
-
-
- -
+ + + OpenSearch Dashboards + + +
+ +
+ +
+
- - +
+
- - + +
-
- -
- -
+
+ +
+ - + +
+
+ +
+ +
- - -
- - -
- -
- - - OpenSearch Dashboards - - -
-
-
-
-
- -
+ + + OpenSearch Dashboards + + +
+ +
+ +
+
- - +
+
- - + +
-
- -
- -
+
+ +
+ - + +
+
+ +
+ +
- - -
- - -
- -
- - - OpenSearch Dashboards - - -
-
-
-
-
- -
+ + + OpenSearch Dashboards + + +
+ +
+ +
+
- - +
+
- - + +
-
- -
- -
+
+ +
+ - + +
+
+ +
+ +
- - -
- - -
- -
- - - OpenSearch Dashboards - - -
-
-
-
-
- -
+ + + OpenSearch Dashboards + + +
+ +
+ +
+
- - +
+
- - + +
-
- -
- -
+
+ +
+ - + +
+
+ +
+ +
- - -
- - -
- -
- - - OpenSearch Dashboards - - -
-
-
-
-
- -
+ + + OpenSearch Dashboards + + +
+ +
+ +
+
- - +
+
({ htmlIdGenerator: () => () => 'mockId', @@ -94,7 +93,6 @@ function mockProps(branding = {}) { navigateToApp: () => Promise.resolve(), navigateToUrl: () => Promise.resolve(), getUrlForApp: jest.fn(), - workspaces: workspacesServiceMock.createStartContract(), customNavLink$: new BehaviorSubject(undefined), branding, logos: getLogos(branding, mockBasePath.serverBasePath), diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx index af0bb4d4e28e..a5e6e3b7c092 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx @@ -32,17 +32,19 @@ import './collapsible_nav.scss'; import { EuiCollapsibleNav, EuiCollapsibleNavGroup, + EuiFlexGroup, EuiFlexItem, + EuiIcon, EuiListGroup, EuiListGroupItem, EuiShowFor, + EuiText, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { groupBy, sortBy } from 'lodash'; import React, { useRef } from 'react'; import useObservable from 'react-use/lib/useObservable'; import * as Rx from 'rxjs'; -import { WorkspacesStart } from 'opensearch-dashboards/public'; import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../..'; import { AppCategory } from '../../../../types'; import { InternalApplicationStart } from '../../../application'; @@ -55,7 +57,6 @@ import { emptyRecentlyVisited, CollapsibleNavLink, } from './nav_link'; -import { CollapsibleNavHeader } from './collapsible_nav_header'; function getAllCategories(allCategorizedLinks: Record) { const allCategories = {} as Record; @@ -118,6 +119,7 @@ function setIsCategoryOpen(id: string, isOpen: boolean, storage: Storage) { interface Props { appId$: InternalApplicationStart['currentAppId$']; basePath: HttpStart['basePath']; + collapsibleNavHeaderRender?: () => JSX.Element | null; id: string; isLocked: boolean; isNavOpen: boolean; @@ -132,11 +134,11 @@ interface Props { navigateToUrl: InternalApplicationStart['navigateToUrl']; customNavLink$: Rx.Observable; logos: Logos; - workspaces: WorkspacesStart; } export function CollapsibleNav({ basePath, + collapsibleNavHeaderRender, id, isLocked, isNavOpen, @@ -148,7 +150,6 @@ export function CollapsibleNav({ navigateToApp, navigateToUrl, logos, - workspaces, ...observables }: Props) { const navLinks = useObservable(observables.navLinks$, []).filter((link) => !link.hidden); @@ -184,6 +185,13 @@ export function CollapsibleNav({ }); }; + const defaultHeaderName = i18n.translate( + 'core.ui.primaryNav.workspacePickerMenu.defaultHeaderName', + { + defaultMessage: 'OpenSearch Dashboards', + } + ); + return ( - + {collapsibleNavHeaderRender ? ( + collapsibleNavHeaderRender() + ) : ( + + + + + + + + {defaultHeaderName} + + + + + )} {/* merged NavLinks */} {mergedNavLinks.map((item, i) => { diff --git a/src/core/public/chrome/ui/header/collapsible_nav_header.tsx b/src/core/public/chrome/ui/header/collapsible_nav_header.tsx deleted file mode 100644 index b947a149fa19..000000000000 --- a/src/core/public/chrome/ui/header/collapsible_nav_header.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -import { i18n } from '@osd/i18n'; -import React from 'react'; -import useObservable from 'react-use/lib/useObservable'; -import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText, EuiCollapsibleNavGroup } from '@elastic/eui'; -import { WorkspacesStart } from '../../../../public'; - -interface Props { - workspaces: WorkspacesStart; -} - -export function CollapsibleNavHeader({ workspaces }: Props) { - const workspaceEnabled = useObservable(workspaces.workspaceEnabled$, false); - const defaultHeaderName = i18n.translate( - 'core.ui.primaryNav.workspacePickerMenu.defaultHeaderName', - { - defaultMessage: 'OpenSearch Dashboards', - } - ); - - if (!workspaceEnabled) { - return ( - - - - - - - - {defaultHeaderName} - - - - - ); - } else { - return workspaces.renderWorkspaceMenu(); - } -} diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index b039a37b471b..97c13aff36a2 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -33,7 +33,7 @@ import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { httpServiceMock } from '../../../http/http_service.mock'; -import { applicationServiceMock, chromeServiceMock, workspacesServiceMock } from '../../../mocks'; +import { applicationServiceMock, chromeServiceMock } from '../../../mocks'; import { Header } from './header'; import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; @@ -74,7 +74,6 @@ function mockProps() { getWorkspaceUrl: (id: string) => '', survey: '/', logos: chromeServiceMock.createStartContract().logos, - workspaces: workspacesServiceMock.createStartContract(), }; } diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 6c11374dd1c4..698d6a1a1047 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -44,7 +44,6 @@ import classnames from 'classnames'; import React, { createRef, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { Observable } from 'rxjs'; -import { WorkspacesStart } from 'opensearch-dashboards/public'; import { LoadingIndicator } from '../'; import { ChromeBadge, @@ -73,6 +72,7 @@ export interface HeaderProps { appTitle$: Observable; badge$: Observable; breadcrumbs$: Observable; + collapsibleNavHeaderRender?: () => JSX.Element | null; customNavLink$: Observable; homeHref: string; isVisible$: Observable; @@ -94,7 +94,6 @@ export interface HeaderProps { branding: ChromeBranding; logos: Logos; survey: string | undefined; - workspaces: WorkspacesStart; } export function Header({ @@ -107,7 +106,7 @@ export function Header({ branding, survey, logos, - workspaces, + collapsibleNavHeaderRender, ...observables }: HeaderProps) { const isVisible = useObservable(observables.isVisible$, false); @@ -249,6 +248,7 @@ export function Header({
diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index 81dfa97b9afa..24de9ea6b9ea 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -321,10 +321,6 @@ describe('#start()', () => { it('calls workspaces#start()', async () => { await startCore(); expect(MockWorkspacesService.start).toHaveBeenCalledTimes(1); - expect(MockWorkspacesService.start).toHaveBeenCalledWith({ - application: expect.any(Object), - http: expect.any(Object), - }); }); it('calls coreApp#start()', async () => { diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 92da7ea0e011..aef58371e337 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -171,10 +171,12 @@ export class CoreSystem { }); const application = this.application.setup({ context, http }); this.coreApp.setup({ application, http, injectedMetadata, notifications }); + const chrome = this.chrome.setup(); const core: InternalCoreSetup = { application, context, + chrome, fatalErrors: this.fatalErrorsSetup, http, injectedMetadata, @@ -225,7 +227,7 @@ export class CoreSystem { targetDomElement: notificationsTargetDomElement, }); const application = await this.application.start({ http, overlays }); - const workspaces = this.workspaces.start({ application, http }); + const workspaces = this.workspaces.start(); const chrome = await this.chrome.start({ application, docLinks, diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 152b52870bb7..3a8358d0f2a0 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -64,6 +64,7 @@ import { ChromeNavLinkUpdateableFields, ChromeDocTitle, ChromeStart, + ChromeSetup, ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, NavType, @@ -221,6 +222,7 @@ export interface CoreSetup deps.application.registerMountContext(plugin.opaqueId, contextName, provider), }, + chrome: deps.chrome, context: deps.context, fatalErrors: deps.fatalErrors, http: deps.http, diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index f26626ed1ca3..b57573e155cb 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -103,6 +103,7 @@ describe('PluginsService', () => { ]; mockSetupDeps = { application: applicationServiceMock.createInternalSetupContract(), + chrome: chromeServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), http: httpServiceMock.createSetupContract(), diff --git a/src/core/public/workspace/index.ts b/src/core/public/workspace/index.ts index 4ef6aaae7fd4..e46cac0b4b51 100644 --- a/src/core/public/workspace/index.ts +++ b/src/core/public/workspace/index.ts @@ -2,9 +2,4 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -export { - WorkspacesStart, - WorkspacesService, - WorkspacesSetup, - WorkspaceObservables, -} from './workspaces_service'; +export { WorkspacesStart, WorkspacesService, WorkspacesSetup } from './workspaces_service'; diff --git a/src/core/public/workspace/workspaces_service.mock.ts b/src/core/public/workspace/workspaces_service.mock.ts index 3b1408b03045..a8d2a91bd3d1 100644 --- a/src/core/public/workspace/workspaces_service.mock.ts +++ b/src/core/public/workspace/workspaces_service.mock.ts @@ -21,7 +21,6 @@ const createWorkspacesSetupContractMock = () => ({ currentWorkspace$, initialized$, workspaceEnabled$, - registerWorkspaceMenuRender: jest.fn(), }); const createWorkspacesStartContractMock = () => ({ @@ -30,7 +29,6 @@ const createWorkspacesStartContractMock = () => ({ currentWorkspace$, initialized$, workspaceEnabled$, - renderWorkspaceMenu: jest.fn(), }); export type WorkspacesServiceContract = PublicMethodsOf; diff --git a/src/core/public/workspace/workspaces_service.test.ts b/src/core/public/workspace/workspaces_service.test.ts index b2d84152da83..b8f9b17ae0c8 100644 --- a/src/core/public/workspace/workspaces_service.test.ts +++ b/src/core/public/workspace/workspaces_service.test.ts @@ -3,22 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { httpServiceMock } from '../http/http_service.mock'; -import { applicationServiceMock } from '../application/application_service.mock'; -import { WorkspacesService, WorkspacesSetup, WorkspacesStart } from './workspaces_service'; +import { WorkspacesService, WorkspacesStart } from './workspaces_service'; describe('WorkspacesService', () => { let workspaces: WorkspacesService; - let workspacesSetup: WorkspacesSetup; let workspacesStart: WorkspacesStart; beforeEach(() => { workspaces = new WorkspacesService(); - workspacesSetup = workspaces.setup(); - workspacesStart = workspaces.start({ - http: httpServiceMock.createStartContract(), - application: applicationServiceMock.createInternalStartContract(), - }); + workspacesStart = workspaces.start(); }); afterEach(() => { @@ -42,17 +35,6 @@ describe('WorkspacesService', () => { expect(workspacesStart.workspaceList$.value.length).toBe(0); }); - it('should call menu render function', () => { - const renderFn = jest.fn(); - workspacesSetup.registerWorkspaceMenuRender(renderFn); - workspacesStart.renderWorkspaceMenu(); - expect(renderFn).toHaveBeenCalled(); - }); - - it('should return null if NO menu render function was registered', () => { - expect(workspacesStart.renderWorkspaceMenu()).toBe(null); - }); - it('the current workspace should also updated after changing current workspace id', () => { expect(workspacesStart.currentWorkspace$.value).toBe(null); diff --git a/src/core/public/workspace/workspaces_service.ts b/src/core/public/workspace/workspaces_service.ts index 9b8f3cabadee..eb6087f95063 100644 --- a/src/core/public/workspace/workspaces_service.ts +++ b/src/core/public/workspace/workspaces_service.ts @@ -7,21 +7,9 @@ import { BehaviorSubject, combineLatest } from 'rxjs'; import { isEqual } from 'lodash'; import { CoreService, WorkspaceAttribute } from '../../types'; -import { InternalApplicationStart } from '../application'; -import { HttpStart } from '../http'; - -type WorkspaceMenuRenderFn = ({ - basePath, - getUrlForApp, - observables, -}: { - getUrlForApp: InternalApplicationStart['getUrlForApp']; - basePath: HttpStart['basePath']; - observables: WorkspaceObservables; -}) => JSX.Element | null; type WorkspaceObject = WorkspaceAttribute & { libraryReadonly?: boolean }; -export interface WorkspaceObservables { +interface WorkspaceObservables { currentWorkspaceId$: BehaviorSubject; currentWorkspace$: BehaviorSubject; workspaceList$: BehaviorSubject; @@ -33,16 +21,8 @@ enum WORKSPACE_ERROR_REASON_MAP { WORKSPACE_STALED = 'WORKSPACE_STALED', } -/** - * @public - */ -export interface WorkspacesSetup extends WorkspaceObservables { - registerWorkspaceMenuRender: (render: WorkspaceMenuRenderFn) => void; -} - -export interface WorkspacesStart extends WorkspaceObservables { - renderWorkspaceMenu: () => JSX.Element | null; -} +export type WorkspacesSetup = WorkspaceObservables; +export type WorkspacesStart = WorkspaceObservables; export class WorkspacesService implements CoreService { private currentWorkspaceId$ = new BehaviorSubject(''); @@ -50,7 +30,6 @@ export class WorkspacesService implements CoreService(null); private initialized$ = new BehaviorSubject(false); private workspaceEnabled$ = new BehaviorSubject(false); - private _renderWorkspaceMenu: WorkspaceMenuRenderFn | null = null; constructor() { combineLatest([this.initialized$, this.workspaceList$, this.currentWorkspaceId$]).subscribe( @@ -88,38 +67,17 @@ export class WorkspacesService implements CoreService - (this._renderWorkspaceMenu = render), }; } - public start({ - http, - application, - }: { - application: InternalApplicationStart; - http: HttpStart; - }): WorkspacesStart { - const observables = { + public start(): WorkspacesStart { + return { currentWorkspaceId$: this.currentWorkspaceId$, currentWorkspace$: this.currentWorkspace$, workspaceList$: this.workspaceList$, initialized$: this.initialized$, workspaceEnabled$: this.workspaceEnabled$, }; - return { - ...observables, - renderWorkspaceMenu: () => { - if (this._renderWorkspaceMenu) { - return this._renderWorkspaceMenu({ - basePath: http.basePath, - getUrlForApp: application.getUrlForApp, - observables, - }); - } - return null; - }, - }; } public async stop() { @@ -128,6 +86,5 @@ export class WorkspacesService implements CoreService { +export const WorkspaceMenu = ({ basePath, getUrlForApp, workspaces }: Props) => { const [isPopoverOpen, setPopover] = useState(false); - const currentWorkspace = useObservable(observables.currentWorkspace$, null); - const workspaceList = useObservable(observables.workspaceList$, []); + const currentWorkspace = useObservable(workspaces.currentWorkspace$, null); + const workspaceList = useObservable(workspaces.workspaceList$, []); const defaultHeaderName = i18n.translate( 'core.ui.primaryNav.workspacePickerMenu.defaultHeaderName', diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index c972e5e8fb0e..04e9d20aa1cb 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -45,7 +45,7 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> } public async setup(core: CoreSetup, { savedObjectsManagement }: WorkspacePluginSetupDeps) { core.workspaces.workspaceEnabled$.next(true); - core.workspaces.registerWorkspaceMenuRender(renderWorkspaceMenu); + core.chrome.registerCollapsibleNavHeader(renderWorkspaceMenu); const workspaceClient = new WorkspaceClient(core.http, core.workspaces); await workspaceClient.init(); diff --git a/src/plugins/workspace/public/render_workspace_menu.tsx b/src/plugins/workspace/public/render_workspace_menu.tsx index 12313594cbdc..c180d5c12e11 100644 --- a/src/plugins/workspace/public/render_workspace_menu.tsx +++ b/src/plugins/workspace/public/render_workspace_menu.tsx @@ -4,20 +4,17 @@ */ import React from 'react'; - +import { ApplicationStart, HttpSetup, WorkspacesStart } from '../../../core/public'; import { WorkspaceMenu } from './components/workspace_menu/workspace_menu'; -import { ApplicationStart, HttpSetup, WorkspaceObservables } from '../../../core/public'; export function renderWorkspaceMenu({ basePath, getUrlForApp, - observables, + workspaces, }: { getUrlForApp: ApplicationStart['getUrlForApp']; basePath: HttpSetup['basePath']; - observables: WorkspaceObservables; + workspaces: WorkspacesStart; }) { - return ( - - ); + return ; }