Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ export const chromeStyles = (euiTheme: UseEuiTheme['euiTheme']) => css`

.header__breadcrumbsWithExtensionContainer {
overflow: hidden; // enables text-ellipsis in the last breadcrumb
.euiHeaderBreadcrumbs {
.euiHeaderBreadcrumbs,
.euiBreadcrumbs {
// stop breadcrumbs from growing.
// this makes the extension appear right next to the last breadcrumb
flex-grow: 0;
Expand All @@ -147,7 +148,7 @@ export const chromeStyles = (euiTheme: UseEuiTheme['euiTheme']) => css`
overflow: hidden; // enables text-ellipsis in the last breadcrumb
}
}
.header__breadcrumbsAppendExtension {
.header__breadcrumbsAppendExtension--last {
flex-grow: 1;
}
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -492,21 +492,70 @@ describe('start', () => {
describe('breadcrumbsAppendExtension$', () => {
it('updates the breadcrumbsAppendExtension$', async () => {
const { chrome, service } = await start();
const promise = chrome.getBreadcrumbsAppendExtension$().pipe(toArray()).toPromise();
const promise = chrome.getBreadcrumbsAppendExtensions$().pipe(toArray()).toPromise();

const ext1 = chrome.setBreadcrumbsAppendExtension({
content: () => () => {},
});
chrome.setBreadcrumbsAppendExtension({
order: 0,
content: () => () => {},
});
const ext3 = chrome.setBreadcrumbsAppendExtension({
order: 100,
content: () => () => {},
});
ext3();
ext1();
service.stop();

await expect(promise).resolves.toMatchInlineSnapshot(`
Array [
undefined,
Object {
"content": [Function],
},
]
`);
Array [
Array [],
Array [
Object {
"content": [Function],
},
],
Array [
Object {
"content": [Function],
"order": 0,
},
Object {
"content": [Function],
},
],
Array [
Object {
"content": [Function],
"order": 0,
},
Object {
"content": [Function],
},
Object {
"content": [Function],
"order": 100,
},
],
Array [
Object {
"content": [Function],
"order": 0,
},
Object {
"content": [Function],
},
],
Array [
Object {
"content": [Function],
"order": 0,
},
],
]
`);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,9 @@ export class ChromeService {
);
const helpExtension$ = new BehaviorSubject<ChromeHelpExtension | undefined>(undefined);
const breadcrumbs$ = new BehaviorSubject<ChromeBreadcrumb[]>([]);
const breadcrumbsAppendExtension$ = new BehaviorSubject<
ChromeBreadcrumbsAppendExtension | undefined
>(undefined);
const breadcrumbsAppendExtensions$ = new BehaviorSubject<ChromeBreadcrumbsAppendExtension[]>(
[]
);
const badge$ = new BehaviorSubject<ChromeBadge | undefined>(undefined);
const customNavLink$ = new BehaviorSubject<ChromeNavLink | undefined>(undefined);
const helpSupportUrl$ = new BehaviorSubject<string>(docLinks.links.kibana.askElastic);
Expand Down Expand Up @@ -467,6 +467,9 @@ export class ChromeService {
globalHelpExtensionMenuLinks$={globalHelpExtensionMenuLinks$}
actionMenu$={application.currentActionMenu$}
breadcrumbs$={currentProjectBreadcrumbs$}
breadcrumbsAppendExtensions$={breadcrumbsAppendExtensions$.pipe(
takeUntil(this.stop$)
)}
customBranding$={customBranding$}
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
Expand Down Expand Up @@ -500,7 +503,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$))}
breadcrumbsAppendExtensions$={breadcrumbsAppendExtensions$.pipe(takeUntil(this.stop$))}
customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))}
kibanaDocLink={docLinks.links.kibana.guide}
docLinks={docLinks}
Expand Down Expand Up @@ -548,12 +551,24 @@ export class ChromeService {

setBreadcrumbs: setClassicBreadcrumbs,

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

setBreadcrumbsAppendExtension: (
breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension
breadcrumbsAppendExtension: ChromeBreadcrumbsAppendExtension
) => {
breadcrumbsAppendExtension$.next(breadcrumbsAppendExtension);
breadcrumbsAppendExtensions$.next(
[...breadcrumbsAppendExtensions$.getValue(), breadcrumbsAppendExtension].sort(
({ order: orderA = 50 }, { order: orderB = 50 }) => orderA - orderB
)
);
return () => {
breadcrumbsAppendExtensions$.next(
breadcrumbsAppendExtensions$
.getValue()
.filter((ext) => ext !== breadcrumbsAppendExtension)
);
};
},

getGlobalHelpExtensionMenuLinks$: () => globalHelpExtensionMenuLinks$.asObservable(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React, { PropsWithChildren } from 'react';
import { Observable } from 'rxjs';
import type { ChromeBreadcrumbsAppendExtension } from '@kbn/core-chrome-browser';
import useObservable from 'react-use/lib/useObservable';
import { EuiFlexGroup } from '@elastic/eui';
import classnames from 'classnames';
import { HeaderExtension } from './header_extension';

export interface Props {
breadcrumbsAppendExtensions$: Observable<ChromeBreadcrumbsAppendExtension[]>;
}

export const BreadcrumbsWithExtensionsWrapper = ({
breadcrumbsAppendExtensions$,
children,
}: PropsWithChildren<Props>) => {
const breadcrumbsAppendExtensions = useObservable(breadcrumbsAppendExtensions$, []);

return breadcrumbsAppendExtensions.length === 0 ? (
<>{children}</>
) : (
<EuiFlexGroup
responsive={false}
wrap={false}
alignItems={'center'}
className={'header__breadcrumbsWithExtensionContainer'}
gutterSize={'none'}
>
{children}
{breadcrumbsAppendExtensions.map((breadcrumbsAppendExtension, index) => {
const isLast = breadcrumbsAppendExtensions.length - 1 === index;
return (
<HeaderExtension
key={index}
extension={breadcrumbsAppendExtension.content}
containerClassName={classnames({
'header__breadcrumbsAppendExtension--last': isLast,
})}
/>
);
})}
</EuiFlexGroup>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ describe('Header', () => {
const recentlyAccessed$ = new BehaviorSubject([
{ link: '', label: 'dashboard', id: 'dashboard' },
]);
const breadcrumbsAppendExtension$ = new BehaviorSubject<
undefined | ChromeBreadcrumbsAppendExtension
>(undefined);
const breadcrumbsAppendExtensions$ = new BehaviorSubject<ChromeBreadcrumbsAppendExtension[]>(
[]
);
const component = mountWithIntl(
<Header
{...mockProps()}
Expand All @@ -93,7 +93,7 @@ describe('Header', () => {
recentlyAccessed$={recentlyAccessed$}
isLocked$={isLocked$}
customNavLink$={customNavLink$}
breadcrumbsAppendExtension$={breadcrumbsAppendExtension$}
breadcrumbsAppendExtensions$={breadcrumbsAppendExtensions$}
headerBanner$={headerBanner$}
helpMenuLinks$={of([])}
isServerless={false}
Expand All @@ -108,17 +108,28 @@ describe('Header', () => {
expect(component.render()).toMatchSnapshot();

act(() =>
breadcrumbsAppendExtension$.next({
content: (root: HTMLDivElement) => {
root.innerHTML = '<div class="my-extension">__render__</div>';
return () => (root.innerHTML = '');
breadcrumbsAppendExtensions$.next([
{
content: (root: HTMLDivElement) => {
root.innerHTML = '<div class="my-extension1">__render__</div>';
return () => (root.innerHTML = '');
},
},
{
content: (root: HTMLDivElement) => {
root.innerHTML = '<div class="my-extension2">__render__</div>';
return () => (root.innerHTML = '');
},
},
})
])
);
component.update();
expect(component.find('HeaderExtension').exists()).toBeTruthy();
expect(component.find('HeaderExtension').length).toBe(2);
expect(
component.find('HeaderExtension').at(0).getDOMNode().querySelector('.my-extension1')
).toBeTruthy();
expect(
component.find('HeaderExtension').getDOMNode().querySelector('.my-extension')
component.find('HeaderExtension').at(1).getDOMNode().querySelector('.my-extension2')
).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/

import {
EuiFlexGroup,
EuiHeader,
EuiHeaderSection,
EuiHeaderSectionItem,
Expand All @@ -19,7 +18,6 @@ import {
import { i18n } from '@kbn/i18n';
import classnames from 'classnames';
import React, { createRef, useState } from 'react';
import useObservable from 'react-use/lib/useObservable';
import type { Observable } from 'rxjs';
import type { HttpStart } from '@kbn/core-http-browser';
import type { InternalApplicationStart } from '@kbn/core-application-browser-internal';
Expand All @@ -45,7 +43,7 @@ import { HeaderHelpMenu } from './header_help_menu';
import { HeaderLogo } from './header_logo';
import { HeaderNavControls } from './header_nav_controls';
import { HeaderActionMenu, useHeaderActionMenuMounter } from './header_action_menu';
import { HeaderExtension } from './header_extension';
import { BreadcrumbsWithExtensionsWrapper } from './breadcrumbs_with_extensions';
import { HeaderTopBanner } from './header_top_banner';
import { HeaderMenuButton } from './header_menu_button';
import { ScreenReaderRouteAnnouncements, SkipToMainContent } from './screen_reader_a11y';
Expand All @@ -56,7 +54,7 @@ export interface HeaderProps {
headerBanner$: Observable<ChromeUserBanner | undefined>;
badge$: Observable<ChromeBadge | undefined>;
breadcrumbs$: Observable<ChromeBreadcrumb[]>;
breadcrumbsAppendExtension$: Observable<ChromeBreadcrumbsAppendExtension | undefined>;
breadcrumbsAppendExtensions$: Observable<ChromeBreadcrumbsAppendExtension[]>;
customNavLink$: Observable<ChromeNavLink | undefined>;
homeHref: string;
kibanaDocLink: string;
Expand Down Expand Up @@ -88,15 +86,14 @@ export function Header({
basePath,
onIsLockedUpdate,
homeHref,
breadcrumbsAppendExtension$,
breadcrumbsAppendExtensions$,
globalHelpExtensionMenuLinks$,
customBranding$,
isServerless,
...observables
}: HeaderProps) {
const [isNavOpen, setIsNavOpen] = useState(false);
const [navId] = useState(htmlIdGenerator()());
const breadcrumbsAppendExtension = useObservable(breadcrumbsAppendExtension$);
const headerActionMenuMounter = useHeaderActionMenuMounter(application.currentActionMenu$);

const toggleCollapsibleNavRef = createRef<HTMLButtonElement & { euiAnimate: () => void }>();
Expand Down Expand Up @@ -206,24 +203,11 @@ export function Header({

<HeaderNavControls side="left" navControls$={observables.navControlsLeft$} />
</EuiHeaderSection>

{!breadcrumbsAppendExtension ? (
Breadcrumbs
) : (
<EuiFlexGroup
responsive={false}
wrap={false}
alignItems={'center'}
className={'header__breadcrumbsWithExtensionContainer'}
gutterSize={'none'}
>
{Breadcrumbs}
<HeaderExtension
extension={breadcrumbsAppendExtension.content}
containerClassName={'header__breadcrumbsAppendExtension'}
/>
</EuiFlexGroup>
)}
<BreadcrumbsWithExtensionsWrapper
breadcrumbsAppendExtensions$={breadcrumbsAppendExtensions$}
>
{Breadcrumbs}
</BreadcrumbsWithExtensionsWrapper>

<HeaderBadge badge$={observables.badge$} />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('Header', () => {
const mockProps: Omit<ProjectHeaderProps, 'children'> = {
application: mockApplication,
breadcrumbs$: Rx.of([]),
breadcrumbsAppendExtensions$: Rx.of([]),
actionMenu$: Rx.of(undefined),
docLinks: docLinksServiceMock.createStartContract(),
globalHelpExtensionMenuLinks$: Rx.of([]),
Expand Down
Loading