mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Serverless/breadcrumbs] Bootstrap and API (#156855)
## Summary Partially address https://github.com/elastic/kibana/issues/156517 Based on: - [Serverless chrome breadcrumbs requirements](https://docs.google.com/document/d/1e5SbDPpySiPeBrjgLJD6Qw6fJyiy34uO2dmGLHlu38E/edit) - [Serverless chrome breadcrumbs API tech doc](https://docs.google.com/document/d/1_qL14NMGYdI0913eclJd3DXG0lQ2jkE0V3578iaDASs/edit#heading=h.ndqge1i76y6p) Adds an api and bootstrap code for project (serverless) breadcrumbs which allows to either set a "deeper context" breadcrumbs or override nav controlled breadcrumbs: ``` plugins.serverless.setBreadcrumbs(myDeeperContextBreadcrumbs); plugins.serverless.setBreadcrumbs(myCustomBreadcrumbs, {absolute: true}); ``` This PR adds an API and sets everything around the breadcrumb building logic. Actual breadcrumbs building is not implemented and depends on https://github.com/elastic/kibana/issues/157702 as we need the navigation tree to be available in chrome service. This PR doesn't have any visible changes
This commit is contained in:
parent
addb7c0859
commit
d4f4a25e60
12 changed files with 211 additions and 3 deletions
|
@ -29,6 +29,7 @@ import type {
|
|||
ChromeUserBanner,
|
||||
ChromeStyle,
|
||||
ChromeProjectNavigation,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import type { CustomBrandingStart } from '@kbn/core-custom-branding-browser';
|
||||
import type { SideNavComponent as ISideNavComponent } from '@kbn/core-chrome-browser';
|
||||
|
@ -198,6 +199,13 @@ export class ChromeService {
|
|||
projectNavigation.setProjectNavigation(config);
|
||||
};
|
||||
|
||||
const setProjectBreadcrumbs = (
|
||||
breadcrumbs: ChromeBreadcrumb[] | ChromeBreadcrumb,
|
||||
params?: ChromeSetProjectBreadcrumbsParams
|
||||
) => {
|
||||
projectNavigation.setProjectBreadcrumbs(breadcrumbs, params);
|
||||
};
|
||||
|
||||
const isIE = () => {
|
||||
const ua = window.navigator.userAgent;
|
||||
const msie = ua.indexOf('MSIE '); // IE 10 or older
|
||||
|
@ -247,9 +255,11 @@ export class ChromeService {
|
|||
// }
|
||||
|
||||
const projectNavigationComponent$ = projectNavigation.getProjectSideNavComponent$();
|
||||
// const projectNavigation$ = projectNavigation.getProjectNavigation$();
|
||||
|
||||
const ProjectHeaderWithNavigation = () => {
|
||||
const CustomSideNavComponent = useObservable(projectNavigationComponent$, undefined);
|
||||
// const projectNavigationConfig = useObservable(projectNavigation$, undefined);
|
||||
|
||||
let SideNavComponent: ISideNavComponent = () => null;
|
||||
|
||||
|
@ -261,6 +271,13 @@ export class ChromeService {
|
|||
: ProjectSideNavigation;
|
||||
}
|
||||
|
||||
// if projectNavigation wasn't set fallback to the default breadcrumbs
|
||||
// TODO: Uncommented when we support the project navigation config
|
||||
// const projectBreadcrumbs$ = projectNavigationConfig
|
||||
// ? projectNavigation.getProjectBreadcrumbs$()
|
||||
// : breadcrumbs$;
|
||||
const projectBreadcrumbs$ = breadcrumbs$;
|
||||
|
||||
return (
|
||||
<ProjectHeader
|
||||
{...{
|
||||
|
@ -268,7 +285,7 @@ export class ChromeService {
|
|||
globalHelpExtensionMenuLinks$,
|
||||
}}
|
||||
actionMenu$={application.currentActionMenu$}
|
||||
breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))}
|
||||
breadcrumbs$={projectBreadcrumbs$.pipe(takeUntil(this.stop$))}
|
||||
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
|
||||
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
|
||||
navControlsRight$={navControls.getRight$()}
|
||||
|
@ -390,6 +407,7 @@ export class ChromeService {
|
|||
project: {
|
||||
setNavigation: setProjectNavigation,
|
||||
setSideNavComponent: setProjectSideNavComponent,
|
||||
setBreadcrumbs: setProjectBreadcrumbs,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ChromeProjectBreadcrumb } from '@kbn/core-chrome-browser';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
export const createHomeBreadcrumb = ({
|
||||
homeHref,
|
||||
}: {
|
||||
homeHref: string;
|
||||
}): ChromeProjectBreadcrumb => {
|
||||
return {
|
||||
text: <EuiIcon type="home" />,
|
||||
title: 'Home',
|
||||
href: homeHref,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ProjectNavigationService } from './project_navigation_service';
|
||||
import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
|
||||
import type { ChromeNavLinks } from '@kbn/core-chrome-browser';
|
||||
|
||||
const setup = () => {
|
||||
const projectNavigationService = new ProjectNavigationService();
|
||||
const projectNavigation = projectNavigationService.start({
|
||||
application: applicationServiceMock.createInternalStartContract(),
|
||||
navLinks: {} as unknown as ChromeNavLinks,
|
||||
});
|
||||
|
||||
return { projectNavigation };
|
||||
};
|
||||
|
||||
describe('breadcrumbs', () => {
|
||||
test('should return list of breadcrumbs home / nav / custom', async () => {
|
||||
const { projectNavigation } = setup();
|
||||
|
||||
projectNavigation.setProjectBreadcrumbs([
|
||||
{ text: 'custom1', href: '/custom1' },
|
||||
{ text: 'custom2', href: '/custom1/custom2' },
|
||||
]);
|
||||
|
||||
// TODO: add projectNavigation.setProjectNavigation() to test the part of breadcrumbs extracted from the nav tree
|
||||
|
||||
const breadcrumbs = await firstValueFrom(projectNavigation.getProjectBreadcrumbs$());
|
||||
expect(breadcrumbs).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"href": "/",
|
||||
"text": <EuiIcon
|
||||
type="home"
|
||||
/>,
|
||||
"title": "Home",
|
||||
},
|
||||
Object {
|
||||
"href": "/custom1",
|
||||
"text": "custom1",
|
||||
},
|
||||
Object {
|
||||
"href": "/custom1/custom2",
|
||||
"text": "custom2",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('should skip the default navigation from project navigation when absolute: true is used', async () => {
|
||||
const { projectNavigation } = setup();
|
||||
|
||||
projectNavigation.setProjectBreadcrumbs(
|
||||
[
|
||||
{ text: 'custom1', href: '/custom1' },
|
||||
{ text: 'custom2', href: '/custom1/custom2' },
|
||||
],
|
||||
{ absolute: true }
|
||||
);
|
||||
|
||||
// TODO: add projectNavigation.setProjectNavigation() to test the part of breadcrumbs extracted from the nav tree
|
||||
|
||||
const breadcrumbs = await firstValueFrom(projectNavigation.getProjectBreadcrumbs$());
|
||||
expect(breadcrumbs).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"href": "/",
|
||||
"text": <EuiIcon
|
||||
type="home"
|
||||
/>,
|
||||
"title": "Home",
|
||||
},
|
||||
Object {
|
||||
"href": "/custom1",
|
||||
"text": "custom1",
|
||||
},
|
||||
Object {
|
||||
"href": "/custom1/custom2",
|
||||
"text": "custom2",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -11,8 +11,11 @@ import {
|
|||
ChromeNavLinks,
|
||||
ChromeProjectNavigation,
|
||||
SideNavComponent,
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, combineLatest, map } from 'rxjs';
|
||||
import { createHomeBreadcrumb } from './home_breadcrumbs';
|
||||
|
||||
interface StartDeps {
|
||||
application: InternalApplicationStart;
|
||||
|
@ -25,6 +28,11 @@ export class ProjectNavigationService {
|
|||
}>({ current: null });
|
||||
private projectNavigation$ = new BehaviorSubject<ChromeProjectNavigation | undefined>(undefined);
|
||||
|
||||
private projectBreadcrumbs$ = new BehaviorSubject<{
|
||||
breadcrumbs: ChromeProjectBreadcrumb[];
|
||||
params: ChromeSetProjectBreadcrumbsParams;
|
||||
}>({ breadcrumbs: [], params: { absolute: false } });
|
||||
|
||||
public start({ application, navLinks }: StartDeps) {
|
||||
// TODO: use application, navLink and projectNavigation$ to:
|
||||
// 1. validate projectNavigation$ against navLinks,
|
||||
|
@ -44,6 +52,33 @@ export class ProjectNavigationService {
|
|||
getProjectSideNavComponent$: () => {
|
||||
return this.customProjectSideNavComponent$.asObservable();
|
||||
},
|
||||
setProjectBreadcrumbs: (
|
||||
breadcrumbs: ChromeProjectBreadcrumb | ChromeProjectBreadcrumb[],
|
||||
params?: Partial<ChromeSetProjectBreadcrumbsParams>
|
||||
) => {
|
||||
this.projectBreadcrumbs$.next({
|
||||
breadcrumbs: Array.isArray(breadcrumbs) ? breadcrumbs : [breadcrumbs],
|
||||
params: { absolute: false, ...params },
|
||||
});
|
||||
},
|
||||
getProjectBreadcrumbs$: (): Observable<ChromeProjectBreadcrumb[]> => {
|
||||
return combineLatest([this.projectBreadcrumbs$, this.projectNavigation$]).pipe(
|
||||
map(([breadcrumbs, projectNavigation]) => {
|
||||
/* TODO: point home breadcrumb to the correct place */
|
||||
const homeBreadcrumb = createHomeBreadcrumb({ homeHref: '/' });
|
||||
|
||||
if (breadcrumbs.params.absolute) {
|
||||
return [homeBreadcrumb, ...breadcrumbs.breadcrumbs];
|
||||
} else {
|
||||
return [
|
||||
homeBreadcrumb,
|
||||
/* TODO: insert nav breadcrumbs based on projectNavigation and application path */
|
||||
...breadcrumbs.breadcrumbs,
|
||||
];
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import type {
|
|||
ChromeProjectNavigation,
|
||||
ChromeStart,
|
||||
SideNavComponent,
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
|
@ -50,5 +52,18 @@ export interface InternalChromeStart extends ChromeStart {
|
|||
* @remarks Has no effect if the chrome style is not `project`.
|
||||
*/
|
||||
setSideNavComponent(component: SideNavComponent | null): void;
|
||||
|
||||
/**
|
||||
* Set project breadcrumbs
|
||||
*
|
||||
* @param breadcrumbs
|
||||
* @param params.absolute If true, If true, the breadcrumbs will replace the defaults, otherwise they will be appended to the default ones. false by default.
|
||||
*
|
||||
* @remarks Has no effect if the chrome style is not `project` or if setNavigation was not called
|
||||
*/
|
||||
setBreadcrumbs(
|
||||
breadcrumbs: ChromeProjectBreadcrumb[] | ChromeProjectBreadcrumb,
|
||||
params?: Partial<ChromeSetProjectBreadcrumbsParams>
|
||||
): void;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ const createStartContractMock = () => {
|
|||
project: {
|
||||
setNavigation: jest.fn(),
|
||||
setSideNavComponent: jest.fn(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
},
|
||||
};
|
||||
startContract.navLinks.getAll.mockReturnValue([]);
|
||||
|
|
|
@ -34,4 +34,6 @@ export type {
|
|||
ChromeProjectNavigationNode,
|
||||
SideNavCompProps,
|
||||
SideNavComponent,
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
} from './src';
|
||||
|
|
|
@ -34,4 +34,6 @@ export type {
|
|||
ChromeProjectNavigationLink,
|
||||
SideNavCompProps,
|
||||
SideNavComponent,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
ChromeProjectBreadcrumb,
|
||||
} from './project_navigation';
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import type { ComponentType } from 'react';
|
||||
import type { ChromeBreadcrumb } from './breadcrumb';
|
||||
import type { ChromeNavLink } from './nav_links';
|
||||
|
||||
/** @internal */
|
||||
|
@ -68,3 +69,11 @@ export interface SideNavCompProps {
|
|||
|
||||
/** @public */
|
||||
export type SideNavComponent = ComponentType<SideNavCompProps>;
|
||||
|
||||
/** @public */
|
||||
export type ChromeProjectBreadcrumb = ChromeBreadcrumb;
|
||||
|
||||
/** @public */
|
||||
export interface ChromeSetProjectBreadcrumbsParams {
|
||||
absolute: boolean;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { ServerlessPluginStart } from './types';
|
|||
const startMock = (): ServerlessPluginStart => ({
|
||||
setSideNavComponent: jest.fn(),
|
||||
setNavigation: jest.fn(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
});
|
||||
|
||||
export const serverlessMock = {
|
||||
|
|
|
@ -67,6 +67,8 @@ export class ServerlessPlugin
|
|||
(core.chrome as InternalChromeStart).project.setSideNavComponent(sideNavigationComponent),
|
||||
setNavigation: (projectNavigation) =>
|
||||
(core.chrome as InternalChromeStart).project.setNavigation(projectNavigation),
|
||||
setBreadcrumbs: (breadcrumbs, params) =>
|
||||
(core.chrome as InternalChromeStart).project.setBreadcrumbs(breadcrumbs, params),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,12 @@
|
|||
*/
|
||||
|
||||
import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
|
||||
import type { SideNavComponent, ChromeProjectNavigation } from '@kbn/core-chrome-browser';
|
||||
import type {
|
||||
SideNavComponent,
|
||||
ChromeProjectNavigation,
|
||||
ChromeProjectBreadcrumb,
|
||||
ChromeSetProjectBreadcrumbsParams,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface ServerlessPluginSetup {}
|
||||
|
@ -14,6 +19,10 @@ export interface ServerlessPluginSetup {}
|
|||
export interface ServerlessPluginStart {
|
||||
setSideNavComponent: (navigation: SideNavComponent) => void;
|
||||
setNavigation(projectNavigation: ChromeProjectNavigation): void;
|
||||
setBreadcrumbs: (
|
||||
breadcrumbs: ChromeProjectBreadcrumb | ChromeProjectBreadcrumb[],
|
||||
params?: Partial<ChromeSetProjectBreadcrumbsParams>
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface ServerlessPluginSetupDependencies {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue