From ece40266b5551082b523e509b780082737f93806 Mon Sep 17 00:00:00 2001 From: Yulong Ruan Date: Fri, 15 Mar 2024 09:31:03 +0800 Subject: [PATCH 1/5] Register a workspace dropdown menu at the top of left nav bar When workspace is enabled, the workspace plugin will register a workspace dropdown menu via `chrome.registerCollapsibleNavHeader` which displays a list of workspaces, links to create workspace page and workspace list page. Signed-off-by: Yulong Ruan --- src/core/public/index.ts | 6 +- src/core/public/mocks.ts | 1 + src/core/public/utils/index.ts | 1 + src/core/public/workspace/index.ts | 7 +- .../workspace/workspaces_service.mock.ts | 44 ++-- .../public/workspace/workspaces_service.ts | 2 +- src/plugins/workspace/common/constants.ts | 4 +- .../workspace_menu/workspace_menu.test.tsx | 120 +++++++++++ .../workspace_menu/workspace_menu.tsx | 191 ++++++++++++++++++ src/plugins/workspace/public/plugin.test.ts | 7 + src/plugins/workspace/public/plugin.ts | 13 ++ .../public/render_workspace_menu.tsx | 12 ++ 12 files changed, 382 insertions(+), 26 deletions(-) create mode 100644 src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx create mode 100644 src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx create mode 100644 src/plugins/workspace/public/render_workspace_menu.tsx diff --git a/src/core/public/index.ts b/src/core/public/index.ts index c9d416cb6f43..c82457ef2184 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -94,7 +94,7 @@ export type { Logos } from '../common'; export { PackageInfo, EnvironmentMode } from '../server/types'; /** @interal */ export { CoreContext, CoreSystem } from './core_system'; -export { DEFAULT_APP_CATEGORIES } from '../utils'; +export { DEFAULT_APP_CATEGORIES, WORKSPACE_TYPE } from '../utils'; export { AppCategory, UiSettingsParams, @@ -357,6 +357,4 @@ export { export { __osdBootstrap__ } from './osd_bootstrap'; -export { WorkspacesStart, WorkspacesSetup, WorkspacesService } from './workspace'; - -export { WORKSPACE_TYPE } from '../utils'; +export { WorkspacesStart, WorkspacesSetup, WorkspacesService, WorkspaceObject } from './workspace'; diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 3acc71424b91..05c3b7d18d1b 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -74,6 +74,7 @@ function createCoreSetupMock({ } = {}) { const mock = { application: applicationServiceMock.createSetupContract(), + chrome: chromeServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), docLinks: docLinksServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), diff --git a/src/core/public/utils/index.ts b/src/core/public/utils/index.ts index c0c6f2582e9c..30055b0ff81c 100644 --- a/src/core/public/utils/index.ts +++ b/src/core/public/utils/index.ts @@ -36,4 +36,5 @@ export { WORKSPACE_TYPE, formatUrlWithWorkspaceId, getWorkspaceIdFromUrl, + cleanWorkspaceId, } from '../../utils'; diff --git a/src/core/public/workspace/index.ts b/src/core/public/workspace/index.ts index 4b9b2c86f649..712ad657fa65 100644 --- a/src/core/public/workspace/index.ts +++ b/src/core/public/workspace/index.ts @@ -3,4 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -export { WorkspacesStart, WorkspacesService, WorkspacesSetup } from './workspaces_service'; +export { + WorkspacesStart, + WorkspacesService, + WorkspacesSetup, + WorkspaceObject, +} from './workspaces_service'; diff --git a/src/core/public/workspace/workspaces_service.mock.ts b/src/core/public/workspace/workspaces_service.mock.ts index ae56c035eb3a..9e8cdfce7393 100644 --- a/src/core/public/workspace/workspaces_service.mock.ts +++ b/src/core/public/workspace/workspaces_service.mock.ts @@ -6,27 +6,33 @@ import { BehaviorSubject } from 'rxjs'; import type { PublicMethodsOf } from '@osd/utility-types'; -import { WorkspacesService } from './workspaces_service'; -import { WorkspaceAttribute } from '..'; +import { WorkspacesService, WorkspaceObject } from './workspaces_service'; -const currentWorkspaceId$ = new BehaviorSubject(''); -const workspaceList$ = new BehaviorSubject([]); -const currentWorkspace$ = new BehaviorSubject(null); -const initialized$ = new BehaviorSubject(false); - -const createWorkspacesSetupContractMock = () => ({ - currentWorkspaceId$, - workspaceList$, - currentWorkspace$, - initialized$, -}); +const createWorkspacesSetupContractMock = () => { + const currentWorkspaceId$ = new BehaviorSubject(''); + const workspaceList$ = new BehaviorSubject([]); + const currentWorkspace$ = new BehaviorSubject(null); + const initialized$ = new BehaviorSubject(false); + return { + currentWorkspaceId$, + workspaceList$, + currentWorkspace$, + initialized$, + }; +}; -const createWorkspacesStartContractMock = () => ({ - currentWorkspaceId$, - workspaceList$, - currentWorkspace$, - initialized$, -}); +const createWorkspacesStartContractMock = () => { + const currentWorkspaceId$ = new BehaviorSubject(''); + const workspaceList$ = new BehaviorSubject([]); + const currentWorkspace$ = new BehaviorSubject(null); + const initialized$ = new BehaviorSubject(false); + return { + currentWorkspaceId$, + workspaceList$, + currentWorkspace$, + initialized$, + }; +}; export type WorkspacesServiceContract = PublicMethodsOf; const createMock = (): jest.Mocked => ({ diff --git a/src/core/public/workspace/workspaces_service.ts b/src/core/public/workspace/workspaces_service.ts index cc19b3c79229..e4cf3bc7a826 100644 --- a/src/core/public/workspace/workspaces_service.ts +++ b/src/core/public/workspace/workspaces_service.ts @@ -8,7 +8,7 @@ import { isEqual } from 'lodash'; import { CoreService, WorkspaceAttribute } from '../../types'; -type WorkspaceObject = WorkspaceAttribute & { readonly?: boolean }; +export type WorkspaceObject = WorkspaceAttribute & { readonly?: boolean }; interface WorkspaceObservables { /** diff --git a/src/plugins/workspace/common/constants.ts b/src/plugins/workspace/common/constants.ts index 6ae89c0edad5..d2da08acb52d 100644 --- a/src/plugins/workspace/common/constants.ts +++ b/src/plugins/workspace/common/constants.ts @@ -3,8 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -export const WORKSPACE_OVERVIEW_APP_ID = 'workspace_overview'; export const WORKSPACE_FATAL_ERROR_APP_ID = 'workspace_fatal_error'; +export const WORKSPACE_CREATE_APP_ID = 'workspace_create'; +export const WORKSPACE_LIST_APP_ID = 'workspace_list'; +export const WORKSPACE_OVERVIEW_APP_ID = 'workspace_overview'; export const WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID = 'workspace'; export const WORKSPACE_CONFLICT_CONTROL_SAVED_OBJECTS_CLIENT_WRAPPER_ID = 'workspace_conflict_control'; diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx new file mode 100644 index 000000000000..c63b232bb232 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx @@ -0,0 +1,120 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; + +import { WorkspaceMenu } from './workspace_menu'; +import { coreMock } from '../../../../../core/public/mocks'; +import { CoreStart } from '../../../../../core/public'; + +describe('', () => { + let coreStartMock: CoreStart; + + beforeEach(() => { + coreStartMock = coreMock.createStart(); + coreStartMock.workspaces.initialized$.next(true); + jest.spyOn(coreStartMock.application, 'getUrlForApp').mockImplementation((appId: string) => { + return `https://test.com/app/${appId}`; + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('should display a list of workspaces in the dropdown', () => { + coreStartMock.workspaces.workspaceList$.next([ + { id: 'workspace-1', name: 'workspace 1' }, + { id: 'workspace-2', name: 'workspace 2' }, + ]); + + render(); + fireEvent.click(screen.getByText(/select a workspace/i)); + + expect(screen.getByText(/workspace 1/i)).toBeInTheDocument(); + expect(screen.getByText(/workspace 2/i)).toBeInTheDocument(); + }); + + it('should display current workspace name', () => { + coreStartMock.workspaces.currentWorkspace$.next({ id: 'workspace-1', name: 'workspace 1' }); + render(); + expect(screen.getByText(/workspace 1/i)).toBeInTheDocument(); + }); + + it('should close the workspace dropdown list', async () => { + render(); + fireEvent.click(screen.getByText(/select a workspace/i)); + + expect(screen.getByLabelText(/close workspace dropdown/i)).toBeInTheDocument(); + fireEvent.click(screen.getByLabelText(/close workspace dropdown/i)); + await waitFor(() => { + expect(screen.queryByLabelText(/close workspace dropdown/i)).not.toBeInTheDocument(); + }); + }); + + it('should navigate to the workspace', () => { + coreStartMock.workspaces.workspaceList$.next([ + { id: 'workspace-1', name: 'workspace 1' }, + { id: 'workspace-2', name: 'workspace 2' }, + ]); + + const originalLocation = window.location; + Object.defineProperty(window, 'location', { + value: { + assign: jest.fn(), + }, + }); + + render(); + fireEvent.click(screen.getByText(/select a workspace/i)); + fireEvent.click(screen.getByText(/workspace 1/i)); + + expect(window.location.assign).toHaveBeenCalledWith( + 'https://test.com/w/workspace-1/app/workspace_overview' + ); + + Object.defineProperty(window, 'location', { + value: originalLocation, + }); + }); + + it('should navigate to create workspace page', () => { + const originalLocation = window.location; + Object.defineProperty(window, 'location', { + value: { + assign: jest.fn(), + }, + }); + + render(); + fireEvent.click(screen.getByText(/select a workspace/i)); + fireEvent.click(screen.getByText(/create workspace/i)); + expect(window.location.assign).toHaveBeenCalledWith('https://test.com/app/workspace_create'); + + Object.defineProperty(window, 'location', { + value: originalLocation, + }); + }); + + it('should navigate to workspace list page', () => { + const originalLocation = window.location; + Object.defineProperty(window, 'location', { + value: { + assign: jest.fn(), + }, + }); + + render(); + fireEvent.click(screen.getByText(/select a workspace/i)); + fireEvent.click(screen.getByText(/all workspace/i)); + expect(window.location.assign).toHaveBeenCalledWith('https://test.com/app/workspace_list'); + + Object.defineProperty(window, 'location', { + value: originalLocation, + }); + }); +}); diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx new file mode 100644 index 000000000000..a095d4e7bb2f --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx @@ -0,0 +1,191 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import React, { useState } from 'react'; +import { useObservable } from 'react-use'; +import { + EuiButtonIcon, + EuiContextMenu, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiListGroup, + EuiListGroupItem, + EuiPopover, + EuiText, +} from '@elastic/eui'; +import type { EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; + +import { + WORKSPACE_CREATE_APP_ID, + WORKSPACE_LIST_APP_ID, + WORKSPACE_OVERVIEW_APP_ID, +} from '../../../common/constants'; +import { cleanWorkspaceId, formatUrlWithWorkspaceId } from '../../../../../core/public/utils'; +import { CoreStart, WorkspaceObject } from '../../../../../core/public'; + +interface Props { + coreStart: CoreStart; +} + +/** + * Show maximum five workspaces in the dropdown list, the current selected workspace + * will be on the top of the list. + */ +function getFilteredWorkspaceList( + workspaceList: WorkspaceObject[], + currentWorkspace: WorkspaceObject | null +): WorkspaceObject[] { + return [ + ...(currentWorkspace ? [currentWorkspace] : []), + ...workspaceList.filter((workspace) => workspace.id !== currentWorkspace?.id), + ].slice(0, 5); +} + +export const WorkspaceMenu = ({ coreStart }: Props) => { + const [isPopoverOpen, setPopover] = useState(false); + const currentWorkspace = useObservable(coreStart.workspaces.currentWorkspace$, null); + const workspaceList = useObservable(coreStart.workspaces.workspaceList$, []); + + const defaultHeaderName = i18n.translate( + 'core.ui.primaryNav.workspacePickerMenu.defaultHeaderName', + { + defaultMessage: 'Select a workspace', + } + ); + const filteredWorkspaceList = getFilteredWorkspaceList(workspaceList, currentWorkspace); + const currentWorkspaceName = currentWorkspace?.name ?? defaultHeaderName; + + const openPopover = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const workspaceToItem = (workspace: WorkspaceObject) => { + const workspaceURL = formatUrlWithWorkspaceId( + coreStart.application.getUrlForApp(WORKSPACE_OVERVIEW_APP_ID, { + absolute: false, + }), + workspace.id, + coreStart.http.basePath + ); + const name = + currentWorkspace?.name === workspace.name ? ( + + {workspace.name} + + ) : ( + workspace.name + ); + return { + name, + key: workspace.id, + icon: , + onClick: () => { + window.location.assign(workspaceURL); + }, + }; + }; + + const getWorkspaceListItems = () => { + const workspaceListItems: EuiContextMenuPanelItemDescriptor[] = filteredWorkspaceList.map( + workspaceToItem + ); + workspaceListItems.push({ + icon: , + name: i18n.translate('core.ui.primaryNav.workspaceContextMenu.createWorkspace', { + defaultMessage: 'Create workspace', + }), + key: WORKSPACE_CREATE_APP_ID, + onClick: () => { + window.location.assign( + cleanWorkspaceId( + coreStart.application.getUrlForApp(WORKSPACE_CREATE_APP_ID, { + absolute: false, + }) + ) + ); + }, + }); + workspaceListItems.push({ + icon: , + name: i18n.translate('core.ui.primaryNav.workspaceContextMenu.allWorkspace', { + defaultMessage: 'All workspaces', + }), + key: WORKSPACE_LIST_APP_ID, + onClick: () => { + window.location.assign( + cleanWorkspaceId( + coreStart.application.getUrlForApp(WORKSPACE_LIST_APP_ID, { + absolute: false, + }) + ) + ); + }, + }); + return workspaceListItems; + }; + + const currentWorkspaceButton = ( + <> + + + + + ); + + const currentWorkspaceTitle = ( + + + {currentWorkspaceName} + + + + + + ); + + const panels = [ + { + id: 0, + title: currentWorkspaceTitle, + items: getWorkspaceListItems(), + }, + ]; + + return ( + + + + ); +}; diff --git a/src/plugins/workspace/public/plugin.test.ts b/src/plugins/workspace/public/plugin.test.ts index 1bdbd7ef31ad..2306c460b88d 100644 --- a/src/plugins/workspace/public/plugin.test.ts +++ b/src/plugins/workspace/public/plugin.test.ts @@ -127,4 +127,11 @@ describe('Workspace plugin', () => { expect(applicationStartMock.navigateToApp).toBeCalledWith(WORKSPACE_OVERVIEW_APP_ID); windowSpy.mockRestore(); }); + + it('#setup register workspace dropdown menu when setup', async () => { + const setupMock = coreMock.createSetup(); + const workspacePlugin = new WorkspacePlugin(); + await workspacePlugin.setup(setupMock); + expect(setupMock.chrome.registerCollapsibleNavHeader).toBeCalledTimes(1); + }); }); diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index e3ecdc34bfb9..5cd4fcacec34 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -15,6 +15,7 @@ import { WORKSPACE_FATAL_ERROR_APP_ID, WORKSPACE_OVERVIEW_APP_ID } from '../comm import { getWorkspaceIdFromUrl } from '../../../core/public/utils'; import { Services } from './types'; import { WorkspaceClient } from './workspace_client'; +import { renderWorkspaceMenu } from './render_workspace_menu'; type WorkspaceAppType = (params: AppMountParameters, services: Services) => () => void; @@ -30,9 +31,11 @@ export class WorkspacePlugin implements Plugin<{}, {}, {}> { }); } } + private getWorkspaceIdFromURL(basePath?: string): string | null { return getWorkspaceIdFromUrl(window.location.href, basePath); } + public async setup(core: CoreSetup) { const workspaceClient = new WorkspaceClient(core.http, core.workspaces); await workspaceClient.init(); @@ -97,6 +100,16 @@ export class WorkspacePlugin implements Plugin<{}, {}, {}> { }, }); + /** + * Register workspace dropdown selector on the top of left navigation menu + */ + core.chrome.registerCollapsibleNavHeader(() => { + if (!this.coreStart) { + return null; + } + return renderWorkspaceMenu(this.coreStart); + }); + return {}; } diff --git a/src/plugins/workspace/public/render_workspace_menu.tsx b/src/plugins/workspace/public/render_workspace_menu.tsx new file mode 100644 index 000000000000..2ce3ee21ee00 --- /dev/null +++ b/src/plugins/workspace/public/render_workspace_menu.tsx @@ -0,0 +1,12 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { CoreStart } from '../../../core/public'; +import { WorkspaceMenu } from './components/workspace_menu/workspace_menu'; + +export function renderWorkspaceMenu(coreStart: CoreStart) { + return ; +} From 2d3bb787282a6686ce2c47c15fb791efa3a442b7 Mon Sep 17 00:00:00 2001 From: Yulong Ruan Date: Fri, 15 Mar 2024 10:02:06 +0800 Subject: [PATCH 2/5] tweaks comments Signed-off-by: Yulong Ruan --- .../public/components/workspace_menu/workspace_menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx index a095d4e7bb2f..6b0932edf723 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx @@ -32,7 +32,7 @@ interface Props { } /** - * Show maximum five workspaces in the dropdown list, the current selected workspace + * Return maximum five workspaces, the current selected workspace * will be on the top of the list. */ function getFilteredWorkspaceList( From 09638a293968011520f77c6db2b079bf3dcd6426 Mon Sep 17 00:00:00 2001 From: Yulong Ruan Date: Fri, 15 Mar 2024 10:07:26 +0800 Subject: [PATCH 3/5] update CHANGELOG Signed-off-by: Yulong Ruan --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe54b0d0deec..0cea2c205910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multiple Datasource] Add default functionality for customer to choose default datasource ([#6058](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/6058)) - [Multiple Datasource] Add import support for Vega when specifying a datasource ([#6123](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6123)) - [Workspace] Validate if workspace exists when setup inside a workspace ([#6154](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6154)) +- [Workspace] Register a workspace dropdown menu at the top of left nav bar ([#6150](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6150)) ### 🐛 Bug Fixes From f077b73519296f84eca206dfe1532467b9bee900 Mon Sep 17 00:00:00 2001 From: Yulong Ruan Date: Fri, 15 Mar 2024 11:03:09 +0800 Subject: [PATCH 4/5] removed unnecessary file Signed-off-by: Yulong Ruan --- src/plugins/workspace/public/plugin.ts | 5 +++-- .../workspace/public/render_workspace_menu.tsx | 12 ------------ 2 files changed, 3 insertions(+), 14 deletions(-) delete mode 100644 src/plugins/workspace/public/render_workspace_menu.tsx diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index 5cd4fcacec34..8a5bf9b125f5 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -4,6 +4,7 @@ */ import type { Subscription } from 'rxjs'; +import React from 'react'; import { Plugin, CoreStart, @@ -15,7 +16,7 @@ import { WORKSPACE_FATAL_ERROR_APP_ID, WORKSPACE_OVERVIEW_APP_ID } from '../comm import { getWorkspaceIdFromUrl } from '../../../core/public/utils'; import { Services } from './types'; import { WorkspaceClient } from './workspace_client'; -import { renderWorkspaceMenu } from './render_workspace_menu'; +import { WorkspaceMenu } from './components/workspace_menu/workspace_menu'; type WorkspaceAppType = (params: AppMountParameters, services: Services) => () => void; @@ -107,7 +108,7 @@ export class WorkspacePlugin implements Plugin<{}, {}, {}> { if (!this.coreStart) { return null; } - return renderWorkspaceMenu(this.coreStart); + return React.createElement(WorkspaceMenu, { coreStart: this.coreStart }); }); return {}; diff --git a/src/plugins/workspace/public/render_workspace_menu.tsx b/src/plugins/workspace/public/render_workspace_menu.tsx deleted file mode 100644 index 2ce3ee21ee00..000000000000 --- a/src/plugins/workspace/public/render_workspace_menu.tsx +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { CoreStart } from '../../../core/public'; -import { WorkspaceMenu } from './components/workspace_menu/workspace_menu'; - -export function renderWorkspaceMenu(coreStart: CoreStart) { - return ; -} From 862ae3b5186e64050287f89a1822462ac351ed29 Mon Sep 17 00:00:00 2001 From: Yulong Ruan Date: Thu, 21 Mar 2024 07:59:34 +0800 Subject: [PATCH 5/5] change workspace dropdown menu id Signed-off-by: Yulong Ruan --- .../public/components/workspace_menu/workspace_menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx index 6b0932edf723..5b16b9766b22 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx @@ -177,7 +177,7 @@ export const WorkspaceMenu = ({ coreStart }: Props) => { return (