From 8c14f93c216b816cec105612686afee484ab5cec Mon Sep 17 00:00:00 2001 From: Abdul Wahab Zahid Date: Wed, 10 Jan 2024 16:33:01 +0100 Subject: [PATCH] [Infra UI] Fix `Open in Logs` links in Infra and APM when in Serverless (#172137) Fixes https://github.com/elastic/kibana/issues/171082 ## Summary The PR wraps the [LogsLocator](https://github.com/elastic/kibana/blob/f59ac2916d02d643310dd44a8f80b7a9cc61f608/x-pack/plugins/infra/common/locators/logs_locator.ts#L18C27-L18C27) and [NodeLogsLocator](https://github.com/elastic/kibana/blob/f59ac2916d02d643310dd44a8f80b7a9cc61f608/x-pack/plugins/infra/common/locators/node_logs_locator.ts#L16) of **infra** plugin inside corresponding locators in **logs_shared** plugin while including the fallback logic to navigate to Logs Explorer when Steam UI isn't available. Previously, it was assumed that Steam UI will always be available as long as Infra UI is available, but **infra** plugin introduced a new feature flag `logsUIEnabled` which when `false` won't enable the `/stream/` route in Serverless. The added locators in **logs_shared** will now check whether locators redirecting to `/steam/` are available, otherwise they'll redirect to Logs Explorer, thus the new locators are abstracting this decision in their definition. This abstraction was already being done in **apm** plugin, which has also been refactored to use the newly added **logs_shared** locators. Links in Serverless: https://github.com/elastic/kibana/assets/2748376/16e5747a-546e-44b3-87e3-95428945cf63 --- .../instance_actions_menu/index.tsx | 8 +- .../instance_actions_menu/menu_sections.ts | 25 +++--- .../shared/links/observability_logs_link.ts | 89 ------------------- .../transaction_action_menu/sections.test.ts | 31 +++---- .../transaction_action_menu/sections.ts | 55 +++++------- .../transaction_action_menu.test.tsx | 33 ++++--- .../transaction_action_menu.tsx | 15 +--- .../apm_plugin/mock_apm_plugin_context.tsx | 12 ++- .../plugins/infra/common/locators/helpers.ts | 11 --- x-pack/plugins/infra/common/locators/index.ts | 8 +- .../infra/common/locators/locators.test.ts | 29 +++--- .../infra/common/locators/logs_locator.ts | 12 +-- .../common/locators/node_logs_locator.ts | 20 +++-- .../log_threshold/log_threshold_rule_type.tsx | 8 +- .../asset_details/tabs/logs/logs.tsx | 21 ++--- .../public/pages/link_to/redirect_to_logs.tsx | 9 +- .../pages/link_to/redirect_to_node_logs.tsx | 13 +-- .../tabs/logs/logs_link_to_stream.tsx | 7 +- .../components/waffle/node_context_menu.tsx | 8 +- x-pack/plugins/infra/public/plugin.ts | 16 ++-- x-pack/plugins/logs_shared/common/index.ts | 12 ++- .../common/locators/get_logs_locators.ts | 25 ++++++ .../logs_shared/common/locators/helpers.ts | 43 +++++++++ .../logs_shared/common/locators/index.ts | 10 +++ .../logs_shared/common/locators/infra.ts | 10 +++ .../common/locators/logs_locator.ts | 45 +++++++--- .../common/locators/node_logs_locator.ts | 43 +++++++-- .../common/locators/trace_logs_locator.ts | 47 ++++++++++ .../logs_shared/common/locators/types.ts | 39 +++++--- x-pack/plugins/logs_shared/kibana.jsonc | 3 +- x-pack/plugins/logs_shared/public/plugin.ts | 33 ++++++- x-pack/plugins/logs_shared/public/types.ts | 16 ++-- x-pack/plugins/logs_shared/tsconfig.json | 4 +- 33 files changed, 441 insertions(+), 319 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/shared/links/observability_logs_link.ts create mode 100644 x-pack/plugins/logs_shared/common/locators/get_logs_locators.ts create mode 100644 x-pack/plugins/logs_shared/common/locators/helpers.ts create mode 100644 x-pack/plugins/logs_shared/common/locators/infra.ts create mode 100644 x-pack/plugins/logs_shared/common/locators/trace_logs_locator.ts 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 450c5ec061971..ff88e61fd5132 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 @@ -19,10 +19,7 @@ import { AllDatasetsLocatorParams, ALL_DATASETS_LOCATOR_ID, } from '@kbn/deeplinks-observability/locators'; -import { - NODE_LOGS_LOCATOR_ID, - NodeLogsLocatorParams, -} from '@kbn/logs-shared-plugin/common'; +import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; import { isJavaAgentName } from '../../../../../../common/agent_name'; import { SERVICE_NODE_NAME } from '../../../../../../common/es_fields/apm'; import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; @@ -63,8 +60,7 @@ export function InstanceActionsMenu({ const allDatasetsLocator = share.url.locators.get( ALL_DATASETS_LOCATOR_ID )!; - const nodeLogsLocator = - share.url.locators.get(NODE_LOGS_LOCATOR_ID)!; + const { nodeLogsLocator } = getLogsLocatorsFromUrlService(share.url); if (isPending(status)) { 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 8401cc6bbc744..3f258ea089a15 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,10 +8,10 @@ import { i18n } from '@kbn/i18n'; import { IBasePath } from '@kbn/core/public'; import moment from 'moment'; -import type { LocatorPublic } from '@kbn/share-plugin/public'; import { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability/locators'; +import type { LocatorPublic } from '@kbn/share-plugin/public'; import { NodeLogsLocatorParams } from '@kbn/logs-shared-plugin/common'; -import { getNodeLogsHref } from '../../../../shared/links/observability_logs_link'; +import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; import { getInfraHref } from '../../../../shared/links/infra_link'; import { @@ -58,20 +58,17 @@ export function getMenuSections({ : undefined; const infraMetricsQuery = getInfraMetricsQuery(instanceDetails['@timestamp']); - const podLogsHref = getNodeLogsHref( - 'pod', - podId!, + const podLogsHref = nodeLogsLocator.getRedirectUrl({ + nodeField: findInventoryFields('pod').id, + nodeId: podId!, time, - allDatasetsLocator, - nodeLogsLocator - ); - const containerLogsHref = getNodeLogsHref( - 'container', - containerId!, + }); + + const containerLogsHref = nodeLogsLocator.getRedirectUrl({ + nodeField: findInventoryFields('container').id, + nodeId: containerId!, time, - allDatasetsLocator, - nodeLogsLocator - ); + }); const podActions: Action[] = [ { diff --git a/x-pack/plugins/apm/public/components/shared/links/observability_logs_link.ts b/x-pack/plugins/apm/public/components/shared/links/observability_logs_link.ts deleted file mode 100644 index 72ae29960942e..0000000000000 --- a/x-pack/plugins/apm/public/components/shared/links/observability_logs_link.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - LogsLocatorParams, - NodeLogsLocatorParams, -} from '@kbn/logs-shared-plugin/common'; -import { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability/locators'; -import { LocatorPublic } from '@kbn/share-plugin/common'; -import moment from 'moment'; -import { DurationInputObject } from 'moment'; - -type NodeType = 'host' | 'pod' | 'container'; - -const NodeTypeMapping: Record = { - host: 'host.name', - container: 'container.id', - pod: 'kubernetes.pod.uid', -}; - -export const getNodeLogsHref = ( - nodeType: NodeType, - id: string, - time: number | undefined, - allDatasetsLocator: LocatorPublic, - infraNodeLocator?: LocatorPublic -): string => { - if (infraNodeLocator) - return infraNodeLocator?.getRedirectUrl({ - nodeId: id!, - nodeType, - time, - }); - - return allDatasetsLocator.getRedirectUrl({ - query: getNodeQuery(nodeType, id), - ...(time - ? { - timeRange: { - from: getTimeRangeStartFromTime(time), - to: getTimeRangeEndFromTime(time), - }, - } - : {}), - }); -}; - -export const getTraceLogsHref = ( - traceId: string, - time: number | undefined, - allDatasetsLocator: LocatorPublic, - infraLogsLocator: LocatorPublic -): string => { - const query = `trace.id:"${traceId}" OR (not trace.id:* AND "${traceId}")`; - - if (infraLogsLocator) - return infraLogsLocator.getRedirectUrl({ - filter: query, - time, - }); - - return allDatasetsLocator.getRedirectUrl({ - query: { language: 'kuery', query }, - ...(time - ? { - timeRange: { - from: getTimeRangeStartFromTime(time), - to: getTimeRangeEndFromTime(time), - }, - } - : {}), - }); -}; - -const getNodeQuery = (type: NodeType, id: string) => { - return { language: 'kuery', query: `${NodeTypeMapping[type]}: ${id}` }; -}; - -const defaultTimeRangeFromPositionOffset: DurationInputObject = { hours: 1 }; - -const getTimeRangeStartFromTime = (time: number): string => - moment(time).subtract(defaultTimeRangeFromPositionOffset).toISOString(); - -const getTimeRangeEndFromTime = (time: number): string => - moment(time).add(defaultTimeRangeFromPositionOffset).toISOString(); 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 7d7a720f27cfc..dd1cfa389453f 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 @@ -7,11 +7,6 @@ import { createMemoryHistory } from 'history'; import { IBasePath } from '@kbn/core/public'; -import { LocatorPublic } from '@kbn/share-plugin/common'; -import { - LogsLocatorParams, - NodeLogsLocatorParams, -} from '@kbn/logs-shared-plugin/common'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import { getSections } from './sections'; import { @@ -19,7 +14,7 @@ import { ApmRouter, } from '../../routing/apm_route_config'; import { - infraLocatorsMock, + logsLocatorsMock, observabilityLogExplorerLocatorsMock, } from '../../../context/apm_plugin/mock_apm_plugin_context'; @@ -30,11 +25,11 @@ const apmRouter = { } as ApmRouter; const { allDatasetsLocator } = observabilityLogExplorerLocatorsMock; -const { nodeLogsLocator, logsLocator } = infraLocatorsMock; +const { nodeLogsLocator, traceLogsLocator } = logsLocatorsMock; -const expectInfraLocatorsToBeCalled = () => { +const expectLogsLocatorsToBeCalled = () => { expect(nodeLogsLocator.getRedirectUrl).toBeCalledTimes(3); - expect(logsLocator.getRedirectUrl).toBeCalledTimes(1); + expect(traceLogsLocator.getRedirectUrl).toBeCalledTimes(1); }; describe('Transaction action menu', () => { @@ -70,9 +65,7 @@ describe('Transaction action menu', () => { location, apmRouter, allDatasetsLocator, - logsLocator: logsLocator as unknown as LocatorPublic, - nodeLogsLocator: - nodeLogsLocator as unknown as LocatorPublic, + logsLocators: logsLocatorsMock, infraLinksAvailable: false, rangeFrom: 'now-24h', rangeTo: 'now', @@ -121,7 +114,7 @@ describe('Transaction action menu', () => { }, ], ]); - expectInfraLocatorsToBeCalled(); + expectLogsLocatorsToBeCalled(); }); it('shows pod and required sections only', () => { @@ -138,10 +131,8 @@ describe('Transaction action menu', () => { basePath, location, apmRouter, - logsLocator: logsLocator as unknown as LocatorPublic, - nodeLogsLocator: - nodeLogsLocator as unknown as LocatorPublic, allDatasetsLocator, + logsLocators: logsLocatorsMock, infraLinksAvailable: true, rangeFrom: 'now-24h', rangeTo: 'now', @@ -209,7 +200,7 @@ describe('Transaction action menu', () => { }, ], ]); - expectInfraLocatorsToBeCalled(); + expectLogsLocatorsToBeCalled(); }); it('shows host and required sections only', () => { @@ -226,10 +217,8 @@ describe('Transaction action menu', () => { basePath, location, apmRouter, - logsLocator: logsLocator as unknown as LocatorPublic, - nodeLogsLocator: - nodeLogsLocator as unknown as LocatorPublic, allDatasetsLocator, + logsLocators: logsLocatorsMock, infraLinksAvailable: true, rangeFrom: 'now-24h', rangeTo: 'now', @@ -296,6 +285,6 @@ describe('Transaction action menu', () => { }, ], ]); - expectInfraLocatorsToBeCalled(); + expectLogsLocatorsToBeCalled(); }); }); 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 09f742ad1254e..398a657d06714 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,10 +11,8 @@ import { IBasePath } from '@kbn/core/public'; import { isEmpty, pickBy } from 'lodash'; import moment from 'moment'; import url from 'url'; -import { - LogsLocatorParams, - NodeLogsLocatorParams, -} from '@kbn/logs-shared-plugin/common'; +import type { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import { LocatorPublic } from '@kbn/share-plugin/common'; import { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability/locators'; import type { ProfilingLocators } from '@kbn/observability-shared-plugin/public'; @@ -27,10 +25,6 @@ import { fromQuery } from '../links/url_helpers'; import { SectionRecord, getNonEmptySections, Action } from './sections_helper'; import { HOST_NAME, TRACE_ID } from '../../../../common/es_fields/apm'; import { ApmRouter } from '../../routing/apm_route_config'; -import { - getNodeLogsHref, - getTraceLogsHref, -} from '../links/observability_logs_link'; function getInfraMetricsQuery(transaction: Transaction) { const timestamp = new Date(transaction['@timestamp']).getTime(); @@ -53,8 +47,7 @@ export const getSections = ({ rangeTo, environment, allDatasetsLocator, - logsLocator, - nodeLogsLocator, + logsLocators, dataViewId, }: { transaction?: Transaction; @@ -67,8 +60,7 @@ export const getSections = ({ rangeTo: string; environment: Environment; allDatasetsLocator: LocatorPublic; - logsLocator: LocatorPublic; - nodeLogsLocator: LocatorPublic; + logsLocators: ReturnType; dataViewId?: string; }) => { if (!transaction) return []; @@ -95,33 +87,26 @@ export const getSections = ({ }); // Logs hrefs - const podLogsHref = getNodeLogsHref( - 'pod', - podId!, + const podLogsHref = logsLocators.nodeLogsLocator.getRedirectUrl({ + nodeField: findInventoryFields('pod').id, + nodeId: podId!, time, - allDatasetsLocator, - nodeLogsLocator - ); - const containerLogsHref = getNodeLogsHref( - 'container', - containerId!, + }); + const containerLogsHref = logsLocators.nodeLogsLocator.getRedirectUrl({ + nodeField: findInventoryFields('container').id, + nodeId: containerId!, time, - allDatasetsLocator, - nodeLogsLocator - ); - const hostLogsHref = getNodeLogsHref( - 'host', - hostName!, + }); + const hostLogsHref = logsLocators.nodeLogsLocator.getRedirectUrl({ + nodeField: findInventoryFields('host').id, + nodeId: hostName!, time, - allDatasetsLocator, - nodeLogsLocator - ); - const traceLogsHref = getTraceLogsHref( - transaction.trace.id!, + }); + + const traceLogsHref = logsLocators.traceLogsLocator.getRedirectUrl({ + traceId: transaction.trace.id!, time, - allDatasetsLocator, - logsLocator - ); + }); const podActions: Action[] = [ { 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 be78efeb870ee..ce9d81a52eb32 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 @@ -13,13 +13,14 @@ import { License } from '@kbn/licensing-plugin/common/license'; import { LOGS_LOCATOR_ID, NODE_LOGS_LOCATOR_ID, + TRACE_LOGS_LOCATOR_ID, } from '@kbn/logs-shared-plugin/common'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, - infraLocatorsMock, + logsLocatorsMock, } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { LicenseContext } from '../../../context/license/license_context'; import * as hooks from '../../../hooks/use_fetcher'; @@ -43,11 +44,15 @@ const apmContextMock = { locators: { get: (id: string) => { if (id === LOGS_LOCATOR_ID) { - return infraLocatorsMock.logsLocator; + return logsLocatorsMock.logsLocator; } if (id === NODE_LOGS_LOCATOR_ID) { - return infraLocatorsMock.nodeLogsLocator; + return logsLocatorsMock.nodeLogsLocator; + } + + if (id === TRACE_LOGS_LOCATOR_ID) { + return logsLocatorsMock.traceLogsLocator; } }, }, @@ -102,9 +107,9 @@ const renderTransaction = async (transaction: Record) => { return rendered; }; -const expectInfraLocatorsToBeCalled = () => { - expect(infraLocatorsMock.nodeLogsLocator.getRedirectUrl).toBeCalled(); - expect(infraLocatorsMock.logsLocator.getRedirectUrl).toBeCalled(); +const expectLogsLocatorsToBeCalled = () => { + expect(logsLocatorsMock.nodeLogsLocator.getRedirectUrl).toBeCalled(); + expect(logsLocatorsMock.traceLogsLocator.getRedirectUrl).toBeCalled(); }; let useAdHocApmDataViewSpy: jest.SpyInstance; @@ -144,10 +149,10 @@ describe('TransactionActionMenu ', () => { expect(findByText('View transaction in Discover')).not.toBeNull(); }); - it('should call infra locators getRedirectUrl function', async () => { + it('should call logs locators getRedirectUrl function', async () => { await renderTransaction(Transactions.transactionWithMinimalData); - expectInfraLocatorsToBeCalled(); + expectLogsLocatorsToBeCalled(); }); describe('when there is no pod id', () => { @@ -169,10 +174,10 @@ describe('TransactionActionMenu ', () => { }); describe('when there is a pod id', () => { - it('should call infra locators getRedirectUrl function', async () => { + it('should call logs locators getRedirectUrl function', async () => { await renderTransaction(Transactions.transactionWithKubernetesData); - expectInfraLocatorsToBeCalled(); + expectLogsLocatorsToBeCalled(); }); it('renders the pod metrics link', async () => { @@ -206,11 +211,11 @@ describe('TransactionActionMenu ', () => { }); }); - describe('should call infra locators getRedirectUrl function', () => { + describe('should call logs locators getRedirectUrl function', () => { it('renders the Container logs link', async () => { await renderTransaction(Transactions.transactionWithContainerData); - expectInfraLocatorsToBeCalled(); + expectLogsLocatorsToBeCalled(); }); it('renders the Container metrics link', async () => { @@ -245,10 +250,10 @@ describe('TransactionActionMenu ', () => { }); describe('when there is a hostname', () => { - it('should call infra locators getRedirectUrl function', async () => { + it('should call logs locators getRedirectUrl function', async () => { await renderTransaction(Transactions.transactionWithHostData); - expectInfraLocatorsToBeCalled(); + expectLogsLocatorsToBeCalled(); }); 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 21cfea70c4e31..fe3cd90222ef4 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 @@ -25,13 +25,8 @@ import { AllDatasetsLocatorParams, ALL_DATASETS_LOCATOR_ID, } from '@kbn/deeplinks-observability/locators'; -import { - LOGS_LOCATOR_ID, - LogsLocatorParams, - NODE_LOGS_LOCATOR_ID, - NodeLogsLocatorParams, -} from '@kbn/logs-shared-plugin/common'; import type { ProfilingLocators } from '@kbn/observability-shared-plugin/public'; +import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; import { ApmFeatureFlagName } from '../../../../common/apm_feature_flags'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; @@ -144,10 +139,7 @@ function ActionMenuSections({ const allDatasetsLocator = share.url.locators.get( ALL_DATASETS_LOCATOR_ID )!; - const logsLocator = - share.url.locators.get(LOGS_LOCATOR_ID)!; - const nodeLogsLocator = - share.url.locators.get(NODE_LOGS_LOCATOR_ID)!; + const logsLocators = getLogsLocatorsFromUrlService(share.url); const infraLinksAvailable = useApmFeatureFlag( ApmFeatureFlagName.InfraUiAvailable @@ -173,8 +165,7 @@ function ActionMenuSections({ rangeTo, environment, allDatasetsLocator, - logsLocator, - nodeLogsLocator, + logsLocators, dataViewId: dataView?.id, }); 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 61710babd1dac..f6f45a273de45 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 @@ -13,6 +13,11 @@ import { merge } from 'lodash'; import { coreMock } from '@kbn/core/public/mocks'; import { UrlService } from '@kbn/share-plugin/common/url_service'; import { createObservabilityRuleTypeRegistryMock } from '@kbn/observability-plugin/public'; +import { + LogsLocatorParams, + NodeLogsLocatorParams, + TraceLogsLocatorParams, +} from '@kbn/logs-shared-plugin/common'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { MlLocatorDefinition } from '@kbn/ml-plugin/public'; import { enableComparisonByDefault } from '@kbn/observability-plugin/public'; @@ -131,9 +136,10 @@ export const observabilityLogExplorerLocatorsMock = { singleDatasetLocator: sharePluginMock.createLocator(), }; -export const infraLocatorsMock = { - nodeLogsLocator: sharePluginMock.createLocator(), - logsLocator: sharePluginMock.createLocator(), +export const logsLocatorsMock = { + logsLocator: sharePluginMock.createLocator(), + nodeLogsLocator: sharePluginMock.createLocator(), + traceLogsLocator: sharePluginMock.createLocator(), }; const mockCorePlugins = { diff --git a/x-pack/plugins/infra/common/locators/helpers.ts b/x-pack/plugins/infra/common/locators/helpers.ts index 582499407bb40..d067ea15e7ebe 100644 --- a/x-pack/plugins/infra/common/locators/helpers.ts +++ b/x-pack/plugins/infra/common/locators/helpers.ts @@ -13,10 +13,8 @@ import { LogViewReference, ResolvedLogView, LogsLocatorParams, - NodeLogsLocatorParams, } from '@kbn/logs-shared-plugin/common'; import { flowRight } from 'lodash'; -import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import type { InfraClientCoreSetup } from '../../public/types'; import { MESSAGE_FIELD, TIMESTAMP_FIELD } from '../constants'; import type { TimeRange } from '../time'; @@ -33,15 +31,6 @@ interface LocationToDiscoverParams { logView?: LogViewReference; } -export const createNodeLogsQuery = (params: NodeLogsLocatorParams) => { - const { nodeType, nodeId, filter } = params; - - const nodeFilter = `${findInventoryFields(nodeType).id}: ${nodeId}`; - const query = filter ? `(${nodeFilter}) and (${filter})` : nodeFilter; - - return query; -}; - export const createSearchString = ({ time, timeRange, diff --git a/x-pack/plugins/infra/common/locators/index.ts b/x-pack/plugins/infra/common/locators/index.ts index d84c42a6dc21e..914334d2df97c 100644 --- a/x-pack/plugins/infra/common/locators/index.ts +++ b/x-pack/plugins/infra/common/locators/index.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { LogsLocator } from './logs_locator'; -import type { NodeLogsLocator } from './node_logs_locator'; +import type { InfraLogsLocator } from './logs_locator'; +import type { InfraNodeLogsLocator } from './node_logs_locator'; export * from './logs_locator'; export * from './node_logs_locator'; export interface InfraLocators { - logsLocator: LogsLocator; - nodeLogsLocator: NodeLogsLocator; + logsLocator?: InfraLogsLocator; + nodeLogsLocator?: InfraNodeLogsLocator; } diff --git a/x-pack/plugins/infra/common/locators/locators.test.ts b/x-pack/plugins/infra/common/locators/locators.test.ts index 607fd41b1bab6..7996380e3268b 100644 --- a/x-pack/plugins/infra/common/locators/locators.test.ts +++ b/x-pack/plugins/infra/common/locators/locators.test.ts @@ -6,8 +6,8 @@ */ import { v4 as uuidv4 } from 'uuid'; -import { LogsLocatorDefinition, LogsLocatorDependencies } from './logs_locator'; -import { NodeLogsLocatorDefinition } from './node_logs_locator'; +import { InfraLogsLocatorDefinition, InfraLogsLocatorDependencies } from './logs_locator'; +import { InfraNodeLogsLocatorDefinition } from './node_logs_locator'; import { coreMock } from '@kbn/core/public/mocks'; import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import moment from 'moment'; @@ -19,11 +19,11 @@ import { } from '@kbn/logs-shared-plugin/common'; const setupLogsLocator = async () => { - const deps: LogsLocatorDependencies = { + const deps: InfraLogsLocatorDependencies = { core: coreMock.createSetup(), }; - const logsLocator = new LogsLocatorDefinition(deps); - const nodeLogsLocator = new NodeLogsLocatorDefinition(deps); + const logsLocator = new InfraLogsLocatorDefinition(deps); + const nodeLogsLocator = new InfraNodeLogsLocatorDefinition(deps); return { logsLocator, @@ -33,8 +33,9 @@ const setupLogsLocator = async () => { describe('Infra Locators', () => { const APP_ID = 'logs'; - const nodeType = 'host'; const FILTER_QUERY = 'trace.id:1234'; + const nodeType = 'host'; + const nodeField = findInventoryFields(nodeType).id; const nodeId = uuidv4(); const time = 1550671089404; const from = 1676815089000; @@ -124,7 +125,7 @@ describe('Infra Locators', () => { it('should create a link to Node Logs with no state', async () => { const params: NodeLogsLocatorParams = { nodeId, - nodeType, + nodeField, time, }; const { nodeLogsLocator } = await setupLogsLocator(); @@ -139,7 +140,7 @@ describe('Infra Locators', () => { it('should allow specifying specific logPosition', async () => { const params: NodeLogsLocatorParams = { nodeId, - nodeType, + nodeField, time, }; const { nodeLogsLocator } = await setupLogsLocator(); @@ -152,7 +153,7 @@ describe('Infra Locators', () => { it('should allow specifying specific filter', async () => { const params: NodeLogsLocatorParams = { nodeId, - nodeType, + nodeField, time, filter: FILTER_QUERY, }; @@ -166,7 +167,7 @@ describe('Infra Locators', () => { it('should allow specifying specific view id', async () => { const params: NodeLogsLocatorParams = { nodeId, - nodeType, + nodeField, time, logView: { ...DEFAULT_LOG_VIEW, logViewId: 'test' }, }; @@ -180,7 +181,7 @@ describe('Infra Locators', () => { it('should allow specifying specific time range', async () => { const params: NodeLogsLocatorParams = { nodeId, - nodeType, + nodeField, time, from, to, @@ -196,7 +197,7 @@ describe('Infra Locators', () => { it('should return correct structured url', async () => { const params: NodeLogsLocatorParams = { nodeId, - nodeType, + nodeField, time, logView: DEFAULT_LOG_VIEW, filter: FILTER_QUERY, @@ -237,7 +238,7 @@ const constructLogPosition = (time: number = 1550671089404) => { }; const constructLogFilter = ({ - nodeType, + nodeField, nodeId, filter, timeRange, @@ -246,7 +247,7 @@ const constructLogFilter = ({ let finalFilter = filter || ''; if (nodeId) { - const nodeFilter = `${findInventoryFields(nodeType!).id}: ${nodeId}`; + const nodeFilter = `${nodeField}: ${nodeId}`; finalFilter = filter ? `(${nodeFilter}) and (${filter})` : nodeFilter; } diff --git a/x-pack/plugins/infra/common/locators/logs_locator.ts b/x-pack/plugins/infra/common/locators/logs_locator.ts index e481c0d53d390..952a6b4704aea 100644 --- a/x-pack/plugins/infra/common/locators/logs_locator.ts +++ b/x-pack/plugins/infra/common/locators/logs_locator.ts @@ -6,19 +6,19 @@ */ import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; -import { LOGS_LOCATOR_ID, LogsLocatorParams } from '@kbn/logs-shared-plugin/common'; +import { INFRA_LOGS_LOCATOR_ID, LogsLocatorParams } from '@kbn/logs-shared-plugin/common'; import type { InfraClientCoreSetup } from '../../public/types'; -export type LogsLocator = LocatorPublic; +export type InfraLogsLocator = LocatorPublic; -export interface LogsLocatorDependencies { +export interface InfraLogsLocatorDependencies { core: InfraClientCoreSetup; } -export class LogsLocatorDefinition implements LocatorDefinition { - public readonly id = LOGS_LOCATOR_ID; +export class InfraLogsLocatorDefinition implements LocatorDefinition { + public readonly id = INFRA_LOGS_LOCATOR_ID; - constructor(protected readonly deps: LogsLocatorDependencies) {} + constructor(protected readonly deps: InfraLogsLocatorDependencies) {} public readonly getLocation = async (params: LogsLocatorParams) => { const { createSearchString } = await import('./helpers'); diff --git a/x-pack/plugins/infra/common/locators/node_logs_locator.ts b/x-pack/plugins/infra/common/locators/node_logs_locator.ts index c8c53ec69292e..d5bfe4d7ac936 100644 --- a/x-pack/plugins/infra/common/locators/node_logs_locator.ts +++ b/x-pack/plugins/infra/common/locators/node_logs_locator.ts @@ -6,20 +6,24 @@ */ import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; -import { NODE_LOGS_LOCATOR_ID, NodeLogsLocatorParams } from '@kbn/logs-shared-plugin/common'; -import type { LogsLocatorDependencies } from './logs_locator'; +import { + INFRA_NODE_LOGS_LOCATOR_ID, + NodeLogsLocatorParams, + createNodeLogsQuery, +} from '@kbn/logs-shared-plugin/common'; +import type { InfraLogsLocatorDependencies } from './logs_locator'; -export type NodeLogsLocator = LocatorPublic; +export type InfraNodeLogsLocator = LocatorPublic; -export type NodeLogsLocatorDependencies = LogsLocatorDependencies; +export type InfraNodeLogsLocatorDependencies = InfraLogsLocatorDependencies; -export class NodeLogsLocatorDefinition implements LocatorDefinition { - public readonly id = NODE_LOGS_LOCATOR_ID; +export class InfraNodeLogsLocatorDefinition implements LocatorDefinition { + public readonly id = INFRA_NODE_LOGS_LOCATOR_ID; - constructor(protected readonly deps: NodeLogsLocatorDependencies) {} + constructor(protected readonly deps: InfraNodeLogsLocatorDependencies) {} public readonly getLocation = async (params: NodeLogsLocatorParams) => { - const { createNodeLogsQuery, createSearchString } = await import('./helpers'); + const { createSearchString } = await import('./helpers'); const query = createNodeLogsQuery(params); diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.tsx index 1f2e8731a85df..f87558de360b7 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.tsx @@ -6,9 +6,9 @@ */ import { i18n } from '@kbn/i18n'; +import { UrlService } from '@kbn/share-plugin/common/url_service'; +import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; import { ObservabilityRuleTypeModel } from '@kbn/observability-plugin/public'; -import type { LocatorPublic } from '@kbn/share-plugin/public'; -import type { LogsLocatorParams } from '@kbn/logs-shared-plugin/common'; import { LOG_DOCUMENT_COUNT_RULE_TYPE_ID, PartialRuleParams, @@ -44,7 +44,7 @@ const logThresholdDefaultRecoveryMessage = i18n.translate( export function createLogThresholdRuleType( core: InfraClientCoreSetup, - logsLocator: LocatorPublic + urlService: UrlService ): ObservabilityRuleTypeModel { const ruleParamsExpression = createLazyComponentWithKibanaContext( core, @@ -56,6 +56,8 @@ export function createLogThresholdRuleType( () => import('./components/alert_details_app_section') ); + const { logsLocator } = getLogsLocatorsFromUrlService(urlService); + return { id: LOG_DOCUMENT_COUNT_RULE_TYPE_ID, description: i18n.translate('xpack.infra.logs.alertFlyout.alertDescription', { diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx index 1999c6604f553..cec2bb6f5e3e5 100644 --- a/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx @@ -12,7 +12,11 @@ import { i18n } from '@kbn/i18n'; import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { LogStream } from '@kbn/logs-shared-plugin/public'; -import { DEFAULT_LOG_VIEW, LogViewReference } from '@kbn/logs-shared-plugin/common'; +import { + DEFAULT_LOG_VIEW, + getLogsLocatorsFromUrlService, + LogViewReference, +} from '@kbn/logs-shared-plugin/common'; import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; import { InfraLoadingPanel } from '../../../loading'; @@ -34,7 +38,7 @@ export const Logs = () => { const { loading: logViewLoading, reference: logViewReference } = logs ?? {}; const { services } = useKibanaContextForPlugin(); - const { locators } = services; + const { nodeLogsLocator } = getLogsLocatorsFromUrlService(services.share.url); const [textQuery, setTextQuery] = useState(urlState?.logsSearch ?? ''); const [textQueryDebounced, setTextQueryDebounced] = useState(urlState?.logsSearch ?? ''); @@ -77,21 +81,14 @@ export const Logs = () => { ); const logsUrl = useMemo(() => { - return locators.nodeLogsLocator.getRedirectUrl({ - nodeType: asset.type, + return nodeLogsLocator.getRedirectUrl({ + nodeField: findInventoryFields(asset.type).id, nodeId: asset.name, time: state.startTimestamp, filter: textQueryDebounced, logView, }); - }, [ - locators.nodeLogsLocator, - asset.name, - asset.type, - state.startTimestamp, - textQueryDebounced, - logView, - ]); + }, [nodeLogsLocator, asset.name, asset.type, state.startTimestamp, textQueryDebounced, logView]); return ( diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.tsx index 663df4c0f4d1a..16f13171f7106 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_logs.tsx @@ -7,7 +7,7 @@ import { useEffect } from 'react'; import { useLocation, useParams } from 'react-router-dom'; -import { DEFAULT_LOG_VIEW } from '@kbn/logs-shared-plugin/common'; +import { DEFAULT_LOG_VIEW, getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; import { getFilterFromLocation, getTimeFromLocation } from './query_params'; import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; @@ -16,14 +16,15 @@ export const RedirectToLogs = () => { const location = useLocation(); const { - services: { locators }, + services: { share }, } = useKibanaContextForPlugin(); + const { logsLocator } = getLogsLocatorsFromUrlService(share.url); const filter = getFilterFromLocation(location); const time = getTimeFromLocation(location); useEffect(() => { - locators.logsLocator.navigate( + logsLocator.navigate( { time, filter, @@ -31,7 +32,7 @@ export const RedirectToLogs = () => { }, { replace: true } ); - }, [filter, locators.logsLocator, logViewId, time]); + }, [filter, logsLocator, logViewId, time]); return null; }; diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx index 0eade78931ed0..0be958882cedb 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx @@ -7,8 +7,8 @@ import { useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { DEFAULT_LOG_VIEW } from '@kbn/logs-shared-plugin/common'; -import { InventoryItemType } from '@kbn/metrics-data-access-plugin/common'; +import { DEFAULT_LOG_VIEW, getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { findInventoryFields, InventoryItemType } from '@kbn/metrics-data-access-plugin/common'; import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; import { getFilterFromLocation, getTimeFromLocation } from './query_params'; @@ -26,24 +26,25 @@ export const RedirectToNodeLogs = ({ location, }: RedirectToNodeLogsType) => { const { - services: { locators }, + services: { share }, } = useKibanaContextForPlugin(); + const { nodeLogsLocator } = getLogsLocatorsFromUrlService(share.url); const filter = getFilterFromLocation(location); const time = getTimeFromLocation(location); useEffect(() => { - locators.nodeLogsLocator.navigate( + nodeLogsLocator.navigate( { + nodeField: findInventoryFields(nodeType).id, nodeId, - nodeType, time, filter, logView: { type: 'log-view-reference', logViewId }, }, { replace: true } ); - }, [filter, locators.nodeLogsLocator, logViewId, nodeId, nodeType, time]); + }, [filter, nodeLogsLocator, logViewId, nodeId, nodeType, time]); return null; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx index cd2537418e46c..d7fbd4dbf1be9 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { LogViewReference } from '@kbn/logs-shared-plugin/common'; +import { getLogsLocatorsFromUrlService, LogViewReference } from '@kbn/logs-shared-plugin/common'; import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; interface LogsLinkToStreamProps { @@ -20,12 +20,13 @@ interface LogsLinkToStreamProps { export const LogsLinkToStream = ({ startTime, endTime, query, logView }: LogsLinkToStreamProps) => { const { services } = useKibanaContextForPlugin(); - const { locators } = services; + const { share } = services; + const { logsLocator } = getLogsLocatorsFromUrlService(share.url); return ( = withTheme const inventoryModel = findInventoryModel(nodeType); const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000; const { services } = useKibanaContextForPlugin(); - const { application, share, locators } = services; + const { application, share } = services; + const { nodeLogsLocator } = getLogsLocatorsFromUrlService(share.url); const uiCapabilities = application?.capabilities; // Due to the changing nature of the fields between APM and this UI, // We need to have some exceptions until 7.0 & ECS is finalized. Reference @@ -109,8 +111,8 @@ export const NodeContextMenu: React.FC = withTheme defaultMessage: '{inventoryName} logs', values: { inventoryName: inventoryModel.singularDisplayName }, }), - href: locators.nodeLogsLocator.getRedirectUrl({ - nodeType, + href: nodeLogsLocator.getRedirectUrl({ + nodeField: findInventoryFields(nodeType).id, nodeId: node.id, time: currentTime, }), diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index 7d717cf9057e4..f89d99a43a57b 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -27,8 +27,8 @@ import { LOG_STREAM_EMBEDDABLE } from './components/log_stream/log_stream_embedd import { LogStreamEmbeddableFactoryDefinition } from './components/log_stream/log_stream_embeddable_factory'; import { type InfraLocators, - LogsLocatorDefinition, - NodeLogsLocatorDefinition, + InfraLogsLocatorDefinition, + InfraNodeLogsLocatorDefinition, } from '../common/locators'; import { createMetricsFetchData, createMetricsHasData } from './metrics_overview_fetchers'; import { registerFeatures } from './register_feature'; @@ -179,13 +179,15 @@ export class Plugin implements InfraClientPluginClass { ); // Register Locators - const logsLocator = pluginsSetup.share.url.locators.create(new LogsLocatorDefinition({ core })); - const nodeLogsLocator = pluginsSetup.share.url.locators.create( - new NodeLogsLocatorDefinition({ core }) - ); + const logsLocator = this.config.featureFlags.logsUIEnabled + ? pluginsSetup.share.url.locators.create(new InfraLogsLocatorDefinition({ core })) + : undefined; + const nodeLogsLocator = this.config.featureFlags.logsUIEnabled + ? pluginsSetup.share.url.locators.create(new InfraNodeLogsLocatorDefinition({ core })) + : undefined; pluginsSetup.observability.observabilityRuleTypeRegistry.register( - createLogThresholdRuleType(core, logsLocator) + createLogThresholdRuleType(core, pluginsSetup.share.url) ); if (this.config.featureFlags.logsUIEnabled) { diff --git a/x-pack/plugins/logs_shared/common/index.ts b/x-pack/plugins/logs_shared/common/index.ts index 99fd7c1166863..f6b1e9ea27e43 100644 --- a/x-pack/plugins/logs_shared/common/index.ts +++ b/x-pack/plugins/logs_shared/common/index.ts @@ -60,5 +60,13 @@ export { } from './http_api'; // Locators -export { LOGS_LOCATOR_ID, NODE_LOGS_LOCATOR_ID } from './locators'; -export type { LogsLocatorParams, NodeLogsLocatorParams } from './locators'; +export { + LOGS_LOCATOR_ID, + TRACE_LOGS_LOCATOR_ID, + NODE_LOGS_LOCATOR_ID, + INFRA_LOGS_LOCATOR_ID, + INFRA_NODE_LOGS_LOCATOR_ID, + getLogsLocatorsFromUrlService, +} from './locators'; +export type { LogsLocatorParams, NodeLogsLocatorParams, TraceLogsLocatorParams } from './locators'; +export { createNodeLogsQuery } from './locators/helpers'; diff --git a/x-pack/plugins/logs_shared/common/locators/get_logs_locators.ts b/x-pack/plugins/logs_shared/common/locators/get_logs_locators.ts new file mode 100644 index 0000000000000..5c403c2bcb5b0 --- /dev/null +++ b/x-pack/plugins/logs_shared/common/locators/get_logs_locators.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UrlService } from '@kbn/share-plugin/common/url_service'; + +import { LogsLocatorParams, NodeLogsLocatorParams, TraceLogsLocatorParams } from './types'; +import { LOGS_LOCATOR_ID } from './logs_locator'; +import { NODE_LOGS_LOCATOR_ID } from './node_logs_locator'; +import { TRACE_LOGS_LOCATOR_ID } from './trace_logs_locator'; + +export const getLogsLocatorsFromUrlService = (urlService: UrlService) => { + const logsLocator = urlService.locators.get(LOGS_LOCATOR_ID)!; + const nodeLogsLocator = urlService.locators.get(NODE_LOGS_LOCATOR_ID)!; + const traceLogsLocator = urlService.locators.get(TRACE_LOGS_LOCATOR_ID)!; + + return { + logsLocator, + traceLogsLocator, + nodeLogsLocator, + }; +}; diff --git a/x-pack/plugins/logs_shared/common/locators/helpers.ts b/x-pack/plugins/logs_shared/common/locators/helpers.ts new file mode 100644 index 0000000000000..ae25c8abb3c18 --- /dev/null +++ b/x-pack/plugins/logs_shared/common/locators/helpers.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment, { DurationInputObject } from 'moment'; +import { LogsLocatorParams, NodeLogsLocatorParams, TraceLogsLocatorParams } from './types'; + +export const getLogsQuery = (params: LogsLocatorParams) => { + const { filter } = params; + + return filter ? { language: 'kuery', query: filter } : undefined; +}; + +export const createNodeLogsQuery = (params: NodeLogsLocatorParams) => { + const { nodeField, nodeId, filter } = params; + + const nodeFilter = `${nodeField}: ${nodeId}`; + return filter ? `(${nodeFilter}) and (${filter})` : nodeFilter; +}; + +export const getNodeQuery = (params: NodeLogsLocatorParams) => { + return { language: 'kuery', query: createNodeLogsQuery(params) }; +}; + +export const getTraceQuery = (params: TraceLogsLocatorParams) => { + const { traceId, filter } = params; + + const traceFilter = `trace.id:"${traceId}" OR (not trace.id:* AND "${traceId}")`; + const query = filter ? `(${traceFilter}) and (${filter})` : traceFilter; + + return { language: 'kuery', query }; +}; + +const defaultTimeRangeFromPositionOffset: DurationInputObject = { hours: 1 }; + +export const getTimeRangeStartFromTime = (time: number): string => + moment(time).subtract(defaultTimeRangeFromPositionOffset).toISOString(); + +export const getTimeRangeEndFromTime = (time: number): string => + moment(time).add(defaultTimeRangeFromPositionOffset).toISOString(); diff --git a/x-pack/plugins/logs_shared/common/locators/index.ts b/x-pack/plugins/logs_shared/common/locators/index.ts index d680977f29f89..2cbe5cc2d6ba3 100644 --- a/x-pack/plugins/logs_shared/common/locators/index.ts +++ b/x-pack/plugins/logs_shared/common/locators/index.ts @@ -6,4 +6,14 @@ */ export * from './logs_locator'; +export * from './trace_logs_locator'; export * from './node_logs_locator'; +export * from './infra'; +export * from './get_logs_locators'; + +export type { + LogsSharedLocators, + LogsLocatorParams, + NodeLogsLocatorParams, + TraceLogsLocatorParams, +} from './types'; diff --git a/x-pack/plugins/logs_shared/common/locators/infra.ts b/x-pack/plugins/logs_shared/common/locators/infra.ts new file mode 100644 index 0000000000000..c9351c375d03f --- /dev/null +++ b/x-pack/plugins/logs_shared/common/locators/infra.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const INFRA_LOGS_LOCATOR_ID = 'INFRA_LOGS_LOCATOR'; + +export const INFRA_NODE_LOGS_LOCATOR_ID = 'INFRA_NODE_LOGS_LOCATOR'; diff --git a/x-pack/plugins/logs_shared/common/locators/logs_locator.ts b/x-pack/plugins/logs_shared/common/locators/logs_locator.ts index bfd53276ab3e9..2ebb343e10bbc 100644 --- a/x-pack/plugins/logs_shared/common/locators/logs_locator.ts +++ b/x-pack/plugins/logs_shared/common/locators/logs_locator.ts @@ -5,19 +5,40 @@ * 2.0. */ -import { SerializableRecord } from '@kbn/utility-types'; -import type { TimeRange } from './time_range'; -import type { LogViewReference } from '../log_views/types'; +import { ALL_DATASETS_LOCATOR_ID, AllDatasetsLocatorParams } from '@kbn/deeplinks-observability'; +import { LocatorDefinition } from '@kbn/share-plugin/common'; +import { LocatorClient } from '@kbn/share-plugin/common/url_service'; + +import { INFRA_LOGS_LOCATOR_ID } from './infra'; +import { LogsLocatorParams } from './types'; +import { getLogsQuery, getTimeRangeEndFromTime, getTimeRangeStartFromTime } from './helpers'; export const LOGS_LOCATOR_ID = 'LOGS_LOCATOR'; -export interface LogsLocatorParams extends SerializableRecord { - /** Defines log position */ - time?: number; - /** - * Optionally set the time range in the time picker. - */ - timeRange?: TimeRange; - filter?: string; - logView?: LogViewReference; +export class LogsLocatorDefinition implements LocatorDefinition { + public readonly id = LOGS_LOCATOR_ID; + + constructor(private readonly locators: LocatorClient) {} + + public readonly getLocation = async (params: LogsLocatorParams) => { + const infraLogsLocator = this.locators.get(INFRA_LOGS_LOCATOR_ID); + if (infraLogsLocator) { + return infraLogsLocator.getLocation(params); + } + + const allDatasetsLocator = + this.locators.get(ALL_DATASETS_LOCATOR_ID)!; + const { time } = params; + return allDatasetsLocator.getLocation({ + query: getLogsQuery(params), + ...(time + ? { + timeRange: { + from: getTimeRangeStartFromTime(time), + to: getTimeRangeEndFromTime(time), + }, + } + : {}), + }); + }; } diff --git a/x-pack/plugins/logs_shared/common/locators/node_logs_locator.ts b/x-pack/plugins/logs_shared/common/locators/node_logs_locator.ts index 188c4d1e5a966..e5288630334b8 100644 --- a/x-pack/plugins/logs_shared/common/locators/node_logs_locator.ts +++ b/x-pack/plugins/logs_shared/common/locators/node_logs_locator.ts @@ -5,12 +5,45 @@ * 2.0. */ -import type { InventoryItemType } from './types'; -import type { LogsLocatorParams } from './logs_locator'; +import { + AllDatasetsLocatorParams, + ALL_DATASETS_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; +import { LocatorClient, LocatorDefinition } from '@kbn/share-plugin/common/url_service'; + +import { NodeLogsLocatorParams } from './types'; +import { INFRA_NODE_LOGS_LOCATOR_ID } from './infra'; +import { getNodeQuery, getTimeRangeStartFromTime, getTimeRangeEndFromTime } from './helpers'; export const NODE_LOGS_LOCATOR_ID = 'NODE_LOGS_LOCATOR'; -export interface NodeLogsLocatorParams extends LogsLocatorParams { - nodeId: string; - nodeType: InventoryItemType; +export class NodeLogsLocatorDefinition implements LocatorDefinition { + public readonly id = NODE_LOGS_LOCATOR_ID; + + constructor(private readonly locators: LocatorClient) {} + + public readonly getLocation = async (params: NodeLogsLocatorParams) => { + const infraNodeLogsLocator = this.locators.get( + INFRA_NODE_LOGS_LOCATOR_ID + ); + + if (infraNodeLogsLocator) { + return infraNodeLogsLocator.getLocation(params); + } + + const allDatasetsLocator = + this.locators.get(ALL_DATASETS_LOCATOR_ID)!; + const { time } = params; + return allDatasetsLocator.getLocation({ + query: getNodeQuery(params), + ...(time + ? { + timeRange: { + from: getTimeRangeStartFromTime(time), + to: getTimeRangeEndFromTime(time), + }, + } + : {}), + }); + }; } diff --git a/x-pack/plugins/logs_shared/common/locators/trace_logs_locator.ts b/x-pack/plugins/logs_shared/common/locators/trace_logs_locator.ts new file mode 100644 index 0000000000000..a62155aaaf4d1 --- /dev/null +++ b/x-pack/plugins/logs_shared/common/locators/trace_logs_locator.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ALL_DATASETS_LOCATOR_ID, AllDatasetsLocatorParams } from '@kbn/deeplinks-observability'; +import { LocatorDefinition } from '@kbn/share-plugin/common'; +import { LocatorClient } from '@kbn/share-plugin/common/url_service'; +import { INFRA_LOGS_LOCATOR_ID } from './infra'; +import { LogsLocatorParams, TraceLogsLocatorParams } from './types'; + +import { getTraceQuery, getTimeRangeEndFromTime, getTimeRangeStartFromTime } from './helpers'; + +export const TRACE_LOGS_LOCATOR_ID = 'TRACE_LOGS_LOCATOR'; + +export class TraceLogsLocatorDefinition implements LocatorDefinition { + public readonly id = TRACE_LOGS_LOCATOR_ID; + + constructor(private readonly locators: LocatorClient) {} + + public readonly getLocation = async (params: TraceLogsLocatorParams) => { + const infraLogsLocator = this.locators.get(INFRA_LOGS_LOCATOR_ID); + if (infraLogsLocator) { + return infraLogsLocator.getLocation({ + ...params, + filter: getTraceQuery(params).query, + }); + } + + const { time } = params; + const allDatasetsLocator = + this.locators.get(ALL_DATASETS_LOCATOR_ID)!; + return allDatasetsLocator.getLocation({ + query: getTraceQuery(params), + ...(time + ? { + timeRange: { + from: getTimeRangeStartFromTime(time), + to: getTimeRangeEndFromTime(time), + }, + } + : {}), + }); + }; +} diff --git a/x-pack/plugins/logs_shared/common/locators/types.ts b/x-pack/plugins/logs_shared/common/locators/types.ts index af6ec963098a0..07c50590b3efb 100644 --- a/x-pack/plugins/logs_shared/common/locators/types.ts +++ b/x-pack/plugins/logs_shared/common/locators/types.ts @@ -5,16 +5,33 @@ * 2.0. */ -import * as rt from 'io-ts'; +import { SerializableRecord } from '@kbn/utility-types'; +import { LocatorPublic } from '@kbn/share-plugin/common'; +import { LogViewReference } from '../log_views/types'; +import { TimeRange } from './time_range'; -export const ItemTypeRT = rt.keyof({ - host: null, - pod: null, - container: null, - awsEC2: null, - awsS3: null, - awsSQS: null, - awsRDS: null, -}); +export interface LogsLocatorParams extends SerializableRecord { + /** Defines log position */ + time?: number; + /** + * Optionally set the time range in the time picker. + */ + timeRange?: TimeRange; + filter?: string; + logView?: LogViewReference; +} -export type InventoryItemType = rt.TypeOf; +export interface TraceLogsLocatorParams extends LogsLocatorParams { + traceId: string; +} + +export interface NodeLogsLocatorParams extends LogsLocatorParams { + nodeField: string; + nodeId: string; +} + +export interface LogsSharedLocators { + logsLocator: LocatorPublic; + nodeLogsLocator: LocatorPublic; + traceLogsLocator: LocatorPublic; +} diff --git a/x-pack/plugins/logs_shared/kibana.jsonc b/x-pack/plugins/logs_shared/kibana.jsonc index b78503b140a71..fc8dcf0e64d96 100644 --- a/x-pack/plugins/logs_shared/kibana.jsonc +++ b/x-pack/plugins/logs_shared/kibana.jsonc @@ -13,7 +13,8 @@ "dataViews", "usageCollection", "observabilityShared", - "observabilityAIAssistant" + "observabilityAIAssistant", + "share" ], "requiredBundles": ["kibanaUtils", "kibanaReact"], "extraPublicDirs": ["common"] diff --git a/x-pack/plugins/logs_shared/public/plugin.ts b/x-pack/plugins/logs_shared/public/plugin.ts index 092e95570db7f..1d6063c34eed3 100644 --- a/x-pack/plugins/logs_shared/public/plugin.ts +++ b/x-pack/plugins/logs_shared/public/plugin.ts @@ -6,9 +6,19 @@ */ import { CoreStart } from '@kbn/core/public'; +import { + LogsLocatorDefinition, + NodeLogsLocatorDefinition, + TraceLogsLocatorDefinition, +} from '../common/locators'; import { createLogAIAssistant } from './components/log_ai_assistant'; import { LogViewsService } from './services/log_views'; -import { LogsSharedClientPluginClass, LogsSharedClientStartDeps } from './types'; +import { + LogsSharedClientCoreSetup, + LogsSharedClientPluginClass, + LogsSharedClientSetupDeps, + LogsSharedClientStartDeps, +} from './types'; export class LogsSharedPlugin implements LogsSharedClientPluginClass { private logViews: LogViewsService; @@ -17,10 +27,27 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { this.logViews = new LogViewsService(); } - public setup() { + public setup(_: LogsSharedClientCoreSetup, pluginsSetup: LogsSharedClientSetupDeps) { const logViews = this.logViews.setup(); - return { logViews }; + const logsLocator = pluginsSetup.share.url.locators.create( + new LogsLocatorDefinition(pluginsSetup.share.url.locators) + ); + const nodeLogsLocator = pluginsSetup.share.url.locators.create( + new NodeLogsLocatorDefinition(pluginsSetup.share.url.locators) + ); + + const traceLogsLocator = pluginsSetup.share.url.locators.create( + new TraceLogsLocatorDefinition(pluginsSetup.share.url.locators) + ); + + const locators = { + logsLocator, + nodeLogsLocator, + traceLogsLocator, + }; + + return { logViews, locators }; } public start(core: CoreStart, plugins: LogsSharedClientStartDeps) { diff --git a/x-pack/plugins/logs_shared/public/types.ts b/x-pack/plugins/logs_shared/public/types.ts index 2a2e9c1cf742d..da5d2ec49e622 100644 --- a/x-pack/plugins/logs_shared/public/types.ts +++ b/x-pack/plugins/logs_shared/public/types.ts @@ -5,18 +5,14 @@ * 2.0. */ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - import type { CoreSetup, CoreStart, Plugin as PluginClass } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public'; +import { SharePluginSetup } from '@kbn/share-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; + +import { LogsSharedLocators } from '../common/locators'; import type { LogAIAssistantProps } from './components/log_ai_assistant/log_ai_assistant'; // import type { OsqueryPluginStart } from '../../osquery/public'; import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views'; @@ -24,6 +20,7 @@ import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views // Our own setup and start contract values export interface LogsSharedClientSetupExports { logViews: LogViewsServiceSetup; + locators: LogsSharedLocators; } export interface LogsSharedClientStartExports { @@ -31,8 +28,9 @@ export interface LogsSharedClientStartExports { LogAIAssistant: (props: Omit) => JSX.Element; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface LogsSharedClientSetupDeps {} +export interface LogsSharedClientSetupDeps { + share: SharePluginSetup; +} export interface LogsSharedClientStartDeps { data: DataPublicPluginStart; diff --git a/x-pack/plugins/logs_shared/tsconfig.json b/x-pack/plugins/logs_shared/tsconfig.json index b7c17b46c9a62..8d06a68a70873 100644 --- a/x-pack/plugins/logs_shared/tsconfig.json +++ b/x-pack/plugins/logs_shared/tsconfig.json @@ -26,6 +26,8 @@ "@kbn/datemath", "@kbn/core-http-browser", "@kbn/ui-actions-plugin", - "@kbn/observability-ai-assistant-plugin" + "@kbn/observability-ai-assistant-plugin", + "@kbn/deeplinks-observability", + "@kbn/share-plugin" ] }