Skip to content

Commit

Permalink
[APM] Fix APM breadcrumbs on Serverless (elastic#191506)
Browse files Browse the repository at this point in the history
fixes
[#8](elastic/observability-accessibility#8)
fixes
[#7](elastic/observability-accessibility#7)
 
## Summary

Fixes APM breadcrumbs on serverless

| Serverless  |  Stateful  |
|---|---|
| <img width="700px" alt="image"
src="https://github.com/user-attachments/assets/944a7d58-7de3-4a7f-be02-3c8c1110a0e2">
|<img width="800px" alt="image"
src="https://github.com/user-attachments/assets/450664b1-ddfc-4395-9fa3-a7b941affb3b">|
|<img width="500px" alt="image"
src="https://github.com/user-attachments/assets/944a7d58-7de3-4a7f-be02-3c8c1110a0e2">
|<img width="500px" alt="image"
src="https://github.com/user-attachments/assets/450664b1-ddfc-4395-9fa3-a7b941affb3b">|
| <img width="500px" alt="image"
src="https://github.com/user-attachments/assets/944a7d58-7de3-4a7f-be02-3c8c1110a0e2">
|<img width="500px" alt="image"
src="https://github.com/user-attachments/assets/cb8a39e2-ca33-4cf9-a8ac-4c84566d092d">|
|<img width="500px" alt="image"
src="https://github.com/user-attachments/assets/151a3a9c-c81e-4558-9d00-e695e3d1d79c">|<img
width="500px" alt="image"
src="https://github.com/user-attachments/assets/2562e96f-d5e4-4aa4-a221-6721f8995883">|
|<img width="500px" alt="image"
src="https://github.com/user-attachments/assets/8d877d11-8c3f-4ac5-8146-6a11125eae7c">|<img
width="500px" alt="image"
src="https://github.com/user-attachments/assets/36e588cb-4c18-4d66-a2c6-f0e66392f708">|
|<img width="500px" alt="image"
src="https://github.com/user-attachments/assets/14253196-06de-4343-811f-61aa31ea0d1e">|<img
width="500px" alt="image"
src="https://github.com/user-attachments/assets/0cdfc83f-6545-433f-8c14-5bbf2a581175">|
|<img width="500px" alt="image"
src="https://github.com/user-attachments/assets/89a58e2b-2cef-4188-b2be-f359ba6890db">|<img
width="500px" alt="image"
src="https://github.com/user-attachments/assets/f15e767f-5b60-4485-ac71-7b6fd850ec50">|
|<img width="500px" alt="image"
src="https://github.com/user-attachments/assets/a0f7bfae-bfda-4f49-b92a-e736d80fea4c">|<img
width="500px" alt="image"
src="https://github.com/user-attachments/assets/680db8ab-58b8-454b-a0d7-6e1681dbe616">|


### How to test
#### Serverless
- Start a local ES serverless instance: `yarn es serverless
--projectType=oblt --ssl -k/--insecure`
- Start a local Kibana serverless instance: ` yarn start
--serverless=oblt --no-ssl`
- Run some synthtrace scenarios
- `NODE_TLS_REJECT_UNAUTHORIZED=0 node scripts/synthtrace mobile.ts
--live --target=https://elastic_serverless:[email protected]:9200
--kibana=http://elastic_serverless:[email protected]:5601`
- `NODE_TLS_REJECT_UNAUTHORIZED=0 node scripts/synthtrace service_map.ts
--live --target=https://elastic_serverless:[email protected]:9200
--kibana=http://elastic_serverless:[email protected]:5601`
- Navigate to Applications and click through the links

### Stateful
- Start a local ES and Kibana instance
- Run the some synthtrace scenarios:
  -  `node scripts/synthtrace mobile.ts --live`
  -  `node scripts/synthtrace service_map.ts --live`
- Navigate to Applications and click through the links

---------

Co-authored-by: kibanamachine <[email protected]>
crespocarlos and kibanamachine authored Aug 30, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 232d7cf commit 60b8c05
Showing 25 changed files with 185 additions and 100 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/observability_solution/apm/kibana.jsonc
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@
"ml",
"security",
"spaces",
"serverless",
"taskManager",
"usageCollection",
"customIntegrations", // Move this to requiredPlugins after completely migrating from the Tutorials Home App
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -69,7 +69,10 @@ export function DependencyDetailView({ children }: { children: React.ReactChild
rangeTo,
refreshInterval,
refreshPaused,
]
],
{
omitRootOnServerless: true,
}
);
return <DependencyDetailTemplate>{children}</DependencyDetailTemplate>;
}
Original file line number Diff line number Diff line change
@@ -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 });

Original file line number Diff line number Diff line change
@@ -96,7 +96,7 @@ export function TraceOverview({ children }: { children: React.ReactElement }) {
: [];

return (
<Breadcrumb href="/traces" title={title}>
<Breadcrumb href="/traces" title={title} omitOnServerless>
<ApmMainTemplate
pageTitle={title}
pageSectionProps={{
Original file line number Diff line number Diff line change
@@ -87,6 +87,7 @@ const apmRoutes = {
defaultMessage: 'APM',
})}
href="/"
omitOnServerless
>
<Outlet />
</Breadcrumb>
@@ -95,7 +96,7 @@ const apmRoutes = {
// this route fails on navigation unless it's defined before home
'/service-groups': {
element: (
<Breadcrumb title={ServiceGroupsTitle} href={'/service-groups'}>
<Breadcrumb title={ServiceGroupsTitle} href={'/service-groups'} omitOnServerless>
<ApmMainTemplate
pageTitle={ServiceGroupsTitle}
environmentFilter={false}
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ import { euiDarkVars, euiLightVars } from '@kbn/ui-theme';
import React from 'react';
import { DefaultTheme, ThemeProvider } from 'styled-components';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { useKibanaEnvironmentContextProvider } from '../../../context/kibana_environment_context/use_kibana_environment_context';
import { KibanaEnvironmentContextProvider } from '../../../context/kibana_environment_context/kibana_environment_context';
import { AnomalyDetectionJobsContextProvider } from '../../../context/anomaly_detection_jobs/anomaly_detection_jobs_context';
import {
ApmPluginContext,
@@ -56,7 +56,6 @@ export function ApmAppRoot({
apmServices: ApmServices;
}) {
const { appMountParameters, kibanaEnvironment, core } = apmPluginContextValue;
const KibanaEnvironmentContextProvider = useKibanaEnvironmentContextProvider(kibanaEnvironment);
const { history } = appMountParameters;
const i18nCore = core.i18n;

Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@ import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import { environmentRt } from '../../../../common/environment_rt';
import { TraceSearchType } from '../../../../common/trace_explorer';
import { ApmTimeRangeMetadataContextProvider } from '../../../context/time_range_metadata/time_range_metadata_context';
import { Breadcrumb } from '../../app/breadcrumb';
import { ServiceInventory } from '../../app/service_inventory';
import { ServiceMapHome } from '../../app/service_map';
import { TopTracesOverview } from '../../app/top_traces_overview';
@@ -50,11 +49,13 @@ function serviceGroupPage<TPath extends string>({
return {
[path]: {
element: (
<Breadcrumb title={title} href={path}>
<ServiceGroupTemplate pageTitle={title} serviceGroupContextTab={serviceGroupContextTab}>
{element}
</ServiceGroupTemplate>
</Breadcrumb>
<ServiceGroupTemplate
pageTitle={title}
pagePath={path}
serviceGroupContextTab={serviceGroupContextTab}
>
{element}
</ServiceGroupTemplate>
),
params: t.type({
query: t.type({ serviceGroup: t.string }),
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ export function page<
return {
[path]: {
element: (
<Breadcrumb title={title} href={path}>
<Breadcrumb title={title} href={path} omitOnServerless>
<ApmMainTemplate
pageTitle={title}
showServiceGroupSaveButton={showServiceGroupSaveButton}
Original file line number Diff line number Diff line change
@@ -33,7 +33,10 @@ export function ApmServiceWrapper() {
}),
},
],
[query, router, serviceName]
[query, router, serviceName],
{
omitRootOnServerless: true,
}
);

return <Outlet />;
Original file line number Diff line number Diff line change
@@ -52,6 +52,7 @@ export const settingsRoute = {
title={i18n.translate('xpack.apm.views.listSettings.title', {
defaultMessage: 'Settings',
})}
omitOnServerless
>
<Outlet />
</Breadcrumb>
Original file line number Diff line number Diff line change
@@ -82,7 +82,10 @@ function TemplateWithContext({ title, children, selectedTabKey, searchBarOptions
]
: []),
],
[query, router, selectedTab, serviceName, servicesLink]
[query, router, selectedTab, serviceName, servicesLink],
{
omitRootOnServerless: true,
}
);

return (
Original file line number Diff line number Diff line change
@@ -92,7 +92,10 @@ function TemplateWithContext({ title, children, selectedTabKey, searchBarOptions
]
: []),
],
[query, router, selectedTab, serviceName, servicesLink]
[query, router, selectedTab, serviceName, servicesLink],
{
omitRootOnServerless: true,
}
);

return (
Original file line number Diff line number Diff line change
@@ -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 (
<ApmMainTemplate
pageTitle={serviceGroupsPageTitle}
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import { ChromeBreadcrumb } from '@kbn/core/public';
import { compact, isEqual } from 'lodash';
import React, { createContext, useMemo, useState } from 'react';
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
import { useKibana } from '../kibana_context/use_kibana';

export interface Breadcrumb {
title: string;
@@ -25,6 +26,9 @@ export const BreadcrumbsContext = createContext<BreadcrumbApi | undefined>(undef

export function BreadcrumbsContextProvider({ children }: { children: React.ReactElement }) {
const [, forceUpdate] = useState({});
const {
services: { serverless },
} = useKibana();

const breadcrumbs = useMemo(() => {
return new Map<Route, Breadcrumb[]>();
@@ -72,7 +76,7 @@ export function BreadcrumbsContextProvider({ children }: { children: React.React
};
});

useBreadcrumbs(formattedBreadcrumbs);
useBreadcrumbs(formattedBreadcrumbs, { serverless });

return <BreadcrumbsContext.Provider value={api}>{children}</BreadcrumbsContext.Provider>;
}
Original file line number Diff line number Diff line change
@@ -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,14 +30,25 @@ 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);
}

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 () => {
Original file line number Diff line number Diff line change
@@ -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<KibanaEnvContext>({});

export function KibanaEnvironmentContextProvider({
children,
kibanaEnvironment,
}: {
kibanaEnvironment: KibanaEnvContext;
children: React.ReactElement;
}) {
return (
<KibanaEnvironmentContext.Provider value={kibanaEnvironment}>
{children}
</KibanaEnvironmentContext.Provider>
);
}
Original file line number Diff line number Diff line change
@@ -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);
};
Original file line number Diff line number Diff line change
@@ -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,
2 changes: 2 additions & 0 deletions x-pack/plugins/observability_solution/apm/public/plugin.ts
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions x-pack/plugins/observability_solution/apm/tsconfig.json
Original file line number Diff line number Diff line change
@@ -128,6 +128,7 @@
"@kbn/core-analytics-browser",
"@kbn/apm-types",
"@kbn/entities-schema",
"@kbn/serverless",
"@kbn/aiops-log-rate-analysis",
"@kbn/router-utils"
],
Original file line number Diff line number Diff line change
@@ -60,7 +60,7 @@ export function ExploratoryViewPage({
}),
},
],
app
{ app }
);

const kbnUrlStateStorage = useSessionStorage
Original file line number Diff line number Diff line change
@@ -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 (
<ObservabilityPageTemplate
Original file line number Diff line number Diff line change
@@ -7,9 +7,10 @@

import { i18n } from '@kbn/i18n';
import { ApplicationStart, ChromeBreadcrumb, ChromeStart } from '@kbn/core/public';
import { MouseEvent, useEffect } from 'react';
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 { useQueryParams } from './use_query_params';

function addClickHandlers(
@@ -37,14 +38,18 @@ function getTitleFromBreadCrumbs(breadcrumbs: ChromeBreadcrumb[]) {

export const useBreadcrumbs = (
extraCrumbs: ChromeBreadcrumb[],
app?: { id: string; label: string },
breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension
options?: {
app?: { id: string; label: string };
breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension;
serverless?: ServerlessPluginStart;
}
) => {
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,
]);
};
Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@
"@kbn/core-chrome-browser",
"@kbn/rule-data-utils",
"@kbn/es-query",
"@kbn/serverless",
],
"exclude": ["target/**/*"]
}

0 comments on commit 60b8c05

Please sign in to comment.