diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index ab717ffed873bf..da2fe62b47266d 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -48,6 +48,7 @@ export const renderApp = ({ plugins: pluginsSetup, data: pluginsStart.data, inspector: pluginsStart.inspector, + infra: pluginsStart.infra, observability: pluginsStart.observability, observabilityShared: pluginsStart.observabilityShared, observabilityRuleTypeRegistry, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx index 250556ccc359e1..4b2f8bb39579e2 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx @@ -40,7 +40,10 @@ export function InstanceActionsMenu({ kuery, onClose, }: Props) { - const { core } = useApmPluginContext(); + const { + core, + infra: { locators }, + } = useApmPluginContext(); const { data, status } = useInstanceDetailsFetcher({ serviceName, serviceNodeName, @@ -89,6 +92,7 @@ export function InstanceActionsMenu({ basePath: core.http.basePath, onFilterByInstanceClick: handleFilterByInstanceClick, metricsHref, + infraLocators: locators, }); return ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts index decc23e0535656..6267592058868b 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { IBasePath } from '@kbn/core/public'; import moment from 'moment'; +import type { InfraLocators } from '@kbn/infra-plugin/public/locators'; import { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; import { getInfraHref } from '../../../../shared/links/infra_link'; import { @@ -37,11 +38,13 @@ export function getMenuSections({ basePath, onFilterByInstanceClick, metricsHref, + infraLocators, }: { instanceDetails: InstaceDetails; basePath: IBasePath; onFilterByInstanceClick: () => void; metricsHref: string; + infraLocators: InfraLocators; }) { const podId = instanceDetails.kubernetes?.pod?.uid; const containerId = instanceDetails.container?.id; @@ -49,6 +52,7 @@ export function getMenuSections({ ? new Date(instanceDetails['@timestamp']).valueOf() : undefined; const infraMetricsQuery = getInfraMetricsQuery(instanceDetails['@timestamp']); + const infraNodeLocator = infraLocators.nodeLogsLocator; const podActions: Action[] = [ { @@ -57,11 +61,10 @@ export function getMenuSections({ 'xpack.apm.serviceOverview.instancesTable.actionMenus.podLogs', { defaultMessage: 'Pod logs' } ), - href: getInfraHref({ - app: 'logs', - basePath, - path: `/link-to/pod-logs/${podId}`, - query: { time }, + href: infraNodeLocator.getRedirectUrl({ + nodeId: podId!, + nodeType: 'pod', + time, }), condition: !!podId, }, @@ -88,11 +91,10 @@ export function getMenuSections({ 'xpack.apm.serviceOverview.instancesTable.actionMenus.containerLogs', { defaultMessage: 'Container logs' } ), - href: getInfraHref({ - app: 'logs', - basePath, - path: `/link-to/container-logs/${containerId}`, - query: { time }, + href: infraNodeLocator.getRedirectUrl({ + nodeId: containerId!, + nodeType: 'container', + time, }), condition: !!containerId, }, diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts index db5fe72714fbc6..8e9b19350aa806 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts @@ -13,6 +13,7 @@ import { apmRouter as apmRouterBase, ApmRouter, } from '../../routing/apm_route_config'; +import { infraLocatorsMock } from '../../../context/apm_plugin/mock_apm_plugin_context'; const apmRouter = { ...apmRouterBase, @@ -20,6 +21,13 @@ const apmRouter = { `some-basepath/app/apm${apmRouterBase.link(...args)}`, } as ApmRouter; +const infraLocators = infraLocatorsMock; + +const expectInfraLocatorsToBeCalled = () => { + expect(infraLocators.nodeLogsLocator.getRedirectUrl).toBeCalledTimes(3); + expect(infraLocators.logsLocator.getRedirectUrl).toBeCalledTimes(1); +}; + describe('Transaction action menu', () => { const basePath = { prepend: (url: string) => { @@ -35,6 +43,10 @@ describe('Transaction action menu', () => { ); const location = history.location; + afterEach(() => { + jest.clearAllMocks(); + }); + it('shows required sections only', () => { const transaction = { timestamp, @@ -48,6 +60,7 @@ describe('Transaction action menu', () => { basePath, location, apmRouter, + infraLocators, infraLinksAvailable: false, }) ).toEqual([ @@ -60,7 +73,6 @@ describe('Transaction action menu', () => { { key: 'traceLogs', label: 'Trace logs', - href: 'some-basepath/app/logs/link-to/logs?time=1580986800&filter=trace.id:%22123%22%20OR%20(not%20trace.id:*%20AND%20%22123%22)', condition: true, }, ], @@ -93,6 +105,7 @@ describe('Transaction action menu', () => { }, ], ]); + expectInfraLocatorsToBeCalled(); }); it('shows pod and required sections only', () => { @@ -109,6 +122,7 @@ describe('Transaction action menu', () => { basePath, location, apmRouter, + infraLocators, infraLinksAvailable: true, }) ).toEqual([ @@ -122,7 +136,6 @@ describe('Transaction action menu', () => { { key: 'podLogs', label: 'Pod logs', - href: 'some-basepath/app/logs/link-to/pod-logs/123?time=1580986800', condition: true, }, { @@ -141,7 +154,6 @@ describe('Transaction action menu', () => { { key: 'traceLogs', label: 'Trace logs', - href: 'some-basepath/app/logs/link-to/logs?time=1580986800&filter=trace.id:%22123%22%20OR%20(not%20trace.id:*%20AND%20%22123%22)', condition: true, }, ], @@ -174,6 +186,7 @@ describe('Transaction action menu', () => { }, ], ]); + expectInfraLocatorsToBeCalled(); }); it('shows host and required sections only', () => { @@ -190,6 +203,7 @@ describe('Transaction action menu', () => { basePath, location, apmRouter, + infraLocators, infraLinksAvailable: true, }) ).toEqual([ @@ -202,7 +216,6 @@ describe('Transaction action menu', () => { { key: 'hostLogs', label: 'Host logs', - href: 'some-basepath/app/logs/link-to/host-logs/foo?time=1580986800', condition: true, }, { @@ -221,7 +234,6 @@ describe('Transaction action menu', () => { { key: 'traceLogs', label: 'Trace logs', - href: 'some-basepath/app/logs/link-to/logs?time=1580986800&filter=trace.id:%22123%22%20OR%20(not%20trace.id:*%20AND%20%22123%22)', condition: true, }, ], @@ -254,5 +266,6 @@ describe('Transaction action menu', () => { }, ], ]); + expectInfraLocatorsToBeCalled(); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts index 769a96002d5ef6..e0df994ac15c02 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/sections.ts @@ -11,6 +11,7 @@ import { IBasePath } from '@kbn/core/public'; import { isEmpty, pickBy } from 'lodash'; import moment from 'moment'; import url from 'url'; +import type { InfraLocators } from '@kbn/infra-plugin/public/locators'; import type { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import { getDiscoverHref } from '../links/discover_links/discover_link'; import { getDiscoverQuery } from '../links/discover_links/discover_transaction_link'; @@ -35,18 +36,21 @@ export const getSections = ({ basePath, location, apmRouter, + infraLocators, infraLinksAvailable, }: { transaction?: Transaction; basePath: IBasePath; location: Location; apmRouter: ApmRouter; + infraLocators: InfraLocators; infraLinksAvailable: boolean; }) => { if (!transaction) return []; const hostName = transaction.host?.hostname; const podId = transaction.kubernetes?.pod?.uid; const containerId = transaction.container?.id; + const { nodeLogsLocator, logsLocator } = infraLocators; const time = Math.round(transaction.timestamp.us / 1000); const infraMetricsQuery = getInfraMetricsQuery(transaction); @@ -78,11 +82,10 @@ export const getSections = ({ 'xpack.apm.transactionActionMenu.showPodLogsLinkLabel', { defaultMessage: 'Pod logs' } ), - href: getInfraHref({ - app: 'logs', - basePath, - path: `/link-to/pod-logs/${podId}`, - query: { time }, + href: nodeLogsLocator.getRedirectUrl({ + nodeId: podId!, + nodeType: 'pod', + time, }), condition: !!podId, }, @@ -109,11 +112,10 @@ export const getSections = ({ 'xpack.apm.transactionActionMenu.showContainerLogsLinkLabel', { defaultMessage: 'Container logs' } ), - href: getInfraHref({ - app: 'logs', - basePath, - path: `/link-to/container-logs/${containerId}`, - query: { time }, + href: nodeLogsLocator.getRedirectUrl({ + nodeId: containerId!, + nodeType: 'container', + time, }), condition: !!containerId, }, @@ -140,11 +142,10 @@ export const getSections = ({ 'xpack.apm.transactionActionMenu.showHostLogsLinkLabel', { defaultMessage: 'Host logs' } ), - href: getInfraHref({ - app: 'logs', - basePath, - path: `/link-to/host-logs/${hostName}`, - query: { time }, + href: nodeLogsLocator.getRedirectUrl({ + nodeId: hostName!, + nodeType: 'host', + time, }), condition: !!hostName, }, @@ -171,14 +172,9 @@ export const getSections = ({ 'xpack.apm.transactionActionMenu.showTraceLogsLinkLabel', { defaultMessage: 'Trace logs' } ), - href: getInfraHref({ - app: 'logs', - basePath, - path: `/link-to/logs`, - query: { - time, - filter: `trace.id:"${transaction.trace.id}" OR (not trace.id:* AND "${transaction.trace.id}")`, - }, + href: logsLocator.getRedirectUrl({ + filter: `trace.id:"${transaction.trace.id}" OR (not trace.id:* AND "${transaction.trace.id}")`, + time, }), condition: true, }, diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx index 3de7ed3374c568..039d248f419acc 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx @@ -28,15 +28,13 @@ import { TransactionActionMenu } from './transaction_action_menu'; import * as Transactions from './__fixtures__/mock_data'; import { apmRouter } from '../../routing/apm_route_config'; -function getMockAPMContext({ canSave }: { canSave: boolean }) { - return { - ...mockApmPluginContextValue, - core: { - ...mockApmPluginContextValue.core, - application: { capabilities: { apm: { save: canSave }, ml: {} } }, - }, - } as unknown as ApmPluginContextValue; -} +const apmContextMock = { + ...mockApmPluginContextValue, + core: { + ...mockApmPluginContextValue.core, + application: { capabilities: { apm: { save: true }, ml: {} } }, + }, +} as unknown as ApmPluginContextValue; const history = createMemoryHistory(); history.replace( @@ -46,7 +44,7 @@ history.replace( function Wrapper({ children }: { children?: React.ReactNode }) { return ( - + {children} @@ -73,6 +71,13 @@ const renderTransaction = async (transaction: Record) => { return rendered; }; +const expectInfraLocatorsToBeCalled = () => { + expect( + apmContextMock.infra.locators.nodeLogsLocator.getRedirectUrl + ).toBeCalled(); + expect(apmContextMock.infra.locators.logsLocator.getRedirectUrl).toBeCalled(); +}; + describe('TransactionActionMenu component', () => { beforeAll(() => { jest.spyOn(hooks, 'useFetcher').mockReturnValue({ @@ -81,7 +86,7 @@ describe('TransactionActionMenu component', () => { refetch: jest.fn(), }); }); - afterAll(() => { + afterEach(() => { jest.clearAllMocks(); }); it('should always render the discover link', async () => { @@ -92,16 +97,10 @@ describe('TransactionActionMenu component', () => { expect(queryByText('View transaction in Discover')).not.toBeNull(); }); - it('always renders the trace logs link', async () => { - const { getByText } = await renderTransaction( - Transactions.transactionWithMinimalData - ); + it('should call infra locators getRedirectUrl function', async () => { + await renderTransaction(Transactions.transactionWithMinimalData); - expect( - (getByText('Trace logs').parentElement as HTMLAnchorElement).href - ).toEqual( - 'http://localhost/basepath/app/logs/link-to/logs?time=1545092070952&filter=trace.id:%228b60bd32ecc6e1506735a8b6cfcf175c%22%20OR%20(not%20trace.id:*%20AND%20%228b60bd32ecc6e1506735a8b6cfcf175c%22)' - ); + expectInfraLocatorsToBeCalled(); }); describe('when there is no pod id', () => { @@ -123,16 +122,10 @@ describe('TransactionActionMenu component', () => { }); describe('when there is a pod id', () => { - it('renders the pod logs link', async () => { - const { getByText } = await renderTransaction( - Transactions.transactionWithKubernetesData - ); + it('should call infra locators getRedirectUrl function', async () => { + await renderTransaction(Transactions.transactionWithKubernetesData); - expect( - (getByText('Pod logs').parentElement as HTMLAnchorElement).href - ).toEqual( - 'http://localhost/basepath/app/logs/link-to/pod-logs/pod123456abcdef?time=1545092070952' - ); + expectInfraLocatorsToBeCalled(); }); it('renders the pod metrics link', async () => { @@ -166,17 +159,11 @@ describe('TransactionActionMenu component', () => { }); }); - describe('when there is a container id', () => { + describe('should call infra locators getRedirectUrl function', () => { it('renders the Container logs link', async () => { - const { getByText } = await renderTransaction( - Transactions.transactionWithContainerData - ); + await renderTransaction(Transactions.transactionWithContainerData); - expect( - (getByText('Container logs').parentElement as HTMLAnchorElement).href - ).toEqual( - 'http://localhost/basepath/app/logs/link-to/container-logs/container123456abcdef?time=1545092070952' - ); + expectInfraLocatorsToBeCalled(); }); it('renders the Container metrics link', async () => { @@ -211,16 +198,10 @@ describe('TransactionActionMenu component', () => { }); describe('when there is a hostname', () => { - it('renders the Host logs link', async () => { - const { getByText } = await renderTransaction( - Transactions.transactionWithHostData - ); + it('should call infra locators getRedirectUrl function', async () => { + await renderTransaction(Transactions.transactionWithHostData); - expect( - (getByText('Host logs').parentElement as HTMLAnchorElement).href - ).toEqual( - 'http://localhost/basepath/app/logs/link-to/host-logs/227453131a17?time=1545092070952' - ); + expectInfraLocatorsToBeCalled(); }); it('renders the Host metrics link', async () => { diff --git a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx index 2d3c3d62d2f970..30b4bf4bc669de 100644 --- a/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx +++ b/x-pack/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx @@ -89,7 +89,11 @@ export function TransactionActionMenu({ transaction, isLoading }: Props) { } function ActionMenuSections({ transaction }: { transaction?: Transaction }) { - const { core, uiActions } = useApmPluginContext(); + const { + core, + uiActions, + infra: { locators }, + } = useApmPluginContext(); const location = useLocation(); const apmRouter = useApmRouter(); @@ -102,6 +106,7 @@ function ActionMenuSections({ transaction }: { transaction?: Transaction }) { basePath: core.http.basePath, location, apmRouter, + infraLocators: locators, infraLinksAvailable, }); diff --git a/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx index fe1a640ea5a34b..32084c66b289b4 100644 --- a/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx @@ -15,6 +15,7 @@ import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import type { InfraClientStartExports } from '@kbn/infra-plugin/public'; import { ApmPluginSetupDeps } from '../../plugin'; import { ConfigSchema } from '../..'; @@ -26,6 +27,7 @@ export interface ApmPluginContextValue { plugins: ApmPluginSetupDeps & { maps?: MapsStartApi }; observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; observability: ObservabilityPublicStart; + infra: InfraClientStartExports; dataViews: DataViewsPublicPluginStart; data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx index 3d245e30418e4c..f4d5dad5c4a741 100644 --- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx @@ -16,6 +16,8 @@ import { createObservabilityRuleTypeRegistryMock } from '@kbn/observability-plug import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { MlLocatorDefinition } from '@kbn/ml-plugin/public'; import { enableComparisonByDefault } from '@kbn/observability-plugin/public'; +import type { InfraLocators } from '@kbn/infra-plugin/public/locators'; +import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; import { ApmPluginContext, ApmPluginContextValue } from './apm_plugin_context'; import { ConfigSchema } from '../..'; import { createCallApmApi } from '../../services/rest/create_call_apm_api'; @@ -89,6 +91,11 @@ const mockPlugin = { }, }; +export const infraLocatorsMock: InfraLocators = { + logsLocator: sharePluginMock.createLocator(), + nodeLogsLocator: sharePluginMock.createLocator(), +}; + const mockCorePlugins = { embeddable: {}, inspector: {}, @@ -111,6 +118,9 @@ export const mockApmPluginContextValue = { plugins: mockPlugin, observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), corePlugins: mockCorePlugins, + infra: { + locators: infraLocatorsMock, + }, deps: {}, unifiedSearch: mockUnifiedSearch, uiActions: { diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index e9703c74297094..8b7cb792276f73 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -109,7 +109,7 @@ export interface ApmPluginStartDeps { fieldFormats?: FieldFormatsStart; security?: SecurityPluginStart; spaces?: SpacesPluginStart; - infra?: InfraClientStartExports; + infra: InfraClientStartExports; dataViews: DataViewsPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; storage: IStorageWrapper;