mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Force last breadcrumb to be inactive (#166785)
**Related to:** https://github.com/elastic/kibana/issues/161540, https://github.com/elastic/kibana/issues/161539 ## Summary Always force the last breadcrumb to be inactive. ## Details Usual UX expects the last breadcrumb to be inactive as it represents the current page. The same can be seen from EUI [examples](https://eui.elastic.co/#/navigation/breadcrumbs). It turns out Serverless Security Solution plugin does't remove `href` and `onClick` fields from the last breadcrumb and passes it to `chrome.setBreadcrumbs()` or `serverless.setBreadcrumbs()` which renders the last breadcrumb as active but clicking on it only refreshes the page. ESS Security Solution on the other hand processes breadcrumbs currently. The same behavior may be the case for the other plungs as well. As it's much simpler to strip off undesired fields at one place instead of processing them in plugins it's done in `packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_breadcrumbs.tsx`. Security Solution codebase has been updated accordingly. A side effect of this PR is consistent ESS and Serverless breadcrumbs behavior and it will help to reuse ESS tests for Serverless.
This commit is contained in:
parent
22a9f4afb2
commit
522f577e4c
5 changed files with 43 additions and 193 deletions
|
@ -1,127 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 1`] = `
|
||||
<li
|
||||
class="euiBreadcrumb emotion-euiBreadcrumb-application-isTruncated"
|
||||
data-test-subj="euiBreadcrumb"
|
||||
>
|
||||
<span
|
||||
aria-current="page"
|
||||
class="euiBreadcrumb__content emotion-euiBreadcrumb__content-application-isTruncatedLast-onlyChild-euiTextColor-default"
|
||||
data-test-subj="breadcrumb first last"
|
||||
title="First"
|
||||
>
|
||||
First
|
||||
</span>
|
||||
</li>
|
||||
`;
|
||||
|
||||
exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 2`] = `
|
||||
<li
|
||||
class="euiBreadcrumb emotion-euiBreadcrumb-application-isTruncated"
|
||||
data-test-subj="euiBreadcrumb"
|
||||
>
|
||||
<span
|
||||
aria-current="page"
|
||||
class="euiBreadcrumb__content emotion-euiBreadcrumb__content-application-isTruncatedLast-onlyChild-euiTextColor-default"
|
||||
data-test-subj="breadcrumb first last"
|
||||
title="First"
|
||||
>
|
||||
First
|
||||
</span>
|
||||
</li>
|
||||
`;
|
||||
|
||||
exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 3`] = `
|
||||
<li
|
||||
class="euiBreadcrumb emotion-euiBreadcrumb-application-isTruncated"
|
||||
data-test-subj="euiBreadcrumb"
|
||||
>
|
||||
<span
|
||||
class="euiBreadcrumb__content emotion-euiBreadcrumb__content-application-isTruncated-firstChild-euiTextColor-subdued"
|
||||
data-test-subj="breadcrumb first"
|
||||
title="First"
|
||||
>
|
||||
First
|
||||
</span>
|
||||
</li>
|
||||
`;
|
||||
|
||||
exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 4`] = `
|
||||
<li
|
||||
class="euiBreadcrumb emotion-euiBreadcrumb-application-isTruncated"
|
||||
data-test-subj="euiBreadcrumb"
|
||||
>
|
||||
<span
|
||||
class="euiBreadcrumb__content emotion-euiBreadcrumb__content-application-isTruncated-firstChild-euiTextColor-subdued"
|
||||
data-test-subj="breadcrumb first"
|
||||
title="First"
|
||||
>
|
||||
First
|
||||
</span>
|
||||
</li>
|
||||
`;
|
||||
|
||||
exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 5`] = `
|
||||
<li
|
||||
class="euiBreadcrumb emotion-euiBreadcrumb-application-isTruncated"
|
||||
data-test-subj="euiBreadcrumb"
|
||||
>
|
||||
<span
|
||||
aria-current="page"
|
||||
class="euiBreadcrumb__content emotion-euiBreadcrumb__content-application-isTruncatedLast-lastChild-euiTextColor-default"
|
||||
data-test-subj="breadcrumb last"
|
||||
title="Second"
|
||||
>
|
||||
Second
|
||||
</span>
|
||||
</li>
|
||||
`;
|
||||
|
||||
exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 6`] = `
|
||||
<li
|
||||
class="euiBreadcrumb emotion-euiBreadcrumb-application-isTruncated"
|
||||
data-test-subj="euiBreadcrumb"
|
||||
>
|
||||
<span
|
||||
aria-current="page"
|
||||
class="euiBreadcrumb__content emotion-euiBreadcrumb__content-application-isTruncatedLast-lastChild-euiTextColor-default"
|
||||
data-test-subj="breadcrumb last"
|
||||
title="Second"
|
||||
>
|
||||
Second
|
||||
</span>
|
||||
</li>
|
||||
`;
|
||||
|
||||
exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 7`] = `
|
||||
<li
|
||||
class="euiBreadcrumb emotion-euiBreadcrumb-application-isTruncated"
|
||||
data-test-subj="euiBreadcrumb"
|
||||
>
|
||||
<span
|
||||
aria-current="page"
|
||||
class="euiBreadcrumb__content emotion-euiBreadcrumb__content-application-isTruncatedLast-onlyChild-euiTextColor-default"
|
||||
data-test-subj="breadcrumb first"
|
||||
title="First"
|
||||
>
|
||||
Kibana
|
||||
</span>
|
||||
</li>
|
||||
`;
|
||||
|
||||
exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 8`] = `
|
||||
<li
|
||||
class="euiBreadcrumb emotion-euiBreadcrumb-application-isTruncated"
|
||||
data-test-subj="euiBreadcrumb"
|
||||
>
|
||||
<span
|
||||
aria-current="page"
|
||||
class="euiBreadcrumb__content emotion-euiBreadcrumb__content-application-isTruncatedLast-onlyChild-euiTextColor-default"
|
||||
data-test-subj="breadcrumb first"
|
||||
title="First"
|
||||
>
|
||||
Kibana
|
||||
</span>
|
||||
</li>
|
||||
`;
|
|
@ -6,24 +6,40 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
import '@testing-library/jest-dom';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { BehaviorSubject, of } from 'rxjs';
|
||||
import { HeaderBreadcrumbs } from './header_breadcrumbs';
|
||||
|
||||
describe('HeaderBreadcrumbs', () => {
|
||||
it('renders updates to the breadcrumbs$ observable', () => {
|
||||
it('renders updates to the breadcrumbs$ observable', async () => {
|
||||
const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]);
|
||||
const wrapper = mount(<HeaderBreadcrumbs breadcrumbs$={breadcrumbs$} />);
|
||||
wrapper.find('.euiBreadcrumb').forEach((el) => expect(el.render()).toMatchSnapshot());
|
||||
|
||||
render(<HeaderBreadcrumbs breadcrumbs$={breadcrumbs$} />);
|
||||
|
||||
expect(await screen.findByLabelText('Breadcrumbs')).toHaveTextContent('First');
|
||||
|
||||
act(() => breadcrumbs$.next([{ text: 'First' }, { text: 'Second' }]));
|
||||
wrapper.update();
|
||||
wrapper.find('.euiBreadcrumb').forEach((el) => expect(el.render()).toMatchSnapshot());
|
||||
|
||||
expect(await screen.findByLabelText('Breadcrumbs')).toHaveTextContent('FirstSecond');
|
||||
|
||||
act(() => breadcrumbs$.next([]));
|
||||
wrapper.update();
|
||||
wrapper.find('.euiBreadcrumb').forEach((el) => expect(el.render()).toMatchSnapshot());
|
||||
|
||||
expect(await screen.findByLabelText('Breadcrumbs')).toHaveTextContent('Kibana');
|
||||
});
|
||||
|
||||
it('forces the last breadcrumb inactivity', async () => {
|
||||
const breadcrumbs$ = of([
|
||||
{ text: 'First' },
|
||||
{ text: 'Last', href: '/something', onClick: jest.fn() },
|
||||
]);
|
||||
|
||||
render(<HeaderBreadcrumbs breadcrumbs$={breadcrumbs$} />);
|
||||
|
||||
const lastBreadcrumb = await screen.findByTitle('Last');
|
||||
|
||||
expect(lastBreadcrumb).not.toHaveAttribute('href');
|
||||
expect(lastBreadcrumb.tagName).not.toBe('a');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,15 +25,21 @@ export function HeaderBreadcrumbs({ breadcrumbs$ }: Props) {
|
|||
crumbs = [{ text: 'Kibana' }];
|
||||
}
|
||||
|
||||
crumbs = crumbs.map((breadcrumb, i) => ({
|
||||
...breadcrumb,
|
||||
'data-test-subj': classNames(
|
||||
'breadcrumb',
|
||||
breadcrumb['data-test-subj'],
|
||||
i === 0 && 'first',
|
||||
i === breadcrumbs.length - 1 && 'last'
|
||||
),
|
||||
}));
|
||||
crumbs = crumbs.map((breadcrumb, i) => {
|
||||
const isLast = i === breadcrumbs.length - 1;
|
||||
|
||||
return {
|
||||
...breadcrumb,
|
||||
href: isLast ? undefined : breadcrumb.href,
|
||||
onClick: isLast ? undefined : breadcrumb.onClick,
|
||||
'data-test-subj': classNames(
|
||||
'breadcrumb',
|
||||
breadcrumb['data-test-subj'],
|
||||
i === 0 && 'first',
|
||||
isLast && 'last'
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return <EuiHeaderBreadcrumbs breadcrumbs={crumbs} max={10} data-test-subj="breadcrumbs" />;
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ChromeBreadcrumb } from '@kbn/core/public';
|
||||
import { emptyLastBreadcrumbUrl } from './breadcrumbs';
|
||||
|
||||
describe('emptyLastBreadcrumbUrl', () => {
|
||||
it('should empty the URL and onClick function of the last breadcrumb', () => {
|
||||
const breadcrumbs: ChromeBreadcrumb[] = [
|
||||
{ text: 'Home', href: '/home', onClick: () => {} },
|
||||
{ text: 'Breadcrumb 1', href: '/bc1', onClick: () => {} },
|
||||
{ text: 'Last Breadcrumbs', href: '/last_bc', onClick: () => {} },
|
||||
];
|
||||
|
||||
const expectedBreadcrumbs = [
|
||||
{ text: 'Home', href: '/home', onClick: breadcrumbs[0].onClick },
|
||||
{ text: 'Breadcrumb 1', href: '/bc1', onClick: breadcrumbs[1].onClick },
|
||||
{ text: 'Last Breadcrumbs', href: '', onClick: undefined },
|
||||
];
|
||||
|
||||
expect(emptyLastBreadcrumbUrl(breadcrumbs)).toEqual(expectedBreadcrumbs);
|
||||
});
|
||||
|
||||
it('should return the original breadcrumbs if the input is empty', () => {
|
||||
const emptyBreadcrumbs: ChromeBreadcrumb[] = [];
|
||||
|
||||
expect(emptyLastBreadcrumbUrl(emptyBreadcrumbs)).toEqual(emptyBreadcrumbs);
|
||||
});
|
||||
});
|
|
@ -5,23 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ChromeBreadcrumb } from '@kbn/core/public';
|
||||
import type { Services } from '../common/services';
|
||||
|
||||
export const subscribeBreadcrumbs = (services: Services) => {
|
||||
const { chrome, securitySolution } = services;
|
||||
const { securitySolution, chrome } = services;
|
||||
securitySolution.getBreadcrumbsNav$().subscribe((breadcrumbsNav) => {
|
||||
const breadcrumbs = [...breadcrumbsNav.leading, ...breadcrumbsNav.trailing];
|
||||
if (breadcrumbs.length > 0) {
|
||||
chrome.setBreadcrumbs(emptyLastBreadcrumbUrl(breadcrumbs));
|
||||
}
|
||||
chrome.setBreadcrumbs([...breadcrumbsNav.leading, ...breadcrumbsNav.trailing]);
|
||||
});
|
||||
};
|
||||
|
||||
export const emptyLastBreadcrumbUrl = (breadcrumbs: ChromeBreadcrumb[]) => {
|
||||
const lastBreadcrumb = breadcrumbs[breadcrumbs.length - 1];
|
||||
if (lastBreadcrumb) {
|
||||
return [...breadcrumbs.slice(0, -1), { ...lastBreadcrumb, href: '', onClick: undefined }];
|
||||
}
|
||||
return breadcrumbs;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue