[Breadcrumbs] Hide "deployment" in breadcrumb when on-prem (#220110)

## Summary

Closes #219869

**Before**

![image](https://github.com/user-attachments/assets/59f0f6fc-2113-44ee-8e36-69c4eb718fad)


**After**
<img width="1320" alt="image"
src="https://github.com/user-attachments/assets/85ad1117-4aab-4a8d-807c-4fa90cd8b917"
/>


### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Tim Sullivan 2025-05-06 12:43:47 -07:00 committed by GitHub
parent 0712aa6ab8
commit 0acd88e3b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 185 additions and 51 deletions

View file

@ -462,6 +462,9 @@ describe('start', () => {
it('allows the project breadcrumb to also be set', async () => {
const { chrome } = await start();
chrome.project.setCloudUrls({
deploymentUrl: 'my-deployment-url.com',
});
chrome.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }]); // only setting the classic breadcrumbs
{

View file

@ -0,0 +1,126 @@
/*
* 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 type {
ChromeBreadcrumb,
ChromeProjectNavigationNode,
CloudLinks,
} from '@kbn/core-chrome-browser/src';
import { buildBreadcrumbs } from './breadcrumbs';
describe('buildBreadcrumbs', () => {
const mockCloudLinks = {
deployment: { href: '/deployment' },
deployments: { href: '/deployments', title: 'All Deployments' },
} as CloudLinks;
it('returns breadcrumbs with root crumb when absolute is true', () => {
const projectBreadcrumbs = {
breadcrumbs: [{ text: 'Project Crumb', href: '/project' }],
params: { absolute: true },
};
const result = buildBreadcrumbs({
projectName: 'Test Project',
cloudLinks: mockCloudLinks,
projectBreadcrumbs,
activeNodes: [],
chromeBreadcrumbs: [],
isServerless: true,
});
expect(result).toEqual([
expect.objectContaining({
text: 'Test Project',
}),
...projectBreadcrumbs.breadcrumbs,
]);
});
it('builds breadcrumbs from activeNodes when no project breadcrumbs are set', () => {
const activeNodes = [
[
{ title: 'Node 1', breadcrumbStatus: 'visible', href: '/node1' },
{ title: 'Node 2', breadcrumbStatus: 'visible', href: '/node2' },
],
] as ChromeProjectNavigationNode[][];
const result = buildBreadcrumbs({
projectName: 'Test Project',
cloudLinks: mockCloudLinks,
projectBreadcrumbs: { breadcrumbs: [], params: { absolute: false } },
activeNodes,
chromeBreadcrumbs: [],
isServerless: false,
});
expect(result).toEqual([
expect.objectContaining({
text: 'Deployment',
}),
{ text: 'Node 1', href: '/node1' },
{ text: 'Node 2', href: '/node2' },
]);
});
it('merges chromeBreadcrumbs and navBreadcrumbs based on deepLinkId', () => {
const activeNodes = [
[
{ title: 'Node 1', breadcrumbStatus: 'visible', href: '/node1', deepLink: { id: '1' } },
{ title: 'Node 2', breadcrumbStatus: 'visible', href: '/node2', deepLink: { id: '2' } },
],
] as ChromeProjectNavigationNode[][];
const chromeBreadcrumbs = [
{ text: 'Chrome Crumb 1', href: '/chrome1', deepLinkId: '2' },
{ text: 'Chrome Crumb 2', href: '/chrome2' },
] as ChromeBreadcrumb[];
const result = buildBreadcrumbs({
projectName: 'Test Project',
cloudLinks: mockCloudLinks,
projectBreadcrumbs: { breadcrumbs: [], params: { absolute: false } },
activeNodes,
chromeBreadcrumbs,
isServerless: false,
});
expect(result).toEqual([
expect.objectContaining({
text: 'Deployment',
}),
{ text: 'Node 1', href: '/node1', deepLinkId: '1' },
{ text: 'Chrome Crumb 1', href: '/chrome1', deepLinkId: '2' },
{ text: 'Chrome Crumb 2', href: '/chrome2' },
]);
});
it('returns breadcrumbs without root crumb if projectName/cloudLinks is empty', () => {
const activeNodes = [
[
{ title: 'Node 1', breadcrumbStatus: 'visible', href: '/node1', deepLink: { id: '1' } },
{ title: 'Node 2', breadcrumbStatus: 'visible', href: '/node2', deepLink: { id: '2' } },
],
] as ChromeProjectNavigationNode[][];
const chromeBreadcrumbs = [
{ text: 'Chrome Crumb 1', href: '/chrome1', deepLinkId: '2' },
{ text: 'Chrome Crumb 2', href: '/chrome2' },
] as ChromeBreadcrumb[];
const result = buildBreadcrumbs({
projectName: undefined,
cloudLinks: {},
projectBreadcrumbs: { breadcrumbs: [], params: { absolute: false } },
activeNodes,
chromeBreadcrumbs,
isServerless: false,
});
expect(result).toEqual([
{ text: 'Node 1', href: '/node1', deepLinkId: '1' },
{ text: 'Chrome Crumb 1', href: '/chrome1', deepLinkId: '2' },
{ text: 'Chrome Crumb 2', href: '/chrome2' },
]);
});
});

View file

@ -19,6 +19,13 @@ import type {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
function prependRootCrumb(rootCrumb: ChromeBreadcrumb | undefined, rest: ChromeBreadcrumb[]) {
if (rootCrumb) {
return [rootCrumb, ...rest];
}
return rest;
}
export function buildBreadcrumbs({
projectName,
cloudLinks,
@ -44,7 +51,7 @@ export function buildBreadcrumbs({
});
if (projectBreadcrumbs.params.absolute) {
return [rootCrumb, ...projectBreadcrumbs.breadcrumbs];
return prependRootCrumb(rootCrumb, projectBreadcrumbs.breadcrumbs);
}
// breadcrumbs take the first active path
@ -62,7 +69,7 @@ export function buildBreadcrumbs({
// if there are project breadcrumbs set, use them
if (projectBreadcrumbs.breadcrumbs.length !== 0) {
return [rootCrumb, ...navBreadcrumbs, ...projectBreadcrumbs.breadcrumbs];
return prependRootCrumb(rootCrumb, [...navBreadcrumbs, ...projectBreadcrumbs.breadcrumbs]);
}
// otherwise try to merge legacy breadcrumbs with navigational project breadcrumbs using deeplinkid
@ -80,13 +87,12 @@ export function buildBreadcrumbs({
}
if (chromeBreadcrumbStartIndex === -1) {
return [rootCrumb, ...navBreadcrumbs];
return prependRootCrumb(rootCrumb, navBreadcrumbs);
} else {
return [
rootCrumb,
return prependRootCrumb(rootCrumb, [
...navBreadcrumbs.slice(0, navBreadcrumbEndIndex),
...chromeBreadcrumbs.slice(chromeBreadcrumbStartIndex),
];
]);
}
}
@ -98,7 +104,7 @@ function buildRootCrumb({
projectName?: string;
cloudLinks: CloudLinks;
isServerless: boolean;
}): ChromeBreadcrumb {
}): ChromeBreadcrumb | undefined {
if (isServerless) {
return {
text:
@ -131,47 +137,49 @@ function buildRootCrumb({
};
}
return {
text: i18n.translate('core.ui.primaryNav.cloud.deploymentLabel', {
defaultMessage: 'Deployment',
}),
'data-test-subj': 'deploymentCrumb',
popoverContent: () => (
<>
{cloudLinks.deployment && (
<EuiButtonEmpty
href={cloudLinks.deployment.href}
color="text"
iconType="gear"
data-test-subj="manageDeploymentBtn"
size="s"
>
{i18n.translate('core.ui.primaryNav.cloud.breadCrumbDropdown.manageDeploymentLabel', {
defaultMessage: 'Manage this deployment',
})}
</EuiButtonEmpty>
)}
if (cloudLinks.deployment || cloudLinks.deployments) {
return {
text: i18n.translate('core.ui.primaryNav.cloud.deploymentLabel', {
defaultMessage: 'Deployment',
}),
'data-test-subj': 'deploymentCrumb',
popoverContent: () => (
<>
{cloudLinks.deployment && (
<EuiButtonEmpty
href={cloudLinks.deployment.href}
color="text"
iconType="gear"
data-test-subj="manageDeploymentBtn"
size="s"
>
{i18n.translate('core.ui.primaryNav.cloud.breadCrumbDropdown.manageDeploymentLabel', {
defaultMessage: 'Manage this deployment',
})}
</EuiButtonEmpty>
)}
{cloudLinks.deployments && (
<EuiButtonEmpty
href={cloudLinks.deployments.href}
color="text"
iconType="spaces"
data-test-subj="viewDeploymentsBtn"
size="s"
>
{cloudLinks.deployments.title}
</EuiButtonEmpty>
)}
</>
),
popoverProps: {
panelPaddingSize: 's',
zIndex: 6000,
panelStyle: { maxWidth: 240 },
panelProps: {
'data-test-subj': 'deploymentLinksPanel',
{cloudLinks.deployments && (
<EuiButtonEmpty
href={cloudLinks.deployments.href}
color="text"
iconType="spaces"
data-test-subj="viewDeploymentsBtn"
size="s"
>
{cloudLinks.deployments.title}
</EuiButtonEmpty>
)}
</>
),
popoverProps: {
panelPaddingSize: 's',
zIndex: 6000,
panelStyle: { maxWidth: 240 },
panelProps: {
'data-test-subj': 'deploymentLinksPanel',
},
},
},
};
};
}
}

View file

@ -65,7 +65,6 @@ export default function searchSolutionNavigation({
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'enterpriseSearch',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Deployment' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Data' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Index Management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
@ -121,7 +120,6 @@ export default function searchSolutionNavigation({
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'elasticsearchIndexManagement',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Deployment' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Data' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Index Management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({

View file

@ -52,7 +52,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'elasticsearchIndexManagement',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Deployment' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Data' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Index Management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({