Skip to content

Commit

Permalink
[Stateful sidenav] Fix breadcrumbs (elastic#196169)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebelga authored Oct 15, 2024
1 parent 8abe259 commit 204f9d3
Show file tree
Hide file tree
Showing 17 changed files with 104 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ describe('start', () => {
describe('breadcrumbs', () => {
it('updates/emits the current set of breadcrumbs', async () => {
const { chrome, service } = await start();
const promise = chrome.getBreadcrumbs$().pipe(toArray()).toPromise();
const promise = firstValueFrom(chrome.getBreadcrumbs$().pipe(toArray()));

chrome.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }]);
chrome.setBreadcrumbs([{ text: 'foo' }]);
Expand Down Expand Up @@ -425,6 +425,35 @@ describe('start', () => {
]
`);
});

it('allows the project breadcrumb to also be set', async () => {
const { chrome } = await start();

chrome.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }]); // only setting the classic breadcrumbs

{
const breadcrumbs = await firstValueFrom(chrome.project.getBreadcrumbs$());
expect(breadcrumbs.length).toBe(1);
expect(breadcrumbs[0]).toMatchObject({
'data-test-subj': 'deploymentCrumb',
});
}

chrome.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }], {
project: { value: [{ text: 'baz' }] }, // also setting the project breadcrumb
});

{
const breadcrumbs = await firstValueFrom(chrome.project.getBreadcrumbs$());
expect(breadcrumbs.length).toBe(2);
expect(breadcrumbs[0]).toMatchObject({
'data-test-subj': 'deploymentCrumb',
});
expect(breadcrumbs[1]).toEqual({
text: 'baz', // the project breadcrumb
});
}
});
});

describe('breadcrumbsAppendExtension$', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type {
ChromeNavLink,
ChromeBadge,
ChromeBreadcrumb,
ChromeSetBreadcrumbsParams,
ChromeBreadcrumbsAppendExtension,
ChromeGlobalHelpExtensionMenuLink,
ChromeHelpExtension,
Expand Down Expand Up @@ -354,6 +355,17 @@ export class ChromeService {
projectNavigation.setProjectBreadcrumbs(breadcrumbs, params);
};

const setClassicBreadcrumbs = (
newBreadcrumbs: ChromeBreadcrumb[],
{ project }: ChromeSetBreadcrumbsParams = {}
) => {
breadcrumbs$.next(newBreadcrumbs);
if (project) {
const { value: projectValue, absolute = false } = project;
setProjectBreadcrumbs(projectValue ?? [], { absolute });
}
};

const setProjectHome = (homeHref: string) => {
validateChromeStyle();
projectNavigation.setProjectHome(homeHref);
Expand Down Expand Up @@ -507,9 +519,7 @@ export class ChromeService {

getBreadcrumbs$: () => breadcrumbs$.pipe(takeUntil(this.stop$)),

setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => {
breadcrumbs$.next(newBreadcrumbs);
},
setBreadcrumbs: setClassicBreadcrumbs,

getBreadcrumbsAppendExtension$: () => breadcrumbsAppendExtension$.pipe(takeUntil(this.stop$)),

Expand Down Expand Up @@ -586,6 +596,7 @@ export class ChromeService {
getNavigationTreeUi$: () => projectNavigation.getNavigationTreeUi$(),
setSideNavComponent: setProjectSideNavComponent,
setBreadcrumbs: setProjectBreadcrumbs,
getBreadcrumbs$: projectNavigation.getProjectBreadcrumbs$.bind(projectNavigation),
getActiveNavigationNodes$: () => projectNavigation.getActiveNodes$(),
updateSolutionNavigations: projectNavigation.updateSolutionNavigations,
changeActiveSolutionNavigation: projectNavigation.changeActiveSolutionNavigation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import React from 'react';
import { EuiContextMenuPanel, EuiContextMenuItem, EuiButtonEmpty } from '@elastic/eui';
import type {
AppDeepLinkId,
ChromeProjectBreadcrumb,
ChromeProjectNavigationNode,
ChromeSetProjectBreadcrumbsParams,
ChromeBreadcrumb,
Expand All @@ -30,14 +29,14 @@ export function buildBreadcrumbs({
}: {
projectName?: string;
projectBreadcrumbs: {
breadcrumbs: ChromeProjectBreadcrumb[];
breadcrumbs: ChromeBreadcrumb[];
params: ChromeSetProjectBreadcrumbsParams;
};
chromeBreadcrumbs: ChromeBreadcrumb[];
cloudLinks: CloudLinks;
activeNodes: ChromeProjectNavigationNode[][];
isServerless: boolean;
}): ChromeProjectBreadcrumb[] {
}): ChromeBreadcrumb[] {
const rootCrumb = buildRootCrumb({
projectName,
cloudLinks,
Expand All @@ -54,7 +53,7 @@ export function buildBreadcrumbs({
(n) => Boolean(n.title) && n.breadcrumbStatus !== 'hidden'
);
const navBreadcrumbs = navBreadcrumbPath.map(
(node): ChromeProjectBreadcrumb => ({
(node): ChromeBreadcrumb => ({
href: node.deepLink?.url ?? node.href,
deepLinkId: node.deepLink?.id as AppDeepLinkId,
text: node.title,
Expand Down Expand Up @@ -99,7 +98,7 @@ function buildRootCrumb({
projectName?: string;
cloudLinks: CloudLinks;
isServerless: boolean;
}): ChromeProjectBreadcrumb {
}): ChromeBreadcrumb {
if (isServerless) {
return {
text:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { InternalApplicationStart } from '@kbn/core-application-browser-internal
import type {
ChromeNavLinks,
SideNavComponent,
ChromeProjectBreadcrumb,
ChromeBreadcrumb,
ChromeSetProjectBreadcrumbsParams,
ChromeProjectNavigationNode,
Expand Down Expand Up @@ -80,7 +79,7 @@ export class ProjectNavigationService {
);

private projectBreadcrumbs$ = new BehaviorSubject<{
breadcrumbs: ChromeProjectBreadcrumb[];
breadcrumbs: ChromeBreadcrumb[];
params: ChromeSetProjectBreadcrumbsParams;
}>({ breadcrumbs: [], params: { absolute: false } });
private readonly stop$ = new ReplaySubject<void>(1);
Expand Down Expand Up @@ -153,15 +152,15 @@ export class ProjectNavigationService {
return this.customProjectSideNavComponent$.asObservable();
},
setProjectBreadcrumbs: (
breadcrumbs: ChromeProjectBreadcrumb | ChromeProjectBreadcrumb[],
breadcrumbs: ChromeBreadcrumb | ChromeBreadcrumb[],
params?: Partial<ChromeSetProjectBreadcrumbsParams>
) => {
this.projectBreadcrumbs$.next({
breadcrumbs: Array.isArray(breadcrumbs) ? breadcrumbs : [breadcrumbs],
params: { absolute: false, ...params },
});
},
getProjectBreadcrumbs$: (): Observable<ChromeProjectBreadcrumb[]> => {
getProjectBreadcrumbs$: (): Observable<ChromeBreadcrumb[]> => {
return combineLatest([
this.projectBreadcrumbs$,
this.activeNodes$,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

import type {
ChromeStart,
ChromeBreadcrumb,
SideNavComponent,
ChromeProjectBreadcrumb,
ChromeSetProjectBreadcrumbsParams,
ChromeProjectNavigationNode,
AppDeepLinkId,
Expand Down Expand Up @@ -87,6 +87,9 @@ export interface InternalChromeStart extends ChromeStart {
*/
setSideNavComponent(component: SideNavComponent | null): void;

/** Get an Observable of the current project breadcrumbs */
getBreadcrumbs$(): Observable<ChromeBreadcrumb[]>;

/**
* Set project breadcrumbs
* @param breadcrumbs
Expand All @@ -95,7 +98,7 @@ export interface InternalChromeStart extends ChromeStart {
* Use {@link ServerlessPluginStart.setBreadcrumbs} to set project breadcrumbs.
*/
setBreadcrumbs(
breadcrumbs: ChromeProjectBreadcrumb[] | ChromeProjectBreadcrumb,
breadcrumbs: ChromeBreadcrumb[] | ChromeBreadcrumb,
params?: Partial<ChromeSetProjectBreadcrumbsParams>
): void;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const createStartContractMock = () => {
initNavigation: jest.fn(),
setSideNavComponent: jest.fn(),
setBreadcrumbs: jest.fn(),
getBreadcrumbs$: jest.fn(),
getActiveNavigationNodes$: jest.fn(),
getNavigationTreeUi$: jest.fn(),
changeActiveSolutionNavigation: jest.fn(),
Expand Down
2 changes: 1 addition & 1 deletion packages/core/chrome/core-chrome-browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type {
AppId,
ChromeBadge,
ChromeBreadcrumb,
ChromeSetBreadcrumbsParams,
ChromeBreadcrumbsAppendExtension,
ChromeDocTitle,
ChromeGlobalHelpExtensionMenuLink,
Expand Down Expand Up @@ -41,7 +42,6 @@ export type {
SideNavCompProps,
SideNavComponent,
SideNavNodeStatus,
ChromeProjectBreadcrumb,
ChromeSetProjectBreadcrumbsParams,
NodeDefinition,
NodeDefinitionWithChildren,
Expand Down
19 changes: 19 additions & 0 deletions packages/core/chrome/core-chrome-browser/src/breadcrumb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,22 @@ export interface ChromeBreadcrumb extends EuiBreadcrumb {
export interface ChromeBreadcrumbsAppendExtension {
content: MountPoint<HTMLDivElement>;
}

/** @public */
export interface ChromeSetBreadcrumbsParams {
/**
* Declare the breadcrumbs for the project/solution type navigation in stateful.
* Those breadcrumbs correspond to the serverless breadcrumbs declaration.
*/
project?: {
/**
* The breadcrumb value to set. Can be a single breadcrumb or an array of breadcrumbs.
*/
value: ChromeBreadcrumb | ChromeBreadcrumb[];
/**
* Indicates whether the breadcrumb should be absolute (replaces the full path) or relative.
* @default false
*/
absolute?: boolean;
};
}
8 changes: 6 additions & 2 deletions packages/core/chrome/core-chrome-browser/src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import type { ChromeRecentlyAccessed } from './recently_accessed';
import type { ChromeDocTitle } from './doc_title';
import type { ChromeHelpMenuLink, ChromeNavControls } from './nav_controls';
import type { ChromeHelpExtension } from './help_extension';
import type { ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension } from './breadcrumb';
import type {
ChromeBreadcrumb,
ChromeBreadcrumbsAppendExtension,
ChromeSetBreadcrumbsParams,
} from './breadcrumb';
import type { ChromeBadge, ChromeStyle, ChromeUserBanner } from './types';
import type { ChromeGlobalHelpExtensionMenuLink } from './help_extension';
import type { PanelSelectedNode } from './project_navigation';
Expand Down Expand Up @@ -84,7 +88,7 @@ export interface ChromeStart {
/**
* Override the current set of breadcrumbs
*/
setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void;
setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[], params?: ChromeSetBreadcrumbsParams): void;

/**
* Get an observable of the current extension appended to breadcrumbs
Expand Down
7 changes: 5 additions & 2 deletions packages/core/chrome/core-chrome-browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export type { ChromeBreadcrumbsAppendExtension, ChromeBreadcrumb } from './breadcrumb';
export type {
ChromeBreadcrumbsAppendExtension,
ChromeBreadcrumb,
ChromeSetBreadcrumbsParams,
} from './breadcrumb';
export type { ChromeStart } from './contracts';
export type { ChromeDocTitle } from './doc_title';
export type {
Expand Down Expand Up @@ -42,7 +46,6 @@ export type {
SideNavComponent,
SideNavNodeStatus,
ChromeSetProjectBreadcrumbsParams,
ChromeProjectBreadcrumb,
NodeDefinition,
NodeDefinitionWithChildren,
RenderAs as NodeRenderAs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import type { AppId as SecurityApp, DeepLinkId as SecurityLink } from '@kbn/deep
import type { AppId as FleetApp, DeepLinkId as FleetLink } from '@kbn/deeplinks-fleet';
import type { AppId as SharedApp, DeepLinkId as SharedLink } from '@kbn/deeplinks-shared';

import type { ChromeBreadcrumb } from './breadcrumb';
import type { ChromeNavLink } from './nav_links';
import type { ChromeRecentlyAccessedHistoryItem } from './recently_accessed';

Expand Down Expand Up @@ -262,9 +261,6 @@ export interface SideNavCompProps {
/** @public */
export type SideNavComponent = ComponentType<SideNavCompProps>;

/** @public */
export type ChromeProjectBreadcrumb = ChromeBreadcrumb;

/** @public */
export interface ChromeSetProjectBreadcrumbsParams {
absolute: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ export function InternalDashboardTopNav({
},
},
...dashboardTitleBreadcrumbs,
])
]),
{
project: { value: dashboardTitleBreadcrumbs },
}
);
}
}, [redirectTo, dashboardTitle, dashboardApi, viewMode, customLeadingBreadCrumbs]);
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/management/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ export class ManagementPlugin
const [, ...trailingBreadcrumbs] = newBreadcrumbs;
deps.serverless.setBreadcrumbs(trailingBreadcrumbs);
} else {
coreStart.chrome.setBreadcrumbs(newBreadcrumbs);
coreStart.chrome.setBreadcrumbs(newBreadcrumbs, {
project: { value: newBreadcrumbs, absolute: true },
});
}
},
isSidebarEnabled$: managementPlugin.isSidebarEnabled$,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import type { Services } from '../common/services';
export const subscribeBreadcrumbs = (services: Services) => {
const { securitySolution, chrome } = services;
securitySolution.getBreadcrumbsNav$().subscribe((breadcrumbsNav) => {
chrome.setBreadcrumbs([...breadcrumbsNav.leading, ...breadcrumbsNav.trailing]);
chrome.setBreadcrumbs([...breadcrumbsNav.leading, ...breadcrumbsNav.trailing], {
project: {
value: breadcrumbsNav.trailing,
},
});
});
};
4 changes: 2 additions & 2 deletions x-pack/plugins/serverless/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import type {
ChromeProjectBreadcrumb,
ChromeBreadcrumb,
ChromeSetProjectBreadcrumbsParams,
SideNavComponent,
NavigationTreeDefinition,
Expand All @@ -21,7 +21,7 @@ export interface ServerlessPluginSetup {}

export interface ServerlessPluginStart {
setBreadcrumbs: (
breadcrumbs: ChromeProjectBreadcrumb | ChromeProjectBreadcrumb[],
breadcrumbs: ChromeBreadcrumb | ChromeBreadcrumb[],
params?: Partial<ChromeSetProjectBreadcrumbsParams>
) => void;
setProjectHome(homeHref: string): void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await solutionNavigation.sidenav.openSection('project_settings_project_nav');
await solutionNavigation.sidenav.clickLink({ deepLinkId: 'management' });
await solutionNavigation.sidenav.expectLinkActive({ deepLinkId: 'management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Stack Management' });

// navigate back to the home page using header logo
await solutionNavigation.clickLogo();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await solutionNavigation.sidenav.openSection('project_settings_project_nav');
await solutionNavigation.sidenav.clickLink({ deepLinkId: 'management' });
await solutionNavigation.sidenav.expectLinkActive({ deepLinkId: 'management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Stack Management' });

// navigate back to the home page using header logo
await solutionNavigation.clickLogo();
Expand Down

0 comments on commit 204f9d3

Please sign in to comment.