Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 2.16] [Navigation-next] Enrich breadcrumbs by workspace and use case #7393

Merged
merged 1 commit into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/7360.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- Enrich breadcrumbs by workspace and use case ([#7360](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7360))
2 changes: 2 additions & 0 deletions src/core/public/chrome/chrome_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ const createStartContractMock = () => {
setBadge: jest.fn(),
getBreadcrumbs$: jest.fn(),
setBreadcrumbs: jest.fn(),
getBreadcrumbsEnricher$: jest.fn(),
setBreadcrumbsEnricher: jest.fn(),
getHelpExtension$: jest.fn(),
setHelpExtension: jest.fn(),
setHelpSupportUrl: jest.fn(),
Expand Down
30 changes: 29 additions & 1 deletion src/core/public/chrome/chrome_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
/** @public */
export type ChromeBreadcrumb = EuiBreadcrumb;

/** @public */
export type ChromeBreadcrumbEnricher = (breadcrumbs: ChromeBreadcrumb[]) => ChromeBreadcrumb[];

/** @public */
export type ChromeBranding = Branding;

Expand Down Expand Up @@ -191,6 +194,9 @@
const applicationClasses$ = new BehaviorSubject<Set<string>>(new Set());
const helpExtension$ = new BehaviorSubject<ChromeHelpExtension | undefined>(undefined);
const breadcrumbs$ = new BehaviorSubject<ChromeBreadcrumb[]>([]);
const breadcrumbsEnricher$ = new BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(
undefined
);
const badge$ = new BehaviorSubject<ChromeBadge | undefined>(undefined);
const customNavLink$ = new BehaviorSubject<ChromeNavLink | undefined>(undefined);
const helpSupportUrl$ = new BehaviorSubject<string>(OPENSEARCH_DASHBOARDS_ASK_OPENSEARCH_LINK);
Expand All @@ -201,7 +207,12 @@
const navLinks = this.navLinks.start({ application, http });
const recentlyAccessed = await this.recentlyAccessed.start({ http, workspaces });
const docTitle = this.docTitle.start({ document: window.document });
const navGroup = await this.navGroup.start({ navLinks, application });
const navGroup = await this.navGroup.start({
navLinks,
application,
breadcrumbsEnricher$,
workspaces,
});

// erase chrome fields from a previous app while switching to a next app
application.currentAppId$.subscribe(() => {
Expand Down Expand Up @@ -280,6 +291,7 @@
badge$={badge$.pipe(takeUntil(this.stop$))}
basePath={http.basePath}
breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))}
breadcrumbsEnricher$={breadcrumbsEnricher$.pipe(takeUntil(this.stop$))}
customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))}
opensearchDashboardsDocLink={docLinks.links.opensearchDashboards.introduction}
forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()}
Expand Down Expand Up @@ -347,6 +359,12 @@
breadcrumbs$.next(newBreadcrumbs);
},

getBreadcrumbsEnricher$: () => breadcrumbsEnricher$.pipe(takeUntil(this.stop$)),

Check warning on line 362 in src/core/public/chrome/chrome_service.tsx

View check run for this annotation

Codecov / codecov/patch

src/core/public/chrome/chrome_service.tsx#L362

Added line #L362 was not covered by tests

setBreadcrumbsEnricher: (enricher: ChromeBreadcrumbEnricher) => {
breadcrumbsEnricher$.next(enricher);

Check warning on line 365 in src/core/public/chrome/chrome_service.tsx

View check run for this annotation

Codecov / codecov/patch

src/core/public/chrome/chrome_service.tsx#L365

Added line #L365 was not covered by tests
},

getHelpExtension$: () => helpExtension$.pipe(takeUntil(this.stop$)),

setHelpExtension: (helpExtension?: ChromeHelpExtension) => {
Expand Down Expand Up @@ -483,6 +501,16 @@
*/
setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void;

/**
* Get an observable of the current breadcrumbs enricher
*/
getBreadcrumbsEnricher$(): Observable<ChromeBreadcrumbEnricher | undefined>;

/**
* Override the current ChromeBreadcrumbEnricher
*/
setBreadcrumbsEnricher(newBreadcrumbsEnricher: ChromeBreadcrumbEnricher | undefined): void;

/**
* Get an observable of the current custom nav link
*/
Expand Down
214 changes: 213 additions & 1 deletion src/core/public/chrome/nav_group/nav_group_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import {
} from './nav_group_service';
import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.mock';
import { NavLinksService } from '../nav_links';
import { applicationServiceMock, httpServiceMock } from '../../mocks';
import { applicationServiceMock, httpServiceMock, workspacesServiceMock } from '../../mocks';
import { AppCategory } from 'opensearch-dashboards/public';
import { DEFAULT_NAV_GROUPS } from '../../';
import { ChromeBreadcrumbEnricher } from '../chrome_service';

const mockedGroupFoo = {
id: 'foo',
Expand Down Expand Up @@ -52,6 +53,7 @@ const mockedCategoryBar: AppCategory = {

const mockedHttpService = httpServiceMock.createStartContract();
const mockedApplicationService = applicationServiceMock.createInternalStartContract();
const mockWorkspaceService = workspacesServiceMock.createStartContract();
const mockedNavLink = new NavLinksService();
const mockedNavLinkService = mockedNavLink.start({
http: mockedHttpService,
Expand Down Expand Up @@ -124,6 +126,8 @@ describe('ChromeNavGroupService#setup()', () => {
const chromeNavGroupServiceStart = await chromeNavGroupService.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$: new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(undefined),
workspaces: workspacesServiceMock.createStartContract(),
});
const groupsMap = await chromeNavGroupServiceStart.getNavGroupsMap$().pipe(first()).toPromise();
expect(groupsMap[mockedGroupFoo.id].navLinks.length).toEqual(2);
Expand All @@ -147,6 +151,8 @@ describe('ChromeNavGroupService#setup()', () => {
const chromeNavGroupServiceStart = await chromeNavGroupService.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$: new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(undefined),
workspaces: workspacesServiceMock.createStartContract(),
});
const groupsMap = await chromeNavGroupServiceStart.getNavGroupsMap$().pipe(first()).toPromise();
expect(groupsMap[mockedGroupFoo.id].navLinks.length).toEqual(1);
Expand Down Expand Up @@ -206,6 +212,8 @@ describe('ChromeNavGroupService#start()', () => {
const chromeStart = await chromeNavGroupService.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$: new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(undefined),
workspaces: workspacesServiceMock.createStartContract(),
});

const groupsMap = await chromeStart.getNavGroupsMap$().pipe(first()).toPromise();
Expand All @@ -231,6 +239,8 @@ describe('ChromeNavGroupService#start()', () => {
const chromeNavGroupServiceStart = await chromeNavGroupService.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$: new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(undefined),
workspaces: workspacesServiceMock.createStartContract(),
});

expect(chromeNavGroupServiceStart.getNavGroupEnabled()).toBe(true);
Expand All @@ -246,6 +256,8 @@ describe('ChromeNavGroupService#start()', () => {
const chromeNavGroupServiceStart = await chromeNavGroupService.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$: new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(undefined),
workspaces: workspacesServiceMock.createStartContract(),
});

navGroupEnabled$.next(false);
Expand Down Expand Up @@ -276,6 +288,8 @@ describe('ChromeNavGroupService#start()', () => {
const chromeNavGroupServiceStart = await chromeNavGroupService.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$: new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(undefined),
workspaces: workspacesServiceMock.createStartContract(),
});

// set an existing nav group id
Expand Down Expand Up @@ -311,6 +325,200 @@ describe('ChromeNavGroupService#start()', () => {
expect(sessionStorageMock.getItem(CURRENT_NAV_GROUP_ID)).toBeFalsy();
expect(currentNavGroup).toBeUndefined();
});

it('should set current nav group automatically if application only belongs 1 nav group', async () => {
const uiSettings = uiSettingsServiceMock.createSetupContract();
const navGroupEnabled$ = new Rx.BehaviorSubject(true);
uiSettings.get$.mockImplementation(() => navGroupEnabled$);

const chromeNavGroupService = new ChromeNavGroupService();
const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings });

chromeNavGroupServiceSetup.addNavLinksToGroup(
{
id: 'foo-group',
title: 'fooGroupTitle',
description: 'foo description',
},
[mockedNavLinkFoo]
);

chromeNavGroupServiceSetup.addNavLinksToGroup(
{
id: 'bar-group',
title: 'barGroupTitle',
description: 'bar description',
},
[mockedNavLinkFoo, mockedNavLinkBar]
);

const chromeNavGroupServiceStart = await chromeNavGroupService.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$: new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(undefined),
workspaces: workspacesServiceMock.createStartContract(),
});

mockedApplicationService.navigateToApp(mockedNavLinkFoo.id);
let currentNavGroup = await chromeNavGroupServiceStart
.getCurrentNavGroup$()
.pipe(first())
.toPromise();

expect(currentNavGroup).toBeFalsy();

// reset
chromeNavGroupServiceStart.setCurrentNavGroup(undefined);

mockedApplicationService.navigateToApp(mockedNavLinkBar.id);

currentNavGroup = await chromeNavGroupServiceStart
.getCurrentNavGroup$()
.pipe(first())
.toPromise();

expect(currentNavGroup?.id).toEqual('bar-group');
expect(currentNavGroup?.title).toEqual('barGroupTitle');
});

it('should erase current nav group if application is home', async () => {
const uiSettings = uiSettingsServiceMock.createSetupContract();
const navGroupEnabled$ = new Rx.BehaviorSubject(true);
uiSettings.get$.mockImplementation(() => navGroupEnabled$);

const chromeNavGroupService = new ChromeNavGroupService();
const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings });

chromeNavGroupServiceSetup.addNavLinksToGroup(
{
id: 'foo-group',
title: 'fooGroupTitle',
description: 'foo description',
},
[mockedNavLinkFoo]
);

chromeNavGroupServiceSetup.addNavLinksToGroup(
{
id: 'bar-group',
title: 'barGroupTitle',
description: 'bar description',
},
[mockedNavLinkFoo, mockedNavLinkBar]
);

const chromeNavGroupServiceStart = await chromeNavGroupService.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$: new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(undefined),
workspaces: workspacesServiceMock.createStartContract(),
});

chromeNavGroupServiceStart.setCurrentNavGroup('foo-group');

mockedApplicationService.navigateToApp('home');
const currentNavGroup = await chromeNavGroupServiceStart
.getCurrentNavGroup$()
.pipe(first())
.toPromise();

expect(currentNavGroup).toBeFalsy();
});

it('should set breadcrumbs enricher when nav group is enabled', async () => {
const uiSettings = uiSettingsServiceMock.createSetupContract();
const navGroupEnabled$ = new Rx.BehaviorSubject(true);
uiSettings.get$.mockImplementation(() => navGroupEnabled$);

const chromeNavGroupService = new ChromeNavGroupService();
const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings });

chromeNavGroupServiceSetup.addNavLinksToGroup(
{
id: 'foo-group',
title: 'fooGroupTitle',
description: 'foo description',
},
[mockedNavLinkFoo]
);

chromeNavGroupServiceSetup.addNavLinksToGroup(
{
id: 'bar-group',
title: 'barGroupTitle',
description: 'bar description',
},
[mockedNavLinkFoo, mockedNavLinkBar]
);

const breadcrumbsEnricher$ = new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(
undefined
);

const chromeNavGroupServiceStart = await chromeNavGroupService.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$,
workspaces: mockWorkspaceService,
});

chromeNavGroupServiceStart.setCurrentNavGroup('bar-group');

expect(breadcrumbsEnricher$.getValue()).toBeTruthy();

const breadcrumbs = [{ text: 'test' }];
const enrichedBreadcrumbs = breadcrumbsEnricher$.getValue()?.(breadcrumbs);

// home -> bar-group -> test
expect(enrichedBreadcrumbs).toHaveLength(3);

// reset current nav group
chromeNavGroupServiceStart.setCurrentNavGroup(undefined);
expect(breadcrumbsEnricher$.getValue()).toBeFalsy();
});

it('should NOT set breadcrumbs enricher when in a workspace', async () => {
const uiSettings = uiSettingsServiceMock.createSetupContract();
const navGroupEnabled$ = new Rx.BehaviorSubject(true);
uiSettings.get$.mockImplementation(() => navGroupEnabled$);

const chromeNavGroupService = new ChromeNavGroupService();
const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings });

chromeNavGroupServiceSetup.addNavLinksToGroup(
{
id: 'foo-group',
title: 'fooGroupTitle',
description: 'foo description',
},
[mockedNavLinkFoo]
);

chromeNavGroupServiceSetup.addNavLinksToGroup(
{
id: 'bar-group',
title: 'barGroupTitle',
description: 'bar description',
},
[mockedNavLinkFoo, mockedNavLinkBar]
);

const breadcrumbsEnricher$ = new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(
undefined
);
mockWorkspaceService.currentWorkspace$.next({ id: 'test', name: 'test workspace' });

const chromeNavGroupServiceStart = await chromeNavGroupService.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$,
workspaces: mockWorkspaceService,
});

chromeNavGroupServiceStart.setCurrentNavGroup('bar-group');

expect(breadcrumbsEnricher$.getValue()).toBeFalsy();
});
});

describe('nav group updater', () => {
Expand All @@ -328,6 +536,8 @@ describe('nav group updater', () => {
const navGroupStart = await navGroup.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$: new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(undefined),
workspaces: workspacesServiceMock.createStartContract(),
});

expect(await navGroupStart.getNavGroupsMap$().pipe(first()).toPromise()).toEqual({
Expand Down Expand Up @@ -365,6 +575,8 @@ describe('nav group updater', () => {
const navGroupStart = await navGroup.start({
navLinks: mockedNavLinkService,
application: mockedApplicationService,
breadcrumbsEnricher$: new Rx.BehaviorSubject<ChromeBreadcrumbEnricher | undefined>(undefined),
workspaces: workspacesServiceMock.createStartContract(),
});
expect(await navGroupStart.getNavGroupsMap$().pipe(first()).toPromise()).toEqual({
dataAdministration: expect.objectContaining({
Expand Down
Loading
Loading