[8.16] [Observability] Update breadcrumbs for observability project based navigation (#196785) (#198216)

# Backport

This will backport the following commits from `main` to `8.16`:
- [[Observability] Update breadcrumbs for observability project based
navigation (#196785)](https://github.com/elastic/kibana/pull/196785)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Kerry
Gallagher","email":"kerry.gallagher@elastic.co"},"sourceCommit":{"committedDate":"2024-10-29T22:04:01Z","message":"[Observability]
Update breadcrumbs for observability project based navigation
(#196785)\n\n~⚠️ I'm still putting out some fires with tests, but this
is ready to\r\nstart being reviewed.~\r\n\r\n## Summary\r\n\r\nA
continuation of https://github.com/elastic/kibana/pull/196169
for\r\nObservability (please read that PR description
first).\r\n\r\nRelated:
https://github.com/elastic/kibana/issues/192050\r\n\r\n## Overview of
changes\r\n\r\nThere are essentially three types of breadcrumbs -
serverless (which is\r\nproject style), stateful project style (set
through spaces settings),\r\nand classic style (the old breadcrumbs
we've seen for years). Whilst\r\nserverless and stateful project style
both use the project based style\r\nthe navigation trees are slightly
different, so the breadcrumbs results\r\nare not identical [when they
derive the
\"nav\r\ncrumbs\"](9577aa980d/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx (L55)).\r\n\r\nHere
\"project style\" will refer to serverless and stateful
project\r\nstyle.\r\n\r\nIn these changes I've, for the most part, tried
to refactor things so\r\nObservability solutions route their breadcrumbs
through the\r\nobservability-shared `useBreadcrumbs` hook, this way the
logic around\r\nproject style, adding an Observability crumb in classic
etc is\r\nconsolidated in one place.\r\n\r\n[For several solutions
`absolute` breadcrumbs are
being\r\nused](9577aa980d/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx (L46)),\r\nand
this means we'll roughly have the same breadcrumbs across the
3\r\nexperiences (bar Observability being prepended). Teams may want
to\r\nrefine this going forward to pass curated breadcrumbs that take
into\r\naccount the navigation derived \"nav crumbs\" (again, bearing in
mind the\r\ntrees from serverless and project based chrome do differ),
and not use\r\nabsolute mode. APM is an example of this at the moment.
Right now this\r\nis an 8.16 bug though, so this aims to make things
acceptable, but not\r\nnecessarily perfect.\r\n\r\n### APM\r\n\r\n-
Project style chrome crumbs have been modelled off the
serverless\r\nones. The navigation trees here are the same so this
should be fine.\r\n\r\n### Infra\r\n\r\n- The `infra` `useBreadcrumbs`
hook has been removed, it was only being\r\nused by logs. Logs now goes
via the Observability shared hook using\r\n`classicOnly`.\r\n\r\n-
Metrics (`useMetricsBreadcrumbs` hook) has been slightly amended
to\r\nroute more of it's logic through the shared hook.\r\n\r\n### Logs
explorer\r\n\r\n- Wasn't setting any nested breadcrumbs at the moment so
the logic has\r\nbeen simplified to just set some classic crumbs, and
defer the rest to\r\nthe nav crumbs via the shared hook.\r\n\r\n###
Synthetics \r\n\r\n- Removed custom logic around prepending
Observability, adding link\r\nhandlers etc in favour of the shared
hook.\r\n\r\n\r\n### Alerts / rules / SLOs etc\r\n\r\n- Simple
breadcrumb needs so these are mostly setting `classicOnly`
and\r\ndeferring to the nav crumbs in project style.\r\n\r\nSeveral
solutions have had their usage of the shared hook updated to\r\npass in
the `serverless` plugin. This was missing before, so calls
to\r\n`serverless.setBreadcrumbs` weren't explicitly
happening.\r\n\r\n## Testing \r\n\r\n- Add the following to your
`kibana.dev.yml`:\r\n\r\n```yml\r\nxpack.cloud.id:
\"ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=\"\r\nxpack.cloud.base_url:
\"https://cloud.elastic.co\"\r\n```\r\n\r\n- For testing stateful
project style chrome you'll need to go to Stack\r\nManagement > Spaces
and change the solution view:\r\n\r\n![Screenshot 2024-10-21 at 12
44\r\n21](https://github.com/user-attachments/assets/e3d9fe64-f79f-4e31-a5b6-45a06ca4915d)\r\n\r\n-
Set the above to Classic to test classic breadcrumbs.\r\n\r\n- As a
reviewer please check your solution against the 3 modes.\r\n\r\n##
Comparison\r\n\r\nBefore these changes we'd see something like the
following in APM:\r\n\r\n![Screenshot 2024-10-11 at 10
56\r\n54](https://github.com/user-attachments/assets/4938b31e-9d4a-429e-abf0-add04d69b62a)\r\n\r\nNow
we'll see something like this in project style:\r\n\r\n![Screenshot
2024-10-21 at 12
48\r\n54](https://github.com/user-attachments/assets/0645a3ae-909e-4a70-a077-d9f83bd1d639)","sha":"641d9159451447484f3940f0b1c17438472fea5c","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","ci:project-deploy-observability","Team:obs-ux-infra_services","Team:obs-ux-management","apm:review","v8.16.0","backport:version","v8.17.0"],"title":"[Observability]
Update breadcrumbs for observability project based
navigation","number":196785,"url":"https://github.com/elastic/kibana/pull/196785","mergeCommit":{"message":"[Observability]
Update breadcrumbs for observability project based navigation
(#196785)\n\n~⚠️ I'm still putting out some fires with tests, but this
is ready to\r\nstart being reviewed.~\r\n\r\n## Summary\r\n\r\nA
continuation of https://github.com/elastic/kibana/pull/196169
for\r\nObservability (please read that PR description
first).\r\n\r\nRelated:
https://github.com/elastic/kibana/issues/192050\r\n\r\n## Overview of
changes\r\n\r\nThere are essentially three types of breadcrumbs -
serverless (which is\r\nproject style), stateful project style (set
through spaces settings),\r\nand classic style (the old breadcrumbs
we've seen for years). Whilst\r\nserverless and stateful project style
both use the project based style\r\nthe navigation trees are slightly
different, so the breadcrumbs results\r\nare not identical [when they
derive the
\"nav\r\ncrumbs\"](9577aa980d/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx (L55)).\r\n\r\nHere
\"project style\" will refer to serverless and stateful
project\r\nstyle.\r\n\r\nIn these changes I've, for the most part, tried
to refactor things so\r\nObservability solutions route their breadcrumbs
through the\r\nobservability-shared `useBreadcrumbs` hook, this way the
logic around\r\nproject style, adding an Observability crumb in classic
etc is\r\nconsolidated in one place.\r\n\r\n[For several solutions
`absolute` breadcrumbs are
being\r\nused](9577aa980d/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx (L46)),\r\nand
this means we'll roughly have the same breadcrumbs across the
3\r\nexperiences (bar Observability being prepended). Teams may want
to\r\nrefine this going forward to pass curated breadcrumbs that take
into\r\naccount the navigation derived \"nav crumbs\" (again, bearing in
mind the\r\ntrees from serverless and project based chrome do differ),
and not use\r\nabsolute mode. APM is an example of this at the moment.
Right now this\r\nis an 8.16 bug though, so this aims to make things
acceptable, but not\r\nnecessarily perfect.\r\n\r\n### APM\r\n\r\n-
Project style chrome crumbs have been modelled off the
serverless\r\nones. The navigation trees here are the same so this
should be fine.\r\n\r\n### Infra\r\n\r\n- The `infra` `useBreadcrumbs`
hook has been removed, it was only being\r\nused by logs. Logs now goes
via the Observability shared hook using\r\n`classicOnly`.\r\n\r\n-
Metrics (`useMetricsBreadcrumbs` hook) has been slightly amended
to\r\nroute more of it's logic through the shared hook.\r\n\r\n### Logs
explorer\r\n\r\n- Wasn't setting any nested breadcrumbs at the moment so
the logic has\r\nbeen simplified to just set some classic crumbs, and
defer the rest to\r\nthe nav crumbs via the shared hook.\r\n\r\n###
Synthetics \r\n\r\n- Removed custom logic around prepending
Observability, adding link\r\nhandlers etc in favour of the shared
hook.\r\n\r\n\r\n### Alerts / rules / SLOs etc\r\n\r\n- Simple
breadcrumb needs so these are mostly setting `classicOnly`
and\r\ndeferring to the nav crumbs in project style.\r\n\r\nSeveral
solutions have had their usage of the shared hook updated to\r\npass in
the `serverless` plugin. This was missing before, so calls
to\r\n`serverless.setBreadcrumbs` weren't explicitly
happening.\r\n\r\n## Testing \r\n\r\n- Add the following to your
`kibana.dev.yml`:\r\n\r\n```yml\r\nxpack.cloud.id:
\"ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=\"\r\nxpack.cloud.base_url:
\"https://cloud.elastic.co\"\r\n```\r\n\r\n- For testing stateful
project style chrome you'll need to go to Stack\r\nManagement > Spaces
and change the solution view:\r\n\r\n![Screenshot 2024-10-21 at 12
44\r\n21](https://github.com/user-attachments/assets/e3d9fe64-f79f-4e31-a5b6-45a06ca4915d)\r\n\r\n-
Set the above to Classic to test classic breadcrumbs.\r\n\r\n- As a
reviewer please check your solution against the 3 modes.\r\n\r\n##
Comparison\r\n\r\nBefore these changes we'd see something like the
following in APM:\r\n\r\n![Screenshot 2024-10-11 at 10
56\r\n54](https://github.com/user-attachments/assets/4938b31e-9d4a-429e-abf0-add04d69b62a)\r\n\r\nNow
we'll see something like this in project style:\r\n\r\n![Screenshot
2024-10-21 at 12
48\r\n54](https://github.com/user-attachments/assets/0645a3ae-909e-4a70-a077-d9f83bd1d639)","sha":"641d9159451447484f3940f0b1c17438472fea5c"}},"sourceBranch":"main","suggestedTargetBranches":["8.16","8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196785","number":196785,"mergeCommit":{"message":"[Observability]
Update breadcrumbs for observability project based navigation
(#196785)\n\n~⚠️ I'm still putting out some fires with tests, but this
is ready to\r\nstart being reviewed.~\r\n\r\n## Summary\r\n\r\nA
continuation of https://github.com/elastic/kibana/pull/196169
for\r\nObservability (please read that PR description
first).\r\n\r\nRelated:
https://github.com/elastic/kibana/issues/192050\r\n\r\n## Overview of
changes\r\n\r\nThere are essentially three types of breadcrumbs -
serverless (which is\r\nproject style), stateful project style (set
through spaces settings),\r\nand classic style (the old breadcrumbs
we've seen for years). Whilst\r\nserverless and stateful project style
both use the project based style\r\nthe navigation trees are slightly
different, so the breadcrumbs results\r\nare not identical [when they
derive the
\"nav\r\ncrumbs\"](9577aa980d/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx (L55)).\r\n\r\nHere
\"project style\" will refer to serverless and stateful
project\r\nstyle.\r\n\r\nIn these changes I've, for the most part, tried
to refactor things so\r\nObservability solutions route their breadcrumbs
through the\r\nobservability-shared `useBreadcrumbs` hook, this way the
logic around\r\nproject style, adding an Observability crumb in classic
etc is\r\nconsolidated in one place.\r\n\r\n[For several solutions
`absolute` breadcrumbs are
being\r\nused](9577aa980d/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx (L46)),\r\nand
this means we'll roughly have the same breadcrumbs across the
3\r\nexperiences (bar Observability being prepended). Teams may want
to\r\nrefine this going forward to pass curated breadcrumbs that take
into\r\naccount the navigation derived \"nav crumbs\" (again, bearing in
mind the\r\ntrees from serverless and project based chrome do differ),
and not use\r\nabsolute mode. APM is an example of this at the moment.
Right now this\r\nis an 8.16 bug though, so this aims to make things
acceptable, but not\r\nnecessarily perfect.\r\n\r\n### APM\r\n\r\n-
Project style chrome crumbs have been modelled off the
serverless\r\nones. The navigation trees here are the same so this
should be fine.\r\n\r\n### Infra\r\n\r\n- The `infra` `useBreadcrumbs`
hook has been removed, it was only being\r\nused by logs. Logs now goes
via the Observability shared hook using\r\n`classicOnly`.\r\n\r\n-
Metrics (`useMetricsBreadcrumbs` hook) has been slightly amended
to\r\nroute more of it's logic through the shared hook.\r\n\r\n### Logs
explorer\r\n\r\n- Wasn't setting any nested breadcrumbs at the moment so
the logic has\r\nbeen simplified to just set some classic crumbs, and
defer the rest to\r\nthe nav crumbs via the shared hook.\r\n\r\n###
Synthetics \r\n\r\n- Removed custom logic around prepending
Observability, adding link\r\nhandlers etc in favour of the shared
hook.\r\n\r\n\r\n### Alerts / rules / SLOs etc\r\n\r\n- Simple
breadcrumb needs so these are mostly setting `classicOnly`
and\r\ndeferring to the nav crumbs in project style.\r\n\r\nSeveral
solutions have had their usage of the shared hook updated to\r\npass in
the `serverless` plugin. This was missing before, so calls
to\r\n`serverless.setBreadcrumbs` weren't explicitly
happening.\r\n\r\n## Testing \r\n\r\n- Add the following to your
`kibana.dev.yml`:\r\n\r\n```yml\r\nxpack.cloud.id:
\"ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=\"\r\nxpack.cloud.base_url:
\"https://cloud.elastic.co\"\r\n```\r\n\r\n- For testing stateful
project style chrome you'll need to go to Stack\r\nManagement > Spaces
and change the solution view:\r\n\r\n![Screenshot 2024-10-21 at 12
44\r\n21](https://github.com/user-attachments/assets/e3d9fe64-f79f-4e31-a5b6-45a06ca4915d)\r\n\r\n-
Set the above to Classic to test classic breadcrumbs.\r\n\r\n- As a
reviewer please check your solution against the 3 modes.\r\n\r\n##
Comparison\r\n\r\nBefore these changes we'd see something like the
following in APM:\r\n\r\n![Screenshot 2024-10-11 at 10
56\r\n54](https://github.com/user-attachments/assets/4938b31e-9d4a-429e-abf0-add04d69b62a)\r\n\r\nNow
we'll see something like this in project style:\r\n\r\n![Screenshot
2024-10-21 at 12
48\r\n54](https://github.com/user-attachments/assets/0645a3ae-909e-4a70-a077-d9f83bd1d639)","sha":"641d9159451447484f3940f0b1c17438472fea5c"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Kerry Gallagher <kerry.gallagher@elastic.co>
This commit is contained in:
Kibana Machine 2024-10-31 02:57:01 +11:00 committed by GitHub
parent 07ea8ebd81
commit d92ea8356c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 371 additions and 409 deletions

View file

@ -76,7 +76,7 @@ export function BreadcrumbsContextProvider({ children }: { children: React.React
};
});
useBreadcrumbs(formattedBreadcrumbs, { serverless });
useBreadcrumbs(formattedBreadcrumbs, { serverless, absoluteProjectStyleBreadcrumbs: false });
return <BreadcrumbsContext.Provider value={api}>{children}</BreadcrumbsContext.Provider>;
}

View file

@ -8,8 +8,10 @@
import { useCurrentRoute } from '@kbn/typed-react-router-config';
import { useContext, useEffect, useRef } from 'react';
import { castArray } from 'lodash';
import useObservable from 'react-use/lib/useObservable';
import { Breadcrumb, BreadcrumbsContext } from './context';
import { useKibanaEnvironmentContext } from '../kibana_environment_context/use_kibana_environment_context';
import { useKibana } from '../kibana_context/use_kibana';
export function useBreadcrumb(
callback: () => Breadcrumb | Breadcrumb[],
@ -17,6 +19,9 @@ export function useBreadcrumb(
options?: { omitRootOnServerless?: boolean; omitOnServerless?: boolean }
) {
const { isServerlessEnv } = useKibanaEnvironmentContext();
const {
services: { chrome },
} = useKibana();
const { omitRootOnServerless = false, omitOnServerless = false } = options || {};
const api = useContext(BreadcrumbsContext);
@ -29,8 +34,11 @@ export function useBreadcrumb(
const matchedRoute = useRef(match?.route);
const chromeStyle = useObservable(chrome.getChromeStyle$());
useEffect(() => {
if (isServerlessEnv && omitOnServerless) {
const isProjectStyle = isServerlessEnv || chromeStyle === 'project';
if (isProjectStyle && omitOnServerless) {
return;
}
@ -42,10 +50,9 @@ export function useBreadcrumb(
if (matchedRoute.current) {
const breadcrumbs = castArray(callback());
api.set(
matchedRoute.current,
isServerlessEnv && omitRootOnServerless && breadcrumbs.length >= 1
isProjectStyle && omitRootOnServerless && breadcrumbs.length >= 1
? breadcrumbs.slice(1)
: breadcrumbs
);
@ -57,5 +64,5 @@ export function useBreadcrumb(
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [match, ...fnDeps]);
}, [match, chromeStyle, ...fnDeps]);
}

View file

@ -60,7 +60,7 @@ export function ExploratoryViewPage({
}),
},
],
{ app }
{ app, classicOnly: true }
);
const kbnUrlStateStorage = useSessionStorage

View file

@ -50,18 +50,15 @@ export const Page = ({ tabs = [], links = [] }: ContentTemplateProps) => {
const parentBreadcrumbResolver = useParentBreadcrumbResolver();
const breadcrumbOptions = parentBreadcrumbResolver.getBreadcrumbOptions(asset.type);
useMetricsBreadcrumbs(
[
{
...breadcrumbOptions.link,
text: breadcrumbOptions.text,
},
{
text: asset.name,
},
],
{ deeperContextServerless: true }
);
useMetricsBreadcrumbs([
{
...breadcrumbOptions.link,
text: breadcrumbOptions.text,
},
{
text: asset.name,
},
]);
useEffect(() => {
if (trackOnlyOnce.current) {

View file

@ -1,42 +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 { ChromeBreadcrumb } from '@kbn/core/public';
import { useEffect } from 'react';
import { useLinkProps } from '@kbn/observability-shared-plugin/public';
import { observabilityTitle } from '../translations';
import { useKibanaContextForPlugin } from './use_kibana';
type AppId = 'logs' | 'metrics';
export const useBreadcrumbs = (app: AppId, appTitle: string, extraCrumbs: ChromeBreadcrumb[]) => {
const {
services: { chrome },
} = useKibanaContextForPlugin();
const observabilityLinkProps = useLinkProps({ app: 'observability-overview' });
const appLinkProps = useLinkProps({ app });
useEffect(() => {
const breadcrumbs = [
{
...observabilityLinkProps,
text: observabilityTitle,
},
{
...appLinkProps,
text: appTitle,
},
...extraCrumbs,
];
const docTitle = [...breadcrumbs].reverse().map((breadcrumb) => breadcrumb.text as string);
chrome.docTitle.change(docTitle);
chrome.setBreadcrumbs(breadcrumbs);
}, [appLinkProps, appTitle, chrome, extraCrumbs, observabilityLinkProps]);
};

View file

@ -6,10 +6,18 @@
*/
import { ChromeBreadcrumb } from '@kbn/core/public';
import { useBreadcrumbs } from './use_breadcrumbs';
import { useBreadcrumbs, useLinkProps } from '@kbn/observability-shared-plugin/public';
import { LOGS_APP } from '../../common/constants';
import { logsTitle } from '../translations';
export const useLogsBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => {
useBreadcrumbs(LOGS_APP, logsTitle, extraCrumbs);
const appLinkProps = useLinkProps({ app: LOGS_APP });
const breadcrumbs = [
{
...appLinkProps,
text: logsTitle,
},
...extraCrumbs,
];
useBreadcrumbs(breadcrumbs, { classicOnly: true });
};

View file

@ -5,42 +5,25 @@
* 2.0.
*/
import { useEffect, useMemo } from 'react';
import { ChromeBreadcrumb } from '@kbn/core/public';
import { useBreadcrumbs, useLinkProps } from '@kbn/observability-shared-plugin/public';
import { METRICS_APP } from '../../common/constants';
import { metricsTitle } from '../translations';
import { useKibanaContextForPlugin } from './use_kibana';
export const useMetricsBreadcrumbs = (
extraCrumbs: ChromeBreadcrumb[],
options?: { deeperContextServerless: boolean }
) => {
export const useMetricsBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => {
const {
services: { serverless },
} = useKibanaContextForPlugin();
const appLinkProps = useLinkProps({ app: METRICS_APP });
const breadcrumbs = useMemo(
() => [
{
...appLinkProps,
text: metricsTitle,
},
...extraCrumbs,
],
[appLinkProps, extraCrumbs]
);
const breadcrumbs = [
{
...appLinkProps,
text: metricsTitle,
},
...extraCrumbs,
];
useBreadcrumbs(breadcrumbs);
useEffect(() => {
// For deeper context breadcrumbs in serveless, the `serverless` plugin provides its own breadcrumb service.
// https://docs.elastic.dev/kibana-dev-docs/serverless-project-navigation#breadcrumbs
if (serverless && options?.deeperContextServerless) {
// The initial path is already set in the breadcrumbs
const [, ...serverlessBreadcrumbs] = breadcrumbs;
serverless.setBreadcrumbs(serverlessBreadcrumbs);
}
}, [breadcrumbs, options?.deeperContextServerless, serverless]);
useBreadcrumbs(breadcrumbs, { serverless });
};

View file

@ -54,18 +54,15 @@ export const MetricDetailPage = () => {
});
const breadcrumbOptions = parentBreadcrumbResolver.getBreadcrumbOptions(nodeType);
useMetricsBreadcrumbs(
[
{
...breadcrumbOptions.link,
text: breadcrumbOptions.text,
},
{
text: name,
},
],
{ deeperContextServerless: true }
);
useMetricsBreadcrumbs([
{
...breadcrumbOptions.link,
text: breadcrumbOptions.text,
},
{
text: name,
},
]);
const [sideNav, setSideNav] = useState<NavItem[]>([]);

View file

@ -39,7 +39,7 @@ export const metricsTitle = i18n.translate('xpack.infra.header.infrastructureTit
});
export const inventoryTitle = i18n.translate('xpack.infra.metrics.infrastructureInventoryTitle', {
defaultMessage: 'Infrastructure Inventory',
defaultMessage: 'Infrastructure inventory',
});
export const metricsExplorerTitle = i18n.translate('xpack.infra.metrics.metricsExplorerTitle', {

View file

@ -93,6 +93,7 @@ export function AlertDetails() {
triggersActionsUi: { ruleTypeRegistry },
observabilityAIAssistant,
uiSettings,
serverless,
} = useKibana().services;
const { search } = useLocation();
@ -158,20 +159,23 @@ export function AlertDetails() {
}
}, [alertDetail, ruleTypeRegistry]);
useBreadcrumbs([
{
href: http.basePath.prepend(paths.observability.alerts),
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
defaultMessage: 'Alerts',
}),
deepLinkId: 'observability-overview:alerts',
},
{
text: alertDetail
? getPageTitle(alertDetail.formatted.fields[ALERT_RULE_CATEGORY])
: defaultBreadcrumb,
},
]);
useBreadcrumbs(
[
{
href: http.basePath.prepend(paths.observability.alerts),
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
defaultMessage: 'Alerts',
}),
deepLinkId: 'observability-overview:alerts',
},
{
text: alertDetail
? getPageTitle(alertDetail.formatted.fields[ALERT_RULE_CATEGORY])
: defaultBreadcrumb,
},
],
{ serverless }
);
const onUntrackAlert = () => {
setAlertStatus(ALERT_STATUS_UNTRACKED);

View file

@ -159,13 +159,18 @@ function InternalAlertsPage() {
[alertSearchBarStateProps.rangeFrom, alertSearchBarStateProps.rangeTo, bucketSize, esQuery]
);
useBreadcrumbs([
useBreadcrumbs(
[
{
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
defaultMessage: 'Alerts',
}),
},
],
{
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
defaultMessage: 'Alerts',
}),
},
]);
classicOnly: true,
}
);
async function loadRuleStats() {
setRuleStatsLoading(true);

View file

@ -56,13 +56,18 @@ export function OverviewPage() {
const { ObservabilityPageTemplate, observabilityRuleTypeRegistry } = usePluginContext();
useBreadcrumbs([
useBreadcrumbs(
[
{
text: i18n.translate('xpack.observability.breadcrumbs.overviewLinkText', {
defaultMessage: 'Overview',
}),
},
],
{
text: i18n.translate('xpack.observability.breadcrumbs.overviewLinkText', {
defaultMessage: 'Overview',
}),
},
]);
classicOnly: true,
}
);
const { data: newsFeed } = useFetcher(
() => getNewsFeed({ http, kibanaVersion }),

View file

@ -61,6 +61,7 @@ export function RuleDetailsPage() {
getRuleDefinition: RuleDefinition,
getRuleStatusPanel: RuleStatusPanel,
},
serverless,
} = useKibana().services;
const { ObservabilityPageTemplate } = usePluginContext();
@ -72,24 +73,27 @@ export function RuleDetailsPage() {
filterByRuleTypeIds: filteredRuleTypes,
});
useBreadcrumbs([
{
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
defaultMessage: 'Alerts',
}),
href: basePath.prepend(paths.observability.alerts),
deepLinkId: 'observability-overview:alerts',
},
{
href: basePath.prepend(paths.observability.rules),
text: i18n.translate('xpack.observability.breadcrumbs.rulesLinkText', {
defaultMessage: 'Rules',
}),
},
{
text: rule && rule.name,
},
]);
useBreadcrumbs(
[
{
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
defaultMessage: 'Alerts',
}),
href: basePath.prepend(paths.observability.alerts),
deepLinkId: 'observability-overview:alerts',
},
{
href: basePath.prepend(paths.observability.rules),
text: i18n.translate('xpack.observability.breadcrumbs.rulesLinkText', {
defaultMessage: 'Rules',
}),
},
{
text: rule && rule.name,
},
],
{ serverless }
);
const [activeTabId, setActiveTabId] = useState<TabId>(() => {
const searchParams = new URLSearchParams(search);

View file

@ -42,6 +42,7 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) {
getAddRuleFlyout: AddRuleFlyout,
getRulesSettingsLink: RulesSettingsLink,
},
serverless,
} = useKibana().services;
const { ObservabilityPageTemplate } = usePluginContext();
const history = useHistory();
@ -50,20 +51,23 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) {
const [addRuleFlyoutVisibility, setAddRuleFlyoutVisibility] = useState(false);
const [stateRefresh, setRefresh] = useState(new Date());
useBreadcrumbs([
{
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
defaultMessage: 'Alerts',
}),
href: http.basePath.prepend('/app/observability/alerts'),
deepLinkId: 'observability-overview:alerts',
},
{
text: i18n.translate('xpack.observability.breadcrumbs.rulesLinkText', {
defaultMessage: 'Rules',
}),
},
]);
useBreadcrumbs(
[
{
text: i18n.translate('xpack.observability.breadcrumbs.alertsLinkText', {
defaultMessage: 'Alerts',
}),
href: http.basePath.prepend('/app/observability/alerts'),
deepLinkId: 'observability-overview:alerts',
},
{
text: i18n.translate('xpack.observability.breadcrumbs.rulesLinkText', {
defaultMessage: 'Rules',
}),
},
],
{ serverless }
);
const filteredRuleTypes = useGetFilteredRuleTypes();
const {

View file

@ -21,26 +21,17 @@ import {
} from '../../state_machines/observability_logs_explorer/src';
import { LazyOriginInterpreter } from '../../state_machines/origin_interpreter/src/lazy_component';
import { ObservabilityLogsExplorerHistory } from '../../types';
import { noBreadcrumbs, useBreadcrumbs } from '../../utils/breadcrumbs';
import { useBreadcrumbs } from '../../utils/breadcrumbs';
import { useKbnUrlStateStorageFromRouterContext } from '../../utils/kbn_url_state_context';
import { useKibanaContextForPlugin } from '../../utils/use_kibana';
export const ObservabilityLogsExplorerMainRoute = () => {
const { services } = useKibanaContextForPlugin();
const {
logsExplorer,
serverless,
chrome,
notifications,
appParams,
analytics,
i18n,
theme,
logsDataAccess,
} = services;
const { logsExplorer, notifications, appParams, analytics, i18n, theme, logsDataAccess } =
services;
const { history } = appParams;
useBreadcrumbs(noBreadcrumbs, chrome, serverless);
useBreadcrumbs();
const urlStateStorageContainer = useKbnUrlStateStorageFromRouterContext();

View file

@ -5,71 +5,27 @@
* 2.0.
*/
import { EuiBreadcrumb } from '@elastic/eui';
import type { ChromeStart } from '@kbn/core-chrome-browser';
import {
LOGS_APP_ID,
OBSERVABILITY_LOGS_EXPLORER_APP_ID,
OBSERVABILITY_OVERVIEW_APP_ID,
} from '@kbn/deeplinks-observability';
import { LOGS_APP_ID, OBSERVABILITY_LOGS_EXPLORER_APP_ID } from '@kbn/deeplinks-observability';
import { useLinkProps } from '@kbn/observability-shared-plugin/public';
import type { ServerlessPluginStart } from '@kbn/serverless/public';
import { useEffect } from 'react';
import {
logsExplorerAppTitle,
logsAppTitle,
observabilityAppTitle,
} from '../../common/translations';
import { useMemo } from 'react';
import { useBreadcrumbs as observabilityUseBreadcrumbs } from '@kbn/observability-shared-plugin/public';
import { logsExplorerAppTitle, logsAppTitle } from '../../common/translations';
export const useBreadcrumbs = (
breadcrumbs: EuiBreadcrumb[],
chromeService: ChromeStart,
serverlessService?: ServerlessPluginStart
) => {
const observabilityLinkProps = useLinkProps({ app: OBSERVABILITY_OVERVIEW_APP_ID });
export const useBreadcrumbs = () => {
const logsLinkProps = useLinkProps({ app: LOGS_APP_ID });
const logsExplorerLinkProps = useLinkProps({ app: OBSERVABILITY_LOGS_EXPLORER_APP_ID });
const classicCrumbs = useMemo(() => {
return [
{
text: logsAppTitle,
...logsLinkProps,
},
{
text: logsExplorerAppTitle,
...logsExplorerLinkProps,
},
];
}, [logsExplorerLinkProps, logsLinkProps]);
useEffect(() => {
setBreadcrumbs(
serverlessService
? breadcrumbs
: [
{
text: observabilityAppTitle,
...observabilityLinkProps,
},
{
text: logsAppTitle,
...logsLinkProps,
},
{
text: logsExplorerAppTitle,
...logsExplorerLinkProps,
},
...breadcrumbs,
],
chromeService,
serverlessService
);
}, [breadcrumbs, chromeService, serverlessService]); // eslint-disable-line react-hooks/exhaustive-deps
observabilityUseBreadcrumbs(classicCrumbs, { classicOnly: true });
};
export function setBreadcrumbs(
breadcrumbs: EuiBreadcrumb[],
chromeService: ChromeStart,
serverlessService?: ServerlessPluginStart
) {
chromeService.docTitle.change(getDocTitle(breadcrumbs));
if (serverlessService) {
serverlessService.setBreadcrumbs(breadcrumbs);
} else if (chromeService) {
chromeService.setBreadcrumbs(breadcrumbs);
}
}
export function getDocTitle(breadcrumbs: EuiBreadcrumb[]) {
return breadcrumbs.map(({ text }) => text as string).reverse();
}
export const noBreadcrumbs: EuiBreadcrumb[] = [];

View file

@ -13,7 +13,6 @@
"kbn_references": [
"@kbn/config-schema",
"@kbn/core",
"@kbn/core-chrome-browser",
"@kbn/core-mount-utils-browser-internal",
"@kbn/core-notifications-browser",
"@kbn/data-plugin",

View file

@ -11,12 +11,18 @@ import { MemoryRouter } from 'react-router-dom';
import { CoreStart } from '@kbn/core/public';
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
import { useBreadcrumbs } from './use_breadcrumbs';
import { BehaviorSubject } from 'rxjs';
import { ChromeStyle } from '@kbn/core-chrome-browser';
const setBreadcrumbs = jest.fn();
const setTitle = jest.fn();
const kibanaServices = {
application: { getUrlForApp: () => {}, navigateToApp: () => {} },
chrome: { setBreadcrumbs, docTitle: { change: setTitle } },
chrome: {
setBreadcrumbs,
docTitle: { change: setTitle },
getChromeStyle$: () => new BehaviorSubject<ChromeStyle>('classic').asObservable(),
},
uiSettings: { get: () => true },
settings: { client: { get: () => true } },
} as unknown as Partial<CoreStart>;
@ -61,9 +67,15 @@ describe('useBreadcrumbs', () => {
it('sets the overview breadcrumb', () => {
renderHook(() => useBreadcrumbs([]), { wrapper: Wrapper });
expect(setBreadcrumbs).toHaveBeenCalledWith([
{ href: '/overview', onClick: expect.any(Function), text: 'Observability' },
]);
expect(setBreadcrumbs).toHaveBeenCalledWith(
[{ href: '/overview', onClick: expect.any(Function), text: 'Observability' }],
{
project: {
absolute: true,
value: [{ href: '/overview', onClick: expect.any(Function), text: 'Observability' }],
},
}
);
});
it('sets the overview title', () => {
@ -86,17 +98,29 @@ describe('useBreadcrumbs', () => {
{ wrapper: Wrapper }
);
expect(setBreadcrumbs).toHaveBeenCalledWith([
{ href: '/overview', onClick: expect.any(Function), text: 'Observability' },
expect(setBreadcrumbs).toHaveBeenCalledWith(
[
{ href: '/overview', onClick: expect.any(Function), text: 'Observability' },
{
href: '/one',
onClick: expect.any(Function),
text: 'One',
},
{
text: 'Two',
},
],
{
href: '/one',
onClick: expect.any(Function),
text: 'One',
},
{
text: 'Two',
},
]);
project: {
absolute: true,
value: [
{ href: '/overview', onClick: expect.any(Function), text: 'Observability' },
{ href: '/one', onClick: expect.any(Function), text: 'One' },
{ text: 'Two' },
],
},
}
);
});
it('sets the title', () => {

View file

@ -11,8 +11,16 @@ import { MouseEvent, useEffect, useMemo } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ChromeBreadcrumbsAppendExtension } from '@kbn/core-chrome-browser';
import type { ServerlessPluginStart } from '@kbn/serverless/public';
import useObservable from 'react-use/lib/useObservable';
import { useQueryParams } from './use_query_params';
const OBSERVABILITY_TEXT = i18n.translate(
'xpack.observabilityShared.breadcrumbs.observabilityLinkText',
{
defaultMessage: 'Observability',
}
);
function addClickHandlers(
breadcrumbs: ChromeBreadcrumb[],
navigateToHref?: (url: string) => Promise<void>
@ -33,23 +41,49 @@ function addClickHandlers(
}
function getTitleFromBreadCrumbs(breadcrumbs: ChromeBreadcrumb[]) {
return breadcrumbs.map(({ text }) => text?.toString() ?? '').reverse();
return breadcrumbs
.map(({ text }) => text?.toString() ?? '')
.reverse()
.concat([OBSERVABILITY_TEXT]);
}
/**
*
* By default the breadcrumbs will be passed to either serverless.setBreadcrumbs or chrome.setBreadcrumbs depending on the
* environment. The breadcrumbs will *also* be passed to the project style breadcrumbs for stateful project style. We will use "project style"
* here to refer to serverless chrome and stateful project style chrome. Classic refers to stateful classic chrome.
*
* Project style breadcrumbs add a root crumb ("deployment" etc) and "nav crumbs" which are derived from the navigation structure. By default
* the "absolute" mode is used which means the breadcrumbs passed here will omit the navigation derived "nav crumbs". You can pass
* absoluteProjectStyleBreadcrumbs: false to include the 'smart' "nav crumbs".
*
* In classic mode (not project style) the 'Observability' crumb is added.
*
* You can also pass classicOnly to only set breadrumbs in the classic chrome context. This can be useful if your solution just wants to defer all project style crumbs to the "nav crumbs".
*/
export const useBreadcrumbs = (
extraCrumbs: ChromeBreadcrumb[],
options?: {
app?: { id: string; label: string };
breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension;
serverless?: ServerlessPluginStart;
absoluteProjectStyleBreadcrumbs?: boolean;
classicOnly?: boolean;
}
) => {
const params = useQueryParams();
const { app, breadcrumbsAppendExtension, serverless } = options ?? {};
const absolute = options?.absoluteProjectStyleBreadcrumbs === false ? false : true;
const classicOnly = options?.classicOnly ?? false;
const {
services: {
chrome: { docTitle, setBreadcrumbs: chromeSetBreadcrumbs, setBreadcrumbsAppendExtension },
chrome: {
docTitle,
setBreadcrumbs: chromeSetBreadcrumbs,
setBreadcrumbsAppendExtension,
getChromeStyle$,
},
application: { getUrlForApp, navigateToUrl },
},
} = useKibana<{
@ -58,11 +92,27 @@ export const useBreadcrumbs = (
}>();
const setTitle = docTitle.change;
const appPath = getUrlForApp(app?.id ?? 'observability-overview') ?? '';
const chromeStyle = useObservable(getChromeStyle$());
const setBreadcrumbs = useMemo(
() => serverless?.setBreadcrumbs ?? chromeSetBreadcrumbs,
[serverless, chromeSetBreadcrumbs]
);
const setBreadcrumbs = useMemo(() => {
if (!serverless?.setBreadcrumbs) {
return (breadcrumbs: ChromeBreadcrumb[]) =>
chromeSetBreadcrumbs(
breadcrumbs,
!classicOnly
? {
project: {
value: breadcrumbs,
absolute,
},
}
: undefined
);
}
if (!classicOnly)
return (breadcrumbs: ChromeBreadcrumb[]) =>
serverless?.setBreadcrumbs(breadcrumbs, { absolute });
}, [serverless, classicOnly, absolute, chromeSetBreadcrumbs]);
useEffect(() => {
if (breadcrumbsAppendExtension) {
@ -76,15 +126,12 @@ export const useBreadcrumbs = (
}, [breadcrumbsAppendExtension, setBreadcrumbsAppendExtension]);
useEffect(() => {
const breadcrumbs = serverless
const isProjectStyle = serverless || chromeStyle === 'project';
const breadcrumbs = isProjectStyle
? extraCrumbs
: [
{
text:
app?.label ??
i18n.translate('xpack.observabilityShared.breadcrumbs.observabilityLinkText', {
defaultMessage: 'Observability',
}),
text: app?.label ?? OBSERVABILITY_TEXT,
href: appPath + '/overview',
},
...extraCrumbs,
@ -94,11 +141,12 @@ export const useBreadcrumbs = (
setBreadcrumbs(addClickHandlers(breadcrumbs, navigateToUrl));
}
if (setTitle) {
setTitle(getTitleFromBreadCrumbs(breadcrumbs));
setTitle(getTitleFromBreadCrumbs(extraCrumbs));
}
}, [
app?.label,
appPath,
chromeStyle,
extraCrumbs,
navigateToUrl,
params,

View file

@ -40,6 +40,7 @@ export function SloDetailsPage() {
application: { navigateToUrl },
http: { basePath },
observabilityAIAssistant,
serverless,
} = useKibana().services;
const { ObservabilityPageTemplate } = usePluginContext();
const { hasAtLeast } = useLicense();
@ -105,7 +106,7 @@ export function SloDetailsPage() {
}
}, [onPageReady, slo, isLoading]);
useBreadcrumbs(getBreadcrumbs(basePath, slo));
useBreadcrumbs(getBreadcrumbs(basePath, slo), { serverless });
const isSloNotFound = !isLoading && slo === undefined;
if (isSloNotFound) {

View file

@ -22,6 +22,7 @@ export function SloEditPage() {
const {
application: { navigateToUrl },
http: { basePath },
serverless,
} = useKibana().services;
const { sloId } = useParams<{ sloId: string | undefined }>();
@ -32,32 +33,35 @@ export function SloEditPage() {
const hasRightLicense = hasAtLeast('platinum');
const { data: slo } = useFetchSloDetails({ sloId });
useBreadcrumbs([
{
href: basePath.prepend(paths.slos),
text: i18n.translate('xpack.slo.breadcrumbs.sloLabel', {
defaultMessage: 'SLOs',
}),
deepLinkId: 'slo',
},
...(!!slo
? [
{
href: basePath.prepend(paths.sloDetails(slo!.id)),
text: slo!.name,
},
]
: []),
{
text: slo
? i18n.translate('xpack.slo.breadcrumbs.sloEditLabel', {
defaultMessage: 'Edit',
})
: i18n.translate('xpack.slo.breadcrumbs.sloCreateLabel', {
defaultMessage: 'Create',
}),
},
]);
useBreadcrumbs(
[
{
href: basePath.prepend(paths.slos),
text: i18n.translate('xpack.slo.breadcrumbs.sloLabel', {
defaultMessage: 'SLOs',
}),
deepLinkId: 'slo',
},
...(!!slo
? [
{
href: basePath.prepend(paths.sloDetails(slo!.id)),
text: slo!.name,
},
]
: []),
{
text: slo
? i18n.translate('xpack.slo.breadcrumbs.sloEditLabel', {
defaultMessage: 'Edit',
})
: i18n.translate('xpack.slo.breadcrumbs.sloCreateLabel', {
defaultMessage: 'Create',
}),
},
],
{ serverless }
);
useEffect(() => {
if (hasRightLicense === false || permissions?.hasAllReadRequested === false) {

View file

@ -23,26 +23,30 @@ import { OutdatedSloSearchBar } from './outdated_slo_search_bar';
export function SlosOutdatedDefinitions() {
const {
http: { basePath },
serverless,
} = useKibana().services;
const { data: permissions } = usePermissions();
const { ObservabilityPageTemplate } = usePluginContext();
const { hasAtLeast } = useLicense();
useBreadcrumbs([
{
href: basePath.prepend(paths.slos),
text: i18n.translate('xpack.slo.breadcrumbs.slosLinkText', {
defaultMessage: 'SLOs',
}),
deepLinkId: 'slo',
},
{
text: i18n.translate('xpack.slo.breadcrumbs.slosOutdatedDefinitions', {
defaultMessage: 'Outdated SLO Definitions',
}),
},
]);
useBreadcrumbs(
[
{
href: basePath.prepend(paths.slos),
text: i18n.translate('xpack.slo.breadcrumbs.slosLinkText', {
defaultMessage: 'SLOs',
}),
deepLinkId: 'slo',
},
{
text: i18n.translate('xpack.slo.breadcrumbs.slosOutdatedDefinitions', {
defaultMessage: 'Outdated SLO Definitions',
}),
},
],
{ serverless }
);
const [search, setSearch] = useState<string>('');
const [activePage, setActivePage] = useState<number>(0);

View file

@ -16,17 +16,21 @@ import { HeaderMenu } from '../../components/header_menu/header_menu';
export function SloSettingsPage() {
const {
http: { basePath },
serverless,
} = useKibana().services;
const { ObservabilityPageTemplate } = usePluginContext();
useBreadcrumbs([
{
href: basePath.prepend(paths.slosSettings),
text: i18n.translate('xpack.slo.breadcrumbs.slosSettingsText', {
defaultMessage: 'SLOs Settings',
}),
},
]);
useBreadcrumbs(
[
{
href: basePath.prepend(paths.slosSettings),
text: i18n.translate('xpack.slo.breadcrumbs.slosSettingsText', {
defaultMessage: 'SLOs Settings',
}),
},
],
{ serverless }
);
return (
<ObservabilityPageTemplate

View file

@ -29,6 +29,7 @@ export function SlosPage() {
const {
application: { navigateToUrl },
http: { basePath },
serverless,
} = useKibana().services;
const { ObservabilityPageTemplate } = usePluginContext();
const { hasAtLeast } = useLicense();
@ -37,15 +38,18 @@ export function SlosPage() {
const { isLoading, isError, data: sloList } = useFetchSloList({ perPage: 0 });
const { total } = sloList ?? { total: 0 };
useBreadcrumbs([
{
href: basePath.prepend(paths.slos),
text: i18n.translate('xpack.slo.breadcrumbs.slosLinkText', {
defaultMessage: 'SLOs',
}),
deepLinkId: 'slo',
},
]);
useBreadcrumbs(
[
{
href: basePath.prepend(paths.slos),
text: i18n.translate('xpack.slo.breadcrumbs.slosLinkText', {
defaultMessage: 'SLOs',
}),
deepLinkId: 'slo',
},
],
{ serverless }
);
useEffect(() => {
if ((!isLoading && total === 0) || hasAtLeast('platinum') === false || isError) {

View file

@ -38,7 +38,6 @@ export interface SyntheticsAppProps {
setBadge: (badge?: ChromeBadge) => void;
renderGlobalHelpControls(): void;
commonlyUsedRanges: CommonlyUsedDateRange[];
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
appMountParameters: AppMountParameters;
isDev: boolean;
isServerless: boolean;
@ -89,7 +88,6 @@ export const SyntheticsSettingsContextProvider: React.FC<PropsWithChildren<Synth
isLogsAvailable,
commonlyUsedRanges,
isDev,
setBreadcrumbs,
isServerless,
} = props;
@ -110,7 +108,6 @@ export const SyntheticsSettingsContextProvider: React.FC<PropsWithChildren<Synth
commonlyUsedRanges,
dateRangeStart: dateRangeStart ?? DATE_RANGE_START,
dateRangeEnd: dateRangeEnd ?? DATE_RANGE_END,
setBreadcrumbs,
isServerless,
};
}, [
@ -123,7 +120,6 @@ export const SyntheticsSettingsContextProvider: React.FC<PropsWithChildren<Synth
dateRangeStart,
dateRangeEnd,
commonlyUsedRanges,
setBreadcrumbs,
isServerless,
]);

View file

@ -40,6 +40,7 @@ export const SyntheticsSharedContext: React.FC<
unifiedSearch: startPlugins.unifiedSearch,
embeddable: startPlugins.embeddable,
slo: startPlugins.slo,
serverless: startPlugins.serverless,
}}
>
<EuiThemeProvider darkMode={darkMode}>

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { coreMock } from '@kbn/core/public/mocks';
import { ChromeBreadcrumb } from '@kbn/core/public';
import { render } from '../utils/testing';
import React from 'react';
@ -18,6 +19,8 @@ import {
} from '../utils/url_params/get_supported_url_params';
import { makeBaseBreadcrumb, useBreadcrumbs } from './use_breadcrumbs';
import { SyntheticsSettingsContext } from '../contexts';
import { BehaviorSubject } from 'rxjs';
import { ChromeStyle } from '@kbn/core-chrome-browser';
describe('useBreadcrumbs', () => {
it('sets the given breadcrumbs', () => {
@ -71,9 +74,10 @@ describe('useBreadcrumbs', () => {
const urlParams: SyntheticsUrlParams = getSupportedUrlParams({});
expect(JSON.stringify(getBreadcrumbs())).toEqual(
JSON.stringify(
makeBaseBreadcrumb('/app/synthetics', '/app/observability', urlParams, false).concat(
expectedCrumbs
)
[
{ text: 'Observability', href: '/app/observability/overview' },
...makeBaseBreadcrumb('/app/synthetics', urlParams),
].concat(expectedCrumbs)
)
);
});
@ -84,6 +88,8 @@ const mockCore: () => [() => ChromeBreadcrumb[], any] = () => {
const get = () => {
return breadcrumbObj;
};
const defaultCoreMock = coreMock.createStart();
const core = {
application: {
getUrlForApp: (app: string) =>
@ -91,6 +97,8 @@ const mockCore: () => [() => ChromeBreadcrumb[], any] = () => {
navigateToUrl: jest.fn(),
},
chrome: {
...defaultCoreMock.chrome,
getChromeStyle$: () => new BehaviorSubject<ChromeStyle>('classic').asObservable(),
setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => {
breadcrumbObj = newBreadcrumbs;
},

View file

@ -7,47 +7,20 @@
import { ChromeBreadcrumb } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { MouseEvent, useContext, useEffect } from 'react';
import { useMemo } from 'react';
import { EuiBreadcrumb } from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useBreadcrumbs as useObservabilityBreadcrumbs } from '@kbn/observability-shared-plugin/public';
import { ClientPluginsStart } from '../../../plugin';
import { SyntheticsUrlParams, stringifyUrlParams } from '../utils/url_params';
import { useUrlParams } from './use_url_params';
import { PLUGIN } from '../../../../common/constants/plugin';
import { SyntheticsSettingsContext } from '../contexts';
const EMPTY_QUERY = '?';
function handleBreadcrumbClick(
breadcrumbs: ChromeBreadcrumb[],
navigateToHref?: (url: string) => Promise<void>
) {
return breadcrumbs.map((bc) => ({
...bc,
...(bc.href
? {
onClick: (event: MouseEvent) => {
if (navigateToHref && bc.href) {
event.preventDefault();
navigateToHref(bc.href);
}
},
}
: {}),
...(bc['data-test-subj']
? {
'data-test-subj': bc['data-test-subj'],
}
: {
'data-test-subj': bc.href,
}),
}));
}
export const makeBaseBreadcrumb = (
uptimePath: string,
observabilityPath: string,
params?: SyntheticsUrlParams,
isServerless?: boolean
params?: SyntheticsUrlParams
): EuiBreadcrumb[] => {
if (params) {
const crumbParams: Partial<SyntheticsUrlParams> = { ...params };
@ -59,18 +32,6 @@ export const makeBaseBreadcrumb = (
const baseBreadcrumbs: EuiBreadcrumb[] = [];
// serverless Kibana has a curated UX flow, and "Observability" is already a given,
// thus we don't need to include it explicitly in the breadcrumb trail
if (!isServerless) {
baseBreadcrumbs.push({
text: i18n.translate('xpack.synthetics.breadcrumbs.observabilityText', {
defaultMessage: 'Observability',
}),
href: observabilityPath,
'data-test-subj': 'observabilityPathBreadcrumb',
});
}
baseBreadcrumbs.push({
text: i18n.translate('xpack.synthetics.breadcrumbs.overviewBreadcrumbText', {
defaultMessage: 'Synthetics',
@ -84,32 +45,12 @@ export const makeBaseBreadcrumb = (
export const useBreadcrumbs = (extraCrumbs: ChromeBreadcrumb[]) => {
const params = useUrlParams()[0]();
const kibana = useKibana();
const { setBreadcrumbs, isServerless } = useContext(SyntheticsSettingsContext);
const kibana = useKibana<ClientPluginsStart>();
const syntheticsPath =
kibana.services.application?.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ?? '';
const observabilityPath =
kibana.services.application?.getUrlForApp('observability-overview') ?? '';
const navigate = kibana.services.application?.navigateToUrl;
const breadcrumbs = useMemo(() => {
return makeBaseBreadcrumb(syntheticsPath, params).concat(extraCrumbs);
}, [extraCrumbs, params, syntheticsPath]);
useEffect(() => {
if (setBreadcrumbs) {
setBreadcrumbs(
handleBreadcrumbClick(
makeBaseBreadcrumb(syntheticsPath, observabilityPath, params, isServerless).concat(
extraCrumbs
),
navigate
)
);
}
}, [
syntheticsPath,
observabilityPath,
extraCrumbs,
navigate,
params,
setBreadcrumbs,
isServerless,
]);
useObservabilityBreadcrumbs(breadcrumbs, { serverless: kibana.services.serverless });
};

View file

@ -66,7 +66,6 @@ export const getSyntheticsAppProps = (): SyntheticsAppProps => {
setBadge,
appMountParameters,
isServerless,
setBreadcrumbs: startPlugins.serverless?.setBreadcrumbs ?? coreStart.chrome.setBreadcrumbs,
};
};

View file

@ -7,7 +7,7 @@
import React, { ReactElement, ReactNode } from 'react';
import { i18n } from '@kbn/i18n';
import { of } from 'rxjs';
import { BehaviorSubject, of } from 'rxjs';
// eslint-disable-next-line import/no-extraneous-dependencies
import {
render as reactTestLibRender,
@ -29,6 +29,7 @@ import { KibanaContextProvider, KibanaServices } from '@kbn/kibana-react-plugin/
import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { ChromeStyle } from '@kbn/core-chrome-browser';
import { mockState } from './__mocks__/synthetics_store.mock';
import { MountWithReduxProvider } from './helper_with_redux';
import { AppState } from '../../state';
@ -166,6 +167,10 @@ export const mockCore: () => Partial<CoreStart> = () => {
</div>
),
},
chrome: {
...defaultCore.chrome,
getChromeStyle$: () => new BehaviorSubject<ChromeStyle>('classic').asObservable(),
},
};
return core;

View file

@ -104,7 +104,8 @@
"@kbn/babel-register",
"@kbn/slo-plugin",
"@kbn/ebt-tools",
"@kbn/alerting-types"
"@kbn/alerting-types",
"@kbn/core-chrome-browser"
],
"exclude": ["target/**/*"]
}

View file

@ -17,6 +17,8 @@ import { coreMock } from '@kbn/core/public/mocks';
import { merge } from 'lodash';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks';
import { BehaviorSubject } from 'rxjs';
import { ChromeStyle } from '@kbn/core-chrome-browser';
jest.mock('../services/rest/data_view', () => ({
createStaticDataView: () => Promise.resolve(undefined),
@ -122,6 +124,10 @@ const mockCore = merge({}, coreStart, {
return uiSettings[key];
},
},
chrome: {
...coreStart.chrome,
getChromeStyle$: () => new BehaviorSubject<ChromeStyle>('classic').asObservable(),
},
});
export const mockApmPluginContextValue = {

View file

@ -49,7 +49,8 @@
"@kbn/react-kibana-context-render",
"@kbn/react-kibana-context-theme",
"@kbn/search-types",
"@kbn/server-route-repository-utils"
"@kbn/server-route-repository-utils",
"@kbn/core-chrome-browser"
],
"exclude": ["target/**/*"]
}

View file

@ -43696,7 +43696,6 @@
"xpack.synthetics.badge.readOnly.text": "Lecture seule",
"xpack.synthetics.badge.readOnly.tooltip": "Enregistrement impossible",
"xpack.synthetics.blocked": "Bloqué",
"xpack.synthetics.breadcrumbs.observabilityText": "Observabilité",
"xpack.synthetics.breadcrumbs.overviewBreadcrumbText": "Synthetics",
"xpack.synthetics.certificates.heading": "Certificats TLS ({total})",
"xpack.synthetics.certificates.loading": "Chargement des certificats...",

View file

@ -43434,7 +43434,6 @@
"xpack.synthetics.badge.readOnly.text": "読み取り専用",
"xpack.synthetics.badge.readOnly.tooltip": "を保存できませんでした",
"xpack.synthetics.blocked": "ブロック",
"xpack.synthetics.breadcrumbs.observabilityText": "Observability",
"xpack.synthetics.breadcrumbs.overviewBreadcrumbText": "Synthetics",
"xpack.synthetics.certificates.heading": "TLS証明書{total}",
"xpack.synthetics.certificates.loading": "証明書を読み込んでいます...",

View file

@ -43484,7 +43484,6 @@
"xpack.synthetics.badge.readOnly.text": "只读",
"xpack.synthetics.badge.readOnly.tooltip": "无法保存",
"xpack.synthetics.blocked": "已阻止",
"xpack.synthetics.breadcrumbs.observabilityText": "Observability",
"xpack.synthetics.breadcrumbs.overviewBreadcrumbText": "Synthetics",
"xpack.synthetics.certificates.heading": "TLS 证书 ({total})",
"xpack.synthetics.certificates.loading": "正在加载证书......",

View file

@ -144,7 +144,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const documentTitle = await browser.getTitle();
expect(documentTitle).to.contain(
'Infrastructure Inventory - Infrastructure - Observability - Elastic'
'Infrastructure inventory - Infrastructure - Observability - Elastic'
);
});
@ -459,7 +459,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await retry.tryForTime(5000, async () => {
const documentTitle = await browser.getTitle();
expect(documentTitle).to.contain(
'host-5 - Infrastructure Inventory - Infrastructure - Observability - Elastic'
'host-5 - Infrastructure inventory - Infrastructure - Observability - Elastic'
);
});
@ -476,7 +476,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await retry.tryForTime(5000, async () => {
const documentTitle = await browser.getTitle();
expect(documentTitle).to.contain(
'pod-0 - Infrastructure Inventory - Infrastructure - Observability - Elastic'
'pod-0 - Infrastructure inventory - Infrastructure - Observability - Elastic'
);
});
@ -494,7 +494,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await retry.tryForTime(5000, async () => {
const documentTitle = await browser.getTitle();
expect(documentTitle).to.contain(
'container-id-4 - Infrastructure Inventory - Infrastructure - Observability - Elastic'
'container-id-4 - Infrastructure inventory - Infrastructure - Observability - Elastic'
);
});

View file

@ -57,7 +57,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const documentTitle = await browser.getTitle();
expect(documentTitle).to.contain(
'Infrastructure Inventory - Infrastructure - Observability - Elastic'
'Infrastructure inventory - Infrastructure - Observability - Elastic'
);
});
@ -87,7 +87,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await retry.try(async () => {
const documentTitle = await browser.getTitle();
expect(documentTitle).to.contain(
'demo-stack-redis-01 - Infrastructure Inventory - Infrastructure - Observability - Elastic'
'demo-stack-redis-01 - Infrastructure inventory - Infrastructure - Observability - Elastic'
);
});
@ -103,7 +103,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await retry.try(async () => {
const documentTitle = await browser.getTitle();
expect(documentTitle).to.contain(
'pod-0 - Infrastructure Inventory - Infrastructure - Observability - Elastic'
'pod-0 - Infrastructure inventory - Infrastructure - Observability - Elastic'
);
});