From dd06f81811d7f816a73203792605d513215da9d1 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Tue, 13 Feb 2024 13:17:14 +0100 Subject: [PATCH] [Logs Explorer] Support logs data views (#176078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ๐Ÿ““ Summary Closes #175767 ๐Ÿงช You can access a live deployment of this PR [here](https://issue-deploy-kibana-pr176078.kb.us-west2.gcp.elastic-cloud.com/app/observability-logs-explorer/). There has been a lot of talking around supporting selection for data views and staying on the Logs Explorer for those concerning logs data streams. This work supports selecting and exploring Discover data views on Logs Explorer. This is currently limited to logs data views. https://github.com/elastic/kibana/assets/34506779/cccd6863-e1c1-4fa6-a530-9aed5d8d97c1 ## Next steps We had already an offline conversation with the team about how naming the selector, selection modes and related entities is becoming inconsistent and more difficult to maintain. To keep this PR narrowed to the data views support, an upcoming PR will focus on renaming according to the new selector's responsibilities. ## Core changes ### DataViewDescriptor The `DataViewDescriptor` instance is a way to describe a data view in the context of Logs Explorer. This does not represent a DataView object complete of fields and all the details provided with a DataView instance, but it instead encapsulates the logic around identifying what data type a data view is about, as well as defining the logic to use it with the new `dataView` selection mode. It creates a new instance starting from a `DataViewListItem` object, which is a minimal object provided by the dataViews service to list existing data views. ### LogExplorerController The `LogExplorerController` state machine now handles the selected entry depending on its type, triggering different flows. There are 3 different journeys depending on the selected entry: - For a data view entry which is not about logs, the page redirects to Discover with the data view selected - For a data view entry about logs, the data loads in the Logs Explorer, switching to the persisted DataView. - For a dataset entry, an ad-hoc data view loads in the Logs Explorer. To avoid updating twice the data view (once during initialization, and immediately after during selection validation), the validation flow has been anticipated and restructured to follow different flows, depending on the selection type. Screenshot 2024-02-09 at 12 02 10 ### Dataset selector The selector state machine unifies the selection handler and expands the selection modes, adding a new `dataView` mode which handles logs data view selections. Screenshot 2024-02-05 at 16 24 31 --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../models/data_view_descriptor.test.ts | 83 +++++++++++ .../data_views/models/data_view_descriptor.ts | 102 ++++++++++++++ .../logs_explorer/common/data_views/types.ts | 29 ++++ .../logs_explorer/common/data_views/utils.ts | 15 ++ .../dataset_selection/data_view_selection.ts | 47 +++++++ .../hydrate_dataset_selection.ts.ts | 3 + .../common/dataset_selection/index.ts | 15 +- .../single_dataset_selection.ts | 2 +- .../common/dataset_selection/types.ts | 12 ++ .../dataset_selector.stories.tsx | 16 +-- .../dataset_selector/dataset_selector.tsx | 16 +-- .../state_machine/state_machine.ts | 66 +++++++-- .../dataset_selector/state_machine/types.ts | 21 ++- .../state_machine/use_dataset_selector.ts | 10 +- .../sub_components/data_view_menu_item.tsx | 36 +++++ .../sub_components/data_views_panel_title.tsx | 21 --- ...asets_popover.tsx => selector_popover.tsx} | 68 ++++++--- .../components/dataset_selector/types.ts | 14 +- .../public/controller/create_controller.ts | 5 +- .../custom_dataset_selector.tsx | 7 +- .../customizations/logs_explorer_profile.tsx | 4 +- .../public/hooks/use_data_views.tsx | 15 +- .../public/hooks/use_dataset_selection.ts | 4 +- .../logs_explorer/public/hooks/use_esql.tsx | 14 +- .../data_views/src/state_machine.ts | 22 +-- .../state_machines/data_views/src/types.ts | 14 +- .../datasets/src/state_machine.ts | 2 +- .../src/notifications.ts | 18 ++- .../src/services/data_view_service.ts | 23 +++- .../src/services/discover_service.ts | 33 +++++ .../src/services/selection_service.ts | 110 ++++++++++----- .../src/state_machine.ts | 130 +++++++++++++----- .../logs_explorer_controller/src/types.ts | 57 +++++--- .../public/utils/parse_data_view_list_item.ts | 15 -- 34 files changed, 793 insertions(+), 256 deletions(-) create mode 100644 x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.test.ts create mode 100644 x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts create mode 100644 x-pack/plugins/observability_solution/logs_explorer/common/data_views/types.ts create mode 100644 x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.ts create mode 100644 x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/data_view_selection.ts create mode 100644 x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_view_menu_item.tsx delete mode 100644 x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx rename x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/{datasets_popover.tsx => selector_popover.tsx} (55%) delete mode 100644 x-pack/plugins/observability_solution/logs_explorer/public/utils/parse_data_view_list_item.ts diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.test.ts b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.test.ts new file mode 100644 index 00000000000000..c5eb32a3c4f5ea --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.test.ts @@ -0,0 +1,83 @@ +/* + * 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 { DataViewDescriptor } from './data_view_descriptor'; + +describe('DataViewDescriptor', () => { + it('should correctly assert whether a data view has "logs" type', () => { + const id = 'test-id'; + + // Assert truthy cases + expect(DataViewDescriptor.create({ id, title: 'auditbeat*' }).isLogsDataType()).toBeTruthy(); + expect(DataViewDescriptor.create({ id, title: 'auditbeat-*' }).isLogsDataType()).toBeTruthy(); + expect(DataViewDescriptor.create({ id, title: 'logs*' }).isLogsDataType()).toBeTruthy(); + expect(DataViewDescriptor.create({ id, title: 'logs-*' }).isLogsDataType()).toBeTruthy(); + expect(DataViewDescriptor.create({ id, title: 'logs-*-*' }).isLogsDataType()).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'logs-system.syslog-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'logs-system.syslog-default' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-*-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-system.syslog-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ + id, + title: 'cluster1:logs-system.syslog-default', + }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'logs-*,cluster1:logs-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'logs-*,cluster1:logs-*,' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-*,cluster2:logs-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-*,cluster2:logs-*' }).isLogsDataType() + ).toBeTruthy(); + expect( + DataViewDescriptor.create({ + id, + title: '*:logs-system.syslog-*,*:logs-system.errors-*', + }).isLogsDataType() + ).toBeTruthy(); + + // Assert falsy cases + expect(DataViewDescriptor.create({ id, title: 'auditbeats*' }).isLogsDataType()).toBeFalsy(); + expect(DataViewDescriptor.create({ id, title: 'auditbeats-*' }).isLogsDataType()).toBeFalsy(); + expect(DataViewDescriptor.create({ id, title: 'logss*' }).isLogsDataType()).toBeFalsy(); + expect(DataViewDescriptor.create({ id, title: 'logss-*' }).isLogsDataType()).toBeFalsy(); + expect(DataViewDescriptor.create({ id, title: 'metrics*' }).isLogsDataType()).toBeFalsy(); + expect(DataViewDescriptor.create({ id, title: 'metrics-*' }).isLogsDataType()).toBeFalsy(); + expect( + DataViewDescriptor.create({ + id, + title: '*:metrics-system.syslog-*,logs-system.errors-*', + }).isLogsDataType() + ).toBeFalsy(); + expect( + DataViewDescriptor.create({ id, title: 'cluster1:logs-*,clust,er2:logs-*' }).isLogsDataType() + ).toBeFalsy(); + expect( + DataViewDescriptor.create({ + id, + title: 'cluster1:logs-*, cluster2:logs-*', + }).isLogsDataType() + ).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts new file mode 100644 index 00000000000000..9b9652301dddd2 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts @@ -0,0 +1,102 @@ +/* + * 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 { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { DataViewSpecWithId } from '../../dataset_selection'; +import { DataViewDescriptorType } from '../types'; +import { buildIndexPatternRegExp } from '../utils'; + +type Allowlist = Array; + +const LOGS_ALLOWLIST: Allowlist = [ + buildIndexPatternRegExp(['logs', 'auditbeat', 'filebeat', 'winbeat']), + // Add more strings or regex patterns as needed +]; + +export class DataViewDescriptor { + id: DataViewDescriptorType['id']; + dataType: DataViewDescriptorType['dataType']; + kibanaSpaces: DataViewDescriptorType['kibanaSpaces']; + name: DataViewDescriptorType['name']; + title: DataViewDescriptorType['title']; + type: DataViewDescriptorType['type']; + + private constructor(dataViewDescriptor: DataViewDescriptorType) { + this.id = dataViewDescriptor.id; + this.dataType = dataViewDescriptor.dataType; + this.kibanaSpaces = dataViewDescriptor.kibanaSpaces; + this.name = dataViewDescriptor.name; + this.title = dataViewDescriptor.title; + this.type = dataViewDescriptor.type; + } + + getFullTitle() { + return this.name; + } + + toDataviewSpec(): DataViewSpecWithId { + return { + id: this.id, + name: this.name, + title: this.title, + }; + } + + toPlain() { + return { + id: this.id, + dataType: this.dataType, + name: this.name, + title: this.title, + }; + } + + public static create({ id, namespaces, title, type, name }: DataViewListItem) { + const nameWithFallbackTitle = name ?? title; + const dataType = DataViewDescriptor.#extractDataType(title); + const kibanaSpaces = namespaces; + + return new DataViewDescriptor({ + id, + dataType, + kibanaSpaces, + name: nameWithFallbackTitle, + title, + type, + }); + } + + static #extractDataType(title: string): DataViewDescriptorType['dataType'] { + if (isAllowed(title, LOGS_ALLOWLIST)) { + return 'logs'; + } + + return 'unknown'; + } + + public isLogsDataType() { + return this.dataType === 'logs'; + } + + public isUnknownDataType() { + return this.dataType === 'unknown'; + } +} + +function isAllowed(value: string, allowList: Allowlist) { + for (const allowedItem of allowList) { + if (typeof allowedItem === 'string') { + return value === allowedItem; + } + if (allowedItem instanceof RegExp) { + return allowedItem.test(value); + } + } + + // If no match is found in the allowList, return false + return false; +} diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/types.ts b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/types.ts new file mode 100644 index 00000000000000..82030bcbef318c --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/types.ts @@ -0,0 +1,29 @@ +/* + * 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 * as rt from 'io-ts'; + +const dataTypeRT = rt.keyof({ + logs: null, + unknown: null, +}); + +export const dataViewDescriptorRT = rt.exact( + rt.intersection([ + rt.type({ + id: rt.string, + name: rt.string, + title: rt.string, + dataType: dataTypeRT, + }), + rt.partial({ + kibanaSpaces: rt.array(rt.string), + type: rt.string, + }), + ]) +); + +export type DataViewDescriptorType = rt.TypeOf; diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.ts b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.ts new file mode 100644 index 00000000000000..08a68b0d46c94f --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const buildIndexPatternRegExp = (basePatterns: string[]) => { + // Create the base patterns union with strict boundaries + const basePatternGroup = `\\b(${basePatterns.join('|')})\\b[^,\\s]+`; + // Apply base patterns union for local and remote clusters + const localAndRemotePatternGroup = `((${basePatternGroup})|([^:,\\s]+:${basePatternGroup}))`; + // Handle trailing comma and multiple pattern concatenation + return new RegExp(`^${localAndRemotePatternGroup}(,${localAndRemotePatternGroup})*(,$|$)`, 'i'); +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/data_view_selection.ts b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/data_view_selection.ts new file mode 100644 index 00000000000000..4886270a30ad71 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/data_view_selection.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataViewDescriptor } from '../data_views/models/data_view_descriptor'; +import { DatasetSelectionStrategy, DataViewSelectionPayload } from './types'; + +export class DataViewSelection implements DatasetSelectionStrategy { + selectionType: 'dataView'; + selection: { + dataView: DataViewDescriptor; + }; + + private constructor(dataViewDescriptor: DataViewDescriptor) { + this.selectionType = 'dataView'; + this.selection = { + dataView: dataViewDescriptor, + }; + } + + toDataviewSpec() { + return this.selection.dataView.toDataviewSpec(); + } + + toPlainSelection() { + return { + selectionType: this.selectionType, + selection: { + dataView: this.selection.dataView.toPlain(), + }, + }; + } + + public static fromSelection(selection: DataViewSelectionPayload) { + const { dataView } = selection; + + const dataViewDescriptor = DataViewDescriptor.create(dataView); + return DataViewSelection.create(dataViewDescriptor); + } + + public static create(dataViewDescriptor: DataViewDescriptor) { + return new DataViewSelection(dataViewDescriptor); + } +} diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts index f881e90723e14c..fbef397ebfb194 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/hydrate_dataset_selection.ts.ts @@ -6,6 +6,7 @@ */ import { AllDatasetSelection } from './all_dataset_selection'; +import { DataViewSelection } from './data_view_selection'; import { SingleDatasetSelection } from './single_dataset_selection'; import { DatasetSelectionPlain } from './types'; import { UnresolvedDatasetSelection } from './unresolved_dataset_selection'; @@ -15,6 +16,8 @@ export const hydrateDatasetSelection = (datasetSelection: DatasetSelectionPlain) return AllDatasetSelection.create(); } else if (datasetSelection.selectionType === 'single') { return SingleDatasetSelection.fromSelection(datasetSelection.selection); + } else if (datasetSelection.selectionType === 'dataView') { + return DataViewSelection.fromSelection(datasetSelection.selection); } else { return UnresolvedDatasetSelection.fromSelection(datasetSelection.selection); } diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/index.ts b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/index.ts index 26fd974d0f0ac5..a5b1f1cebd7e28 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/index.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/index.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { DataViewListItem } from '@kbn/data-views-plugin/common'; import { AllDatasetSelection } from './all_dataset_selection'; +import { DataViewSelection } from './data_view_selection'; import { SingleDatasetSelection } from './single_dataset_selection'; import { UnresolvedDatasetSelection } from './unresolved_dataset_selection'; @@ -14,8 +14,7 @@ export type DatasetSelection = | AllDatasetSelection | SingleDatasetSelection | UnresolvedDatasetSelection; -export type DatasetSelectionChange = (datasetSelection: DatasetSelection) => void; -export type DataViewSelection = (dataView: DataViewListItem) => void; +export type SelectionChange = (selection: DatasetSelection | DataViewSelection) => void; export const isDatasetSelection = (input: any): input is DatasetSelection => { return ( @@ -25,7 +24,17 @@ export const isDatasetSelection = (input: any): input is DatasetSelection => { ); }; +export const isUnresolvedDatasetSelection = (input: any): input is UnresolvedDatasetSelection => { + return input instanceof UnresolvedDatasetSelection; +}; + +export const isDataViewSelection = (input: any): input is DataViewSelection => { + return input instanceof DataViewSelection; +}; + export * from './all_dataset_selection'; +export * from './data_view_selection'; +export * from './single_dataset_selection'; export * from './single_dataset_selection'; export * from './unresolved_dataset_selection'; export * from './errors'; diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/single_dataset_selection.ts b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/single_dataset_selection.ts index 6667dd55f3abe1..f9eecab1feaffb 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/single_dataset_selection.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/single_dataset_selection.ts @@ -50,7 +50,7 @@ export class SingleDatasetSelection implements DatasetSelectionStrategy { const integration = name && version ? { name, title, version } : undefined; const datasetInstance = Dataset.create(dataset, integration); - return new SingleDatasetSelection(datasetInstance); + return SingleDatasetSelection.create(datasetInstance); } public static create(dataset: Dataset) { diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/types.ts b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/types.ts index db3638aff63312..0f32211f2888c3 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/types.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/dataset_selection/types.ts @@ -7,6 +7,7 @@ import { DataViewSpec } from '@kbn/data-views-plugin/common'; import * as rt from 'io-ts'; import { datasetRT } from '../datasets'; +import { dataViewDescriptorRT } from '../data_views/types'; export const allDatasetSelectionPlainRT = rt.type({ selectionType: rt.literal('all'), @@ -33,6 +34,10 @@ const singleDatasetSelectionPayloadRT = rt.intersection([ }), ]); +const dataViewSelectionPayloadRT = rt.type({ + dataView: dataViewDescriptorRT, +}); + const unresolvedDatasetSelectionPayloadRT = rt.intersection([ integrationNameRT, rt.type({ @@ -45,6 +50,11 @@ export const singleDatasetSelectionPlainRT = rt.type({ selection: singleDatasetSelectionPayloadRT, }); +export const dataViewSelectionPlainRT = rt.type({ + selectionType: rt.literal('dataView'), + selection: dataViewSelectionPayloadRT, +}); + export const unresolvedDatasetSelectionPlainRT = rt.type({ selectionType: rt.literal('unresolved'), selection: unresolvedDatasetSelectionPayloadRT, @@ -52,11 +62,13 @@ export const unresolvedDatasetSelectionPlainRT = rt.type({ export const datasetSelectionPlainRT = rt.union([ allDatasetSelectionPlainRT, + dataViewSelectionPlainRT, singleDatasetSelectionPlainRT, unresolvedDatasetSelectionPlainRT, ]); export type SingleDatasetSelectionPayload = rt.TypeOf; +export type DataViewSelectionPayload = rt.TypeOf; export type UnresolvedDatasetSelectionPayload = rt.TypeOf< typeof unresolvedDatasetSelectionPayloadRT >; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.stories.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.stories.tsx index 04c0c1ddedac96..ce172f45211da6 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.stories.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.stories.tsx @@ -11,11 +11,12 @@ 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 { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { DataViewDescriptor } from '../../../common/data_views/models/data_view_descriptor'; import { AllDatasetSelection, DatasetSelection, - DatasetSelectionChange, + DataViewSelection, + SelectionChange, } from '../../../common/dataset_selection'; import { Dataset, Integration } from '../../../common/datasets'; import { DatasetSelector } from './dataset_selector'; @@ -43,8 +44,8 @@ const meta: Meta = { export default meta; const DatasetSelectorTemplate: Story = (args) => { - const [datasetSelection, setDatasetSelection] = useState(() => - AllDatasetSelection.create() + const [datasetSelection, setDatasetSelection] = useState( + () => AllDatasetSelection.create() ); const [search, setSearch] = useState({ @@ -59,7 +60,7 @@ const DatasetSelectorTemplate: Story = (args) => { } }; - const onSelectionChange: DatasetSelectionChange = (newSelection) => { + const onSelectionChange: SelectionChange = (newSelection) => { setDatasetSelection(newSelection); }; @@ -123,7 +124,6 @@ Basic.args = { isLoadingUncategorized: false, isSearchingIntegrations: false, onDataViewsReload: () => alert('Reload data views...'), - onDataViewSelection: (dataView) => alert(`Navigate to data view "${dataView.name}"`), onDataViewsTabClick: () => console.log('Load data views...'), onIntegrationsReload: () => alert('Reload integrations...'), onUncategorizedTabClick: () => console.log('Load uncategorized streams...'), @@ -508,7 +508,7 @@ const mockDatasets: Dataset[] = [ { name: 'data-scaling-logs-*' as IndexPattern }, ].map((dataset) => Dataset.create(dataset)); -const mockDataViews: DataViewListItem[] = [ +const mockDataViews: DataViewDescriptor[] = [ { id: 'logs-*', namespaces: ['default'], @@ -528,4 +528,4 @@ const mockDataViews: DataViewListItem[] = [ typeMeta: {}, name: 'synthetics-dashboard', }, -]; +].map((dataView) => DataViewDescriptor.create(dataView)); diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.tsx index b4ccd66a42cca2..507f4d83f8957a 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/dataset_selector.tsx @@ -23,8 +23,8 @@ import { UNCATEGORIZED_TAB_ID, } from './constants'; import { useDatasetSelector } from './state_machine/use_dataset_selector'; -import { DatasetsPopover } from './sub_components/datasets_popover'; -import { DataViewsPanelTitle } from './sub_components/data_views_panel_title'; +import { SelectorPopover } from './sub_components/selector_popover'; +import { DataViewMenuItem } from './sub_components/data_view_menu_item'; import { SearchControls } from './sub_components/search_controls'; import { ESQLButton, SelectorFooter, ShowAllLogsButton } from './sub_components/selector_footer'; import { DatasetSelectorProps } from './types'; @@ -49,7 +49,6 @@ export function DatasetSelector({ isLoadingIntegrations, isLoadingUncategorized, isSearchingIntegrations, - onDataViewSelection, onDataViewsReload, onDataViewsSearch, onDataViewsSort, @@ -86,7 +85,6 @@ export function DatasetSelector({ togglePopover, } = useDatasetSelector({ initialContext: { selection: datasetSelection }, - onDataViewSelection, onDataViewsSearch, onDataViewsSort, onIntegrationsLoadMore, @@ -164,7 +162,7 @@ export function DatasetSelector({ return dataViews.map((dataView) => ({ 'data-test-subj': getDataViewTestSubj(dataView.title), - name: dataView.name, + name: , onClick: () => selectDataView(dataView), })); }, [dataViews, dataViewsError, isLoadingDataViews, selectDataView, onDataViewsReload]); @@ -208,8 +206,8 @@ export function DatasetSelector({ )); return ( - , + title: dataViewsLabel, width: DATA_VIEW_POPOVER_CONTENT_WIDTH, items: dataViewsItems, }, @@ -282,7 +280,7 @@ export function DatasetSelector({ {isEsqlEnabled && } - + ); } diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/state_machine.ts b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/state_machine.ts index 5e5df10504ec35..3982933e96fb07 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/state_machine.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/state_machine.ts @@ -6,7 +6,13 @@ */ import { actions, assign, createMachine, raise } from 'xstate'; -import { AllDatasetSelection, SingleDatasetSelection } from '../../../../common/dataset_selection'; +import { + AllDatasetSelection, + DataViewSelection, + isDatasetSelection, + isDataViewSelection, + SingleDatasetSelection, +} from '../../../../common/dataset_selection'; import { DATA_VIEWS_TAB_ID, INTEGRATIONS_TAB_ID, UNCATEGORIZED_TAB_ID } from '../constants'; import { defaultSearch, DEFAULT_CONTEXT } from './defaults'; import { @@ -20,7 +26,7 @@ import { export const createPureDatasetsSelectorStateMachine = ( initialContext: Partial = DEFAULT_CONTEXT ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgZTABswBjdAewCcA6AB3PoDcwaTDzsIBiAFQHkA4gIAyAUQDaABgC6iUPVgBLdIvIA7OSAAeiAIy6ATAGZqBgBwAWMwHYzdgwE4zD6wBoQAT0RGj16gDYzIwBWKwdJC2CDawMAX1j3NExsXAJiMio6BnJmGgYwNS4AYWE+PAkZTQVlVQ0kbUQbP0kDf0l-IwddSUkXYPcvBH99ah6jNqMzFrMDXSN4xIwsHHwiUgoaeiYWanzC-iExKVl66pV1TR0EO11qI0MLDosLXWsHWYHEUOCAmJ6u3QWBxWawLEBJZapNYZTbZXK7WgFLjlMRFHgAfQAgsJhOjSgI8OjkJieJjyjxjlUODULvUrmZ-H5rLopp0jO1rMEHP5PkMZndOT5JJ1ggy4glwUsUqt0hssts8oi1NRFGp0GAoJQMLVYDxUAAjZEAdQAkjwigAJdH8dEAVQAckUSaIBHwAEomgBaomQ1sxACFKadqec6qArgBaXT+H4GZ4RSS6YIxfwWSRuTx6OaSajWfwGVrRcIRIzixbJFZpdaZLY5HZ7FVqjVa0O6g3Gs2W618IkkzHogBqJtERsJpMDlWDSlDl0QswM1AsrTTDnuSZFFl5+nZufzxmeMSTorBEOlVZh8rrioKjfVmu16jb+uohEUsBUaigJqb99bxQtmL2gIojogACoBojCEG8ghrUs4IN0DimPmoRBP4-hOO0Zi8pytwOMEnRAtEbSzGYJ5SpW0JyrW8INqqd4tjqerPq+76ql+P6MY+yJFG6fA4t26ImvaPAum6JImnw9qEv6fA8PwACy0EgGccF0ogEYFj8yZGBY9yPCELIOLyyYLhhuirjpHSzP45EVlCso1nC9ZKrezYPmoT4vm+H4cQxHmwMioiYm6Xb+gAmui9qYgpFQnDB05qeGGl6T87SkZEYzWJEm6ZkMQKmMEgQEbYjLMnZkIytWsIKgiN70e5rbMd5bGft+-l-ng7oYhF6Lusgohuspqm0slCARrpJhRI8MRvLMSa5YMZjBNpLTptywwuL4FVnlRTm1XRnEBc1rG+e1jW1Hg6CUGAqAALaBZagHAWBEFQZOCU0mGDQIb0yEGKh4wYVMMY4YmozDJyJUGMKeY7ZRjk1VedXKg1v5MQaLVnUdoZXTd92BeUIVhZF0WxcNsGjT9wQsgEwTCpYKbWJyBgmVypjro8q5vHpDjww51WXrRrlo1xnknT57Hnej6h47dD3It16K9f1g0U4lVNXMYzTOO8XQONylis3lK0LsC+jzpMDJLvzVUXjRLn1TjGMsZLbXO7L13y4TkGiGivakuS6tffBXK3GYFncnp7LssCOGTIu4zWC09P6PmtkSqeCOCw717KgArmoJAYBqVCKAAXpAzEduaVo2sJokCOJPCSdJfoTvFKmU99kaOEhpGTIKdhdHmW7tCYJHjFM7TRrMtvntRzl59QhfF3eZeVxA1d4KateCcSpKDsOo7t8HM7qQgcYBOncYMxZRXWEYW6OD8BszEuq4uBYtjz3tSPCzeVeJcoAbyru2ImoUrS9TJnFKkGse4pSmAEKYBF0IFmyvhLcpYLDUFeMnYE7ReaMl-ojIWjsC5F2AaAre4ClYqzdANIaH0u7wPghGb+Zk8z5lmrHem-Q8rbn8NQIq612TrhjPMTOFEBb2yXijFelD16UArmAw0KI-YYgPmSUQFJmEjQQeNOMOC5hxnpq0IqrQtx6SEamF4S5SKOAsCQnOciGwQCWAORQYAADuT4a5dnriJMSEkpJjgDGfJKP1xhm3vn0bknRWRj2TMImGQQ0yhEMLoZxsiDquXcZgTxPi-E707HXHsDonSN3dF6H0p89Hd3goCO4bRIjJ2TjTFakhsICNMYuQIQ8lyzwzuWSqC99rIzcR4rxvjt7BUgcrUmMVYFThDhfYYOZ3hWEyT0FanIsGODuFMecycZhRDLJKeydtF65JvPk1AhSZm0LdD1SKqsmGd30fBdZdxHiBANsCTk7wtxzFuC8NBBskwuEcNk65Ey8lTKKbM1Emi+xHxHBEzWiAR4cycARLky0IiLT0HGBcekZiWFCOyGYMLMjYFlLUagShPzECCsirEAl8SEi0UHeprCL5RlXKMdk5hOhf0mO0LczMmR8OBPhdkjwnFSMuWMmgdL1gMqZVAFl6j-bcp0RigxEYohIQjoEbojgfDhGNoMaM4RcEvALKmEIQQug0tVVRBlqBCCEFZRogO2jdEfIafy6MPx0Lj1TEEdo3Itypj8M4VMDNHjT1BGCNQ5AIBwE0FnGRGw4GrLGhGbhEMMr01hpEeO1BVwhF8HmRMI9ghurIZQfN59C3JiQulFkmVy38JtcMRONabB6RaM8PmSrRl-2bdQNgHBICtsiVcNOJhojpheJZHwHw8pLhweMXwqZzB6Qtk23OKMF2YvGgRW4XaKVZQrXlOMZgq0+AIlC0UpkT2uNcgACx8uegxjIn1OABq8V42CYi8h8EI6OoR0xdL+eOkZu1SGnsOh1F2-62FWBMDentvgcq8heE+543RcLjEkFEVNSHs45LhU7dDj5mq-vfJhi+zNcE03Se0IqcwgS8neBPRMsMxHpjhhO5DLibmow9uLTGp0pYyfgCsttP1NLtFGN0ZwYLlrLQBryRkxjMq2FFYzLJ4maOwoAdJhjsnXatT8hdT2+MHqsbGo4BcwouQxCsJ50U+m7DPsZDGOt9NOifqkwotepdlGb2Yq51T5ghERzZAe5MGFME9NpuYA2HR8KwcfuFujyo7kPKfPFq4pZbjDCBCyZaPn8zPyTFWwE+EpjW2FE2tVZBImfP5cmeN0YI4tGrVarcIi7jxJBN-EFVGLmTtIV10MjL2LEHKxpXSnbIif3uBhaI0YtwgwCDDXw+hy3QvM7m2lHr1DUC9YQNbCEmu+GHZ-Dh+g+16Ajjg47zJjDLU6ZI+IQA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgZTABswBjdAewCcA6AB3PoDcwaTDzsIBiAFQHkA4gIAyAUQDaABgC6iUPVgBLdIvIA7OSACeiACwAmXdQDMANmMB2UwYAcu4wEYH+gDQgAHogdmj5yU9MATmMAVmNjfQcAXyi3NExsXAJiMio6BnJmGgYwNS4AYWE+PAkZTQVlVQ0kD0QbCwtqSX1TSXNAh0lJQIsQtx0EUycmyTNRm2abSOMYuIwsHHwiUgoaeiYWahy8-iExKVkaipV1TU8EGxsHE2ddc11dBwtAyP7EEN0Q6lMLfS6Ohy6QK6eqzEDxBZJZapNYZLJbWi5LglMT5HgAfQAgsJhOiigI8OjkJieJiSjwDuUOJVTjVzjYftQLA4JsZgm1eoFTG9BlMTL1wqNAiEGfowRDEksUqt0htsoi1NRFGp0GAoJQMFVYDxUAAjZEAdQAkjx8gAJdH8dEAVQAcvkSaIBHwAEpGgBaomQlsxACFKUdqSdqqBzgBaJ6SajCwLA5n+YxTYw8pzGKNWfT6YwPX4OEIi8XzSXJFZpdaZTbbJUqtUa4PavWGk3my18IkkzHogBqRtEBsJpP9ZUDSmDZ0QkX01AM1m6jjzbI+Ke86dMmezulz+ZshYSixLMNlFfluWrqvVmvUDd11EIilgKjUUCNNYv9YKZsxtoEonRAAUv1EYQA3kIMqnHBBOkCagWn0D4bDMIIJlMGweV6a5hTZIF9CsZoWV3SEpVLWE5QRU9lXPOstR1G87wfZVn1fKir2RfIXT4HFW3RI1bR4J0XRJI0+FtQlfT4Hh+AAWRAkBjnAulEDDTMvhCCxs0cO5QhZQIeVUqcggcYJVLMCIHFMAji2hGVy3hKsKNrS81GvW970fRjKMc2BkVETEXRbX0AE10VtTFJNKQ5QNHeTQ0U+wvjaSI7BCLpLE+XQeWsaC4JQsILAZBpolicEi33KyyzhSsFTPBz6xolz6KfF8PPfPBXQxQL0VdZBRBdGS5NpGKEDDdTqBCQwfhwl4nCXbQ6nzUbmkkZ5TCGHpLAs0rpXK0i7KYzy6rotympqqo8HQSgwFQABbLzzS-H9-0A4Dh0imkQ1qSDuhgtd4MQwJkL6WaEAsfwmiGXpcr+SxzKKiVNuIo9bKq+y32ovV6qOvbgzOi7rq8kpfP8oKQrCvqwIGj6QhZb5ksTTcWgaMbdJCLKFzuYJnnsQINqhLaSOPMjFRR5inIO1yGOO1H1Bxy6buRNr0Q6rqerJqKKfOLNGkkGxY30DpYxQgxmanYEnEnRMGQMHmiMPGzKvIrG0do8XGsd6Xztl-GgNENF21JclVbeiCGRsb5-vqSQRVGbp0qBqxrhQyafoCFprYPayKpPRUAFc1BIDA1SoRQAC9IBoptTQtK0eL4gQBJ4ISRJ9IcItk8n3vDcIpyzFpujgkGQYZFM2iMUJWgZ-6swZNOyv5pHT1z-PzyL0uIHLvBjUrrjiVJbte37ZvA7HBSEEMb41zXXRxkMkIfmTIHnGFaNJ4MYIek3HdYZK3mEbtrPqEXgXKAK8y6NgJn5C0HUSbhSpGrDusUJjfAmGEFamYLBAkBgMbwZ8ngg2BCPNkPwZ580RvbHOecgEgLXmAhWSsXTdV6i9NucCIJhnpt8Kwa5fhqW6MlTBXhvCmFGq0Z4aYFymDCMQ3+mdBYAIocvSgJdQH6hRD7DEO8ySiApEw-q8CLgoTDpcR4QQ9ZDBTPYUO3R-rBB+J8FCMM5h7h-rbGRVYIDzC7IoMAAB3a8FcWzV14vxQSwkBx+iPtFD6ZgTY3x6CzdorJh6qVGn8BCV8PjOEKo4wi6dtoCzcR4rxvj16bwCW2O0Dpa6ug9F6Q+Oj24QUBCYVonwB6RzzMlVCD9DBfDuPUMIdhIgXykS4naVV3GYE8T4vx4CibBVCjAkcQcT5DCjC8EEmSuj5l6CmCI0FEx4RwtrOCmYRkZzGaeCZqApnFJoS6dqQVlaMNbroiCqyTB3BQrGOMLNXAP28NcYxa5Yx5h6HrM5eT56KiuTcmZ3tfYaL3n2CJ6s6jWGjICLoSYr71FjlgrM1xwg-HqFcewvQxRfycTbGU2BpRVGoIwVAd4JkMXTlULgKK9FhmSgncGLw1IvBwk8FM6CjD9LMDYFmeYswhAhTQWlKx6WMuZZqJ8bL1AcocC8hpJ9uV-GoGZLkkcsyCt+CmVk0YeGGDsNK0IcrqAKrIEqpligWVqqsuy8Q+htUsN1WEKMXzBEglNX8rBoomSJkyjasadrKU5Nng6j16gHUMWIN5VEGJsS4nxISDRAd6m+sGk4UOjg8orSpvYUIeURVsmaZ8Eli4hj2sdcGFNT402qIRR2fNPrlmDSUpIIwBtMo4UnMKFMEiTZjUCEtfQlxko4WbUmxUSh21gHTWov2nYezIoLX2j6SlgTTlCFmZobI0HCofh0IR2twhpjUhsuCS66XJqZYQDdXb-ZaM5RBSw0Fb4vEjihUYVhJUTsZMGzWdg7BLQccVKluT5XLuoG+j96iOxIoND+k+lhjDRi5iDE1EwDD3wGIlIRC7lJJTaM+xVyaYVFLQ1iTiOat09tgfu84xaTAin5cyemm5uRA1SUIrSeZcE-EHbRp19HCk+KY3m79e7j79rytQS4kwJVX2zJHFM16mgIUFA+q4T642WRIS2+lDH5OdvQ7vHdWHlORPDIZa4t6Z2gbypEHZwmnhCJ1hp1oHQY2yrBGocgEA4CaDhs41YHGVMHq4WDRKnwUroP4cDPDwRQhX0Hl8l49q-7xec4pVS0EEoslSyBz4E7AXhA+EtbW+WKXZPM9I0ibAOCQGK6iyCZk8NHPQYZUIXcHA8gMKPcwVhbD2DNoV1xCoetcrCG5tclXkrVYy9a6M9XctNZ1gVsz8NRn5KqgAC1cktt5anJ5U2ZGpQwZqgZEuPTlxrIdYzzYuULN2os9RXd1SCPDFXqObbxXoamDwzbOAttYFr8H40kL-rI4W+10YXYfADotzgTDTCSitTcN8eTBFDg0cIeUyeAmZF907DtmpOwxhLX78AlkJfDH8G9nQdbGMlZKuCGV0EGtS3lNk-1Cc06hdVKWf3nYNXcidd2uMbpY4+nrKcowWa-BBBrkUGVLg7Z+BI3CtNuZHdi5Cshcil6F0UavGiKv2cMnU94GxBhVJBHHd06mc6DaLga2pCXlvrO3N1A7xAplviAg6PO7Xa4UyRC+B0DBExLajGkwl15fq1OBp8DrNBoavC316dO2d87mgWHT86lVbl1XvUz-2oEeGVp5klYOqbM0sHlujPmUI2tUtwZi9StIlnk2rqgMQMPQ19V3sTNmdcykcIit6M7hodgCeTgr2bofSGX2KjfZP7MjQzANGaBIwdAG9MGCZJJro5+Vp5Ur7JyZRTJ-DSnHwiIDWWQi892R5wU5iM9Z6Z+kKUYggA */ createMachine( { context: { ...DEFAULT_CONTEXT, ...initialContext }, @@ -134,7 +140,7 @@ export const createPureDatasetsSelectorStateMachine = ( }, SELECT_DATA_VIEW: { target: '#closed', - actions: ['selectDataView'], + actions: ['storeDataViewSelection'], }, }, }, @@ -143,8 +149,15 @@ export const createPureDatasetsSelectorStateMachine = ( }, }, selection: { - initial: 'single', + initial: 'validatingSelection', states: { + validatingSelection: { + always: [ + { cond: 'isDataViewSelection', target: 'dataView' }, + { cond: 'isAllDatasetSelection', target: 'all' }, + { cond: 'isSingleDatasetSelection', target: 'single' }, + ], + }, single: { on: { SELECT_ALL_LOGS_DATASET: { @@ -154,6 +167,10 @@ export const createPureDatasetsSelectorStateMachine = ( SELECT_DATASET: { actions: ['storeSingleSelection', 'notifySelectionChanged'], }, + SELECT_DATA_VIEW: { + actions: ['storeDataViewSelection', 'notifySelectionChanged'], + target: 'dataView', + }, }, }, all: { @@ -162,6 +179,25 @@ export const createPureDatasetsSelectorStateMachine = ( actions: ['storeSingleSelection', 'notifySelectionChanged'], target: 'single', }, + SELECT_DATA_VIEW: { + actions: ['storeDataViewSelection', 'notifySelectionChanged'], + target: 'dataView', + }, + }, + }, + dataView: { + on: { + SELECT_ALL_LOGS_DATASET: { + actions: ['storeAllSelection', 'notifySelectionChanged'], + target: 'all', + }, + SELECT_DATASET: { + actions: ['storeSingleSelection', 'notifySelectionChanged'], + target: 'single', + }, + SELECT_DATA_VIEW: { + actions: ['storeDataViewSelection', 'notifySelectionChanged'], + }, }, }, }, @@ -191,7 +227,14 @@ export const createPureDatasetsSelectorStateMachine = ( selection: AllDatasetSelection.create(), })), storeSingleSelection: assign((_context, event) => - 'dataset' in event ? { selection: SingleDatasetSelection.create(event.dataset) } : {} + event.type === 'SELECT_DATASET' + ? { selection: SingleDatasetSelection.create(event.selection) } + : {} + ), + storeDataViewSelection: assign((_context, event) => + event.type === 'SELECT_DATA_VIEW' + ? { selection: DataViewSelection.create(event.selection) } + : {} ), retrieveSearchFromCache: assign((context, event) => { if (event.type === 'CHANGE_PANEL' && 'panelId' in event) { @@ -228,12 +271,18 @@ export const createPureDatasetsSelectorStateMachine = ( } }), }, + guards: { + isDataViewSelection: (context) => isDataViewSelection(context.selection), + isAllDatasetSelection: (context) => + isDatasetSelection(context.selection) && context.selection.selectionType === 'all', + isSingleDatasetSelection: (context) => + isDatasetSelection(context.selection) && context.selection.selectionType === 'single', + }, } ); export const createDatasetsSelectorStateMachine = ({ initialContext, - onDataViewSelection, onDataViewsSearch, onDataViewsSort, onIntegrationsLoadMore, @@ -255,11 +304,6 @@ export const createDatasetsSelectorStateMachine = ({ loadMoreIntegrations: onIntegrationsLoadMore, relaodIntegrations: onIntegrationsReload, reloadUncategorized: onUncategorizedReload, - selectDataView: (_context, event) => { - if (event.type === 'SELECT_DATA_VIEW' && 'dataView' in event) { - return onDataViewSelection(event.dataView); - } - }, // Search actions searchIntegrations: (_context, event) => { if ('search' in event) { diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/types.ts b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/types.ts index 6ca09c9c2de078..0dd6f199c8fc7e 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/types.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/types.ts @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor'; import { SearchDataViews } from '../../../hooks/use_data_views'; import { DatasetSelection, - DatasetSelectionChange, DataViewSelection, + SelectionChange, } from '../../../../common/dataset_selection'; import { Dataset } from '../../../../common/datasets/models/dataset'; import { ReloadDatasets, SearchDatasets } from '../../../hooks/use_datasets'; @@ -22,7 +22,7 @@ import type { IHashedCache } from '../../../../common/hashed_cache'; import { DatasetsSelectorSearchParams, PanelId, TabId } from '../types'; export interface DefaultDatasetsSelectorContext { - selection: DatasetSelection; + selection: DatasetSelection | DataViewSelection; tabId: TabId; panelId: PanelId; searchCache: IHashedCache; @@ -70,10 +70,18 @@ export type DatasetsSelectorTypestate = value: 'selection'; context: DefaultDatasetsSelectorContext; } + | { + value: 'selection.validatingSelection'; + context: DefaultDatasetsSelectorContext; + } | { value: 'selection.single'; context: DefaultDatasetsSelectorContext; } + | { + value: 'selection.dataView'; + context: DefaultDatasetsSelectorContext; + } | { value: 'selection.all'; context: DefaultDatasetsSelectorContext; @@ -103,11 +111,11 @@ export type DatasetsSelectorEvent = } | { type: 'SELECT_DATASET'; - dataset: Dataset; + selection: Dataset; } | { type: 'SELECT_DATA_VIEW'; - dataView: DataViewListItem; + selection: DataViewDescriptor; } | { type: 'SELECT_ALL_LOGS_DATASET'; @@ -126,7 +134,6 @@ export type DatasetsSelectorEvent = export interface DatasetsSelectorStateMachineDependencies { initialContext?: Partial; - onDataViewSelection: DataViewSelection; onDataViewsSearch: SearchDataViews; onDataViewsSort: SearchDataViews; onIntegrationsLoadMore: LoadMoreIntegrations; @@ -135,7 +142,7 @@ export interface DatasetsSelectorStateMachineDependencies { onIntegrationsSort: SearchIntegrations; onIntegrationsStreamsSearch: SearchIntegrations; onIntegrationsStreamsSort: SearchIntegrations; - onSelectionChange: DatasetSelectionChange; + onSelectionChange: SelectionChange; onUncategorizedReload: ReloadDatasets; onUncategorizedSearch: SearchDatasets; onUncategorizedSort: SearchDatasets; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts index 125217e8decddf..e1db03600a1435 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/state_machine/use_dataset_selector.ts @@ -19,7 +19,6 @@ import { DatasetsSelectorStateMachineDependencies } from './types'; export const useDatasetSelector = ({ initialContext, - onDataViewSelection, onDataViewsSearch, onDataViewsSort, onIntegrationsLoadMore, @@ -36,7 +35,6 @@ export const useDatasetSelector = ({ const datasetsSelectorStateService = useInterpret(() => createDatasetsSelectorStateMachine({ initialContext, - onDataViewSelection, onDataViewsSearch, onDataViewsSort, onIntegrationsLoadMore, @@ -101,12 +99,16 @@ export const useDatasetSelector = ({ ); const selectDataset = useCallback( - (dataset) => datasetsSelectorStateService.send({ type: 'SELECT_DATASET', dataset }), + (dataset) => datasetsSelectorStateService.send({ type: 'SELECT_DATASET', selection: dataset }), [datasetsSelectorStateService] ); const selectDataView = useCallback( - (dataView) => datasetsSelectorStateService.send({ type: 'SELECT_DATA_VIEW', dataView }), + (dataViewDescriptor) => + datasetsSelectorStateService.send({ + type: 'SELECT_DATA_VIEW', + selection: dataViewDescriptor, + }), [datasetsSelectorStateService] ); diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_view_menu_item.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_view_menu_item.tsx new file mode 100644 index 00000000000000..04388f4f479ec0 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_view_menu_item.tsx @@ -0,0 +1,36 @@ +/* + * 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 React from 'react'; +import { EuiIcon, EuiToolTip } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor'; +import { openDiscoverLabel } from '../constants'; + +interface DataViewMenuItemProps { + dataView: DataViewDescriptor; +} + +const rightSpacing = css` + margin-right: ${euiThemeVars.euiSizeS}; +`; + +export const DataViewMenuItem = ({ dataView }: DataViewMenuItemProps) => { + if (dataView.dataType === 'logs') { + return {dataView.name}; + } + + return ( + <> + {dataView.name} + + + + + ); +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx deleted file mode 100644 index 401a1728f9a7ce..00000000000000 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/data_views_panel_title.tsx +++ /dev/null @@ -1,21 +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 React from 'react'; -import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { dataViewsLabel, openDiscoverLabel } from '../constants'; - -export const DataViewsPanelTitle = () => { - return ( - - {dataViewsLabel} - - {openDiscoverLabel} - - - ); -}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/selector_popover.tsx similarity index 55% rename from x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx rename to x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/selector_popover.tsx index 6324ffe0fe85e6..15ac903bb75817 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/sub_components/selector_popover.tsx @@ -15,29 +15,29 @@ import { useIsWithinBreakpoints, } from '@elastic/eui'; import { PackageIcon } from '@kbn/fleet-plugin/public'; -import { DatasetSelection } from '../../../../common/dataset_selection'; +import { + DatasetSelection, + DataViewSelection, + isDataViewSelection, +} from '../../../../common/dataset_selection'; import { DATA_VIEW_POPOVER_CONTENT_WIDTH, POPOVER_ID } from '../constants'; import { getPopoverButtonStyles } from '../utils'; const panelStyle = { width: DATA_VIEW_POPOVER_CONTENT_WIDTH }; -interface DatasetsPopoverProps extends Omit { +interface SelectorPopoverProps extends Omit { children: React.ReactNode; onClick: () => void; - selection: DatasetSelection['selection']; + selection: DatasetSelection | DataViewSelection; } -export const DatasetsPopover = ({ +export const SelectorPopover = ({ children, onClick, selection, ...props -}: DatasetsPopoverProps) => { - const { iconType, parentIntegration } = selection.dataset; - const title = selection.dataset.getFullTitle(); +}: SelectorPopoverProps) => { const isMobile = useIsWithinBreakpoints(['xs', 's']); - const buttonStyles = getPopoverButtonStyles({ fullWidth: isMobile }); - const hasIntegration = typeof parentIntegration === 'object'; return ( - {iconType ? ( - - ) : hasIntegration ? ( - - ) : null} - {title} + {isDataViewSelection(selection) ? ( + + ) : ( + + )} } panelPaddingSize="none" @@ -83,3 +76,36 @@ export const DatasetsPopover = ({ ); }; + +const DataViewPopoverContent = ({ + dataViewSelection, +}: { + dataViewSelection: DataViewSelection; +}) => { + const { name } = dataViewSelection.selection.dataView; + + return {name}; +}; + +const DatasetPopoverContent = ({ datasetSelection }: { datasetSelection: DatasetSelection }) => { + const { iconType, parentIntegration } = datasetSelection.selection.dataset; + const title = datasetSelection.selection.dataset.getFullTitle(); + const hasIntegration = typeof parentIntegration === 'object'; + + return ( + <> + {iconType ? ( + + ) : hasIntegration ? ( + + ) : null} + {title} + + ); +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/types.ts b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/types.ts index ebd0a802caf520..5076f382f2fcb2 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/types.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/dataset_selector/types.ts @@ -6,14 +6,14 @@ */ import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu'; -import { DataViewListItem } from '@kbn/data-views-plugin/common'; import type { DatasetSelection, - DatasetSelectionChange, + SelectionChange, DataViewSelection, } from '../../../common/dataset_selection'; import { SortOrder } from '../../../common/latest'; import { Dataset, Integration, IntegrationId } from '../../../common/datasets'; +import { DataViewDescriptor } from '../../../common/data_views/models/data_view_descriptor'; import { LoadDatasets, ReloadDatasets, SearchDatasets } from '../../hooks/use_datasets'; import { LoadMoreIntegrations, @@ -35,9 +35,9 @@ export interface DatasetSelectorProps { /* Any error occurred to show when the user preview the generic data streams */ datasetsError: Error | null; /* The current selection instance */ - datasetSelection: DatasetSelection; + datasetSelection: DatasetSelection | DataViewSelection; /* The available data views list */ - dataViews: DataViewListItem[] | null; + dataViews: DataViewDescriptor[] | null; /* Any error occurred to show when the user preview the data views */ dataViewsError: Error | null; /* url props to navigate to discover ES|QL */ @@ -55,8 +55,6 @@ export interface DatasetSelectorProps { isEsqlEnabled: boolean; /* Triggered when retrying to load the data views */ onDataViewsReload: ReloadDataViews; - /* Triggered when selecting a data view */ - onDataViewSelection: DataViewSelection; /* Triggered when the data views tab is selected */ onDataViewsTabClick: LoadDataViews; /* Triggered when we reach the bottom of the integration list and want to load more */ @@ -77,7 +75,7 @@ export interface DatasetSelectorProps { /* Triggered when the uncategorized tab is selected */ onUncategorizedTabClick: LoadDatasets; /* Triggered when the selection is updated */ - onSelectionChange: DatasetSelectionChange; + onSelectionChange: SelectionChange; } export type PanelId = typeof INTEGRATIONS_PANEL_ID | IntegrationId; @@ -101,4 +99,4 @@ export type ChangePanelHandler = ({ panelId }: { panelId: EuiContextMenuPanelId export type DatasetSelectionHandler = (dataset: Dataset) => void; -export type DataViewSelectionHandler = (dataView: DataViewListItem) => void; +export type DataViewSelectionHandler = (dataView: DataViewDescriptor) => void; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/controller/create_controller.ts b/x-pack/plugins/observability_solution/logs_explorer/public/controller/create_controller.ts index cf7a9c2a07f8a9..59aad01a0b3884 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/controller/create_controller.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/controller/create_controller.ts @@ -35,7 +35,7 @@ interface Dependencies { type InitialState = LogsExplorerPublicStateUpdate; export const createLogsExplorerControllerFactory = - ({ core, plugins: { data } }: Dependencies) => + ({ core, plugins }: Dependencies) => async ({ customizations = {}, initialState, @@ -43,6 +43,8 @@ export const createLogsExplorerControllerFactory = customizations?: LogsExplorerCustomizations; initialState?: InitialState; }): Promise => { + const { data, dataViews, discover } = plugins; + const datasetsClient = new DatasetsService().start({ http: core.http, }).client; @@ -68,6 +70,7 @@ export const createLogsExplorerControllerFactory = const machine = createLogsExplorerControllerStateMachine({ datasetsClient, + plugins: { dataViews, discover }, initialContext, query: discoverServices.data.query, toasts: core.notifications.toasts, diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_dataset_selector.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_dataset_selector.tsx index 302f551fa5890d..b298a2363a9833 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_dataset_selector.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_dataset_selector.tsx @@ -6,7 +6,6 @@ */ import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DiscoverStart } from '@kbn/discover-plugin/public'; import React from 'react'; import { DatasetSelector } from '../components/dataset_selector'; import { DatasetsProvider, useDatasetsContext } from '../hooks/use_datasets'; @@ -55,7 +54,6 @@ export const CustomDatasetSelector = withProviders(({ logsExplorerControllerStat isLoading: isLoadingDataViews, loadDataViews, reloadDataViews, - selectDataView, searchDataViews, sortDataViews, } = useDataViewsContext(); @@ -77,7 +75,6 @@ export const CustomDatasetSelector = withProviders(({ logsExplorerControllerStat isLoadingIntegrations={isLoadingIntegrations} isLoadingUncategorized={isLoadingUncategorized} isSearchingIntegrations={isSearchingIntegrations} - onDataViewSelection={selectDataView} onDataViewsReload={reloadDataViews} onDataViewsSearch={searchDataViews} onDataViewsSort={sortDataViews} @@ -103,20 +100,18 @@ export default CustomDatasetSelector; export type CustomDatasetSelectorBuilderProps = CustomDatasetSelectorProps & { datasetsClient: IDatasetsClient; dataViews: DataViewsPublicPluginStart; - discover: DiscoverStart; }; function withProviders(Component: React.FunctionComponent) { return function ComponentWithProviders({ datasetsClient, dataViews, - discover, logsExplorerControllerStateService, }: CustomDatasetSelectorBuilderProps) { return ( - + diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx index faa8c8d4a51eed..ac5eb2d6d81c6a 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx @@ -43,8 +43,7 @@ export const createLogsExplorerProfileCustomizations = ...plugins, ...discoverServices, }; - const { data, dataViews, discover, navigation, unifiedSearch } = pluginsWithOverrides; - + const { data, dataViews, navigation, unifiedSearch } = pluginsWithOverrides; service.send('RECEIVED_STATE_CONTAINER', { discoverStateContainer: stateContainer }); /** @@ -67,7 +66,6 @@ export const createLogsExplorerProfileCustomizations = diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_data_views.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_data_views.tsx index c6283aca4b7f9c..a71375ff2ea25c 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_data_views.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_data_views.tsx @@ -9,13 +9,11 @@ import { useCallback } from 'react'; import createContainer from 'constate'; import { useInterpret, useSelector } from '@xstate/react'; import { DataViewListItem, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DiscoverStart } from '@kbn/discover-plugin/public'; import { SortOrder } from '../../common/latest'; import { createDataViewsStateMachine } from '../state_machines/data_views'; interface DataViewsContextDeps { dataViewsService: DataViewsPublicPluginStart; - discoverService: DiscoverStart; } export interface SearchDataViewsParams { @@ -28,11 +26,10 @@ export type SearchDataViews = (params: SearchDataViewsParams) => void; export type LoadDataViews = () => void; export type ReloadDataViews = () => void; -const useDataViews = ({ dataViewsService, discoverService }: DataViewsContextDeps) => { +const useDataViews = ({ dataViewsService }: DataViewsContextDeps) => { const dataViewsStateService = useInterpret(() => createDataViewsStateMachine({ dataViews: dataViewsService, - discover: discoverService, }) ); @@ -61,15 +58,6 @@ const useDataViews = ({ dataViewsService, discoverService }: DataViewsContextDep [dataViewsStateService] ); - const selectDataView: DataViewSelectionHandler = useCallback( - (dataView) => - dataViewsStateService.send({ - type: 'SELECT_DATA_VIEW', - dataView, - }), - [dataViewsStateService] - ); - const sortDataViews: SearchDataViews = useCallback( (searchParams) => dataViewsStateService.send({ @@ -96,7 +84,6 @@ const useDataViews = ({ dataViewsService, discoverService }: DataViewsContextDep loadDataViews, reloadDataViews, searchDataViews, - selectDataView, sortDataViews, }; }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_dataset_selection.ts b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_dataset_selection.ts index 68a14ad838f2c5..67610cfa6e85ce 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_dataset_selection.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_dataset_selection.ts @@ -7,7 +7,7 @@ import { useSelector } from '@xstate/react'; import { useCallback } from 'react'; -import { DatasetSelectionChange } from '../../common/dataset_selection'; +import { SelectionChange } from '../../common/dataset_selection'; import { LogsExplorerControllerStateService } from '../state_machines/logs_explorer_controller'; export const useDatasetSelection = ( @@ -17,7 +17,7 @@ export const useDatasetSelection = ( return state.context.datasetSelection; }); - const handleDatasetSelectionChange: DatasetSelectionChange = useCallback( + const handleDatasetSelectionChange: SelectionChange = useCallback( (data) => { logsExplorerControllerStateService.send({ type: 'UPDATE_DATASET_SELECTION', data }); }, diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx index d7b4689b7194d7..54b7c5e9750663 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/hooks/use_esql.tsx @@ -5,7 +5,11 @@ * 2.0. */ -import { DatasetSelection } from '../../common/dataset_selection'; +import { + DatasetSelection, + DataViewSelection, + isDatasetSelection, +} from '../../common/dataset_selection'; import { useKibanaContextForPlugin } from '../utils/use_kibana'; export interface DiscoverEsqlUrlProps { @@ -19,7 +23,7 @@ export interface UseEsqlResult { } interface EsqlContextDeps { - datasetSelection: DatasetSelection; + datasetSelection: DatasetSelection | DataViewSelection; } export const useEsql = ({ datasetSelection }: EsqlContextDeps): UseEsqlResult => { @@ -31,7 +35,11 @@ export const useEsql = ({ datasetSelection }: EsqlContextDeps): UseEsqlResult => const discoverLinkParams = { query: { - esql: `from ${datasetSelection.selection.dataset.name} | limit 10`, + esql: `from ${ + isDatasetSelection(datasetSelection) + ? datasetSelection.selection.dataset.name + : datasetSelection.selection.dataView.title + } | limit 10`, }, }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/state_machine.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/state_machine.ts index 60bc3e21e9ff69..fb13f55a598f86 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/state_machine.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/state_machine.ts @@ -5,11 +5,10 @@ * 2.0. */ -import { DataViewListItem, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { isError } from 'lodash'; import { assign, createMachine } from 'xstate'; -import { DiscoverStart } from '@kbn/discover-plugin/public'; -import { parseDataViewListItem } from '../../../utils/parse_data_view_list_item'; +import { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor'; import { createComparatorByField } from '../../../utils/comparator_by_field'; import { createDefaultContext } from './defaults'; import type { @@ -25,7 +24,7 @@ export const createPureDataViewsStateMachine = ( ) => createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVA1AlmA7rAHQCuAdjhejqgDY4BekAxADIDyAgsgPrKcAVTjywBJAKIB1AMoBtAAwBdRKAAOAe1g5q6sipAAPRABYAzKaIB2a6bOWArABoQAT0QBaAJzyi9y8YAmAEYADgcAX3DnNExcAmJadVQISihmCF0wIkoAN3UAayyY7DxCIkTk1IRc9QBjDBxdBUVm-Q0tHT0kQxNjSyJPP3lTIICnV0RLTyJ5eUsQ0ftI6IwS+PKklLI0sAAnXfVdolVaDAAzQ4BbImK4soqtqGqyPPrO5tbu9u1GrtAjBAANgcRFMDmcbgQIPsIQCw0WyxAt1KCU2kGyEFoYGY0nEnAASgBhAASfEEwjEUjkSjamh+un0AOsgN8nksgPsgLGEJMHKIgNCgRC9mMC2siOR6we6JwmOx0nY+IEZKEIgkMk+ajpnUZJk8ISIAQCHK540hpgCBoWgOM8lNEtWd1RyRlcpx4lY4kJyv4qspkk1IG+Ou6AMB8gNfkCoXBEwQ9gtM1GgzZ+qC4qiSMdKI2LogRAgYAARupyLUwNIwKhdrUABapSWEd0Ekkqinq6nKL7a366hBBeT2ez8zncuMJ6ZzEX2VNiywO2I56X5wslssVqs1+vbRuwZgGWCYdBZVBnY+7AAUplmAEpmLvc4WV8XS2Ry5Xq3WG9n4oHg73Q0mEIDXDYwwLMexLUBMEeQQcwLE8II0wCYxARCcMpwXNZ7k2VIADFUBwLEIGYfEPS4XhfXbKk-x7BlAKBaDfACSxbDBM0PACUFPGMBMEURMh1ELeBul3WkOgA-5EFMYUrBsOwOIQdwUK4oYRjGLCnVICgqBoegmAgcT6T+HoEDAlkxnmRZYPcIIhxmSx4Q0zMHweVIjJDKSzNtIgQlmKyx0hZThxCHi+OclZFylNFDO7CT6K83ifCNE1AsQFCglBIIbTtCKsyinC8wxLEPMk0zARtUF7Ds01YN44dAj8OFglFBTNKXGKCxfdcPy3b8CpErV4pMgEBztUEVJjRSzEyzxLOaoJWvY9rosqbYCKIyBSoS0y5q4nLarjZT+j8BZnMiIA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVA1AlmA7rAHQCuAdjhejqgDY4BekAxADIDyAgsgPrKcAVTjywBJAKIB1AMoBtAAwBdRKAAOAe1g5q6sipAAPRACYALMaIA2AIwB2awA5bAVgA0IAJ6JrzgMxFfeQdnAE5fFwBfCPc0TFwCYlp1VAhKKGYIXTAiSgA3dQBrbNjsPEIiJJS0hDz1AGMMHF0FRRb9DS0dPSRDRFNneSJnY1tfU3C3T29jEKJ5AdsHa2NnKJiMUoSK5NSydLAAJwP1A6JVWgwAMxOAWyIS+PLK3agasnyGrpa2no7tJu6oCMCDMzjmIzGE3cXgQYwcREcvlCEzWIAeZUSO0gOQgtDAzGk4k4ACUAMIACT4gmEYikciU7U0-10+mBIUWCPkPksK2hiF8jiIDmF8mMQV8EsltlR6K2z2xOFx+Ok7GJAipQhEEhkPzUTK6rMQliWRFsIWNLj5IIcFnGpgcIWcDklUplG0emJSCqVBPErHEpPV-E1tMkupAfwNPWBtntRBC5vkYUtUwQ1iTCNM1hCI3FLul0TR7ox2y9ECIEDAACN1OQ6mBpGBUAc6gALNKywi+kkUjU07X05S-fUAw1piGmxwpmG+EZWZzprOWZcryy+N1xEvy8uVmt1htNlvtvad2DMAywTDobKoS7Xg4ACkC8gAlMxT6XKzvq7WyPXG82bYdsWCThpGo7RogITCkKITyDykwwsY1j+KMljsiE1irsu66Fh+zxpAAYqgOB4hAzDEn6XC8MG-Z0mBI4spBCDjNYRArL4cEIVapguPGLjGJY8hLiuBaFmQ6iVvAPSnoynQQUC3g2kK4zTogAC0lhgsEDjyLYMxYauuHrJuWzkJQ-x0IwkBycygK9CCQmmksvKpsEQwbpsTw7GktlRopIKBCpUKpsYxj+OmopCSJK4hJ5HqfjZw7yUxAVrqh5pOIhJhsZYWY5rYeaunhIHeWWOJ4n5CkOcEpimuhfjLNlIJzpx2a5s6+bxVuWLfnuf4HoBx5QLJyV2WO1iIqaphCcmzXIWx9hIjFRndXKPl7MRpFJXqKX2cCZh1YJzjcam5i2EQ9qqasUQREAA */ context: initialContext, preserveActionOrder: true, predictableActionArguments: true, @@ -125,22 +124,13 @@ export const createPureDataViewsStateMachine = ( export interface DataViewsStateMachineDependencies { initialContext?: DefaultDataViewsContext; dataViews: DataViewsPublicPluginStart; - discover: DiscoverStart; } export const createDataViewsStateMachine = ({ initialContext, dataViews, - discover, }: DataViewsStateMachineDependencies) => createPureDataViewsStateMachine(initialContext).withConfig({ - actions: { - navigateToDiscoverDataView: (_context, event) => { - if (event.type === 'SELECT_DATA_VIEW' && 'dataView' in event) { - discover.locator?.navigate({ dataViewId: event.dataView.id }); - } - }, - }, services: { loadDataViews: (context) => { const searchParams = context.search; @@ -148,16 +138,16 @@ export const createDataViewsStateMachine = ({ ? Promise.resolve(context.cache.get(searchParams)) : dataViews .getIdsWithTitle() - .then((views) => views.map(parseDataViewListItem)) + .then((views) => views.map(DataViewDescriptor.create)) .then((views) => searchDataViews(views, searchParams)); }, }, }); -const searchDataViews = (dataViews: DataViewListItem[], search: DataViewsSearchParams) => { +const searchDataViews = (dataViews: DataViewDescriptor[], search: DataViewsSearchParams) => { const { name, sortOrder } = search; return dataViews .filter((dataView) => Boolean(dataView.name?.includes(name ?? ''))) - .sort(createComparatorByField('name', sortOrder)); + .sort(createComparatorByField('name', sortOrder)); }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/types.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/types.ts index a2f369133521c4..da065115f4a3aa 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/types.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/data_views/src/types.ts @@ -5,7 +5,7 @@ * 2.0. */ import { DoneInvokeEvent } from 'xstate'; -import type { DataViewListItem } from '@kbn/data-views-plugin/common'; +import { DataViewDescriptor } from '../../../../common/data_views/models/data_view_descriptor'; import type { IHashedCache } from '../../../../common/hashed_cache'; import { SortOrder } from '../../../../common/latest'; @@ -15,7 +15,7 @@ export interface DataViewsSearchParams { } export interface WithCache { - cache: IHashedCache; + cache: IHashedCache; } export interface WithSearch { @@ -23,8 +23,8 @@ export interface WithSearch { } export interface WithDataViews { - dataViewsSource: DataViewListItem[]; - dataViews: DataViewListItem[]; + dataViewsSource: DataViewDescriptor[]; + dataViews: DataViewDescriptor[]; } export interface WithNullishDataViews { @@ -86,10 +86,6 @@ export type DataViewsEvent = | { type: 'RELOAD_DATA_VIEWS'; } - | { - type: 'SELECT_DATA_VIEW'; - dataView: DataViewListItem; - } | { type: 'SEARCH_DATA_VIEWS'; search: DataViewsSearchParams; @@ -98,4 +94,4 @@ export type DataViewsEvent = type: 'SORT_DATA_VIEWS'; search: DataViewsSearchParams; } - | DoneInvokeEvent; + | DoneInvokeEvent; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/datasets/src/state_machine.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/datasets/src/state_machine.ts index 083c4e8720490a..0f0bad0e6b2f79 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/datasets/src/state_machine.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/datasets/src/state_machine.ts @@ -20,7 +20,7 @@ import type { export const createPureDatasetsStateMachine = ( initialContext: DefaultDatasetsContext = createDefaultContext() ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgOgFcA7AS1PRNQBsSAvSAYgBkB5AQWQH1k2AVNgMoBRXgIDaABgC6iUAAcA9rBIUFRWSAAeiAGwBmAJx4DAdh0BWPXvMAWAIzn9BgDQgAnogAcdvDYP+dACYdfQk9e08AX0jXNExsXDwqBVQIMigGCDUwPDIANwUAaxy4rBx8ZNT0hHyFAGMMEjVJKRaNRWVVdSQtXUNjM0tre0dDVw8EG09A339PHU8wwMWw6NiMMsTKtKIMsAAnfYV9vDkqDAAzY4BbPFKEipSdqBqiAoaulraejpUm7tA2gQdhBMwkEhMJnMjhseh0Ek8nnGiEC5iM5hBJmm+k8BimwTWIHu5SST3SDGEbAASgBhAAS3D4ghE4mk7SUfzUGiBOhMNjwJnC4Vh8J0eJsyOBnnMxjsFmW5m8JjsnhshOJWzJuwpLCpvEZ-GEom+8g5XW5iAMEiMsL5NnhWMChkCksxeB0NkC4MVCz0nj5OnVGwepNSjEptIZPENLJNIF+5p6QLlEl8ljsCvCEjlC0lXr0sosiNhqokapiRODJO24d1+ujzONbJ+Zv+FuB8LTegz0qzOaR7kQVk8AsFgQM03HJitUQrGvwEDAACMFMQ6mABGBUPs6gALdLzilCan0g2N1kyFudNtJxA2cF4QJ2KbdsKQq0uwfA++zfwihxYgigZzlWiSLiua4bluO77rsh6aLAmDoDkqAXMh+wABR6OCACUDDzng4GrkQ66btue4HqBsBxgmN6An0RimBYVi2A4Th5nCvhyrY8yeOEKoEiB8TVlqUAAGKoCQVCMFSQisBwZ5Ghe7LXlyt7AqCeDgpC0IenCCIDhMXpGNmdhmKiegmIEL56NEFZEAoi7wD084qZyAK9AgAC0OiSj5QbCYkxBkH81B0JAbmJvRkwGAWcr3jYJjWjYGJ2C4X7TFp4LgiqIIToKJgBZsjxVLskV0Z5uL8kEfgpYY-o2BKX5PiZBgYiliWNRIHqzusgUlYuEDlWp0WepK5hOlptjTcs07deWfXFYRy7EaR0EUXBVHDR5QKWamQpWW1VndUErptcY-gGPoyp8osfJFSG2zpBJUkRVe7ntnY3YjsqCJTAYcqovMrpingaIYo4V0pXx912UAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBECGAXVsztgOgFcA7AS1PRNQBsSAvSAYgBkB5AQWQH1k2AVNgMoBRXgIDaABgC6iUAAcA9rBIUFRWSAAeiAGwBmAJx4DAdh0BWHQCZzAGhABPRAA4AjHnMBfT-bSZsuHhUCqgQZFAMEGpgeGQAbgoA1jF+WDj4waHhCPEKAMYYJGqSUiUaisqq6khauobGZpY29k4IACzOVh7evhhpgZlhRBFgAE6jCqN4clQYAGaTALZ4qQEZIUNQOUQJBVUlZTUVKkXVoNoIrlddEhImbs2OiDZG5lcGEm1mrh9fPSCrdJBDbhBjCNgAJQAwgAJbh8QQicTScpKE5qDQXHQmNp4Ex6Np6Ex2J6XZzmYyuHTUmm0-T-QEDEHDMEsCG8eH8YSiQ7yNFVTGID5GQnElqIK4mPA6NpWK56CQGcxWPTOO4MvprYGhRjg6FwnhcpG8kDHAU1C5UiR4NrmPSuR6tKwSPSUunuvQa-xAwa6tkcw2Inkoo7806Cy46a22+2OxB6VV4-Girw+AGaoEQMAAIwUxDyYAEYFQozyAAtwozYGChJDYZyg8iZKHKuGLYg2rc8HKOinxZdO9001W8Fnc-nC8XSxXhlWGJpYJh0DFUHNl6MABQKiQASgYI7HeaIBaLJfLlYzuBNZrb5zqRlMFmsJKdeh0Nqp7rpXv66yywwAMVQEgqEYCEhFYDgG25JtUVbDF20ua48Fue4HRfZ5FRQ95Pm+X4TG8NMiAULN4BqKs4PRM5agQABaHR+3on8tWIMgTmoOhIEo8073aAxXSpTtrDaAxRLEtp+06ZifWZKBuNvGjnAMXFn37OUjEVZVVU0lU1QI4dLz-LMIHkhDeNlfstJQ8w2h0ZxiWkwJDwnU9pwvb1cFM6iLiJa0CSJDDLgMHRHKM8IgJAriWyoiNXHtZw8VcRUAv7KlXiVXSdO0-TvCAA */ createMachine( { context: initialContext, diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/notifications.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/notifications.ts index f1c6240aaa40e6..1a9e287ee50ebb 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/notifications.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/notifications.ts @@ -22,10 +22,16 @@ export const createDatasetSelectionRestoreFailedNotifier = (toasts: IToasts) => export const createCreateDataViewFailedNotifier = (toasts: IToasts) => () => toasts.addWarning({ - title: i18n.translate('xpack.logsExplorer.datasetSelection.createDataViewFailedToastTitle', { - defaultMessage: "We couldn't create a data view for your selection.", - }), - text: i18n.translate('xpack.logsExplorer.datasetSelection.createDataViewFailedToastMessage', { - defaultMessage: 'We switched to "All log datasets" as the default selection.', - }), + title: i18n.translate( + 'xpack.logsExplorer.datasetSelection.createAdHocDataViewFailedToastTitle', + { + defaultMessage: "We couldn't create a data view for your selection.", + } + ), + text: i18n.translate( + 'xpack.logsExplorer.datasetSelection.createAdHocDataViewFailedToastMessage', + { + defaultMessage: 'We switched to "All log datasets" as the default selection.', + } + ), }); diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/data_view_service.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/data_view_service.ts index 2d16297eb3d1aa..0caa5659ba4b7c 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/data_view_service.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/data_view_service.ts @@ -5,10 +5,11 @@ * 2.0. */ +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { InvokeCreator } from 'xstate'; import { LogsExplorerControllerContext, LogsExplorerControllerEvent } from '../types'; -export const createAndSetDataView = +export const createAdHocDataView = (): InvokeCreator => async (context) => { if (!('discoverStateContainer' in context)) return; @@ -26,3 +27,23 @@ export const createAndSetDataView = */ discoverStateContainer.actions.setDataView(dataView); }; + +export const changeDataView = + ({ + dataViews, + }: { + dataViews: DataViewsPublicPluginStart; + }): InvokeCreator => + async (context) => { + if (!('discoverStateContainer' in context)) return; + const { discoverStateContainer } = context; + + // We need to manually retrieve the data view and force a set and change on the state container + // to guarantee the correct update on the data view selection and avoid a race condition + // when updating the control panels. + const nextDataView = await dataViews.get(context.datasetSelection.toDataviewSpec().id, false); + if (nextDataView.id) { + await discoverStateContainer.actions.onChangeDataView(nextDataView.id); + } + discoverStateContainer.actions.setDataView(nextDataView); + }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/discover_service.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/discover_service.ts index b4644cb635d4ab..d67c74097d701b 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/discover_service.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/discover_service.ts @@ -5,8 +5,11 @@ * 2.0. */ +import { DiscoverStart } from '@kbn/discover-plugin/public'; import { isEmpty } from 'lodash'; import { ActionFunction, actions, InvokeCallback } from 'xstate'; +import { getDiscoverColumnsWithFallbackFieldsFromDisplayOptions } from '../../../../utils/convert_discover_app_state'; +import { DataViewSelection, isDataViewSelection } from '../../../../../common/dataset_selection'; import { getChartDisplayOptionsFromDiscoverAppState, getDiscoverAppStateFromContext, @@ -106,3 +109,33 @@ export const updateDiscoverAppStateFromContext: ActionFunction< context.discoverStateContainer.appState.update(getDiscoverAppStateFromContext(context)); }; + +export const redirectToDiscoverAction = + ( + discover: DiscoverStart + ): ActionFunction => + (context, event) => { + if (event.type === 'UPDATE_DATASET_SELECTION' && isDataViewSelection(event.data)) { + return redirectToDiscover({ context, datasetSelection: event.data, discover }); + } + }; + +export const redirectToDiscover = ({ + context, + datasetSelection, + discover, +}: { + discover: DiscoverStart; + context: LogsExplorerControllerContext; + datasetSelection: DataViewSelection; +}) => { + return discover.locator?.navigate({ + breakdownField: context.chart.breakdownField ?? undefined, + columns: getDiscoverColumnsWithFallbackFieldsFromDisplayOptions(context), + dataViewSpec: datasetSelection.selection.dataView.toDataviewSpec(), + filters: context.filters, + query: context.query, + refreshInterval: context.refreshInterval, + timeRange: context.time, + }); +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/selection_service.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/selection_service.ts index caf51c5d015b62..281b4468692766 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/selection_service.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/services/selection_service.ts @@ -5,59 +5,103 @@ * 2.0. */ +import { DiscoverStart } from '@kbn/discover-plugin/public'; import { InvokeCreator } from 'xstate'; import { Dataset } from '../../../../../common/datasets'; -import { SingleDatasetSelection } from '../../../../../common/dataset_selection'; +import { + isDataViewSelection, + isUnresolvedDatasetSelection, + SingleDatasetSelection, + UnresolvedDatasetSelection, +} from '../../../../../common/dataset_selection'; import { IDatasetsClient } from '../../../../services/datasets'; import { LogsExplorerControllerContext, LogsExplorerControllerEvent } from '../types'; +import { redirectToDiscover } from './discover_service'; -interface LogsExplorerControllerUrlStateDependencies { +interface LogsExplorerControllerSelectionServiceDeps { datasetsClient: IDatasetsClient; + discover: DiscoverStart; } -export const validateSelection = +export const initializeSelection = ({ datasetsClient, - }: LogsExplorerControllerUrlStateDependencies): InvokeCreator< + discover, + }: LogsExplorerControllerSelectionServiceDeps): InvokeCreator< LogsExplorerControllerContext, LogsExplorerControllerEvent > => (context) => async (send) => { - const unresolvedIntegrationName = - context.datasetSelection.selection.dataset.parentIntegration?.name; - const unresolvedDatasetName = context.datasetSelection.selection.dataset.name; + /** + * First validation. + * If the selection is a data view which is not of logs type, redirect to Discover. + */ + if ( + isDataViewSelection(context.datasetSelection) && + context.datasetSelection.selection.dataView.isUnknownDataType() + ) { + return redirectToDiscover({ context, datasetSelection: context.datasetSelection, discover }); + } - if (context.datasetSelection.selectionType !== 'unresolved' || !unresolvedIntegrationName) { - return send('LISTEN_TO_CHANGES'); + /** + * Second validation. + * If the selection is a data view, initialize it. + */ + if (isDataViewSelection(context.datasetSelection)) { + return send('INITIALIZE_DATA_VIEW'); } - try { - const { items } = await datasetsClient.findIntegrations({ - nameQuery: unresolvedIntegrationName, - }); + /** + * Third validation. + * If the selection is an unresolved dataset, perform a look up against integrations.. + */ + if (isUnresolvedDatasetSelection(context.datasetSelection)) { + try { + const selection = await lookupUnresolvedDatasetSelection(context.datasetSelection, { + datasetsClient, + }); - // 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 (selection !== null) { + return send({ type: 'INITIALIZE_DATASET', data: selection }); + } + } catch { + return send('DATASET_SELECTION_RESTORE_FAILURE'); } + } - // 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'); - } + /** + * For any remaining case, initialize the current dataset selection + */ + return send('INITIALIZE_DATASET'); + }; - const dataset = Dataset.create(targetDataset, installedIntegration); - const datasetSelection = SingleDatasetSelection.create(dataset); +const lookupUnresolvedDatasetSelection = async ( + datasetSelection: UnresolvedDatasetSelection, + { datasetsClient }: Pick +) => { + const nameQuery = datasetSelection.selection.dataset.parentIntegration?.name; - send({ type: 'UPDATE_DATASET_SELECTION', data: datasetSelection }); - } catch (error) { - return send('DATASET_SELECTION_RESTORE_FAILURE'); - } - }; + if (nameQuery) { + return null; + } + + const { items } = await datasetsClient.findIntegrations({ nameQuery }); + + // 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 null; + } + + // 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 === nameQuery); + if (!targetDataset) { + return null; + } + + const dataset = Dataset.create(targetDataset, installedIntegration); + return SingleDatasetSelection.create(dataset); +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/state_machine.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/state_machine.ts index 3c244a73bbd5b1..3354fbbcd7a025 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/state_machine.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/state_machine.ts @@ -8,8 +8,13 @@ import { IToasts } from '@kbn/core/public'; import { QueryStart } from '@kbn/data-plugin/public'; import { actions, createMachine, interpret, InterpreterFrom, raise } from 'xstate'; +import { LogsExplorerStartDeps } from '../../../types'; import { ControlPanelRT } from '../../../../common/control_panels'; -import { isDatasetSelection } from '../../../../common/dataset_selection'; +import { + AllDatasetSelection, + isDatasetSelection, + isDataViewSelection, +} from '../../../../common/dataset_selection'; import { IDatasetsClient } from '../../../services/datasets'; import { DEFAULT_CONTEXT } from './defaults'; import { @@ -21,14 +26,15 @@ import { subscribeControlGroup, updateControlPanels, } from './services/control_panels'; -import { createAndSetDataView } from './services/data_view_service'; +import { changeDataView, createAdHocDataView } from './services/data_view_service'; import { + redirectToDiscoverAction, subscribeToDiscoverState, updateContextFromDiscoverAppState, updateContextFromDiscoverDataState, updateDiscoverAppStateFromContext, } from './services/discover_service'; -import { validateSelection } from './services/selection_service'; +import { initializeSelection } from './services/selection_service'; import { subscribeToTimefilterService, updateContextFromTimefilter, @@ -43,7 +49,7 @@ import { export const createPureLogsExplorerControllerStateMachine = ( initialContext: LogsExplorerControllerContext ) => - /** @xstate-layout N4IgpgJg5mDOIC5QBkD2UCiAPADgG1QCcxCBhVAOwBdDU88SA6AVwoEt2q2BDPNgL0gBiAEoZSGAJIA1DABEA+gGUAKgEEVGBaQDyAOXWS9GEQG0ADAF1EoHKlhsulGyCyIAtACYAbIwDsAIzmAJwAzH4ArAA0IACeiJ5+AByMwZ6hnokALD6e5kneAL6FMWiYuATEZJQ0dAyEjByOPHz8HFBy3FTc0mxgAO5CEJRgjRQAbqgA1qNl2PhEJOTUtPRMTVy8Au2d3b0DCByTAMZdbJQWlpcudg5OFC5uCAGejObentFxiBHBwYyeLIBX5JfLeX7BCLFUroeaVJY1Vb1MbNLZtCgdLo9PqDEi0Br4LoAMyIAFtGHMKotqis6utOC1thjdtiDkdUKd7pdrkgQLdms5eU9sowAqEkn8IuLIUDvN4YvEEFksqE3hFQaEglkperPNCQJSFlVlrU1g0Noz0VATasAArcChgPCwIYjMaTGYU2FU42Iunmhlo9o2uj2x3Ow4TDlnC5WHm2ewCh5Cjy+YLeJJ+cGfTMS96fBWIJJJAKMULa8wZbzmPyfbxZfWG+E003Ii1BjEhvBhp0uvFERiEqgkwjkpvUrttwOtYN+7sO3uRk4xijcqw3RP3R4JPyqgJZDM14sygJywsIXf-TV+LLBEIFXc5Rveo0I2lmlGbVrCMQSGRaORJCUXRZBEBQ1FtW1lHUTR4z5TdzmTUBhVCdNRX3QEQmSbUknPCJAn8IJawiTxgm1dIoRKA0X2bSd6VRb8IFEcQpFkBQAEUAFUTAATWgjQMDg-ktxTBBMIiRhiyzcUpSzSJz0fRhvACPws0rEsIgietn3KV8WyReivwESBGAgLFYDAKglCdMBjnuRhxi2MyuAxayGDsxChGQIDND0BQVB0bQAAk1D0ABxDAlCEhDBWQxAyN8W8wkCJIfDSIFzzlLJ0KyfIwmVAIklCUIdLhCc5ynBjjIgUzzMstzbPsxy+Gc9oGo8yghE4205AEhRevUJQMBUZQMGQcQVEkfRoruRDtwQB9RXBVCIn3cwQnBc8S1VcEVQ0yIPgCAJSp9N9W0My0TOc7gLKsmyOooBynLOVz7vuIQBrUIaRqG8bSEm-QFDEVQdDEBQADE1EkZBOLEGak3m8FS1COUAmCNS5Wvc80hSYtPFS4rvDvJJNJOvS6IDKrBBq67bva+y2AgBgup6vrPu+0a-oBvR4ZEuLnh8FJCaK6t8drcstrFJSIj24EDs8I6ydoiqLrRK66ru9yGaZsAPo0L7hs5iapr84GArByHodhwT115YS5tE4FwUYFUifMdJlQid2sklnaZfFOWtIV46qPHX130qozqdq7o6bexCWBwVrmSxfZBmGR13WmWYaPKiPVcYmObvq+PKET5PMT2HEl2jLk41thNZti1xEEK4qlKBPIUfVHJQmxtCIVS9MvewopQ9z8PzspqP1djkutYT5gk5eyvWVxQh8UHPBiTJL1dOV-Pp8ummNfpxfl5c1e05rzlELXaw7ZipCW+edMUi9kIwh77xNXPIJITeGkfKy0ayniVnnKen5j6MGOHOMKtAl6wBYOwac1UhBGEkJNNQ3kABaWhdAGBEDoZACgwpEO6uBW0kheYO35nmAEgI0afGVAHXC3xnjlgkrlSUwQmHuzHjCfeECDJHzVjVWB754GoEQY0HWet1AKGkJIDAAB1BQ3UBryBoc3J4LwDwAk1Ojcih5wh-ylNlIqljEj4wopRQRZVJ4iKgWImBcCEE4CQYzZmGi+oEJUEQkhtpQpjSig3eCTdn66JRn4VImRqx+DCLwzIAQzFkRdvuDMqk-D5FvHY6iQjHH+mcYXCRpopEyKXhXLsPZnSukzuyT0YczpOPbCUtx0iPHlxXtUhcEZ2S31jFcMJ9sdGt3RuYN4GRAShG-r-dhKkMzSzvFpXcRNEgh3sadfSRTWnVVcZI9xSDKndLnDUvsG8BxDhHGOCezSdmoOjqU1Y5TOnHMvj08MsAb4rnvhuCJ80VJpKSNqLIoJ+FpBvH-QIEl1rlhJumImaRKJUQoKgCAcAXBNO2WaP5CNRLuBvIREI4QviKgSf4CUwQSYkQPOkMi4DCkflYLs6muK+YvxyH-bwMT8Z-D5fyv4Aj8kOLuR+FlOxU44jZbQl+7hSye01AWdhyoJnYV+DM7UMzUoMtFZHS0s53xnOlaMhAXgJlZCzEHJK-KCgKX3GWFSt57zcvLHqceBTdUF2qsayJPwSKpA+JWBWvwgTanPKhV4CteEkTlCEHIwQdXYr1S42m89GoypGb6hA1ZVQo2UujasmMVJ4QIoENIGZgS5MSImimxS9mps1umsuzVGYrzPs3TN81Ah7hybC3aeQ-CZV+ACJhqUf5BoKDWlWojC4NvbY9LxYAfUAu1DyksCSSb1nMAeNhioso5TyuWIERUSrupFUmr10c52l0em8iVVcBjLsdqeGJwJULig3WRIq-dspVgjcCI8B4p2HzrY89piCn382rBM9MXdg1kX3KS1unxX0oyBO+9U6M8lYtrSykyTy6AvKOSgqmkBIMv3rCkWDQbgQIbDfMlGKQxTZlPGkfhwHIF4fEeBzpi7yO6KlP8UiylbzZLyPjP+R6lLdwVms9jZ6tm4YefhnjRyL4GtNEax+-zHZYQBH8EInxNLrsHfMt2bxKygt+HKP4nxijFCAA */ + /** @xstate-layout N4IgpgJg5mDOIC5QBkD2VYFEAeAHANqgE5hEDCqAdgC5Gr76kB0ArpQJYfXsCG+7AL0gBiAEqYymAJIA1TABEA+gGUAKgEFVmRWQDyAOQ1T9mUQG0ADAF1EoXKljtuVWyGyIATBYtMAbB4AOABYAZgsAdnCg8N8AVliAGhAAT0QARjSPJhCQjyC0iO9YgE4PEOKAXwqktAwcAmJSCho6BmZOJ15+AU4oZTBGAGNnSmFjKVUpdWQpAC1teU11RRkpTAB1SxskEHtHEdd3BCCIv2KwtOK4gO9zpNTjnKYLL3P-Eo9i2JCqmvQsPCEEjkKi0eiMIhMDrcPiCXr9IYjMb6CZTGbzRSLDTKTCqLauPadFw7I6+IJMYoBTIfNIhXzFIJle6IE7FJgBYoWL5BAI3cJeOm-EC1AENYHNMFtSHQrpwyh9AZgYbsKjCLHqHGqFSYZASSYGRTiNS6cSKABi6ikyAAquJ8TtCQcSYgALRpAKxJh5XKc4qc2L0oKJFKeXwhJixXl+sPu87fXxCkX1IFNUGtCFQriynry+Q8ag8GTsMAAd2EECoYEzADdUABrKtJwGNEEtcHtLOwnNQPMFoulhCcWuDfMqyhbe12BxEyiHRAhcLhiwnXzBcIBk7xZkIcLeZ5R4ppUK73wWH7VYX-ZMtiXpjudLu9XuF4tl0h0SEEfMAM2IAFsmCbMVUzbKVMwfbon3zF8ByHVARxGCdrAJacnVAUkAnCJhGV8DIqXCL4rmDB5Yg8LIuS5cI0nXfxQnPP46mbcU03baVO0g3NoNgMBqHLSsa3rRsryYkDJQzGVH04gtuOoQdKGHUcqCQ7Yp32Mc5wQF0yKYGILG+KiQndMj8nCbddx8CwOQselOUXXJYkTYTgNbMT7xhDiey4njhHfYgmC-ahfyIACgJTFy7zYiC5U86SeLkhTEOsSddlQ9TnU02NsL5SkTkiTk6W3AIF2eWJqOKKiPF8d4EwvUKbxYsCJI8296AABR4SgBlgPjOoEhtAKcsKWsa9jouG9rOvwWB4vgxTxyS5CHVS4l0MQAyKXKor8g8eJ+VMkMEACMlnmXXlylCXwT0cxjnOG8TRu7caOq6nyiA-fz8B-f8BpuoaGvuqLHoaiaupmhCx2UlC1JWtw1qPPxrkyQybipDxCr07CohyXDDK5Dw0mu0U-tAgH3MEERxEkWQFikZQ9DkURFHUVrWpUDQtGSx00tWhBIx8fIuUM09KvOAJt1pXJsNCWkCnOmIPEJ69mJJtzswpiRpDkRRJgAWUwM0rS0RndcwTnltndLTzZMpMNifI0kui5xfdcNYmsj1FxOB2aoYon6pVyKyaECAxA16ntakPWDeQI3DX1o0AAlFGMI2ZGmM3oYtnn4nDBc7dPAiLG9-aHgKHl2SpYIj0+eIPHCRWRPC1jwKDyAmAgLzqARJURihCBGGEa1WqxBYlk1bVdTIfV9AzmcNMyKqIxKMk9M+Hbom3Bc2SuDIQmiQ9i4b27-tVrs2472Ku8VZUqD7geh5HzEx9xCe9SkAwzDSFSUsz+fDNd2kpEqpfGolVbcbssgEVynXLkcZKi1UGv7Vygc1YQHbp3buN9KB3zAIPYemhR7YhfjiSe08zAeG-lzGGRw0ikTSH4Iq7pSrY1KGjA6kQfCHhuCEEolk6HwN9krUSEUW6oPQZfTBvdBgAAsOpQCgn2V8PUqxwX6nVZWyDRFnzQRfHgMlJFjiYDIuRCiYIljBnNSGS1f7pSRmyf+VJ6RF1KhvA67xsi7nOLuS4QR6QEwQb9JBIimrkx0Rg6+UjZHylMf2N8b0-IBSCiFRBGjgkPXPuExEhjjHRKkmYixiUrCzzQrDBAdjsKrnyG7M8+MghmS8EwXeVIF48g9A5AJftUnNxCcHcReieIGNvoMEgo55TqAgAneCz5YnKL6kJQJXSRqAwyRIiJ2SRncDGRMqZ0FYkFIhgtSh5s-7FQ+DEXIDs6JsIeJyAIEZAjY18d7Q8R9iaaJ6Ss-pV8slDI2b0cZkzBjTKUb5T8n1ArfXUcI7p6SwmrJ+dg4ZYBRlQABTsxRsF5KzUKcU7mpSJZ3MqhEXIVI3YcjFgdAopxeRcn8IXVeQRXlBJhcstBgwGoAHE6AsFwLAVgHBYXIlRNMOY2g9CGFELoZAigOWSqHkzVqUhcXUPSGETh+MjxFXiJdcIFLS5lE9LyS6NxVwmvxkyxZpMxHstAly1APK+XsH7rg9UKw1jrEUA-Ah8hlVZ3xZkNksRdV7R5LuUIer0guOwqRUlQQ415BiBa6FSzW5ss5dy3lOC8GP3FaoSV0rWrqBMMgZQvq-7+AjBcMMkRIwRrKVGoMgRaFxsZNEeil4FnJqtdoox6b7WZp5RfXoT1JrdQrL1VR8zOldtPt0NuNrJR2odawXAQ75QjtBnBcGSlDlQznrY8o4Z-DBACDtKikYCqUpCMECkvIdrBC+Ne-ISam4putX25dg6UUbqmq9d6iTIUpJnSgntC7WhLoHau79wNnpTX2Tuopi1VL7p5rSBkXoriNuoi8E4dSr2VXZIESyR00NRAVkKSgqAIBwFcFC19EI90lKOFpYqPIIg5QiIXS9Dw8jslyJuai5RqKBBfXdZgbAPkQEY3i5jwmspUW8AUHap5MLix4dkSy5V+R0j3uVdtdGxMgY8oMv1VC-VHDIlhdey4chUSuEdXw24670JyMuMM1lSpUiDKJk+RnorAtLNJlVxwXgMNpJROkcbIzgOiM8LwVVsanqDPXDpQj6Ozv853IL5n5zkndMeaI-JLJVOIogfO7JLqVS858K4+mgPpb80DUCIMprZY0te3wfGaJ7xyM065iASO3qPEEL4Z4RsOx8wHLRc6pPWJQ-il44Y9OVR4UXCIp7nZZB3hLKIR5-CXUm+82FfT9FrJhmZ+eEQsgFFXJ8a9646Ql08KUCpGQ15knGz7Dt06GvTdCSdgZZ3sFOsYG12xmQfA3dPaLB7l1twnGu8uINtCdo8IyIdtJrKAffJ7tkqJ8i8mxLB6h5LFIeFPtKKUfI25-C52iKVRcNLOT+MEY3Qzf3em6NOwioxfytmAoCyWYn+LIzki8adCIVVhbbk5J1t2n2iovGZxjllqbe22ozfAObTH0gRHoctsMbsCi6v62UzV2QHPeGc4ydprPj5Tck+rxdmv+WSeFzQzIdztWwLDGGfIIRxb3oq7Z9cdtyj8hV2+0DH7M0g7AO79IZJPSlDs-SwTanT0UjJGdG7lUdqR+7TNp34GXdfs2VAH9WvkM67N5cCki4VtG-W6b2kNwvTeHwpZfkgR4FVCAA */ createMachine< LogsExplorerControllerContext, LogsExplorerControllerEvent, @@ -58,14 +64,47 @@ export const createPureLogsExplorerControllerStateMachine = ( uninitialized: { on: { RECEIVED_STATE_CONTAINER: { - target: 'initializingDataView', + target: 'initializingSelection', actions: ['storeDiscoverStateContainer'], }, }, }, + initializingSelection: { + invoke: { + src: 'initializeSelection', + }, + on: { + INITIALIZE_DATA_VIEW: 'initializingDataView', + INITIALIZE_DATASET: { + target: 'initializingDataset', + actions: ['storeDatasetSelection'], + }, + DATASET_SELECTION_RESTORE_FAILURE: { + target: 'initializingDataset', + actions: ['storeDefaultSelection', 'notifyDatasetSelectionRestoreFailed'], + }, + }, + }, initializingDataView: { invoke: { - src: 'createDataView', + src: 'changeDataView', + onDone: { + target: 'initializingControlPanels', + actions: ['updateDiscoverAppStateFromContext', 'updateTimefilterFromContext'], + }, + onError: { + target: 'initialized', + actions: [ + 'notifyCreateDataViewFailed', + 'updateDiscoverAppStateFromContext', + 'updateTimefilterFromContext', + ], + }, + }, + }, + initializingDataset: { + invoke: { + src: 'createAdHocDataView', onDone: { target: 'initializingControlPanels', actions: ['updateDiscoverAppStateFromContext', 'updateTimefilterFromContext'], @@ -107,41 +146,43 @@ export const createPureLogsExplorerControllerStateMachine = ( entry: ['resetRows'], states: { datasetSelection: { - initial: 'validatingSelection', + initial: 'idle', states: { - validatingSelection: { - invoke: { - src: 'validateSelection', - }, + idle: { on: { - LISTEN_TO_CHANGES: { - target: 'idle', - }, - UPDATE_DATASET_SELECTION: { - target: 'updatingDataView', - actions: ['storeDatasetSelection'], - }, - DATASET_SELECTION_RESTORE_FAILURE: { - target: 'updatingDataView', - actions: ['notifyDatasetSelectionRestoreFailed'], - }, + UPDATE_DATASET_SELECTION: [ + { + cond: 'isUnknownDataViewDescriptor', + actions: ['redirectToDiscover'], + }, + { + cond: 'isLogsDataViewDescriptor', + target: 'changingDataView', + actions: ['storeDatasetSelection'], + }, + { + target: 'creatingAdHocDataView', + actions: ['storeDatasetSelection'], + }, + ], }, }, - idle: { - on: { - UPDATE_DATASET_SELECTION: { - target: 'updatingDataView', - actions: ['storeDatasetSelection'], + changingDataView: { + invoke: { + src: 'changeDataView', + onDone: { + target: 'idle', + actions: ['notifyDataViewUpdate'], }, - DATASET_SELECTION_RESTORE_FAILURE: { - target: 'updatingDataView', - actions: ['notifyDatasetSelectionRestoreFailed'], + onError: { + target: 'idle', + actions: ['notifyCreateDataViewFailed'], }, }, }, - updatingDataView: { + creatingAdHocDataView: { invoke: { - src: 'createDataView', + src: 'createAdHocDataView', onDone: { target: 'idle', actions: ['notifyDataViewUpdate'], @@ -216,8 +257,11 @@ export const createPureLogsExplorerControllerStateMachine = ( }, { actions: { + storeDefaultSelection: actions.assign((_context) => ({ + datasetSelection: AllDatasetSelection.create(), + })), storeDatasetSelection: actions.assign((_context, event) => - 'data' in event && isDatasetSelection(event.data) + 'data' in event && (isDatasetSelection(event.data) || isDataViewSelection(event.data)) ? { datasetSelection: event.data, } @@ -257,12 +301,25 @@ export const createPureLogsExplorerControllerStateMachine = ( controlGroupAPIExists: (_context, event) => { return 'controlGroupAPI' in event && event.controlGroupAPI != null; }, + isLogsDataViewDescriptor: (_context, event) => { + if (event.type === 'UPDATE_DATASET_SELECTION' && isDataViewSelection(event.data)) { + return event.data.selection.dataView.isLogsDataType(); + } + return false; + }, + isUnknownDataViewDescriptor: (_context, event) => { + if (event.type === 'UPDATE_DATASET_SELECTION' && isDataViewSelection(event.data)) { + return event.data.selection.dataView.isUnknownDataType(); + } + return false; + }, }, } ); export interface LogsExplorerControllerStateMachineDependencies { datasetsClient: IDatasetsClient; + plugins: Pick; initialContext?: LogsExplorerControllerContext; query: QueryStart; toasts: IToasts; @@ -270,6 +327,7 @@ export interface LogsExplorerControllerStateMachineDependencies { export const createLogsExplorerControllerStateMachine = ({ datasetsClient, + plugins: { dataViews, discover }, initialContext = DEFAULT_CONTEXT, query, toasts, @@ -278,14 +336,16 @@ export const createLogsExplorerControllerStateMachine = ({ actions: { notifyCreateDataViewFailed: createCreateDataViewFailedNotifier(toasts), notifyDatasetSelectionRestoreFailed: createDatasetSelectionRestoreFailedNotifier(toasts), + redirectToDiscover: redirectToDiscoverAction(discover), updateTimefilterFromContext: updateTimefilterFromContext(query), }, services: { - createDataView: createAndSetDataView(), + changeDataView: changeDataView({ dataViews }), + createAdHocDataView: createAdHocDataView(), initializeControlPanels: initializeControlPanels(), + initializeSelection: initializeSelection({ datasetsClient, discover }), subscribeControlGroup: subscribeControlGroup(), updateControlPanels: updateControlPanels(), - validateSelection: validateSelection({ datasetsClient }), discoverStateService: subscribeToDiscoverState(), timefilterService: subscribeToTimefilterService(query), }, diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/types.ts b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/types.ts index 5e7d617a1cd4e1..a72b1700fce433 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/types.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/state_machines/logs_explorer_controller/src/types.ts @@ -15,10 +15,15 @@ import type { import { DoneInvokeEvent } from 'xstate'; import type { DataTableRecord } from '@kbn/discover-utils/src/types'; import { ControlPanels, DisplayOptions } from '../../../../common'; -import type { DatasetEncodingError, DatasetSelection } from '../../../../common/dataset_selection'; +import type { + DatasetEncodingError, + DatasetSelection, + DataViewSelection, + SingleDatasetSelection, +} from '../../../../common/dataset_selection'; export interface WithDatasetSelection { - datasetSelection: DatasetSelection; + datasetSelection: DatasetSelection | DataViewSelection; } export interface WithControlPanelGroupAPI { @@ -52,28 +57,40 @@ export type LogsExplorerControllerTypeState = context: WithDatasetSelection & WithControlPanels & WithQueryState & WithDisplayOptions; } | { - value: 'initializingDataView'; - context: WithDatasetSelection & WithControlPanels & WithQueryState & WithDisplayOptions; + value: 'initializingSelection'; + context: WithDatasetSelection & + WithControlPanels & + WithQueryState & + WithDisplayOptions & + WithDataTableRecord & + WithDiscoverStateContainer; } | { - value: 'initializingControlPanels'; - context: WithDatasetSelection & WithControlPanels & WithQueryState & WithDisplayOptions; + value: 'initializingDataset'; + context: WithDatasetSelection & + WithControlPanels & + WithQueryState & + WithDisplayOptions & + WithDiscoverStateContainer; } | { - value: 'initializingStateContainer'; - context: WithDatasetSelection & WithControlPanels & WithQueryState & WithDisplayOptions; + value: 'initializingDataView'; + context: WithDatasetSelection & + WithControlPanels & + WithQueryState & + WithDisplayOptions & + WithDiscoverStateContainer; } | { - value: 'initialized'; + value: 'initializingControlPanels'; context: WithDatasetSelection & WithControlPanels & WithQueryState & WithDisplayOptions & - WithDataTableRecord & WithDiscoverStateContainer; } | { - value: 'initialized.datasetSelection.validatingSelection'; + value: 'initialized'; context: WithDatasetSelection & WithControlPanels & WithQueryState & @@ -91,7 +108,7 @@ export type LogsExplorerControllerTypeState = WithDiscoverStateContainer; } | { - value: 'initialized.datasetSelection.updatingDataView'; + value: 'initialized.datasetSelection.changingDataView'; context: WithDatasetSelection & WithControlPanels & WithQueryState & @@ -100,7 +117,7 @@ export type LogsExplorerControllerTypeState = WithDiscoverStateContainer; } | { - value: 'initialized.datasetSelection.updatingStateContainer'; + value: 'initialized.datasetSelection.creatingAdHocDataView'; context: WithDatasetSelection & WithControlPanels & WithQueryState & @@ -148,14 +165,18 @@ export type LogsExplorerControllerEvent = discoverStateContainer: DiscoverStateContainer; } | { - type: 'LISTEN_TO_CHANGES'; + type: 'DATASET_SELECTION_RESTORE_FAILURE'; } | { - type: 'UPDATE_DATASET_SELECTION'; - data: DatasetSelection; + type: 'INITIALIZE_DATA_VIEW'; } | { - type: 'DATASET_SELECTION_RESTORE_FAILURE'; + type: 'INITIALIZE_DATASET'; + data?: SingleDatasetSelection; + } + | { + type: 'UPDATE_DATASET_SELECTION'; + data: DatasetSelection | DataViewSelection; } | { type: 'INITIALIZE_CONTROL_GROUP_API'; @@ -181,7 +202,7 @@ export type LogsExplorerControllerEvent = type: 'RECEIVE_TIMEFILTER_REFRESH_INTERVAL'; refreshInterval: RefreshInterval; } - | DoneInvokeEvent + | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/utils/parse_data_view_list_item.ts b/x-pack/plugins/observability_solution/logs_explorer/public/utils/parse_data_view_list_item.ts deleted file mode 100644 index ecf509c0945d93..00000000000000 --- a/x-pack/plugins/observability_solution/logs_explorer/public/utils/parse_data_view_list_item.ts +++ /dev/null @@ -1,15 +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 { DataViewListItem } from '@kbn/data-views-plugin/common'; - -export const parseDataViewListItem = (dataViewListItem: DataViewListItem) => { - return { - ...dataViewListItem, - name: dataViewListItem.name ?? dataViewListItem.title, - }; -};