diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md
new file mode 100644
index 0000000000000..dfe25c5c9e42d
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeStart](./kibana-plugin-core-public.chromestart.md) > [getBreadcrumbsAppendExtension$](./kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md)
+
+## ChromeStart.getBreadcrumbsAppendExtension$() method
+
+Get an observable of the current extension appended to breadcrumbs
+
+Signature:
+
+```typescript
+getBreadcrumbsAppendExtension$(): Observable;
+```
+Returns:
+
+`Observable`
+
diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.md
index 2594848ef0847..663b326193de5 100644
--- a/docs/development/core/public/kibana-plugin-core-public.chromestart.md
+++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.md
@@ -55,6 +55,7 @@ core.chrome.setHelpExtension(elem => {
| [getBadge$()](./kibana-plugin-core-public.chromestart.getbadge_.md) | Get an observable of the current badge |
| [getBrand$()](./kibana-plugin-core-public.chromestart.getbrand_.md) | Get an observable of the current brand information. |
| [getBreadcrumbs$()](./kibana-plugin-core-public.chromestart.getbreadcrumbs_.md) | Get an observable of the current list of breadcrumbs |
+| [getBreadcrumbsAppendExtension$()](./kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md) | Get an observable of the current extension appended to breadcrumbs |
| [getCustomNavLink$()](./kibana-plugin-core-public.chromestart.getcustomnavlink_.md) | Get an observable of the current custom nav link |
| [getHelpExtension$()](./kibana-plugin-core-public.chromestart.gethelpextension_.md) | Get an observable of the current custom help conttent |
| [getIsNavDrawerLocked$()](./kibana-plugin-core-public.chromestart.getisnavdrawerlocked_.md) | Get an observable of the current locked state of the nav drawer. |
@@ -64,6 +65,7 @@ core.chrome.setHelpExtension(elem => {
| [setBadge(badge)](./kibana-plugin-core-public.chromestart.setbadge.md) | Override the current badge |
| [setBrand(brand)](./kibana-plugin-core-public.chromestart.setbrand.md) | Set the brand configuration. |
| [setBreadcrumbs(newBreadcrumbs)](./kibana-plugin-core-public.chromestart.setbreadcrumbs.md) | Override the current set of breadcrumbs |
+| [setBreadcrumbsAppendExtension(breadcrumbsAppendExtension)](./kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md) | Mount an element next to the last breadcrumb |
| [setCustomNavLink(newCustomNavLink)](./kibana-plugin-core-public.chromestart.setcustomnavlink.md) | Override the current set of custom nav link |
| [setHelpExtension(helpExtension)](./kibana-plugin-core-public.chromestart.sethelpextension.md) | Override the current set of custom help content |
| [setHelpSupportUrl(url)](./kibana-plugin-core-public.chromestart.sethelpsupporturl.md) | Override the default support URL shown in the help menu |
diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md
new file mode 100644
index 0000000000000..02adb9b4d325d
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeStart](./kibana-plugin-core-public.chromestart.md) > [setBreadcrumbsAppendExtension](./kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md)
+
+## ChromeStart.setBreadcrumbsAppendExtension() method
+
+Mount an element next to the last breadcrumb
+
+Signature:
+
+```typescript
+setBreadcrumbsAppendExtension(breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension): void;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| breadcrumbsAppendExtension | ChromeBreadcrumbsAppendExtension
| |
+
+Returns:
+
+`void`
+
diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts
index cbcd23615d34c..6df8d57c8c574 100644
--- a/src/core/public/chrome/chrome_service.mock.ts
+++ b/src/core/public/chrome/chrome_service.mock.ts
@@ -63,6 +63,8 @@ const createStartContractMock = () => {
setBadge: jest.fn(),
getBreadcrumbs$: jest.fn(),
setBreadcrumbs: jest.fn(),
+ getBreadcrumbsAppendExtension$: jest.fn(),
+ setBreadcrumbsAppendExtension: jest.fn(),
getHelpExtension$: jest.fn(),
setHelpExtension: jest.fn(),
setHelpSupportUrl: jest.fn(),
@@ -76,6 +78,7 @@ const createStartContractMock = () => {
startContract.getApplicationClasses$.mockReturnValue(new BehaviorSubject(['class-name']));
startContract.getBadge$.mockReturnValue(new BehaviorSubject({} as ChromeBadge));
startContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as ChromeBreadcrumb]));
+ startContract.getBreadcrumbsAppendExtension$.mockReturnValue(new BehaviorSubject(undefined));
startContract.getCustomNavLink$.mockReturnValue(new BehaviorSubject(undefined));
startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined));
startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false));
diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts
index 0150554a60906..3783f3bd9b65e 100644
--- a/src/core/public/chrome/chrome_service.test.ts
+++ b/src/core/public/chrome/chrome_service.test.ts
@@ -363,6 +363,25 @@ describe('start', () => {
});
});
+ describe('breadcrumbsAppendExtension$', () => {
+ it('updates the breadcrumbsAppendExtension$', async () => {
+ const { chrome, service } = await start();
+ const promise = chrome.getBreadcrumbsAppendExtension$().pipe(toArray()).toPromise();
+
+ chrome.setBreadcrumbsAppendExtension({ content: (element) => () => {} });
+ service.stop();
+
+ await expect(promise).resolves.toMatchInlineSnapshot(`
+ Array [
+ undefined,
+ Object {
+ "content": [Function],
+ },
+ ]
+ `);
+ });
+ });
+
describe('custom nav link', () => {
it('updates/emits the current custom nav link', async () => {
const { chrome, service } = await start();
@@ -429,33 +448,33 @@ describe('start', () => {
expect(docTitleResetSpy).toBeCalledTimes(1);
await expect(promises).resolves.toMatchInlineSnapshot(`
- Array [
- Array [
- undefined,
- Object {
- "appName": "App name",
- },
- undefined,
- ],
- Array [
- Array [],
- Array [
- Object {
- "text": "App breadcrumb",
- },
- ],
- Array [],
- ],
- Array [
- undefined,
- Object {
- "text": "App badge",
- "tooltip": "App tooltip",
- },
- undefined,
- ],
- ]
- `);
+ Array [
+ Array [
+ undefined,
+ Object {
+ "appName": "App name",
+ },
+ undefined,
+ ],
+ Array [
+ Array [],
+ Array [
+ Object {
+ "text": "App breadcrumb",
+ },
+ ],
+ Array [],
+ ],
+ Array [
+ undefined,
+ Object {
+ "text": "App badge",
+ "tooltip": "App tooltip",
+ },
+ undefined,
+ ],
+ ]
+ `);
});
});
});
diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx
index b01f120b81305..b39c83498859c 100644
--- a/src/core/public/chrome/chrome_service.tsx
+++ b/src/core/public/chrome/chrome_service.tsx
@@ -24,6 +24,7 @@ import { BehaviorSubject, combineLatest, merge, Observable, of, ReplaySubject }
import { flatMap, map, takeUntil } from 'rxjs/operators';
import { parse } from 'url';
import { EuiLink } from '@elastic/eui';
+import { MountPoint } from '../types';
import { mountReactNode } from '../utils/mount';
import { InternalApplicationStart } from '../application';
import { DocLinksStart } from '../doc_links';
@@ -58,6 +59,11 @@ export interface ChromeBrand {
/** @public */
export type ChromeBreadcrumb = EuiBreadcrumb;
+/** @public */
+export interface ChromeBreadcrumbsAppendExtension {
+ content: MountPoint;
+}
+
/** @public */
export interface ChromeHelpExtension {
/**
@@ -146,6 +152,9 @@ export class ChromeService {
const applicationClasses$ = new BehaviorSubject>(new Set());
const helpExtension$ = new BehaviorSubject(undefined);
const breadcrumbs$ = new BehaviorSubject([]);
+ const breadcrumbsAppendExtension$ = new BehaviorSubject<
+ ChromeBreadcrumbsAppendExtension | undefined
+ >(undefined);
const badge$ = new BehaviorSubject(undefined);
const customNavLink$ = new BehaviorSubject(undefined);
const helpSupportUrl$ = new BehaviorSubject(KIBANA_ASK_ELASTIC_LINK);
@@ -225,6 +234,7 @@ export class ChromeService {
badge$={badge$.pipe(takeUntil(this.stop$))}
basePath={http.basePath}
breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))}
+ breadcrumbsAppendExtension$={breadcrumbsAppendExtension$.pipe(takeUntil(this.stop$))}
customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))}
kibanaDocLink={docLinks.links.kibana}
forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()}
@@ -290,6 +300,14 @@ export class ChromeService {
breadcrumbs$.next(newBreadcrumbs);
},
+ getBreadcrumbsAppendExtension$: () => breadcrumbsAppendExtension$.pipe(takeUntil(this.stop$)),
+
+ setBreadcrumbsAppendExtension: (
+ breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension
+ ) => {
+ breadcrumbsAppendExtension$.next(breadcrumbsAppendExtension);
+ },
+
getHelpExtension$: () => helpExtension$.pipe(takeUntil(this.stop$)),
setHelpExtension: (helpExtension?: ChromeHelpExtension) => {
@@ -431,6 +449,18 @@ export interface ChromeStart {
*/
setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void;
+ /**
+ * Get an observable of the current extension appended to breadcrumbs
+ */
+ getBreadcrumbsAppendExtension$(): Observable;
+
+ /**
+ * Mount an element next to the last breadcrumb
+ */
+ setBreadcrumbsAppendExtension(
+ breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension
+ ): void;
+
/**
* Get an observable of the current custom nav link
*/
diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
index 34c5f213a7221..ee2fcbd5078af 100644
--- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
+++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap
@@ -300,6 +300,55 @@ exports[`Header renders 1`] = `
"thrownError": null,
}
}
+ breadcrumbsAppendExtension$={
+ BehaviorSubject {
+ "_isScalar": false,
+ "_value": undefined,
+ "closed": false,
+ "hasError": false,
+ "isStopped": false,
+ "observers": Array [
+ Subscriber {
+ "_parentOrParents": null,
+ "_subscriptions": Array [
+ SubjectSubscription {
+ "_parentOrParents": [Circular],
+ "_subscriptions": null,
+ "closed": false,
+ "subject": [Circular],
+ "subscriber": [Circular],
+ },
+ ],
+ "closed": false,
+ "destination": SafeSubscriber {
+ "_complete": undefined,
+ "_context": [Circular],
+ "_error": undefined,
+ "_next": [Function],
+ "_parentOrParents": null,
+ "_parentSubscriber": [Circular],
+ "_subscriptions": null,
+ "closed": false,
+ "destination": Object {
+ "closed": true,
+ "complete": [Function],
+ "error": [Function],
+ "next": [Function],
+ },
+ "isStopped": false,
+ "syncErrorThrowable": false,
+ "syncErrorThrown": false,
+ "syncErrorValue": null,
+ },
+ "isStopped": false,
+ "syncErrorThrowable": true,
+ "syncErrorThrown": false,
+ "syncErrorValue": null,
+ },
+ ],
+ "thrownError": null,
+ }
+ }
customNavLink$={
BehaviorSubject {
"_isScalar": false,
@@ -5029,6 +5078,55 @@ exports[`Header renders 1`] = `
"thrownError": null,
}
}
+ breadcrumbsAppendExtension$={
+ BehaviorSubject {
+ "_isScalar": false,
+ "_value": undefined,
+ "closed": false,
+ "hasError": false,
+ "isStopped": false,
+ "observers": Array [
+ Subscriber {
+ "_parentOrParents": null,
+ "_subscriptions": Array [
+ SubjectSubscription {
+ "_parentOrParents": [Circular],
+ "_subscriptions": null,
+ "closed": false,
+ "subject": [Circular],
+ "subscriber": [Circular],
+ },
+ ],
+ "closed": false,
+ "destination": SafeSubscriber {
+ "_complete": undefined,
+ "_context": [Circular],
+ "_error": undefined,
+ "_next": [Function],
+ "_parentOrParents": null,
+ "_parentSubscriber": [Circular],
+ "_subscriptions": null,
+ "closed": false,
+ "destination": Object {
+ "closed": true,
+ "complete": [Function],
+ "error": [Function],
+ "next": [Function],
+ },
+ "isStopped": false,
+ "syncErrorThrowable": false,
+ "syncErrorThrown": false,
+ "syncErrorValue": null,
+ },
+ "isStopped": false,
+ "syncErrorThrowable": true,
+ "syncErrorThrown": false,
+ "syncErrorValue": null,
+ },
+ ],
+ "thrownError": null,
+ }
+ }
>
;
badge$: Observable;
breadcrumbs$: Observable;
+ breadcrumbsAppendExtension$: Observable;
customNavLink$: Observable;
homeHref: string;
isVisible$: Observable;
@@ -169,6 +170,7 @@ export function Header({
diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx
index 7fe2c91087090..64401171d142a 100644
--- a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx
+++ b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx
@@ -22,12 +22,17 @@ import React from 'react';
import { act } from 'react-dom/test-utils';
import { BehaviorSubject } from 'rxjs';
import { HeaderBreadcrumbs } from './header_breadcrumbs';
+import { ChromeBreadcrumbsAppendExtension } from '../../chrome_service';
describe('HeaderBreadcrumbs', () => {
it('renders updates to the breadcrumbs$ observable', () => {
const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]);
const wrapper = mount(
-
+
);
expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot();
@@ -39,4 +44,29 @@ describe('HeaderBreadcrumbs', () => {
wrapper.update();
expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot();
});
+
+ it('renders breadcrumbs extension', () => {
+ const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]);
+ const breadcrumbsAppendExtension$ = new BehaviorSubject<
+ undefined | ChromeBreadcrumbsAppendExtension
+ >({
+ content: (root: HTMLDivElement) => {
+ root.innerHTML = '__render__
';
+ return () => (root.innerHTML = '');
+ },
+ });
+
+ const wrapper = mount(
+
+ );
+
+ expect(wrapper.find('.euiBreadcrumb').getDOMNode().querySelector('my-extension')).toBeDefined();
+ act(() => breadcrumbsAppendExtension$.next(undefined));
+ wrapper.update();
+ expect(wrapper.find('.euiBreadcrumb').getDOMNode().querySelector('my-extension')).toBeNull();
+ });
});
diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx
index 52412f8990c7a..d52faa87cfecd 100644
--- a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx
+++ b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx
@@ -22,16 +22,19 @@ import classNames from 'classnames';
import React from 'react';
import useObservable from 'react-use/lib/useObservable';
import { Observable } from 'rxjs';
-import { ChromeBreadcrumb } from '../../chrome_service';
+import { ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension } from '../../chrome_service';
+import { HeaderExtension } from './header_extension';
interface Props {
appTitle$: Observable;
breadcrumbs$: Observable;
+ breadcrumbsAppendExtension$: Observable;
}
-export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) {
+export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$, breadcrumbsAppendExtension$ }: Props) {
const appTitle = useObservable(appTitle$, 'Kibana');
const breadcrumbs = useObservable(breadcrumbs$, []);
+ const breadcrumbsAppendExtension = useObservable(breadcrumbsAppendExtension$);
let crumbs = breadcrumbs;
if (breadcrumbs.length === 0 && appTitle) {
@@ -48,5 +51,15 @@ export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) {
),
}));
+ if (breadcrumbsAppendExtension) {
+ const lastCrumb = crumbs[crumbs.length - 1];
+ lastCrumb.text = (
+ <>
+ {lastCrumb.text}
+
+ >
+ );
+ }
+
return ;
}
diff --git a/src/core/public/chrome/ui/header/header_extension.test.tsx b/src/core/public/chrome/ui/header/header_extension.test.tsx
index 3d5678b8bb7ef..ba00c74b81cfa 100644
--- a/src/core/public/chrome/ui/header/header_extension.test.tsx
+++ b/src/core/public/chrome/ui/header/header_extension.test.tsx
@@ -32,6 +32,14 @@ describe('HeaderExtension', () => {
expect(divNode).toBeInstanceOf(HTMLElement);
});
+ it('calls navControl.render with div node as inlineBlock', () => {
+ const renderSpy = jest.fn();
+ mount();
+
+ const [divNode] = renderSpy.mock.calls[0];
+ expect(divNode).toHaveAttribute('style', 'display: inline-block;');
+ });
+
it('calls unrender callback when unmounted', () => {
const unrenderSpy = jest.fn();
const render = () => unrenderSpy;
diff --git a/src/core/public/chrome/ui/header/header_extension.tsx b/src/core/public/chrome/ui/header/header_extension.tsx
index 76413a0ea0317..97cf38f44c3f1 100644
--- a/src/core/public/chrome/ui/header/header_extension.tsx
+++ b/src/core/public/chrome/ui/header/header_extension.tsx
@@ -22,6 +22,7 @@ import { MountPoint } from '../../../types';
interface Props {
extension?: MountPoint;
+ display?: 'block' | 'inlineBlock';
}
export class HeaderExtension extends React.Component {
@@ -46,7 +47,12 @@ export class HeaderExtension extends React.Component {
}
public render() {
- return ;
+ return (
+
+ );
}
private renderExtension() {
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index f52d5b6fbd6a5..4babec38a936e 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -343,6 +343,8 @@ export interface ChromeStart {
getBadge$(): Observable;
getBrand$(): Observable;
getBreadcrumbs$(): Observable;
+ // Warning: (ae-forgotten-export) The symbol "ChromeBreadcrumbsAppendExtension" needs to be exported by the entry point index.d.ts
+ getBreadcrumbsAppendExtension$(): Observable;
getCustomNavLink$(): Observable | undefined>;
getHelpExtension$(): Observable;
getIsNavDrawerLocked$(): Observable;
@@ -355,6 +357,7 @@ export interface ChromeStart {
setBadge(badge?: ChromeBadge): void;
setBrand(brand: ChromeBrand): void;
setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void;
+ setBreadcrumbsAppendExtension(breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension): void;
setCustomNavLink(newCustomNavLink?: Partial): void;
setHelpExtension(helpExtension?: ChromeHelpExtension): void;
setHelpSupportUrl(url: string): void;