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]>
  • Loading branch information
crespocarlos and kibanamachine authored Aug 30, 2024
1 parent 232d7cf commit 60b8c05
Show file tree
Hide file tree
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
Expand Up @@ -45,6 +45,7 @@
"ml",
"security",
"spaces",
"serverless",
"taskManager",
"usageCollection",
"customIntegrations", // Move this to requiredPlugins after completely migrating from the Tutorials Home App
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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 });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const apmRoutes = {
defaultMessage: 'APM',
})}
href="/"
omitOnServerless
>
<Outlet />
</Breadcrumb>
Expand All @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ export function ApmServiceWrapper() {
}),
},
],
[query, router, serviceName]
[query, router, serviceName],
{
omitRootOnServerless: true,
}
);

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

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

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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[]>();
Expand Down Expand Up @@ -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
Expand Up @@ -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) {
Expand All @@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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>
);
}
Loading

0 comments on commit 60b8c05

Please sign in to comment.