diff --git a/x-pack/plugins/observability_solution/apm/kibana.jsonc b/x-pack/plugins/observability_solution/apm/kibana.jsonc index c6a53232aaec0..0e305bc449c0e 100644 --- a/x-pack/plugins/observability_solution/apm/kibana.jsonc +++ b/x-pack/plugins/observability_solution/apm/kibana.jsonc @@ -45,6 +45,7 @@ "ml", "security", "spaces", + "serverless", "taskManager", "usageCollection", "customIntegrations", // Move this to requiredPlugins after completely migrating from the Tutorials Home App diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/breadcrumb/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/breadcrumb/index.tsx index d6afe4a7c1d19..24c4b7e88fb1b 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/breadcrumb/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/breadcrumb/index.tsx @@ -11,16 +11,22 @@ import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb'; export const Breadcrumb = ({ title, href, + omitOnServerless = false, children, }: { title: string; href: string; + omitOnServerless?: boolean; children: React.ReactElement; }) => { const { core } = useApmPluginContext(); + useBreadcrumb( () => ({ title, href: core.http.basePath.prepend('/app/apm' + href) }), - [core.http.basePath, href, title] + [core.http.basePath, href, title], + { + omitOnServerless, + } ); return children; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/dependency_detail_view/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/dependency_detail_view/index.tsx index 5f6542fc7fe15..64ba12d64c06d 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/dependency_detail_view/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/dependency_detail_view/index.tsx @@ -69,7 +69,10 @@ export function DependencyDetailView({ children }: { children: React.ReactChild rangeTo, refreshInterval, refreshPaused, - ] + ], + { + omitRootOnServerless: true, + } ); return {children}; } diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/dependency_operation_detail_view/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/dependency_operation_detail_view/index.tsx index 63a7bf42c6650..ef7ea348d37f7 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/dependency_operation_detail_view/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/dependency_operation_detail_view/index.tsx @@ -50,7 +50,12 @@ export function DependencyOperationDetailView() { }, } = useApmParams('/dependencies/operation'); - useDependencyDetailOperationsBreadcrumb(); + useDependencyDetailOperationsBreadcrumb({ + title: spanName, + href: router.link('/dependencies/operation', { + query, + }), + }); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/trace_overview/index.tsx index 9304dc298a2d6..40b079b684498 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/trace_overview/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/trace_overview/index.tsx @@ -96,7 +96,7 @@ export function TraceOverview({ children }: { children: React.ReactElement }) { : []; return ( - + @@ -95,7 +96,7 @@ const apmRoutes = { // this route fails on navigation unless it's defined before home '/service-groups': { element: ( - + ({ return { [path]: { element: ( - - - {element} - - + + {element} + ), params: t.type({ query: t.type({ serviceGroup: t.string }), diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/home/page_template.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/home/page_template.tsx index e003de27d982d..1e5508bceb4ed 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/home/page_template.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/home/page_template.tsx @@ -38,7 +38,7 @@ export function page< return { [path]: { element: ( - + ; diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/settings/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/settings/index.tsx index 1cb7d7ec9ede1..e8f03da90c4c6 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/settings/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/settings/index.tsx @@ -52,6 +52,7 @@ export const settingsRoute = { title={i18n.translate('xpack.apm.views.listSettings.title', { defaultMessage: 'Settings', })} + omitOnServerless > diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/entities/logs_service_template/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/entities/logs_service_template/index.tsx index 1588b6ffafece..5b2b95ef41624 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/entities/logs_service_template/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/entities/logs_service_template/index.tsx @@ -82,7 +82,10 @@ function TemplateWithContext({ title, children, selectedTabKey, searchBarOptions ] : []), ], - [query, router, selectedTab, serviceName, servicesLink] + [query, router, selectedTab, serviceName, servicesLink], + { + omitRootOnServerless: true, + } ); return ( diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/mobile_service_template/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/mobile_service_template/index.tsx index d9069c7d9f2f2..0c6a1f4176ec5 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/mobile_service_template/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/mobile_service_template/index.tsx @@ -92,7 +92,10 @@ function TemplateWithContext({ title, children, selectedTabKey, searchBarOptions ] : []), ], - [query, router, selectedTab, serviceName, servicesLink] + [query, router, selectedTab, serviceName, servicesLink], + { + omitRootOnServerless: true, + } ); return ( diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/service_group_template.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/service_group_template.tsx index 0617e900c601f..67695c6485006 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/service_group_template.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/service_group_template.tsx @@ -26,13 +26,15 @@ import { useEntityManagerEnablementContext } from '../../../context/entity_manag export function ServiceGroupTemplate({ pageTitle, pageHeader, + pagePath, children, environmentFilter = true, serviceGroupContextTab, ...pageTemplateProps }: { - pageTitle?: React.ReactNode; + pageTitle: string; pageHeader?: EuiPageHeaderProps; + pagePath: string; children: React.ReactNode; environmentFilter?: boolean; serviceGroupContextTab: ServiceGroupContextTab['key']; @@ -81,34 +83,44 @@ export function ServiceGroupTemplate({ ); const tabs = useTabs(serviceGroupContextTab); - const selectedTab = tabs?.find(({ isSelected }) => isSelected); + const selectedTab = tabs.find(({ isSelected }) => isSelected); + + // this is only used for building the breadcrumbs for the service group page useBreadcrumb( - () => [ - { - title: i18n.translate('xpack.apm.serviceGroups.breadcrumb.title', { - defaultMessage: 'Services', - }), - href: serviceGroupsLink, - }, - ...(selectedTab + () => + !serviceGroupName ? [ - ...(serviceGroupName + { + title: pageTitle, + href: pagePath, + }, + ] + : [ + { + title: i18n.translate('xpack.apm.serviceGroups.breadcrumb.title', { + defaultMessage: 'Services', + }), + href: serviceGroupsLink, + }, + { + title: serviceGroupName, + href: router.link('/services', { query }), + }, + ...(selectedTab ? [ { - title: serviceGroupName, - href: router.link('/services', { query }), - }, + title: selectedTab.breadcrumbLabel || selectedTab.label, + href: selectedTab.href, + } as { title: string; href: string }, ] : []), - { - title: selectedTab.breadcrumbLabel || selectedTab.label, - href: selectedTab.href, - } as { title: string; href: string }, - ] - : []), - ], - [query, router, selectedTab, serviceGroupName, serviceGroupsLink] + ], + [pagePath, pageTitle, query, router, selectedTab, serviceGroupName, serviceGroupsLink], + { + omitRootOnServerless: true, + } ); + return ( (undef export function BreadcrumbsContextProvider({ children }: { children: React.ReactElement }) { const [, forceUpdate] = useState({}); + const { + services: { serverless }, + } = useKibana(); const breadcrumbs = useMemo(() => { return new Map(); @@ -72,7 +76,7 @@ export function BreadcrumbsContextProvider({ children }: { children: React.React }; }); - useBreadcrumbs(formattedBreadcrumbs); + useBreadcrumbs(formattedBreadcrumbs, { serverless }); return {children}; } diff --git a/x-pack/plugins/observability_solution/apm/public/context/breadcrumbs/use_breadcrumb.ts b/x-pack/plugins/observability_solution/apm/public/context/breadcrumbs/use_breadcrumb.ts index 5e3fb08b8f276..a6b80fd088bff 100644 --- a/x-pack/plugins/observability_solution/apm/public/context/breadcrumbs/use_breadcrumb.ts +++ b/x-pack/plugins/observability_solution/apm/public/context/breadcrumbs/use_breadcrumb.ts @@ -9,8 +9,16 @@ import { useCurrentRoute } from '@kbn/typed-react-router-config'; import { useContext, useEffect, useRef } from 'react'; import { castArray } from 'lodash'; import { Breadcrumb, BreadcrumbsContext } from './context'; +import { useKibanaEnvironmentContext } from '../kibana_environment_context/use_kibana_environment_context'; + +export function useBreadcrumb( + callback: () => Breadcrumb | Breadcrumb[], + fnDeps: any[], + options?: { omitRootOnServerless?: boolean; omitOnServerless?: boolean } +) { + const { isServerlessEnv } = useKibanaEnvironmentContext(); + const { omitRootOnServerless = false, omitOnServerless = false } = options || {}; -export function useBreadcrumb(callback: () => Breadcrumb | Breadcrumb[], fnDeps: any[]) { const api = useContext(BreadcrumbsContext); if (!api) { @@ -22,6 +30,10 @@ export function useBreadcrumb(callback: () => Breadcrumb | Breadcrumb[], fnDeps: const matchedRoute = useRef(match?.route); useEffect(() => { + if (isServerlessEnv && omitOnServerless) { + return; + } + if (matchedRoute.current && matchedRoute.current !== match?.route) { api.unset(matchedRoute.current); } @@ -29,7 +41,14 @@ export function useBreadcrumb(callback: () => Breadcrumb | Breadcrumb[], fnDeps: matchedRoute.current = match?.route; if (matchedRoute.current) { - api.set(matchedRoute.current, castArray(callback())); + const breadcrumbs = castArray(callback()); + + api.set( + matchedRoute.current, + isServerlessEnv && omitRootOnServerless && breadcrumbs.length >= 1 + ? breadcrumbs.slice(1) + : breadcrumbs + ); } return () => { diff --git a/x-pack/plugins/observability_solution/apm/public/context/kibana_environment_context/kibana_environment_context.tsx b/x-pack/plugins/observability_solution/apm/public/context/kibana_environment_context/kibana_environment_context.tsx index acf974d4d2dae..61c7f1e5e7ff1 100644 --- a/x-pack/plugins/observability_solution/apm/public/context/kibana_environment_context/kibana_environment_context.tsx +++ b/x-pack/plugins/observability_solution/apm/public/context/kibana_environment_context/kibana_environment_context.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import React from 'react'; import { createContext } from 'react'; export interface KibanaEnvContext { @@ -14,3 +14,17 @@ export interface KibanaEnvContext { } export const KibanaEnvironmentContext = createContext({}); + +export function KibanaEnvironmentContextProvider({ + children, + kibanaEnvironment, +}: { + kibanaEnvironment: KibanaEnvContext; + children: React.ReactElement; +}) { + return ( + + {children} + + ); +} diff --git a/x-pack/plugins/observability_solution/apm/public/context/kibana_environment_context/use_kibana_environment_context.tsx b/x-pack/plugins/observability_solution/apm/public/context/kibana_environment_context/use_kibana_environment_context.tsx index 5f3830a5524f0..fbdbe1073cf8c 100644 --- a/x-pack/plugins/observability_solution/apm/public/context/kibana_environment_context/use_kibana_environment_context.tsx +++ b/x-pack/plugins/observability_solution/apm/public/context/kibana_environment_context/use_kibana_environment_context.tsx @@ -5,35 +5,9 @@ * 2.0. */ -import { useMemo, createElement, FC, PropsWithChildren } from 'react'; -import { KibanaEnvironmentContext, type KibanaEnvContext } from './kibana_environment_context'; +import { useContext } from 'react'; +import { KibanaEnvironmentContext } from './kibana_environment_context'; -export const useKibanaEnvironmentContextProvider = ({ - kibanaVersion, - isCloudEnv, - isServerlessEnv, -}: KibanaEnvContext) => { - const value = useMemo( - () => ({ - kibanaVersion, - isCloudEnv, - isServerlessEnv, - }), - [kibanaVersion, isCloudEnv, isServerlessEnv] - ); - - const Provider: FC< - PropsWithChildren<{ - kibanaEnvironment?: KibanaEnvContext; - }> - > = ({ kibanaEnvironment = {}, children }) => { - const newProvider = createElement(KibanaEnvironmentContext.Provider, { - value: { ...kibanaEnvironment, ...value }, - children, - }); - - return newProvider; - }; - - return Provider; +export const useKibanaEnvironmentContext = () => { + return useContext(KibanaEnvironmentContext); }; diff --git a/x-pack/plugins/observability_solution/apm/public/hooks/use_dependency_detail_operations_breadcrumb.ts b/x-pack/plugins/observability_solution/apm/public/hooks/use_dependency_detail_operations_breadcrumb.ts index 5a0516531f443..197e03845a953 100644 --- a/x-pack/plugins/observability_solution/apm/public/hooks/use_dependency_detail_operations_breadcrumb.ts +++ b/x-pack/plugins/observability_solution/apm/public/hooks/use_dependency_detail_operations_breadcrumb.ts @@ -6,11 +6,15 @@ */ import { i18n } from '@kbn/i18n'; +import { castArray } from 'lodash'; import { useBreadcrumb } from '../context/breadcrumbs/use_breadcrumb'; import { useAnyOfApmParams } from './use_apm_params'; import { useApmRouter } from './use_apm_router'; +import { Breadcrumb } from '../context/breadcrumbs/context'; -export function useDependencyDetailOperationsBreadcrumb() { +export function useDependencyDetailOperationsBreadcrumb( + extraBreadCrumbs: Breadcrumb | Breadcrumb[] = [] +) { const { query: { dependencyName, @@ -45,12 +49,14 @@ export function useDependencyDetailOperationsBreadcrumb() { }, }), }, + ...castArray(extraBreadCrumbs), ], [ apmRouter, comparisonEnabled, dependencyName, environment, + extraBreadCrumbs, kuery, rangeFrom, rangeTo, diff --git a/x-pack/plugins/observability_solution/apm/public/plugin.ts b/x-pack/plugins/observability_solution/apm/public/plugin.ts index c45d8f7abba64..9524f328755c0 100644 --- a/x-pack/plugins/observability_solution/apm/public/plugin.ts +++ b/x-pack/plugins/observability_solution/apm/public/plugin.ts @@ -69,6 +69,7 @@ import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import { from } from 'rxjs'; import { map } from 'rxjs'; import type { CloudSetup } from '@kbn/cloud-plugin/public'; +import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { ConfigSchema } from '.'; import { registerApmRuleTypes } from './components/alerting/rule_types/register_apm_rule_types'; import { registerEmbeddables } from './embeddable/register_embeddables'; @@ -134,6 +135,7 @@ export interface ApmPluginStartDeps { fieldFormats?: FieldFormatsStart; security?: SecurityPluginStart; spaces?: SpacesPluginStart; + serverless?: ServerlessPluginStart; dataViews: DataViewsPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; storage: IStorageWrapper; diff --git a/x-pack/plugins/observability_solution/apm/tsconfig.json b/x-pack/plugins/observability_solution/apm/tsconfig.json index a31ddcca0bdce..7de9609dbbafe 100644 --- a/x-pack/plugins/observability_solution/apm/tsconfig.json +++ b/x-pack/plugins/observability_solution/apm/tsconfig.json @@ -128,6 +128,7 @@ "@kbn/core-analytics-browser", "@kbn/apm-types", "@kbn/entities-schema", + "@kbn/serverless", "@kbn/aiops-log-rate-analysis", "@kbn/router-utils" ], diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/index.tsx b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/index.tsx index 392420ee547f8..c4061f05ce91b 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/index.tsx +++ b/x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/index.tsx @@ -60,7 +60,7 @@ export function ExploratoryViewPage({ }), }, ], - app + { app } ); const kbnUrlStateStorage = useSessionStorage diff --git a/x-pack/plugins/observability_solution/observability/public/pages/annotations/annotations.tsx b/x-pack/plugins/observability_solution/observability/public/pages/annotations/annotations.tsx index d0c6723f4f438..f601b4ff99a3f 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/annotations/annotations.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/annotations/annotations.tsx @@ -22,20 +22,24 @@ export const ANNOTATIONS_PAGE_ID = 'annotations-container'; export function AnnotationsPage() { const { http: { basePath }, + serverless, } = useKibana().services; const { ObservabilityPageTemplate } = usePluginContext(); const checkPrivileges = useAnnotationsPrivileges(); - useBreadcrumbs([ - { - href: basePath.prepend(paths.observability.annotations), - text: i18n.translate('xpack.observability.breadcrumbs.annotationsLinkText', { - defaultMessage: 'Annotations', - }), - deepLinkId: 'observability-overview', - }, - ]); + useBreadcrumbs( + [ + { + href: basePath.prepend(paths.observability.annotations), + text: i18n.translate('xpack.observability.breadcrumbs.annotationsLinkText', { + defaultMessage: 'Annotations', + }), + deepLinkId: 'observability-overview', + }, + ], + { serverless } + ); return ( { const params = useQueryParams(); + const { app, breadcrumbsAppendExtension, serverless } = options ?? {}; const { services: { - chrome: { docTitle, setBreadcrumbs, setBreadcrumbsAppendExtension }, + chrome: { docTitle, setBreadcrumbs: chromeSetBreadcrumbs, setBreadcrumbsAppendExtension }, application: { getUrlForApp, navigateToUrl }, }, } = useKibana<{ @@ -54,6 +59,11 @@ export const useBreadcrumbs = ( const setTitle = docTitle.change; const appPath = getUrlForApp(app?.id ?? 'observability-overview') ?? ''; + const setBreadcrumbs = useMemo( + () => serverless?.setBreadcrumbs ?? chromeSetBreadcrumbs, + [serverless, chromeSetBreadcrumbs] + ); + useEffect(() => { if (breadcrumbsAppendExtension) { setBreadcrumbsAppendExtension(breadcrumbsAppendExtension); @@ -66,22 +76,34 @@ export const useBreadcrumbs = ( }, [breadcrumbsAppendExtension, setBreadcrumbsAppendExtension]); useEffect(() => { - const breadcrumbs = [ - { - text: - app?.label ?? - i18n.translate('xpack.observabilityShared.breadcrumbs.observabilityLinkText', { - defaultMessage: 'Observability', - }), - href: appPath + '/overview', - }, - ...extraCrumbs, - ]; + const breadcrumbs = serverless + ? extraCrumbs + : [ + { + text: + app?.label ?? + i18n.translate('xpack.observabilityShared.breadcrumbs.observabilityLinkText', { + defaultMessage: 'Observability', + }), + href: appPath + '/overview', + }, + ...extraCrumbs, + ]; + if (setBreadcrumbs) { setBreadcrumbs(addClickHandlers(breadcrumbs, navigateToUrl)); } if (setTitle) { setTitle(getTitleFromBreadCrumbs(breadcrumbs)); } - }, [app?.label, appPath, extraCrumbs, navigateToUrl, params, setBreadcrumbs, setTitle]); + }, [ + app?.label, + appPath, + extraCrumbs, + navigateToUrl, + params, + serverless, + setBreadcrumbs, + setTitle, + ]); }; diff --git a/x-pack/plugins/observability_solution/observability_shared/tsconfig.json b/x-pack/plugins/observability_solution/observability_shared/tsconfig.json index 371807ecdd5a2..6453bae28d999 100644 --- a/x-pack/plugins/observability_solution/observability_shared/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_shared/tsconfig.json @@ -43,6 +43,7 @@ "@kbn/core-chrome-browser", "@kbn/rule-data-utils", "@kbn/es-query", + "@kbn/serverless", ], "exclude": ["target/**/*"] }