Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
98b52c9
Add deployment name to breadcrumbs in ECH
kowalczyk-krzysztof Oct 8, 2025
2b866b4
[CI] Auto-commit changed files from 'node scripts/yarn_deduplicate'
kibanamachine Oct 8, 2025
14ecdf5
Remove import type to avoid circular dependency error
kowalczyk-krzysztof Oct 8, 2025
fddca5d
Fix TypeError
kowalczyk-krzysztof Oct 8, 2025
355cc81
Merge branch 'main' into feat/add-deployment-name-to-breadcrumbs
kowalczyk-krzysztof Oct 19, 2025
5b78863
Simplify and add tests
kowalczyk-krzysztof Oct 19, 2025
01cffa7
Cleanup
kowalczyk-krzysztof Oct 21, 2025
0ef47f2
Merge branch 'main' into feat/add-deployment-name-to-breadcrumbs
kowalczyk-krzysztof Oct 21, 2025
d0a6c44
Fix test
kowalczyk-krzysztof Oct 21, 2025
ad379b5
Merge branch 'main' into feat/add-deployment-name-to-breadcrumbs
kowalczyk-krzysztof Oct 21, 2025
71c16eb
Set deployment name from cloud plugin
kowalczyk-krzysztof Oct 22, 2025
56a5e8e
[CI] Auto-commit changed files from 'node scripts/yarn_deduplicate'
kibanamachine Oct 22, 2025
c12c12f
Add serverless check
kowalczyk-krzysztof Oct 22, 2025
ee84cb8
Remove unused tests
kowalczyk-krzysztof Oct 22, 2025
e5a23f6
[CI] Auto-commit changed files from 'node scripts/eslint_all_files --…
kibanamachine Oct 22, 2025
15b0636
Increase cloud plugin limit
kowalczyk-krzysztof Oct 22, 2025
6ecf3cc
Fix tests
kowalczyk-krzysztof Oct 22, 2025
4489d47
Merge branch 'main' into feat/add-deployment-name-to-breadcrumbs
kowalczyk-krzysztof Oct 22, 2025
3d88034
Merge branch 'main' into feat/add-deployment-name-to-breadcrumbs
kowalczyk-krzysztof Oct 22, 2025
d4ca914
Merge branch 'main' into feat/add-deployment-name-to-breadcrumbs
kowalczyk-krzysztof Oct 23, 2025
c41b353
Merge branch 'main' into feat/add-deployment-name-to-breadcrumbs
kowalczyk-krzysztof Oct 27, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pageLoadAssetSize:
canvas: 15210
cases: 153204
charts: 40269
cloud: 9012
cloud: 9300
cloudDataMigration: 5687
cloudExperiments: 103984
cloudFullStory: 4752
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,8 @@ export class ChromeService {
projectNavigation.setProjectHome(homeHref);
};

const setProjectName = (projectName: string) => {
validateChromeStyle();
Copy link
Contributor

@gsoldevila gsoldevila Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems related to my other comment.
Does this mean that everybody can now set the text of the first breadcrumb?
Not saying it's necessarily a bad thing, goes in the lines of exposing the setter publicly in Chrome's API.

projectNavigation.setProjectName(projectName);
const setKibanaName = (kibanaName: string) => {
projectNavigation.setKibanaName(kibanaName);
};

const setIsSideNavCollapsed = (isCollapsed: boolean) => {
Expand Down Expand Up @@ -789,7 +788,7 @@ export class ChromeService {
setHome: setProjectHome,
setCloudUrls: projectNavigation.setCloudUrls.bind(projectNavigation),
setFeedbackUrlParams: projectNavigation.setFeedbackUrlParams.bind(projectNavigation),
setProjectName,
setKibanaName,
initNavigation: initProjectNavigation,
getNavigationTreeUi$: () => projectNavigation.getNavigationTreeUi$(),
setBreadcrumbs: setProjectBreadcrumbs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('buildBreadcrumbs', () => {
params: { absolute: true },
};
const result = buildBreadcrumbs({
projectName: 'Test Project',
kibanaName: 'Test Project',
cloudLinks: mockCloudLinks,
projectBreadcrumbs,
activeNodes: [],
Expand All @@ -50,7 +50,6 @@ describe('buildBreadcrumbs', () => {
],
] as ChromeProjectNavigationNode[][];
const result = buildBreadcrumbs({
projectName: 'Test Project',
cloudLinks: mockCloudLinks,
projectBreadcrumbs: { breadcrumbs: [], params: { absolute: false } },
activeNodes,
Expand Down Expand Up @@ -79,7 +78,6 @@ describe('buildBreadcrumbs', () => {
{ text: 'Chrome Crumb 2', href: '/chrome2' },
] as ChromeBreadcrumb[];
const result = buildBreadcrumbs({
projectName: 'Test Project',
cloudLinks: mockCloudLinks,
projectBreadcrumbs: { breadcrumbs: [], params: { absolute: false } },
activeNodes,
Expand All @@ -97,7 +95,7 @@ describe('buildBreadcrumbs', () => {
]);
});

it('returns breadcrumbs without root crumb if projectName/cloudLinks is empty', () => {
it('returns breadcrumbs without root crumb if kibanaName/cloudLinks is empty', () => {
const activeNodes = [
[
{ title: 'Node 1', breadcrumbStatus: 'visible', href: '/node1', deepLink: { id: '1' } },
Expand All @@ -109,7 +107,7 @@ describe('buildBreadcrumbs', () => {
{ text: 'Chrome Crumb 2', href: '/chrome2' },
] as ChromeBreadcrumb[];
const result = buildBreadcrumbs({
projectName: undefined,
kibanaName: undefined,
cloudLinks: {},
projectBreadcrumbs: { breadcrumbs: [], params: { absolute: false } },
activeNodes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
*/

import React from 'react';
import { EuiContextMenuPanel, EuiContextMenuItem, EuiButtonEmpty } from '@elastic/eui';
import {
EuiContextMenuPanel,
EuiContextMenuItem,
EuiButtonEmpty,
EuiTextTruncate,
} from '@elastic/eui';
import type {
AppDeepLinkId,
ChromeProjectNavigationNode,
Expand All @@ -27,14 +32,14 @@ function prependRootCrumb(rootCrumb: ChromeBreadcrumb | undefined, rest: ChromeB
}

export function buildBreadcrumbs({
projectName,
kibanaName,
cloudLinks,
projectBreadcrumbs,
activeNodes,
chromeBreadcrumbs,
isServerless,
}: {
projectName?: string;
kibanaName?: string;
projectBreadcrumbs: {
breadcrumbs: ChromeBreadcrumb[];
params: ChromeSetProjectBreadcrumbsParams;
Expand All @@ -45,7 +50,7 @@ export function buildBreadcrumbs({
isServerless: boolean;
}): ChromeBreadcrumb[] {
const rootCrumb = buildRootCrumb({
projectName,
kibanaName,
cloudLinks,
isServerless,
});
Expand Down Expand Up @@ -97,18 +102,18 @@ export function buildBreadcrumbs({
}

function buildRootCrumb({
projectName,
kibanaName,
cloudLinks,
isServerless,
}: {
projectName?: string;
kibanaName?: string;
cloudLinks: CloudLinks;
isServerless: boolean;
}): ChromeBreadcrumb | undefined {
if (isServerless) {
return {
text:
projectName ??
kibanaName ??
i18n.translate('core.ui.primaryNav.cloud.projectLabel', {
defaultMessage: 'Project',
}),
Expand Down Expand Up @@ -139,9 +144,13 @@ function buildRootCrumb({

if (cloudLinks.deployment || cloudLinks.deployments) {
return {
text: i18n.translate('core.ui.primaryNav.cloud.deploymentLabel', {
defaultMessage: 'Deployment',
}),
text: kibanaName ? (
<EuiTextTruncate text={kibanaName} width={96} />
) : (
i18n.translate('core.ui.primaryNav.cloud.deploymentLabel', {
defaultMessage: 'Deployment',
})
),
'data-test-subj': 'deploymentCrumb',
popoverContent: () => (
<>
Expand All @@ -151,6 +160,12 @@ function buildRootCrumb({
color="text"
iconType="gear"
data-test-subj="manageDeploymentBtn"
aria-label={i18n.translate(
'core.ui.primaryNav.cloud.breadCrumbDropdown.manageDeploymentLabel',
{
defaultMessage: 'Manage this deployment',
}
)}
size="s"
>
{i18n.translate('core.ui.primaryNav.cloud.breadCrumbDropdown.manageDeploymentLabel', {
Expand All @@ -165,6 +180,7 @@ function buildRootCrumb({
color="text"
iconType="spaces"
data-test-subj="viewDeploymentsBtn"
aria-label={cloudLinks.deployments.title}
size="s"
>
{cloudLinks.deployments.title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ interface StartDeps {
export class ProjectNavigationService {
private logger: Logger | undefined;
private projectHome$ = new BehaviorSubject<string | undefined>(undefined);
private projectName$ = new BehaviorSubject<string | undefined>(undefined);
private kibanaName$ = new BehaviorSubject<string | undefined>(undefined);
private feedbackUrlParams$ = new BehaviorSubject<URLSearchParams | undefined>(undefined);
private navigationTree$ = new BehaviorSubject<ChromeProjectNavigationNode[] | undefined>(
undefined
Expand Down Expand Up @@ -146,11 +146,11 @@ export class ProjectNavigationService {
setFeedbackUrlParams: (feedbackUrlParams: URLSearchParams) => {
this.feedbackUrlParams$.next(feedbackUrlParams);
},
setProjectName: (projectName: string) => {
this.projectName$.next(projectName);
setKibanaName: (kibanaName: string) => {
this.kibanaName$.next(kibanaName);
},
getProjectName$: () => {
return this.projectName$.asObservable();
getKibanaName$: () => {
return this.kibanaName$.asObservable();
},
getFeedbackUrlParams$: () => {
return this.feedbackUrlParams$.asObservable();
Expand Down Expand Up @@ -180,12 +180,12 @@ export class ProjectNavigationService {
this.projectBreadcrumbs$,
this.activeNodes$,
chromeBreadcrumbs$,
this.projectName$,
this.kibanaName$,
this.cloudLinks$,
]).pipe(
map(([projectBreadcrumbs, activeNodes, chromeBreadcrumbs, projectName, cloudLinks]) => {
map(([projectBreadcrumbs, activeNodes, chromeBreadcrumbs, kibanaName, cloudLinks]) => {
return buildBreadcrumbs({
projectName,
kibanaName,
projectBreadcrumbs,
activeNodes,
chromeBreadcrumbs,
Expand Down
6 changes: 3 additions & 3 deletions src/core/packages/chrome/browser-internal/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,10 @@ export interface InternalChromeStart extends ChromeStart {
setFeedbackUrlParams(feedbackUrlParams: URLSearchParams): void;

/**
* Sets the project name.
* @param projectName
* Sets the Kibana name - project name for serverless, deployment name for ECH.
* @param kibanaName
*/
setProjectName(projectName: string): void;
setKibanaName(kibanaName: string): void;

initNavigation<
LinkId extends AppDeepLinkId = AppDeepLinkId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const createStartContractMock = () => {
project: lazyObject({
setHome: jest.fn(),
setCloudUrls: jest.fn(),
setProjectName: jest.fn(),
setKibanaName: jest.fn(),
setFeedbackUrlParams: jest.fn(),
initNavigation: jest.fn(),
setBreadcrumbs: jest.fn(),
Expand Down
2 changes: 2 additions & 0 deletions x-pack/platform/plugins/shared/cloud/public/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ describe('Cloud Plugin', () => {
});

const coreStart = coreMock.createStart();
coreStart.http.get.mockResolvedValue({});
plugin.start(coreStart);

expect(coreStart.chrome.setHelpSupportUrl).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -295,6 +296,7 @@ describe('Cloud Plugin', () => {
serverless: undefined,
});
const coreStart = coreMock.createStart();
coreStart.http.get.mockResolvedValue({});
const start = plugin.start(coreStart);
expect(start.isServerlessEnabled).toBe(false);
});
Expand Down
16 changes: 15 additions & 1 deletion x-pack/platform/plugins/shared/cloud/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import type { Logger } from '@kbn/logging';
import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';

import type { KibanaProductTier, KibanaSolution } from '@kbn/projects-solutions-groups';
import type { InternalChromeStart } from '@kbn/core-chrome-browser-internal';
import { registerCloudDeploymentMetadataAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context';
import { getIsCloudEnabled } from '../common/is_cloud_enabled';
import { parseDeploymentIdFromDeploymentUrl } from '../common/parse_deployment_id_from_deployment_url';
import { ELASTICSEARCH_CONFIG_ROUTE } from '../common/constants';
import { decodeCloudId, type DecodedCloudId } from '../common/decode_cloud_id';
import { parseOnboardingSolution } from '../common/parse_onboarding_default_solution';
import type { ElasticsearchConfigType } from '../common/types';
import type { CloudDataAttributes, ElasticsearchConfigType } from '../common/types';
import type { CloudSetup, CloudStart, PublicElasticsearchConfigType } from './types';
import { CloudUrlsService } from './urls';
import { getSupportUrl } from './utils';
Expand Down Expand Up @@ -126,6 +127,19 @@ export class CloudPlugin implements Plugin<CloudSetup, CloudStart> {
public start(coreStart: CoreStart): CloudStart {
coreStart.chrome.setHelpSupportUrl(getSupportUrl(this.config));

// Deployment name is only available in ECH
if (this.isCloudEnabled && !this.isServerlessEnabled) {
coreStart.http
.get<CloudDataAttributes>('/internal/cloud/solution', { version: '1' })
.then((response) => {
const deploymentName = response?.resourceData?.deployment?.name;
if (deploymentName) {
(coreStart.chrome as InternalChromeStart)?.project?.setKibanaName(deploymentName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit hacky to me.

  • We need to access an internal property to set a name.
  • That setter is under a "project" property, but here we're specifically talking about ECH and !Serverless, thus deployments. I'm guessing this was used on Serverless only and your PR sets it on ECH too.

If the chrome plugin does not know how to set a name yet it owns the logic to set it... the setter should be available in its public API IMO. And if project property holds functionality for both ECH and Serverless perhaps we should generalise it into something that covers both. I wonder if any of these setters make sense for self-managed customers.

Copy link
Contributor

@Dosant Dosant Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea for InternalChromeStart was that we had to expose apis for plugins like serverless, but we didn't want anyone else to know about them :D I am also fine to move this to public type, but we really don't want other plugins to call this ever

That setter is under a "project" property, but here we're specifically talking about ECH and !Serverless, thus deployments. I'm guessing this was used on Serverless only and your PR sets it on ECH too.

This part got messed up when we added solution view to ech/hosted.
What is "project" navigation in chrome initially was build for "serverless" but now it is "serverless" + "solution view of hosted/ech" .

In theory, we could refactor this from Project to SolutionView or something like that.

In this PR we need to set breadcrumb for "solution view of ech" . We do it from cloud plugin, but cloud plugin is also run in serveless, so we don't want to override what serverless plugin has set :(
Ideally, we would have a ech plugin that runs only for ECH and another that runs only in serverless. But we don't.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarifications!
I'm fine with renaming project to solutionView in a follow-up PR.

Copy link
Member Author

@kowalczyk-krzysztof kowalczyk-krzysztof Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created follow-up issue: #240747

}
})
.catch();
}

// Nest all the registered context providers under the Cloud Services Provider.
// This way, plugins only need to require Cloud's context provider to have all the enriched Cloud services.
const CloudContextProvider: FC<PropsWithChildren<unknown>> = ({ children }) => {
Expand Down
1 change: 1 addition & 0 deletions x-pack/platform/plugins/shared/cloud/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@kbn/core-saved-objects-server",
"@kbn/std",
"@kbn/projects-solutions-groups",
"@kbn/core-chrome-browser-internal",
],
"exclude": [
"target/**/*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class ServerlessPlugin
chrome.setChromeStyle('project');

if (cloud.serverless.projectName) {
project.setProjectName(cloud.serverless.projectName);
project.setKibanaName(cloud.serverless.projectName);
}

project.setCloudUrls(cloud.getUrls()); // Ensure the project has the non-privileged URLs immediately
Expand Down