Skip to content

Commit

Permalink
Use observability plugin breadcrumbs in APM (#103168) (#103247)
Browse files Browse the repository at this point in the history
Both APM and Observability plugins have a `useBreadcrumbs` hook.

APM's takes the whole list of route definitions, creates the whole path of breadcrumbs, and has an effect to set the breadcrumbs and the page title.

The Observability plugin's `useBreadcrumbs` just takes an array of breadcrumb objects, adds onclick handlers for them, and has an effect to set the breadcrumbs and the page title.

Rename APM's `useBreadcrumbs` to `useApmBreadcrumbs`. It still constructs the path based on the routes and the current route, but then just calls out to the Observability plugin's `useBreadcrumbs` to do the breadcrumb and title setting.

Now all APM breadcrumbs begin with "Observability" which links to the Observability overview, but the rest of them remain the same.

Co-authored-by: Nathan L Smith <[email protected]>
  • Loading branch information
kibanamachine and smith authored Jun 24, 2021
1 parent 1cb4ce0 commit ad3037c
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 74 deletions.
4 changes: 2 additions & 2 deletions x-pack/plugins/apm/public/components/routing/app_root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
} from '../../context/apm_plugin/apm_plugin_context';
import { LicenseProvider } from '../../context/license/license_context';
import { UrlParamsProvider } from '../../context/url_params_context/url_params_context';
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
import { useApmBreadcrumbs } from '../../hooks/use_apm_breadcrumbs';
import { ApmPluginStartDeps } from '../../plugin';
import { HeaderMenuPortal } from '../../../../observability/public';
import { ApmHeaderActionMenu } from '../shared/apm_header_action_menu';
Expand Down Expand Up @@ -79,7 +79,7 @@ export function ApmAppRoot({
}

function MountApmHeaderActionMenu() {
useBreadcrumbs(apmRouteConfig);
useApmBreadcrumbs(apmRouteConfig);
const { setHeaderActionMenu } = useApmPluginContext().appMountParameters;

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const mockCore = {
ml: {},
},
currentAppId$: new Observable(),
getUrlForApp: (appId: string) => '',
navigateToUrl: (url: string) => {},
},
chrome: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ import {
mockApmPluginContextValue,
MockApmPluginContextWrapper,
} from '../context/apm_plugin/mock_apm_plugin_context';
import { useBreadcrumbs } from './use_breadcrumbs';
import { useApmBreadcrumbs } from './use_apm_breadcrumbs';
import { useBreadcrumbs } from '../../../observability/public';

jest.mock('../../../observability/public');

function createWrapper(path: string) {
return ({ children }: { children?: ReactNode }) => {
const value = (produce(mockApmPluginContextValue, (draft) => {
draft.core.application.navigateToUrl = (url: string) => Promise.resolve();
draft.core.chrome.docTitle.change = changeTitle;
draft.core.chrome.setBreadcrumbs = setBreadcrumbs;
}) as unknown) as ApmPluginContextValue;

return (
Expand All @@ -36,27 +37,18 @@ function createWrapper(path: string) {
}

function mountBreadcrumb(path: string) {
renderHook(() => useBreadcrumbs(apmRouteConfig), {
renderHook(() => useApmBreadcrumbs(apmRouteConfig), {
wrapper: createWrapper(path),
});
}

const changeTitle = jest.fn();
const setBreadcrumbs = jest.fn();

describe('useBreadcrumbs', () => {
it('changes the page title', () => {
mountBreadcrumb('/');

expect(changeTitle).toHaveBeenCalledWith(['APM']);
});

describe('useApmBreadcrumbs', () => {
test('/services/:serviceName/errors/:groupId', () => {
mountBreadcrumb(
'/services/opbeans-node/errors/myGroupId?kuery=myKuery&rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0'
);

expect(setBreadcrumbs).toHaveBeenCalledWith(
expect(useBreadcrumbs).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
text: 'APM',
Expand All @@ -81,20 +73,12 @@ describe('useBreadcrumbs', () => {
expect.objectContaining({ text: 'myGroupId', href: undefined }),
])
);

expect(changeTitle).toHaveBeenCalledWith([
'myGroupId',
'Errors',
'opbeans-node',
'Services',
'APM',
]);
});

test('/services/:serviceName/errors', () => {
mountBreadcrumb('/services/opbeans-node/errors?kuery=myKuery');

expect(setBreadcrumbs).toHaveBeenCalledWith(
expect(useBreadcrumbs).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
text: 'APM',
Expand All @@ -111,19 +95,12 @@ describe('useBreadcrumbs', () => {
expect.objectContaining({ text: 'Errors', href: undefined }),
])
);

expect(changeTitle).toHaveBeenCalledWith([
'Errors',
'opbeans-node',
'Services',
'APM',
]);
});

test('/services/:serviceName/transactions', () => {
mountBreadcrumb('/services/opbeans-node/transactions?kuery=myKuery');

expect(setBreadcrumbs).toHaveBeenCalledWith(
expect(useBreadcrumbs).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
text: 'APM',
Expand All @@ -140,21 +117,14 @@ describe('useBreadcrumbs', () => {
expect.objectContaining({ text: 'Transactions', href: undefined }),
])
);

expect(changeTitle).toHaveBeenCalledWith([
'Transactions',
'opbeans-node',
'Services',
'APM',
]);
});

test('/services/:serviceName/transactions/view?transactionName=my-transaction-name', () => {
mountBreadcrumb(
'/services/opbeans-node/transactions/view?kuery=myKuery&transactionName=my-transaction-name'
);

expect(setBreadcrumbs).toHaveBeenCalledWith(
expect(useBreadcrumbs).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
text: 'APM',
Expand All @@ -179,13 +149,5 @@ describe('useBreadcrumbs', () => {
}),
])
);

expect(changeTitle).toHaveBeenCalledWith([
'my-transaction-name',
'Transactions',
'opbeans-node',
'Services',
'APM',
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@

import { History, Location } from 'history';
import { ChromeBreadcrumb } from 'kibana/public';
import { MouseEvent, ReactNode, useEffect } from 'react';
import { MouseEvent } from 'react';
import {
match as Match,
matchPath,
RouteComponentProps,
useHistory,
match as Match,
useLocation,
} from 'react-router-dom';
import { useBreadcrumbs } from '../../../observability/public';
import { APMRouteDefinition, BreadcrumbTitle } from '../application/routes';
import { getAPMHref } from '../components/shared/Links/apm/APMLink';
import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context';
Expand Down Expand Up @@ -164,33 +165,17 @@ function routeDefinitionsToBreadcrumbs({
return breadcrumbs;
}

/**
* Get an array for a page title from a list of breadcrumbs
*/
function getTitleFromBreadcrumbs(breadcrumbs: ChromeBreadcrumb[]): string[] {
function removeNonStrings(item: ReactNode): item is string {
return typeof item === 'string';
}

return breadcrumbs
.map(({ text }) => text)
.reverse()
.filter(removeNonStrings);
}

/**
* Determine the breadcrumbs from the routes, set them, and update the page
* title when the route changes.
*/
export function useBreadcrumbs(routes: APMRouteDefinition[]) {
export function useApmBreadcrumbs(routes: APMRouteDefinition[]) {
const history = useHistory();
const location = useLocation();
const { search } = location;
const { core } = useApmPluginContext();
const { basePath } = core.http;
const { navigateToUrl } = core.application;
const { docTitle, setBreadcrumbs } = core.chrome;
const changeTitle = docTitle.change;

function wrappedGetAPMHref(path: string) {
return getAPMHref({ basePath, path, search });
Expand All @@ -206,10 +191,6 @@ export function useBreadcrumbs(routes: APMRouteDefinition[]) {
wrappedGetAPMHref,
navigateToUrl,
});
const title = getTitleFromBreadcrumbs(breadcrumbs);

useEffect(() => {
changeTitle(title);
setBreadcrumbs(breadcrumbs);
}, [breadcrumbs, changeTitle, location, title, setBreadcrumbs]);
useBreadcrumbs(breadcrumbs);
}

0 comments on commit ad3037c

Please sign in to comment.