From 9c41ae47686f65098a1dd600cc08dc3b0a5e551a Mon Sep 17 00:00:00 2001 From: mohamedhamed-ahmed Date: Wed, 20 Sep 2023 19:49:57 +0200 Subject: [PATCH] [Log Explorer] Add Explorer app locator (#165962) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes https://github.com/elastic/kibana/issues/164995 closes https://github.com/elastic/kibana/issues/165618 closes https://github.com/elastic/kibana/issues/166596 ## 📝 Summary ### Observability Log Explorer Locators: This PR adds 2 new customized locators to the Observability log explorer profile. At the moment we implemented: 1- Single dataset selector locator 2- All dataset selector locator With more locators to come in the future depending on the use cases. ### Log Explorer Locators: We also added a log explorer locator that navigates to discover, this can be used in case the **Observability Log Explorer** plugin is disabled. ### Logs Onboarding: The PR also replaces the temp navigation to the default discover we implemented for[ 8.10 here](https://github.com/elastic/kibana/pull/163218) with the above new Observability Log Explorer locators. ### APM: After [disabling infra plugin in serverless projects](https://github.com/elastic/kibana/pull/165289), APM links to infra locators in serverless have been replaced to use the above locators. ### Observability Landing Page: The landing page now redirects to the Log Explorer if `logs-*-*` has data in it, otherwise the flow continues as before. ### Necessary Refactoring: To avoid the circular dependency between `ObservabilityLogExplorer` & `ObservabilityOnboarding` after each one using the other's locator and importing the necessary types, I moved the type definition for all locators in the `deeplinks` package. ## ✅ Testing - Onboarding Wizard in Serverless and Stateful 1. Navigate to the onboarding flow `/app/observabilityOnboarding/` 2. Choose either System logs or Stream log files 3. Go through the onboarding wizard 4. Click the Explore logs button at the end 5. You should be redirected to observability log explorer with the integration and dataset preselected. - APM links in Serverless 1. Navigate to APM and click on the logs links as shown in the Demos below 2. All links should navigate to Observability Log Explorer with the queries set in the search bar. ## 🎥 Demos - APM Serverless https://github.com/elastic/kibana/assets/11225826/7161364e-333f-4ac4-87d5-7f1ffec699b3 - APM Stateful https://github.com/elastic/kibana/assets/11225826/058c9587-b766-4d4f-a73d-50fd381be4bb - Onboarding Serverless https://github.com/elastic/kibana/assets/11225826/ee1cab42-f91c-4558-aa5f-4fa7e8963427 - Onboarding Stateful https://github.com/elastic/kibana/assets/11225826/a376a12b-499b-4488-a75a-d06e81f8e21d - Observability Landing Page https://github.com/elastic/kibana/assets/11225826/c1c084ca-b1b1-4c4b-a4e6-ae8e157dcf57 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Marco Antonio Ghiani Co-authored-by: Marco Antonio Ghiani --- packages/deeplinks/observability/index.ts | 2 + .../deeplinks/observability/locators/index.ts | 11 + .../observability/locators/log_explorer.ts | 51 +++ .../locators/observability_log_explorer.ts | 31 ++ .../locators/observability_onboarding.ts | 15 + .../deeplinks/observability/tsconfig.json | 2 + .../plugins/apm/public/application/index.tsx | 1 + .../instance_actions_menu/index.tsx | 11 +- .../instance_actions_menu/menu_sections.ts | 134 ++++--- .../shared/links/observability_logs_link.ts | 91 +++++ .../transaction_action_menu/sections.test.ts | 9 +- .../transaction_action_menu/sections.ts | 365 +++++++++--------- .../transaction_action_menu.test.tsx | 6 +- .../transaction_action_menu.tsx | 11 +- .../context/apm_plugin/apm_plugin_context.tsx | 4 +- .../apm_plugin/mock_apm_plugin_context.tsx | 1 + x-pack/plugins/apm/tsconfig.json | 3 +- .../all_dataset_selection.ts | 2 +- .../dataset_selection/encoding.test.ts | 0 .../dataset_selection/encoding.ts | 2 +- .../dataset_selection/errors.ts | 0 .../hydrate_dataset_selection.ts.ts | 4 + .../dataset_selection/index.ts | 13 +- .../single_dataset_selection.ts | 2 +- .../dataset_selection/types.ts | 18 +- .../unresolved_dataset_selection.ts | 59 +++ .../common/datasets/models/dataset.ts | 4 +- x-pack/plugins/log_explorer/common/index.ts | 8 + .../log_explorer/common/locators/index.ts | 14 + .../common/locators/log_explorer/index.ts | 8 + .../log_explorer/log_explorer_locator.test.ts | 60 +++ .../log_explorer/log_explorer_locator.ts | 38 ++ .../common/locators/log_explorer/types.ts | 12 + x-pack/plugins/log_explorer/kibana.jsonc | 8 +- .../dataset_selector.stories.tsx | 8 +- .../state_machine/defaults.ts | 2 +- .../state_machine/state_machine.ts | 2 +- .../dataset_selector/state_machine/types.ts | 2 +- .../sub_components/datasets_popover.tsx | 2 +- .../components/dataset_selector/types.ts | 2 +- .../components/log_explorer/log_explorer.tsx | 3 +- .../customizations/log_explorer_profile.tsx | 7 +- .../public/hooks/use_dataset_selection.ts | 2 +- x-pack/plugins/log_explorer/public/plugin.ts | 22 +- .../services/datasets/datasets_client.ts | 1 + .../services/datasets/datasets_service.ts | 2 +- .../log_explorer_profile/src/defaults.ts | 2 +- .../src/selection_service.ts | 63 +++ .../log_explorer_profile/src/state_machine.ts | 29 +- .../log_explorer_profile/src/types.ts | 9 +- .../src/url_state_storage_service.ts | 10 +- x-pack/plugins/log_explorer/public/types.ts | 14 +- x-pack/plugins/log_explorer/tsconfig.json | 2 + .../public/pages/landing/landing.tsx | 15 +- x-pack/plugins/observability/tsconfig.json | 4 +- .../common/index.ts | 8 + .../all_datasets/all_datasets_locator.ts | 34 ++ .../common/locators/all_datasets/index.ts | 8 + .../common/locators/index.ts | 18 + .../common/locators/locators.test.ts | 333 ++++++++++++++++ .../common/locators/single_dataset/index.ts | 8 + .../single_dataset/single_dataset_locator.ts | 50 +++ .../common/locators/types.ts | 20 + .../common/locators/utils/helpers.ts | 60 +++ .../common/locators/utils/index.ts | 8 + .../observability_log_explorer/kibana.jsonc | 8 +- .../components/log_explorer_top_nav_menu.tsx | 2 +- .../public/plugin.ts | 30 +- .../public/types.ts | 11 +- .../observability_log_explorer/tsconfig.json | 5 +- .../custom_logs/install_elastic_agent.cy.ts | 6 +- .../e2e/cypress/e2e/logs/system_logs.cy.ts | 6 +- .../app/custom_logs/install_elastic_agent.tsx | 39 +- .../app/system_logs/install_elastic_agent.tsx | 24 +- .../public/components/app/utils.ts | 56 --- .../onboarding_locator/locator_definition.ts | 8 +- .../locators/onboarding_locator/types.ts | 8 +- .../observability_onboarding/public/plugin.ts | 2 - .../observability_onboarding/tsconfig.json | 5 +- 79 files changed, 1549 insertions(+), 411 deletions(-) create mode 100644 packages/deeplinks/observability/locators/index.ts create mode 100644 packages/deeplinks/observability/locators/log_explorer.ts create mode 100644 packages/deeplinks/observability/locators/observability_log_explorer.ts create mode 100644 packages/deeplinks/observability/locators/observability_onboarding.ts create mode 100644 x-pack/plugins/apm/public/components/shared/links/observability_logs_link.ts rename x-pack/plugins/log_explorer/{public/utils => common}/dataset_selection/all_dataset_selection.ts (95%) rename x-pack/plugins/log_explorer/{public/utils => common}/dataset_selection/encoding.test.ts (100%) rename x-pack/plugins/log_explorer/{public/utils => common}/dataset_selection/encoding.ts (95%) rename x-pack/plugins/log_explorer/{public/utils => common}/dataset_selection/errors.ts (100%) rename x-pack/plugins/log_explorer/{public/utils => common}/dataset_selection/hydrate_dataset_selection.ts.ts (77%) rename x-pack/plugins/log_explorer/{public/utils => common}/dataset_selection/index.ts (66%) rename x-pack/plugins/log_explorer/{public/utils => common}/dataset_selection/single_dataset_selection.ts (97%) rename x-pack/plugins/log_explorer/{public/utils => common}/dataset_selection/types.ts (74%) create mode 100644 x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts create mode 100644 x-pack/plugins/log_explorer/common/index.ts create mode 100644 x-pack/plugins/log_explorer/common/locators/index.ts create mode 100644 x-pack/plugins/log_explorer/common/locators/log_explorer/index.ts create mode 100644 x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.test.ts create mode 100644 x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.ts create mode 100644 x-pack/plugins/log_explorer/common/locators/log_explorer/types.ts create mode 100644 x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/selection_service.ts create mode 100644 x-pack/plugins/observability_log_explorer/common/index.ts create mode 100644 x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts create mode 100644 x-pack/plugins/observability_log_explorer/common/locators/all_datasets/index.ts create mode 100644 x-pack/plugins/observability_log_explorer/common/locators/index.ts create mode 100644 x-pack/plugins/observability_log_explorer/common/locators/locators.test.ts create mode 100644 x-pack/plugins/observability_log_explorer/common/locators/single_dataset/index.ts create mode 100644 x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts create mode 100644 x-pack/plugins/observability_log_explorer/common/locators/types.ts create mode 100644 x-pack/plugins/observability_log_explorer/common/locators/utils/helpers.ts create mode 100644 x-pack/plugins/observability_log_explorer/common/locators/utils/index.ts delete mode 100644 x-pack/plugins/observability_onboarding/public/components/app/utils.ts diff --git a/packages/deeplinks/observability/index.ts b/packages/deeplinks/observability/index.ts index 9292cdf77d70c..39608b4b7f7a9 100644 --- a/packages/deeplinks/observability/index.ts +++ b/packages/deeplinks/observability/index.ts @@ -9,3 +9,5 @@ export { OBSERVABILITY_ONBOARDING_APP_ID } from './constants'; export type { AppId, DeepLinkId } from './deep_links'; + +export * from './locators'; diff --git a/packages/deeplinks/observability/locators/index.ts b/packages/deeplinks/observability/locators/index.ts new file mode 100644 index 0000000000000..8761b00b7a159 --- /dev/null +++ b/packages/deeplinks/observability/locators/index.ts @@ -0,0 +1,11 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './log_explorer'; +export * from './observability_log_explorer'; +export * from './observability_onboarding'; diff --git a/packages/deeplinks/observability/locators/log_explorer.ts b/packages/deeplinks/observability/locators/log_explorer.ts new file mode 100644 index 0000000000000..752ae3d79bee8 --- /dev/null +++ b/packages/deeplinks/observability/locators/log_explorer.ts @@ -0,0 +1,51 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import type { SerializableRecord } from '@kbn/utility-types'; +import type { Filter, TimeRange, Query, AggregateQuery } from '@kbn/es-query'; + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type RefreshInterval = { + pause: boolean; + value: number; +}; + +export const LOG_EXPLORER_LOCATOR_ID = 'LOG_EXPLORER_LOCATOR'; + +export interface LogExplorerNavigationParams extends SerializableRecord { + /** + * Optionally set the time range in the time picker. + */ + timeRange?: TimeRange; + /** + * Optionally set the refresh interval. + */ + refreshInterval?: RefreshInterval; + /** + * Optionally set a query. + */ + query?: Query | AggregateQuery; + /** + * Columns displayed in the table + */ + columns?: string[]; + /** + * Array of the used sorting [[field,direction],...] + */ + sort?: string[][]; + /** + * Optionally apply filters. + */ + filters?: Filter[]; +} + +export interface LogExplorerLocatorParams extends LogExplorerNavigationParams { + /** + * Dataset name to be selected. + */ + dataset?: string; +} diff --git a/packages/deeplinks/observability/locators/observability_log_explorer.ts b/packages/deeplinks/observability/locators/observability_log_explorer.ts new file mode 100644 index 0000000000000..e5392712f76b5 --- /dev/null +++ b/packages/deeplinks/observability/locators/observability_log_explorer.ts @@ -0,0 +1,31 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { LogExplorerNavigationParams } from './log_explorer'; + +export type DatasetLocatorParams = LogExplorerNavigationParams; + +// All datasets locator +export const ALL_DATASETS_LOCATOR_ID = 'ALL_DATASETS_LOCATOR'; + +export type AllDatasetsLocatorParams = DatasetLocatorParams; + +// Single dataset locator +export const SINGLE_DATASET_LOCATOR_ID = 'SINGLE_DATASET_LOCATOR'; + +export interface SingleDatasetLocatorParams extends DatasetLocatorParams { + /** + * Integration name to be selected. + */ + integration?: string; + /** + * Dataset name to be selected. + * ex: system.syslog + */ + dataset: string; +} diff --git a/packages/deeplinks/observability/locators/observability_onboarding.ts b/packages/deeplinks/observability/locators/observability_onboarding.ts new file mode 100644 index 0000000000000..08bde8331c045 --- /dev/null +++ b/packages/deeplinks/observability/locators/observability_onboarding.ts @@ -0,0 +1,15 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { SerializableRecord } from '@kbn/utility-types'; + +export const OBSERVABILITY_ONBOARDING_LOCATOR = 'OBSERVABILITY_ONBOARDING_LOCATOR' as const; + +export interface ObservabilityOnboardingLocatorParams extends SerializableRecord { + /** If given, it will load the given map else will load the create a new map page. */ + source?: 'customLogs' | 'systemLogs'; +} diff --git a/packages/deeplinks/observability/tsconfig.json b/packages/deeplinks/observability/tsconfig.json index 94b099694eaf4..425a5a8612854 100644 --- a/packages/deeplinks/observability/tsconfig.json +++ b/packages/deeplinks/observability/tsconfig.json @@ -16,5 +16,7 @@ "target/**/*" ], "kbn_references": [ + "@kbn/utility-types", + "@kbn/es-query", ] } diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index a79be6c310be5..52020aed93453 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -58,6 +58,7 @@ export const renderApp = ({ lens: pluginsStart.lens, uiActions: pluginsStart.uiActions, observabilityAIAssistant: pluginsStart.observabilityAIAssistant, + share: pluginsSetup.share, }; // render APM feedback link in global help menu 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 d2e8034cab32f..b6d51aec3268e 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 @@ -15,6 +15,10 @@ import { SectionSubtitle, SectionTitle, } from '@kbn/observability-shared-plugin/public'; +import { + AllDatasetsLocatorParams, + ALL_DATASETS_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; 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'; @@ -40,7 +44,7 @@ export function InstanceActionsMenu({ kuery, onClose, }: Props) { - const { core, infra } = useApmPluginContext(); + const { core, infra, share } = useApmPluginContext(); const { data, status } = useInstanceDetailsFetcher({ serviceName, serviceNodeName, @@ -52,6 +56,10 @@ export function InstanceActionsMenu({ const metricOverviewHref = useMetricOverviewHref(serviceName); const history = useHistory(); + const allDatasetsLocator = share.url.locators.get( + ALL_DATASETS_LOCATOR_ID + )!; + if (isPending(status)) { return (
void; metricsHref: string; - infraLocators: InfraLocators; + infraLocators?: InfraLocators; + allDatasetsLocator: LocatorPublic; }) { const podId = instanceDetails.kubernetes?.pod?.uid; const containerId = instanceDetails.container?.id; @@ -54,69 +59,72 @@ export function getMenuSections({ const infraMetricsQuery = getInfraMetricsQuery(instanceDetails['@timestamp']); const infraNodeLocator = infraLocators?.nodeLogsLocator; - const podActions: Action[] = infraNodeLocator - ? [ - { - key: 'podLogs', - label: i18n.translate( - 'xpack.apm.serviceOverview.instancesTable.actionMenus.podLogs', - { defaultMessage: 'Pod logs' } - ), - href: infraNodeLocator?.getRedirectUrl({ - nodeId: podId!, - nodeType: 'pod', - time, - }), - condition: !!podId, - }, - { - key: 'podMetrics', - label: i18n.translate( - 'xpack.apm.serviceOverview.instancesTable.actionMenus.podMetrics', - { defaultMessage: 'Pod metrics' } - ), - href: getInfraHref({ - app: 'metrics', - basePath, - path: `/link-to/pod-detail/${podId}`, - query: infraMetricsQuery, - }), - condition: !!podId, - }, - ] - : []; + const podLogsHref = getNodeLogsHref( + 'pod', + podId!, + time, + allDatasetsLocator, + infraNodeLocator + ); + const containerLogsHref = getNodeLogsHref( + 'container', + containerId!, + time, + allDatasetsLocator, + infraNodeLocator + ); - const containerActions: Action[] = infraNodeLocator - ? [ - { - key: 'containerLogs', - label: i18n.translate( - 'xpack.apm.serviceOverview.instancesTable.actionMenus.containerLogs', - { defaultMessage: 'Container logs' } - ), - href: infraNodeLocator?.getRedirectUrl({ - nodeId: containerId!, - nodeType: 'container', - time, - }), - condition: !!containerId, - }, - { - key: 'containerMetrics', - label: i18n.translate( - 'xpack.apm.serviceOverview.instancesTable.actionMenus.containerMetrics', - { defaultMessage: 'Container metrics' } - ), - href: getInfraHref({ - app: 'metrics', - basePath, - path: `/link-to/container-detail/${containerId}`, - query: infraMetricsQuery, - }), - condition: !!containerId, - }, - ] - : []; + const podActions: Action[] = [ + { + key: 'podLogs', + label: i18n.translate( + 'xpack.apm.serviceOverview.instancesTable.actionMenus.podLogs', + { defaultMessage: 'Pod logs' } + ), + href: podLogsHref, + condition: !!podId, + }, + { + key: 'podMetrics', + label: i18n.translate( + 'xpack.apm.serviceOverview.instancesTable.actionMenus.podMetrics', + { defaultMessage: 'Pod metrics' } + ), + href: getInfraHref({ + app: 'metrics', + basePath, + path: `/link-to/pod-detail/${podId}`, + query: infraMetricsQuery, + }), + condition: !!podId && !!infraLocators, + }, + ]; + + const containerActions: Action[] = [ + { + key: 'containerLogs', + label: i18n.translate( + 'xpack.apm.serviceOverview.instancesTable.actionMenus.containerLogs', + { defaultMessage: 'Container logs' } + ), + href: containerLogsHref, + condition: !!containerId, + }, + { + key: 'containerMetrics', + label: i18n.translate( + 'xpack.apm.serviceOverview.instancesTable.actionMenus.containerMetrics', + { defaultMessage: 'Container metrics' } + ), + href: getInfraHref({ + app: 'metrics', + basePath, + path: `/link-to/container-detail/${containerId}`, + query: infraMetricsQuery, + }), + condition: !!containerId && !!infraLocators, + }, + ]; const apmActions: 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 new file mode 100644 index 0000000000000..6909d0035b2f0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/links/observability_logs_link.ts @@ -0,0 +1,91 @@ +/* + * 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 { + NodeLogsLocator, + DiscoverNodeLogsLocator, + LogsLocator, + DiscoverLogsLocator, +} from '@kbn/infra-plugin/common/locators'; +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?: NodeLogsLocator | DiscoverNodeLogsLocator +): 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?: LogsLocator | DiscoverLogsLocator +): 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 3ed8c4c1f0ffc..803a69bd44e21 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,7 +13,10 @@ import { apmRouter as apmRouterBase, ApmRouter, } from '../../routing/apm_route_config'; -import { infraLocatorsMock } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { + infraLocatorsMock, + observabilityLogExplorerLocatorsMock, +} from '../../../context/apm_plugin/mock_apm_plugin_context'; const apmRouter = { ...apmRouterBase, @@ -22,6 +25,7 @@ const apmRouter = { } as ApmRouter; const infraLocators = infraLocatorsMock; +const observabilityLogExplorerLocators = observabilityLogExplorerLocatorsMock; const expectInfraLocatorsToBeCalled = () => { expect(infraLocators.nodeLogsLocator.getRedirectUrl).toBeCalledTimes(3); @@ -61,6 +65,7 @@ describe('Transaction action menu', () => { location, apmRouter, infraLocators, + observabilityLogExplorerLocators, infraLinksAvailable: false, rangeFrom: 'now-24h', rangeTo: 'now', @@ -126,6 +131,7 @@ describe('Transaction action menu', () => { location, apmRouter, infraLocators, + observabilityLogExplorerLocators, infraLinksAvailable: true, rangeFrom: 'now-24h', rangeTo: 'now', @@ -210,6 +216,7 @@ describe('Transaction action menu', () => { location, apmRouter, infraLocators, + observabilityLogExplorerLocators, infraLinksAvailable: true, rangeFrom: 'now-24h', rangeTo: 'now', 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 8f230ba94abe8..c17bedeb8635a 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 @@ -13,6 +13,8 @@ import moment from 'moment'; import url from 'url'; import type { InfraLocators } from '@kbn/infra-plugin/common/locators'; import type { ProfilingLocators } from '@kbn/profiling-plugin/public'; +import { LocatorPublic } from '@kbn/share-plugin/common'; +import { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability/locators'; import { Environment } from '../../../../common/environment_rt'; import type { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import { getDiscoverHref } from '../links/discover_links/discover_link'; @@ -22,6 +24,10 @@ 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(); @@ -44,6 +50,7 @@ export const getSections = ({ rangeFrom, rangeTo, environment, + allDatasetsLocator, }: { transaction?: Transaction; basePath: IBasePath; @@ -55,8 +62,10 @@ export const getSections = ({ rangeFrom: string; rangeTo: string; environment: Environment; + allDatasetsLocator: LocatorPublic; }) => { if (!transaction) return []; + const hostName = transaction.host?.hostname; const podId = transaction.kubernetes?.pod?.uid; const containerId = transaction.container?.id; @@ -79,102 +88,111 @@ export const getSections = ({ )}`, }); - const podActions: Action[] = nodeLogsLocator - ? [ - { - key: 'podLogs', - label: i18n.translate( - 'xpack.apm.transactionActionMenu.showPodLogsLinkLabel', - { defaultMessage: 'Pod logs' } - ), - href: nodeLogsLocator.getRedirectUrl({ - nodeId: podId!, - nodeType: 'pod', - time, - }), - condition: !!podId, - }, - { - key: 'podMetrics', - label: i18n.translate( - 'xpack.apm.transactionActionMenu.showPodMetricsLinkLabel', - { defaultMessage: 'Pod metrics' } - ), - href: getInfraHref({ - app: 'metrics', - basePath, - path: `/link-to/pod-detail/${podId}`, - query: infraMetricsQuery, - }), - condition: !!podId, - }, - ] - : []; + // Logs hrefs + const podLogsHref = getNodeLogsHref( + 'pod', + podId!, + time, + allDatasetsLocator, + nodeLogsLocator + ); + const containerLogsHref = getNodeLogsHref( + 'container', + containerId!, + time, + allDatasetsLocator, + nodeLogsLocator + ); + const hostLogsHref = getNodeLogsHref( + 'host', + hostName!, + time, + allDatasetsLocator, + nodeLogsLocator + ); + const traceLogsHref = getTraceLogsHref( + transaction.trace.id!, + time, + allDatasetsLocator, + logsLocator + ); - const containerActions: Action[] = nodeLogsLocator - ? [ - { - key: 'containerLogs', - label: i18n.translate( - 'xpack.apm.transactionActionMenu.showContainerLogsLinkLabel', - { defaultMessage: 'Container logs' } - ), - href: nodeLogsLocator.getRedirectUrl({ - nodeId: containerId!, - nodeType: 'container', - time, - }), - condition: !!containerId, - }, - { - key: 'containerMetrics', - label: i18n.translate( - 'xpack.apm.transactionActionMenu.showContainerMetricsLinkLabel', - { defaultMessage: 'Container metrics' } - ), - href: getInfraHref({ - app: 'metrics', - basePath, - path: `/link-to/container-detail/${containerId}`, - query: infraMetricsQuery, - }), - condition: !!containerId, - }, - ] - : []; + const podActions: Action[] = [ + { + key: 'podLogs', + label: i18n.translate( + 'xpack.apm.transactionActionMenu.showPodLogsLinkLabel', + { defaultMessage: 'Pod logs' } + ), + href: podLogsHref, + condition: !!podId, + }, + { + key: 'podMetrics', + label: i18n.translate( + 'xpack.apm.transactionActionMenu.showPodMetricsLinkLabel', + { defaultMessage: 'Pod metrics' } + ), + href: getInfraHref({ + app: 'metrics', + basePath, + path: `/link-to/pod-detail/${podId}`, + query: infraMetricsQuery, + }), + condition: !!podId && infraLinksAvailable, + }, + ]; + + const containerActions: Action[] = [ + { + key: 'containerLogs', + label: i18n.translate( + 'xpack.apm.transactionActionMenu.showContainerLogsLinkLabel', + { defaultMessage: 'Container logs' } + ), + href: containerLogsHref, + condition: !!containerId, + }, + { + key: 'containerMetrics', + label: i18n.translate( + 'xpack.apm.transactionActionMenu.showContainerMetricsLinkLabel', + { defaultMessage: 'Container metrics' } + ), + href: getInfraHref({ + app: 'metrics', + basePath, + path: `/link-to/container-detail/${containerId}`, + query: infraMetricsQuery, + }), + condition: !!containerId && infraLinksAvailable, + }, + ]; const hostActions: Action[] = [ - ...(nodeLogsLocator - ? [ - { - key: 'hostLogs', - label: i18n.translate( - 'xpack.apm.transactionActionMenu.showHostLogsLinkLabel', - { defaultMessage: 'Host logs' } - ), - href: nodeLogsLocator.getRedirectUrl({ - nodeId: hostName!, - nodeType: 'host', - time, - }), - condition: !!hostName, - }, - { - key: 'hostMetrics', - label: i18n.translate( - 'xpack.apm.transactionActionMenu.showHostMetricsLinkLabel', - { defaultMessage: 'Host metrics' } - ), - href: getInfraHref({ - app: 'metrics', - basePath, - path: `/link-to/host-detail/${hostName}`, - query: infraMetricsQuery, - }), - condition: !!hostName, - }, - ] - : []), + { + key: 'hostLogs', + label: i18n.translate( + 'xpack.apm.transactionActionMenu.showHostLogsLinkLabel', + { defaultMessage: 'Host logs' } + ), + href: hostLogsHref, + condition: !!hostName, + }, + { + key: 'hostMetrics', + label: i18n.translate( + 'xpack.apm.transactionActionMenu.showHostMetricsLinkLabel', + { defaultMessage: 'Host metrics' } + ), + href: getInfraHref({ + app: 'metrics', + basePath, + path: `/link-to/host-detail/${hostName}`, + query: infraMetricsQuery, + }), + condition: !!hostName && infraLinksAvailable, + }, { key: 'hostProfilingFlamegraph', label: i18n.translate( @@ -213,22 +231,17 @@ export const getSections = ({ }, ]; - const logActions: Action[] = logsLocator - ? [ - { - key: 'traceLogs', - label: i18n.translate( - 'xpack.apm.transactionActionMenu.showTraceLogsLinkLabel', - { defaultMessage: 'Trace logs' } - ), - href: logsLocator.getRedirectUrl({ - filter: `trace.id:"${transaction.trace.id}" OR (not trace.id:* AND "${transaction.trace.id}")`, - time, - }), - condition: true, - }, - ] - : []; + const logActions: Action[] = [ + { + key: 'traceLogs', + label: i18n.translate( + 'xpack.apm.transactionActionMenu.showTraceLogsLinkLabel', + { defaultMessage: 'Trace logs' } + ), + href: traceLogsHref, + condition: true, + }, + ]; const uptimeActions: Action[] = [ { @@ -283,82 +296,64 @@ export const getSections = ({ const sectionRecord: SectionRecord = { observability: [ - ...(infraLinksAvailable && infraLocators - ? [ - { - key: 'podDetails', - title: i18n.translate( - 'xpack.apm.transactionActionMenu.pod.title', - { - defaultMessage: 'Pod details', - } - ), - subtitle: i18n.translate( - 'xpack.apm.transactionActionMenu.pod.subtitle', - { - defaultMessage: - 'View logs and metrics for this pod to get further details.', - } - ), - actions: podActions, - }, - { - key: 'containerDetails', - title: i18n.translate( - 'xpack.apm.transactionActionMenu.container.title', - { - defaultMessage: 'Container details', - } - ), - subtitle: i18n.translate( - 'xpack.apm.transactionActionMenu.container.subtitle', - { - defaultMessage: - 'View logs and metrics for this container to get further details.', - } - ), - actions: containerActions, - }, - { - key: 'hostDetails', - title: i18n.translate( - 'xpack.apm.transactionActionMenu.host.title', - { - defaultMessage: 'Host details', - } - ), - subtitle: i18n.translate( - 'xpack.apm.transactionActionMenu.host.subtitle', - { - defaultMessage: - 'View host logs and metrics to get further details.', - } - ), - actions: hostActions, - }, - ] - : []), - - ...(infraLocators - ? [ - { - key: 'traceDetails', - title: i18n.translate( - 'xpack.apm.transactionActionMenu.trace.title', - { - defaultMessage: 'Trace details', - } - ), - subtitle: i18n.translate( - 'xpack.apm.transactionActionMenu.trace.subtitle', - { - defaultMessage: 'View trace logs to get further details.', - } - ), - actions: logActions, - }, - ] - : []), + { + key: 'podDetails', + title: i18n.translate('xpack.apm.transactionActionMenu.pod.title', { + defaultMessage: 'Pod details', + }), + subtitle: i18n.translate( + 'xpack.apm.transactionActionMenu.pod.subtitle', + { + defaultMessage: + 'View logs and metrics for this pod to get further details.', + } + ), + actions: podActions, + }, + { + key: 'containerDetails', + title: i18n.translate( + 'xpack.apm.transactionActionMenu.container.title', + { + defaultMessage: 'Container details', + } + ), + subtitle: i18n.translate( + 'xpack.apm.transactionActionMenu.container.subtitle', + { + defaultMessage: + 'View logs and metrics for this container to get further details.', + } + ), + actions: containerActions, + }, + { + key: 'hostDetails', + title: i18n.translate('xpack.apm.transactionActionMenu.host.title', { + defaultMessage: 'Host details', + }), + subtitle: i18n.translate( + 'xpack.apm.transactionActionMenu.host.subtitle', + { + defaultMessage: + 'View host logs and metrics to get further details.', + } + ), + actions: hostActions, + }, + { + key: 'traceDetails', + title: i18n.translate('xpack.apm.transactionActionMenu.trace.title', { + defaultMessage: 'Trace details', + }), + subtitle: i18n.translate( + 'xpack.apm.transactionActionMenu.trace.subtitle', + { + defaultMessage: 'View trace logs to get further details.', + } + ), + actions: logActions, + }, { key: 'statusDetails', title: i18n.translate('xpack.apm.transactionActionMenu.status.title', { 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 3f88469664baa..555217d1c0b7b 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 @@ -69,9 +69,11 @@ const renderTransaction = async (transaction: Record) => { const expectInfraLocatorsToBeCalled = () => { expect( - apmContextMock.infra.locators.nodeLogsLocator.getRedirectUrl + apmContextMock.infra?.locators.nodeLogsLocator.getRedirectUrl + ).toBeCalled(); + expect( + apmContextMock.infra?.locators.logsLocator.getRedirectUrl ).toBeCalled(); - expect(apmContextMock.infra.locators.logsLocator.getRedirectUrl).toBeCalled(); }; describe('TransactionActionMenu component', () => { 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 14cae53ba28cf..162fdf8e90b85 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 @@ -22,6 +22,10 @@ import { ProfilingLocators } from '@kbn/profiling-plugin/public'; import React, { useState } from 'react'; import { useLocation } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; +import { + AllDatasetsLocatorParams, + ALL_DATASETS_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; import { ApmFeatureFlagName } from '../../../../common/apm_feature_flags'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; @@ -125,10 +129,14 @@ function ActionMenuSections({ transaction?: Transaction; profilingLocators?: ProfilingLocators; }) { - const { core, uiActions, infra } = useApmPluginContext(); + const { core, uiActions, infra, share } = useApmPluginContext(); const location = useLocation(); const apmRouter = useApmRouter(); + const allDatasetsLocator = share.url.locators.get( + ALL_DATASETS_LOCATOR_ID + )!; + const infraLinksAvailable = useApmFeatureFlag( ApmFeatureFlagName.InfraUiAvailable ); @@ -153,6 +161,7 @@ function ActionMenuSections({ rangeFrom, rangeTo, environment, + allDatasetsLocator, }); const externalMenuItems = useAsync(() => { 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 6bc5fd0ca2eb0..efe88ced91d4a 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 @@ -17,6 +17,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { InfraClientStartExports } from '@kbn/infra-plugin/public'; import type { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public'; +import { SharePluginSetup } from '@kbn/share-plugin/public'; import type { ApmPluginSetupDeps } from '../../plugin'; import type { ConfigSchema } from '../..'; @@ -28,12 +29,13 @@ export interface ApmPluginContextValue { plugins: ApmPluginSetupDeps & { maps?: MapsStartApi }; observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; observability: ObservabilityPublicStart; - infra: InfraClientStartExports; + infra?: InfraClientStartExports; dataViews: DataViewsPublicPluginStart; data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; uiActions: UiActionsStart; observabilityAIAssistant: ObservabilityAIAssistantPluginStart; + share: SharePluginSetup; } export const ApmPluginContext = createContext({} as ApmPluginContextValue); 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 3e3a811504a61..919e98267c8b2 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 @@ -141,6 +141,7 @@ export const mockApmPluginContextValue = { locators: infraLocatorsMock, }, deps: {}, + share: sharePluginMock.createSetupContract(), unifiedSearch: mockUnifiedSearch, uiActions: { getTriggerCompatibleActions: () => Promise.resolve([]), diff --git a/x-pack/plugins/apm/tsconfig.json b/x-pack/plugins/apm/tsconfig.json index fe859abb02ec4..7a22ac6e4a4c2 100644 --- a/x-pack/plugins/apm/tsconfig.json +++ b/x-pack/plugins/apm/tsconfig.json @@ -101,7 +101,8 @@ "@kbn/profiling-utils", "@kbn/core-analytics-server", "@kbn/analytics-client", - "@kbn/monaco" + "@kbn/monaco", + "@kbn/deeplinks-observability" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/log_explorer/public/utils/dataset_selection/all_dataset_selection.ts b/x-pack/plugins/log_explorer/common/dataset_selection/all_dataset_selection.ts similarity index 95% rename from x-pack/plugins/log_explorer/public/utils/dataset_selection/all_dataset_selection.ts rename to x-pack/plugins/log_explorer/common/dataset_selection/all_dataset_selection.ts index ebe3254968fb0..c505a07da7768 100644 --- a/x-pack/plugins/log_explorer/public/utils/dataset_selection/all_dataset_selection.ts +++ b/x-pack/plugins/log_explorer/common/dataset_selection/all_dataset_selection.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Dataset } from '../../../common/datasets'; +import { Dataset } from '../datasets'; import { encodeDatasetSelection } from './encoding'; import { DatasetSelectionStrategy } from './types'; diff --git a/x-pack/plugins/log_explorer/public/utils/dataset_selection/encoding.test.ts b/x-pack/plugins/log_explorer/common/dataset_selection/encoding.test.ts similarity index 100% rename from x-pack/plugins/log_explorer/public/utils/dataset_selection/encoding.test.ts rename to x-pack/plugins/log_explorer/common/dataset_selection/encoding.test.ts diff --git a/x-pack/plugins/log_explorer/public/utils/dataset_selection/encoding.ts b/x-pack/plugins/log_explorer/common/dataset_selection/encoding.ts similarity index 95% rename from x-pack/plugins/log_explorer/public/utils/dataset_selection/encoding.ts rename to x-pack/plugins/log_explorer/common/dataset_selection/encoding.ts index 5a84c398e7d4e..83a7c5357fde5 100644 --- a/x-pack/plugins/log_explorer/public/utils/dataset_selection/encoding.ts +++ b/x-pack/plugins/log_explorer/common/dataset_selection/encoding.ts @@ -7,7 +7,7 @@ import { decode, encode, RisonValue } from '@kbn/rison'; import * as lz from 'lz-string'; -import { decodeOrThrow } from '../../../common/runtime_types'; +import { decodeOrThrow } from '../runtime_types'; import { DatasetEncodingError } from './errors'; import { DatasetSelectionPlain, datasetSelectionPlainRT } from './types'; diff --git a/x-pack/plugins/log_explorer/public/utils/dataset_selection/errors.ts b/x-pack/plugins/log_explorer/common/dataset_selection/errors.ts similarity index 100% rename from x-pack/plugins/log_explorer/public/utils/dataset_selection/errors.ts rename to x-pack/plugins/log_explorer/common/dataset_selection/errors.ts diff --git a/x-pack/plugins/log_explorer/public/utils/dataset_selection/hydrate_dataset_selection.ts.ts b/x-pack/plugins/log_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts similarity index 77% rename from x-pack/plugins/log_explorer/public/utils/dataset_selection/hydrate_dataset_selection.ts.ts rename to x-pack/plugins/log_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts index 8a855a46412f3..43faebc618140 100644 --- a/x-pack/plugins/log_explorer/public/utils/dataset_selection/hydrate_dataset_selection.ts.ts +++ b/x-pack/plugins/log_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts @@ -8,6 +8,7 @@ import { AllDatasetSelection } from './all_dataset_selection'; import { SingleDatasetSelection } from './single_dataset_selection'; import { DatasetSelectionPlain } from './types'; +import { UnresolvedDatasetSelection } from './unresolved_dataset_selection'; export const hydrateDatasetSelection = (datasetSelection: DatasetSelectionPlain) => { if (datasetSelection.selectionType === 'all') { @@ -16,4 +17,7 @@ export const hydrateDatasetSelection = (datasetSelection: DatasetSelectionPlain) if (datasetSelection.selectionType === 'single') { return SingleDatasetSelection.fromSelection(datasetSelection.selection); } + if (datasetSelection.selectionType === 'unresolved') { + return UnresolvedDatasetSelection.fromSelection(datasetSelection.selection); + } }; diff --git a/x-pack/plugins/log_explorer/public/utils/dataset_selection/index.ts b/x-pack/plugins/log_explorer/common/dataset_selection/index.ts similarity index 66% rename from x-pack/plugins/log_explorer/public/utils/dataset_selection/index.ts rename to x-pack/plugins/log_explorer/common/dataset_selection/index.ts index a3c430df1d356..3284610f53bcc 100644 --- a/x-pack/plugins/log_explorer/public/utils/dataset_selection/index.ts +++ b/x-pack/plugins/log_explorer/common/dataset_selection/index.ts @@ -7,16 +7,25 @@ import { AllDatasetSelection } from './all_dataset_selection'; import { SingleDatasetSelection } from './single_dataset_selection'; +import { UnresolvedDatasetSelection } from './unresolved_dataset_selection'; -export type DatasetSelection = AllDatasetSelection | SingleDatasetSelection; +export type DatasetSelection = + | AllDatasetSelection + | SingleDatasetSelection + | UnresolvedDatasetSelection; export type DatasetSelectionChange = (datasetSelection: DatasetSelection) => void; export const isDatasetSelection = (input: any): input is DatasetSelection => { - return input instanceof AllDatasetSelection || input instanceof SingleDatasetSelection; + return ( + input instanceof AllDatasetSelection || + input instanceof SingleDatasetSelection || + input instanceof UnresolvedDatasetSelection + ); }; export * from './all_dataset_selection'; export * from './single_dataset_selection'; +export * from './unresolved_dataset_selection'; export * from './encoding'; export * from './errors'; export * from './hydrate_dataset_selection.ts'; diff --git a/x-pack/plugins/log_explorer/public/utils/dataset_selection/single_dataset_selection.ts b/x-pack/plugins/log_explorer/common/dataset_selection/single_dataset_selection.ts similarity index 97% rename from x-pack/plugins/log_explorer/public/utils/dataset_selection/single_dataset_selection.ts rename to x-pack/plugins/log_explorer/common/dataset_selection/single_dataset_selection.ts index 6f772200cf6c0..21c788579ed70 100644 --- a/x-pack/plugins/log_explorer/public/utils/dataset_selection/single_dataset_selection.ts +++ b/x-pack/plugins/log_explorer/common/dataset_selection/single_dataset_selection.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Dataset } from '../../../common/datasets'; +import { Dataset } from '../datasets'; import { encodeDatasetSelection } from './encoding'; import { DatasetSelectionStrategy, SingleDatasetSelectionPayload } from './types'; diff --git a/x-pack/plugins/log_explorer/public/utils/dataset_selection/types.ts b/x-pack/plugins/log_explorer/common/dataset_selection/types.ts similarity index 74% rename from x-pack/plugins/log_explorer/public/utils/dataset_selection/types.ts rename to x-pack/plugins/log_explorer/common/dataset_selection/types.ts index a25f16cee7654..239bbc1108a29 100644 --- a/x-pack/plugins/log_explorer/public/utils/dataset_selection/types.ts +++ b/x-pack/plugins/log_explorer/common/dataset_selection/types.ts @@ -6,7 +6,7 @@ */ import { DataViewSpec } from '@kbn/data-views-plugin/common'; import * as rt from 'io-ts'; -import { datasetRT } from '../../../common/datasets'; +import { datasetRT } from '../datasets'; export const allDatasetSelectionPlainRT = rt.type({ selectionType: rt.literal('all'), @@ -33,17 +33,33 @@ const singleDatasetSelectionPayloadRT = rt.intersection([ }), ]); +const unresolvedDatasetSelectionPayloadRT = rt.intersection([ + integrationNameRT, + rt.type({ + dataset: datasetRT, + }), +]); + export const singleDatasetSelectionPlainRT = rt.type({ selectionType: rt.literal('single'), selection: singleDatasetSelectionPayloadRT, }); +export const unresolvedDatasetSelectionPlainRT = rt.type({ + selectionType: rt.literal('unresolved'), + selection: unresolvedDatasetSelectionPayloadRT, +}); + export const datasetSelectionPlainRT = rt.union([ allDatasetSelectionPlainRT, singleDatasetSelectionPlainRT, + unresolvedDatasetSelectionPlainRT, ]); export type SingleDatasetSelectionPayload = rt.TypeOf; +export type UnresolvedDatasetSelectionPayload = rt.TypeOf< + typeof unresolvedDatasetSelectionPayloadRT +>; export type DatasetSelectionPlain = rt.TypeOf; export interface DatasetSelectionStrategy { diff --git a/x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts b/x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts new file mode 100644 index 0000000000000..acfd5180f0ed3 --- /dev/null +++ b/x-pack/plugins/log_explorer/common/dataset_selection/unresolved_dataset_selection.ts @@ -0,0 +1,59 @@ +/* + * 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 { Dataset } from '../datasets'; +import { encodeDatasetSelection } from './encoding'; +import { DatasetSelectionStrategy, UnresolvedDatasetSelectionPayload } from './types'; + +export class UnresolvedDatasetSelection implements DatasetSelectionStrategy { + selectionType: 'unresolved'; + selection: { + name?: string; + dataset: Dataset; + }; + + private constructor(dataset: Dataset) { + this.selectionType = 'unresolved'; + this.selection = { + name: dataset.parentIntegration?.name, + dataset, + }; + } + + toDataviewSpec() { + const { name, title } = this.selection.dataset.toDataviewSpec(); + return { + id: this.toURLSelectionId(), + name, + title, + }; + } + + toURLSelectionId() { + return encodeDatasetSelection({ + selectionType: this.selectionType, + selection: { + name: this.selection.name, + dataset: this.selection.dataset.toPlain(), + }, + }); + } + + public static fromSelection(selection: UnresolvedDatasetSelectionPayload) { + const { name, dataset } = selection; + + // Attempt reconstructing the integration object + const integration = name ? { name } : undefined; + const datasetInstance = Dataset.create(dataset, integration); + + return new UnresolvedDatasetSelection(datasetInstance); + } + + public static create(dataset: Dataset) { + return new UnresolvedDatasetSelection(dataset); + } +} diff --git a/x-pack/plugins/log_explorer/common/datasets/models/dataset.ts b/x-pack/plugins/log_explorer/common/datasets/models/dataset.ts index 5a817e69a191c..a07220ff77d71 100644 --- a/x-pack/plugins/log_explorer/common/datasets/models/dataset.ts +++ b/x-pack/plugins/log_explorer/common/datasets/models/dataset.ts @@ -11,7 +11,7 @@ import { IndexPattern } from '@kbn/io-ts-utils'; import { TIMESTAMP_FIELD } from '../../constants'; import { DatasetId, DatasetType, IntegrationType } from '../types'; -type IntegrationBase = Pick; +type IntegrationBase = Partial>; interface DatasetDeps extends DatasetType { iconType?: IconType; @@ -31,7 +31,7 @@ export class Dataset { this.title = dataset.title ?? dataset.name; this.parentIntegration = parentIntegration && { name: parentIntegration.name, - title: parentIntegration.title, + title: parentIntegration.title ?? parentIntegration.name, icons: parentIntegration.icons, version: parentIntegration.version, }; diff --git a/x-pack/plugins/log_explorer/common/index.ts b/x-pack/plugins/log_explorer/common/index.ts new file mode 100644 index 0000000000000..989f981879ac0 --- /dev/null +++ b/x-pack/plugins/log_explorer/common/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { AllDatasetSelection, UnresolvedDatasetSelection } from './dataset_selection'; diff --git a/x-pack/plugins/log_explorer/common/locators/index.ts b/x-pack/plugins/log_explorer/common/locators/index.ts new file mode 100644 index 0000000000000..ebcd27baa5543 --- /dev/null +++ b/x-pack/plugins/log_explorer/common/locators/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { LogExplorerLocator } from './log_explorer/log_explorer_locator'; + +export * from './log_explorer'; + +export interface LogExplorerLocators { + logExplorerLocator: LogExplorerLocator; +} diff --git a/x-pack/plugins/log_explorer/common/locators/log_explorer/index.ts b/x-pack/plugins/log_explorer/common/locators/log_explorer/index.ts new file mode 100644 index 0000000000000..738ad7cea2f39 --- /dev/null +++ b/x-pack/plugins/log_explorer/common/locators/log_explorer/index.ts @@ -0,0 +1,8 @@ +/* + * 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 * from './log_explorer_locator'; diff --git a/x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.test.ts b/x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.test.ts new file mode 100644 index 0000000000000..2785def1f2927 --- /dev/null +++ b/x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { sharePluginMock } from '@kbn/share-plugin/public/mocks'; +import { LogExplorerLocatorDefinition } from './log_explorer_locator'; +import { LogExplorerLocatorDependencies } from './types'; + +const setup = async () => { + const discoverSetupContract: LogExplorerLocatorDependencies = { + discover: { + locator: sharePluginMock.createLocator(), + }, + }; + const logExplorerLocator = new LogExplorerLocatorDefinition(discoverSetupContract); + + return { + logExplorerLocator, + discoverGetLocation: discoverSetupContract.discover.locator?.getLocation, + }; +}; + +describe('Logs Explorer Locators', () => { + const dataset = 'logs-*-*'; + it('should call discover locator with empty params', async () => { + const { logExplorerLocator, discoverGetLocation } = await setup(); + await logExplorerLocator.getLocation({}); + + expect(discoverGetLocation).toBeCalledWith({}); + }); + + it('should call discover locator with correct dataViewId if dataset is sent', async () => { + const { logExplorerLocator, discoverGetLocation } = await setup(); + await logExplorerLocator.getLocation({ dataset }); + + expect(discoverGetLocation).toBeCalledWith( + expect.objectContaining({ + dataViewId: 'logs-*-*', + }) + ); + }); + + it('should call discover locator with correct dataViewSpec if dataset is sent', async () => { + const { logExplorerLocator, discoverGetLocation } = await setup(); + await logExplorerLocator.getLocation({ dataset }); + + expect(discoverGetLocation).toBeCalledWith( + expect.objectContaining({ + dataViewId: 'logs-*-*', + dataViewSpec: { + id: 'logs-*-*', + title: 'logs-*-*', + }, + }) + ); + }); +}); diff --git a/x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.ts b/x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.ts new file mode 100644 index 0000000000000..f0265e088a206 --- /dev/null +++ b/x-pack/plugins/log_explorer/common/locators/log_explorer/log_explorer_locator.ts @@ -0,0 +1,38 @@ +/* + * 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 { DataViewSpec } from '@kbn/data-views-plugin/common'; +import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; +import { + LogExplorerLocatorParams, + LOG_EXPLORER_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; +import { LogExplorerLocatorDependencies } from './types'; + +export type LogExplorerLocator = LocatorPublic; + +export class LogExplorerLocatorDefinition implements LocatorDefinition { + public readonly id = LOG_EXPLORER_LOCATOR_ID; + + constructor(protected readonly deps: LogExplorerLocatorDependencies) {} + + public readonly getLocation = (params: LogExplorerLocatorParams) => { + const { dataset } = params; + const dataViewSpec: DataViewSpec | undefined = dataset + ? { + id: dataset, + title: dataset, + } + : undefined; + + return this.deps.discover.locator?.getLocation({ + ...params, + dataViewId: dataset, + dataViewSpec, + })!; + }; +} diff --git a/x-pack/plugins/log_explorer/common/locators/log_explorer/types.ts b/x-pack/plugins/log_explorer/common/locators/log_explorer/types.ts new file mode 100644 index 0000000000000..f35a74088b60d --- /dev/null +++ b/x-pack/plugins/log_explorer/common/locators/log_explorer/types.ts @@ -0,0 +1,12 @@ +/* + * 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 { DiscoverSetup } from '@kbn/discover-plugin/public'; + +export interface LogExplorerLocatorDependencies { + discover: DiscoverSetup; +} diff --git a/x-pack/plugins/log_explorer/kibana.jsonc b/x-pack/plugins/log_explorer/kibana.jsonc index 612bd34859b98..969fa50c87a8c 100644 --- a/x-pack/plugins/log_explorer/kibana.jsonc +++ b/x-pack/plugins/log_explorer/kibana.jsonc @@ -19,9 +19,13 @@ "kibanaReact", "kibanaUtils", "controls", - "embeddable" + "embeddable", + "share", ], "optionalPlugins": [], - "requiredBundles": [] + "requiredBundles": [], + "extraPublicDirs": [ + "common", + ] } } diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx index c1549bc899ab4..10bc958c8f2ce 100644 --- a/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx +++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/dataset_selector.stories.tsx @@ -11,14 +11,14 @@ import React, { useState } from 'react'; import { I18nProvider } from '@kbn/i18n-react'; import type { Meta, Story } from '@storybook/react'; import { IndexPattern } from '@kbn/io-ts-utils'; -import { Dataset, Integration } from '../../../common/datasets'; -import { DatasetSelector } from './dataset_selector'; -import { DatasetSelectorProps, DatasetsSelectorSearchParams } from './types'; import { AllDatasetSelection, DatasetSelection, DatasetSelectionChange, -} from '../../utils/dataset_selection'; +} from '../../../common/dataset_selection'; +import { Dataset, Integration } from '../../../common/datasets'; +import { DatasetSelector } from './dataset_selector'; +import { DatasetSelectorProps, DatasetsSelectorSearchParams } from './types'; const meta: Meta = { component: DatasetSelector, diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/defaults.ts b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/defaults.ts index a818c6645bda7..142908a782436 100644 --- a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/defaults.ts +++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/defaults.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AllDatasetSelection } from '../../../utils/dataset_selection'; +import { AllDatasetSelection } from '../../../../common/dataset_selection'; import { HashedCache } from '../../../../common/hashed_cache'; import { INTEGRATION_PANEL_ID } from '../constants'; import { DatasetsSelectorSearchParams } from '../types'; diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/state_machine.ts b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/state_machine.ts index aea09e7c6f2e7..1361629c819d8 100644 --- a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/state_machine.ts +++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/state_machine.ts @@ -6,7 +6,7 @@ */ import { actions, assign, createMachine, raise } from 'xstate'; -import { AllDatasetSelection, SingleDatasetSelection } from '../../../utils/dataset_selection'; +import { AllDatasetSelection, SingleDatasetSelection } from '../../../../common/dataset_selection'; import { UNMANAGED_STREAMS_PANEL_ID } from '../constants'; import { defaultSearch, DEFAULT_CONTEXT } from './defaults'; import { diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/types.ts b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/types.ts index 17e526aea87f9..e434692a658b2 100644 --- a/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/types.ts +++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/state_machine/types.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { DatasetSelection, DatasetSelectionChange } from '../../../utils/dataset_selection'; +import { DatasetSelection, DatasetSelectionChange } from '../../../../common/dataset_selection'; import { Dataset } from '../../../../common/datasets/models/dataset'; import { ReloadDatasets, SearchDatasets } from '../../../hooks/use_datasets'; import { diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx b/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx index 7428ffbd47612..652cbafdc35d9 100644 --- a/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx +++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import styled from '@emotion/styled'; import { PackageIcon } from '@kbn/fleet-plugin/public'; -import { DatasetSelection } from '../../../utils/dataset_selection'; +import { DatasetSelection } from '../../../../common/dataset_selection'; import { DATA_VIEW_POPOVER_CONTENT_WIDTH, POPOVER_ID, selectDatasetLabel } from '../constants'; import { getPopoverButtonStyles } from '../utils'; diff --git a/x-pack/plugins/log_explorer/public/components/dataset_selector/types.ts b/x-pack/plugins/log_explorer/public/components/dataset_selector/types.ts index 50cbcf6c1c5ba..9aed76eb602fa 100644 --- a/x-pack/plugins/log_explorer/public/components/dataset_selector/types.ts +++ b/x-pack/plugins/log_explorer/public/components/dataset_selector/types.ts @@ -6,6 +6,7 @@ */ import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; +import type { DatasetSelection, DatasetSelectionChange } from '../../../common/dataset_selection'; import { SortOrder } from '../../../common/latest'; import { Dataset, Integration, IntegrationId } from '../../../common/datasets'; import { LoadDatasets, ReloadDatasets, SearchDatasets } from '../../hooks/use_datasets'; @@ -15,7 +16,6 @@ import { SearchIntegrations, } from '../../hooks/use_integrations'; import { INTEGRATION_PANEL_ID, UNMANAGED_STREAMS_PANEL_ID } from './constants'; -import type { DatasetSelection, DatasetSelectionChange } from '../../utils/dataset_selection'; export interface DatasetSelectorProps { /* The generic data stream list */ diff --git a/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx b/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx index 6a945afa19ab2..336c3782438c1 100644 --- a/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx +++ b/x-pack/plugins/log_explorer/public/components/log_explorer/log_explorer.tsx @@ -8,8 +8,9 @@ import React, { useMemo } from 'react'; import { ScopedHistory } from '@kbn/core-application-browser'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { DiscoverAppState, DiscoverStart } from '@kbn/discover-plugin/public'; +import { DiscoverStart } from '@kbn/discover-plugin/public'; import type { BehaviorSubject } from 'rxjs'; +import { DiscoverAppState } from '@kbn/discover-plugin/public/application/main/services/discover_app_state_container'; import { createLogExplorerProfileCustomizations, CreateLogExplorerProfileCustomizationsDeps, diff --git a/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx b/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx index 7e80fdd54f22f..ee8f7ffbc9779 100644 --- a/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx +++ b/x-pack/plugins/log_explorer/public/customizations/log_explorer_profile.tsx @@ -33,11 +33,12 @@ export const createLogExplorerProfileCustomizations = const [{ DatasetsService }, { initializeLogExplorerProfileStateService, waitForState }] = await Promise.all([datasetServiceModuleLoadable, logExplorerMachineModuleLoadable]); - const datasetsService = new DatasetsService().start({ + const datasetsClient = new DatasetsService().start({ http: core.http, - }); + }).client; const logExplorerProfileStateService = initializeLogExplorerProfileStateService({ + datasetsClient, stateContainer, toasts: core.notifications.toasts, }); @@ -70,7 +71,7 @@ export const createLogExplorerProfileCustomizations = id: 'search_bar', CustomDataViewPicker: () => ( ), diff --git a/x-pack/plugins/log_explorer/public/hooks/use_dataset_selection.ts b/x-pack/plugins/log_explorer/public/hooks/use_dataset_selection.ts index 05fbc2bc81506..6bff4055f3635 100644 --- a/x-pack/plugins/log_explorer/public/hooks/use_dataset_selection.ts +++ b/x-pack/plugins/log_explorer/public/hooks/use_dataset_selection.ts @@ -7,8 +7,8 @@ import { useSelector } from '@xstate/react'; import { useCallback } from 'react'; +import { DatasetSelectionChange } from '../../common/dataset_selection'; import { LogExplorerProfileStateService } from '../state_machines/log_explorer_profile'; -import { DatasetSelectionChange } from '../utils/dataset_selection'; export const useDatasetSelection = ( logExplorerProfileStateService: LogExplorerProfileStateService diff --git a/x-pack/plugins/log_explorer/public/plugin.ts b/x-pack/plugins/log_explorer/public/plugin.ts index 5807e8260f326..57df6b8e1ef0e 100644 --- a/x-pack/plugins/log_explorer/public/plugin.ts +++ b/x-pack/plugins/log_explorer/public/plugin.ts @@ -6,6 +6,7 @@ */ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import { LogExplorerLocatorDefinition, LogExplorerLocators } from '../common/locators'; import { createLogExplorer } from './components/log_explorer'; import { LogExplorerPluginSetup, @@ -15,9 +16,28 @@ import { } from './types'; export class LogExplorerPlugin implements Plugin { + private locators?: LogExplorerLocators; + constructor(context: PluginInitializerContext) {} - public setup(core: CoreSetup, plugins: LogExplorerSetupDeps) {} + public setup(core: CoreSetup, plugins: LogExplorerSetupDeps) { + const { share, discover } = plugins; + + // Register Locators + const logExplorerLocator = share.url.locators.create( + new LogExplorerLocatorDefinition({ + discover, + }) + ); + + this.locators = { + logExplorerLocator, + }; + + return { + locators: this.locators, + }; + } public start(core: CoreStart, plugins: LogExplorerStartDeps) { const { data, discover } = plugins; diff --git a/x-pack/plugins/log_explorer/public/services/datasets/datasets_client.ts b/x-pack/plugins/log_explorer/public/services/datasets/datasets_client.ts index 6afa8016781ab..5e746e6544e4a 100644 --- a/x-pack/plugins/log_explorer/public/services/datasets/datasets_client.ts +++ b/x-pack/plugins/log_explorer/public/services/datasets/datasets_client.ts @@ -6,6 +6,7 @@ */ import { HttpStart } from '@kbn/core/public'; + import { Dataset, Integration } from '../../../common/datasets'; import { DATASETS_URL, diff --git a/x-pack/plugins/log_explorer/public/services/datasets/datasets_service.ts b/x-pack/plugins/log_explorer/public/services/datasets/datasets_service.ts index 7e6c016f94ab9..fd8222dcaec28 100644 --- a/x-pack/plugins/log_explorer/public/services/datasets/datasets_service.ts +++ b/x-pack/plugins/log_explorer/public/services/datasets/datasets_service.ts @@ -6,7 +6,7 @@ */ import { DatasetsClient } from './datasets_client'; -import { DatasetsServiceStartDeps, DatasetsServiceSetup, DatasetsServiceStart } from './types'; +import { DatasetsServiceSetup, DatasetsServiceStart, DatasetsServiceStartDeps } from './types'; export class DatasetsService { constructor() {} diff --git a/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/defaults.ts b/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/defaults.ts index fe19f34d7565c..2a058e2dde017 100644 --- a/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/defaults.ts +++ b/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/defaults.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AllDatasetSelection } from '../../../utils/dataset_selection'; +import { AllDatasetSelection } from '../../../../common/dataset_selection'; import { ControlPanels, DefaultLogExplorerProfileState } from './types'; export const DEFAULT_CONTEXT: DefaultLogExplorerProfileState = { diff --git a/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/selection_service.ts b/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/selection_service.ts new file mode 100644 index 0000000000000..6de3d24025802 --- /dev/null +++ b/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/selection_service.ts @@ -0,0 +1,63 @@ +/* + * 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 { InvokeCreator } from 'xstate'; +import { Dataset } from '../../../../common/datasets'; +import { SingleDatasetSelection } from '../../../../common/dataset_selection'; +import { IDatasetsClient } from '../../../services/datasets'; +import { LogExplorerProfileContext, LogExplorerProfileEvent } from './types'; + +interface LogExplorerProfileUrlStateDependencies { + datasetsClient: IDatasetsClient; +} + +export const validateSelection = + ({ + datasetsClient, + }: LogExplorerProfileUrlStateDependencies): InvokeCreator< + LogExplorerProfileContext, + LogExplorerProfileEvent + > => + (context) => + async (send) => { + const unresolvedIntegrationName = + context.datasetSelection.selection.dataset.parentIntegration?.name; + const unresolvedDatasetName = context.datasetSelection.selection.dataset.name; + + if (context.datasetSelection.selectionType !== 'unresolved' || !unresolvedIntegrationName) { + return send('LISTEN_TO_CHANGES'); + } + + try { + const { items } = await datasetsClient.findIntegrations({ + nameQuery: unresolvedIntegrationName, + }); + + // There should only be one matching integration with the given name + // If no integration matches, skip the update and listen for user changes + const installedIntegration = items[0]; + if (!installedIntegration) { + return send('LISTEN_TO_CHANGES'); + } + + // If no dataset matches the passed name for the retrieved integration, + // skip the update and listen for user changes + const targetDataset = installedIntegration.datasets.find( + (d) => d.name === unresolvedDatasetName + ); + if (!targetDataset) { + return send('LISTEN_TO_CHANGES'); + } + + const dataset = Dataset.create(targetDataset, installedIntegration); + const datasetSelection = SingleDatasetSelection.create(dataset); + + send({ type: 'UPDATE_DATASET_SELECTION', data: datasetSelection }); + } catch (error) { + return send('DATASET_SELECTION_RESTORE_FAILURE'); + } + }; diff --git a/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/state_machine.ts b/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/state_machine.ts index 617c66b10140f..4fa1673b5879d 100644 --- a/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/state_machine.ts +++ b/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/state_machine.ts @@ -8,8 +8,10 @@ import { IToasts } from '@kbn/core/public'; import { DiscoverStateContainer } from '@kbn/discover-plugin/public'; import { actions, createMachine, interpret, InterpreterFrom, raise } from 'xstate'; -import { isDatasetSelection } from '../../../utils/dataset_selection'; +import { IDatasetsClient } from '../../../services/datasets'; +import { isDatasetSelection } from '../../../../common/dataset_selection'; import { createAndSetDataView } from './data_view_service'; +import { validateSelection } from './selection_service'; import { DEFAULT_CONTEXT } from './defaults'; import { createCreateDataViewFailedNotifier, @@ -33,7 +35,7 @@ import { export const createPureLogExplorerProfileStateMachine = ( initialContext: LogExplorerProfileContext ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBkD2UCiAPADgG1QCcxCAFQ1AMwEs8wA6AVwDtrWAXagQz2oC9IAYgDaABgC6iUDlSxqnVMykgsiAIwAOAMz0NorVoDsAVi0a1x0QE4ATADYANCACe6zfVMbjeu-rWi7DQAWAF8QpzRMXAJiMgoaOno2eW5ePjYoADEKAFsAVUI8QQhFBjYAN1QAawZI7HwiEnIqWjKOVP4M7NR8woQK1ABjLgVmMXFx5Rk5UeVVBEsNekMrQK0bYKC7fztHF3VDII8vDTXDNUNtu2MwiPR6mKb41qT2nk7mLNyCopIKQno+BGlCIOXodWijTiLUSyU473Sn26vTw-WYlWGo3GkyQIGmKUUc0Qi2Wq20GyCWx2e1cCHM9CCXmuekMokO2y0txAEIasWaCTaKQRGQAIiMuAA1ahgADuxVKr0qNXB90hfOesLeaVF4qlsrRGJG1EU2IkU1kBKUuPmdhsTlpxjUR00Nis+g0NjUVis3i5PMe0IFryF2s+YvYkulcr+REBeGBoJVUV5Txhgvhoag4cj+oGmONYwkOOkFtm1uJdv2CDUFzsHkOjpshhsWguahsftVKcDLzhHURUAAwop2BQ8KQuMwwHhYPKp4rqrUuwH+b2tR8hyOxxOpzODUMjSai2bcfiy6B5qZjAyjIzDOYbNYm-bEFYDPRrJZNJpvI+NJ3kxXDV037DJh2YUdUHHSdp1nGMASBdgQUIMF-ShVdNRDDdwMg6Dd1gfd8yPCYTxLGYCyJBYtGvIJbw0e92yfQwXwQIJvXoN9jCsIIAiMV1XQAh50OA4MM34SB6AgcVYDAdgAGVpzAQZRiSCA6EEPJSBFABBAAVDAAH0dN07S5IwXSDLM5AMEHXSAEkAHkADlizxUsKPLatrCOZt720LRRFdIIbErWlmzUZZjEMaLNHMbRzkEtVUyDPsEQkqSIxk+TFOUgtVPU4zTPMyyMGs2zHKcgyACUMDk3SHJqgzMm0uzkDyGrXLPDyL3UbzlibDR-MC7iQpY8xDF0LjqNsIx6LURLuwwkC0ogSTpNkhS6FyxQmBwDKdQjPU5RKecBmVND1TTUT+3S9bsq2lTGD2o0w11KNCMPQsSMkU93MJTz21Megm18QJLnbRkaVfNQdFWURggMKKtnvBagKu1K0luzKNpyx7ns4V7DqjQR4LjBMUKTITLpS9cBFWjKuCyzalLx-bCZzGUPqxY8frIy1KPbdxLC0WxaLY8WghY20lmuBtRB41tHw7cJuWXYT0dpiTBi3KCAHEKCe2AmFYTWIEEOynLs+ztOQOyAC1DMHZzdKqhzkAM3XXc0gztNIOzOr+q0eurQGPEZCwBqbIJLhY-xWQ4rRGUpa5PXWVH1ZprC6fobWILHfXUEN-KwA0rS9IwEUjL07SDIlOyMAAdQD8j-uDmHLg4wLG3sN0rCdWPqKWGtKW9Aw+9tdPqbXLOtZ1vAC6L6g1JLzTjMd53Xfd0htKc0q5Ob-mAYMHRtiCYxAlOMwgn7qsazsCb9DsWjH3htRdkn5Lp7E7Pc9whecCNk9Nmm485QR3LBOcbQlRLkAhnL+N1Vq-3zgbABu1gE4W3DBPceZPqml5m5FuQcVBuBsNeYw4djCR0ODHW+noJqUN8GfeGoh9AGA-j2TC39Z6gPnigwB+MwJz3ATOEmhB-hkyQomC6n9OEIJznPf+-D0FCKwQRHB3NvrmkIQLUO5DzCUPvFHGhtIYb+HoLaOwWhdjGG8CYfQYQVbMFQBAOAyhpEcLAFow+wcAC0UMEA+OvN6YJITQmchVu4paxsMbiQgF488xDWKhTcPQow3Fz4rBhi2dhUSYkDmRD8eJ3VEkDV0IcKw4VAotkCCxbiH5r7aECI6YI2hQgRLVlPWRwp2ZHSKa3RJKwrAeD7jWfwidgqrFqUceijpWQ-m-PfHJIk8mCJ4cI+Av1tGeXsDLXwFhyGGGoosSWVZGRDMCByYKVwmxWCWRrGecTNneIGZSegFg7H8SbCYfxZ8T6nBhlct+Ny7B3Mzlw+md1mbbSIV1fp8wVg2DeVFSwnzornxYuQiKbYEUGBbIcUF8CVprWxvdFmeUl50D6UQ+YwRRBIo+bYL56KqwpzeTWUhpg-zUXCXcWBnTlqYwhSSqFrMXpZjerKKlAsthDLfNsUWdgKnXysCxQ5EVtjeFMOPDJbTeVUxkQK2J8ieGKKlZ5BF9KUWMrRf4qw2gGRsU0MyGGXhbntL5Qa66RKkF6z4dE02Zrg4+iWIcRk6xvQsI0G+WOfUbA8W0BcEaFhdWqw9R4r1grjV-z9RSzxTyEnzDfmY9JrZmyWPPtHWOnprzaFMEClOrSCVdMzT63hhdUFALFRgsBqjA2JNpZa4aIUbWxzfkMzQjIPTNjjefZWYQgA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBkD2UCiAPADgG1QCcxCAFQ1AMwEs8wA6AVwDtrWAXagQz2oC9IAYgDaABgC6iUDlSxqnVMykgsiAIwAOAMz0NagEwBWUdoBsWgOxaAnEYA0IAJ7rt9UxesWD+2xf2WAXwCHNExcAmIyCho6ejZ5bl4+NigAMQoAWwBVQjxBCEUGNgA3VABrBlDsfCIScipaIo5E-hT01GzchBLUAGMuBWYxcWHlGTlB5VUEYw1dfQ1RQ30LC0NrbX0HZwQ1CwAWemtDLTV991FTPWWtIJD0aoi66Ma45p5W5jTMnLySCkI9HwA0oRAy9Cq4VqUQasXinA+yS+7U6eG6zFK-UGw1GSBA4wSiimiFm80Wy1W60220QenoxlEjP2J32alEmmsdxAkJqkXqMSaCURKQAIgMuAA1ahgADu+UKb1KFQhDyhfJecPeSVF4qlsvRmIG1EUOIkY1khKUeOmPkM9D8pkMFm0bMpFhpM3Obi0tqdxk0Fi5PKeMIFbyF2q+YvYkulcv+RCBeBBYJVYV5z1hgoRkag0dj+p6WONQwkuOkFsm1sQtvt+kdztOojdHquonoah91h9WkWS1MQdVGdDr3hLSRUAAwop2BQ8KQuMwwHhYPKl4rypUhyH+aOtZ8pzO5wulyuDX0jSay2a8QSq6BphZTNZ6Po1O+jMy++YPScLEdLi0UwDG7Z9jkHdMdw1bNxxSadmFnVB50XZdVwTQFgXYUFCHBYNoV3TUIwPeDEOQ09YHPYsrxGG8KwmEtiQQJ8XzfD9DC-RkfycRB9msdt9kuBYNA0Dxlg0Adgm5bd8Og8McwPABlGN2DAEiuDYEg1yaJUt0gmSszk2CviUgZVJndSl0ISjL1LGjJFvSsGOrBAtC0Q4tFEc5DHE-YPI0XjTA9NRDCdI4rhEvR9HrEKIMefSwzHYVjOUsyEIszT0KTFMcLTOL1QMxLcxMlS1I0qyixs017Loy1GNc9zPMdHy-ICoLDDZehzg0Wx3z4vtbkkvD8oS-cBAgegIHFWAwHYBTlzAXpBnoYoPkmzhjPmxaS0EZAAEkFIAFQwAA5AB9A6AHlTsnAAJABBY6AHEMAU8t8UcolnOE-9fLWZ9NEsAwgtc9ttG6p0fQsQCNFitVMxGoixomqaZrmugtsUZbVqNDb0cGQQslIEU7qO07iYOu6FIwA7Tqp5AMEnA7dou463rvJyH1pETOssQx-u0Lwtm43Yzjtbz1jZGwDg2Ab7j04a90RyBkZjabZs2paVt4NaUjRhb8fJynqdpjB6cZ5mzoAJRey7rdO1I7t25AsmttmPqtTmEG+nm-usAHBba9rOp67trGffz-Nh4cCJgxFlbWrg1b1jHmDiCA6AJomSYwMmSaNmm6YZpmWbd+jPs9s5PFff19jWZsDH2IWdk7MP7UdUx-GbdxTGbKOoIK0b45R9W8ZLNOM8NqmC9NouLdO63Douu2Hadl2MFL2rnMr-8jHZWvjEFxvgcZehTn0fZIr48WYcG6SFcI+SkYTpONbHxgcB1qNdTjLSN2VIb4aK0fkPVWqNX6Y3fp-PM39CwYgvNia81V3plw9ioGsbI1CvnfJ5SwtdTB4OBmoF83k+KmHcCcNyN85Z5UAQ-ccIDE5gNHhAj+ONoExj1PGQgAIspYVTAAkcdC47jWfkw-Wb9WHrXYQWGU1kEF2XNCgxib52RYLZL9PBBDhadncPQEwfF-BXGWKIBYfd4pAPoSI4eyclqQLYcVVKMYyq-x6P-O+tDY5JAYS-Zhqc7FSIcaVSyciSxVUUZvT2KjMGsRwQcJ8Wjm7sUwdYEOlgnxOk5LfeWHjDLCJVowke4iWFQMCeZZxmVMLYVwu4wRnj+DeLESnJgkjdYpSCSQEJ1EN73jQQgFR+h6TnGsL5TQQlz5BWCnMEwEsTGeSipk6hcNam5K8eNXoR4kKPQoO-WATBWCDwgIIXax1dpMzuntAAWjnScLMDqWwusgU6j17mE1OndUgu1ukc16bYQ4ngyHuDDs2ICgVtFrB+syICfse5QxsGY++dSkbrIQnOLZqAdnjzAIIQ2p0JS7QwAAdVOoTcmGARRfPLr098T5T6+T4s6RYWhvIen0J5O0TLgpkPWN3FY8KcmFXqWsjZeA0UYuoOnLFJLs7XVufcx5pAHqm1erRZBESqWdh0EsWu-kiG1zWEFDQJxdAbHaqIYZVxnQSUWdHWSAqkXCtFTgXZ-i4LCpPKhFxcC3HZOWXa5WyLSKOudS0r4JFjwoTPBVeRFLUHTF+UcJ8ZCPAwpBUFPi7kNjnGlm+c1fLfUHPoAG1F2ynXNKgWGpC7qVyCAqcmPhOUBExxWYKwtDqS3BvLW6iNFEo2hMQeEnpcbz4JoBcm4FZC01V0NWoHuYd8F9SCJJZgqAIBwGUI26CA7vnTAALSgp2Duu0KTj0npPVFPNTaWB+ogFuyl0wj7aI0AMwFBxOx8ydG+C9trRptB+LkW9saazOmNf5aZ+CfTMjai+PYngjCdgOJ5bQX6B6Ix1BwuMAHGLuFbPg0+6xTV6EuMyK1UkfVNrta6lFlbu2YecsYAZjciGms8qyVlhhfxRQ7F2KW58Fh+2QwjR+rTTLtMILRz22HtHtRYhyN87gpZMoExY4R4nensR0E6PQz5WTsm+h6Tw7ZvIwuZBSZ8stSM0PzUrKxoDCkp1U9MCW9JnQzuGRgvT2ilhTMNSk8+axOz7CU0I1Z+SfFFNTlrcV9jwGoPZnexA5hiEue0+5kSx9-xgxScJXiWqFkWaWeRgtoi7NLXFXQBz6gjDPq8H7KkAkbDpd0DYfs6wTGdiC4ihpJWJFQPzJwiruwRIMf8rxXyzZmSdmPi+VyfFJawfEoYDrzan7WJi2W+xbSymWQG4Y0wnURvNYg0yiZ-ksHn20LxXsIUSMbpQ8AoVVGRXtoG05zTrmdOaDS9oshL5hkUL9D3KKVD8s2ru5Y1tj2g17OvQNtYcwZ2smy+Qp8WggrOnbCHYCLmNX6CW9eiHgb22YoG2cDqrJWR+Dwf6DQaaTG6GluJNy13nR44LUWzZROXWhq7eRAbiXnNabc7pr7zdDV7cQ+1CbthnyBkXUAA */ createMachine( { context: initialContext, @@ -96,8 +98,26 @@ export const createPureLogExplorerProfileStateMachine = ( type: 'parallel', states: { datasetSelection: { - initial: 'idle', + initial: 'validatingSelection', states: { + validatingSelection: { + invoke: { + src: 'validateSelection', + }, + on: { + LISTEN_TO_CHANGES: { + target: 'idle', + }, + UPDATE_DATASET_SELECTION: { + target: 'updatingDataView', + actions: ['storeDatasetSelection'], + }, + DATASET_SELECTION_RESTORE_FAILURE: { + target: 'updatingDataView', + actions: ['notifyDatasetSelectionRestoreFailed'], + }, + }, + }, idle: { invoke: { src: 'listenUrlChange', @@ -218,12 +238,14 @@ export const createPureLogExplorerProfileStateMachine = ( export interface LogExplorerProfileStateMachineDependencies { initialContext?: LogExplorerProfileContext; + datasetsClient: IDatasetsClient; stateContainer: DiscoverStateContainer; toasts: IToasts; } export const createLogExplorerProfileStateMachine = ({ initialContext = DEFAULT_CONTEXT, + datasetsClient, stateContainer, toasts, }: LogExplorerProfileStateMachineDependencies) => @@ -240,6 +262,7 @@ export const createLogExplorerProfileStateMachine = ({ subscribeControlGroup: subscribeControlGroup({ stateContainer }), updateControlPanels: updateControlPanels({ stateContainer }), updateStateContainer: updateStateContainer({ stateContainer }), + validateSelection: validateSelection({ datasetsClient }), }, }); diff --git a/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/types.ts b/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/types.ts index 621f1c8f56b76..fe4323fac0cd4 100644 --- a/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/types.ts +++ b/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/types.ts @@ -8,7 +8,7 @@ import * as rt from 'io-ts'; import { ControlGroupAPI } from '@kbn/controls-plugin/public'; import { DoneInvokeEvent } from 'xstate'; -import type { DatasetEncodingError, DatasetSelection } from '../../../utils/dataset_selection'; +import type { DatasetEncodingError, DatasetSelection } from '../../../../common/dataset_selection'; export interface WithDatasetSelection { datasetSelection: DatasetSelection; @@ -49,6 +49,10 @@ export type LogExplorerProfileTypeState = value: 'initialized'; context: WithDatasetSelection & WithControlPanels; } + | { + value: 'initialized.datasetSelection.validatingSelection'; + context: WithDatasetSelection & WithControlPanels; + } | { value: 'initialized.datasetSelection.idle'; context: WithDatasetSelection & WithControlPanels; @@ -79,6 +83,9 @@ export type LogExplorerProfileContext = LogExplorerProfileTypeState['context']; export type LogExplorerProfileStateValue = LogExplorerProfileTypeState['value']; export type LogExplorerProfileEvent = + | { + type: 'LISTEN_TO_CHANGES'; + } | { type: 'UPDATE_DATASET_SELECTION'; data: DatasetSelection; diff --git a/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/url_state_storage_service.ts b/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/url_state_storage_service.ts index 1864a4a558dac..5fd46dec9bf0b 100644 --- a/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/url_state_storage_service.ts +++ b/x-pack/plugins/log_explorer/public/state_machines/log_explorer_profile/src/url_state_storage_service.ts @@ -10,16 +10,16 @@ import deepEqual from 'fast-deep-equal'; import { DiscoverAppState, DiscoverStateContainer } from '@kbn/discover-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { ROWS_HEIGHT_OPTIONS } from '@kbn/unified-data-table'; -import { - DATA_GRID_COLUMNS_PREFERENCES, - DATA_GRID_DEFAULT_COLUMNS, -} from '../../../../common/constants'; import { AllDatasetSelection, decodeDatasetSelectionId, hydrateDatasetSelection, isDatasetSelection, -} from '../../../utils/dataset_selection'; +} from '../../../../common/dataset_selection'; +import { + DATA_GRID_COLUMNS_PREFERENCES, + DATA_GRID_DEFAULT_COLUMNS, +} from '../../../../common/constants'; import { ControlPanelRT, ControlPanels, diff --git a/x-pack/plugins/log_explorer/public/types.ts b/x-pack/plugins/log_explorer/public/types.ts index d0b488950fee4..07a345bc661d2 100644 --- a/x-pack/plugins/log_explorer/public/types.ts +++ b/x-pack/plugins/log_explorer/public/types.ts @@ -5,17 +5,23 @@ * 2.0. */ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { DiscoverStart } from '@kbn/discover-plugin/public'; +import type { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public'; +import { SharePluginSetup } from '@kbn/share-plugin/public'; import type { ComponentType } from 'react'; +import { LogExplorerLocators } from '../common/locators'; import type { LogExplorerProps } from './components/log_explorer'; -export type LogExplorerPluginSetup = void; +export interface LogExplorerPluginSetup { + locators: LogExplorerLocators; +} export interface LogExplorerPluginStart { LogExplorer: ComponentType; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface LogExplorerSetupDeps {} +export interface LogExplorerSetupDeps { + share: SharePluginSetup; + discover: DiscoverSetup; +} export interface LogExplorerStartDeps { data: DataPublicPluginStart; diff --git a/x-pack/plugins/log_explorer/tsconfig.json b/x-pack/plugins/log_explorer/tsconfig.json index 9cfb123160983..8845fdb4c3b0c 100644 --- a/x-pack/plugins/log_explorer/tsconfig.json +++ b/x-pack/plugins/log_explorer/tsconfig.json @@ -20,7 +20,9 @@ "@kbn/data-plugin", "@kbn/unified-field-list", "@kbn/core-application-browser", + "@kbn/share-plugin", "@kbn/unified-data-table", + "@kbn/deeplinks-observability" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability/public/pages/landing/landing.tsx b/x-pack/plugins/observability/public/pages/landing/landing.tsx index 9568af1a0e0b8..cbeebf0edab1e 100644 --- a/x-pack/plugins/observability/public/pages/landing/landing.tsx +++ b/x-pack/plugins/observability/public/pages/landing/landing.tsx @@ -4,8 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { DISCOVER_APP_ID } from '@kbn/deeplinks-analytics'; import React, { useEffect } from 'react'; +import { + AllDatasetsLocatorParams, + ALL_DATASETS_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; import { useHasData } from '../../hooks/use_has_data'; import { useKibana } from '../../utils/kibana_react'; @@ -14,6 +17,7 @@ export function LandingPage() { const { application: { navigateToUrl, navigateToApp }, http: { basePath }, + share: { url }, } = useKibana().services; useEffect(() => { @@ -23,16 +27,17 @@ export function LandingPage() { const hasLogsData = logs?.hasData; if (hasLogsData) { - navigateToApp(DISCOVER_APP_ID, { - deepLinkId: 'log-explorer', - }); + const allDataSetsLocator = + url.locators.get(ALL_DATASETS_LOCATOR_ID); + + allDataSetsLocator?.navigate({}); } else if (hasApmData) { navigateToUrl(basePath.prepend('/app/apm/services')); } else { navigateToUrl(basePath.prepend('/app/observabilityOnboarding')); } } - }, [basePath, hasDataMap, isAllRequestsComplete, navigateToApp, navigateToUrl]); + }, [basePath, hasDataMap, isAllRequestsComplete, navigateToApp, navigateToUrl, url.locators]); return <>; } diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json index 35137bbddb002..53a2fd3815170 100644 --- a/x-pack/plugins/observability/tsconfig.json +++ b/x-pack/plugins/observability/tsconfig.json @@ -82,11 +82,11 @@ "@kbn/data-view-editor-plugin", "@kbn/actions-plugin", "@kbn/core-capabilities-common", - "@kbn/deeplinks-analytics", "@kbn/observability-ai-assistant-plugin", "@kbn/osquery-plugin", "@kbn/aiops-plugin", - "@kbn/content-management-plugin" + "@kbn/content-management-plugin", + "@kbn/deeplinks-observability" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_log_explorer/common/index.ts b/x-pack/plugins/observability_log_explorer/common/index.ts new file mode 100644 index 0000000000000..40e3f1373687d --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { SingleDatasetLocatorDefinition, AllDatasetsLocatorDefinition } from './locators'; diff --git a/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts b/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts new file mode 100644 index 0000000000000..17c8b2ae02047 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/all_datasets_locator.ts @@ -0,0 +1,34 @@ +/* + * 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 { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; +import { AllDatasetSelection } from '@kbn/log-explorer-plugin/common'; +import { + AllDatasetsLocatorParams, + ALL_DATASETS_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; +import { DatasetLocatorDependencies } from '../types'; +import { constructLocatorPath } from '../utils'; + +export type AllDatasetsLocator = LocatorPublic; + +export class AllDatasetsLocatorDefinition implements LocatorDefinition { + public readonly id = ALL_DATASETS_LOCATOR_ID; + + constructor(protected readonly deps: DatasetLocatorDependencies) {} + + public readonly getLocation = (params: AllDatasetsLocatorParams) => { + const { useHash } = this.deps; + const index = AllDatasetSelection.create().toDataviewSpec().id; + + return constructLocatorPath({ + locatorParams: params, + index, + useHash, + }); + }; +} diff --git a/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/index.ts b/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/index.ts new file mode 100644 index 0000000000000..078549b8593b1 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/all_datasets/index.ts @@ -0,0 +1,8 @@ +/* + * 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 * from './all_datasets_locator'; diff --git a/x-pack/plugins/observability_log_explorer/common/locators/index.ts b/x-pack/plugins/observability_log_explorer/common/locators/index.ts new file mode 100644 index 0000000000000..7571731a22221 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/index.ts @@ -0,0 +1,18 @@ +/* + * 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 { AllDatasetsLocator } from './all_datasets'; +import { SingleDatasetLocator } from './single_dataset'; + +export * from './single_dataset'; +export * from './all_datasets'; +export * from './utils'; + +export interface ObservabilityLogExplorerLocators { + allDatasetsLocator: AllDatasetsLocator; + singleDatasetLocator: SingleDatasetLocator; +} diff --git a/x-pack/plugins/observability_log_explorer/common/locators/locators.test.ts b/x-pack/plugins/observability_log_explorer/common/locators/locators.test.ts new file mode 100644 index 0000000000000..fa4fea9baf7a8 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/locators.test.ts @@ -0,0 +1,333 @@ +/* + * 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 { FilterStateStore } from '@kbn/es-query'; +import { getStatesFromKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import { + AllDatasetsLocatorParams, + SingleDatasetLocatorParams, +} from '@kbn/deeplinks-observability/locators'; +import { OBSERVABILITY_LOG_EXPLORER_APP_ID } from '../constants'; +import { AllDatasetsLocatorDefinition } from './all_datasets/all_datasets_locator'; +import { SingleDatasetLocatorDefinition } from './single_dataset'; +import { DatasetLocatorDependencies } from './types'; + +const setup = async () => { + const dep: DatasetLocatorDependencies = { + useHash: false, + }; + const allDatasetsLocator = new AllDatasetsLocatorDefinition(dep); + const singleDatasetLocator = new SingleDatasetLocatorDefinition(dep); + + return { + allDatasetsLocator, + singleDatasetLocator, + }; +}; + +describe('Observability Logs Explorer Locators', () => { + const timeRange = { to: 'now', from: 'now-30m' }; + + describe('All Dataset Locator', () => { + it('should create a link with no state', async () => { + const { allDatasetsLocator } = await setup(); + const location = await allDatasetsLocator.getLocation({}); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: '/?_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA)', + state: {}, + }); + }); + + it('should allow specifiying time range', async () => { + const params: AllDatasetsLocatorParams = { + timeRange, + }; + + const { allDatasetsLocator } = await setup(); + const location = await allDatasetsLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: '/?_g=(time:(from:now-30m,to:now))&_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA)', + state: {}, + }); + }); + it('should allow specifiying query', async () => { + const params: AllDatasetsLocatorParams = { + query: { + language: 'kuery', + query: 'foo', + }, + }; + + const { allDatasetsLocator } = await setup(); + const location = await allDatasetsLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: '/?_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA,query:(language:kuery,query:foo))', + state: {}, + }); + }); + + it('should allow specifiying refresh interval', async () => { + const params: AllDatasetsLocatorParams = { + refreshInterval: { + pause: false, + value: 666, + }, + }; + + const { allDatasetsLocator } = await setup(); + const location = await allDatasetsLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: '/?_g=(refreshInterval:(pause:!f,value:666))&_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA)', + state: {}, + }); + }); + + it('should allow specifiying columns and sort', async () => { + const params: AllDatasetsLocatorParams = { + columns: ['_source'], + sort: [['timestamp, asc']] as string[][], + }; + + const { allDatasetsLocator } = await setup(); + const location = await allDatasetsLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_a=(columns:!(_source),index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA,sort:!(!('timestamp,%20asc')))`, + state: {}, + }); + }); + + it('should allow specifiying filters', async () => { + const params: AllDatasetsLocatorParams = { + filters: [ + { + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.APP_STATE, + }, + }, + { + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.GLOBAL_STATE, + }, + }, + ], + }; + + const { allDatasetsLocator } = await setup(); + const { path } = await allDatasetsLocator.getLocation(params); + + const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g'], { getFromHashQuery: false }); + + expect(_a).toEqual({ + filters: [ + { + $state: { + store: 'appState', + }, + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + }, + ], + index: 'BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA', + }); + expect(_g).toEqual({ + filters: [ + { + $state: { + store: 'globalState', + }, + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + }, + ], + }); + }); + }); + + describe('Single Dataset Locator', () => { + const integration = 'Test'; + const dataset = 'test-*'; + it('should create a link with correct index', async () => { + const { singleDatasetLocator } = await setup(); + const location = await singleDatasetLocator.getLocation({ + integration, + dataset, + }); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA)`, + state: {}, + }); + }); + + it('should allow specifiying time range', async () => { + const params: SingleDatasetLocatorParams = { + integration, + dataset, + timeRange, + }; + + const { singleDatasetLocator } = await setup(); + const location = await singleDatasetLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_g=(time:(from:now-30m,to:now))&_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA)`, + state: {}, + }); + }); + + it('should allow specifiying query', async () => { + const params: SingleDatasetLocatorParams = { + integration, + dataset, + query: { + language: 'kuery', + query: 'foo', + }, + }; + + const { singleDatasetLocator } = await setup(); + const location = await singleDatasetLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA,query:(language:kuery,query:foo))`, + state: {}, + }); + }); + + it('should allow specifiying refresh interval', async () => { + const params: SingleDatasetLocatorParams = { + integration, + dataset, + refreshInterval: { + pause: false, + value: 666, + }, + }; + + const { singleDatasetLocator } = await setup(); + const location = await singleDatasetLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_g=(refreshInterval:(pause:!f,value:666))&_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA)`, + state: {}, + }); + }); + + it('should allow specifiying columns and sort', async () => { + const params: SingleDatasetLocatorParams = { + integration, + dataset, + columns: ['_source'], + sort: [['timestamp, asc']] as string[][], + }; + + const { singleDatasetLocator } = await setup(); + const location = await singleDatasetLocator.getLocation(params); + + expect(location).toMatchObject({ + app: OBSERVABILITY_LOG_EXPLORER_APP_ID, + path: `/?_a=(columns:!(_source),index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA,sort:!(!('timestamp,%20asc')))`, + state: {}, + }); + }); + + it('should allow specifiying filters', async () => { + const params: SingleDatasetLocatorParams = { + integration, + dataset, + filters: [ + { + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.APP_STATE, + }, + }, + { + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + $state: { + store: FilterStateStore.GLOBAL_STATE, + }, + }, + ], + }; + + const { singleDatasetLocator } = await setup(); + const { path } = await singleDatasetLocator.getLocation(params); + + const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g'], { getFromHashQuery: false }); + + expect(_a).toEqual({ + filters: [ + { + $state: { + store: 'appState', + }, + meta: { + alias: 'foo', + disabled: false, + negate: false, + }, + }, + ], + index: + 'BQZwpgNmDGAuCWB7AdgLmAEwIay+W6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA', + }); + expect(_g).toEqual({ + filters: [ + { + $state: { + store: 'globalState', + }, + meta: { + alias: 'bar', + disabled: false, + negate: false, + }, + }, + ], + }); + }); + }); +}); diff --git a/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/index.ts b/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/index.ts new file mode 100644 index 0000000000000..ae8e21e7c8296 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/index.ts @@ -0,0 +1,8 @@ +/* + * 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 * from './single_dataset_locator'; diff --git a/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts b/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts new file mode 100644 index 0000000000000..632d0d8d93de6 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/single_dataset/single_dataset_locator.ts @@ -0,0 +1,50 @@ +/* + * 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 { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; +import { UnresolvedDatasetSelection } from '@kbn/log-explorer-plugin/common'; +import type { IndexPattern } from '@kbn/io-ts-utils'; +import { + SingleDatasetLocatorParams, + SINGLE_DATASET_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; +import { DatasetLocatorDependencies } from '../types'; +import { constructLocatorPath } from '../utils'; + +export type SingleDatasetLocator = LocatorPublic; + +export class SingleDatasetLocatorDefinition + implements LocatorDefinition +{ + public readonly id = SINGLE_DATASET_LOCATOR_ID; + + constructor(protected readonly deps: DatasetLocatorDependencies) {} + + public readonly getLocation = (params: SingleDatasetLocatorParams) => { + const { useHash } = this.deps; + const { integration, dataset } = params; + + const unresolvedDatasetSelection = UnresolvedDatasetSelection.fromSelection({ + name: integration, + dataset: { + name: this.composeIndexPattern(dataset), + }, + }); + + const index = unresolvedDatasetSelection.toDataviewSpec().id; + + return constructLocatorPath({ + locatorParams: params, + index, + useHash, + }); + }; + + private composeIndexPattern(datasetName: SingleDatasetLocatorParams['dataset']) { + return `logs-${datasetName}-*` as IndexPattern; + } +} diff --git a/x-pack/plugins/observability_log_explorer/common/locators/types.ts b/x-pack/plugins/observability_log_explorer/common/locators/types.ts new file mode 100644 index 0000000000000..a181fdce10a02 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/types.ts @@ -0,0 +1,20 @@ +/* + * 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 { AggregateQuery, Filter, Query } from '@kbn/es-query'; + +export interface AppState { + index?: string; + query?: Query | AggregateQuery; + filters?: Filter[]; + columns?: string[]; + sort?: string[][]; +} + +export interface DatasetLocatorDependencies { + useHash: boolean; +} diff --git a/x-pack/plugins/observability_log_explorer/common/locators/utils/helpers.ts b/x-pack/plugins/observability_log_explorer/common/locators/utils/helpers.ts new file mode 100644 index 0000000000000..c22c000732839 --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/utils/helpers.ts @@ -0,0 +1,60 @@ +/* + * 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 { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public'; +import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/common'; +import { DatasetLocatorParams } from '@kbn/deeplinks-observability/locators'; +import { AppState } from '../types'; + +interface LocatorPathCosntructionParams { + locatorParams: DatasetLocatorParams; + index: string; + useHash: boolean; +} + +export const constructLocatorPath = async (params: LocatorPathCosntructionParams) => { + const { isFilterPinned } = await import('@kbn/es-query'); + + const { + locatorParams: { filters, query, refreshInterval, timeRange, columns, sort }, + index, + useHash, + } = params; + const appState: AppState = {}; + const queryState: GlobalQueryStateFromUrl = {}; + + // App state + if (index) appState.index = index; + if (query) appState.query = query; + if (filters && filters.length) appState.filters = filters?.filter((f) => !isFilterPinned(f)); + if (columns) appState.columns = columns; + if (sort) appState.sort = sort; + + // Global State + if (timeRange) queryState.time = timeRange; + if (filters && filters.length) queryState.filters = filters?.filter((f) => isFilterPinned(f)); + if (refreshInterval) queryState.refreshInterval = refreshInterval; + + let path = '/'; + + if (Object.keys(queryState).length) { + path = setStateToKbnUrl( + '_g', + queryState, + { useHash, storeInHashQuery: false }, + path + ); + } + + path = setStateToKbnUrl('_a', appState, { useHash, storeInHashQuery: false }, path); + + return { + app: 'observability-log-explorer', + path, + state: {}, + }; +}; diff --git a/x-pack/plugins/observability_log_explorer/common/locators/utils/index.ts b/x-pack/plugins/observability_log_explorer/common/locators/utils/index.ts new file mode 100644 index 0000000000000..6c315f929b9bb --- /dev/null +++ b/x-pack/plugins/observability_log_explorer/common/locators/utils/index.ts @@ -0,0 +1,8 @@ +/* + * 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 * from './helpers'; diff --git a/x-pack/plugins/observability_log_explorer/kibana.jsonc b/x-pack/plugins/observability_log_explorer/kibana.jsonc index 15beb1ed3c8d2..7ac940de86dd4 100644 --- a/x-pack/plugins/observability_log_explorer/kibana.jsonc +++ b/x-pack/plugins/observability_log_explorer/kibana.jsonc @@ -16,11 +16,15 @@ "discover", "logExplorer", "observabilityShared", - "share" + "share", + "kibanaUtils", ], "optionalPlugins": [ "serverless" ], - "requiredBundles": ["kibanaReact", "observabilityOnboarding"] + "requiredBundles": ["kibanaReact"], + "extraPublicDirs": [ + "common", + ] } } diff --git a/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx b/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx index dab2ddb772010..d9456e6ed253b 100644 --- a/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx +++ b/x-pack/plugins/observability_log_explorer/public/components/log_explorer_top_nav_menu.tsx @@ -25,7 +25,7 @@ import { LogExplorerStateContainer } from '@kbn/log-explorer-plugin/public'; import { OBSERVABILITY_ONBOARDING_LOCATOR, ObservabilityOnboardingLocatorParams, -} from '@kbn/observability-onboarding-plugin/public'; +} from '@kbn/deeplinks-observability/locators'; import { KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { css } from '@emotion/react'; diff --git a/x-pack/plugins/observability_log_explorer/public/plugin.ts b/x-pack/plugins/observability_log_explorer/public/plugin.ts index 8ca79e4ec7be4..e82d863727a60 100644 --- a/x-pack/plugins/observability_log_explorer/public/plugin.ts +++ b/x-pack/plugins/observability_log_explorer/public/plugin.ts @@ -13,6 +13,11 @@ import { Plugin, PluginInitializerContext, } from '@kbn/core/public'; +import { + ObservabilityLogExplorerLocators, + SingleDatasetLocatorDefinition, + AllDatasetsLocatorDefinition, +} from '../common/locators'; import { type ObservabilityLogExplorerConfig } from '../common/plugin_config'; import { OBSERVABILITY_LOG_EXPLORER_APP_ID } from '../common/constants'; import { logExplorerAppTitle } from '../common/translations'; @@ -28,6 +33,7 @@ export class ObservabilityLogExplorerPlugin implements Plugin { private config: ObservabilityLogExplorerConfig; + private locators?: ObservabilityLogExplorerLocators; constructor(context: PluginInitializerContext) { this.config = context.config.get(); @@ -37,6 +43,9 @@ export class ObservabilityLogExplorerPlugin core: CoreSetup, _pluginsSetup: ObservabilityLogExplorerSetupDeps ) { + const { share } = _pluginsSetup; + const useHash = core.uiSettings.get('state:storeInSessionStorage'); + core.application.register({ id: OBSERVABILITY_LOG_EXPLORER_APP_ID, title: logExplorerAppTitle, @@ -58,7 +67,26 @@ export class ObservabilityLogExplorerPlugin }, }); - return {}; + // Register Locators + const singleDatasetLocator = share.url.locators.create( + new SingleDatasetLocatorDefinition({ + useHash, + }) + ); + const allDatasetsLocator = share.url.locators.create( + new AllDatasetsLocatorDefinition({ + useHash, + }) + ); + + this.locators = { + singleDatasetLocator, + allDatasetsLocator, + }; + + return { + locators: this.locators, + }; } public start(_core: CoreStart, _pluginsStart: ObservabilityLogExplorerStartDeps) { diff --git a/x-pack/plugins/observability_log_explorer/public/types.ts b/x-pack/plugins/observability_log_explorer/public/types.ts index 48b2ad624796a..a4596995a4a7b 100644 --- a/x-pack/plugins/observability_log_explorer/public/types.ts +++ b/x-pack/plugins/observability_log_explorer/public/types.ts @@ -6,20 +6,23 @@ */ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { DiscoverStart } from '@kbn/discover-plugin/public'; import { LogExplorerPluginStart } from '@kbn/log-explorer-plugin/public'; +import { DiscoverStart } from '@kbn/discover-plugin/public'; import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; import { ServerlessPluginStart } from '@kbn/serverless/public'; -import { SharePluginStart } from '@kbn/share-plugin/public'; +import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; +import { ObservabilityLogExplorerLocators } from '../common/locators'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ObservabilityLogExplorerPluginSetup {} +export interface ObservabilityLogExplorerPluginSetup { + locators: ObservabilityLogExplorerLocators; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ObservabilityLogExplorerPluginStart {} export interface ObservabilityLogExplorerSetupDeps { serverless?: ServerlessPluginStart; + share: SharePluginSetup; } export interface ObservabilityLogExplorerStartDeps { diff --git a/x-pack/plugins/observability_log_explorer/tsconfig.json b/x-pack/plugins/observability_log_explorer/tsconfig.json index 0100aa5abb37a..9a0fb5d43e7b9 100644 --- a/x-pack/plugins/observability_log_explorer/tsconfig.json +++ b/x-pack/plugins/observability_log_explorer/tsconfig.json @@ -22,11 +22,14 @@ "@kbn/serverless", "@kbn/core-chrome-browser", "@kbn/config-schema", + "@kbn/kibana-utils-plugin", "@kbn/core-application-browser", "@kbn/discover-plugin", - "@kbn/observability-onboarding-plugin", + "@kbn/es-query", "@kbn/react-kibana-mount", "@kbn/share-plugin", + "@kbn/io-ts-utils", + "@kbn/deeplinks-observability" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts index 1307b4da93100..0843423f10b1b 100644 --- a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts @@ -618,12 +618,12 @@ describe('[Logs onboarding] Custom logs - install elastic agent', () => { .should('exist'); }); - it('when user clicks on Explore Logs it navigates to discover', () => { + it('when user clicks on Explore Logs it navigates to observability log explorer', () => { cy.wait('@checkOnboardingProgress'); cy.getByTestSubj('obltOnboardingExploreLogs').should('exist').click(); - cy.url().should('include', '/app/discover'); - cy.get('button[title="logs-*"]').should('exist'); + cy.url().should('include', '/app/observability-log-explorer'); + cy.get('button').contains('[mylogs] mylogs').should('exist'); }); }); }); diff --git a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/system_logs.cy.ts b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/system_logs.cy.ts index 3497ac145eaf3..41347191561ca 100644 --- a/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/system_logs.cy.ts +++ b/x-pack/plugins/observability_onboarding/e2e/cypress/e2e/logs/system_logs.cy.ts @@ -645,13 +645,13 @@ describe('[Logs onboarding] System logs', () => { .should('exist'); }); - it('when user clicks on Explore Logs it navigates to discover', () => { + it('when user clicks on Explore Logs it navigates to observability log explorer', () => { cy.wait('@systemIntegrationInstall'); cy.wait('@checkOnboardingProgress'); cy.getByTestSubj('obltOnboardingExploreLogs').should('exist').click(); - cy.url().should('include', '/app/discover'); - cy.get('button[title="logs-*"]').should('exist'); + cy.url().should('include', '/app/observability-log-explorer'); + cy.get('button').contains('[System] syslog').should('exist'); }); }); }); diff --git a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx index fc8e36626c4cd..aea68cdd6711c 100644 --- a/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx +++ b/x-pack/plugins/observability_onboarding/public/components/app/custom_logs/install_elastic_agent.tsx @@ -15,6 +15,10 @@ import { import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { default as React, useCallback, useEffect, useState } from 'react'; +import { + SingleDatasetLocatorParams, + SINGLE_DATASET_LOCATOR_ID, +} from '@kbn/deeplinks-observability/locators'; import { ObservabilityOnboardingPluginSetupDeps } from '../../../plugin'; import { useWizard } from '.'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; @@ -34,25 +38,38 @@ import { } from '../../shared/step_panel'; import { ApiKeyBanner } from './api_key_banner'; import { BackButton } from './back_button'; -import { getDiscoverNavigationParams } from '../utils'; import { WindowsInstallStep } from '../../shared/windows_install_step'; import { TroubleshootingLink } from '../../shared/troubleshooting_link'; export function InstallElasticAgent() { const { - services: { - discover: { locator }, - }, + services: { share }, } = useKibana(); + + const singleDatasetLocator = + share.url.locators.get( + SINGLE_DATASET_LOCATOR_ID + ); + const { goBack, getState, setState } = useWizard(); const wizardState = getState(); + const { + integrationName: integration, + datasetName: dataset, + autoDownloadConfig, + } = wizardState; + const [elasticAgentPlatform, setElasticAgentPlatform] = useState('linux-tar'); + const enforcedDatasetName = + integration === dataset ? dataset : `${integration}.${dataset}`; + async function onContinue() { - await locator?.navigate( - getDiscoverNavigationParams([wizardState.datasetName]) - ); + await singleDatasetLocator!.navigate({ + integration, + dataset: enforcedDatasetName, + }); } function onAutoDownloadConfig() { @@ -258,7 +275,7 @@ export function InstallElasticAgent() {

- {wizardState.integrationName && ( + {integration && ( <> (); + const singleDatasetLocator = + share.url.locators.get( + SINGLE_DATASET_LOCATOR_ID + ); + const { navigateToKibanaUrl } = useKibanaNavigation(); const { getState, setState } = useWizard(); const wizardState = getState(); @@ -60,11 +65,10 @@ export function InstallElasticAgent() { navigateToKibanaUrl('/app/observabilityOnboarding'); } async function onContinue() { - const dataStreams = getSystemLogsDataStreams(); - const dataSets = dataStreams.map( - (dataSream) => dataSream.data_stream.dataset - ); - await locator?.navigate(getDiscoverNavigationParams(dataSets)); + await singleDatasetLocator!.navigate({ + integration: 'system', + dataset: 'system.syslog', + }); } function onAutoDownloadConfig() { diff --git a/x-pack/plugins/observability_onboarding/public/components/app/utils.ts b/x-pack/plugins/observability_onboarding/public/components/app/utils.ts deleted file mode 100644 index 843002cb1fcc6..0000000000000 --- a/x-pack/plugins/observability_onboarding/public/components/app/utils.ts +++ /dev/null @@ -1,56 +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 { DataViewSpec } from '@kbn/data-views-plugin/common'; -import { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; -import { Filter, FilterStateStore } from '@kbn/es-query'; - -type DiscoverPropertiesToPick = 'dataViewId' | 'dataViewSpec' | 'filters'; - -type DiscoverNavigationParams = Pick< - DiscoverAppLocatorParams, - DiscoverPropertiesToPick ->; - -const defaultFilterKey = 'data_stream.dataset'; -const defaultLogsDataViewId = 'logs-*'; -const defaultLogsDataView: DataViewSpec = { - id: defaultLogsDataViewId, - title: defaultLogsDataViewId, -}; - -const getDefaultDatasetFilter = (datasets: string[]): Filter[] => [ - { - meta: { - index: defaultLogsDataViewId, - key: defaultFilterKey, - params: datasets, - type: 'phrases', - }, - query: { - bool: { - minimum_should_match: 1, - should: datasets.map((dataset) => ({ - match_phrase: { - [defaultFilterKey]: dataset, - }, - })), - }, - }, - $state: { - store: FilterStateStore.APP_STATE, - }, - }, -]; - -export const getDiscoverNavigationParams = ( - datasets: string[] -): DiscoverNavigationParams => ({ - dataViewId: defaultLogsDataViewId, - dataViewSpec: defaultLogsDataView, - filters: getDefaultDatasetFilter(datasets), -}); diff --git a/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/locator_definition.ts b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/locator_definition.ts index 0c18e06cb92f5..a4f8965dda5a3 100644 --- a/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/locator_definition.ts +++ b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/locator_definition.ts @@ -6,10 +6,10 @@ */ import type { LocatorDefinition } from '@kbn/share-plugin/public'; -import type { ObservabilityOnboardingLocatorParams } from './types'; - -export const OBSERVABILITY_ONBOARDING_LOCATOR = - 'OBSERVABILITY_ONBOARDING_LOCATOR' as const; +import { + ObservabilityOnboardingLocatorParams, + OBSERVABILITY_ONBOARDING_LOCATOR, +} from '@kbn/deeplinks-observability/locators'; export class ObservabilityOnboardingLocatorDefinition implements LocatorDefinition diff --git a/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/types.ts b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/types.ts index b8709f6af361d..61f6d923db49d 100644 --- a/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/types.ts +++ b/x-pack/plugins/observability_onboarding/public/locators/onboarding_locator/types.ts @@ -6,13 +6,7 @@ */ import type { LocatorPublic } from '@kbn/share-plugin/public'; -import { SerializableRecord } from '@kbn/utility-types'; - -export interface ObservabilityOnboardingLocatorParams - extends SerializableRecord { - /** If given, it will load the given map else will load the create a new map page. */ - source?: 'customLogs' | 'systemLogs'; -} +import type { ObservabilityOnboardingLocatorParams } from '@kbn/deeplinks-observability/locators'; export type ObservabilityOnboardingLocator = LocatorPublic; diff --git a/x-pack/plugins/observability_onboarding/public/plugin.ts b/x-pack/plugins/observability_onboarding/public/plugin.ts index 45fa8a640c02f..05806059b12e3 100644 --- a/x-pack/plugins/observability_onboarding/public/plugin.ts +++ b/x-pack/plugins/observability_onboarding/public/plugin.ts @@ -23,7 +23,6 @@ import { DataPublicPluginSetup, DataPublicPluginStart, } from '@kbn/data-plugin/public'; -import type { DiscoverSetup } from '@kbn/discover-plugin/public'; import { SharePluginSetup } from '@kbn/share-plugin/public'; import type { ObservabilityOnboardingConfig } from '../server'; import { PLUGIN_ID } from '../common'; @@ -35,7 +34,6 @@ export type ObservabilityOnboardingPluginStart = void; export interface ObservabilityOnboardingPluginSetupDeps { data: DataPublicPluginSetup; - discover: DiscoverSetup; observability: ObservabilityPublicSetup; share: SharePluginSetup; } diff --git a/x-pack/plugins/observability_onboarding/tsconfig.json b/x-pack/plugins/observability_onboarding/tsconfig.json index 3188e63982483..4e1e2bacdf437 100644 --- a/x-pack/plugins/observability_onboarding/tsconfig.json +++ b/x-pack/plugins/observability_onboarding/tsconfig.json @@ -14,7 +14,6 @@ "kbn_references": [ "@kbn/core", "@kbn/data-plugin", - "@kbn/discover-plugin", "@kbn/kibana-react-plugin", "@kbn/observability-plugin", "@kbn/i18n", @@ -30,12 +29,10 @@ "@kbn/core-http-server", "@kbn/security-plugin", "@kbn/std", - "@kbn/data-views-plugin", - "@kbn/es-query", "@kbn/use-tracked-promise", "@kbn/custom-integrations", "@kbn/share-plugin", - "@kbn/utility-types", + "@kbn/deeplinks-observability" ], "exclude": [ "target/**/*",