Skip to content

Commit c0d29b9

Browse files
authored
[9.0] [Breadcrumbs] Hide "deployment" in breadcrumb when on-prem (#220110) (#220451)
# Backport This will backport the following commits from `main` to `9.0`: - [[Breadcrumbs] Hide "deployment" in breadcrumb when on-prem (#220110)](#220110) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Tim Sullivan","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-05-06T19:43:47Z","message":"[Breadcrumbs] Hide \"deployment\" in breadcrumb when on-prem (#220110)\n\n## Summary\n\nCloses #219869\n\n**Before**\n\n![image](https://github.com/user-attachments/assets/59f0f6fc-2113-44ee-8e36-69c4eb718fad)\n\n\n**After**\n<img width=\"1320\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/85ad1117-4aab-4a8d-807c-4fa90cd8b917\"\n/>\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"0acd88e3b9ce22dc375682735b3a881d6cdc117b","branchLabelMapping":{"^v9.1.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:version","v9.1.0","v8.19.0","v8.18.2","v9.0.2"],"title":"[Breadcrumbs] Hide \"deployment\" in breadcrumb when on-prem","number":220110,"url":"https://github.com/elastic/kibana/pull/220110","mergeCommit":{"message":"[Breadcrumbs] Hide \"deployment\" in breadcrumb when on-prem (#220110)\n\n## Summary\n\nCloses #219869\n\n**Before**\n\n![image](https://github.com/user-attachments/assets/59f0f6fc-2113-44ee-8e36-69c4eb718fad)\n\n\n**After**\n<img width=\"1320\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/85ad1117-4aab-4a8d-807c-4fa90cd8b917\"\n/>\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"0acd88e3b9ce22dc375682735b3a881d6cdc117b"}},"sourceBranch":"main","suggestedTargetBranches":["8.19","8.18","9.0"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/220110","number":220110,"mergeCommit":{"message":"[Breadcrumbs] Hide \"deployment\" in breadcrumb when on-prem (#220110)\n\n## Summary\n\nCloses #219869\n\n**Before**\n\n![image](https://github.com/user-attachments/assets/59f0f6fc-2113-44ee-8e36-69c4eb718fad)\n\n\n**After**\n<img width=\"1320\" alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/85ad1117-4aab-4a8d-807c-4fa90cd8b917\"\n/>\n\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios","sha":"0acd88e3b9ce22dc375682735b3a881d6cdc117b"}},{"branch":"8.19","label":"v8.19.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.2","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.0","label":"v9.0.2","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
1 parent 09eaf55 commit c0d29b9

File tree

5 files changed

+185
-50
lines changed

5 files changed

+185
-50
lines changed

src/core/packages/chrome/browser-internal/src/chrome_service.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,9 @@ describe('start', () => {
462462
it('allows the project breadcrumb to also be set', async () => {
463463
const { chrome } = await start();
464464

465+
chrome.project.setCloudUrls({
466+
deploymentUrl: 'my-deployment-url.com',
467+
});
465468
chrome.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }]); // only setting the classic breadcrumbs
466469

467470
{
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import type {
11+
ChromeBreadcrumb,
12+
ChromeProjectNavigationNode,
13+
CloudLinks,
14+
} from '@kbn/core-chrome-browser/src';
15+
import { buildBreadcrumbs } from './breadcrumbs';
16+
17+
describe('buildBreadcrumbs', () => {
18+
const mockCloudLinks = {
19+
deployment: { href: '/deployment' },
20+
deployments: { href: '/deployments', title: 'All Deployments' },
21+
} as CloudLinks;
22+
23+
it('returns breadcrumbs with root crumb when absolute is true', () => {
24+
const projectBreadcrumbs = {
25+
breadcrumbs: [{ text: 'Project Crumb', href: '/project' }],
26+
params: { absolute: true },
27+
};
28+
const result = buildBreadcrumbs({
29+
projectName: 'Test Project',
30+
cloudLinks: mockCloudLinks,
31+
projectBreadcrumbs,
32+
activeNodes: [],
33+
chromeBreadcrumbs: [],
34+
isServerless: true,
35+
});
36+
37+
expect(result).toEqual([
38+
expect.objectContaining({
39+
text: 'Test Project',
40+
}),
41+
...projectBreadcrumbs.breadcrumbs,
42+
]);
43+
});
44+
45+
it('builds breadcrumbs from activeNodes when no project breadcrumbs are set', () => {
46+
const activeNodes = [
47+
[
48+
{ title: 'Node 1', breadcrumbStatus: 'visible', href: '/node1' },
49+
{ title: 'Node 2', breadcrumbStatus: 'visible', href: '/node2' },
50+
],
51+
] as ChromeProjectNavigationNode[][];
52+
const result = buildBreadcrumbs({
53+
projectName: 'Test Project',
54+
cloudLinks: mockCloudLinks,
55+
projectBreadcrumbs: { breadcrumbs: [], params: { absolute: false } },
56+
activeNodes,
57+
chromeBreadcrumbs: [],
58+
isServerless: false,
59+
});
60+
61+
expect(result).toEqual([
62+
expect.objectContaining({
63+
text: 'Deployment',
64+
}),
65+
{ text: 'Node 1', href: '/node1' },
66+
{ text: 'Node 2', href: '/node2' },
67+
]);
68+
});
69+
70+
it('merges chromeBreadcrumbs and navBreadcrumbs based on deepLinkId', () => {
71+
const activeNodes = [
72+
[
73+
{ title: 'Node 1', breadcrumbStatus: 'visible', href: '/node1', deepLink: { id: '1' } },
74+
{ title: 'Node 2', breadcrumbStatus: 'visible', href: '/node2', deepLink: { id: '2' } },
75+
],
76+
] as ChromeProjectNavigationNode[][];
77+
const chromeBreadcrumbs = [
78+
{ text: 'Chrome Crumb 1', href: '/chrome1', deepLinkId: '2' },
79+
{ text: 'Chrome Crumb 2', href: '/chrome2' },
80+
] as ChromeBreadcrumb[];
81+
const result = buildBreadcrumbs({
82+
projectName: 'Test Project',
83+
cloudLinks: mockCloudLinks,
84+
projectBreadcrumbs: { breadcrumbs: [], params: { absolute: false } },
85+
activeNodes,
86+
chromeBreadcrumbs,
87+
isServerless: false,
88+
});
89+
90+
expect(result).toEqual([
91+
expect.objectContaining({
92+
text: 'Deployment',
93+
}),
94+
{ text: 'Node 1', href: '/node1', deepLinkId: '1' },
95+
{ text: 'Chrome Crumb 1', href: '/chrome1', deepLinkId: '2' },
96+
{ text: 'Chrome Crumb 2', href: '/chrome2' },
97+
]);
98+
});
99+
100+
it('returns breadcrumbs without root crumb if projectName/cloudLinks is empty', () => {
101+
const activeNodes = [
102+
[
103+
{ title: 'Node 1', breadcrumbStatus: 'visible', href: '/node1', deepLink: { id: '1' } },
104+
{ title: 'Node 2', breadcrumbStatus: 'visible', href: '/node2', deepLink: { id: '2' } },
105+
],
106+
] as ChromeProjectNavigationNode[][];
107+
const chromeBreadcrumbs = [
108+
{ text: 'Chrome Crumb 1', href: '/chrome1', deepLinkId: '2' },
109+
{ text: 'Chrome Crumb 2', href: '/chrome2' },
110+
] as ChromeBreadcrumb[];
111+
const result = buildBreadcrumbs({
112+
projectName: undefined,
113+
cloudLinks: {},
114+
projectBreadcrumbs: { breadcrumbs: [], params: { absolute: false } },
115+
activeNodes,
116+
chromeBreadcrumbs,
117+
isServerless: false,
118+
});
119+
120+
expect(result).toEqual([
121+
{ text: 'Node 1', href: '/node1', deepLinkId: '1' },
122+
{ text: 'Chrome Crumb 1', href: '/chrome1', deepLinkId: '2' },
123+
{ text: 'Chrome Crumb 2', href: '/chrome2' },
124+
]);
125+
});
126+
});

src/core/packages/chrome/browser-internal/src/project_navigation/breadcrumbs.tsx

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ import type {
1919
import { i18n } from '@kbn/i18n';
2020
import { FormattedMessage } from '@kbn/i18n-react';
2121

22+
function prependRootCrumb(rootCrumb: ChromeBreadcrumb | undefined, rest: ChromeBreadcrumb[]) {
23+
if (rootCrumb) {
24+
return [rootCrumb, ...rest];
25+
}
26+
return rest;
27+
}
28+
2229
export function buildBreadcrumbs({
2330
projectName,
2431
cloudLinks,
@@ -44,7 +51,7 @@ export function buildBreadcrumbs({
4451
});
4552

4653
if (projectBreadcrumbs.params.absolute) {
47-
return [rootCrumb, ...projectBreadcrumbs.breadcrumbs];
54+
return prependRootCrumb(rootCrumb, projectBreadcrumbs.breadcrumbs);
4855
}
4956

5057
// breadcrumbs take the first active path
@@ -62,7 +69,7 @@ export function buildBreadcrumbs({
6269

6370
// if there are project breadcrumbs set, use them
6471
if (projectBreadcrumbs.breadcrumbs.length !== 0) {
65-
return [rootCrumb, ...navBreadcrumbs, ...projectBreadcrumbs.breadcrumbs];
72+
return prependRootCrumb(rootCrumb, [...navBreadcrumbs, ...projectBreadcrumbs.breadcrumbs]);
6673
}
6774

6875
// otherwise try to merge legacy breadcrumbs with navigational project breadcrumbs using deeplinkid
@@ -80,13 +87,12 @@ export function buildBreadcrumbs({
8087
}
8188

8289
if (chromeBreadcrumbStartIndex === -1) {
83-
return [rootCrumb, ...navBreadcrumbs];
90+
return prependRootCrumb(rootCrumb, navBreadcrumbs);
8491
} else {
85-
return [
86-
rootCrumb,
92+
return prependRootCrumb(rootCrumb, [
8793
...navBreadcrumbs.slice(0, navBreadcrumbEndIndex),
8894
...chromeBreadcrumbs.slice(chromeBreadcrumbStartIndex),
89-
];
95+
]);
9096
}
9197
}
9298

@@ -98,7 +104,7 @@ function buildRootCrumb({
98104
projectName?: string;
99105
cloudLinks: CloudLinks;
100106
isServerless: boolean;
101-
}): ChromeBreadcrumb {
107+
}): ChromeBreadcrumb | undefined {
102108
if (isServerless) {
103109
return {
104110
text:
@@ -131,47 +137,49 @@ function buildRootCrumb({
131137
};
132138
}
133139

134-
return {
135-
text: i18n.translate('core.ui.primaryNav.cloud.deploymentLabel', {
136-
defaultMessage: 'Deployment',
137-
}),
138-
'data-test-subj': 'deploymentCrumb',
139-
popoverContent: () => (
140-
<>
141-
{cloudLinks.deployment && (
142-
<EuiButtonEmpty
143-
href={cloudLinks.deployment.href}
144-
color="text"
145-
iconType="gear"
146-
data-test-subj="manageDeploymentBtn"
147-
size="s"
148-
>
149-
{i18n.translate('core.ui.primaryNav.cloud.breadCrumbDropdown.manageDeploymentLabel', {
150-
defaultMessage: 'Manage this deployment',
151-
})}
152-
</EuiButtonEmpty>
153-
)}
140+
if (cloudLinks.deployment || cloudLinks.deployments) {
141+
return {
142+
text: i18n.translate('core.ui.primaryNav.cloud.deploymentLabel', {
143+
defaultMessage: 'Deployment',
144+
}),
145+
'data-test-subj': 'deploymentCrumb',
146+
popoverContent: () => (
147+
<>
148+
{cloudLinks.deployment && (
149+
<EuiButtonEmpty
150+
href={cloudLinks.deployment.href}
151+
color="text"
152+
iconType="gear"
153+
data-test-subj="manageDeploymentBtn"
154+
size="s"
155+
>
156+
{i18n.translate('core.ui.primaryNav.cloud.breadCrumbDropdown.manageDeploymentLabel', {
157+
defaultMessage: 'Manage this deployment',
158+
})}
159+
</EuiButtonEmpty>
160+
)}
154161

155-
{cloudLinks.deployments && (
156-
<EuiButtonEmpty
157-
href={cloudLinks.deployments.href}
158-
color="text"
159-
iconType="spaces"
160-
data-test-subj="viewDeploymentsBtn"
161-
size="s"
162-
>
163-
{cloudLinks.deployments.title}
164-
</EuiButtonEmpty>
165-
)}
166-
</>
167-
),
168-
popoverProps: {
169-
panelPaddingSize: 's',
170-
zIndex: 6000,
171-
panelStyle: { maxWidth: 240 },
172-
panelProps: {
173-
'data-test-subj': 'deploymentLinksPanel',
162+
{cloudLinks.deployments && (
163+
<EuiButtonEmpty
164+
href={cloudLinks.deployments.href}
165+
color="text"
166+
iconType="spaces"
167+
data-test-subj="viewDeploymentsBtn"
168+
size="s"
169+
>
170+
{cloudLinks.deployments.title}
171+
</EuiButtonEmpty>
172+
)}
173+
</>
174+
),
175+
popoverProps: {
176+
panelPaddingSize: 's',
177+
zIndex: 6000,
178+
panelStyle: { maxWidth: 240 },
179+
panelProps: {
180+
'data-test-subj': 'deploymentLinksPanel',
181+
},
174182
},
175-
},
176-
};
183+
};
184+
}
177185
}

x-pack/test/functional_search/tests/solution_navigation.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ export default function searchSolutionNavigation({
115115
await solutionNavigation.sidenav.expectLinkActive({
116116
deepLinkId: 'management:index_management',
117117
});
118-
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Deployment' });
119118
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Stack Management' });
120119
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Index Management' });
121120
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({

x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
5555
await solutionNavigation.sidenav.expectLinkActive({
5656
deepLinkId: 'management:index_management',
5757
});
58-
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Deployment' });
5958
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Stack Management' });
6059
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Index Management' });
6160
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({

0 commit comments

Comments
 (0)