diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 31625748a2cb9..8b3a331e4ba76 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -35,7 +35,7 @@ pageLoadAssetSize: dataViews: 65000 dataVisualizer: 30000 devTools: 38637 - discover: 99999 + discover: 25000 discoverEnhanced: 42730 discoverShared: 17111 embeddable: 24000 diff --git a/src/platform/plugins/shared/discover/common/app_locator.test.ts b/src/platform/plugins/shared/discover/common/app_locator.test.ts index d541f32835e4a..22309b7e3a270 100644 --- a/src/platform/plugins/shared/discover/common/app_locator.test.ts +++ b/src/platform/plugins/shared/discover/common/app_locator.test.ts @@ -14,9 +14,11 @@ import { } from '@kbn/kibana-utils-plugin/public'; import { mockStorage } from '@kbn/kibana-utils-plugin/public/storage/hashed_item_store/mock'; import { FilterStateStore } from '@kbn/es-query'; -import { DiscoverAppLocatorDefinition } from './app_locator'; +import type { DiscoverAppLocatorParams } from './app_locator'; +import { DISCOVER_APP_LOCATOR } from './app_locator'; import type { SerializableRecord } from '@kbn/utility-types'; import { createDataViewDataSource, createEsqlDataSource } from './data_sources'; +import { appLocatorGetLocationCommon } from './app_locator_get_location'; const dataViewId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002'; const savedSearchId: string = '571aaf70-4c88-11e8-b3d7-01146121b73d'; @@ -26,11 +28,14 @@ interface SetupParams { } const setup = async ({ useHash = false }: SetupParams = {}) => { - const locator = new DiscoverAppLocatorDefinition({ useHash, setStateToKbnUrl }); - - return { - locator, + const locator = { + id: DISCOVER_APP_LOCATOR, + getLocation: (params: DiscoverAppLocatorParams) => { + return appLocatorGetLocationCommon({ useHash, setStateToKbnUrl }, params); + }, }; + + return { locator }; }; beforeEach(() => { @@ -267,7 +272,7 @@ describe('Discover url generator', () => { const { locator } = await setup(); const { state } = await locator.getLocation({ dataViewSpec: dataViewSpecMock }); - expect(state.dataViewSpec).toEqual(dataViewSpecMock); + expect((state as Record).dataViewSpec).toEqual(dataViewSpecMock); }); describe('useHash property', () => { diff --git a/src/platform/plugins/shared/discover/common/app_locator.ts b/src/platform/plugins/shared/discover/common/app_locator.ts index 7598304d270b9..d2a73ee6b5ab7 100644 --- a/src/platform/plugins/shared/discover/common/app_locator.ts +++ b/src/platform/plugins/shared/discover/common/app_locator.ts @@ -9,15 +9,11 @@ import type { SerializableRecord } from '@kbn/utility-types'; import type { Filter, TimeRange, Query, AggregateQuery } from '@kbn/es-query'; -import { isOfAggregateQueryType } from '@kbn/es-query'; -import type { GlobalQueryStateFromUrl, RefreshInterval } from '@kbn/data-plugin/public'; +import type { RefreshInterval } from '@kbn/data-plugin/public'; import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; import type { DiscoverGridSettings } from '@kbn/saved-search-plugin/common'; import type { DataViewSpec } from '@kbn/data-views-plugin/common'; -import type { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/common'; import type { VIEW_MODE } from './constants'; -import type { DiscoverAppState } from '../public'; -import { createDataViewDataSource, createEsqlDataSource } from './data_sources'; export const DISCOVER_APP_LOCATOR = 'DISCOVER_APP_LOCATOR'; @@ -113,11 +109,6 @@ export interface DiscoverAppLocatorParams extends SerializableRecord { export type DiscoverAppLocator = LocatorPublic; -export interface DiscoverAppLocatorDependencies { - useHash: boolean; - setStateToKbnUrl: typeof setStateToKbnUrl; -} - /** * Location state of scoped history (history instance of Kibana Platform application service) */ @@ -126,83 +117,5 @@ export interface MainHistoryLocationState { isAlertResults?: boolean; } -export class DiscoverAppLocatorDefinition implements LocatorDefinition { - public readonly id = DISCOVER_APP_LOCATOR; - - constructor(protected readonly deps: DiscoverAppLocatorDependencies) {} - - public readonly getLocation = async (params: DiscoverAppLocatorParams) => { - const { - useHash = this.deps.useHash, - filters, - dataViewId, - indexPatternId, - dataViewSpec, - query, - refreshInterval, - savedSearchId, - timeRange, - searchSessionId, - columns, - grid, - savedQuery, - sort, - interval, - viewMode, - hideAggregatedPreview, - breakdownField, - isAlertResults, - } = params; - const savedSearchPath = savedSearchId ? `view/${encodeURIComponent(savedSearchId)}` : ''; - const appState: Partial = {}; - const queryState: GlobalQueryStateFromUrl = {}; - const { isFilterPinned } = await import('@kbn/es-query'); - - if (query) appState.query = query; - if (filters && filters.length) appState.filters = filters?.filter((f) => !isFilterPinned(f)); - if (indexPatternId) - appState.dataSource = createDataViewDataSource({ dataViewId: indexPatternId }); - if (dataViewId) appState.dataSource = createDataViewDataSource({ dataViewId }); - if (isOfAggregateQueryType(query)) appState.dataSource = createEsqlDataSource(); - if (columns) appState.columns = columns; - if (grid) appState.grid = grid; - if (savedQuery) appState.savedQuery = savedQuery; - if (sort) appState.sort = sort; - if (interval) appState.interval = interval; - if (timeRange) queryState.time = timeRange; - if (filters && filters.length) queryState.filters = filters?.filter((f) => isFilterPinned(f)); - if (refreshInterval) queryState.refreshInterval = refreshInterval; - if (viewMode) appState.viewMode = viewMode; - if (hideAggregatedPreview) appState.hideAggregatedPreview = hideAggregatedPreview; - if (breakdownField) appState.breakdownField = breakdownField; - - const state: MainHistoryLocationState = {}; - if (dataViewSpec) state.dataViewSpec = dataViewSpec; - if (isAlertResults) state.isAlertResults = isAlertResults; - - let path = `#/${savedSearchPath}`; - - if (searchSessionId) { - path = `${path}?searchSessionId=${searchSessionId}`; - } - - if (Object.keys(queryState).length) { - path = this.deps.setStateToKbnUrl( - '_g', - queryState, - { useHash }, - path - ); - } - - if (Object.keys(appState).length) { - path = this.deps.setStateToKbnUrl('_a', appState, { useHash }, path); - } - - return { - app: 'discover', - path, - state, - }; - }; -} +export type DiscoverAppLocatorGetLocation = + LocatorDefinition['getLocation']; diff --git a/src/platform/plugins/shared/discover/common/app_locator_get_location.ts b/src/platform/plugins/shared/discover/common/app_locator_get_location.ts new file mode 100644 index 0000000000000..420c67c3cb467 --- /dev/null +++ b/src/platform/plugins/shared/discover/common/app_locator_get_location.ts @@ -0,0 +1,93 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public'; +import { isFilterPinned, isOfAggregateQueryType } from '@kbn/es-query'; +import type { setStateToKbnUrl as setStateToKbnUrlCommon } from '@kbn/kibana-utils-plugin/common'; +import type { DiscoverAppLocatorGetLocation, MainHistoryLocationState } from './app_locator'; +import type { DiscoverAppState } from '../public'; +import { createDataViewDataSource, createEsqlDataSource } from './data_sources'; + +export const appLocatorGetLocationCommon = async ( + { + useHash: useHashOriginal, + setStateToKbnUrl, + }: { + useHash: boolean; + setStateToKbnUrl: typeof setStateToKbnUrlCommon; + }, + ...[params]: Parameters +): ReturnType => { + const { + useHash = useHashOriginal, + filters, + dataViewId, + indexPatternId, + dataViewSpec, + query, + refreshInterval, + savedSearchId, + timeRange, + searchSessionId, + columns, + grid, + savedQuery, + sort, + interval, + viewMode, + hideAggregatedPreview, + breakdownField, + isAlertResults, + } = params; + const savedSearchPath = savedSearchId ? `view/${encodeURIComponent(savedSearchId)}` : ''; + const appState: Partial = {}; + const queryState: GlobalQueryStateFromUrl = {}; + + if (query) appState.query = query; + if (filters && filters.length) appState.filters = filters?.filter((f) => !isFilterPinned(f)); + if (indexPatternId) + appState.dataSource = createDataViewDataSource({ dataViewId: indexPatternId }); + if (dataViewId) appState.dataSource = createDataViewDataSource({ dataViewId }); + if (isOfAggregateQueryType(query)) appState.dataSource = createEsqlDataSource(); + if (columns) appState.columns = columns; + if (grid) appState.grid = grid; + if (savedQuery) appState.savedQuery = savedQuery; + if (sort) appState.sort = sort; + if (interval) appState.interval = interval; + if (timeRange) queryState.time = timeRange; + if (filters && filters.length) queryState.filters = filters?.filter((f) => isFilterPinned(f)); + if (refreshInterval) queryState.refreshInterval = refreshInterval; + if (viewMode) appState.viewMode = viewMode; + if (hideAggregatedPreview) appState.hideAggregatedPreview = hideAggregatedPreview; + if (breakdownField) appState.breakdownField = breakdownField; + + const state: MainHistoryLocationState = {}; + if (dataViewSpec) state.dataViewSpec = dataViewSpec; + if (isAlertResults) state.isAlertResults = isAlertResults; + + let path = `#/${savedSearchPath}`; + + if (searchSessionId) { + path = `${path}?searchSessionId=${searchSessionId}`; + } + + if (Object.keys(queryState).length) { + path = setStateToKbnUrl('_g', queryState, { useHash }, path); + } + + if (Object.keys(appState).length) { + path = setStateToKbnUrl('_a', appState, { useHash }, path); + } + + return { + app: 'discover', + path, + state, + }; +}; diff --git a/src/platform/plugins/shared/discover/common/esql_locator.ts b/src/platform/plugins/shared/discover/common/esql_locator.ts index 4960de65f61e4..52b51bceb146d 100644 --- a/src/platform/plugins/shared/discover/common/esql_locator.ts +++ b/src/platform/plugins/shared/discover/common/esql_locator.ts @@ -7,36 +7,12 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { DISCOVER_ESQL_LOCATOR } from '@kbn/deeplinks-analytics'; import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/common'; import type { SerializableRecord } from '@kbn/utility-types'; -import { getIndexForESQLQuery, getInitialESQLQuery, getESQLAdHocDataview } from '@kbn/esql-utils'; -import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; export type DiscoverESQLLocatorParams = SerializableRecord; -export interface DiscoverESQLLocatorDependencies { - discoverAppLocator: LocatorPublic; - dataViews: DataViewsPublicPluginStart; -} - export type DiscoverESQLLocator = LocatorPublic; -export class DiscoverESQLLocatorDefinition implements LocatorDefinition { - public readonly id = DISCOVER_ESQL_LOCATOR; - - constructor(protected readonly deps: DiscoverESQLLocatorDependencies) {} - - public readonly getLocation = async () => { - const { discoverAppLocator, dataViews } = this.deps; - const indexName = (await getIndexForESQLQuery({ dataViews })) ?? '*'; - const dataView = await getESQLAdHocDataview(`from ${indexName}`, dataViews); - const esql = getInitialESQLQuery(dataView); - - const params = { - query: { esql }, - }; - - return await discoverAppLocator.getLocation(params); - }; -} +export type DiscoverESQLLocatorGetLocation = + LocatorDefinition['getLocation']; diff --git a/src/platform/plugins/shared/discover/common/esql_locator_get_location.ts b/src/platform/plugins/shared/discover/common/esql_locator_get_location.ts new file mode 100644 index 0000000000000..b53c63edc727c --- /dev/null +++ b/src/platform/plugins/shared/discover/common/esql_locator_get_location.ts @@ -0,0 +1,32 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { LocatorPublic } from '@kbn/share-plugin/common'; +import type { SerializableRecord } from '@kbn/utility-types'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { getESQLAdHocDataview, getIndexForESQLQuery, getInitialESQLQuery } from '@kbn/esql-utils'; +import type { DiscoverESQLLocatorGetLocation } from './esql_locator'; + +export const esqlLocatorGetLocation = async ({ + discoverAppLocator, + dataViews, +}: { + discoverAppLocator: LocatorPublic; + dataViews: DataViewsPublicPluginStart; +}): ReturnType => { + const indexName = (await getIndexForESQLQuery({ dataViews })) ?? '*'; + const dataView = await getESQLAdHocDataview(`from ${indexName}`, dataViews); + const esql = getInitialESQLQuery(dataView); + + const params = { + query: { esql }, + }; + + return await discoverAppLocator.getLocation(params); +}; diff --git a/src/platform/plugins/shared/discover/common/index.ts b/src/platform/plugins/shared/discover/common/index.ts index abdf8379088d2..13a2084b2f33c 100644 --- a/src/platform/plugins/shared/discover/common/index.ts +++ b/src/platform/plugins/shared/discover/common/index.ts @@ -10,12 +10,11 @@ export const PLUGIN_ID = 'discover'; export const APP_ICON = 'discoverApp'; -export { DISCOVER_APP_LOCATOR, DiscoverAppLocatorDefinition } from './app_locator'; +export { DISCOVER_APP_LOCATOR } from './app_locator'; export type { DiscoverAppLocator, DiscoverAppLocatorParams, MainHistoryLocationState, } from './app_locator'; -export { DiscoverESQLLocatorDefinition } from './esql_locator'; export type { DiscoverESQLLocator, DiscoverESQLLocatorParams } from './esql_locator'; diff --git a/src/platform/plugins/shared/discover/public/__mocks__/services.ts b/src/platform/plugins/shared/discover/public/__mocks__/services.ts index 160b70bd5a5d7..fa8903a464f51 100644 --- a/src/platform/plugins/shared/discover/public/__mocks__/services.ts +++ b/src/platform/plugins/shared/discover/public/__mocks__/services.ts @@ -46,7 +46,7 @@ import type { SearchSourceDependencies } from '@kbn/data-plugin/common'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import { createElement } from 'react'; import { createContextAwarenessMocks } from '../context_awareness/__mocks__'; -import { DiscoverEBTManager } from '../services/discover_ebt_manager'; +import { DiscoverEBTManager } from '../plugin_imports/discover_ebt_manager'; import { discoverSharedPluginMock } from '@kbn/discover-shared-plugin/public/mocks'; import { createUrlTrackerMock } from './url_tracker.mock'; diff --git a/src/platform/plugins/shared/discover/public/application/context/services/locator.test.ts b/src/platform/plugins/shared/discover/public/application/context/services/locator.test.ts index 5011540059800..e432b9483c876 100644 --- a/src/platform/plugins/shared/discover/public/application/context/services/locator.test.ts +++ b/src/platform/plugins/shared/discover/public/application/context/services/locator.test.ts @@ -8,7 +8,9 @@ */ import { getStatesFromKbnUrl } from '@kbn/kibana-utils-plugin/public'; -import { DiscoverContextAppLocatorDefinition } from './locator'; +import type { DiscoverContextAppLocatorParams } from './locator'; +import { DISCOVER_CONTEXT_APP_LOCATOR } from './locator'; +import { contextAppLocatorGetLocation } from './locator_get_location'; const dataViewId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002'; @@ -17,7 +19,13 @@ interface SetupParams { } const setup = async ({ useHash = false }: SetupParams = {}) => { - const locator = new DiscoverContextAppLocatorDefinition({ useHash }); + const locator = { + id: DISCOVER_CONTEXT_APP_LOCATOR, + getLocation: async (params: DiscoverContextAppLocatorParams) => { + return contextAppLocatorGetLocation({ useHash }, params); + }, + }; + return { locator }; }; diff --git a/src/platform/plugins/shared/discover/public/application/context/services/locator.ts b/src/platform/plugins/shared/discover/public/application/context/services/locator.ts index df9525873e635..b29eaf84d729c 100644 --- a/src/platform/plugins/shared/discover/public/application/context/services/locator.ts +++ b/src/platform/plugins/shared/discover/public/application/context/services/locator.ts @@ -9,9 +9,7 @@ import type { SerializableRecord } from '@kbn/utility-types'; import type { Filter } from '@kbn/es-query'; -import type { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public'; import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; -import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; import type { DataViewSpec } from '@kbn/data-views-plugin/public'; export const DISCOVER_CONTEXT_APP_LOCATOR = 'DISCOVER_CONTEXT_APP_LOCATOR'; @@ -26,58 +24,10 @@ export interface DiscoverContextAppLocatorParams extends SerializableRecord { export type DiscoverContextAppLocator = LocatorPublic; -export interface DiscoverContextAppLocatorDependencies { - useHash: boolean; -} - export interface ContextHistoryLocationState { referrer: string; dataViewSpec?: DataViewSpec; } -export class DiscoverContextAppLocatorDefinition - implements LocatorDefinition -{ - public readonly id = DISCOVER_CONTEXT_APP_LOCATOR; - - constructor(protected readonly deps: DiscoverContextAppLocatorDependencies) {} - - public readonly getLocation = async (params: DiscoverContextAppLocatorParams) => { - const useHash = this.deps.useHash; - const { index, rowId, columns, filters, referrer } = params; - - const appState: { filters?: Filter[]; columns?: string[] } = {}; - const queryState: GlobalQueryStateFromUrl = {}; - - const { isFilterPinned } = await import('@kbn/es-query'); - if (filters && filters.length) appState.filters = filters?.filter((f) => !isFilterPinned(f)); - if (columns) appState.columns = columns; - - if (filters && filters.length) queryState.filters = filters?.filter((f) => isFilterPinned(f)); - - let dataViewId; - const state: ContextHistoryLocationState = { referrer }; - if (typeof index === 'object') { - state.dataViewSpec = index; - dataViewId = index.id!; - } else { - dataViewId = index; - } - - let path = `#/context/${dataViewId}/${encodeURIComponent(rowId)}`; - - if (Object.keys(queryState).length) { - path = setStateToKbnUrl('_g', queryState, { useHash }, path); - } - - if (Object.keys(appState).length) { - path = setStateToKbnUrl('_a', appState, { useHash }, path); - } - - return { - app: 'discover', - path, - state, - }; - }; -} +export type DiscoverContextAppLocatorGetLocation = + LocatorDefinition['getLocation']; diff --git a/src/platform/plugins/shared/discover/public/application/context/services/locator_get_location.ts b/src/platform/plugins/shared/discover/public/application/context/services/locator_get_location.ts new file mode 100644 index 0000000000000..360cdee5026c3 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/application/context/services/locator_get_location.ts @@ -0,0 +1,54 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { Filter } from '@kbn/es-query'; +import type { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public'; +import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import type { ContextHistoryLocationState, DiscoverContextAppLocatorGetLocation } from './locator'; + +export const contextAppLocatorGetLocation = async ( + { useHash }: { useHash: boolean }, + ...[params]: Parameters +): ReturnType => { + const { index, rowId, columns, filters, referrer } = params; + + const appState: { filters?: Filter[]; columns?: string[] } = {}; + const queryState: GlobalQueryStateFromUrl = {}; + + const { isFilterPinned } = await import('@kbn/es-query'); + if (filters && filters.length) appState.filters = filters?.filter((f) => !isFilterPinned(f)); + if (columns) appState.columns = columns; + + if (filters && filters.length) queryState.filters = filters?.filter((f) => isFilterPinned(f)); + + let dataViewId; + const state: ContextHistoryLocationState = { referrer }; + if (typeof index === 'object') { + state.dataViewSpec = index; + dataViewId = index.id!; + } else { + dataViewId = index; + } + + let path = `#/context/${dataViewId}/${encodeURIComponent(rowId)}`; + + if (Object.keys(queryState).length) { + path = setStateToKbnUrl('_g', queryState, { useHash }, path); + } + + if (Object.keys(appState).length) { + path = setStateToKbnUrl('_a', appState, { useHash }, path); + } + + return { + app: 'discover', + path, + state, + }; +}; diff --git a/src/platform/plugins/shared/discover/public/application/doc/locator.test.ts b/src/platform/plugins/shared/discover/public/application/doc/locator.test.ts index b6d0dddb0f7cd..a59bc22b9553a 100644 --- a/src/platform/plugins/shared/discover/public/application/doc/locator.test.ts +++ b/src/platform/plugins/shared/discover/public/application/doc/locator.test.ts @@ -7,12 +7,20 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { DiscoverSingleDocLocatorDefinition } from './locator'; +import type { DiscoverSingleDocLocatorParams } from './locator'; +import { DISCOVER_SINGLE_DOC_LOCATOR } from './locator'; +import { singleDocLocatorGetLocation } from './locator_get_location'; const dataViewId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002'; const setup = () => { - const locator = new DiscoverSingleDocLocatorDefinition(); + const locator = { + id: DISCOVER_SINGLE_DOC_LOCATOR, + getLocation: async (params: DiscoverSingleDocLocatorParams) => { + return singleDocLocatorGetLocation(params); + }, + }; + return { locator }; }; diff --git a/src/platform/plugins/shared/discover/public/application/doc/locator.ts b/src/platform/plugins/shared/discover/public/application/doc/locator.ts index 936828c36f1e0..389542719b660 100644 --- a/src/platform/plugins/shared/discover/public/application/doc/locator.ts +++ b/src/platform/plugins/shared/discover/public/application/doc/locator.ts @@ -27,31 +27,5 @@ export interface DocHistoryLocationState { dataViewSpec?: DataViewSpec; } -export class DiscoverSingleDocLocatorDefinition - implements LocatorDefinition -{ - public readonly id = DISCOVER_SINGLE_DOC_LOCATOR; - - constructor() {} - - public readonly getLocation = async (params: DiscoverSingleDocLocatorParams) => { - const { index, rowId, rowIndex, referrer } = params; - - let dataViewId; - const state: DocHistoryLocationState = { referrer }; - if (typeof index === 'object') { - state.dataViewSpec = index; - dataViewId = index.id!; - } else { - dataViewId = index; - } - - const path = `#/doc/${dataViewId}/${rowIndex}?id=${encodeURIComponent(rowId)}`; - - return { - app: 'discover', - path, - state, - }; - }; -} +export type DiscoverSingleDocLocatorGetLocation = + LocatorDefinition['getLocation']; diff --git a/src/platform/plugins/shared/discover/public/application/doc/locator_get_location.ts b/src/platform/plugins/shared/discover/public/application/doc/locator_get_location.ts new file mode 100644 index 0000000000000..8e4a948e50d9d --- /dev/null +++ b/src/platform/plugins/shared/discover/public/application/doc/locator_get_location.ts @@ -0,0 +1,33 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { DiscoverSingleDocLocatorGetLocation, DocHistoryLocationState } from './locator'; + +export const singleDocLocatorGetLocation = async ( + ...[params]: Parameters +): ReturnType => { + const { index, rowId, rowIndex, referrer } = params; + + let dataViewId; + const state: DocHistoryLocationState = { referrer }; + if (typeof index === 'object') { + state.dataViewSpec = index; + dataViewId = index.id!; + } else { + dataViewId = index; + } + + const path = `#/doc/${dataViewId}/${rowIndex}?id=${encodeURIComponent(rowId)}`; + + return { + app: 'discover', + path, + state, + }; +}; diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/discover_saved_search_container.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/discover_saved_search_container.ts index 56d8776e08711..2b99427e8d16b 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/discover_saved_search_container.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/discover_saved_search_container.ts @@ -21,7 +21,7 @@ import type { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; import { isEqual, isFunction } from 'lodash'; import { i18n } from '@kbn/i18n'; import { VIEW_MODE } from '../../../../common/constants'; -import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search'; +import { restoreStateFromSavedSearch } from './utils/restore_from_saved_search'; import { updateSavedSearch } from './utils/update_saved_search'; import { addLog } from '../../../utils/add_log'; import { handleSourceColumnState } from '../../../utils/state_helpers'; diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/discover_state.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/discover_state.ts index 84fcebbba2fd0..97f918de6a47f 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/discover_state.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/discover_state.ts @@ -24,7 +24,7 @@ import { isOfAggregateQueryType, isOfQueryType } from '@kbn/es-query'; import { isFunction } from 'lodash'; import type { DiscoverServices } from '../../..'; import { loadSavedSearch as loadSavedSearchFn } from './utils/load_saved_search'; -import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search'; +import { restoreStateFromSavedSearch } from './utils/restore_from_saved_search'; import { FetchStatus } from '../../types'; import { changeDataView } from './utils/change_data_view'; import { buildStateSubscribe } from './utils/build_state_subscribe'; diff --git a/src/platform/plugins/shared/discover/public/services/saved_searches/restore_from_saved_search.test.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/utils/restore_from_saved_search.test.ts similarity index 99% rename from src/platform/plugins/shared/discover/public/services/saved_searches/restore_from_saved_search.test.ts rename to src/platform/plugins/shared/discover/public/application/main/state_management/utils/restore_from_saved_search.test.ts index e2a4886f8c55c..a499c35170edb 100644 --- a/src/platform/plugins/shared/discover/public/services/saved_searches/restore_from_saved_search.test.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/utils/restore_from_saved_search.test.ts @@ -9,7 +9,7 @@ import type { TimefilterContract } from '@kbn/data-plugin/public'; import type { TimeRange, RefreshInterval } from '@kbn/data-plugin/common'; -import { savedSearchMock, savedSearchMockWithTimeField } from '../../__mocks__/saved_search'; +import { savedSearchMock, savedSearchMockWithTimeField } from '../../../../__mocks__/saved_search'; import { restoreStateFromSavedSearch } from './restore_from_saved_search'; describe('discover restore state from saved search', () => { diff --git a/src/platform/plugins/shared/discover/public/services/saved_searches/restore_from_saved_search.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/utils/restore_from_saved_search.ts similarity index 97% rename from src/platform/plugins/shared/discover/public/services/saved_searches/restore_from_saved_search.ts rename to src/platform/plugins/shared/discover/public/application/main/state_management/utils/restore_from_saved_search.ts index bdc78ebbfefe5..40f579666e1b8 100644 --- a/src/platform/plugins/shared/discover/public/services/saved_searches/restore_from_saved_search.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/utils/restore_from_saved_search.ts @@ -9,7 +9,7 @@ import type { TimefilterContract } from '@kbn/data-plugin/public'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { isRefreshIntervalValid, isTimeRangeValid } from '../../utils/validate_time'; +import { isRefreshIntervalValid, isTimeRangeValid } from '../../../../utils/validate_time'; export const restoreStateFromSavedSearch = ({ savedSearch, diff --git a/src/platform/plugins/shared/discover/public/build_services.ts b/src/platform/plugins/shared/discover/public/build_services.ts index 612f4715c8580..1ba2e86054d93 100644 --- a/src/platform/plugins/shared/discover/public/build_services.ts +++ b/src/platform/plugins/shared/discover/public/build_services.ts @@ -55,7 +55,7 @@ import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { SettingsStart } from '@kbn/core-ui-settings-browser'; import type { ContentClient } from '@kbn/content-management-plugin/public'; import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public'; -import { memoize, noop } from 'lodash'; +import { noop } from 'lodash'; import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public'; import type { AiopsPluginStart } from '@kbn/aiops-plugin/public'; import type { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public'; @@ -68,7 +68,7 @@ import type { DiscoverContextAppLocator } from './application/context/services/l import type { DiscoverSingleDocLocator } from './application/doc/locator'; import type { DiscoverAppLocator } from '../common'; import type { ProfilesManager } from './context_awareness'; -import type { DiscoverEBTManager } from './services/discover_ebt_manager'; +import type { DiscoverEBTManager } from './plugin_imports/discover_ebt_manager'; /** * Location state of internal Discover history instance @@ -144,98 +144,96 @@ export interface DiscoverServices { embeddableEnhanced?: EmbeddableEnhancedPluginStart; } -export const buildServices = memoize( - ({ +export const buildServices = ({ + core, + plugins, + context, + locator, + contextLocator, + singleDocLocator, + history, + scopedHistory, + urlTracker, + profilesManager, + ebtManager, + setHeaderActionMenu = noop, +}: { + core: CoreStart; + plugins: DiscoverStartPlugins; + context: PluginInitializerContext; + locator: DiscoverAppLocator; + contextLocator: DiscoverContextAppLocator; + singleDocLocator: DiscoverSingleDocLocator; + history: History; + scopedHistory?: ScopedHistory; + urlTracker: UrlTracker; + profilesManager: ProfilesManager; + ebtManager: DiscoverEBTManager; + setHeaderActionMenu?: AppMountParameters['setHeaderActionMenu']; +}): DiscoverServices => { + const { usageCollection } = plugins; + const storage = new Storage(localStorage); + + return { + aiops: plugins.aiops, + application: core.application, + addBasePath: core.http.basePath.prepend, + analytics: core.analytics, + capabilities: core.application.capabilities, + chrome: core.chrome, core, - plugins, - context, + data: plugins.data, + dataVisualizer: plugins.dataVisualizer, + discoverShared: plugins.discoverShared, + docLinks: core.docLinks, + embeddable: plugins.embeddable, + i18n: core.i18n, + theme: core.theme, + userProfile: core.userProfile, + fieldFormats: plugins.fieldFormats, + filterManager: plugins.data.query.filterManager, + history, + getScopedHistory: () => scopedHistory as ScopedHistory, + setHeaderActionMenu, + dataViews: plugins.data.dataViews, + inspector: plugins.inspector, + metadata: { + branch: context.env.packageInfo.branch, + }, + navigation: plugins.navigation, + share: plugins.share, + urlForwarding: plugins.urlForwarding, + urlTracker, + timefilter: plugins.data.query.timefilter.timefilter, + toastNotifications: core.notifications.toasts, + notifications: core.notifications, + uiSettings: core.uiSettings, + settings: core.settings, + storage, + trackUiMetric: usageCollection?.reportUiCounter.bind(usageCollection, 'discover'), + dataViewFieldEditor: plugins.dataViewFieldEditor, + http: core.http, + spaces: plugins.spaces, + dataViewEditor: plugins.dataViewEditor, + triggersActionsUi: plugins.triggersActionsUi, locator, contextLocator, singleDocLocator, - history, - scopedHistory, - urlTracker, + expressions: plugins.expressions, + charts: plugins.charts, + savedObjectsTagging: plugins.savedObjectsTaggingOss?.getTaggingApi(), + savedObjectsManagement: plugins.savedObjectsManagement, + savedSearch: plugins.savedSearch, + unifiedSearch: plugins.unifiedSearch, + lens: plugins.lens, + uiActions: plugins.uiActions, + contentClient: plugins.contentManagement.client, + noDataPage: plugins.noDataPage, + observabilityAIAssistant: plugins.observabilityAIAssistant, profilesManager, ebtManager, - setHeaderActionMenu = noop, - }: { - core: CoreStart; - plugins: DiscoverStartPlugins; - context: PluginInitializerContext; - locator: DiscoverAppLocator; - contextLocator: DiscoverContextAppLocator; - singleDocLocator: DiscoverSingleDocLocator; - history: History; - scopedHistory?: ScopedHistory; - urlTracker: UrlTracker; - profilesManager: ProfilesManager; - ebtManager: DiscoverEBTManager; - setHeaderActionMenu?: AppMountParameters['setHeaderActionMenu']; - }): DiscoverServices => { - const { usageCollection } = plugins; - const storage = new Storage(localStorage); - - return { - aiops: plugins.aiops, - application: core.application, - addBasePath: core.http.basePath.prepend, - analytics: core.analytics, - capabilities: core.application.capabilities, - chrome: core.chrome, - core, - data: plugins.data, - dataVisualizer: plugins.dataVisualizer, - discoverShared: plugins.discoverShared, - docLinks: core.docLinks, - embeddable: plugins.embeddable, - i18n: core.i18n, - theme: core.theme, - userProfile: core.userProfile, - fieldFormats: plugins.fieldFormats, - filterManager: plugins.data.query.filterManager, - history, - getScopedHistory: () => scopedHistory as ScopedHistory, - setHeaderActionMenu, - dataViews: plugins.data.dataViews, - inspector: plugins.inspector, - metadata: { - branch: context.env.packageInfo.branch, - }, - navigation: plugins.navigation, - share: plugins.share, - urlForwarding: plugins.urlForwarding, - urlTracker, - timefilter: plugins.data.query.timefilter.timefilter, - toastNotifications: core.notifications.toasts, - notifications: core.notifications, - uiSettings: core.uiSettings, - settings: core.settings, - storage, - trackUiMetric: usageCollection?.reportUiCounter.bind(usageCollection, 'discover'), - dataViewFieldEditor: plugins.dataViewFieldEditor, - http: core.http, - spaces: plugins.spaces, - dataViewEditor: plugins.dataViewEditor, - triggersActionsUi: plugins.triggersActionsUi, - locator, - contextLocator, - singleDocLocator, - expressions: plugins.expressions, - charts: plugins.charts, - savedObjectsTagging: plugins.savedObjectsTaggingOss?.getTaggingApi(), - savedObjectsManagement: plugins.savedObjectsManagement, - savedSearch: plugins.savedSearch, - unifiedSearch: plugins.unifiedSearch, - lens: plugins.lens, - uiActions: plugins.uiActions, - contentClient: plugins.contentManagement.client, - noDataPage: plugins.noDataPage, - observabilityAIAssistant: plugins.observabilityAIAssistant, - profilesManager, - ebtManager, - fieldsMetadata: plugins.fieldsMetadata, - logsDataAccess: plugins.logsDataAccess, - embeddableEnhanced: plugins.embeddableEnhanced, - }; - } -); + fieldsMetadata: plugins.fieldsMetadata, + logsDataAccess: plugins.logsDataAccess, + embeddableEnhanced: plugins.embeddableEnhanced, + }; +}; diff --git a/src/platform/plugins/shared/discover/public/components/discover_container/discover_container.test.tsx b/src/platform/plugins/shared/discover/public/components/discover_container/discover_container.test.tsx index b7e8719361bc5..8f565f2e60221 100644 --- a/src/platform/plugins/shared/discover/public/components/discover_container/discover_container.test.tsx +++ b/src/platform/plugins/shared/discover/public/components/discover_container/discover_container.test.tsx @@ -39,7 +39,7 @@ const TestComponent = (props: Partial) => { overrideServices={props.overrideServices ?? mockOverrideService} customizationCallbacks={props.customizationCallbacks ?? [customizeMock]} scopedHistory={props.scopedHistory ?? getScopedHistory()!} - getDiscoverServices={getDiscoverServicesMock} + getDiscoverServices={() => Promise.resolve(getDiscoverServicesMock())} /> ); }; diff --git a/src/platform/plugins/shared/discover/public/components/discover_container/discover_container.tsx b/src/platform/plugins/shared/discover/public/components/discover_container/discover_container.tsx index f6a1e40485e5d..9aad343d18acf 100644 --- a/src/platform/plugins/shared/discover/public/components/discover_container/discover_container.tsx +++ b/src/platform/plugins/shared/discover/public/components/discover_container/discover_container.tsx @@ -13,6 +13,7 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import React, { useMemo } from 'react'; import { css } from '@emotion/react'; import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; +import useAsync from 'react-use/lib/useAsync'; import { DiscoverMainRoute } from '../../application/main'; import type { DiscoverServices } from '../../build_services'; import type { CustomizationCallback, DiscoverCustomizationContext } from '../../customizations'; @@ -26,7 +27,7 @@ export interface DiscoverContainerInternalProps { * already consumes. */ overrideServices: Partial; - getDiscoverServices: () => DiscoverServices; + getDiscoverServices: () => Promise; scopedHistory: ScopedHistory; customizationCallbacks: CustomizationCallback[]; stateStorageContainer?: IKbnUrlStateStorage; @@ -54,15 +55,20 @@ export const DiscoverContainerInternal = ({ stateStorageContainer, isLoading = false, }: DiscoverContainerInternalProps) => { - const services = useMemo(() => { + const { value: discoverServices } = useAsync(getDiscoverServices, [getDiscoverServices]); + const services = useMemo(() => { + if (!discoverServices) { + return undefined; + } + return { - ...getDiscoverServices(), + ...discoverServices, ...overrideServices, getScopedHistory: () => scopedHistory as ScopedHistory, }; - }, [getDiscoverServices, overrideServices, scopedHistory]); + }, [discoverServices, overrideServices, scopedHistory]); - if (isLoading) { + if (isLoading || !services) { return ( diff --git a/src/platform/plugins/shared/discover/public/context_awareness/__mocks__/index.tsx b/src/platform/plugins/shared/discover/public/context_awareness/__mocks__/index.tsx index 6504706365c86..174139df13ece 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/__mocks__/index.tsx +++ b/src/platform/plugins/shared/discover/public/context_awareness/__mocks__/index.tsx @@ -25,7 +25,7 @@ import { } from '../profiles'; import type { ProfileProviderServices } from '../profile_providers/profile_provider_services'; import { ProfilesManager } from '../profiles_manager'; -import { DiscoverEBTManager } from '../../services/discover_ebt_manager'; +import { DiscoverEBTManager } from '../../plugin_imports/discover_ebt_manager'; import { createLogsContextServiceMock } from '@kbn/discover-utils/src/__mocks__'; import { discoverSharedPluginMock } from '@kbn/discover-shared-plugin/public/mocks'; diff --git a/src/platform/plugins/shared/discover/public/context_awareness/profiles_manager.ts b/src/platform/plugins/shared/discover/public/context_awareness/profiles_manager.ts index 753b51d4ec5ed..6e4f51acd2cff 100644 --- a/src/platform/plugins/shared/discover/public/context_awareness/profiles_manager.ts +++ b/src/platform/plugins/shared/discover/public/context_awareness/profiles_manager.ts @@ -25,7 +25,7 @@ import type { DocumentContext, } from './profiles'; import type { ContextWithProfileId } from './profile_service'; -import type { DiscoverEBTManager } from '../services/discover_ebt_manager'; +import type { DiscoverEBTManager } from '../plugin_imports/discover_ebt_manager'; import type { AppliedProfile } from './composable_profile'; interface SerializedRootProfileParams { diff --git a/src/platform/plugins/shared/discover/public/customizations/index.ts b/src/platform/plugins/shared/discover/public/customizations/index.ts index 840f28c201197..523533b0a0b3c 100644 --- a/src/platform/plugins/shared/discover/public/customizations/index.ts +++ b/src/platform/plugins/shared/discover/public/customizations/index.ts @@ -9,6 +9,5 @@ export * from './customization_types'; export * from './customization_provider'; -export * from './defaults'; export * from './types'; export type { DiscoverCustomization, DiscoverCustomizationService } from './customization_service'; diff --git a/src/platform/plugins/shared/discover/public/embeddable/actions/view_saved_search_action.ts b/src/platform/plugins/shared/discover/public/embeddable/actions/view_saved_search_action.ts index 3511e298e4827..2a112717b384b 100644 --- a/src/platform/plugins/shared/discover/public/embeddable/actions/view_saved_search_action.ts +++ b/src/platform/plugins/shared/discover/public/embeddable/actions/view_saved_search_action.ts @@ -11,11 +11,10 @@ import type { ApplicationStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import type { EmbeddableApiContext } from '@kbn/presentation-publishing'; import type { Action } from '@kbn/ui-actions-plugin/public'; - import type { DiscoverAppLocator } from '../../../common'; import { getDiscoverLocatorParams } from '../utils/get_discover_locator_params'; - -export const ACTION_VIEW_SAVED_SEARCH = 'ACTION_VIEW_SAVED_SEARCH'; +import { compatibilityCheck } from './view_saved_search_compatibility_check'; +import { ACTION_VIEW_SAVED_SEARCH } from '../constants'; export class ViewSavedSearchAction implements Action { public id = ACTION_VIEW_SAVED_SEARCH; @@ -28,7 +27,6 @@ export class ViewSavedSearchAction implements Action { ) {} async execute({ embeddable }: EmbeddableApiContext): Promise { - const { compatibilityCheck } = await import('./view_saved_search_compatibility_check'); if (!compatibilityCheck(embeddable)) { return; } @@ -53,7 +51,7 @@ export class ViewSavedSearchAction implements Action { (capabilities.discover_v2.show as boolean) || (capabilities.discover_v2.save as boolean); if (!hasDiscoverPermissions) return false; // early return to delay async import until absolutely necessary - const { compatibilityCheck } = await import('./view_saved_search_compatibility_check'); + return compatibilityCheck(embeddable); } } diff --git a/src/platform/plugins/shared/discover/public/embeddable/components/search_embeddable_grid_component.tsx b/src/platform/plugins/shared/discover/public/embeddable/components/search_embeddable_grid_component.tsx index 88339e6ba4bd5..48d7324f38f47 100644 --- a/src/platform/plugins/shared/discover/public/embeddable/components/search_embeddable_grid_component.tsx +++ b/src/platform/plugins/shared/discover/public/embeddable/components/search_embeddable_grid_component.tsx @@ -25,7 +25,7 @@ import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import type { DiscoverGridSettings } from '@kbn/saved-search-plugin/common'; import useObservable from 'react-use/lib/useObservable'; import { useDiscoverServices } from '../../hooks/use_discover_services'; -import { getSortForEmbeddable } from '../../utils'; +import { getSortForEmbeddable } from '../../utils/sorting'; import { getAllowedSampleSize, getMaxAllowedSampleSize } from '../../utils/get_allowed_sample_size'; import { SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID } from '../constants'; import { isEsqlMode } from '../initialize_fetch'; diff --git a/src/platform/plugins/shared/discover/public/embeddable/constants.ts b/src/platform/plugins/shared/discover/public/embeddable/constants.ts index cfccf18bf45ed..64028b9cf1b28 100644 --- a/src/platform/plugins/shared/discover/public/embeddable/constants.ts +++ b/src/platform/plugins/shared/discover/public/embeddable/constants.ts @@ -23,6 +23,8 @@ export const SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER: Trigger = { 'This trigger is used to replace the cell actions for Discover session embeddable grid.', } as const; +export const ACTION_VIEW_SAVED_SEARCH = 'ACTION_VIEW_SAVED_SEARCH'; + export const DEFAULT_HEADER_ROW_HEIGHT_LINES = 3; /** This constant refers to the parts of the saved search state that can be edited from a dashboard */ diff --git a/src/platform/plugins/shared/discover/public/embeddable/get_on_add_search_embeddable.ts b/src/platform/plugins/shared/discover/public/embeddable/get_on_add_search_embeddable.ts new file mode 100644 index 0000000000000..2dac96b88683f --- /dev/null +++ b/src/platform/plugins/shared/discover/public/embeddable/get_on_add_search_embeddable.ts @@ -0,0 +1,32 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; +import { SEARCH_EMBEDDABLE_TYPE } from '@kbn/discover-utils'; +import type { DiscoverServices } from '../build_services'; +import { deserializeState } from './utils/serialization_utils'; + +export const getOnAddSearchEmbeddable = + ( + discoverServices: DiscoverServices + ): Parameters[0]['onAdd'] => + async (container, savedObject) => { + const initialState = await deserializeState({ + serializedState: { + rawState: { savedObjectId: savedObject.id }, + references: savedObject.references, + }, + discoverServices, + }); + + container.addNewPanel({ + panelType: SEARCH_EMBEDDABLE_TYPE, + initialState, + }); + }; diff --git a/src/platform/plugins/shared/discover/public/embeddable/utils/serialization_utils.ts b/src/platform/plugins/shared/discover/public/embeddable/utils/serialization_utils.ts index 89d3d3d71692b..0bab4579bb61b 100644 --- a/src/platform/plugins/shared/discover/public/embeddable/utils/serialization_utils.ts +++ b/src/platform/plugins/shared/discover/public/embeddable/utils/serialization_utils.ts @@ -9,17 +9,18 @@ import { omit, pick } from 'lodash'; import deepEqual from 'react-fast-compare'; - import type { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; import type { SerializedTimeRange, SerializedTitles, SerializedPanelState, } from '@kbn/presentation-publishing'; -import type { SavedSearch, SavedSearchAttributes } from '@kbn/saved-search-plugin/common'; -import { toSavedSearchAttributes } from '@kbn/saved-search-plugin/common'; +import { + toSavedSearchAttributes, + type SavedSearch, + type SavedSearchAttributes, +} from '@kbn/saved-search-plugin/common'; import type { SavedSearchUnwrapResult } from '@kbn/saved-search-plugin/public'; - import type { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public/plugin'; import { extract, inject } from '../../../common/embeddable/search_inject_extract'; import type { DiscoverServices } from '../../build_services'; diff --git a/src/platform/plugins/shared/discover/public/global_search/search_provider.ts b/src/platform/plugins/shared/discover/public/global_search/search_provider.ts deleted file mode 100644 index e2e7502933682..0000000000000 --- a/src/platform/plugins/shared/discover/public/global_search/search_provider.ts +++ /dev/null @@ -1,90 +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", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import type { ApplicationStart } from '@kbn/core/public'; -import { from, of } from 'rxjs'; -import { i18n } from '@kbn/i18n'; -import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; -import type { GlobalSearchResultProvider } from '@kbn/global-search-plugin/public'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { getInitialESQLQuery } from '@kbn/esql-utils'; -import type { DiscoverAppLocator } from '../../common'; - -/** - * Global search provider adding an ES|QL and ESQL entry. - * This is necessary because ES|QL is part of the Discover application. - * - * It navigates to Discover with a default query extracted from the default dataview - */ -export const getESQLSearchProvider: ( - isESQLEnabled: boolean, - uiCapabilities: Promise, - data: Promise, - locator?: DiscoverAppLocator -) => GlobalSearchResultProvider = (isESQLEnabled, uiCapabilities, data, locator) => ({ - id: 'esql', - find: ({ term = '', types, tags }) => { - if (tags || (types && !types.includes('application')) || !locator || !isESQLEnabled) { - return of([]); - } - - return from( - Promise.all([uiCapabilities, data]).then(async ([{ navLinks }, { dataViews }]) => { - if (!navLinks.discover) { - return []; - } - const title = i18n.translate('discover.globalSearch.esqlSearchTitle', { - defaultMessage: 'Create ES|QL queries', - description: 'ES|QL is a product name and should not be translated', - }); - const defaultDataView = await dataViews.getDefaultDataView({ displayErrors: false }); - - if (!defaultDataView) { - return []; - } - - const params = { - query: { - esql: getInitialESQLQuery(defaultDataView), - }, - dataViewSpec: defaultDataView?.toSpec(), - }; - - const discoverLocation = await locator?.getLocation(params); - - term = term.toLowerCase(); - let score = 0; - - if (term === 'es|ql' || term === 'esql') { - score = 100; - } else if (term && ('es|ql'.includes(term) || 'esql'.includes(term))) { - score = 90; - } - - if (score === 0) return []; - - return [ - { - id: 'esql', - title, - type: 'application', - icon: 'logoKibana', - meta: { - categoryId: DEFAULT_APP_CATEGORIES.kibana.id, - categoryLabel: DEFAULT_APP_CATEGORIES.kibana.label, - }, - score, - url: `/app/${discoverLocation.app}${discoverLocation.path}`, - }, - ]; - }) - ); - }, - getSearchableTypes: () => ['application'], -}); diff --git a/src/platform/plugins/shared/discover/public/index.ts b/src/platform/plugins/shared/discover/public/index.ts index a359184f3b4a6..a99fe3aeef332 100644 --- a/src/platform/plugins/shared/discover/public/index.ts +++ b/src/platform/plugins/shared/discover/public/index.ts @@ -39,5 +39,6 @@ export { type SearchEmbeddableApi, type NonPersistedDisplayOptions, } from './embeddable'; -export { loadSharingDataHelpers } from './utils'; export type { DiscoverServices } from './build_services'; + +export const loadSharingDataHelpers = () => import('./utils/get_sharing_data'); diff --git a/src/platform/plugins/shared/discover/public/plugin.tsx b/src/platform/plugins/shared/discover/public/plugin.tsx index b2408874f5e8f..33bda138aa46a 100644 --- a/src/platform/plugins/shared/discover/public/plugin.tsx +++ b/src/platform/plugins/shared/discover/public/plugin.tsx @@ -20,31 +20,34 @@ import type { } from '@kbn/core/public'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; import { ENABLE_ESQL } from '@kbn/esql-utils'; -import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; import { SEARCH_EMBEDDABLE_TYPE } from '@kbn/discover-utils'; -import type { SavedSearchAttributes } from '@kbn/saved-search-plugin/common'; import { SavedSearchType } from '@kbn/saved-search-plugin/common'; +import type { SavedSearchAttributes } from '@kbn/saved-search-plugin/common'; import { i18n } from '@kbn/i18n'; -import { PLUGIN_ID } from '../common'; -import { registerFeature } from './register_feature'; +import { once } from 'lodash'; +import { DISCOVER_ESQL_LOCATOR } from '@kbn/deeplinks-analytics'; +import { DISCOVER_APP_LOCATOR, PLUGIN_ID, type DiscoverAppLocator } from '../common'; +import { + DISCOVER_CONTEXT_APP_LOCATOR, + type DiscoverContextAppLocator, +} from './application/context/services/locator'; +import { + DISCOVER_SINGLE_DOC_LOCATOR, + type DiscoverSingleDocLocator, +} from './application/doc/locator'; +import { registerFeature } from './plugin_imports/register_feature'; import type { UrlTracker } from './build_services'; -import { buildServices } from './build_services'; -import { ViewSavedSearchAction } from './embeddable/actions/view_saved_search_action'; import { initializeKbnUrlTracking } from './utils/initialize_kbn_url_tracking'; -import type { DiscoverContextAppLocator } from './application/context/services/locator'; -import { DiscoverContextAppLocatorDefinition } from './application/context/services/locator'; -import type { DiscoverSingleDocLocator } from './application/doc/locator'; -import { DiscoverSingleDocLocatorDefinition } from './application/doc/locator'; -import type { DiscoverAppLocator } from '../common'; -import { DiscoverAppLocatorDefinition, DiscoverESQLLocatorDefinition } from '../common'; -import { defaultCustomizationContext } from './customizations'; -import { SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER } from './embeddable/constants'; +import { defaultCustomizationContext } from './customizations/defaults'; +import { + SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER, + ACTION_VIEW_SAVED_SEARCH, +} from './embeddable/constants'; import { DiscoverContainerInternal, type DiscoverContainerProps, } from './components/discover_container'; -import { getESQLSearchProvider } from './global_search/search_provider'; -import { HistoryService } from './history_service'; +import { getESQLSearchProvider } from './plugin_imports/search_provider'; import type { ConfigSchema, ExperimentalFeatures } from '../server/config'; import type { DiscoverSetup, @@ -52,13 +55,14 @@ import type { DiscoverStart, DiscoverStartPlugins, } from './types'; -import { deserializeState } from './embeddable/utils/serialization_utils'; import { DISCOVER_CELL_ACTIONS_TRIGGER } from './context_awareness/types'; -import { RootProfileService } from './context_awareness/profiles/root_profile'; -import { DataSourceProfileService } from './context_awareness/profiles/data_source_profile'; -import { DocumentProfileService } from './context_awareness/profiles/document_profile'; -import { ProfilesManager } from './context_awareness/profiles_manager'; -import { DiscoverEBTManager } from './services/discover_ebt_manager'; +import type { + DiscoverEBTContextProps, + DiscoverEBTManager, +} from './plugin_imports/discover_ebt_manager'; +import type { ProfilesManager } from './context_awareness'; +import { forwardLegacyUrls } from './plugin_imports/forward_legacy_urls'; +import { registerDiscoverEBTManagerAnalytics } from './plugin_imports/discover_ebt_manager_registrations'; /** * Contains Discover, one of the oldest parts of Kibana @@ -67,8 +71,10 @@ import { DiscoverEBTManager } from './services/discover_ebt_manager'; export class DiscoverPlugin implements Plugin { + private readonly discoverEbtContext$ = new BehaviorSubject({ + discoverProfiles: [], + }); private readonly appStateUpdater = new BehaviorSubject(() => ({})); - private readonly historyService = new HistoryService(); private readonly experimentalFeatures: ExperimentalFeatures; private scopedHistory?: ScopedHistory; @@ -96,35 +102,41 @@ export class DiscoverPlugin if (plugins.share) { const useHash = core.uiSettings.get('state:storeInSessionStorage'); - this.locator = plugins.share.url.locators.create( - new DiscoverAppLocatorDefinition({ useHash, setStateToKbnUrl }) - ); - this.contextLocator = plugins.share.url.locators.create( - new DiscoverContextAppLocatorDefinition({ useHash }) - ); - this.singleDocLocator = plugins.share.url.locators.create( - new DiscoverSingleDocLocatorDefinition() - ); + this.locator = plugins.share.url.locators.create({ + id: DISCOVER_APP_LOCATOR, + getLocation: async (params) => { + const { appLocatorGetLocation } = await getLocators(); + return appLocatorGetLocation({ useHash }, params); + }, + }); + + this.contextLocator = plugins.share.url.locators.create({ + id: DISCOVER_CONTEXT_APP_LOCATOR, + getLocation: async (params) => { + const { contextAppLocatorGetLocation } = await getLocators(); + return contextAppLocatorGetLocation({ useHash }, params); + }, + }); + + this.singleDocLocator = plugins.share.url.locators.create({ + id: DISCOVER_SINGLE_DOC_LOCATOR, + getLocation: async (params) => { + const { singleDocLocatorGetLocation } = await getLocators(); + return singleDocLocatorGetLocation(params); + }, + }); } if (plugins.globalSearch) { - const enableESQL = core.uiSettings.get(ENABLE_ESQL); plugins.globalSearch.registerResultProvider( - getESQLSearchProvider( - enableESQL, - core.getStartServices().then( - ([ - { - application: { capabilities }, - }, - ]) => capabilities - ), - core.getStartServices().then((deps) => { - const { data } = deps[1]; - return data; - }), - this.locator - ) + getESQLSearchProvider({ + isESQLEnabled: core.uiSettings.get(ENABLE_ESQL), + locator: this.locator, + getServices: async () => { + const [coreStart, startPlugins] = await core.getStartServices(); + return [coreStart, startPlugins]; + }, + }) ); } @@ -146,13 +158,15 @@ export class DiscoverPlugin this.urlTracker = { setTrackedUrl, restorePreviousUrl, setTrackingEnabled }; this.stopUrlTracking = stopUrlTracker; - const ebtManager = new DiscoverEBTManager(); - ebtManager.initialize({ - core, - shouldInitializeCustomContext: true, - shouldInitializeCustomEvents: true, + const getEbtManager = once(async () => { + const { DiscoverEBTManager } = await getSharedServices(); + const ebtManager = new DiscoverEBTManager(); + ebtManager.initialize({ core, discoverEbtContext$: this.discoverEbtContext$ }); + return ebtManager; }); + registerDiscoverEBTManagerAnalytics(core, this.discoverEbtContext$); + core.application.register({ id: PLUGIN_ID, title: 'Discover', @@ -168,7 +182,7 @@ export class DiscoverPlugin // Store the current scoped history so initializeKbnUrlTracking can access it this.scopedHistory = params.history; - this.historyService.syncHistoryLocations(); + (await getHistoryService()).syncHistoryLocations(); appMounted(); // dispatch synthetic hash change event to update hash history objects @@ -177,24 +191,14 @@ export class DiscoverPlugin window.dispatchEvent(new HashChangeEvent('hashchange')); }); + const ebtManager = await getEbtManager(); ebtManager.onDiscoverAppMounted(); - const services = buildServices({ + const services = await this.getDiscoverServicesWithProfiles({ core: coreStart, plugins: discoverStartPlugins, - context: this.initializerContext, - locator: this.locator!, - contextLocator: this.contextLocator!, - singleDocLocator: this.singleDocLocator!, - history: this.historyService.getHistory(), - scopedHistory: this.scopedHistory, - urlTracker: this.urlTracker!, - profilesManager: await this.createProfilesManager({ - core: coreStart, - plugins: discoverStartPlugins, - ebtManager, - }), ebtManager, + scopedHistory: this.scopedHistory, setHeaderActionMenu: params.setHeaderActionMenu, }); @@ -222,63 +226,48 @@ export class DiscoverPlugin }, }); - plugins.urlForwarding.forwardApp('doc', 'discover', (path) => { - return `#${path}`; - }); - plugins.urlForwarding.forwardApp('context', 'discover', (path) => { - const urlParts = path.split('/'); - // take care of urls containing legacy url, those split in the following way - // ["", "context", indexPatternId, _type, id + params] - if (urlParts[4]) { - // remove _type part - const newPath = [...urlParts.slice(0, 3), ...urlParts.slice(4)].join('/'); - return `#${newPath}`; - } - return `#${path}`; - }); - plugins.urlForwarding.forwardApp('discover', 'discover', (path) => { - const [, id, tail] = /discover\/([^\?]+)(.*)/.exec(path) || []; - if (!id) { - return `#${path.replace('/discover', '') || '/'}`; - } - return `#/view/${id}${tail || ''}`; - }); - if (plugins.home) { registerFeature(plugins.home); } + forwardLegacyUrls(plugins.urlForwarding); this.registerEmbeddable(core, plugins); return { locator: this.locator }; } start(core: CoreStart, plugins: DiscoverStartPlugins): DiscoverStart { - const viewSavedSearchAction = new ViewSavedSearchAction(core.application, this.locator!); - - plugins.uiActions.addTriggerAction('CONTEXT_MENU_TRIGGER', viewSavedSearchAction); + plugins.uiActions.addTriggerActionAsync( + 'CONTEXT_MENU_TRIGGER', + ACTION_VIEW_SAVED_SEARCH, + async () => { + const { ViewSavedSearchAction } = await getEmbeddableServices(); + return new ViewSavedSearchAction(core.application, this.locator!); + } + ); plugins.uiActions.registerTrigger(SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER); plugins.uiActions.registerTrigger(DISCOVER_CELL_ACTIONS_TRIGGER); const isEsqlEnabled = core.uiSettings.get(ENABLE_ESQL); if (plugins.share && this.locator && isEsqlEnabled) { - plugins.share?.url.locators.create( - new DiscoverESQLLocatorDefinition({ - discoverAppLocator: this.locator, - dataViews: plugins.dataViews, - }) - ); + const discoverAppLocator = this.locator; + plugins.share?.url.locators.create({ + id: DISCOVER_ESQL_LOCATOR, + getLocation: async () => { + const { esqlLocatorGetLocation } = await getLocators(); + return esqlLocatorGetLocation({ + discoverAppLocator, + dataViews: plugins.dataViews, + }); + }, + }); } - const getDiscoverServicesInternal = () => { - const ebtManager = new DiscoverEBTManager(); // It is not initialized outside of Discover - return this.getDiscoverServices( - core, - plugins, - this.createEmptyProfilesManager({ ebtManager }), - ebtManager - ); + const getDiscoverServicesInternal = async () => { + const ebtManager = await getEmptyEbtManager(); + const { profilesManager } = await this.createProfileServices(ebtManager); + return this.getDiscoverServices({ core, plugins, profilesManager, ebtManager }); }; return { @@ -295,62 +284,88 @@ export class DiscoverPlugin } } - private createProfileServices() { + private async createProfileServices(ebtManager: DiscoverEBTManager) { + const { + RootProfileService, + DataSourceProfileService, + DocumentProfileService, + ProfilesManager, + } = await getSharedServices(); + const rootProfileService = new RootProfileService(); const dataSourceProfileService = new DataSourceProfileService(); const documentProfileService = new DocumentProfileService(); + const profilesManager = new ProfilesManager( + rootProfileService, + dataSourceProfileService, + documentProfileService, + ebtManager + ); - return { rootProfileService, dataSourceProfileService, documentProfileService }; + return { + rootProfileService, + dataSourceProfileService, + documentProfileService, + profilesManager, + }; } - private async createProfilesManager({ + private async getDiscoverServicesWithProfiles({ core, plugins, ebtManager, + scopedHistory, + setHeaderActionMenu, }: { core: CoreStart; plugins: DiscoverStartPlugins; ebtManager: DiscoverEBTManager; + scopedHistory?: ScopedHistory; + setHeaderActionMenu?: AppMountParameters['setHeaderActionMenu']; }) { - const { registerProfileProviders } = await import('./context_awareness/profile_providers'); - const { rootProfileService, dataSourceProfileService, documentProfileService } = - this.createProfileServices(); - - const enabledExperimentalProfileIds = this.experimentalFeatures.enabledProfiles ?? []; - - const profilesManager = new ProfilesManager( + const { rootProfileService, dataSourceProfileService, documentProfileService, - ebtManager - ); + profilesManager, + } = await this.createProfileServices(ebtManager); + const services = await this.getDiscoverServices({ + core, + plugins, + profilesManager, + ebtManager, + scopedHistory, + setHeaderActionMenu, + }); + const { registerProfileProviders } = await import('./context_awareness/profile_providers'); await registerProfileProviders({ rootProfileService, dataSourceProfileService, documentProfileService, - enabledExperimentalProfileIds, - services: this.getDiscoverServices(core, plugins, profilesManager, ebtManager), + enabledExperimentalProfileIds: this.experimentalFeatures.enabledProfiles ?? [], + services, }); - return profilesManager; - } - - private createEmptyProfilesManager({ ebtManager }: { ebtManager: DiscoverEBTManager }) { - return new ProfilesManager( - new RootProfileService(), - new DataSourceProfileService(), - new DocumentProfileService(), - ebtManager - ); + return services; } - private getDiscoverServices = ( - core: CoreStart, - plugins: DiscoverStartPlugins, - profilesManager: ProfilesManager, - ebtManager: DiscoverEBTManager - ) => { + private getDiscoverServices = async ({ + core, + plugins, + profilesManager, + ebtManager, + scopedHistory, + setHeaderActionMenu, + }: { + core: CoreStart; + plugins: DiscoverStartPlugins; + profilesManager: ProfilesManager; + ebtManager: DiscoverEBTManager; + scopedHistory?: ScopedHistory; + setHeaderActionMenu?: AppMountParameters['setHeaderActionMenu']; + }) => { + const { buildServices } = await getSharedServices(); return buildServices({ core, plugins, @@ -358,16 +373,16 @@ export class DiscoverPlugin locator: this.locator!, contextLocator: this.contextLocator!, singleDocLocator: this.singleDocLocator!, - history: this.historyService.getHistory(), + history: (await getHistoryService()).getHistory(), + scopedHistory, urlTracker: this.urlTracker!, profilesManager, ebtManager, + setHeaderActionMenu, }); }; private registerEmbeddable(core: CoreSetup, plugins: DiscoverSetupPlugins) { - const ebtManager = new DiscoverEBTManager(); // It is not initialized outside of Discover - const getStartServices = async () => { const [coreStart, deps] = await core.getStartServices(); return { @@ -378,29 +393,19 @@ export class DiscoverPlugin const getDiscoverServicesForEmbeddable = async () => { const [coreStart, deps] = await core.getStartServices(); - - const profilesManager = await this.createProfilesManager({ + const ebtManager = await getEmptyEbtManager(); + return this.getDiscoverServicesWithProfiles({ core: coreStart, plugins: deps, ebtManager, }); - return this.getDiscoverServices(coreStart, deps, profilesManager, ebtManager); }; plugins.embeddable.registerAddFromLibraryType({ - onAdd: async (container, savedObject) => { + onAdd: async (...params) => { const services = await getDiscoverServicesForEmbeddable(); - const initialState = await deserializeState({ - serializedState: { - rawState: { savedObjectId: savedObject.id }, - references: savedObject.references, - }, - discoverServices: services, - }); - container.addNewPanel({ - panelType: SEARCH_EMBEDDABLE_TYPE, - initialState, - }); + const { getOnAddSearchEmbeddable } = await getEmbeddableServices(); + return getOnAddSearchEmbeddable(services)(...params); }, savedObjectType: SavedSearchType, savedObjectName: i18n.translate('discover.savedSearch.savedObjectName', { @@ -413,7 +418,7 @@ export class DiscoverPlugin const [startServices, discoverServices, { getSearchEmbeddableFactory }] = await Promise.all([ getStartServices(), getDiscoverServicesForEmbeddable(), - import('./embeddable/get_search_embeddable_factory'), + getEmbeddableServices(), ]); return getSearchEmbeddableFactory({ @@ -423,3 +428,17 @@ export class DiscoverPlugin }); } } + +const getLocators = () => import('./plugin_imports/locators'); +const getEmbeddableServices = () => import('./plugin_imports/embeddable_services'); +const getSharedServices = () => import('./plugin_imports/shared_services'); + +const getHistoryService = once(async () => { + const { HistoryService } = await getSharedServices(); + return new HistoryService(); +}); + +const getEmptyEbtManager = once(async () => { + const { DiscoverEBTManager } = await getSharedServices(); + return new DiscoverEBTManager(); // It is not initialized outside of Discover +}); diff --git a/src/platform/plugins/shared/discover/public/services/discover_ebt_manager.test.ts b/src/platform/plugins/shared/discover/public/plugin_imports/discover_ebt_manager.test.ts similarity index 91% rename from src/platform/plugins/shared/discover/public/services/discover_ebt_manager.test.ts rename to src/platform/plugins/shared/discover/public/plugin_imports/discover_ebt_manager.test.ts index a7530bf6b2ece..791077cdda0be 100644 --- a/src/platform/plugins/shared/discover/public/services/discover_ebt_manager.test.ts +++ b/src/platform/plugins/shared/discover/public/plugin_imports/discover_ebt_manager.test.ts @@ -9,12 +9,14 @@ import { BehaviorSubject } from 'rxjs'; import { coreMock } from '@kbn/core/public/mocks'; -import { DiscoverEBTManager } from './discover_ebt_manager'; -import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; +import { type DiscoverEBTContextProps, DiscoverEBTManager } from './discover_ebt_manager'; +import { registerDiscoverEBTManagerAnalytics } from './discover_ebt_manager_registrations'; import { ContextualProfileLevel } from '../context_awareness/profiles_manager'; +import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; describe('DiscoverEBTManager', () => { let discoverEBTContextManager: DiscoverEBTManager; + let discoverEbtContext$: BehaviorSubject; const coreSetupMock = coreMock.createSetup(); @@ -32,15 +34,19 @@ describe('DiscoverEBTManager', () => { beforeEach(() => { discoverEBTContextManager = new DiscoverEBTManager(); + discoverEbtContext$ = new BehaviorSubject({ + discoverProfiles: [], + }); (coreSetupMock.analytics.reportEvent as jest.Mock).mockClear(); }); describe('register', () => { it('should register the context provider and custom events', () => { + registerDiscoverEBTManagerAnalytics(coreSetupMock, discoverEbtContext$); + discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: true, - shouldInitializeCustomEvents: true, + discoverEbtContext$, }); expect(coreSetupMock.analytics.registerContextProvider).toHaveBeenCalledWith({ @@ -94,8 +100,7 @@ describe('DiscoverEBTManager', () => { const dscProfiles2 = ['profile21', 'profile22']; discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: true, - shouldInitializeCustomEvents: false, + discoverEbtContext$, }); discoverEBTContextManager.onDiscoverAppMounted(); @@ -111,8 +116,7 @@ describe('DiscoverEBTManager', () => { const dscProfiles2 = ['profile1', 'profile2']; discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: true, - shouldInitializeCustomEvents: false, + discoverEbtContext$, }); discoverEBTContextManager.onDiscoverAppMounted(); @@ -127,8 +131,7 @@ describe('DiscoverEBTManager', () => { const dscProfiles = ['profile1', 'profile2']; discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: true, - shouldInitializeCustomEvents: false, + discoverEbtContext$, }); discoverEBTContextManager.updateProfilesContextWith(dscProfiles); @@ -139,8 +142,7 @@ describe('DiscoverEBTManager', () => { const dscProfiles = ['profile1', 'profile2']; discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: true, - shouldInitializeCustomEvents: false, + discoverEbtContext$, }); discoverEBTContextManager.onDiscoverAppMounted(); discoverEBTContextManager.updateProfilesContextWith(dscProfiles); @@ -159,8 +161,7 @@ describe('DiscoverEBTManager', () => { it('should track the field usage when a field is added to the table', async () => { discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: false, - shouldInitializeCustomEvents: true, + discoverEbtContext$, }); await discoverEBTContextManager.trackDataTableSelection({ @@ -186,8 +187,7 @@ describe('DiscoverEBTManager', () => { it('should track the field usage when a field is removed from the table', async () => { discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: false, - shouldInitializeCustomEvents: true, + discoverEbtContext$, }); await discoverEBTContextManager.trackDataTableRemoval({ @@ -213,8 +213,7 @@ describe('DiscoverEBTManager', () => { it('should track the field usage when a filter is created', async () => { discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: false, - shouldInitializeCustomEvents: true, + discoverEbtContext$, }); await discoverEBTContextManager.trackFilterAddition({ @@ -246,8 +245,7 @@ describe('DiscoverEBTManager', () => { it('should track the event when a next contextual profile is resolved', async () => { discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: false, - shouldInitializeCustomEvents: true, + discoverEbtContext$, }); discoverEBTContextManager.trackContextualProfileResolvedEvent({ @@ -296,8 +294,7 @@ describe('DiscoverEBTManager', () => { it('should not trigger duplicate requests', async () => { discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: false, - shouldInitializeCustomEvents: true, + discoverEbtContext$, }); discoverEBTContextManager.trackContextualProfileResolvedEvent({ @@ -325,8 +322,7 @@ describe('DiscoverEBTManager', () => { it('should trigger similar requests after remount', async () => { discoverEBTContextManager.initialize({ core: coreSetupMock, - shouldInitializeCustomContext: false, - shouldInitializeCustomEvents: true, + discoverEbtContext$, }); discoverEBTContextManager.trackContextualProfileResolvedEvent({ diff --git a/src/platform/plugins/shared/discover/public/services/discover_ebt_manager.ts b/src/platform/plugins/shared/discover/public/plugin_imports/discover_ebt_manager.ts similarity index 64% rename from src/platform/plugins/shared/discover/public/services/discover_ebt_manager.ts rename to src/platform/plugins/shared/discover/public/plugin_imports/discover_ebt_manager.ts index afa93f4d55743..74df6252d67bd 100644 --- a/src/platform/plugins/shared/discover/public/services/discover_ebt_manager.ts +++ b/src/platform/plugins/shared/discover/public/plugin_imports/discover_ebt_manager.ts @@ -7,19 +7,20 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { BehaviorSubject } from 'rxjs'; +import type { BehaviorSubject } from 'rxjs'; import { isEqual } from 'lodash'; import type { CoreSetup } from '@kbn/core-lifecycle-browser'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; import { ContextualProfileLevel } from '../context_awareness/profiles_manager'; - -/** - * Field usage events i.e. when a field is selected in the data table, removed from the data table, or a filter is added - */ -const FIELD_USAGE_EVENT_TYPE = 'discover_field_usage'; -const FIELD_USAGE_EVENT_NAME = 'eventName'; -const FIELD_USAGE_FIELD_NAME = 'fieldName'; -const FIELD_USAGE_FILTER_OPERATION = 'filterOperation'; +import { + CONTEXTUAL_PROFILE_ID, + CONTEXTUAL_PROFILE_LEVEL, + CONTEXTUAL_PROFILE_RESOLVED_EVENT_TYPE, + FIELD_USAGE_EVENT_NAME, + FIELD_USAGE_EVENT_TYPE, + FIELD_USAGE_FIELD_NAME, + FIELD_USAGE_FILTER_OPERATION, +} from './discover_ebt_manager_registrations'; type FilterOperation = '+' | '-' | '_exists_'; @@ -34,14 +35,6 @@ interface FieldUsageEventData { [FIELD_USAGE_FILTER_OPERATION]?: FilterOperation; } -/** - * Contextual profile resolved event i.e. when a different contextual profile is resolved at root, data source, or document level - * Duplicated events for the same profile level will not be sent. - */ -const CONTEXTUAL_PROFILE_RESOLVED_EVENT_TYPE = 'discover_profile_resolved'; -const CONTEXTUAL_PROFILE_LEVEL = 'contextLevel'; -const CONTEXTUAL_PROFILE_ID = 'profileId'; - interface ContextualProfileResolvedEventData { [CONTEXTUAL_PROFILE_LEVEL]: ContextualProfileLevel; [CONTEXTUAL_PROFILE_ID]: string; @@ -73,85 +66,13 @@ export class DiscoverEBTManager { // https://docs.elastic.dev/telemetry/collection/event-based-telemetry public initialize({ core, - shouldInitializeCustomContext, - shouldInitializeCustomEvents, + discoverEbtContext$, }: { core: CoreSetup; - shouldInitializeCustomContext: boolean; - shouldInitializeCustomEvents: boolean; + discoverEbtContext$: BehaviorSubject; }) { - if (shouldInitializeCustomContext) { - // Register Discover specific context to be used in EBT - const context$ = new BehaviorSubject({ - discoverProfiles: [], - }); - core.analytics.registerContextProvider({ - name: 'discover_context', - context$, - schema: { - discoverProfiles: { - type: 'array', - items: { - type: 'keyword', - _meta: { - description: 'List of active Discover context awareness profiles', - }, - }, - }, - // If we decide to extend EBT context with more properties, we can do it here - }, - }); - this.customContext$ = context$; - } - - if (shouldInitializeCustomEvents) { - // Register Discover events to be used with EBT - core.analytics.registerEventType({ - eventType: FIELD_USAGE_EVENT_TYPE, - schema: { - [FIELD_USAGE_EVENT_NAME]: { - type: 'keyword', - _meta: { - description: - 'The name of the event that is tracked in the metrics i.e. dataTableSelection, dataTableRemoval', - }, - }, - [FIELD_USAGE_FIELD_NAME]: { - type: 'keyword', - _meta: { - description: "Field name if it's a part of ECS schema", - optional: true, - }, - }, - [FIELD_USAGE_FILTER_OPERATION]: { - type: 'keyword', - _meta: { - description: "Operation type when a filter is added i.e. '+', '-', '_exists_'", - optional: true, - }, - }, - }, - }); - core.analytics.registerEventType({ - eventType: CONTEXTUAL_PROFILE_RESOLVED_EVENT_TYPE, - schema: { - [CONTEXTUAL_PROFILE_LEVEL]: { - type: 'keyword', - _meta: { - description: - 'The context level at which it was resolved i.e. rootLevel, dataSourceLevel, documentLevel', - }, - }, - [CONTEXTUAL_PROFILE_ID]: { - type: 'keyword', - _meta: { - description: 'The resolved name of the active profile', - }, - }, - }, - }); - this.reportEvent = core.analytics.reportEvent; - } + this.customContext$ = discoverEbtContext$; + this.reportEvent = core.analytics.reportEvent; } public onDiscoverAppMounted() { diff --git a/src/platform/plugins/shared/discover/public/plugin_imports/discover_ebt_manager_registrations.ts b/src/platform/plugins/shared/discover/public/plugin_imports/discover_ebt_manager_registrations.ts new file mode 100644 index 0000000000000..83f17d2e8db20 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/plugin_imports/discover_ebt_manager_registrations.ts @@ -0,0 +1,103 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { CoreSetup } from '@kbn/core/public'; +import type { BehaviorSubject } from 'rxjs'; +import type { DiscoverStartPlugins } from '../types'; +import type { DiscoverEBTContextProps } from './discover_ebt_manager'; + +/** + * Field usage events i.e. when a field is selected in the data table, removed from the data table, or a filter is added + */ +export const FIELD_USAGE_EVENT_TYPE = 'discover_field_usage'; +export const FIELD_USAGE_EVENT_NAME = 'eventName'; +export const FIELD_USAGE_FIELD_NAME = 'fieldName'; +export const FIELD_USAGE_FILTER_OPERATION = 'filterOperation'; + +/** + * Contextual profile resolved event i.e. when a different contextual profile is resolved at root, data source, or document level + * Duplicated events for the same profile level will not be sent. + */ +export const CONTEXTUAL_PROFILE_RESOLVED_EVENT_TYPE = 'discover_profile_resolved'; +export const CONTEXTUAL_PROFILE_LEVEL = 'contextLevel'; +export const CONTEXTUAL_PROFILE_ID = 'profileId'; + +/** + * This function is statically imported since analytics registrations must happen at setup, + * while the EBT manager is loaded dynamically when needed to avoid page load bundle bloat + */ +export const registerDiscoverEBTManagerAnalytics = ( + core: CoreSetup, + discoverEbtContext$: BehaviorSubject +) => { + // Register Discover specific context to be used in EBT + core.analytics.registerContextProvider({ + name: 'discover_context', + context$: discoverEbtContext$, + schema: { + discoverProfiles: { + type: 'array', + items: { + type: 'keyword', + _meta: { + description: 'List of active Discover context awareness profiles', + }, + }, + }, + // If we decide to extend EBT context with more properties, we can do it here + }, + }); + + // Register Discover events to be used with EBT + core.analytics.registerEventType({ + eventType: FIELD_USAGE_EVENT_TYPE, + schema: { + [FIELD_USAGE_EVENT_NAME]: { + type: 'keyword', + _meta: { + description: + 'The name of the event that is tracked in the metrics i.e. dataTableSelection, dataTableRemoval', + }, + }, + [FIELD_USAGE_FIELD_NAME]: { + type: 'keyword', + _meta: { + description: "Field name if it's a part of ECS schema", + optional: true, + }, + }, + [FIELD_USAGE_FILTER_OPERATION]: { + type: 'keyword', + _meta: { + description: "Operation type when a filter is added i.e. '+', '-', '_exists_'", + optional: true, + }, + }, + }, + }); + + core.analytics.registerEventType({ + eventType: CONTEXTUAL_PROFILE_RESOLVED_EVENT_TYPE, + schema: { + [CONTEXTUAL_PROFILE_LEVEL]: { + type: 'keyword', + _meta: { + description: + 'The context level at which it was resolved i.e. rootLevel, dataSourceLevel, documentLevel', + }, + }, + [CONTEXTUAL_PROFILE_ID]: { + type: 'keyword', + _meta: { + description: 'The resolved name of the active profile', + }, + }, + }, + }); +}; diff --git a/src/platform/plugins/shared/discover/public/utils/index.ts b/src/platform/plugins/shared/discover/public/plugin_imports/embeddable_services.ts similarity index 63% rename from src/platform/plugins/shared/discover/public/utils/index.ts rename to src/platform/plugins/shared/discover/public/plugin_imports/embeddable_services.ts index 61935bcc15cac..a6b9bd0b43baa 100644 --- a/src/platform/plugins/shared/discover/public/utils/index.ts +++ b/src/platform/plugins/shared/discover/public/plugin_imports/embeddable_services.ts @@ -7,11 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -/* - * Allows the getSharingData function to be lazy loadable - */ -export async function loadSharingDataHelpers() { - return await import('./get_sharing_data'); -} - -export { getSortForEmbeddable } from './sorting'; +export { ViewSavedSearchAction } from '../embeddable/actions/view_saved_search_action'; +export { getOnAddSearchEmbeddable } from '../embeddable/get_on_add_search_embeddable'; +export { getSearchEmbeddableFactory } from '../embeddable/get_search_embeddable_factory'; diff --git a/src/platform/plugins/shared/discover/public/plugin_imports/forward_legacy_urls.ts b/src/platform/plugins/shared/discover/public/plugin_imports/forward_legacy_urls.ts new file mode 100644 index 0000000000000..ac879e33ac636 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/plugin_imports/forward_legacy_urls.ts @@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { UrlForwardingSetup } from '@kbn/url-forwarding-plugin/public'; + +export const forwardLegacyUrls = (urlForwarding: UrlForwardingSetup) => { + urlForwarding.forwardApp('doc', 'discover', (path) => { + return `#${path}`; + }); + + urlForwarding.forwardApp('context', 'discover', (path) => { + const urlParts = path.split('/'); + // take care of urls containing legacy url, those split in the following way + // ["", "context", indexPatternId, _type, id + params] + if (urlParts[4]) { + // remove _type part + const newPath = [...urlParts.slice(0, 3), ...urlParts.slice(4)].join('/'); + return `#${newPath}`; + } + return `#${path}`; + }); + + urlForwarding.forwardApp('discover', 'discover', (path) => { + const [, id, tail] = /discover\/([^\?]+)(.*)/.exec(path) || []; + if (!id) { + return `#${path.replace('/discover', '') || '/'}`; + } + return `#/view/${id}${tail || ''}`; + }); +}; diff --git a/src/platform/plugins/shared/discover/public/history_service.ts b/src/platform/plugins/shared/discover/public/plugin_imports/history_service.ts similarity index 90% rename from src/platform/plugins/shared/discover/public/history_service.ts rename to src/platform/plugins/shared/discover/public/plugin_imports/history_service.ts index 3a28b31b2b528..72983aa4f8934 100644 --- a/src/platform/plugins/shared/discover/public/history_service.ts +++ b/src/platform/plugins/shared/discover/public/plugin_imports/history_service.ts @@ -7,9 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { History } from 'history'; -import { createHashHistory } from 'history'; -import type { HistoryLocationState } from './build_services'; +import { createHashHistory, type History } from 'history'; +import type { HistoryLocationState } from '../build_services'; export class HistoryService { private history?: History; diff --git a/src/platform/plugins/shared/discover/public/plugin_imports/locators.ts b/src/platform/plugins/shared/discover/public/plugin_imports/locators.ts new file mode 100644 index 0000000000000..1487e1fe5a648 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/plugin_imports/locators.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import type { DiscoverAppLocatorParams } from '../../common'; +import { appLocatorGetLocationCommon } from '../../common/app_locator_get_location'; + +export const appLocatorGetLocation = ( + { + useHash, + }: { + useHash: boolean; + }, + params: DiscoverAppLocatorParams +) => appLocatorGetLocationCommon({ useHash, setStateToKbnUrl }, params); + +export { contextAppLocatorGetLocation } from '../application/context/services/locator_get_location'; +export { singleDocLocatorGetLocation } from '../application/doc/locator_get_location'; +export { esqlLocatorGetLocation } from '../../common/esql_locator_get_location'; diff --git a/src/platform/plugins/shared/discover/public/register_feature.ts b/src/platform/plugins/shared/discover/public/plugin_imports/register_feature.ts similarity index 100% rename from src/platform/plugins/shared/discover/public/register_feature.ts rename to src/platform/plugins/shared/discover/public/plugin_imports/register_feature.ts diff --git a/src/platform/plugins/shared/discover/public/global_search/search_provider.test.ts b/src/platform/plugins/shared/discover/public/plugin_imports/search_provider.test.ts similarity index 68% rename from src/platform/plugins/shared/discover/public/global_search/search_provider.test.ts rename to src/platform/plugins/shared/discover/public/plugin_imports/search_provider.test.ts index 9156c5f1ad123..acda1d51b60e8 100644 --- a/src/platform/plugins/shared/discover/public/global_search/search_provider.test.ts +++ b/src/platform/plugins/shared/discover/public/plugin_imports/search_provider.test.ts @@ -8,19 +8,21 @@ */ import { NEVER, lastValueFrom } from 'rxjs'; -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { ApplicationStart } from '@kbn/core/public'; +import type { CoreStart } from '@kbn/core/public'; import { getESQLSearchProvider } from './search_provider'; import { createDiscoverDataViewsMock } from '../__mocks__/data_views'; import type { DiscoverAppLocator } from '../../common'; +import type { DiscoverStartPlugins } from '../types'; +import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; describe('ES|QL search provider', () => { - const uiCapabilitiesMock = new Promise((resolve) => { - resolve({ navLinks: { discover: true } } as unknown as ApplicationStart['capabilities']); - }); - const dataMock = new Promise((resolve) => { - resolve({ dataViews: createDiscoverDataViewsMock() } as unknown as DataPublicPluginStart); - }); + const getServices = (): Promise<[CoreStart, DiscoverStartPlugins]> => + Promise.resolve([ + { + application: { capabilities: { navLinks: { discover: true } } }, + } as unknown as CoreStart, + { dataViews: createDiscoverDataViewsMock() } as unknown as DiscoverStartPlugins, + ]); const locator = { useUrl: jest.fn(() => ''), navigate: jest.fn(), @@ -33,7 +35,11 @@ describe('ES|QL search provider', () => { getRedirectUrl: jest.fn(() => ''), } as unknown as DiscoverAppLocator; test('returns score 100 if term is esql', async () => { - const esqlSearchProvider = getESQLSearchProvider(true, uiCapabilitiesMock, dataMock, locator); + const esqlSearchProvider = getESQLSearchProvider({ + isESQLEnabled: true, + locator, + getServices, + }); const observable = esqlSearchProvider.find( { term: 'esql' }, { aborted$: NEVER, maxResults: 100, preference: '' } @@ -53,7 +59,11 @@ describe('ES|QL search provider', () => { }); test('returns score 90 if user tries to write es|ql', async () => { - const esqlSearchProvider = getESQLSearchProvider(true, uiCapabilitiesMock, dataMock, locator); + const esqlSearchProvider = getESQLSearchProvider({ + isESQLEnabled: true, + locator, + getServices, + }); const observable = esqlSearchProvider.find( { term: 'es|' }, { aborted$: NEVER, maxResults: 100, preference: '' } @@ -73,7 +83,11 @@ describe('ES|QL search provider', () => { }); test('returns empty results if user tries to write something irrelevant', async () => { - const esqlSearchProvider = getESQLSearchProvider(true, uiCapabilitiesMock, dataMock, locator); + const esqlSearchProvider = getESQLSearchProvider({ + isESQLEnabled: true, + locator, + getServices, + }); const observable = esqlSearchProvider.find( { term: 'woof' }, { aborted$: NEVER, maxResults: 100, preference: '' } @@ -83,7 +97,11 @@ describe('ES|QL search provider', () => { }); test('returns empty results if ESQL is disabled', async () => { - const esqlSearchProvider = getESQLSearchProvider(false, uiCapabilitiesMock, dataMock, locator); + const esqlSearchProvider = getESQLSearchProvider({ + isESQLEnabled: false, + locator, + getServices, + }); const observable = esqlSearchProvider.find( { term: 'esql' }, { aborted$: NEVER, maxResults: 100, preference: '' } @@ -94,17 +112,18 @@ describe('ES|QL search provider', () => { test('returns empty results if no default dataview', async () => { const dataViewMock = createDiscoverDataViewsMock(); - const updatedDataMock = new Promise((resolve) => { - resolve({ - dataViews: { ...dataViewMock, getDefaultDataView: jest.fn(() => undefined) }, - } as unknown as DataPublicPluginStart); + const esqlSearchProvider = getESQLSearchProvider({ + isESQLEnabled: true, + locator, + getServices: async () => { + const [core, start] = await getServices(); + start.dataViews = { + ...dataViewMock, + getDefaultDataView: jest.fn(() => undefined), + } as unknown as DataViewsServicePublic; + return [core, start]; + }, }); - const esqlSearchProvider = getESQLSearchProvider( - true, - uiCapabilitiesMock, - updatedDataMock, - locator - ); const observable = esqlSearchProvider.find( { term: 'woof' }, { aborted$: NEVER, maxResults: 100, preference: '' } diff --git a/src/platform/plugins/shared/discover/public/plugin_imports/search_provider.ts b/src/platform/plugins/shared/discover/public/plugin_imports/search_provider.ts new file mode 100644 index 0000000000000..022db4f765152 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/plugin_imports/search_provider.ts @@ -0,0 +1,35 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { CoreStart } from '@kbn/core/public'; +import { defer } from 'rxjs'; +import type { GlobalSearchResultProvider } from '@kbn/global-search-plugin/public'; +import type { DiscoverAppLocator } from '../../common'; +import type { DiscoverStartPlugins } from '../types'; + +/** + * Global search provider adding an ES|QL and ESQL entry. + * This is necessary because ES|QL is part of the Discover application. + * + * It navigates to Discover with a default query extracted from the default dataview + */ +export const getESQLSearchProvider = (options: { + isESQLEnabled: boolean; + locator?: DiscoverAppLocator; + getServices: () => Promise<[CoreStart, DiscoverStartPlugins]>; +}): GlobalSearchResultProvider => ({ + id: 'esql', + find: (...findParams) => { + return defer(async () => { + const { searchProviderFind } = await import('./search_provider_find'); + return searchProviderFind(options, ...findParams); + }); + }, + getSearchableTypes: () => ['application'], +}); diff --git a/src/platform/plugins/shared/discover/public/plugin_imports/search_provider_find.ts b/src/platform/plugins/shared/discover/public/plugin_imports/search_provider_find.ts new file mode 100644 index 0000000000000..5b1d3b4a949b5 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/plugin_imports/search_provider_find.ts @@ -0,0 +1,85 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { + GlobalSearchProviderResult, + GlobalSearchResultProvider, +} from '@kbn/global-search-plugin/public'; +import { DEFAULT_APP_CATEGORIES, type CoreStart } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import { getInitialESQLQuery } from '@kbn/esql-utils'; +import type { DiscoverAppLocator } from '../../common'; +import type { DiscoverStartPlugins } from '../types'; + +export const searchProviderFind: ( + options: { + isESQLEnabled: boolean; + locator?: DiscoverAppLocator; + getServices: () => Promise<[CoreStart, DiscoverStartPlugins]>; + }, + ...findParams: Parameters +) => Promise = async ( + { isESQLEnabled, locator, getServices }, + { term = '', types, tags } +) => { + if (tags || (types && !types.includes('application')) || !locator || !isESQLEnabled) { + return []; + } + + const [core, { dataViews }] = await getServices(); + + if (!core.application.capabilities.navLinks.discover) { + return []; + } + + const title = i18n.translate('discover.globalSearch.esqlSearchTitle', { + defaultMessage: 'Create ES|QL queries', + description: 'ES|QL is a product name and should not be translated', + }); + const defaultDataView = await dataViews.getDefaultDataView({ displayErrors: false }); + + if (!defaultDataView) { + return []; + } + + const params = { + query: { + esql: getInitialESQLQuery(defaultDataView), + }, + dataViewSpec: defaultDataView?.toSpec(), + }; + + const discoverLocation = await locator?.getLocation(params); + + term = term.toLowerCase(); + let score = 0; + + if (term === 'es|ql' || term === 'esql') { + score = 100; + } else if (term && ('es|ql'.includes(term) || 'esql'.includes(term))) { + score = 90; + } + + if (score === 0) return []; + + return [ + { + id: 'esql', + title, + type: 'application', + icon: 'logoKibana', + meta: { + categoryId: DEFAULT_APP_CATEGORIES.kibana.id, + categoryLabel: DEFAULT_APP_CATEGORIES.kibana.label, + }, + score, + url: `/app/${discoverLocation.app}${discoverLocation.path}`, + }, + ]; +}; diff --git a/src/platform/plugins/shared/discover/public/plugin_imports/shared_services.ts b/src/platform/plugins/shared/discover/public/plugin_imports/shared_services.ts new file mode 100644 index 0000000000000..d8c4ae1f03417 --- /dev/null +++ b/src/platform/plugins/shared/discover/public/plugin_imports/shared_services.ts @@ -0,0 +1,16 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { HistoryService } from './history_service'; +export { DiscoverEBTManager } from './discover_ebt_manager'; +export { RootProfileService } from '../context_awareness/profiles/root_profile'; +export { DataSourceProfileService } from '../context_awareness/profiles/data_source_profile'; +export { DocumentProfileService } from '../context_awareness/profiles/document_profile'; +export { ProfilesManager } from '../context_awareness/profiles_manager'; +export { buildServices } from '../build_services'; diff --git a/src/platform/plugins/shared/discover/public/types.ts b/src/platform/plugins/shared/discover/public/types.ts index fdf4a4da60efa..4c0a4a010825a 100644 --- a/src/platform/plugins/shared/discover/public/types.ts +++ b/src/platform/plugins/shared/discover/public/types.ts @@ -45,7 +45,7 @@ import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/pub import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public'; import type { EmbeddableEnhancedPluginStart } from '@kbn/embeddable-enhanced-plugin/public'; import type { DiscoverAppLocator } from '../common'; -import { type DiscoverContainerProps } from './components/discover_container'; +import type { DiscoverContainerProps } from './components/discover_container'; /** * @public @@ -116,6 +116,11 @@ export interface DiscoverStart { * ``` */ readonly locator: undefined | DiscoverAppLocator; + /** + * @deprecated + * Embedding Discover in other applications is discouraged and will be removed in the future. + * Use the Discover context awareness framework instead to register a custom Discover profile. + */ readonly DiscoverContainer: ComponentType; } diff --git a/src/platform/plugins/shared/discover/public/utils/initialize_kbn_url_tracking.ts b/src/platform/plugins/shared/discover/public/utils/initialize_kbn_url_tracking.ts index e247f905631d2..433ed4c555bd7 100644 --- a/src/platform/plugins/shared/discover/public/utils/initialize_kbn_url_tracking.ts +++ b/src/platform/plugins/shared/discover/public/utils/initialize_kbn_url_tracking.ts @@ -9,10 +9,9 @@ import type { AppUpdater, CoreSetup, ScopedHistory } from '@kbn/core/public'; import type { BehaviorSubject } from 'rxjs'; -import { filter, map } from 'rxjs'; +import { filter, switchMap } from 'rxjs'; import { createKbnUrlTracker } from '@kbn/kibana-utils-plugin/public'; import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common'; -import { isFilterPinned } from '@kbn/es-query'; import { SEARCH_SESSION_ID_QUERY_PARAM } from '../constants'; import type { DiscoverSetupPlugins } from '../types'; @@ -69,10 +68,13 @@ export function initializeKbnUrlTracking({ filter( ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) ), - map(({ state }) => ({ - ...state, - filters: state.filters?.filter(isFilterPinned), - })) + switchMap(async ({ state }) => { + const { isFilterPinned } = await import('@kbn/es-query'); + return { + ...state, + filters: state.filters?.filter(isFilterPinned), + }; + }) ), }, ], diff --git a/src/platform/plugins/shared/discover/server/plugin.ts b/src/platform/plugins/shared/discover/server/plugin.ts index 2be541e07f332..cba113c7f270f 100644 --- a/src/platform/plugins/shared/discover/server/plugin.ts +++ b/src/platform/plugins/shared/discover/server/plugin.ts @@ -15,13 +15,14 @@ import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/common'; import type { SharePluginSetup } from '@kbn/share-plugin/server'; import type { PluginInitializerContext } from '@kbn/core/server'; import type { DiscoverServerPluginStart, DiscoverServerPluginStartDeps } from '.'; -import { DiscoverAppLocatorDefinition } from '../common'; +import { DISCOVER_APP_LOCATOR } from '../common'; import { capabilitiesProvider } from './capabilities_provider'; import { createSearchEmbeddableFactory } from './embeddable'; import { initializeLocatorServices } from './locator'; import { registerSampleData } from './sample_data'; import { getUiSettings } from './ui_settings'; import type { ConfigSchema } from './config'; +import { appLocatorGetLocationCommon } from '../common/app_locator_get_location'; export class DiscoverServerPlugin implements Plugin @@ -49,9 +50,12 @@ export class DiscoverServerPlugin } if (plugins.share) { - plugins.share.url.locators.create( - new DiscoverAppLocatorDefinition({ useHash: false, setStateToKbnUrl }) - ); + plugins.share.url.locators.create({ + id: DISCOVER_APP_LOCATOR, + getLocation: (params) => { + return appLocatorGetLocationCommon({ useHash: false, setStateToKbnUrl }, params); + }, + }); } plugins.embeddable.registerEmbeddableFactory(createSearchEmbeddableFactory());