From 6aa56e023b6bfa1e59f9c6335694f5772baaed21 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 4 Sep 2024 17:42:07 +0000 Subject: [PATCH] [navigation] Add workspace icon to left nav / workspace picker menu / home page. (#7823) (#7994) * feat: show workspace picker content in left nav Signed-off-by: SuZhou-Joe * fix: bootstrap error Signed-off-by: SuZhou-Joe * fix: unit test error Signed-off-by: SuZhou-Joe * feat: finish picker content Signed-off-by: SuZhou-Joe * feat: finish picker content Signed-off-by: SuZhou-Joe * feat: only register index patterns to settings and setup when workspace is disabled Signed-off-by: SuZhou-Joe * fix: unit test Signed-off-by: SuZhou-Joe * feat: put discover 2.0 behind discover Signed-off-by: SuZhou-Joe * feat: add coverage Signed-off-by: SuZhou-Joe * feat: improve test coverage Signed-off-by: SuZhou-Joe * feat: merge conflict Signed-off-by: SuZhou-Joe * feat: optimize code based on comment Signed-off-by: SuZhou-Joe * feat: optimize code based on comment Signed-off-by: SuZhou-Joe * feat: optimize filter code Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: add new icon to left navigation and workspace picker menu Signed-off-by: SuZhou-Joe * feat: change use case card in home Signed-off-by: SuZhou-Joe * feat: optimize alignment Signed-off-by: SuZhou-Joe * Changeset file for PR #7823 created/updated * feat: alignment optimize Signed-off-by: SuZhou-Joe * feat: use new icons in workspace picker Signed-off-by: SuZhou-Joe * feat: optimize color Signed-off-by: SuZhou-Joe * fix: unit test error Signed-off-by: SuZhou-Joe * fix: unit test Signed-off-by: SuZhou-Joe * fix: unit test Signed-off-by: SuZhou-Joe * feat: increase test coverage Signed-off-by: SuZhou-Joe * feat: remove useless code Signed-off-by: SuZhou-Joe * Add workspace icon in workspace creator (#19) Signed-off-by: Lin Wang * fix: fatal error when visibleUseCases is empty Signed-off-by: SuZhou-Joe --------- Signed-off-by: SuZhou-Joe Signed-off-by: Lin Wang Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Co-authored-by: Lin Wang (cherry picked from commit 1974ca1b1f0ac01f5ac94bb52b8c68215b94e320) (cherry picked from commit cbdb0fd4222f50eba8fd5cc1c166f0b2887b45c0) Signed-off-by: github-actions[bot] --- changelogs/fragments/7823.yml | 2 + .../header/collapsible_nav_group_enabled.tsx | 13 +-- .../collapsible_nav_group_enabled_top.scss | 3 + ...collapsible_nav_group_enabled_top.test.tsx | 62 +++++++++--- .../collapsible_nav_group_enabled_top.tsx | 95 ++++++++++++------- .../content_management/public/mocks.ts | 4 +- .../setup_get_start_card.scss | 3 + .../setup_get_start_card.tsx | 8 +- .../use_case_overview/setup_overview.scss | 3 + .../use_case_overview/setup_overview.tsx | 7 +- .../workspace_form_summary_panel.test.tsx | 1 + .../workspace_form_summary_panel.tsx | 12 ++- .../workspace_menu/workspace_menu.test.tsx | 4 +- .../workspace_menu/workspace_menu.tsx | 38 +++----- .../workspace_picker_content.tsx | 8 +- src/plugins/workspace/public/plugin.test.ts | 82 +++++++++++++--- src/plugins/workspace/public/plugin.ts | 1 + src/plugins/workspace/public/types.ts | 1 + src/plugins/workspace/public/utils.test.ts | 2 + src/plugins/workspace/public/utils.ts | 2 + 20 files changed, 235 insertions(+), 116 deletions(-) create mode 100644 changelogs/fragments/7823.yml create mode 100644 src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.scss create mode 100644 src/plugins/workspace/public/components/home_get_start_card/setup_get_start_card.scss create mode 100644 src/plugins/workspace/public/components/use_case_overview/setup_overview.scss diff --git a/changelogs/fragments/7823.yml b/changelogs/fragments/7823.yml new file mode 100644 index 000000000000..983365a61c22 --- /dev/null +++ b/changelogs/fragments/7823.yml @@ -0,0 +1,2 @@ +feat: +- Add workspace icon to left nav / workspace picker menu / home page. ([#7823](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7823)) \ No newline at end of file diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx index 21ff68f879e2..9b64995b1824 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx @@ -91,14 +91,6 @@ export function CollapsibleNavGroupEnabled({ const appId = useObservable(observables.appId$, ''); const navGroupsMap = useObservable(observables.navGroupsMap$, {}); const currentNavGroup = useObservable(observables.currentNavGroup$, undefined); - const firstVisibleNavLinkOfAllUseCase = useMemo( - () => - fulfillRegistrationLinksToChromeNavLinks( - navGroupsMap[ALL_USE_CASE_ID]?.navLinks || [], - navLinks - )[0], - [navGroupsMap, navLinks] - ); const visibleUseCases = useMemo(() => getVisibleUseCases(navGroupsMap), [navGroupsMap]); @@ -303,7 +295,8 @@ export function CollapsibleNavGroupEnabled({ > {collapsibleNavHeaderRender()} - + ) : null} ', () => { logos: getLogos({}, mockBasePath.serverBasePath), shouldShrinkNavigation: false, visibleUseCases: [], + navGroupsMap: {}, + navLinks: [], currentWorkspace$: new BehaviorSubject(null), setCurrentNavGroup: jest.fn(), }; }; it('should render back icon when inside a workspace of all use case', async () => { - const props = { + const props: CollapsibleNavTopProps = { ...getMockedProps(), currentWorkspace$: new BehaviorSubject({ id: 'foo', name: 'foo' }), visibleUseCases: [ { - id: ALL_USE_CASE_ID, + ...DEFAULT_NAV_GROUPS.all, title: 'navGroupFoo', description: 'navGroupFoo', - navLinks: [], + navLinks: [ + { + id: 'firstVisibleNavLinkOfAllUseCase', + }, + ], }, ], + navGroupsMap: { + [DEFAULT_NAV_GROUPS.all.id]: { + ...DEFAULT_NAV_GROUPS.all, + title: 'navGroupFoo', + description: 'navGroupFoo', + navLinks: [ + { + id: 'firstVisibleNavLinkOfAllUseCase', + }, + ], + }, + }, + navLinks: [ + getMockedNavLink({ + id: 'firstVisibleNavLinkOfAllUseCase', + }), + ], currentNavGroup: { id: 'navGroupFoo', title: 'navGroupFoo', description: 'navGroupFoo', navLinks: [], }, - firstVisibleNavLinkOfAllUseCase: getMockedNavLink({ - id: 'firstVisibleNavLinkOfAllUseCase', - }), }; - const { findByTestId, findByText, getByTestId } = render(); - await findByTestId('collapsibleNavBackButton'); - await findByText('Back'); - fireEvent.click(getByTestId('collapsibleNavBackButton')); + const { findByTestId, getByTestId } = render(); + await findByTestId(`collapsibleNavIcon-${DEFAULT_NAV_GROUPS.all.icon}`); + fireEvent.click(getByTestId(`collapsibleNavIcon-${DEFAULT_NAV_GROUPS.all.icon}`)); expect(props.navigateToApp).toBeCalledWith('firstVisibleNavLinkOfAllUseCase'); expect(props.setCurrentNavGroup).toBeCalledWith(ALL_USE_CASE_ID); }); it('should render home icon when not in a workspace', async () => { - const { findByTestId } = render(); + const props = getMockedProps(); + const { findByTestId, getByTestId } = render(); await findByTestId('collapsibleNavHome'); + fireEvent.click(getByTestId('collapsibleNavHome')); + expect(props.navigateToApp).toBeCalledWith('home'); }); it('should render expand icon when collapsed', async () => { @@ -79,4 +101,18 @@ describe('', () => { ); await findByTestId('collapsibleNavShrinkButton'); }); + + it('should render successfully without error when visibleUseCases is empty but inside a workspace', async () => { + expect(() => + render( + ({ id: 'foo', name: 'bar' }) + } + shouldShrinkNavigation + /> + ) + ).not.toThrow(); + }); }); diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx index 067fb2ffd2e1..39e985d870ef 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { Logos, WorkspacesStart } from 'opensearch-dashboards/public'; import { @@ -12,19 +12,21 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, + EuiPanel, EuiSpacer, EuiText, } from '@elastic/eui'; import { InternalApplicationStart } from 'src/core/public/application'; -import { i18n } from '@osd/i18n'; import { createEuiListItem } from './nav_link'; import { ChromeNavGroupServiceStartContract, NavGroupItemInMap } from '../../nav_group'; import { ChromeNavLink } from '../../nav_links'; import { ALL_USE_CASE_ID } from '../../../../../core/utils'; +import { fulfillRegistrationLinksToChromeNavLinks } from '../../utils'; +import './collapsible_nav_group_enabled_top.scss'; export interface CollapsibleNavTopProps { homeLink?: ChromeNavLink; - firstVisibleNavLinkOfAllUseCase?: ChromeNavLink; + navGroupsMap: Record; currentNavGroup?: NavGroupItemInMap; navigateToApp: InternalApplicationStart['navigateToApp']; logos: Logos; @@ -33,6 +35,7 @@ export interface CollapsibleNavTopProps { visibleUseCases: NavGroupItemInMap[]; currentWorkspace$: WorkspacesStart['currentWorkspace$']; setCurrentNavGroup: ChromeNavGroupServiceStartContract['setCurrentNavGroup']; + navLinks: ChromeNavLink[]; } export const CollapsibleNavTop = ({ @@ -45,10 +48,20 @@ export const CollapsibleNavTop = ({ currentWorkspace$, setCurrentNavGroup, homeLink, - firstVisibleNavLinkOfAllUseCase, + navGroupsMap, + navLinks, }: CollapsibleNavTopProps) => { const currentWorkspace = useObservable(currentWorkspace$); + const firstVisibleNavLinkInFirstVisibleUseCase = useMemo( + () => + fulfillRegistrationLinksToChromeNavLinks( + navGroupsMap[visibleUseCases[0]?.id]?.navLinks || [], + navLinks + )[0], + [navGroupsMap, navLinks, visibleUseCases] + ); + /** * We can ensure that left nav is inside second level once all the following conditions are met: * 1. Inside a workspace @@ -57,9 +70,15 @@ export const CollapsibleNavTop = ({ */ const isInsideSecondLevelOfAllWorkspace = !!currentWorkspace && - visibleUseCases[0].id === ALL_USE_CASE_ID && + visibleUseCases[0]?.id === ALL_USE_CASE_ID && currentNavGroup?.id !== ALL_USE_CASE_ID; + const homeIcon = logos.Mark.url; + const icon = + !!currentWorkspace && visibleUseCases.length === 1 + ? visibleUseCases[0].icon || homeIcon + : homeIcon; + const shouldShowBackButton = !shouldShrinkNavigation && isInsideSecondLevelOfAllWorkspace; const shouldShowHomeLink = !shouldShrinkNavigation && !shouldShowBackButton; @@ -74,39 +93,47 @@ export const CollapsibleNavTop = ({ return { 'data-test-subj': propsForHomeIcon['data-test-subj'], onClick: propsForHomeIcon.onClick, - href: propsForHomeIcon.href, }; } return {}; }, [homeLink, navigateToApp]); + const onIconClick = useCallback( + (e: React.MouseEvent) => { + if (shouldShowBackButton || visibleUseCases.length === 1) { + if (firstVisibleNavLinkInFirstVisibleUseCase) { + navigateToApp(firstVisibleNavLinkInFirstVisibleUseCase.id); + } + + setCurrentNavGroup(visibleUseCases[0].id); + } else if (shouldShowHomeLink) { + homeLinkProps.onClick?.(e); + } + }, + [ + homeLinkProps, + shouldShowBackButton, + firstVisibleNavLinkInFirstVisibleUseCase, + navigateToApp, + setCurrentNavGroup, + visibleUseCases, + shouldShowHomeLink, + ] + ); + return ( -
+ - {shouldShowHomeLink ? ( - - - - - - ) : null} - {shouldShowBackButton ? ( + {!shouldShrinkNavigation ? ( - { - if (firstVisibleNavLinkOfAllUseCase) { - navigateToApp(firstVisibleNavLinkOfAllUseCase.id); - } - setCurrentNavGroup(ALL_USE_CASE_ID); - }} - data-test-subj="collapsibleNavBackButton" - > - - {i18n.translate('core.ui.primaryNav.backButtonLabel', { - defaultMessage: 'Back', - })} + + ) : null} @@ -114,7 +141,7 @@ export const CollapsibleNavTop = ({ - -
- {currentNavGroup?.title} -
-
+ {currentNavGroup?.title} )} -
+ ); }; diff --git a/src/plugins/content_management/public/mocks.ts b/src/plugins/content_management/public/mocks.ts index 79581c8e03dd..3568d5503fea 100644 --- a/src/plugins/content_management/public/mocks.ts +++ b/src/plugins/content_management/public/mocks.ts @@ -5,7 +5,7 @@ import { ContentManagementPluginSetup, ContentManagementPluginStart } from './types'; -const createStartContract = (): ContentManagementPluginStart => { +const createStartContract = (): jest.Mocked => { return { registerContentProvider: jest.fn(), renderPage: jest.fn(), @@ -13,7 +13,7 @@ const createStartContract = (): ContentManagementPluginStart => { }; }; -const createSetupContract = (): ContentManagementPluginSetup => { +const createSetupContract = (): jest.Mocked => { return { registerPage: jest.fn(), }; diff --git a/src/plugins/workspace/public/components/home_get_start_card/setup_get_start_card.scss b/src/plugins/workspace/public/components/home_get_start_card/setup_get_start_card.scss new file mode 100644 index 000000000000..29bfd1c4b480 --- /dev/null +++ b/src/plugins/workspace/public/components/home_get_start_card/setup_get_start_card.scss @@ -0,0 +1,3 @@ +.homeGettingStartedWorkspaceCardsIcon { + color: $euiColorMediumShade; +} diff --git a/src/plugins/workspace/public/components/home_get_start_card/setup_get_start_card.tsx b/src/plugins/workspace/public/components/home_get_start_card/setup_get_start_card.tsx index 76cda13a39a7..917338f05cef 100644 --- a/src/plugins/workspace/public/components/home_get_start_card/setup_get_start_card.tsx +++ b/src/plugins/workspace/public/components/home_get_start_card/setup_get_start_card.tsx @@ -14,6 +14,7 @@ import { import { WorkspaceUseCase } from '../../types'; import { getFirstUseCaseOfFeatureConfigs, getUseCaseUrl } from '../../utils'; import { UseCaseCardTitle } from './use_case_card_title'; +import './setup_get_start_card.scss'; const createContentCard = (useCase: WorkspaceUseCase, core: CoreStart) => { const { workspaces, application, http } = core; @@ -66,7 +67,12 @@ export const registerGetStartedCardToNewHome = ( order: (index + 1) * 1000, description: useCase.description, ...content, - getIcon: () => React.createElement(EuiIcon, { size: 'xl', type: 'logoOpenSearch' }), + getIcon: () => + React.createElement(EuiIcon, { + size: 'xl', + type: useCase.icon || 'logoOpenSearch', + className: 'homeGettingStartedWorkspaceCardsIcon', + }), cardProps: { layout: 'horizontal', }, diff --git a/src/plugins/workspace/public/components/use_case_overview/setup_overview.scss b/src/plugins/workspace/public/components/use_case_overview/setup_overview.scss new file mode 100644 index 000000000000..607b5eef6863 --- /dev/null +++ b/src/plugins/workspace/public/components/use_case_overview/setup_overview.scss @@ -0,0 +1,3 @@ +.analyticsGettingStartedWorkspaceCardsIcon { + color: $euiColorMediumShade; +} diff --git a/src/plugins/workspace/public/components/use_case_overview/setup_overview.tsx b/src/plugins/workspace/public/components/use_case_overview/setup_overview.tsx index 4b801bf0880d..3d4436895b9c 100644 --- a/src/plugins/workspace/public/components/use_case_overview/setup_overview.tsx +++ b/src/plugins/workspace/public/components/use_case_overview/setup_overview.tsx @@ -19,6 +19,7 @@ import { import { getStartedCards } from './get_started_cards'; import { DEFAULT_NAV_GROUPS } from '../../../../../core/public'; import { Content } from '../../../../../plugins/content_management/public'; +import './setup_overview.scss'; const recentlyViewSectionRender = (contents: Content[]) => { return ( @@ -133,7 +134,11 @@ export const registerAnalyticsAllOverviewContent = ( id: card.id, kind: 'card', getIcon: () => - React.createElement(EuiIcon, { size: 'xl', type: card.icon || 'wsSelector' }), + React.createElement(EuiIcon, { + size: 'xl', + type: card.icon || 'wsSelector', + className: 'analyticsGettingStartedWorkspaceCardsIcon', + }), order: card.order || index, description: card.description, title: card.title, diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.test.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.test.tsx index 4db2d9365ab9..9ce350af85e2 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.test.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.test.tsx @@ -38,6 +38,7 @@ describe('WorkspaceFormSummaryPanel', () => { title: 'Use Case 1', description: 'This is Use Case 1', features: [], + icon: 'wsAnalytics', }, { id: 'useCase2', diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.tsx index a16cad76ede7..e37bf5097021 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.tsx @@ -15,7 +15,7 @@ import { EuiLink, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; -import { WorkspaceFormDataState } from '../forms'; +import { WorkspaceFormDataState } from '../workspace_form'; import { WorkspaceUseCase } from '../../types'; import { RightSidebarScrollField, RIGHT_SIDEBAR_SCROLL_KEY } from './utils'; @@ -33,7 +33,7 @@ const SCROLL_FIELDS = { } ), [RightSidebarScrollField.Color]: i18n.translate('workspace.form.summary.panel.color.title', { - defaultMessage: 'Accent color', + defaultMessage: 'Workspace icon', }), [RightSidebarScrollField.DataSource]: i18n.translate( 'workspace.form.summary.panel.dataSources.title', @@ -164,9 +164,11 @@ export const WorkspaceFormSummaryPanel = ({ {formData.color && ( - - - + {useCase?.icon && ( + + + + )} {formData.color} 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 index 606052f5c92c..39acffd0c42d 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx @@ -99,7 +99,7 @@ describe('', () => { }); render(); - fireEvent.click(screen.getByTestId('current-workspace-button')); + fireEvent.click(screen.getByTestId('workspace-select-button')); expect(screen.getByTestId('workspace-menu-current-workspace-name')).toBeInTheDocument(); expect(screen.getByTestId('workspace-menu-current-use-case')).toBeInTheDocument(); expect(screen.getByText('Observability')).toBeInTheDocument(); @@ -164,7 +164,7 @@ describe('', () => { const navigateToWorkspaceDetail = jest.spyOn(workspaceUtils, 'navigateToWorkspaceDetail'); render(); - fireEvent.click(screen.getByTestId('current-workspace-button')); + fireEvent.click(screen.getByTestId('workspace-select-button')); const button = screen.getByText(/Manage workspace/i); fireEvent.click(button); expect(navigateToWorkspaceDetail).toBeCalled(); 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 d2ce42ed6097..d70b6c4419a0 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx @@ -9,14 +9,14 @@ import { useObservable } from 'react-use'; import { EuiText, EuiPanel, - EuiAvatar, EuiPopover, EuiToolTip, EuiFlexItem, EuiFlexGroup, - EuiSmallButtonIcon, EuiSmallButtonEmpty, EuiSmallButton, + EuiButtonIcon, + EuiIcon, } from '@elastic/eui'; import { BehaviorSubject } from 'rxjs'; import { WORKSPACE_CREATE_APP_ID, WORKSPACE_LIST_APP_ID } from '../../../common/constants'; @@ -79,23 +79,9 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { setPopover(false); }; - const currentWorkspaceButton = currentWorkspace ? ( - - - - ) : ( - { {currentWorkspace ? ( <> - @@ -159,7 +143,7 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { ) : ( <> - + {currentWorkspaceName} @@ -179,7 +163,7 @@ export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { )} - + } label={workspace.name} diff --git a/src/plugins/workspace/public/plugin.test.ts b/src/plugins/workspace/public/plugin.test.ts index cf0f67f89829..0bbf636f6397 100644 --- a/src/plugins/workspace/public/plugin.test.ts +++ b/src/plugins/workspace/public/plugin.test.ts @@ -19,16 +19,18 @@ import { savedObjectsManagementPluginMock } from '../../saved_objects_management import { managementPluginMock } from '../../management/public/mocks'; import { UseCaseService } from './services/use_case_service'; import { workspaceClientMock, WorkspaceClientMock } from './workspace_client.mock'; -import { WorkspacePlugin, WorkspacePluginStartDeps } from './plugin'; +import { WorkspacePlugin } from './plugin'; import { contentManagementPluginMocks } from '../../content_management/public'; +import { navigationPluginMock } from '../../navigation/public/mocks'; // Expect 6 app registrations: create, fatal error, detail, initial, navigation, and list apps. const registrationAppNumber = 6; describe('Workspace plugin', () => { - const mockDependencies: WorkspacePluginStartDeps = { + const getMockDependencies = () => ({ contentManagement: contentManagementPluginMocks.createStartContract(), - }; + navigation: navigationPluginMock.createStartContract(), + }); const getSetupMock = () => coreMock.createSetup(); beforeEach(() => { @@ -54,7 +56,7 @@ describe('Workspace plugin', () => { const setupMock = getSetupMock(); const coreStart = coreMock.createStart(); await workspacePlugin.setup(setupMock, {}); - workspacePlugin.start(coreStart, mockDependencies); + workspacePlugin.start(coreStart, getMockDependencies()); coreStart.workspaces.currentWorkspaceId$.next('foo'); expect(coreStart.savedObjects.client.setCurrentWorkspace).toHaveBeenCalledWith('foo'); expect(setupMock.application.register).toBeCalledTimes(registrationAppNumber); @@ -230,7 +232,7 @@ describe('Workspace plugin', () => { await workspacePlugin.setup(setupMock, {}); expect(collapsibleNavHeaderImplementation()).toEqual(null); const startMock = coreMock.createStart(); - await workspacePlugin.start(startMock, mockDependencies); + await workspacePlugin.start(startMock, getMockDependencies()); expect(collapsibleNavHeaderImplementation()).not.toEqual(null); }); @@ -330,7 +332,7 @@ describe('Workspace plugin', () => { const breadcrumbs = new BehaviorSubject([{ text: 'dashboards' }]); startMock.chrome.getBreadcrumbs$.mockReturnValue(breadcrumbs); const workspacePlugin = new WorkspacePlugin(); - workspacePlugin.start(startMock, mockDependencies); + workspacePlugin.start(startMock, getMockDependencies()); expect(startMock.chrome.setBreadcrumbs).toBeCalledWith( expect.arrayContaining([ expect.objectContaining({ @@ -356,7 +358,7 @@ describe('Workspace plugin', () => { ]); startMock.chrome.getBreadcrumbs$.mockReturnValue(breadcrumbs); const workspacePlugin = new WorkspacePlugin(); - workspacePlugin.start(startMock, mockDependencies); + workspacePlugin.start(startMock, getMockDependencies()); expect(startMock.chrome.setBreadcrumbs).not.toHaveBeenCalled(); }); @@ -364,6 +366,7 @@ describe('Workspace plugin', () => { const startMock = coreMock.createStart(); startMock.chrome.navGroup.getNavGroupEnabled.mockReturnValue(true); const workspacePlugin = new WorkspacePlugin(); + const mockDependencies = getMockDependencies(); workspacePlugin.start(startMock, mockDependencies); expect(mockDependencies.contentManagement.registerContentProvider).toHaveBeenCalledWith( expect.objectContaining({ @@ -385,9 +388,13 @@ describe('Workspace plugin', () => { jest.spyOn(navGroupUpdater$, 'next'); expect(navGroupUpdater$.next).not.toHaveBeenCalled(); - workspacePlugin.start(coreStart, mockDependencies); + workspacePlugin.start(coreStart, getMockDependencies()); + coreStart.workspaces.currentWorkspace$.next({ + id: 'foo', + name: 'foo', + }); - waitFor(() => { + await waitFor(() => { expect(navGroupUpdater$.next).toHaveBeenCalled(); }); }); @@ -396,7 +403,7 @@ describe('Workspace plugin', () => { const coreStart = coreMock.createStart(); coreStart.chrome.navGroup.getNavGroupEnabled.mockReturnValue(true); const workspacePlugin = new WorkspacePlugin(); - workspacePlugin.start(coreStart, mockDependencies); + workspacePlugin.start(coreStart, getMockDependencies()); expect(coreStart.chrome.navControls.registerLeftBottom).toBeCalledTimes(1); }); @@ -427,7 +434,7 @@ describe('Workspace plugin', () => { const appUpdater$ = setupMock.application.registerAppUpdater.mock.calls[0][0]; - workspacePlugin.start(coreStart, mockDependencies); + workspacePlugin.start(coreStart, getMockDependencies()); const appUpdater = await appUpdater$.pipe(first()).toPromise(); @@ -448,7 +455,7 @@ describe('Workspace plugin', () => { const navGroupUpdater$ = setupMock.chrome.navGroup.registerNavGroupUpdater.mock.calls[0][0]; - workspacePlugin.start(coreStart, mockDependencies); + workspacePlugin.start(coreStart, getMockDependencies()); const navGroupUpdater = await navGroupUpdater$.pipe(first()).toPromise(); @@ -458,6 +465,49 @@ describe('Workspace plugin', () => { }); }); + it('#start should only register get started cards of use cases to new home page', async () => { + const workspacePlugin = new WorkspacePlugin(); + const setupMock = getSetupMock(); + const coreStart = coreMock.createStart(); + await workspacePlugin.setup(setupMock, {}); + const registeredUseCases$ = new BehaviorSubject([ + { + id: 'foo', + title: 'Foo', + systematic: true, + description: '', + features: [], + }, + { + id: 'bar', + title: 'Bar', + description: '', + features: [], + }, + ]); + jest.spyOn(UseCaseService.prototype, 'start').mockImplementationOnce(() => ({ + getRegisteredUseCases$: jest.fn(() => registeredUseCases$), + })); + + coreStart.chrome.navGroup.getNavGroupEnabled.mockReturnValue(true); + + const mockDependencies = getMockDependencies(); + + workspacePlugin.start(coreStart, mockDependencies); + await waitFor(() => { + expect(mockDependencies.contentManagement.registerContentProvider).toBeCalledWith( + expect.objectContaining({ + id: `home_get_start_bar`, + }) + ); + expect(mockDependencies.contentManagement.registerContentProvider).not.toBeCalledWith( + expect.objectContaining({ + id: `home_get_start_foo`, + }) + ); + }); + }); + it('#stop should call unregisterNavGroupUpdater', async () => { const workspacePlugin = new WorkspacePlugin(); const setupMock = getSetupMock(); @@ -477,7 +527,11 @@ describe('Workspace plugin', () => { { id: 'foo', title: 'Foo', - features: ['system-feature'], + features: [ + { + id: 'system-feature', + }, + ], systematic: true, description: '', }, @@ -500,7 +554,7 @@ describe('Workspace plugin', () => { const appUpdaterChangeMock = jest.fn(); appUpdater$.subscribe(appUpdaterChangeMock); - workspacePlugin.start(coreStart, mockDependencies); + workspacePlugin.start(coreStart, getMockDependencies()); // Wait for filterNav been executed await new Promise(setImmediate); diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index 7ccf43a251ee..d4026560aae5 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -539,6 +539,7 @@ export class WorkspacePlugin return React.createElement(EuiPanel, { hasShadow: false, hasBorder: false, + paddingSize: 's', children: [ React.createElement(WorkspacePickerContent, { key: 'workspacePickerContent', diff --git a/src/plugins/workspace/public/types.ts b/src/plugins/workspace/public/types.ts index bec84221e2f1..53f2be4dfb36 100644 --- a/src/plugins/workspace/public/types.ts +++ b/src/plugins/workspace/public/types.ts @@ -29,6 +29,7 @@ export interface WorkspaceUseCase { features: WorkspaceUseCaseFeature[]; systematic?: boolean; order?: number; + icon?: string; } export interface DataSourceAttributesWithWorkspaces extends Omit { diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts index f58273822282..5f6b28ee59e1 100644 --- a/src/plugins/workspace/public/utils.test.ts +++ b/src/plugins/workspace/public/utils.test.ts @@ -466,6 +466,7 @@ describe('workspace utils: convertNavGroupToWorkspaceUseCase', () => { title: 'Foo', description: 'Foo description', navLinks: [{ id: 'bar', title: 'Bar' }], + icon: 'wsAnalytics', }) ).toEqual({ id: 'foo', @@ -473,6 +474,7 @@ describe('workspace utils: convertNavGroupToWorkspaceUseCase', () => { description: 'Foo description', features: [{ id: 'bar', title: 'Bar' }], systematic: false, + icon: 'wsAnalytics', }); expect( diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index 559e81c9408b..1ed0bab42cda 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -261,6 +261,7 @@ export const convertNavGroupToWorkspaceUseCase = ({ navLinks, type, order, + icon, }: NavGroupItemInMap): WorkspaceUseCase => ({ id, title, @@ -268,6 +269,7 @@ export const convertNavGroupToWorkspaceUseCase = ({ features: navLinks.map((item) => ({ id: item.id, title: item.title })), systematic: type === NavGroupType.SYSTEM || id === ALL_USE_CASE_ID, order, + icon, }); const compareFeatures = (