diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.extractsearchsourcereferences.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.extractsearchsourcereferences.md new file mode 100644 index 0000000000000..cd051cfeca6b0 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.extractsearchsourcereferences.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [extractSearchSourceReferences](./kibana-plugin-plugins-data-public.extractsearchsourcereferences.md) + +## extractSearchSourceReferences variable + +Signature: + +```typescript +extractReferences: (state: SearchSourceFields) => [SearchSourceFields & { + indexRefName?: string | undefined; +}, SavedObjectReference[]] +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.injectsearchsourcereferences.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.injectsearchsourcereferences.md new file mode 100644 index 0000000000000..b55f5b866244d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.injectsearchsourcereferences.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [injectSearchSourceReferences](./kibana-plugin-plugins-data-public.injectsearchsourcereferences.md) + +## injectSearchSourceReferences variable + +Signature: + +```typescript +injectReferences: (searchSourceFields: SearchSourceFields & { + indexRefName: string; +}, references: SavedObjectReference[]) => SearchSourceFields +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 8b58957b9044a..02cc34baf7c45 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -101,11 +101,14 @@ | [esFilters](./kibana-plugin-plugins-data-public.esfilters.md) | | | [esKuery](./kibana-plugin-plugins-data-public.eskuery.md) | | | [esQuery](./kibana-plugin-plugins-data-public.esquery.md) | | +| [extractSearchSourceReferences](./kibana-plugin-plugins-data-public.extractsearchsourcereferences.md) | | | [fieldFormats](./kibana-plugin-plugins-data-public.fieldformats.md) | | | [FilterBar](./kibana-plugin-plugins-data-public.filterbar.md) | | | [getIndexPatternFieldListCreator](./kibana-plugin-plugins-data-public.getindexpatternfieldlistcreator.md) | | | [getKbnTypeNames](./kibana-plugin-plugins-data-public.getkbntypenames.md) | Get the esTypes known by all kbnFieldTypes {Array} | | [indexPatterns](./kibana-plugin-plugins-data-public.indexpatterns.md) | | +| [injectSearchSourceReferences](./kibana-plugin-plugins-data-public.injectsearchsourcereferences.md) | | +| [parseSearchSourceJSON](./kibana-plugin-plugins-data-public.parsesearchsourcejson.md) | | | [QueryStringInput](./kibana-plugin-plugins-data-public.querystringinput.md) | | | [search](./kibana-plugin-plugins-data-public.search.md) | | | [SearchBar](./kibana-plugin-plugins-data-public.searchbar.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.parsesearchsourcejson.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.parsesearchsourcejson.md new file mode 100644 index 0000000000000..f5014c55fdaab --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.parsesearchsourcejson.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [parseSearchSourceJSON](./kibana-plugin-plugins-data-public.parsesearchsourcejson.md) + +## parseSearchSourceJSON variable + +Signature: + +```typescript +parseSearchSourceJSON: (searchSourceJSON: string) => SearchSourceFields +``` diff --git a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts index f9f4494929014..e3aa49baeae0d 100644 --- a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts +++ b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts @@ -32,6 +32,7 @@ import { chartPluginMock } from '../../../../../plugins/charts/public/mocks'; import { advancedSettingsMock } from '../../../../../plugins/advanced_settings/public/mocks'; import { savedObjectsManagementPluginMock } from '../../../../../plugins/saved_objects_management/public/mocks'; import { visualizationsPluginMock } from '../../../../../plugins/visualizations/public/mocks'; +import { discoverPluginMock } from '../../../../../plugins/discover/public/mocks'; /* eslint-enable @kbn/eslint/no-restricted-paths */ export const pluginsMock = { @@ -48,6 +49,7 @@ export const pluginsMock = { visualizations: visualizationsPluginMock.createSetupContract(), kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(), savedObjectsManagement: savedObjectsManagementPluginMock.createSetupContract(), + discover: discoverPluginMock.createSetupContract(), }), createStart: () => ({ data: dataPluginMock.createStartContract(), @@ -62,6 +64,7 @@ export const pluginsMock = { visualizations: visualizationsPluginMock.createStartContract(), kibanaLegacy: kibanaLegacyPluginMock.createStartContract(), savedObjectsManagement: savedObjectsManagementPluginMock.createStartContract(), + discover: discoverPluginMock.createStartContract(), }), }; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 3caba24748bfa..332a0a0f9ca6e 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -515,6 +515,7 @@ export const npStart = { docViews: { DocViewer: () => null, }, + savedSearchLoader: {}, }, }, }; diff --git a/src/legacy/ui/public/new_platform/set_services.ts b/src/legacy/ui/public/new_platform/set_services.ts index 400f31e73ffa1..9cacb0c09d79a 100644 --- a/src/legacy/ui/public/new_platform/set_services.ts +++ b/src/legacy/ui/public/new_platform/set_services.ts @@ -57,6 +57,7 @@ export function setStartServices(npStart: NpStart) { dataServices.setIndexPatterns(npStart.plugins.data.indexPatterns); dataServices.setQueryService(npStart.plugins.data.query); dataServices.setSearchService(npStart.plugins.data.search); + visualizationsServices.setI18n(npStart.core.i18n); visualizationsServices.setTypes( pick(npStart.plugins.visualizations, ['get', 'all', 'getAliases']) @@ -82,4 +83,5 @@ export function setStartServices(npStart: NpStart) { visualizationTypes: visualizationsServices.getTypes(), }); visualizationsServices.setSavedVisualizationsLoader(savedVisualizationsLoader); + visualizationsServices.setSavedSearchLoader(npStart.plugins.discover.savedSearchLoader); } diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 69dd97a881797..4a5b3fd5714db 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -358,6 +358,9 @@ export { SearchResponse, SearchError, ISearchSource, + parseSearchSourceJSON, + injectSearchSourceReferences, + extractSearchSourceReferences, SearchSourceFields, EsQuerySortValue, SortDirection, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index ee56ad60441f4..e157d2ac6a522 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -46,7 +46,6 @@ import * as React_2 from 'react'; import { Required } from '@kbn/utility-types'; import * as Rx from 'rxjs'; import { SavedObject as SavedObject_2 } from 'src/core/public'; -import { SavedObjectReference } from 'kibana/public'; import { SavedObjectsClientContract } from 'src/core/public'; import { SearchParams } from 'elasticsearch'; import { SearchResponse as SearchResponse_2 } from 'elasticsearch'; @@ -420,6 +419,14 @@ export type ExistsFilter = Filter & { exists?: FilterExistsProperty; }; +// Warning: (ae-forgotten-export) The symbol "SavedObjectReference" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "extractReferences" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const extractSearchSourceReferences: (state: SearchSourceFields) => [SearchSourceFields & { + indexRefName?: string | undefined; +}, SavedObjectReference[]]; + // Warning: (ae-missing-release-tag) "FetchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1059,6 +1066,13 @@ export interface IndexPatternTypeMeta { aggs?: Record; } +// Warning: (ae-missing-release-tag) "injectReferences" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const injectSearchSourceReferences: (searchSourceFields: SearchSourceFields & { + indexRefName: string; +}, references: SavedObjectReference[]) => SearchSourceFields; + // Warning: (ae-missing-release-tag) "InputTimeRange" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1273,6 +1287,11 @@ export interface OptionedValueProp { // @public (undocumented) export type ParsedInterval = ReturnType; +// Warning: (ae-missing-release-tag) "parseSearchSourceJSON" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const parseSearchSourceJSON: (searchSourceJSON: string) => SearchSourceFields; + // Warning: (ae-missing-release-tag) "PhraseFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1809,20 +1828,20 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/public/index.ts:237:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:237:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:237:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:374:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:374:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:374:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:374:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:376:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:377:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:379:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:380:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index eec75b0841133..d6901da99319a 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -282,7 +282,7 @@ export const esaggs = (): ExpressionFunctionDefinition { }, search, searchSource: { - create: (fields?: SearchSourceFields) => new SearchSource(fields, searchSourceDependencies), - fromJSON: createSearchSourceFromJSON(dependencies.indexPatterns, searchSourceDependencies), + create: createSearchSource(dependencies.indexPatterns, searchSourceDependencies), + createEmpty: () => { + return new SearchSource({}, searchSourceDependencies); + }, }, setInterceptor: (searchInterceptor: SearchInterceptor) => { // TODO: should an intercepror have a destroy method? diff --git a/src/plugins/data/public/search/search_source/create_search_source.test.ts b/src/plugins/data/public/search/search_source/create_search_source.test.ts index efa63b0722e28..23ab5979595af 100644 --- a/src/plugins/data/public/search/search_source/create_search_source.test.ts +++ b/src/plugins/data/public/search/search_source/create_search_source.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { createSearchSourceFromJSON } from './create_search_source'; +import { createSearchSource as createSearchSourceFactory } from './create_search_source'; import { IIndexPattern } from '../../../common/index_patterns'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { Filter } from '../../../common/es_query/filters'; @@ -27,7 +27,7 @@ describe('createSearchSource', () => { const indexPatternMock: IIndexPattern = {} as IIndexPattern; let indexPatternContractMock: jest.Mocked; let dependencies: any; - let createSearchSource: ReturnType; + let createSearchSource: ReturnType; beforeEach(() => { const core = coreMock.createStart(); @@ -43,27 +43,17 @@ describe('createSearchSource', () => { get: jest.fn().mockReturnValue(Promise.resolve(indexPatternMock)), } as unknown) as jest.Mocked; - createSearchSource = createSearchSourceFromJSON(indexPatternContractMock, dependencies); + createSearchSource = createSearchSourceFactory(indexPatternContractMock, dependencies); }); - test('should fail if JSON is invalid', () => { - expect(createSearchSource('{', [])).rejects.toThrow(); - expect(createSearchSource('0', [])).rejects.toThrow(); - expect(createSearchSource('"abcdefg"', [])).rejects.toThrow(); - }); - - test('should set fields', async () => { - const searchSource = await createSearchSource( - JSON.stringify({ - highlightAll: true, - query: { - query: '', - language: 'kuery', - }, - }), - [] - ); - + it('should set fields', async () => { + const searchSource = await createSearchSource({ + highlightAll: true, + query: { + query: '', + language: 'kuery', + }, + }); expect(searchSource.getOwnField('highlightAll')).toBe(true); expect(searchSource.getOwnField('query')).toEqual({ query: '', @@ -71,66 +61,32 @@ describe('createSearchSource', () => { }); }); - test('should resolve referenced index pattern', async () => { - const searchSource = await createSearchSource( - JSON.stringify({ - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', - }), - [ + it('should set filters and resolve referenced index patterns', async () => { + const searchSource = await createSearchSource({ + filter: [ { - id: '123-456', - type: 'index-pattern', - name: 'kibanaSavedObjectMeta.searchSourceJSON.index', - }, - ] - ); - - expect(indexPatternContractMock.get).toHaveBeenCalledWith('123-456'); - expect(searchSource.getOwnField('index')).toBe(indexPatternMock); - }); - - test('should set filters and resolve referenced index patterns', async () => { - const searchSource = await createSearchSource( - JSON.stringify({ - filter: [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'category.keyword', - params: { - query: "Men's Clothing", - }, - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'category.keyword', + params: { + query: "Men's Clothing", }, - query: { - match_phrase: { - 'category.keyword': "Men's Clothing", - }, - }, - $state: { - store: 'appState', + index: '123-456', + }, + query: { + match_phrase: { + 'category.keyword': "Men's Clothing", }, }, - ], - }), - [ - { - id: '123-456', - type: 'index-pattern', - name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', }, - ] - ); + ], + }); const filters = searchSource.getOwnField('filter') as Filter[]; - expect(filters[0]).toMatchInlineSnapshot(` Object { - "$state": Object { - "store": "appState", - }, "meta": Object { "alias": null, "disabled": false, @@ -151,15 +107,11 @@ describe('createSearchSource', () => { `); }); - test('should migrate legacy queries on the fly', async () => { - const searchSource = await createSearchSource( - JSON.stringify({ - highlightAll: true, - query: 'a:b', - }), - [] - ); - + it('should migrate legacy queries on the fly', async () => { + const searchSource = await createSearchSource({ + highlightAll: true, + query: 'a:b' as any, + }); expect(searchSource.getOwnField('query')).toEqual({ query: 'a:b', language: 'lucene', diff --git a/src/plugins/data/public/search/search_source/create_search_source.ts b/src/plugins/data/public/search/search_source/create_search_source.ts index cc98f433b3a03..3466d60e5dd7e 100644 --- a/src/plugins/data/public/search/search_source/create_search_source.ts +++ b/src/plugins/data/public/search/search_source/create_search_source.ts @@ -16,11 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { transform, defaults, isFunction } from 'lodash'; -import { SavedObjectReference } from 'kibana/public'; import { migrateLegacyQuery } from '../../../../kibana_legacy/public'; -import { InvalidJSONProperty } from '../../../../kibana_utils/public'; -import { SearchSourceDependencies, SearchSource, ISearchSource } from './search_source'; +import { SearchSource, SearchSourceDependencies } from './search_source'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { SearchSourceFields } from './types'; @@ -33,6 +30,7 @@ import { SearchSourceFields } from './types'; * the start contract of the data plugin as part of the search service * * @param indexPatterns The index patterns contract of the data plugin + * @param searchSourceDependencies * * @return Wired utility function taking two parameters `searchSourceJson`, the json string * returned by `serializeSearchSource` and `references`, a list of references including the ones @@ -40,73 +38,20 @@ import { SearchSourceFields } from './types'; * * * @public */ -export const createSearchSourceFromJSON = ( +export const createSearchSource = ( indexPatterns: IndexPatternsContract, searchSourceDependencies: SearchSourceDependencies -) => async ( - searchSourceJson: string, - references: SavedObjectReference[] -): Promise => { - const searchSource = new SearchSource({}, searchSourceDependencies); +) => async (searchSourceFields: SearchSourceFields = {}) => { + const fields = { ...searchSourceFields }; - // if we have a searchSource, set its values based on the searchSourceJson field - let searchSourceValues: Record; - try { - searchSourceValues = JSON.parse(searchSourceJson); - } catch (e) { - throw new InvalidJSONProperty( - `Invalid JSON in search source. ${e.message} JSON: ${searchSourceJson}` - ); + // hydrating index pattern + if (fields.index && typeof fields.index === 'string') { + fields.index = await indexPatterns.get(searchSourceFields.index as any); } - // This detects a scenario where documents with invalid JSON properties have been imported into the saved object index. - // (This happened in issue #20308) - if (!searchSourceValues || typeof searchSourceValues !== 'object') { - throw new InvalidJSONProperty('Invalid JSON in search source.'); - } - - // Inject index id if a reference is saved - if (searchSourceValues.indexRefName) { - const reference = references.find(ref => ref.name === searchSourceValues.indexRefName); - if (!reference) { - throw new Error(`Could not find reference for ${searchSourceValues.indexRefName}`); - } - searchSourceValues.index = reference.id; - delete searchSourceValues.indexRefName; - } - - if (searchSourceValues.filter && Array.isArray(searchSourceValues.filter)) { - searchSourceValues.filter.forEach((filterRow: any) => { - if (!filterRow.meta || !filterRow.meta.indexRefName) { - return; - } - const reference = references.find((ref: any) => ref.name === filterRow.meta.indexRefName); - if (!reference) { - throw new Error(`Could not find reference for ${filterRow.meta.indexRefName}`); - } - filterRow.meta.index = reference.id; - delete filterRow.meta.indexRefName; - }); - } - - if (searchSourceValues.index && typeof searchSourceValues.index === 'string') { - searchSourceValues.index = await indexPatterns.get(searchSourceValues.index); - } - - const searchSourceFields = searchSource.getFields(); - const fnProps = transform( - searchSourceFields, - function(dynamic, val, name) { - if (isFunction(val) && name) dynamic[name] = val; - }, - {} - ); - - // This assignment might hide problems because the type of values passed from the parsed JSON - // might not fit the SearchSourceFields interface. - const newFields: SearchSourceFields = defaults(searchSourceValues, fnProps); + const searchSource = new SearchSource(fields, searchSourceDependencies); - searchSource.setFields(newFields); + // todo: move to migration script .. create issue const query = searchSource.getOwnField('query'); if (typeof query !== 'undefined') { diff --git a/src/plugins/data/public/search/search_source/extract_references.ts b/src/plugins/data/public/search/search_source/extract_references.ts new file mode 100644 index 0000000000000..f9987767a9688 --- /dev/null +++ b/src/plugins/data/public/search/search_source/extract_references.ts @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectReference } from '../../../../../core/types'; +import { Filter } from '../../../common/es_query/filters'; +import { SearchSourceFields } from './types'; + +export const extractReferences = ( + state: SearchSourceFields +): [SearchSourceFields & { indexRefName?: string }, SavedObjectReference[]] => { + let searchSourceFields: SearchSourceFields & { indexRefName?: string } = { ...state }; + const references: SavedObjectReference[] = []; + if (searchSourceFields.index) { + const indexId = searchSourceFields.index.id || ((searchSourceFields.index as any) as string); + const refName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; + references.push({ + name: refName, + type: 'index-pattern', + id: indexId, + }); + searchSourceFields = { + ...searchSourceFields, + indexRefName: refName, + index: undefined, + }; + } + + if (searchSourceFields.filter) { + searchSourceFields = { + ...searchSourceFields, + filter: (searchSourceFields.filter as Filter[]).map((filterRow, i) => { + if (!filterRow.meta || !filterRow.meta.index) { + return filterRow; + } + const refName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; + references.push({ + name: refName, + type: 'index-pattern', + id: filterRow.meta.index, + }); + return { + ...filterRow, + meta: { + ...filterRow.meta, + indexRefName: refName, + index: undefined, + }, + }; + }), + }; + } + + return [searchSourceFields, references]; +}; diff --git a/src/plugins/data/public/search/search_source/index.ts b/src/plugins/data/public/search/search_source/index.ts index 9c4106b2dc616..48c0338f7e981 100644 --- a/src/plugins/data/public/search/search_source/index.ts +++ b/src/plugins/data/public/search/search_source/index.ts @@ -18,5 +18,8 @@ */ export { SearchSource, ISearchSource, SearchSourceDependencies } from './search_source'; -export { createSearchSourceFromJSON } from './create_search_source'; +export { createSearchSource } from './create_search_source'; export { SortDirection, EsQuerySortValue, SearchSourceFields } from './types'; +export { injectReferences } from './inject_references'; +export { extractReferences } from './extract_references'; +export { parseSearchSourceJSON } from './parse_json'; diff --git a/src/plugins/data/public/search/search_source/inject_references.ts b/src/plugins/data/public/search/search_source/inject_references.ts new file mode 100644 index 0000000000000..a567c33d2280b --- /dev/null +++ b/src/plugins/data/public/search/search_source/inject_references.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SearchSourceFields } from './types'; +import { SavedObjectReference } from '../../../../../core/types'; + +export const injectReferences = ( + searchSourceFields: SearchSourceFields & { indexRefName: string }, + references: SavedObjectReference[] +) => { + const searchSourceReturnFields: SearchSourceFields = { ...searchSourceFields }; + // Inject index id if a reference is saved + if (searchSourceFields.indexRefName) { + const reference = references.find(ref => ref.name === searchSourceFields.indexRefName); + if (!reference) { + throw new Error(`Could not find reference for ${searchSourceFields.indexRefName}`); + } + // @ts-ignore + searchSourceReturnFields.index = reference.id; + // @ts-ignore + delete searchSourceReturnFields.indexRefName; + } + + if (searchSourceReturnFields.filter && Array.isArray(searchSourceReturnFields.filter)) { + searchSourceReturnFields.filter.forEach((filterRow: any) => { + if (!filterRow.meta || !filterRow.meta.indexRefName) { + return; + } + const reference = references.find((ref: any) => ref.name === filterRow.meta.indexRefName); + if (!reference) { + throw new Error(`Could not find reference for ${filterRow.meta.indexRefName}`); + } + filterRow.meta.index = reference.id; + delete filterRow.meta.indexRefName; + }); + } + + return searchSourceReturnFields; +}; diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts index 157331ea87bb0..cf2d009e41b54 100644 --- a/src/plugins/data/public/search/search_source/mocks.ts +++ b/src/plugins/data/public/search/search_source/mocks.ts @@ -43,12 +43,13 @@ export const searchSourceInstanceMock: MockedKeys = { getSearchRequestBody: jest.fn(), destroy: jest.fn(), history: [], + getSerializedFields: jest.fn(), serialize: jest.fn(), }; export const searchSourceMock = { create: jest.fn().mockReturnValue(searchSourceInstanceMock), - fromJSON: jest.fn().mockReturnValue(searchSourceInstanceMock), + createEmpty: jest.fn().mockReturnValue(searchSourceInstanceMock), }; export const createSearchSourceMock = (fields?: SearchSourceFields) => diff --git a/src/plugins/data/public/search/search_source/parse_json.ts b/src/plugins/data/public/search/search_source/parse_json.ts new file mode 100644 index 0000000000000..f0eb377cedc77 --- /dev/null +++ b/src/plugins/data/public/search/search_source/parse_json.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SearchSourceFields } from './types'; +import { InvalidJSONProperty } from '../../../../kibana_utils/public'; + +export const parseSearchSourceJSON = (searchSourceJSON: string) => { + // if we have a searchSource, set its values based on the searchSourceJson field + let searchSourceValues: SearchSourceFields; + try { + searchSourceValues = JSON.parse(searchSourceJSON); + } catch (e) { + throw new InvalidJSONProperty( + `Invalid JSON in search source. ${e.message} JSON: ${searchSourceJSON}` + ); + } + + // This detects a scenario where documents with invalid JSON properties have been imported into the saved object index. + // (This happened in issue #20308) + if (!searchSourceValues || typeof searchSourceValues !== 'object') { + throw new InvalidJSONProperty('Invalid JSON in search source.'); + } + + return searchSourceValues; +}; diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts index 091a27a6f418d..acbb193807623 100644 --- a/src/plugins/data/public/search/search_source/search_source.ts +++ b/src/plugins/data/public/search/search_source/search_source.ts @@ -69,9 +69,9 @@ * `appSearchSource`. */ -import { uniqueId, uniq, extend, pick, difference, set, omit, keys, isFunction } from 'lodash'; +import { uniqueId, uniq, extend, pick, difference, omit, set, keys, isFunction } from 'lodash'; import { map } from 'rxjs/operators'; -import { CoreStart, SavedObjectReference } from 'kibana/public'; +import { CoreStart } from 'kibana/public'; import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/public'; @@ -82,6 +82,7 @@ import { FetchOptions, RequestFailure, getSearchParams, handleResponse } from '. import { getEsQueryConfig, buildEsQuery, Filter } from '../../../common'; import { getHighlightRequest } from '../../../common/field_formats'; import { fetchSoon } from '../legacy'; +import { extractReferences } from './extract_references'; import { ISearchStartLegacy } from '../types'; export interface SearchSourceDependencies { @@ -450,6 +451,25 @@ export class SearchSource { return searchRequest; } + public getSerializedFields() { + const { filter: originalFilters, ...searchSourceFields } = omit(this.getFields(), [ + 'sort', + 'size', + ]); + let serializedSearchSourceFields: SearchSourceFields = { + ...searchSourceFields, + index: searchSourceFields.index ? searchSourceFields.index.id : undefined, + }; + if (originalFilters) { + const filters = this.getFilters(originalFilters); + serializedSearchSourceFields = { + ...serializedSearchSourceFields, + filter: filters, + }; + } + return serializedSearchSourceFields; + } + /** * Serializes the instance to a JSON string and a set of referenced objects. * Use this method to get a representation of the search source which can be stored in a saved object. @@ -461,57 +481,8 @@ export class SearchSource { * Using `createSearchSource`, the instance can be re-created. * @public */ public serialize() { - const references: SavedObjectReference[] = []; - - const { - filter: originalFilters, - ...searchSourceFields - }: Omit = omit(this.getFields(), ['sort', 'size']); - let serializedSearchSourceFields: Omit & { - indexRefName?: string; - filter?: Array & { meta: Filter['meta'] & { indexRefName?: string } }>; - } = searchSourceFields; - if (searchSourceFields.index) { - const indexId = searchSourceFields.index.id!; - const refName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; - references.push({ - name: refName, - type: 'index-pattern', - id: indexId, - }); - serializedSearchSourceFields = { - ...serializedSearchSourceFields, - indexRefName: refName, - index: undefined, - }; - } - if (originalFilters) { - const filters = this.getFilters(originalFilters); - serializedSearchSourceFields = { - ...serializedSearchSourceFields, - filter: filters.map((filterRow, i) => { - if (!filterRow.meta || !filterRow.meta.index) { - return filterRow; - } - const refName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; - references.push({ - name: refName, - type: 'index-pattern', - id: filterRow.meta.index, - }); - return { - ...filterRow, - meta: { - ...filterRow.meta, - indexRefName: refName, - index: undefined, - }, - }; - }), - }; - } - - return { searchSourceJSON: JSON.stringify(serializedSearchSourceFields), references }; + const [searchSourceFields, references] = extractReferences(this.getSerializedFields()); + return { searchSourceJSON: JSON.stringify(searchSourceFields), references }; } private getFilters(filterField: SearchSourceFields['filter']): Filter[] { diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index 1687c8f983393..64b4f1c5c2983 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { CoreStart, SavedObjectReference } from 'kibana/public'; +import { CoreStart } from 'kibana/public'; import { SearchAggsSetup, SearchAggsStart } from './aggs'; import { ISearch, ISearchGeneric } from './i_search'; import { TStrategyTypes } from './strategy_types'; @@ -82,11 +82,8 @@ export interface ISearchStart { setInterceptor: (searchInterceptor: SearchInterceptor) => void; search: ISearchGeneric; searchSource: { - create: (fields?: SearchSourceFields) => ISearchSource; - fromJSON: ( - searchSourceJson: string, - references: SavedObjectReference[] - ) => Promise; + create: (fields?: SearchSourceFields) => Promise; + createEmpty: () => ISearchSource; }; __LEGACY: ISearchStartLegacy; } diff --git a/src/plugins/discover/public/application/angular/context/api/context.ts b/src/plugins/discover/public/application/angular/context/api/context.ts index 0bca820f9a723..a47005b640538 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.ts @@ -113,8 +113,8 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract) { async function createSearchSource(indexPattern: IndexPattern, filters: Filter[]) { const { data } = getServices(); - return data.search.searchSource - .create() + const searchSource = await data.search.searchSource.create(); + return searchSource .setParent(undefined) .setField('index', indexPattern) .setField('filter', filters); diff --git a/src/plugins/discover/public/application/angular/context/query/actions.js b/src/plugins/discover/public/application/angular/context/query/actions.js index 1b1fa7138bfda..6f8d5fe64f831 100644 --- a/src/plugins/discover/public/application/angular/context/query/actions.js +++ b/src/plugins/discover/public/application/angular/context/query/actions.js @@ -30,7 +30,7 @@ import { MarkdownSimple } from '../../../../../../kibana_react/public'; export function QueryActionsProvider(Promise) { const { filterManager, indexPatterns, data } = getServices(); - const fetchAnchor = fetchAnchorProvider(indexPatterns, data.search.searchSource.create()); + const fetchAnchor = fetchAnchorProvider(indexPatterns, data.search.searchSource.createEmpty()); const { fetchSurroundingDocs } = fetchContextProvider(indexPatterns); const { setPredecessorCount, setQueryParameters, setSuccessorCount } = getQueryParameterActions( filterManager, diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 2afd0322f8701..b6076f338d63f 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -1021,7 +1021,7 @@ function discoverController( }, ]; - $scope.vis = visualizations.createVis('histogram', { + $scope.vis = await visualizations.createVis('histogram', { title: savedSearch.title, params: { addLegend: false, @@ -1029,8 +1029,7 @@ function discoverController( }, data: { aggs: visStateAggs, - indexPattern: $scope.searchSource.getField('index').id, - searchSource: $scope.searchSource, + searchSource: $scope.searchSource.getSerializedFields(), }, }); diff --git a/src/plugins/discover/public/mocks.ts b/src/plugins/discover/public/mocks.ts index dd7da5e8bc254..c394fe2c11a71 100644 --- a/src/plugins/discover/public/mocks.ts +++ b/src/plugins/discover/public/mocks.ts @@ -33,9 +33,7 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { - savedSearches: { - createLoader: jest.fn(), - }, + savedSearchLoader: {} as any, }; return startContract; }; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 807365cb26dc0..032483e4e34ba 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -39,7 +39,7 @@ import { KibanaLegacySetup, AngularRenderedAppUpdater } from 'src/plugins/kibana import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { Start as InspectorPublicPluginStart } from 'src/plugins/inspector/public'; import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public'; -import { SavedObjectLoader, SavedObjectKibanaServices } from '../../saved_objects/public'; +import { SavedObjectLoader } from '../../saved_objects/public'; import { createKbnUrlTracker } from '../../kibana_utils/public'; import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types'; @@ -73,13 +73,7 @@ export interface DiscoverSetup { } export interface DiscoverStart { - savedSearches: { - /** - * Create a {@link SavedObjectLoader | loader} to handle the saved searches type. - * @param services - */ - createLoader(services: SavedObjectKibanaServices): SavedObjectLoader; - }; + savedSearchLoader: SavedObjectLoader; } /** @@ -264,9 +258,13 @@ export class DiscoverPlugin }; return { - savedSearches: { - createLoader: createSavedSearchesLoader, - }, + savedSearchLoader: createSavedSearchesLoader({ + savedObjectsClient: core.savedObjects.client, + indexPatterns: plugins.data.indexPatterns, + search: plugins.data.search, + chrome: core.chrome, + overlays: core.overlays, + }), }; } diff --git a/src/plugins/input_control_vis/public/control/create_search_source.ts b/src/plugins/input_control_vis/public/control/create_search_source.ts index d6772a7cba5b8..6cd16161c8a87 100644 --- a/src/plugins/input_control_vis/public/control/create_search_source.ts +++ b/src/plugins/input_control_vis/public/control/create_search_source.ts @@ -25,7 +25,7 @@ import { DataPublicPluginStart, } from 'src/plugins/data/public'; -export function createSearchSource( +export async function createSearchSource( { create }: DataPublicPluginStart['search']['searchSource'], initialState: SearchSourceFields | null, indexPattern: IndexPattern, @@ -34,7 +34,7 @@ export function createSearchSource( filters: PhraseFilter[] = [], timefilter: TimefilterContract ) { - const searchSource = create(initialState || {}); + const searchSource = await create(initialState || {}); // Do not not inherit from rootSearchSource to avoid picking up time and globals searchSource.setParent(undefined); diff --git a/src/plugins/input_control_vis/public/control/list_control_factory.ts b/src/plugins/input_control_vis/public/control/list_control_factory.ts index 123ef83277e0b..87aa4a2486c49 100644 --- a/src/plugins/input_control_vis/public/control/list_control_factory.ts +++ b/src/plugins/input_control_vis/public/control/list_control_factory.ts @@ -147,7 +147,7 @@ export class ListControl extends Control { direction: 'desc', query, }); - const searchSource = createSearchSource( + const searchSource = await createSearchSource( this.searchSource, initialSearchSourceState, indexPattern, diff --git a/src/plugins/input_control_vis/public/control/range_control_factory.ts b/src/plugins/input_control_vis/public/control/range_control_factory.ts index 326756ad5ffc6..eac79ca5fcca8 100644 --- a/src/plugins/input_control_vis/public/control/range_control_factory.ts +++ b/src/plugins/input_control_vis/public/control/range_control_factory.ts @@ -84,7 +84,7 @@ export class RangeControl extends Control { const fieldName = this.filterManager.fieldName; const aggs = minMaxAgg(indexPattern.fields.getByName(fieldName)); - const searchSource = createSearchSource( + const searchSource = await createSearchSource( this.searchSource, null, indexPattern, diff --git a/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts b/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts index df687a051fc7d..9d0e25132271c 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts @@ -19,7 +19,11 @@ import _ from 'lodash'; import { EsResponse, SavedObject, SavedObjectConfig, SavedObjectKibanaServices } from '../../types'; import { expandShorthand, SavedObjectNotFound } from '../../../../kibana_utils/public'; -import { IndexPattern } from '../../../../data/public'; +import { + IndexPattern, + injectSearchSourceReferences, + parseSearchSourceJSON, +} from '../../../../data/public'; /** * A given response of and ElasticSearch containing a plain saved object is applied to the given @@ -63,12 +67,21 @@ export async function applyESResp( _.assign(savedObject, savedObject._source); savedObject.lastSavedTitle = savedObject.title; - if (config.searchSource) { + if (meta.searchSourceJSON) { try { - savedObject.searchSource = await dependencies.search.searchSource.fromJSON( - meta.searchSourceJSON, - resp.references - ); + let searchSourceValues = parseSearchSourceJSON(meta.searchSourceJSON); + + if (config.searchSource) { + searchSourceValues = injectSearchSourceReferences( + searchSourceValues as any, + resp.references + ); + savedObject.searchSource = await dependencies.search.searchSource.create( + searchSourceValues + ); + } else { + savedObject.searchSourceFields = searchSourceValues; + } } catch (error) { if ( error.constructor.name === 'SavedObjectNotFound' && diff --git a/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts b/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts index e5b0e18e7b433..fdc8d79c9428a 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts @@ -55,7 +55,7 @@ export function buildSavedObject( savedObject.defaults = config.defaults || {}; // optional search source which this object configures savedObject.searchSource = config.searchSource - ? services.search.searchSource.create() + ? services.search.searchSource.createEmpty() : undefined; // the id of the document savedObject.id = config.id || void 0; diff --git a/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts b/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts index 78f9eeb8b5fb1..acb371b8af9c2 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/serialize_saved_object.ts @@ -19,6 +19,7 @@ import _ from 'lodash'; import { SavedObject, SavedObjectConfig } from '../../types'; import { expandShorthand } from '../../../../kibana_utils/public'; +import { extractSearchSourceReferences } from '../../../../data/public'; export function serializeSavedObject(savedObject: SavedObject, config: SavedObjectConfig) { // mapping definition for the fields that this object will expose @@ -48,6 +49,15 @@ export function serializeSavedObject(savedObject: SavedObject, config: SavedObje references.push(...searchSourceReferences); } + if (savedObject.searchSourceFields) { + const [searchSourceFields, searchSourceReferences] = extractSearchSourceReferences( + savedObject.searchSourceFields + ); + const searchSourceJSON = JSON.stringify(searchSourceFields); + attributes.kibanaSavedObjectMeta = { searchSourceJSON }; + references.push(...searchSourceReferences); + } + if (savedObject.unresolvedIndexPatternReference) { references.push(savedObject.unresolvedIndexPatternReference); } diff --git a/src/plugins/saved_objects/public/saved_object/saved_object.test.ts b/src/plugins/saved_objects/public/saved_object/saved_object.test.ts index f7e67dbe3ee1d..66587a5d068c9 100644 --- a/src/plugins/saved_objects/public/saved_object/saved_object.test.ts +++ b/src/plugins/saved_objects/public/saved_object/saved_object.test.ts @@ -111,6 +111,7 @@ describe('Saved Object', () => { searchSource: { ...dataStartMock.search.searchSource, create: createSearchSourceMock, + createEmpty: createSearchSourceMock, }, }, } as unknown) as SavedObjectKibanaServices); @@ -572,46 +573,50 @@ describe('Saved Object', () => { }); it('passes references to search source parsing function', async () => { + SavedObjectClass = createSavedObjectClass(({ + savedObjectsClient: savedObjectsClientStub, + indexPatterns: dataStartMock.indexPatterns, + search: { + ...dataStartMock.search, + }, + } as unknown) as SavedObjectKibanaServices); const savedObject = new SavedObjectClass({ type: 'dashboard', searchSource: true }); - await savedObject.init!(); - - const searchSourceJSON = JSON.stringify({ - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', - filter: [ - { - meta: { - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', + return savedObject.init!().then(async () => { + const searchSourceJSON = JSON.stringify({ + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', + filter: [ + { + meta: { + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', + }, + }, + ], + }); + const response = { + found: true, + _source: { + kibanaSavedObjectMeta: { + searchSourceJSON, }, }, - ], - }); - const response = { - found: true, - _source: { - kibanaSavedObjectMeta: { - searchSourceJSON, - }, - }, - references: [ - { - name: 'kibanaSavedObjectMeta.searchSourceJSON.index', - type: 'index-pattern', - id: 'my-index-1', - }, - { - name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', - type: 'index-pattern', - id: 'my-index-2', - }, - ], - }; - const result = await savedObject.applyESResp(response); - - expect(result._source).toEqual({ - kibanaSavedObjectMeta: { - searchSourceJSON: - '{"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index","filter":[{"meta":{"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index"}}]}', - }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: 'my-index-1', + }, + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', + type: 'index-pattern', + id: 'my-index-2', + }, + ], + }; + await savedObject.applyESResp(response); + expect(dataStartMock.search.searchSource.create).toBeCalledWith({ + filter: [{ meta: { index: 'my-index-2' } }], + index: 'my-index-1', + }); }); }); }); diff --git a/src/plugins/saved_objects/public/types.ts b/src/plugins/saved_objects/public/types.ts index 3184038040952..973a493c0a15e 100644 --- a/src/plugins/saved_objects/public/types.ts +++ b/src/plugins/saved_objects/public/types.ts @@ -29,6 +29,7 @@ import { IIndexPattern, IndexPatternsContract, ISearchSource, + SearchSourceFields, } from '../../data/public'; export interface SavedObject { @@ -52,6 +53,7 @@ export interface SavedObject { migrationVersion?: Record; save: (saveOptions: SavedObjectSaveOpts) => Promise; searchSource?: ISearchSource; + searchSourceFields?: SearchSourceFields; showInRecentlyAccessed: boolean; title: string; unresolvedIndexPatternReference?: SavedObjectReference; diff --git a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts index d4764b8949a60..8da8a5b1cebbc 100644 --- a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts +++ b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts @@ -21,7 +21,12 @@ import { i18n } from '@kbn/i18n'; import { cloneDeep } from 'lodash'; import { OverlayStart, SavedObjectReference } from 'src/core/public'; import { SavedObject, SavedObjectLoader } from '../../../saved_objects/public'; -import { IndexPatternsContract, IIndexPattern, DataPublicPluginStart } from '../../../data/public'; +import { + DataPublicPluginStart, + IndexPatternsContract, + IIndexPattern, + injectSearchSourceReferences, +} from '../../../data/public'; type SavedObjectsRawDoc = Record; @@ -207,13 +212,17 @@ export async function resolveIndexPatternConflicts( return reference; }); + const serializedSearchSourceWithInjectedReferences = injectSearchSourceReferences( + serializedSearchSource, + replacedReferences + ); + if (!allResolved) { // The user decided to skip this conflict so do nothing return; } - obj.searchSource = await dependencies.search.searchSource.fromJSON( - JSON.stringify(serializedSearchSource), - replacedReferences + obj.searchSource = await dependencies.search.searchSource.create( + serializedSearchSourceWithInjectedReferences ); if (await saveObject(obj, overwriteAll)) { importCount++; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index 3b7f48f366400..25362c067b4f9 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -280,7 +280,7 @@ exports[`SavedObjectsTable import should show the flyout 1`] = ` "search": [MockFunction], "searchSource": Object { "create": [MockFunction], - "fromJSON": [MockFunction], + "createEmpty": [MockFunction], }, "setInterceptor": [MockFunction], } diff --git a/src/plugins/saved_objects_management/public/register_services.ts b/src/plugins/saved_objects_management/public/register_services.ts index a34b632b78f6c..320169fb01b35 100644 --- a/src/plugins/saved_objects_management/public/register_services.ts +++ b/src/plugins/saved_objects_management/public/register_services.ts @@ -25,7 +25,7 @@ export const registerServices = async ( registry: ISavedObjectsManagementServiceRegistry, getStartServices: StartServicesAccessor ) => { - const [coreStart, { dashboard, data, visualizations, discover }] = await getStartServices(); + const [, { dashboard, visualizations, discover }] = await getStartServices(); if (dashboard) { registry.register({ @@ -47,13 +47,7 @@ export const registerServices = async ( registry.register({ id: 'savedSearches', title: 'searches', - service: discover.savedSearches.createLoader({ - savedObjectsClient: coreStart.savedObjects.client, - indexPatterns: data.indexPatterns, - search: data.search, - chrome: coreStart.chrome, - overlays: coreStart.overlays, - }), + service: discover.savedSearchLoader, }); } }; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index c6d43a4ef2f80..6c4a971858840 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -122,7 +122,9 @@ export class VisualizeEmbeddableFactory try { const savedObject = await savedVisualizations.get(savedObjectId); - const vis = new Vis(savedObject.visState.type, await convertToSerializedVis(savedObject)); + const visState = convertToSerializedVis(savedObject); + const vis = new Vis(savedObject.visState.type, visState); + await vis.setState(visState); return createVisEmbeddableFromObject(this.deps)(vis, input, parent); } catch (e) { console.error(e); // eslint-disable-line no-console diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts index 5476ce6df0390..9130581963800 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts @@ -398,6 +398,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => { return aggs; }, } as any, + searchSource: {} as any, }, isHierarchical: () => { return false; @@ -473,6 +474,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => { return aggs; }, } as any, + searchSource: {} as any, }, isHierarchical: () => { return false; diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index 29d66ea963a66..1bd50c882e2ca 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -43,6 +43,7 @@ import { setAggs, setChrome, setOverlays, + setSavedSearchLoader, } from './services'; import { VISUALIZE_EMBEDDABLE_TYPE, @@ -70,6 +71,7 @@ import { convertFromSerializedVis, convertToSerializedVis, } from './saved_visualizations/_saved_vis'; +import { createSavedSearchesLoader } from '../../discover/public'; /** * Interface for this plugin's returned setup/start contracts. @@ -81,7 +83,7 @@ export type VisualizationsSetup = TypesSetup; export interface VisualizationsStart extends TypesStart { savedVisualizationsLoader: SavedVisualizationsLoader; - createVis: (visType: string, visState?: SerializedVis) => Vis; + createVis: (visType: string, visState: SerializedVis) => Promise; convertToSerializedVis: typeof convertToSerializedVis; convertFromSerializedVis: typeof convertFromSerializedVis; showNewVisModal: typeof showNewVisModal; @@ -174,7 +176,14 @@ export class VisualizationsPlugin visualizationTypes: types, }); setSavedVisualizationsLoader(savedVisualizationsLoader); - + const savedSearchLoader = createSavedSearchesLoader({ + savedObjectsClient: core.savedObjects.client, + indexPatterns: data.indexPatterns, + search: data.search, + chrome: core.chrome, + overlays: core.overlays, + }); + setSavedSearchLoader(savedSearchLoader); return { ...types, showNewVisModal, @@ -183,7 +192,11 @@ export class VisualizationsPlugin * @param {IIndexPattern} indexPattern - index pattern to use * @param {VisState} visState - visualization configuration */ - createVis: (visType: string, visState?: SerializedVis) => new Vis(visType, visState), + createVis: async (visType: string, visState: SerializedVis) => { + const vis = new Vis(visType); + await vis.setState(visState); + return vis; + }, convertToSerializedVis, convertFromSerializedVis, savedVisualizationsLoader, diff --git a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts index 8bc98ca4b4784..81e551b9abdcb 100644 --- a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts +++ b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts @@ -32,32 +32,25 @@ import { // @ts-ignore import { updateOldState } from '../legacy/vis_update_state'; import { extractReferences, injectReferences } from './saved_visualization_references'; -import { IIndexPattern, ISearchSource } from '../../../../plugins/data/public'; +import { IIndexPattern } from '../../../../plugins/data/public'; import { ISavedVis, SerializedVis } from '../types'; -import { createSavedSearchesLoader } from '../../../../plugins/discover/public'; -import { getChrome, getOverlays, getIndexPatterns, getSavedObjects, getSearch } from '../services'; +import { createSavedSearchesLoader } from '../../../discover/public'; -export const convertToSerializedVis = async (savedVis: ISavedVis): Promise => { - const { visState } = savedVis; - const searchSource = - savedVis.searchSource && (await getSearchSource(savedVis.searchSource, savedVis.savedSearchId)); +export const convertToSerializedVis = (savedVis: ISavedVis): SerializedVis => { + const { id, title, description, visState, uiStateJSON, searchSourceFields } = savedVis; - const indexPattern = - searchSource && searchSource.getField('index') ? searchSource.getField('index')!.id : undefined; - - const aggs = indexPattern ? visState.aggs || [] : visState.aggs; + const aggs = searchSourceFields && searchSourceFields.index ? visState.aggs || [] : visState.aggs; return { - id: savedVis.id, - title: savedVis.title, + id, + title, type: visState.type, - description: savedVis.description, + description, params: visState.params, - uiState: JSON.parse(savedVis.uiStateJSON || '{}'), + uiState: JSON.parse(uiStateJSON || '{}'), data: { - indexPattern, aggs, - searchSource, + searchSource: searchSourceFields!, savedSearchId: savedVis.savedSearchId, }, }; @@ -74,36 +67,14 @@ export const convertFromSerializedVis = (vis: SerializedVis): ISavedVis => { params: vis.params, }, uiStateJSON: JSON.stringify(vis.uiState), - searchSource: vis.data.searchSource!, + searchSourceFields: vis.data.searchSource, savedSearchId: vis.data.savedSearchId, }; }; -const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?: string) => { - const search = getSearch(); - - const searchSource = inputSearchSource.createCopy - ? inputSearchSource.createCopy() - : search.searchSource.create({ ...(inputSearchSource as any).fields }); - - if (savedSearchId) { - const savedSearch = await createSavedSearchesLoader({ - search, - savedObjectsClient: getSavedObjects().client, - indexPatterns: getIndexPatterns(), - chrome: getChrome(), - overlays: getOverlays(), - }).get(savedSearchId); - - searchSource.setParent(savedSearch.searchSource); - } - - searchSource!.setField('size', 0); - return searchSource; -}; - export function createSavedVisClass(services: SavedObjectKibanaServices) { const SavedObjectClass = createSavedObjectClass(services); + const savedSearch = createSavedSearchesLoader(services); class SavedVis extends SavedObjectClass { public static type: string = 'visualization'; @@ -117,7 +88,6 @@ export function createSavedVisClass(services: SavedObjectKibanaServices) { }; // Order these fields to the top, the rest are alphabetical public static fieldOrder = ['title', 'description']; - public static searchSource = true; constructor(opts: Record | string = {}) { if (typeof opts !== 'object') { @@ -128,7 +98,6 @@ export function createSavedVisClass(services: SavedObjectKibanaServices) { super({ type: SavedVis.type, mapping: SavedVis.mapping, - searchSource: SavedVis.searchSource, extractReferences, injectReferences, id: (opts.id as string) || '', @@ -144,11 +113,11 @@ export function createSavedVisClass(services: SavedObjectKibanaServices) { afterESResp: async (savedObject: SavedObject) => { const savedVis = (savedObject as any) as ISavedVis; savedVis.visState = await updateOldState(savedVis.visState); - if (savedVis.savedSearchId && savedVis.searchSource) { - savedObject.searchSource = await getSearchSource( - savedVis.searchSource, - savedVis.savedSearchId - ); + if (savedVis.searchSourceFields?.index) { + await services.indexPatterns.get(savedVis.searchSourceFields.index as any); + } + if (savedVis.savedSearchId) { + await savedSearch.get(savedVis.savedSearchId); } return (savedVis as any) as SavedObject; }, diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts index a14595524100b..d28853694b653 100644 --- a/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts +++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualization_references.ts @@ -18,6 +18,7 @@ */ import { SavedObjectAttributes, SavedObjectReference } from '../../../../core/public'; import { VisSavedObject } from '../types'; +import { injectSearchSourceReferences, extractSearchSourceReferences } from '../../../data/public'; export function extractReferences({ attributes, @@ -29,6 +30,14 @@ export function extractReferences({ const updatedAttributes = { ...attributes }; const updatedReferences = [...references]; + if (updatedAttributes.searchSourceFields) { + const [searchSource, searchSourceReferences] = extractSearchSourceReferences( + updatedAttributes.searchSourceFields as any + ); + updatedAttributes.searchSourceFields = searchSource; + searchSourceReferences.forEach(r => updatedReferences.push(r)); + } + // Extract saved search if (updatedAttributes.savedSearchId) { updatedReferences.push({ @@ -66,6 +75,12 @@ export function extractReferences({ } export function injectReferences(savedObject: VisSavedObject, references: SavedObjectReference[]) { + if (savedObject.searchSourceFields) { + savedObject.searchSourceFields = injectSearchSourceReferences( + savedObject.searchSourceFields as any, + references + ); + } if (savedObject.savedSearchRefName) { const savedSearchReference = references.find( reference => reference.name === savedObject.savedSearchRefName diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts index 618c61dff176a..22cdefcee6036 100644 --- a/src/plugins/visualizations/public/services.ts +++ b/src/plugins/visualizations/public/services.ts @@ -38,6 +38,7 @@ import { UsageCollectionSetup } from '../../../plugins/usage_collection/public'; import { ExpressionsStart } from '../../../plugins/expressions/public'; import { UiActionsStart } from '../../../plugins/ui_actions/public'; import { SavedVisualizationsLoader } from './saved_visualizations'; +import { SavedObjectLoader } from '../../saved_objects/public'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); @@ -84,3 +85,7 @@ export const [getAggs, setAggs] = createGetterSetter('Overlays'); export const [getChrome, setChrome] = createGetterSetter('Chrome'); + +export const [getSavedSearchLoader, setSavedSearchLoader] = createGetterSetter( + 'savedSearchLoader' +); diff --git a/src/plugins/visualizations/public/types.ts b/src/plugins/visualizations/public/types.ts index 54528a33414c3..3455d88b6ce9e 100644 --- a/src/plugins/visualizations/public/types.ts +++ b/src/plugins/visualizations/public/types.ts @@ -18,7 +18,7 @@ */ import { SavedObject } from '../../../plugins/saved_objects/public'; -import { ISearchSource, AggConfigOptions } from '../../../plugins/data/public'; +import { AggConfigOptions, SearchSourceFields } from '../../../plugins/data/public'; import { SerializedVis, Vis, VisParams } from './vis'; export { Vis, SerializedVis, VisParams }; @@ -45,7 +45,7 @@ export interface ISavedVis { title: string; description?: string; visState: SavedVisState; - searchSource?: ISearchSource; + searchSourceFields?: SearchSourceFields; uiStateJSON?: string; savedSearchRefName?: string; savedSearchId?: string; diff --git a/src/plugins/visualizations/public/vis.test.ts b/src/plugins/visualizations/public/vis.test.ts index fc9327903fc90..aba735656b7d9 100644 --- a/src/plugins/visualizations/public/vis.test.ts +++ b/src/plugins/visualizations/public/vis.test.ts @@ -18,8 +18,6 @@ */ import { Vis } from './vis'; -// @ts-ignore -import fixturesStubbedLogstashIndexPatternProvider from '../../../fixtures/stubbed_logstash_index_pattern'; jest.mock('./services', () => { class MockVisualizationController { @@ -36,7 +34,10 @@ jest.mock('./services', () => { // eslint-disable-next-line const { BaseVisType } = require('./vis_types/base_vis_type'); - + // eslint-disable-next-line + const { SearchSource } = require('../../data/public/search/search_source'); + // eslint-disable-next-line + const fixturesStubbedLogstashIndexPatternProvider = require('../../../fixtures/stubbed_logstash_index_pattern'); const visType = new BaseVisType({ name: 'pie', title: 'pie', @@ -51,6 +52,13 @@ jest.mock('./services', () => { aggs: cfg.map((aggConfig: any) => ({ ...aggConfig, toJSON: () => aggConfig })), }), }), + getSearch: () => ({ + searchSource: { + create: () => { + return new SearchSource({ index: fixturesStubbedLogstashIndexPatternProvider }); + }, + }, + }), }; }); @@ -66,19 +74,15 @@ describe('Vis Class', function() { { type: 'terms' as any, schema: 'segment', params: { field: 'geo.src' } }, ], searchSource: { - getField: (name: string) => { - if (name === 'index') { - return fixturesStubbedLogstashIndexPatternProvider(); - } - }, - createCopy: jest.fn(), + index: '123', }, }, params: { isDonut: true }, }; - beforeEach(function() { + beforeEach(async function() { vis = new Vis('test', stateFixture as any); + await vis.setState(stateFixture as any); }); const verifyVis = function(visToVerify: Vis) { diff --git a/src/plugins/visualizations/public/vis.ts b/src/plugins/visualizations/public/vis.ts index 009dd71b9a912..916467ac08f4f 100644 --- a/src/plugins/visualizations/public/vis.ts +++ b/src/plugins/visualizations/public/vis.ts @@ -28,21 +28,22 @@ */ import { isFunction, defaults, cloneDeep } from 'lodash'; +import { Assign } from '@kbn/utility-types'; import { PersistedState } from './persisted_state'; -import { getTypes, getAggs } from './services'; +import { getTypes, getAggs, getSearch, getSavedSearchLoader } from './services'; import { VisType } from './vis_types'; import { IAggConfigs, IndexPattern, ISearchSource, AggConfigOptions, + SearchSourceFields, } from '../../../plugins/data/public'; export interface SerializedVisData { expression?: string; aggs: AggConfigOptions[]; - indexPattern?: string; - searchSource?: ISearchSource; + searchSource: SearchSourceFields; savedSearchId?: string; } @@ -68,6 +69,19 @@ export interface VisParams { [key: string]: any; } +const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?: string) => { + const searchSource = inputSearchSource.createCopy(); + if (savedSearchId) { + const savedSearch = await getSavedSearchLoader().get(savedSearchId); + + searchSource.setParent(savedSearch.searchSource); + } + searchSource.setField('size', 0); + return searchSource; +}; + +type PartialVisState = Assign }>; + export class Vis { public readonly type: VisType; public readonly id?: string; @@ -86,8 +100,6 @@ export class Vis { this.params = this.getParams(visState.params); this.uiState = new PersistedState(visState.uiState); this.id = visState.id; - - this.setState(visState || {}); } private getType(visType: string) { @@ -102,7 +114,7 @@ export class Vis { return defaults({}, cloneDeep(params || {}), cloneDeep(this.type.visConfig.defaults || {})); } - setState(state: SerializedVis) { + async setState(state: PartialVisState) { let typeChanged = false; if (state.type && this.type.name !== state.type) { // @ts-ignore @@ -120,19 +132,24 @@ export class Vis { } if (state.data && state.data.searchSource) { - this.data.searchSource = state.data.searchSource!; + this.data.searchSource = await getSearch().searchSource.create(state.data.searchSource!); this.data.indexPattern = this.data.searchSource.getField('index'); } if (state.data && state.data.savedSearchId) { this.data.savedSearchId = state.data.savedSearchId; + if (this.data.searchSource) { + this.data.searchSource = await getSearchSource( + this.data.searchSource, + this.data.savedSearchId + ); + this.data.indexPattern = this.data.searchSource.getField('index'); + } } - if (state.data && state.data.aggs) { - const configStates = this.initializeDefaultsFromSchemas( - cloneDeep(state.data.aggs), - this.type.schemas.all || [] - ); + if (state.data && (state.data.aggs || !this.data.aggs)) { + const aggs = state.data.aggs ? cloneDeep(state.data.aggs) : []; + const configStates = this.initializeDefaultsFromSchemas(aggs, this.type.schemas.all || []); if (!this.data.indexPattern) { - if (state.data.aggs.length) { + if (aggs.length) { throw new Error('trying to initialize aggs without index pattern'); } return; @@ -142,22 +159,31 @@ export class Vis { } clone() { - return new Vis(this.type.name, this.serialize()); + const { data, ...restOfSerialized } = this.serialize(); + const vis = new Vis(this.type.name, restOfSerialized as any); + vis.setState({ ...restOfSerialized, data: {} }); + const aggs = this.data.indexPattern + ? getAggs().createAggConfigs(this.data.indexPattern, data.aggs) + : undefined; + vis.data = { + ...this.data, + aggs, + }; + return vis; } serialize(): SerializedVis { const aggs = this.data.aggs ? this.data.aggs.aggs.map(agg => agg.toJSON()) : []; - const indexPattern = this.data.searchSource && this.data.searchSource.getField('index'); return { id: this.id, title: this.title, + description: this.description, type: this.type.name, params: cloneDeep(this.params) as any, uiState: this.uiState.toJSON(), data: { aggs: aggs as any, - indexPattern: indexPattern ? indexPattern.id : undefined, - searchSource: this.data.searchSource!.createCopy(), + searchSource: this.data.searchSource ? this.data.searchSource.getSerializedFields() : {}, savedSearchId: this.data.savedSearchId, }, }; diff --git a/src/plugins/visualize/public/application/editor/editor.js b/src/plugins/visualize/public/application/editor/editor.js index bd699c762371c..78e5c92c5eab8 100644 --- a/src/plugins/visualize/public/application/editor/editor.js +++ b/src/plugins/visualize/public/application/editor/editor.js @@ -645,8 +645,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState title: savedVis.title, type: savedVis.type || stateContainer.getState().vis.type, }); - savedVis.searchSource.setField('query', stateContainer.getState().query); - savedVis.searchSource.setField('filter', stateContainer.getState().filters); + savedVis.searchSourceFields = searchSource.getSerializedFields(); savedVis.visState = stateContainer.getState().vis; savedVis.uiStateJSON = angular.toJson($scope.uiState.toJSON()); $appStatus.dirty = false; diff --git a/src/plugins/visualize/public/application/legacy_app.js b/src/plugins/visualize/public/application/legacy_app.js index e14057cab6ca9..d1c81e67be1b0 100644 --- a/src/plugins/visualize/public/application/legacy_app.js +++ b/src/plugins/visualize/public/application/legacy_app.js @@ -45,9 +45,9 @@ const getResolvedResults = deps => { return savedVis => { results.savedVis = savedVis; + const serializedVis = visualizations.convertToSerializedVis(savedVis); return visualizations - .convertToSerializedVis(savedVis) - .then(serializedVis => visualizations.createVis(serializedVis.type, serializedVis)) + .createVis(serializedVis.type, serializedVis) .then(vis => { if (vis.type.setup) { return vis.type.setup(vis).catch(() => vis); @@ -171,6 +171,10 @@ export function initVisualizeApp(app, deps) { return data.indexPatterns .ensureDefaultIndexPattern(history) .then(() => savedVisualizations.get($route.current.params)) + .then(savedVis => { + savedVis.searchSourceFields = { index: $route.current.params.indexPattern }; + return savedVis; + }) .then(getResolvedResults(deps)) .then(delay) .catch( diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js index a412c49faceac..21fd8b205b033 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js @@ -399,7 +399,7 @@ export class ESSearchSource extends AbstractESSource { } const searchService = getSearchService(); - const searchSource = searchService.searchSource.create(); + const searchSource = searchService.searchSource.createEmpty(); searchSource.setField('index', indexPattern); searchSource.setField('size', 1); diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js index 87733e347aa2a..b3341a1061d68 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.js @@ -125,7 +125,7 @@ export class AbstractESSource extends AbstractVectorSource { allFilters.push(getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters)); } const searchService = getSearchService(); - const searchSource = searchService.searchSource.create(initialSearchContext); + const searchSource = await searchService.searchSource.create(initialSearchContext); searchSource.setField('index', indexPattern); searchSource.setField('size', limit); @@ -135,7 +135,7 @@ export class AbstractESSource extends AbstractVectorSource { } if (searchFilters.sourceQuery) { - const layerSearchSource = searchService.searchSource.create(); + const layerSearchSource = searchService.searchSource.createEmpty(); layerSearchSource.setField('index', indexPattern); layerSearchSource.setField('query', searchFilters.sourceQuery); @@ -296,7 +296,7 @@ export class AbstractESSource extends AbstractVectorSource { const indexPattern = await this.getIndexPattern(); const searchService = getSearchService(); - const searchSource = searchService.searchSource.create(); + const searchSource = searchService.searchSource.createEmpty(); searchSource.setField('index', indexPattern); searchSource.setField('size', 0);