Skip to content

Commit 1974ca1

Browse files
SuZhou-Joeopensearch-changeset-bot[bot]wanglam
authored
[navigation] Add workspace icon to left nav / workspace picker menu / home page. (opensearch-project#7823)
* feat: show workspace picker content in left nav Signed-off-by: SuZhou-Joe <[email protected]> * fix: bootstrap error Signed-off-by: SuZhou-Joe <[email protected]> * fix: unit test error Signed-off-by: SuZhou-Joe <[email protected]> * feat: finish picker content Signed-off-by: SuZhou-Joe <[email protected]> * feat: finish picker content Signed-off-by: SuZhou-Joe <[email protected]> * feat: only register index patterns to settings and setup when workspace is disabled Signed-off-by: SuZhou-Joe <[email protected]> * fix: unit test Signed-off-by: SuZhou-Joe <[email protected]> * feat: put discover 2.0 behind discover Signed-off-by: SuZhou-Joe <[email protected]> * feat: add coverage Signed-off-by: SuZhou-Joe <[email protected]> * feat: improve test coverage Signed-off-by: SuZhou-Joe <[email protected]> * feat: merge conflict Signed-off-by: SuZhou-Joe <[email protected]> * feat: optimize code based on comment Signed-off-by: SuZhou-Joe <[email protected]> * feat: optimize code based on comment Signed-off-by: SuZhou-Joe <[email protected]> * feat: optimize filter code Signed-off-by: SuZhou-Joe <[email protected]> * feat: update Signed-off-by: SuZhou-Joe <[email protected]> * feat: add new icon to left navigation and workspace picker menu Signed-off-by: SuZhou-Joe <[email protected]> * feat: change use case card in home Signed-off-by: SuZhou-Joe <[email protected]> * feat: optimize alignment Signed-off-by: SuZhou-Joe <[email protected]> * Changeset file for PR opensearch-project#7823 created/updated * feat: alignment optimize Signed-off-by: SuZhou-Joe <[email protected]> * feat: use new icons in workspace picker Signed-off-by: SuZhou-Joe <[email protected]> * feat: optimize color Signed-off-by: SuZhou-Joe <[email protected]> * fix: unit test error Signed-off-by: SuZhou-Joe <[email protected]> * fix: unit test Signed-off-by: SuZhou-Joe <[email protected]> * fix: unit test Signed-off-by: SuZhou-Joe <[email protected]> * feat: increase test coverage Signed-off-by: SuZhou-Joe <[email protected]> * feat: remove useless code Signed-off-by: SuZhou-Joe <[email protected]> * Add workspace icon in workspace creator (#19) Signed-off-by: Lin Wang <[email protected]> * fix: fatal error when visibleUseCases is empty Signed-off-by: SuZhou-Joe <[email protected]> --------- Signed-off-by: SuZhou-Joe <[email protected]> Signed-off-by: Lin Wang <[email protected]> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Co-authored-by: Lin Wang <[email protected]>
1 parent 95929a6 commit 1974ca1

20 files changed

+234
-115
lines changed

changelogs/fragments/7823.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
feat:
2+
- Add workspace icon to left nav / workspace picker menu / home page. ([#7823](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7823))

src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,6 @@ export function CollapsibleNavGroupEnabled({
9191
const appId = useObservable(observables.appId$, '');
9292
const navGroupsMap = useObservable(observables.navGroupsMap$, {});
9393
const currentNavGroup = useObservable(observables.currentNavGroup$, undefined);
94-
const firstVisibleNavLinkOfAllUseCase = useMemo(
95-
() =>
96-
fulfillRegistrationLinksToChromeNavLinks(
97-
navGroupsMap[ALL_USE_CASE_ID]?.navLinks || [],
98-
navLinks
99-
)[0],
100-
[navGroupsMap, navLinks]
101-
);
10294

10395
const visibleUseCases = useMemo(() => getVisibleUseCases(navGroupsMap), [navGroupsMap]);
10496

@@ -303,7 +295,8 @@ export function CollapsibleNavGroupEnabled({
303295
>
304296
<CollapsibleNavTop
305297
homeLink={homeLink}
306-
firstVisibleNavLinkOfAllUseCase={firstVisibleNavLinkOfAllUseCase}
298+
navGroupsMap={navGroupsMap}
299+
navLinks={navLinks}
307300
navigateToApp={navigateToApp}
308301
logos={logos}
309302
setCurrentNavGroup={setCurrentNavGroup}
@@ -326,7 +319,7 @@ export function CollapsibleNavGroupEnabled({
326319
{shouldShowCollapsedNavHeaderContent && collapsibleNavHeaderRender ? (
327320
<>
328321
{collapsibleNavHeaderRender()}
329-
<EuiSpacer size="l" />
322+
<EuiSpacer />
330323
</>
331324
) : null}
332325
<NavGroups
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.leftNavTopIcon {
2+
color: $euiColorMediumShade;
3+
}

src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.test.tsx

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import { ChromeNavLink } from '../../nav_links';
99
import { ChromeRegistrationNavLink } from '../../nav_group';
1010
import { httpServiceMock } from '../../../mocks';
1111
import { getLogos } from '../../../../common';
12-
import { CollapsibleNavTop } from './collapsible_nav_group_enabled_top';
12+
import { CollapsibleNavTop, CollapsibleNavTopProps } from './collapsible_nav_group_enabled_top';
1313
import { BehaviorSubject } from 'rxjs';
1414
import { WorkspaceObject } from 'src/core/public/workspace';
15-
import { ALL_USE_CASE_ID } from '../../../';
15+
import { ALL_USE_CASE_ID, DEFAULT_NAV_GROUPS } from '../../../';
1616

1717
const mockBasePath = httpServiceMock.createSetupContract({ basePath: '/test' }).basePath;
1818

@@ -33,44 +33,66 @@ describe('<CollapsibleNavTop />', () => {
3333
logos: getLogos({}, mockBasePath.serverBasePath),
3434
shouldShrinkNavigation: false,
3535
visibleUseCases: [],
36+
navGroupsMap: {},
37+
navLinks: [],
3638
currentWorkspace$: new BehaviorSubject<WorkspaceObject | null>(null),
3739
setCurrentNavGroup: jest.fn(),
3840
};
3941
};
4042

4143
it('should render back icon when inside a workspace of all use case', async () => {
42-
const props = {
44+
const props: CollapsibleNavTopProps = {
4345
...getMockedProps(),
4446
currentWorkspace$: new BehaviorSubject<WorkspaceObject | null>({ id: 'foo', name: 'foo' }),
4547
visibleUseCases: [
4648
{
47-
id: ALL_USE_CASE_ID,
49+
...DEFAULT_NAV_GROUPS.all,
4850
title: 'navGroupFoo',
4951
description: 'navGroupFoo',
50-
navLinks: [],
52+
navLinks: [
53+
{
54+
id: 'firstVisibleNavLinkOfAllUseCase',
55+
},
56+
],
5157
},
5258
],
59+
navGroupsMap: {
60+
[DEFAULT_NAV_GROUPS.all.id]: {
61+
...DEFAULT_NAV_GROUPS.all,
62+
title: 'navGroupFoo',
63+
description: 'navGroupFoo',
64+
navLinks: [
65+
{
66+
id: 'firstVisibleNavLinkOfAllUseCase',
67+
},
68+
],
69+
},
70+
},
71+
navLinks: [
72+
getMockedNavLink({
73+
id: 'firstVisibleNavLinkOfAllUseCase',
74+
}),
75+
],
5376
currentNavGroup: {
5477
id: 'navGroupFoo',
5578
title: 'navGroupFoo',
5679
description: 'navGroupFoo',
5780
navLinks: [],
5881
},
59-
firstVisibleNavLinkOfAllUseCase: getMockedNavLink({
60-
id: 'firstVisibleNavLinkOfAllUseCase',
61-
}),
6282
};
63-
const { findByTestId, findByText, getByTestId } = render(<CollapsibleNavTop {...props} />);
64-
await findByTestId('collapsibleNavBackButton');
65-
await findByText('Back');
66-
fireEvent.click(getByTestId('collapsibleNavBackButton'));
83+
const { findByTestId, getByTestId } = render(<CollapsibleNavTop {...props} />);
84+
await findByTestId(`collapsibleNavIcon-${DEFAULT_NAV_GROUPS.all.icon}`);
85+
fireEvent.click(getByTestId(`collapsibleNavIcon-${DEFAULT_NAV_GROUPS.all.icon}`));
6786
expect(props.navigateToApp).toBeCalledWith('firstVisibleNavLinkOfAllUseCase');
6887
expect(props.setCurrentNavGroup).toBeCalledWith(ALL_USE_CASE_ID);
6988
});
7089

7190
it('should render home icon when not in a workspace', async () => {
72-
const { findByTestId } = render(<CollapsibleNavTop {...getMockedProps()} />);
91+
const props = getMockedProps();
92+
const { findByTestId, getByTestId } = render(<CollapsibleNavTop {...props} />);
7393
await findByTestId('collapsibleNavHome');
94+
fireEvent.click(getByTestId('collapsibleNavHome'));
95+
expect(props.navigateToApp).toBeCalledWith('home');
7496
});
7597

7698
it('should render expand icon when collapsed', async () => {
@@ -79,4 +101,18 @@ describe('<CollapsibleNavTop />', () => {
79101
);
80102
await findByTestId('collapsibleNavShrinkButton');
81103
});
104+
105+
it('should render successfully without error when visibleUseCases is empty but inside a workspace', async () => {
106+
expect(() =>
107+
render(
108+
<CollapsibleNavTop
109+
{...getMockedProps()}
110+
currentWorkspace$={
111+
new BehaviorSubject<WorkspaceObject | null>({ id: 'foo', name: 'bar' })
112+
}
113+
shouldShrinkNavigation
114+
/>
115+
)
116+
).not.toThrow();
117+
});
82118
});

src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import React, { useMemo } from 'react';
6+
import React, { useCallback, useMemo } from 'react';
77
import useObservable from 'react-use/lib/useObservable';
88
import { Logos, WorkspacesStart } from 'opensearch-dashboards/public';
99
import {
@@ -12,19 +12,21 @@ import {
1212
EuiFlexGroup,
1313
EuiFlexItem,
1414
EuiIcon,
15+
EuiPanel,
1516
EuiSpacer,
1617
EuiText,
1718
} from '@elastic/eui';
1819
import { InternalApplicationStart } from 'src/core/public/application';
19-
import { i18n } from '@osd/i18n';
2020
import { createEuiListItem } from './nav_link';
2121
import { ChromeNavGroupServiceStartContract, NavGroupItemInMap } from '../../nav_group';
2222
import { ChromeNavLink } from '../../nav_links';
2323
import { ALL_USE_CASE_ID } from '../../../../../core/utils';
24+
import { fulfillRegistrationLinksToChromeNavLinks } from '../../utils';
25+
import './collapsible_nav_group_enabled_top.scss';
2426

2527
export interface CollapsibleNavTopProps {
2628
homeLink?: ChromeNavLink;
27-
firstVisibleNavLinkOfAllUseCase?: ChromeNavLink;
29+
navGroupsMap: Record<string, NavGroupItemInMap>;
2830
currentNavGroup?: NavGroupItemInMap;
2931
navigateToApp: InternalApplicationStart['navigateToApp'];
3032
logos: Logos;
@@ -33,6 +35,7 @@ export interface CollapsibleNavTopProps {
3335
visibleUseCases: NavGroupItemInMap[];
3436
currentWorkspace$: WorkspacesStart['currentWorkspace$'];
3537
setCurrentNavGroup: ChromeNavGroupServiceStartContract['setCurrentNavGroup'];
38+
navLinks: ChromeNavLink[];
3639
}
3740

3841
export const CollapsibleNavTop = ({
@@ -45,10 +48,20 @@ export const CollapsibleNavTop = ({
4548
currentWorkspace$,
4649
setCurrentNavGroup,
4750
homeLink,
48-
firstVisibleNavLinkOfAllUseCase,
51+
navGroupsMap,
52+
navLinks,
4953
}: CollapsibleNavTopProps) => {
5054
const currentWorkspace = useObservable(currentWorkspace$);
5155

56+
const firstVisibleNavLinkInFirstVisibleUseCase = useMemo(
57+
() =>
58+
fulfillRegistrationLinksToChromeNavLinks(
59+
navGroupsMap[visibleUseCases[0]?.id]?.navLinks || [],
60+
navLinks
61+
)[0],
62+
[navGroupsMap, navLinks, visibleUseCases]
63+
);
64+
5265
/**
5366
* We can ensure that left nav is inside second level once all the following conditions are met:
5467
* 1. Inside a workspace
@@ -57,9 +70,15 @@ export const CollapsibleNavTop = ({
5770
*/
5871
const isInsideSecondLevelOfAllWorkspace =
5972
!!currentWorkspace &&
60-
visibleUseCases[0].id === ALL_USE_CASE_ID &&
73+
visibleUseCases[0]?.id === ALL_USE_CASE_ID &&
6174
currentNavGroup?.id !== ALL_USE_CASE_ID;
6275

76+
const homeIcon = logos.Mark.url;
77+
const icon =
78+
!!currentWorkspace && visibleUseCases.length === 1
79+
? visibleUseCases[0].icon || homeIcon
80+
: homeIcon;
81+
6382
const shouldShowBackButton = !shouldShrinkNavigation && isInsideSecondLevelOfAllWorkspace;
6483
const shouldShowHomeLink = !shouldShrinkNavigation && !shouldShowBackButton;
6584

@@ -74,47 +93,55 @@ export const CollapsibleNavTop = ({
7493
return {
7594
'data-test-subj': propsForHomeIcon['data-test-subj'],
7695
onClick: propsForHomeIcon.onClick,
77-
href: propsForHomeIcon.href,
7896
};
7997
}
8098

8199
return {};
82100
}, [homeLink, navigateToApp]);
83101

102+
const onIconClick = useCallback(
103+
(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
104+
if (shouldShowBackButton || visibleUseCases.length === 1) {
105+
if (firstVisibleNavLinkInFirstVisibleUseCase) {
106+
navigateToApp(firstVisibleNavLinkInFirstVisibleUseCase.id);
107+
}
108+
109+
setCurrentNavGroup(visibleUseCases[0].id);
110+
} else if (shouldShowHomeLink) {
111+
homeLinkProps.onClick?.(e);
112+
}
113+
},
114+
[
115+
homeLinkProps,
116+
shouldShowBackButton,
117+
firstVisibleNavLinkInFirstVisibleUseCase,
118+
navigateToApp,
119+
setCurrentNavGroup,
120+
visibleUseCases,
121+
shouldShowHomeLink,
122+
]
123+
);
124+
84125
return (
85-
<div>
126+
<EuiPanel hasBorder={false} hasShadow={false}>
86127
<EuiFlexGroup responsive={false} alignItems="center" justifyContent="spaceBetween">
87-
{shouldShowHomeLink ? (
88-
<EuiFlexItem grow={false}>
89-
<EuiButtonEmpty size="l" {...homeLinkProps}>
90-
<EuiIcon type={logos.Mark.url} size="l" />
91-
</EuiButtonEmpty>
92-
</EuiFlexItem>
93-
) : null}
94-
{shouldShowBackButton ? (
128+
{!shouldShrinkNavigation ? (
95129
<EuiFlexItem grow={false}>
96-
<EuiButtonEmpty
97-
size="l"
98-
onClick={() => {
99-
if (firstVisibleNavLinkOfAllUseCase) {
100-
navigateToApp(firstVisibleNavLinkOfAllUseCase.id);
101-
}
102-
setCurrentNavGroup(ALL_USE_CASE_ID);
103-
}}
104-
data-test-subj="collapsibleNavBackButton"
105-
>
106-
<EuiIcon type="arrowLeft" />
107-
{i18n.translate('core.ui.primaryNav.backButtonLabel', {
108-
defaultMessage: 'Back',
109-
})}
130+
<EuiButtonEmpty flush="both" {...homeLinkProps} onClick={onIconClick}>
131+
<EuiIcon
132+
type={icon}
133+
size="l"
134+
className="leftNavTopIcon"
135+
data-test-subj={`collapsibleNavIcon-${icon}`}
136+
/>
110137
</EuiButtonEmpty>
111138
</EuiFlexItem>
112139
) : null}
113140
<EuiFlexItem grow={false}>
114141
<EuiButtonIcon
115142
onClick={onClickShrink}
116143
iconType={shouldShrinkNavigation ? 'menu' : 'menuLeft'}
117-
color="text"
144+
color="subdued"
118145
display={shouldShrinkNavigation ? 'empty' : 'base'}
119146
aria-label="shrink-button"
120147
data-test-subj="collapsibleNavShrinkButton"
@@ -124,13 +151,9 @@ export const CollapsibleNavTop = ({
124151
{currentNavGroup?.title && (
125152
<>
126153
<EuiSpacer />
127-
<EuiText>
128-
<div className="nav-link-item" style={{ fontWeight: 'normal' }}>
129-
{currentNavGroup?.title}
130-
</div>
131-
</EuiText>
154+
<EuiText>{currentNavGroup?.title}</EuiText>
132155
</>
133156
)}
134-
</div>
157+
</EuiPanel>
135158
);
136159
};

src/plugins/content_management/public/mocks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55

66
import { ContentManagementPluginSetup, ContentManagementPluginStart } from './types';
77

8-
const createStartContract = (): ContentManagementPluginStart => {
8+
const createStartContract = (): jest.Mocked<ContentManagementPluginStart> => {
99
return {
1010
registerContentProvider: jest.fn(),
1111
renderPage: jest.fn(),
1212
updatePageSection: jest.fn(),
1313
};
1414
};
1515

16-
const createSetupContract = (): ContentManagementPluginSetup => {
16+
const createSetupContract = (): jest.Mocked<ContentManagementPluginSetup> => {
1717
return {
1818
registerPage: jest.fn(),
1919
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.homeGettingStartedWorkspaceCardsIcon {
2+
color: $euiColorMediumShade;
3+
}

src/plugins/workspace/public/components/home_get_start_card/setup_get_start_card.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { WorkspaceUseCase } from '../../types';
1515
import { getFirstUseCaseOfFeatureConfigs, getUseCaseUrl } from '../../utils';
1616
import { UseCaseCardTitle } from './use_case_card_title';
17+
import './setup_get_start_card.scss';
1718

1819
const createContentCard = (useCase: WorkspaceUseCase, core: CoreStart) => {
1920
const { workspaces, application, http } = core;
@@ -66,7 +67,12 @@ export const registerGetStartedCardToNewHome = (
6667
order: (index + 1) * 1000,
6768
description: useCase.description,
6869
...content,
69-
getIcon: () => React.createElement(EuiIcon, { size: 'xl', type: 'logoOpenSearch' }),
70+
getIcon: () =>
71+
React.createElement(EuiIcon, {
72+
size: 'xl',
73+
type: useCase.icon || 'logoOpenSearch',
74+
className: 'homeGettingStartedWorkspaceCardsIcon',
75+
}),
7076
cardProps: {
7177
layout: 'horizontal',
7278
},
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.analyticsGettingStartedWorkspaceCardsIcon {
2+
color: $euiColorMediumShade;
3+
}

src/plugins/workspace/public/components/use_case_overview/setup_overview.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { getStartedCards } from './get_started_cards';
2020
import { DEFAULT_NAV_GROUPS } from '../../../../../core/public';
2121
import { Content } from '../../../../../plugins/content_management/public';
22+
import './setup_overview.scss';
2223

2324
const recentlyViewSectionRender = (contents: Content[]) => {
2425
return (
@@ -133,7 +134,11 @@ export const registerAnalyticsAllOverviewContent = (
133134
id: card.id,
134135
kind: 'card',
135136
getIcon: () =>
136-
React.createElement(EuiIcon, { size: 'xl', type: card.icon || 'wsSelector' }),
137+
React.createElement(EuiIcon, {
138+
size: 'xl',
139+
type: card.icon || 'wsSelector',
140+
className: 'analyticsGettingStartedWorkspaceCardsIcon',
141+
}),
137142
order: card.order || index,
138143
description: card.description,
139144
title: card.title,

0 commit comments

Comments
 (0)