mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Stateful sidenav] Fix breadcrumbs (#196169)](https://github.com/elastic/kibana/pull/196169) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Sébastien Loix","email":"sebastien.loix@elastic.co"},"sourceCommit":{"committedDate":"2024-10-15T14:37:19Z","message":"[Stateful sidenav] Fix breadcrumbs (#196169)","sha":"204f9d3a2f2fef174e24f3a79eb6d7b2f2ef03f2","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:SharedUX","backport:prev-minor","Feature:Chrome"],"title":"[Stateful sidenav] Fix breadcrumbs","number":196169,"url":"https://github.com/elastic/kibana/pull/196169","mergeCommit":{"message":"[Stateful sidenav] Fix breadcrumbs (#196169)","sha":"204f9d3a2f2fef174e24f3a79eb6d7b2f2ef03f2"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196169","number":196169,"mergeCommit":{"message":"[Stateful sidenav] Fix breadcrumbs (#196169)","sha":"204f9d3a2f2fef174e24f3a79eb6d7b2f2ef03f2"}}]}] BACKPORT--> Co-authored-by: Sébastien Loix <sebastien.loix@elastic.co>
This commit is contained in:
parent
7583e1d596
commit
760021bb27
17 changed files with 104 additions and 31 deletions
|
@ -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' }]);
|
||||
|
@ -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$', () => {
|
||||
|
|
|
@ -27,6 +27,7 @@ import type {
|
|||
ChromeNavLink,
|
||||
ChromeBadge,
|
||||
ChromeBreadcrumb,
|
||||
ChromeSetBreadcrumbsParams,
|
||||
ChromeBreadcrumbsAppendExtension,
|
||||
ChromeGlobalHelpExtensionMenuLink,
|
||||
ChromeHelpExtension,
|
||||
|
@ -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);
|
||||
|
@ -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$)),
|
||||
|
||||
|
@ -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,
|
||||
|
|
|
@ -11,7 +11,6 @@ import React from 'react';
|
|||
import { EuiContextMenuPanel, EuiContextMenuItem, EuiButtonEmpty } from '@elastic/eui';
|
||||
import type {
|
||||
AppDeepLinkId,
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeProjectNavigationNode,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
ChromeBreadcrumb,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -99,7 +98,7 @@ function buildRootCrumb({
|
|||
projectName?: string;
|
||||
cloudLinks: CloudLinks;
|
||||
isServerless: boolean;
|
||||
}): ChromeProjectBreadcrumb {
|
||||
}): ChromeBreadcrumb {
|
||||
if (isServerless) {
|
||||
return {
|
||||
text:
|
||||
|
|
|
@ -11,7 +11,6 @@ import { InternalApplicationStart } from '@kbn/core-application-browser-internal
|
|||
import type {
|
||||
ChromeNavLinks,
|
||||
SideNavComponent,
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeBreadcrumb,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
ChromeProjectNavigationNode,
|
||||
|
@ -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);
|
||||
|
@ -153,7 +152,7 @@ export class ProjectNavigationService {
|
|||
return this.customProjectSideNavComponent$.asObservable();
|
||||
},
|
||||
setProjectBreadcrumbs: (
|
||||
breadcrumbs: ChromeProjectBreadcrumb | ChromeProjectBreadcrumb[],
|
||||
breadcrumbs: ChromeBreadcrumb | ChromeBreadcrumb[],
|
||||
params?: Partial<ChromeSetProjectBreadcrumbsParams>
|
||||
) => {
|
||||
this.projectBreadcrumbs$.next({
|
||||
|
@ -161,7 +160,7 @@ export class ProjectNavigationService {
|
|||
params: { absolute: false, ...params },
|
||||
});
|
||||
},
|
||||
getProjectBreadcrumbs$: (): Observable<ChromeProjectBreadcrumb[]> => {
|
||||
getProjectBreadcrumbs$: (): Observable<ChromeBreadcrumb[]> => {
|
||||
return combineLatest([
|
||||
this.projectBreadcrumbs$,
|
||||
this.activeNodes$,
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
import type {
|
||||
ChromeStart,
|
||||
ChromeBreadcrumb,
|
||||
SideNavComponent,
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
ChromeProjectNavigationNode,
|
||||
AppDeepLinkId,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -12,6 +12,7 @@ export type {
|
|||
AppId,
|
||||
ChromeBadge,
|
||||
ChromeBreadcrumb,
|
||||
ChromeSetBreadcrumbsParams,
|
||||
ChromeBreadcrumbsAppendExtension,
|
||||
ChromeDocTitle,
|
||||
ChromeGlobalHelpExtensionMenuLink,
|
||||
|
@ -41,7 +42,6 @@ export type {
|
|||
SideNavCompProps,
|
||||
SideNavComponent,
|
||||
SideNavNodeStatus,
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
NodeDefinition,
|
||||
NodeDefinitionWithChildren,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
@ -42,7 +46,6 @@ export type {
|
|||
SideNavComponent,
|
||||
SideNavNodeStatus,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
ChromeProjectBreadcrumb,
|
||||
NodeDefinition,
|
||||
NodeDefinitionWithChildren,
|
||||
RenderAs as NodeRenderAs,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -262,9 +261,6 @@ export interface SideNavCompProps {
|
|||
/** @public */
|
||||
export type SideNavComponent = ComponentType<SideNavCompProps>;
|
||||
|
||||
/** @public */
|
||||
export type ChromeProjectBreadcrumb = ChromeBreadcrumb;
|
||||
|
||||
/** @public */
|
||||
export interface ChromeSetProjectBreadcrumbsParams {
|
||||
absolute: boolean;
|
||||
|
|
|
@ -189,7 +189,10 @@ export function InternalDashboardTopNav({
|
|||
},
|
||||
},
|
||||
...dashboardTitleBreadcrumbs,
|
||||
])
|
||||
]),
|
||||
{
|
||||
project: { value: dashboardTitleBreadcrumbs },
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [redirectTo, dashboardTitle, dashboardApi, viewMode, customLeadingBreadCrumbs]);
|
||||
|
|
|
@ -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$,
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type {
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeBreadcrumb,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
SideNavComponent,
|
||||
NavigationTreeDefinition,
|
||||
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue