Skip to content

Commit

Permalink
[navigation] Add workspace icon to left nav / workspace picker menu /…
Browse files Browse the repository at this point in the history
… home page. (opensearch-project#7823)

* feat: show workspace picker content in left nav

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: bootstrap error

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: unit test error

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: finish picker content

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: finish picker content

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: only register index patterns to settings and setup when workspace is disabled

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: unit test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: put discover 2.0 behind discover

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add coverage

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: improve test coverage

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: merge conflict

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: optimize code based on comment

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: optimize code based on comment

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: optimize filter code

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: update

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add new icon to left navigation and workspace picker menu

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: change use case card in home

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: optimize alignment

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* Changeset file for PR opensearch-project#7823 created/updated

* feat: alignment optimize

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: use new icons in workspace picker

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: optimize color

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: unit test error

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: unit test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* fix: unit test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: increase test coverage

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: remove useless code

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* Add workspace icon in workspace creator (#19)

Signed-off-by: Lin Wang <wonglam@amazon.com>

* fix: fatal error when visibleUseCases is empty

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

---------

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
Signed-off-by: Lin Wang <wonglam@amazon.com>
Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
Co-authored-by: Lin Wang <wonglam@amazon.com>
  • Loading branch information
3 people authored Sep 4, 2024
1 parent 95929a6 commit 1974ca1
Show file tree
Hide file tree
Showing 20 changed files with 234 additions and 115 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/7823.yml
Original file line number Diff line number Diff line change
@@ -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))
13 changes: 3 additions & 10 deletions src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down Expand Up @@ -303,7 +295,8 @@ export function CollapsibleNavGroupEnabled({
>
<CollapsibleNavTop
homeLink={homeLink}
firstVisibleNavLinkOfAllUseCase={firstVisibleNavLinkOfAllUseCase}
navGroupsMap={navGroupsMap}
navLinks={navLinks}
navigateToApp={navigateToApp}
logos={logos}
setCurrentNavGroup={setCurrentNavGroup}
Expand All @@ -326,7 +319,7 @@ export function CollapsibleNavGroupEnabled({
{shouldShowCollapsedNavHeaderContent && collapsibleNavHeaderRender ? (
<>
{collapsibleNavHeaderRender()}
<EuiSpacer size="l" />
<EuiSpacer />
</>
) : null}
<NavGroups
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.leftNavTopIcon {
color: $euiColorMediumShade;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { ChromeNavLink } from '../../nav_links';
import { ChromeRegistrationNavLink } from '../../nav_group';
import { httpServiceMock } from '../../../mocks';
import { getLogos } from '../../../../common';
import { CollapsibleNavTop } from './collapsible_nav_group_enabled_top';
import { CollapsibleNavTop, CollapsibleNavTopProps } from './collapsible_nav_group_enabled_top';
import { BehaviorSubject } from 'rxjs';
import { WorkspaceObject } from 'src/core/public/workspace';
import { ALL_USE_CASE_ID } from '../../../';
import { ALL_USE_CASE_ID, DEFAULT_NAV_GROUPS } from '../../../';

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

Expand All @@ -33,44 +33,66 @@ describe('<CollapsibleNavTop />', () => {
logos: getLogos({}, mockBasePath.serverBasePath),
shouldShrinkNavigation: false,
visibleUseCases: [],
navGroupsMap: {},
navLinks: [],
currentWorkspace$: new BehaviorSubject<WorkspaceObject | null>(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<WorkspaceObject | null>({ 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(<CollapsibleNavTop {...props} />);
await findByTestId('collapsibleNavBackButton');
await findByText('Back');
fireEvent.click(getByTestId('collapsibleNavBackButton'));
const { findByTestId, getByTestId } = render(<CollapsibleNavTop {...props} />);
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(<CollapsibleNavTop {...getMockedProps()} />);
const props = getMockedProps();
const { findByTestId, getByTestId } = render(<CollapsibleNavTop {...props} />);
await findByTestId('collapsibleNavHome');
fireEvent.click(getByTestId('collapsibleNavHome'));
expect(props.navigateToApp).toBeCalledWith('home');
});

it('should render expand icon when collapsed', async () => {
Expand All @@ -79,4 +101,18 @@ describe('<CollapsibleNavTop />', () => {
);
await findByTestId('collapsibleNavShrinkButton');
});

it('should render successfully without error when visibleUseCases is empty but inside a workspace', async () => {
expect(() =>
render(
<CollapsibleNavTop
{...getMockedProps()}
currentWorkspace$={
new BehaviorSubject<WorkspaceObject | null>({ id: 'foo', name: 'bar' })
}
shouldShrinkNavigation
/>
)
).not.toThrow();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<string, NavGroupItemInMap>;
currentNavGroup?: NavGroupItemInMap;
navigateToApp: InternalApplicationStart['navigateToApp'];
logos: Logos;
Expand All @@ -33,6 +35,7 @@ export interface CollapsibleNavTopProps {
visibleUseCases: NavGroupItemInMap[];
currentWorkspace$: WorkspacesStart['currentWorkspace$'];
setCurrentNavGroup: ChromeNavGroupServiceStartContract['setCurrentNavGroup'];
navLinks: ChromeNavLink[];
}

export const CollapsibleNavTop = ({
Expand All @@ -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
Expand All @@ -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;

Expand All @@ -74,47 +93,55 @@ 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<HTMLButtonElement, 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 (
<div>
<EuiPanel hasBorder={false} hasShadow={false}>
<EuiFlexGroup responsive={false} alignItems="center" justifyContent="spaceBetween">
{shouldShowHomeLink ? (
<EuiFlexItem grow={false}>
<EuiButtonEmpty size="l" {...homeLinkProps}>
<EuiIcon type={logos.Mark.url} size="l" />
</EuiButtonEmpty>
</EuiFlexItem>
) : null}
{shouldShowBackButton ? (
{!shouldShrinkNavigation ? (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
size="l"
onClick={() => {
if (firstVisibleNavLinkOfAllUseCase) {
navigateToApp(firstVisibleNavLinkOfAllUseCase.id);
}
setCurrentNavGroup(ALL_USE_CASE_ID);
}}
data-test-subj="collapsibleNavBackButton"
>
<EuiIcon type="arrowLeft" />
{i18n.translate('core.ui.primaryNav.backButtonLabel', {
defaultMessage: 'Back',
})}
<EuiButtonEmpty flush="both" {...homeLinkProps} onClick={onIconClick}>
<EuiIcon
type={icon}
size="l"
className="leftNavTopIcon"
data-test-subj={`collapsibleNavIcon-${icon}`}
/>
</EuiButtonEmpty>
</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<EuiButtonIcon
onClick={onClickShrink}
iconType={shouldShrinkNavigation ? 'menu' : 'menuLeft'}
color="text"
color="subdued"
display={shouldShrinkNavigation ? 'empty' : 'base'}
aria-label="shrink-button"
data-test-subj="collapsibleNavShrinkButton"
Expand All @@ -124,13 +151,9 @@ export const CollapsibleNavTop = ({
{currentNavGroup?.title && (
<>
<EuiSpacer />
<EuiText>
<div className="nav-link-item" style={{ fontWeight: 'normal' }}>
{currentNavGroup?.title}
</div>
</EuiText>
<EuiText>{currentNavGroup?.title}</EuiText>
</>
)}
</div>
</EuiPanel>
);
};
4 changes: 2 additions & 2 deletions src/plugins/content_management/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

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

const createStartContract = (): ContentManagementPluginStart => {
const createStartContract = (): jest.Mocked<ContentManagementPluginStart> => {
return {
registerContentProvider: jest.fn(),
renderPage: jest.fn(),
updatePageSection: jest.fn(),
};
};

const createSetupContract = (): ContentManagementPluginSetup => {
const createSetupContract = (): jest.Mocked<ContentManagementPluginSetup> => {
return {
registerPage: jest.fn(),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.homeGettingStartedWorkspaceCardsIcon {
color: $euiColorMediumShade;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.analyticsGettingStartedWorkspaceCardsIcon {
color: $euiColorMediumShade;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 1974ca1

Please sign in to comment.