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

[Stateful sidenav] Fix breadcrumbs #196169

Merged
merged 12 commits into from
Oct 15, 2024
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
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,
},
});
});
};