mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Discover] Optimize Discover plugin page load bundle (#208298)
## Summary This PR optimizes the Discover page load bundle by reducing it to only code which is actually required on startup, and dynamically loading other code when it's needed, resulting in a 55% decrease in the bundle size. Before (44.15 KB):  After (20.12 KB):  ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
99d8400328
commit
e1bffa6a9b
47 changed files with 991 additions and 739 deletions
|
@ -34,7 +34,7 @@ pageLoadAssetSize:
|
|||
dataViews: 65000
|
||||
dataVisualizer: 30000
|
||||
devTools: 38637
|
||||
discover: 99999
|
||||
discover: 25000
|
||||
discoverEnhanced: 42730
|
||||
discoverShared: 17111
|
||||
embeddable: 24000
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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'];
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -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';
|
||||
|
|
|
@ -46,7 +46,7 @@ import type { SearchSourceDependencies } from '@kbn/data-plugin/common';
|
|||
import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { createElement } from 'react';
|
||||
import { createContextAwarenessMocks } from '../context_awareness/__mocks__';
|
||||
import { DiscoverEBTManager } from '../services/discover_ebt_manager';
|
||||
import { DiscoverEBTManager } from '../plugin_imports/discover_ebt_manager';
|
||||
import { discoverSharedPluginMock } from '@kbn/discover-shared-plugin/public/mocks';
|
||||
import { createUrlTrackerMock } from './url_tracker.mock';
|
||||
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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';
|
||||
|
|
|
@ -24,7 +24,7 @@ import { isOfAggregateQueryType, isOfQueryType } from '@kbn/es-query';
|
|||
import { isFunction } from 'lodash';
|
||||
import type { DiscoverServices } from '../../..';
|
||||
import { loadSavedSearch as loadSavedSearchFn } from './utils/load_saved_search';
|
||||
import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search';
|
||||
import { restoreStateFromSavedSearch } from './utils/restore_from_saved_search';
|
||||
import { FetchStatus } from '../../types';
|
||||
import { changeDataView } from './utils/change_data_view';
|
||||
import { buildStateSubscribe } from './utils/build_state_subscribe';
|
||||
|
|
|
@ -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', () => {
|
|
@ -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,
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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())}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
@ -54,15 +55,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" />
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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_v2.show as boolean) || (capabilities.discover_v2.save as boolean);
|
||||
|
||||
if (!hasDiscoverPermissions) return false; // early return to delay async import until absolutely necessary
|
||||
const { compatibilityCheck } = await import('./view_saved_search_compatibility_check');
|
||||
|
||||
return compatibilityCheck(embeddable);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
|
|||
import type { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { useDiscoverServices } from '../../hooks/use_discover_services';
|
||||
import { getSortForEmbeddable } from '../../utils';
|
||||
import { getSortForEmbeddable } from '../../utils/sorting';
|
||||
import { getAllowedSampleSize, getMaxAllowedSampleSize } from '../../utils/get_allowed_sample_size';
|
||||
import { SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID } from '../constants';
|
||||
import { isEsqlMode } from '../initialize_fetch';
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
|
@ -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';
|
||||
|
|
|
@ -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'],
|
||||
});
|
|
@ -39,5 +39,6 @@ export {
|
|||
type SearchEmbeddableApi,
|
||||
type NonPersistedDisplayOptions,
|
||||
} from './embeddable';
|
||||
export { loadSharingDataHelpers } from './utils';
|
||||
export type { DiscoverServices } from './build_services';
|
||||
|
||||
export const loadSharingDataHelpers = () => import('./utils/get_sharing_data');
|
||||
|
|
|
@ -20,31 +20,34 @@ import type {
|
|||
} from '@kbn/core/public';
|
||||
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
|
||||
import { ENABLE_ESQL } from '@kbn/esql-utils';
|
||||
import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public';
|
||||
import { SEARCH_EMBEDDABLE_TYPE } from '@kbn/discover-utils';
|
||||
import type { SavedSearchAttributes } from '@kbn/saved-search-plugin/common';
|
||||
import { SavedSearchType } from '@kbn/saved-search-plugin/common';
|
||||
import type { SavedSearchAttributes } from '@kbn/saved-search-plugin/common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PLUGIN_ID } from '../common';
|
||||
import { registerFeature } from './register_feature';
|
||||
import { once } from 'lodash';
|
||||
import { DISCOVER_ESQL_LOCATOR } from '@kbn/deeplinks-analytics';
|
||||
import { DISCOVER_APP_LOCATOR, PLUGIN_ID, type DiscoverAppLocator } from '../common';
|
||||
import {
|
||||
DISCOVER_CONTEXT_APP_LOCATOR,
|
||||
type DiscoverContextAppLocator,
|
||||
} from './application/context/services/locator';
|
||||
import {
|
||||
DISCOVER_SINGLE_DOC_LOCATOR,
|
||||
type DiscoverSingleDocLocator,
|
||||
} from './application/doc/locator';
|
||||
import { registerFeature } from './plugin_imports/register_feature';
|
||||
import type { UrlTracker } from './build_services';
|
||||
import { buildServices } from './build_services';
|
||||
import { ViewSavedSearchAction } from './embeddable/actions/view_saved_search_action';
|
||||
import { initializeKbnUrlTracking } from './utils/initialize_kbn_url_tracking';
|
||||
import type { DiscoverContextAppLocator } from './application/context/services/locator';
|
||||
import { DiscoverContextAppLocatorDefinition } from './application/context/services/locator';
|
||||
import type { DiscoverSingleDocLocator } from './application/doc/locator';
|
||||
import { DiscoverSingleDocLocatorDefinition } from './application/doc/locator';
|
||||
import type { DiscoverAppLocator } from '../common';
|
||||
import { DiscoverAppLocatorDefinition, DiscoverESQLLocatorDefinition } from '../common';
|
||||
import { defaultCustomizationContext } from './customizations';
|
||||
import { SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER } from './embeddable/constants';
|
||||
import { defaultCustomizationContext } from './customizations/defaults';
|
||||
import {
|
||||
SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER,
|
||||
ACTION_VIEW_SAVED_SEARCH,
|
||||
} from './embeddable/constants';
|
||||
import {
|
||||
DiscoverContainerInternal,
|
||||
type DiscoverContainerProps,
|
||||
} from './components/discover_container';
|
||||
import { getESQLSearchProvider } from './global_search/search_provider';
|
||||
import { HistoryService } from './history_service';
|
||||
import { getESQLSearchProvider } from './plugin_imports/search_provider';
|
||||
import type { ConfigSchema, ExperimentalFeatures } from '../server/config';
|
||||
import type {
|
||||
DiscoverSetup,
|
||||
|
@ -52,13 +55,14 @@ import type {
|
|||
DiscoverStart,
|
||||
DiscoverStartPlugins,
|
||||
} from './types';
|
||||
import { deserializeState } from './embeddable/utils/serialization_utils';
|
||||
import { DISCOVER_CELL_ACTIONS_TRIGGER } from './context_awareness/types';
|
||||
import { RootProfileService } from './context_awareness/profiles/root_profile';
|
||||
import { DataSourceProfileService } from './context_awareness/profiles/data_source_profile';
|
||||
import { DocumentProfileService } from './context_awareness/profiles/document_profile';
|
||||
import { ProfilesManager } from './context_awareness/profiles_manager';
|
||||
import { DiscoverEBTManager } from './services/discover_ebt_manager';
|
||||
import type {
|
||||
DiscoverEBTContextProps,
|
||||
DiscoverEBTManager,
|
||||
} from './plugin_imports/discover_ebt_manager';
|
||||
import type { ProfilesManager } from './context_awareness';
|
||||
import { forwardLegacyUrls } from './plugin_imports/forward_legacy_urls';
|
||||
import { registerDiscoverEBTManagerAnalytics } from './plugin_imports/discover_ebt_manager_registrations';
|
||||
|
||||
/**
|
||||
* Contains Discover, one of the oldest parts of Kibana
|
||||
|
@ -67,8 +71,10 @@ import { DiscoverEBTManager } from './services/discover_ebt_manager';
|
|||
export class DiscoverPlugin
|
||||
implements Plugin<DiscoverSetup, DiscoverStart, DiscoverSetupPlugins, DiscoverStartPlugins>
|
||||
{
|
||||
private readonly discoverEbtContext$ = new BehaviorSubject<DiscoverEBTContextProps>({
|
||||
discoverProfiles: [],
|
||||
});
|
||||
private readonly appStateUpdater = new BehaviorSubject<AppUpdater>(() => ({}));
|
||||
private readonly historyService = new HistoryService();
|
||||
private readonly experimentalFeatures: ExperimentalFeatures;
|
||||
|
||||
private scopedHistory?: ScopedHistory<unknown>;
|
||||
|
@ -96,35 +102,41 @@ export class DiscoverPlugin
|
|||
if (plugins.share) {
|
||||
const useHash = core.uiSettings.get('state:storeInSessionStorage');
|
||||
|
||||
this.locator = plugins.share.url.locators.create(
|
||||
new DiscoverAppLocatorDefinition({ useHash, setStateToKbnUrl })
|
||||
);
|
||||
this.contextLocator = plugins.share.url.locators.create(
|
||||
new DiscoverContextAppLocatorDefinition({ useHash })
|
||||
);
|
||||
this.singleDocLocator = plugins.share.url.locators.create(
|
||||
new DiscoverSingleDocLocatorDefinition()
|
||||
);
|
||||
this.locator = plugins.share.url.locators.create({
|
||||
id: DISCOVER_APP_LOCATOR,
|
||||
getLocation: async (params) => {
|
||||
const { appLocatorGetLocation } = await getLocators();
|
||||
return appLocatorGetLocation({ useHash }, params);
|
||||
},
|
||||
});
|
||||
|
||||
this.contextLocator = plugins.share.url.locators.create({
|
||||
id: DISCOVER_CONTEXT_APP_LOCATOR,
|
||||
getLocation: async (params) => {
|
||||
const { contextAppLocatorGetLocation } = await getLocators();
|
||||
return contextAppLocatorGetLocation({ useHash }, params);
|
||||
},
|
||||
});
|
||||
|
||||
this.singleDocLocator = plugins.share.url.locators.create({
|
||||
id: DISCOVER_SINGLE_DOC_LOCATOR,
|
||||
getLocation: async (params) => {
|
||||
const { singleDocLocatorGetLocation } = await getLocators();
|
||||
return singleDocLocatorGetLocation(params);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (plugins.globalSearch) {
|
||||
const enableESQL = core.uiSettings.get(ENABLE_ESQL);
|
||||
plugins.globalSearch.registerResultProvider(
|
||||
getESQLSearchProvider(
|
||||
enableESQL,
|
||||
core.getStartServices().then(
|
||||
([
|
||||
{
|
||||
application: { capabilities },
|
||||
},
|
||||
]) => capabilities
|
||||
),
|
||||
core.getStartServices().then((deps) => {
|
||||
const { data } = deps[1];
|
||||
return data;
|
||||
}),
|
||||
this.locator
|
||||
)
|
||||
getESQLSearchProvider({
|
||||
isESQLEnabled: core.uiSettings.get(ENABLE_ESQL),
|
||||
locator: this.locator,
|
||||
getServices: async () => {
|
||||
const [coreStart, startPlugins] = await core.getStartServices();
|
||||
return [coreStart, startPlugins];
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -146,13 +158,15 @@ export class DiscoverPlugin
|
|||
this.urlTracker = { setTrackedUrl, restorePreviousUrl, setTrackingEnabled };
|
||||
this.stopUrlTracking = stopUrlTracker;
|
||||
|
||||
const ebtManager = new DiscoverEBTManager();
|
||||
ebtManager.initialize({
|
||||
core,
|
||||
shouldInitializeCustomContext: true,
|
||||
shouldInitializeCustomEvents: true,
|
||||
const getEbtManager = once(async () => {
|
||||
const { DiscoverEBTManager } = await getSharedServices();
|
||||
const ebtManager = new DiscoverEBTManager();
|
||||
ebtManager.initialize({ core, discoverEbtContext$: this.discoverEbtContext$ });
|
||||
return ebtManager;
|
||||
});
|
||||
|
||||
registerDiscoverEBTManagerAnalytics(core, this.discoverEbtContext$);
|
||||
|
||||
core.application.register({
|
||||
id: PLUGIN_ID,
|
||||
title: 'Discover',
|
||||
|
@ -168,7 +182,7 @@ export class DiscoverPlugin
|
|||
// Store the current scoped history so initializeKbnUrlTracking can access it
|
||||
this.scopedHistory = params.history;
|
||||
|
||||
this.historyService.syncHistoryLocations();
|
||||
(await getHistoryService()).syncHistoryLocations();
|
||||
appMounted();
|
||||
|
||||
// dispatch synthetic hash change event to update hash history objects
|
||||
|
@ -177,24 +191,14 @@ export class DiscoverPlugin
|
|||
window.dispatchEvent(new HashChangeEvent('hashchange'));
|
||||
});
|
||||
|
||||
const ebtManager = await getEbtManager();
|
||||
ebtManager.onDiscoverAppMounted();
|
||||
|
||||
const services = buildServices({
|
||||
const services = await this.getDiscoverServicesWithProfiles({
|
||||
core: coreStart,
|
||||
plugins: discoverStartPlugins,
|
||||
context: this.initializerContext,
|
||||
locator: this.locator!,
|
||||
contextLocator: this.contextLocator!,
|
||||
singleDocLocator: this.singleDocLocator!,
|
||||
history: this.historyService.getHistory(),
|
||||
scopedHistory: this.scopedHistory,
|
||||
urlTracker: this.urlTracker!,
|
||||
profilesManager: await this.createProfilesManager({
|
||||
core: coreStart,
|
||||
plugins: discoverStartPlugins,
|
||||
ebtManager,
|
||||
}),
|
||||
ebtManager,
|
||||
scopedHistory: this.scopedHistory,
|
||||
setHeaderActionMenu: params.setHeaderActionMenu,
|
||||
});
|
||||
|
||||
|
@ -222,63 +226,48 @@ export class DiscoverPlugin
|
|||
},
|
||||
});
|
||||
|
||||
plugins.urlForwarding.forwardApp('doc', 'discover', (path) => {
|
||||
return `#${path}`;
|
||||
});
|
||||
plugins.urlForwarding.forwardApp('context', 'discover', (path) => {
|
||||
const urlParts = path.split('/');
|
||||
// take care of urls containing legacy url, those split in the following way
|
||||
// ["", "context", indexPatternId, _type, id + params]
|
||||
if (urlParts[4]) {
|
||||
// remove _type part
|
||||
const newPath = [...urlParts.slice(0, 3), ...urlParts.slice(4)].join('/');
|
||||
return `#${newPath}`;
|
||||
}
|
||||
return `#${path}`;
|
||||
});
|
||||
plugins.urlForwarding.forwardApp('discover', 'discover', (path) => {
|
||||
const [, id, tail] = /discover\/([^\?]+)(.*)/.exec(path) || [];
|
||||
if (!id) {
|
||||
return `#${path.replace('/discover', '') || '/'}`;
|
||||
}
|
||||
return `#/view/${id}${tail || ''}`;
|
||||
});
|
||||
|
||||
if (plugins.home) {
|
||||
registerFeature(plugins.home);
|
||||
}
|
||||
|
||||
forwardLegacyUrls(plugins.urlForwarding);
|
||||
this.registerEmbeddable(core, plugins);
|
||||
|
||||
return { locator: this.locator };
|
||||
}
|
||||
|
||||
start(core: CoreStart, plugins: DiscoverStartPlugins): DiscoverStart {
|
||||
const viewSavedSearchAction = new ViewSavedSearchAction(core.application, this.locator!);
|
||||
|
||||
plugins.uiActions.addTriggerAction('CONTEXT_MENU_TRIGGER', viewSavedSearchAction);
|
||||
plugins.uiActions.addTriggerActionAsync(
|
||||
'CONTEXT_MENU_TRIGGER',
|
||||
ACTION_VIEW_SAVED_SEARCH,
|
||||
async () => {
|
||||
const { ViewSavedSearchAction } = await getEmbeddableServices();
|
||||
return new ViewSavedSearchAction(core.application, this.locator!);
|
||||
}
|
||||
);
|
||||
plugins.uiActions.registerTrigger(SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER);
|
||||
plugins.uiActions.registerTrigger(DISCOVER_CELL_ACTIONS_TRIGGER);
|
||||
|
||||
const isEsqlEnabled = core.uiSettings.get(ENABLE_ESQL);
|
||||
|
||||
if (plugins.share && this.locator && isEsqlEnabled) {
|
||||
plugins.share?.url.locators.create(
|
||||
new DiscoverESQLLocatorDefinition({
|
||||
discoverAppLocator: this.locator,
|
||||
dataViews: plugins.dataViews,
|
||||
})
|
||||
);
|
||||
const discoverAppLocator = this.locator;
|
||||
plugins.share?.url.locators.create({
|
||||
id: DISCOVER_ESQL_LOCATOR,
|
||||
getLocation: async () => {
|
||||
const { esqlLocatorGetLocation } = await getLocators();
|
||||
return esqlLocatorGetLocation({
|
||||
discoverAppLocator,
|
||||
dataViews: plugins.dataViews,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const getDiscoverServicesInternal = () => {
|
||||
const ebtManager = new DiscoverEBTManager(); // It is not initialized outside of Discover
|
||||
return this.getDiscoverServices(
|
||||
core,
|
||||
plugins,
|
||||
this.createEmptyProfilesManager({ ebtManager }),
|
||||
ebtManager
|
||||
);
|
||||
const getDiscoverServicesInternal = async () => {
|
||||
const ebtManager = await getEmptyEbtManager();
|
||||
const { profilesManager } = await this.createProfileServices(ebtManager);
|
||||
return this.getDiscoverServices({ core, plugins, profilesManager, ebtManager });
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -295,29 +284,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,
|
||||
|
@ -325,32 +302,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,
|
||||
|
@ -358,16 +373,16 @@ export class DiscoverPlugin
|
|||
locator: this.locator!,
|
||||
contextLocator: this.contextLocator!,
|
||||
singleDocLocator: this.singleDocLocator!,
|
||||
history: this.historyService.getHistory(),
|
||||
history: (await getHistoryService()).getHistory(),
|
||||
scopedHistory,
|
||||
urlTracker: this.urlTracker!,
|
||||
profilesManager,
|
||||
ebtManager,
|
||||
setHeaderActionMenu,
|
||||
});
|
||||
};
|
||||
|
||||
private registerEmbeddable(core: CoreSetup<DiscoverStartPlugins>, plugins: DiscoverSetupPlugins) {
|
||||
const ebtManager = new DiscoverEBTManager(); // It is not initialized outside of Discover
|
||||
|
||||
const getStartServices = async () => {
|
||||
const [coreStart, deps] = await core.getStartServices();
|
||||
return {
|
||||
|
@ -378,29 +393,19 @@ export class DiscoverPlugin
|
|||
|
||||
const getDiscoverServicesForEmbeddable = async () => {
|
||||
const [coreStart, deps] = await core.getStartServices();
|
||||
|
||||
const profilesManager = await this.createProfilesManager({
|
||||
const ebtManager = await getEmptyEbtManager();
|
||||
return this.getDiscoverServicesWithProfiles({
|
||||
core: coreStart,
|
||||
plugins: deps,
|
||||
ebtManager,
|
||||
});
|
||||
return this.getDiscoverServices(coreStart, deps, profilesManager, ebtManager);
|
||||
};
|
||||
|
||||
plugins.embeddable.registerAddFromLibraryType<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', {
|
||||
|
@ -413,7 +418,7 @@ export class DiscoverPlugin
|
|||
const [startServices, discoverServices, { getSearchEmbeddableFactory }] = await Promise.all([
|
||||
getStartServices(),
|
||||
getDiscoverServicesForEmbeddable(),
|
||||
import('./embeddable/get_search_embeddable_factory'),
|
||||
getEmbeddableServices(),
|
||||
]);
|
||||
|
||||
return getSearchEmbeddableFactory({
|
||||
|
@ -423,3 +428,17 @@ export class DiscoverPlugin
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
const getLocators = () => import('./plugin_imports/locators');
|
||||
const getEmbeddableServices = () => import('./plugin_imports/embeddable_services');
|
||||
const getSharedServices = () => import('./plugin_imports/shared_services');
|
||||
|
||||
const getHistoryService = once(async () => {
|
||||
const { HistoryService } = await getSharedServices();
|
||||
return new HistoryService();
|
||||
});
|
||||
|
||||
const getEmptyEbtManager = once(async () => {
|
||||
const { DiscoverEBTManager } = await getSharedServices();
|
||||
return new DiscoverEBTManager(); // It is not initialized outside of Discover
|
||||
});
|
||||
|
|
|
@ -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({
|
|
@ -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() {
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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';
|
|
@ -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 || ''}`;
|
||||
});
|
||||
};
|
|
@ -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>;
|
|
@ -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';
|
|
@ -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: '' }
|
|
@ -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'],
|
||||
});
|
|
@ -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}`,
|
||||
},
|
||||
];
|
||||
};
|
|
@ -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';
|
|
@ -45,7 +45,7 @@ import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/pub
|
|||
import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public';
|
||||
import type { EmbeddableEnhancedPluginStart } from '@kbn/embeddable-enhanced-plugin/public';
|
||||
import type { DiscoverAppLocator } from '../common';
|
||||
import { type DiscoverContainerProps } from './components/discover_container';
|
||||
import type { DiscoverContainerProps } from './components/discover_container';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
@ -116,6 +116,11 @@ export interface DiscoverStart {
|
|||
* ```
|
||||
*/
|
||||
readonly locator: undefined | DiscoverAppLocator;
|
||||
/**
|
||||
* @deprecated
|
||||
* Embedding Discover in other applications is discouraged and will be removed in the future.
|
||||
* Use the Discover context awareness framework instead to register a custom Discover profile.
|
||||
*/
|
||||
readonly DiscoverContainer: ComponentType<DiscoverContainerProps>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue