[8.x] [Discover] Optimize Discover plugin page load bundle (#208298) (#214867)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Discover] Optimize Discover plugin page load bundle
(#208298)](https://github.com/elastic/kibana/pull/208298)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Davis
McPhee","email":"davis.mcphee@elastic.co"},"sourceCommit":{"committedDate":"2025-03-11T20:30:25Z","message":"[Discover]
Optimize Discover plugin page load bundle (#208298)\n\n##
Summary\n\nThis PR optimizes the Discover page load bundle by reducing
it to only\ncode which is actually required on startup, and dynamically
loading\nother code when it's needed, resulting in a 55% decrease in the
bundle\nsize.\n\nBefore (44.15
KB):\n\n![before](https://github.com/user-attachments/assets/989d1626-4dd7-4710-a9bc-8d80220101eb)\n\nAfter
(20.12
KB):\n\n![after](https://github.com/user-attachments/assets/ff68b367-3293-47cf-9d3f-5c35d0aea27a)\n\n###
Checklist\n\n- [ ] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[
]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\n- [ ] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] If a plugin
configuration key changed, check if it needs to be\nallowlisted in the
cloud and added to the
[docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\nchanges have been approved by the breaking-change committee.
The\n`release_note:breaking` label should be applied in these
situations.\n- [ ] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [x] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"e1bffa6a9b6a82e347b1c1f4dbfaa7571fff546b","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Discover","release_note:skip","backport
missing","Team:DataDiscovery","backport:version","v9.1.0","v8.19.0"],"title":"[Discover]
Optimize Discover plugin page load
bundle","number":208298,"url":"https://github.com/elastic/kibana/pull/208298","mergeCommit":{"message":"[Discover]
Optimize Discover plugin page load bundle (#208298)\n\n##
Summary\n\nThis PR optimizes the Discover page load bundle by reducing
it to only\ncode which is actually required on startup, and dynamically
loading\nother code when it's needed, resulting in a 55% decrease in the
bundle\nsize.\n\nBefore (44.15
KB):\n\n![before](https://github.com/user-attachments/assets/989d1626-4dd7-4710-a9bc-8d80220101eb)\n\nAfter
(20.12
KB):\n\n![after](https://github.com/user-attachments/assets/ff68b367-3293-47cf-9d3f-5c35d0aea27a)\n\n###
Checklist\n\n- [ ] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[
]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\n- [ ] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] If a plugin
configuration key changed, check if it needs to be\nallowlisted in the
cloud and added to the
[docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\nchanges have been approved by the breaking-change committee.
The\n`release_note:breaking` label should be applied in these
situations.\n- [ ] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [x] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"e1bffa6a9b6a82e347b1c1f4dbfaa7571fff546b"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/208298","number":208298,"mergeCommit":{"message":"[Discover]
Optimize Discover plugin page load bundle (#208298)\n\n##
Summary\n\nThis PR optimizes the Discover page load bundle by reducing
it to only\ncode which is actually required on startup, and dynamically
loading\nother code when it's needed, resulting in a 55% decrease in the
bundle\nsize.\n\nBefore (44.15
KB):\n\n![before](https://github.com/user-attachments/assets/989d1626-4dd7-4710-a9bc-8d80220101eb)\n\nAfter
(20.12
KB):\n\n![after](https://github.com/user-attachments/assets/ff68b367-3293-47cf-9d3f-5c35d0aea27a)\n\n###
Checklist\n\n- [ ] Any text added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[
]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas
added for features that require explanation or tutorials\n- [ ] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] If a plugin
configuration key changed, check if it needs to be\nallowlisted in the
cloud and added to the
[docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\nchanges have been approved by the breaking-change committee.
The\n`release_note:breaking` label should be applied in these
situations.\n- [ ] [Flaky
Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\nused on any tests changed\n- [x] The PR description includes the
appropriate Release Notes section,\nand the correct `release_note:*`
label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n---------\n\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"e1bffa6a9b6a82e347b1c1f4dbfaa7571fff546b"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
This commit is contained in:
Davis McPhee 2025-03-18 09:42:08 -03:00 committed by GitHub
parent ccc2c36864
commit 6d2411ffdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 991 additions and 739 deletions

View file

@ -35,7 +35,7 @@ pageLoadAssetSize:
dataViews: 65000
dataVisualizer: 30000
devTools: 38637
discover: 99999
discover: 25000
discoverEnhanced: 42730
discoverShared: 17111
embeddable: 24000

View file

@ -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<string, unknown>).dataViewSpec).toEqual(dataViewSpecMock);
});
describe('useHash property', () => {

View file

@ -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<DiscoverAppLocatorParams>;
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<DiscoverAppLocatorParams> {
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<DiscoverAppState> = {};
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<GlobalQueryStateFromUrl>(
'_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<DiscoverAppLocatorParams>['getLocation'];

View file

@ -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<DiscoverAppLocatorGetLocation>
): ReturnType<DiscoverAppLocatorGetLocation> => {
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<DiscoverAppState> = {};
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<GlobalQueryStateFromUrl>('_g', queryState, { useHash }, path);
}
if (Object.keys(appState).length) {
path = setStateToKbnUrl('_a', appState, { useHash }, path);
}
return {
app: 'discover',
path,
state,
};
};

View file

@ -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<SerializableRecord>;
dataViews: DataViewsPublicPluginStart;
}
export type DiscoverESQLLocator = LocatorPublic<DiscoverESQLLocatorParams>;
export class DiscoverESQLLocatorDefinition implements LocatorDefinition<DiscoverESQLLocatorParams> {
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<DiscoverESQLLocatorParams>['getLocation'];

View file

@ -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<SerializableRecord>;
dataViews: DataViewsPublicPluginStart;
}): ReturnType<DiscoverESQLLocatorGetLocation> => {
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);
};

View file

@ -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';

View file

@ -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 { createUrlTrackerMock } from './url_tracker.mock';
export function createDiscoverServicesMock(): DiscoverServices {

View file

@ -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 };
};

View file

@ -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<DiscoverContextAppLocatorParams>;
export interface DiscoverContextAppLocatorDependencies {
useHash: boolean;
}
export interface ContextHistoryLocationState {
referrer: string;
dataViewSpec?: DataViewSpec;
}
export class DiscoverContextAppLocatorDefinition
implements LocatorDefinition<DiscoverContextAppLocatorParams>
{
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<GlobalQueryStateFromUrl>('_g', queryState, { useHash }, path);
}
if (Object.keys(appState).length) {
path = setStateToKbnUrl('_a', appState, { useHash }, path);
}
return {
app: 'discover',
path,
state,
};
};
}
export type DiscoverContextAppLocatorGetLocation =
LocatorDefinition<DiscoverContextAppLocatorParams>['getLocation'];

View file

@ -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<DiscoverContextAppLocatorGetLocation>
): ReturnType<DiscoverContextAppLocatorGetLocation> => {
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<GlobalQueryStateFromUrl>('_g', queryState, { useHash }, path);
}
if (Object.keys(appState).length) {
path = setStateToKbnUrl('_a', appState, { useHash }, path);
}
return {
app: 'discover',
path,
state,
};
};

View file

@ -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 };
};

View file

@ -27,31 +27,5 @@ export interface DocHistoryLocationState {
dataViewSpec?: DataViewSpec;
}
export class DiscoverSingleDocLocatorDefinition
implements LocatorDefinition<DiscoverSingleDocLocatorParams>
{
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<DiscoverSingleDocLocatorParams>['getLocation'];

View file

@ -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<DiscoverSingleDocLocatorGetLocation>
): ReturnType<DiscoverSingleDocLocatorGetLocation> => {
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,
};
};

View file

@ -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';

View file

@ -23,7 +23,7 @@ import type { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query';
import { isOfAggregateQueryType, isOfQueryType } from '@kbn/es-query';
import { isFunction } from 'lodash';
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';

View file

@ -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', () => {

View file

@ -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,

View file

@ -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<HistoryLocationState>;
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: <T>() => scopedHistory as ScopedHistory<T | undefined>,
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<HistoryLocationState>;
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: <T>() => scopedHistory as ScopedHistory<T | undefined>,
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,
};
};

View file

@ -39,7 +39,7 @@ const TestComponent = (props: Partial<DiscoverContainerInternalProps>) => {
overrideServices={props.overrideServices ?? mockOverrideService}
customizationCallbacks={props.customizationCallbacks ?? [customizeMock]}
scopedHistory={props.scopedHistory ?? getScopedHistory()!}
getDiscoverServices={getDiscoverServicesMock}
getDiscoverServices={() => Promise.resolve(getDiscoverServicesMock())}
/>
);
};

View file

@ -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<DiscoverServices>;
getDiscoverServices: () => DiscoverServices;
getDiscoverServices: () => Promise<DiscoverServices>;
scopedHistory: ScopedHistory;
customizationCallbacks: CustomizationCallback[];
stateStorageContainer?: IKbnUrlStateStorage;
@ -60,15 +61,20 @@ export const DiscoverContainerInternal = ({
stateStorageContainer,
isLoading = false,
}: DiscoverContainerInternalProps) => {
const services = useMemo<DiscoverServices>(() => {
const { value: discoverServices } = useAsync(getDiscoverServices, [getDiscoverServices]);
const services = useMemo(() => {
if (!discoverServices) {
return undefined;
}
return {
...getDiscoverServices(),
...discoverServices,
...overrideServices,
getScopedHistory: <T,>() => scopedHistory as ScopedHistory<T | undefined>,
};
}, [getDiscoverServices, overrideServices, scopedHistory]);
}, [discoverServices, overrideServices, scopedHistory]);
if (isLoading) {
if (isLoading || !services) {
return (
<EuiFlexGroup css={discoverContainerWrapperCss}>
<LoadingIndicator type="spinner" />

View file

@ -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';

View file

@ -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 {

View file

@ -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';

View file

@ -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<EmbeddableApiContext> {
public id = ACTION_VIEW_SAVED_SEARCH;
@ -28,7 +27,6 @@ export class ViewSavedSearchAction implements Action<EmbeddableApiContext> {
) {}
async execute({ embeddable }: EmbeddableApiContext): Promise<void> {
const { compatibilityCheck } = await import('./view_saved_search_compatibility_check');
if (!compatibilityCheck(embeddable)) {
return;
}
@ -53,7 +51,7 @@ export class ViewSavedSearchAction implements Action<EmbeddableApiContext> {
(capabilities.discover.show as boolean) || (capabilities.discover.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);
}
}

View file

@ -31,7 +31,7 @@ import {
} from '@kbn/discover-utils';
import { DiscoverDocTableEmbeddable } from '../../components/doc_table/create_doc_table_embeddable';
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';

View file

@ -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 */

View file

@ -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<EmbeddableSetup['registerAddFromLibraryType']>[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,
});
};

View file

@ -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';

View file

@ -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<ApplicationStart['capabilities']>,
data: Promise<DataPublicPluginStart>,
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'],
});

View file

@ -39,5 +39,6 @@ export {
type SearchEmbeddableApi,
type NonPersistedDisplayOptions,
} from './embeddable';
export { loadSharingDataHelpers } from './utils';
export { LogsExplorerTabs, type LogsExplorerTabsProps } from './components/logs_explorer_tabs';
export const loadSharingDataHelpers = () => import('./utils/get_sharing_data');

View file

@ -21,33 +21,36 @@ 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, TRUNCATE_MAX_HEIGHT } 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 { injectTruncateStyles } from './utils/truncate_styles';
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/defaults';
import {
SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER,
ACTION_VIEW_SAVED_SEARCH,
} from './embeddable/constants';
import type { DiscoverCustomizationContext } from './customizations';
import { defaultCustomizationContext } from './customizations';
import { SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER } 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,
@ -55,13 +58,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
@ -70,8 +74,10 @@ import { DiscoverEBTManager } from './services/discover_ebt_manager';
export class DiscoverPlugin
implements Plugin<DiscoverSetup, DiscoverStart, DiscoverSetupPlugins, DiscoverStartPlugins>
{
private readonly discoverEbtContext$ = new BehaviorSubject<DiscoverEBTContextProps>({
discoverProfiles: [],
});
private readonly appStateUpdater = new BehaviorSubject<AppUpdater>(() => ({}));
private readonly historyService = new HistoryService();
private readonly inlineTopNav: Map<string | null, DiscoverCustomizationContext['inlineTopNav']> =
new Map([[null, defaultCustomizationContext.inlineTopNav]]);
private readonly experimentalFeatures: ExperimentalFeatures;
@ -101,35 +107,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];
},
})
);
}
@ -151,13 +163,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',
@ -173,7 +187,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
@ -182,24 +196,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,
});
@ -239,32 +243,11 @@ 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 {
@ -282,9 +265,14 @@ export class DiscoverPlugin
}
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);
injectTruncateStyles(core.uiSettings.get(TRUNCATE_MAX_HEIGHT));
@ -292,22 +280,23 @@ export class DiscoverPlugin
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 {
@ -324,29 +313,17 @@ 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();
return { rootProfileService, dataSourceProfileService, documentProfileService };
}
private async createProfilesManager({
core,
plugins,
ebtManager,
}: {
core: CoreStart;
plugins: DiscoverStartPlugins;
ebtManager: DiscoverEBTManager;
}) {
const { registerProfileProviders } = await import('./context_awareness/profile_providers');
const { rootProfileService, dataSourceProfileService, documentProfileService } =
this.createProfileServices();
const enabledExperimentalProfileIds = this.experimentalFeatures.enabledProfiles ?? [];
const profilesManager = new ProfilesManager(
rootProfileService,
dataSourceProfileService,
@ -354,32 +331,70 @@ export class DiscoverPlugin
ebtManager
);
return {
rootProfileService,
dataSourceProfileService,
documentProfileService,
profilesManager,
};
}
private async getDiscoverServicesWithProfiles({
core,
plugins,
ebtManager,
scopedHistory,
setHeaderActionMenu,
}: {
core: CoreStart;
plugins: DiscoverStartPlugins;
ebtManager: DiscoverEBTManager;
scopedHistory?: ScopedHistory;
setHeaderActionMenu?: AppMountParameters['setHeaderActionMenu'];
}) {
const {
rootProfileService,
dataSourceProfileService,
documentProfileService,
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;
return services;
}
private createEmptyProfilesManager({ ebtManager }: { ebtManager: DiscoverEBTManager }) {
return new ProfilesManager(
new RootProfileService(),
new DataSourceProfileService(),
new DocumentProfileService(),
ebtManager
);
}
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,
@ -387,16 +402,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<DiscoverStartPlugins>, plugins: DiscoverSetupPlugins) {
const ebtManager = new DiscoverEBTManager(); // It is not initialized outside of Discover
const getStartServices = async () => {
const [coreStart, deps] = await core.getStartServices();
return {
@ -407,29 +422,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<SavedSearchAttributes>({
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', {
@ -442,7 +447,7 @@ export class DiscoverPlugin
const [startServices, discoverServices, { getSearchEmbeddableFactory }] = await Promise.all([
getStartServices(),
getDiscoverServicesForEmbeddable(),
import('./embeddable/get_search_embeddable_factory'),
getEmbeddableServices(),
]);
return getSearchEmbeddableFactory({
@ -452,3 +457,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
});

View file

@ -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<DiscoverEBTContextProps>;
const coreSetupMock = coreMock.createSetup();
@ -32,15 +34,19 @@ describe('DiscoverEBTManager', () => {
beforeEach(() => {
discoverEBTContextManager = new DiscoverEBTManager();
discoverEbtContext$ = new BehaviorSubject<DiscoverEBTContextProps>({
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({

View file

@ -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<DiscoverEBTContextProps>;
}) {
if (shouldInitializeCustomContext) {
// Register Discover specific context to be used in EBT
const context$ = new BehaviorSubject<DiscoverEBTContextProps>({
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() {

View file

@ -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<DiscoverStartPlugins>,
discoverEbtContext$: BehaviorSubject<DiscoverEBTContextProps>
) => {
// 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',
},
},
},
});
};

View file

@ -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';

View file

@ -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 || ''}`;
});
};

View file

@ -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<HistoryLocationState>;

View file

@ -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';

View file

@ -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<ApplicationStart['capabilities']>((resolve) => {
resolve({ navLinks: { discover: true } } as unknown as ApplicationStart['capabilities']);
});
const dataMock = new Promise<DataPublicPluginStart>((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<DataPublicPluginStart>((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: '' }

View file

@ -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'],
});

View file

@ -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<GlobalSearchResultProvider['find']>
) => Promise<GlobalSearchProviderResult[]> = 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}`,
},
];
};

View file

@ -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';

View file

@ -46,7 +46,7 @@ import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/publ
import type { EmbeddableEnhancedPluginStart } from '@kbn/embeddable-enhanced-plugin/public';
import type { DiscoverAppLocator } from '../common';
import type { DiscoverCustomizationContext } from './customizations';
import { type DiscoverContainerProps } from './components/discover_container';
import type { DiscoverContainerProps } from './components/discover_container';
/**
* @public
@ -122,6 +122,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<DiscoverContainerProps>;
}

View file

@ -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),
};
})
),
},
],

View file

@ -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<object, DiscoverServerPluginStart, object, DiscoverServerPluginStartDeps>
@ -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());