making vis (completely) serializable (#64207)

This commit is contained in:
Peter Pisljar 2020-05-12 13:29:58 +02:00 committed by GitHub
parent 3f43763bc3
commit 15c0644c97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 572 additions and 402 deletions

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [extractSearchSourceReferences](./kibana-plugin-plugins-data-public.extractsearchsourcereferences.md)
## extractSearchSourceReferences variable
<b>Signature:</b>
```typescript
extractReferences: (state: SearchSourceFields) => [SearchSourceFields & {
indexRefName?: string | undefined;
}, SavedObjectReference[]]
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [injectSearchSourceReferences](./kibana-plugin-plugins-data-public.injectsearchsourcereferences.md)
## injectSearchSourceReferences variable
<b>Signature:</b>
```typescript
injectReferences: (searchSourceFields: SearchSourceFields & {
indexRefName: string;
}, references: SavedObjectReference[]) => SearchSourceFields
```

View file

@ -101,11 +101,14 @@
| [esFilters](./kibana-plugin-plugins-data-public.esfilters.md) | | | [esFilters](./kibana-plugin-plugins-data-public.esfilters.md) | |
| [esKuery](./kibana-plugin-plugins-data-public.eskuery.md) | | | [esKuery](./kibana-plugin-plugins-data-public.eskuery.md) | |
| [esQuery](./kibana-plugin-plugins-data-public.esquery.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) | | | [fieldFormats](./kibana-plugin-plugins-data-public.fieldformats.md) | |
| [FilterBar](./kibana-plugin-plugins-data-public.filterbar.md) | | | [FilterBar](./kibana-plugin-plugins-data-public.filterbar.md) | |
| [getIndexPatternFieldListCreator](./kibana-plugin-plugins-data-public.getindexpatternfieldlistcreator.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<string>} | | [getKbnTypeNames](./kibana-plugin-plugins-data-public.getkbntypenames.md) | Get the esTypes known by all kbnFieldTypes {<!-- -->Array<string>} |
| [indexPatterns](./kibana-plugin-plugins-data-public.indexpatterns.md) | | | [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) | | | [QueryStringInput](./kibana-plugin-plugins-data-public.querystringinput.md) | |
| [search](./kibana-plugin-plugins-data-public.search.md) | | | [search](./kibana-plugin-plugins-data-public.search.md) | |
| [SearchBar](./kibana-plugin-plugins-data-public.searchbar.md) | | | [SearchBar](./kibana-plugin-plugins-data-public.searchbar.md) | |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [parseSearchSourceJSON](./kibana-plugin-plugins-data-public.parsesearchsourcejson.md)
## parseSearchSourceJSON variable
<b>Signature:</b>
```typescript
parseSearchSourceJSON: (searchSourceJSON: string) => SearchSourceFields
```

View file

@ -32,6 +32,7 @@ import { chartPluginMock } from '../../../../../plugins/charts/public/mocks';
import { advancedSettingsMock } from '../../../../../plugins/advanced_settings/public/mocks'; import { advancedSettingsMock } from '../../../../../plugins/advanced_settings/public/mocks';
import { savedObjectsManagementPluginMock } from '../../../../../plugins/saved_objects_management/public/mocks'; import { savedObjectsManagementPluginMock } from '../../../../../plugins/saved_objects_management/public/mocks';
import { visualizationsPluginMock } from '../../../../../plugins/visualizations/public/mocks'; import { visualizationsPluginMock } from '../../../../../plugins/visualizations/public/mocks';
import { discoverPluginMock } from '../../../../../plugins/discover/public/mocks';
/* eslint-enable @kbn/eslint/no-restricted-paths */ /* eslint-enable @kbn/eslint/no-restricted-paths */
export const pluginsMock = { export const pluginsMock = {
@ -48,6 +49,7 @@ export const pluginsMock = {
visualizations: visualizationsPluginMock.createSetupContract(), visualizations: visualizationsPluginMock.createSetupContract(),
kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(), kibanaLegacy: kibanaLegacyPluginMock.createSetupContract(),
savedObjectsManagement: savedObjectsManagementPluginMock.createSetupContract(), savedObjectsManagement: savedObjectsManagementPluginMock.createSetupContract(),
discover: discoverPluginMock.createSetupContract(),
}), }),
createStart: () => ({ createStart: () => ({
data: dataPluginMock.createStartContract(), data: dataPluginMock.createStartContract(),
@ -62,6 +64,7 @@ export const pluginsMock = {
visualizations: visualizationsPluginMock.createStartContract(), visualizations: visualizationsPluginMock.createStartContract(),
kibanaLegacy: kibanaLegacyPluginMock.createStartContract(), kibanaLegacy: kibanaLegacyPluginMock.createStartContract(),
savedObjectsManagement: savedObjectsManagementPluginMock.createStartContract(), savedObjectsManagement: savedObjectsManagementPluginMock.createStartContract(),
discover: discoverPluginMock.createStartContract(),
}), }),
}; };

View file

@ -515,6 +515,7 @@ export const npStart = {
docViews: { docViews: {
DocViewer: () => null, DocViewer: () => null,
}, },
savedSearchLoader: {},
}, },
}, },
}; };

View file

@ -57,6 +57,7 @@ export function setStartServices(npStart: NpStart) {
dataServices.setIndexPatterns(npStart.plugins.data.indexPatterns); dataServices.setIndexPatterns(npStart.plugins.data.indexPatterns);
dataServices.setQueryService(npStart.plugins.data.query); dataServices.setQueryService(npStart.plugins.data.query);
dataServices.setSearchService(npStart.plugins.data.search); dataServices.setSearchService(npStart.plugins.data.search);
visualizationsServices.setI18n(npStart.core.i18n); visualizationsServices.setI18n(npStart.core.i18n);
visualizationsServices.setTypes( visualizationsServices.setTypes(
pick(npStart.plugins.visualizations, ['get', 'all', 'getAliases']) pick(npStart.plugins.visualizations, ['get', 'all', 'getAliases'])
@ -82,4 +83,5 @@ export function setStartServices(npStart: NpStart) {
visualizationTypes: visualizationsServices.getTypes(), visualizationTypes: visualizationsServices.getTypes(),
}); });
visualizationsServices.setSavedVisualizationsLoader(savedVisualizationsLoader); visualizationsServices.setSavedVisualizationsLoader(savedVisualizationsLoader);
visualizationsServices.setSavedSearchLoader(npStart.plugins.discover.savedSearchLoader);
} }

View file

@ -358,6 +358,9 @@ export {
SearchResponse, SearchResponse,
SearchError, SearchError,
ISearchSource, ISearchSource,
parseSearchSourceJSON,
injectSearchSourceReferences,
extractSearchSourceReferences,
SearchSourceFields, SearchSourceFields,
EsQuerySortValue, EsQuerySortValue,
SortDirection, SortDirection,

View file

@ -46,7 +46,6 @@ import * as React_2 from 'react';
import { Required } from '@kbn/utility-types'; import { Required } from '@kbn/utility-types';
import * as Rx from 'rxjs'; import * as Rx from 'rxjs';
import { SavedObject as SavedObject_2 } from 'src/core/public'; import { SavedObject as SavedObject_2 } from 'src/core/public';
import { SavedObjectReference } from 'kibana/public';
import { SavedObjectsClientContract } from 'src/core/public'; import { SavedObjectsClientContract } from 'src/core/public';
import { SearchParams } from 'elasticsearch'; import { SearchParams } from 'elasticsearch';
import { SearchResponse as SearchResponse_2 } from 'elasticsearch'; import { SearchResponse as SearchResponse_2 } from 'elasticsearch';
@ -420,6 +419,14 @@ export type ExistsFilter = Filter & {
exists?: FilterExistsProperty; 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) // 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) // @public (undocumented)
@ -1059,6 +1066,13 @@ export interface IndexPatternTypeMeta {
aggs?: Record<string, IndexPatternAggRestrictions>; aggs?: Record<string, IndexPatternAggRestrictions>;
} }
// 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) // 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) // @public (undocumented)
@ -1273,6 +1287,11 @@ export interface OptionedValueProp {
// @public (undocumented) // @public (undocumented)
export type ParsedInterval = ReturnType<typeof parseEsInterval>; export type ParsedInterval = ReturnType<typeof parseEsInterval>;
// 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) // 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) // @public (undocumented)
@ -1809,20 +1828,20 @@ export type TSearchStrategyProvider<T extends TStrategyTypes> = (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 "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 "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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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 "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: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: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 "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: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: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/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 // 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

View file

@ -282,7 +282,7 @@ export const esaggs = (): ExpressionFunctionDefinition<typeof name, Input, Argum
const aggs = searchService.aggs.createAggConfigs(indexPattern, aggConfigsState); const aggs = searchService.aggs.createAggConfigs(indexPattern, aggConfigsState);
// we should move searchSource creation inside courier request handler // we should move searchSource creation inside courier request handler
const searchSource = searchService.searchSource.create(); const searchSource = await searchService.searchSource.create();
searchSource.setField('index', indexPattern); searchSource.setField('index', indexPattern);
searchSource.setField('size', 0); searchSource.setField('size', 0);

View file

@ -59,6 +59,9 @@ export {
SearchSourceFields, SearchSourceFields,
EsQuerySortValue, EsQuerySortValue,
SortDirection, SortDirection,
extractReferences as extractSearchSourceReferences,
injectReferences as injectSearchSourceReferences,
parseSearchSourceJSON,
} from './search_source'; } from './search_source';
export { SearchInterceptor } from './search_interceptor'; export { SearchInterceptor } from './search_interceptor';

View file

@ -21,12 +21,7 @@ import { Plugin, CoreSetup, CoreStart, PackageInfo } from '../../../../core/publ
import { ExpressionsSetup } from '../../../../plugins/expressions/public'; import { ExpressionsSetup } from '../../../../plugins/expressions/public';
import { SYNC_SEARCH_STRATEGY, syncSearchStrategyProvider } from './sync_search_strategy'; import { SYNC_SEARCH_STRATEGY, syncSearchStrategyProvider } from './sync_search_strategy';
import { import { createSearchSource, SearchSource, SearchSourceDependencies } from './search_source';
createSearchSourceFromJSON,
SearchSource,
SearchSourceDependencies,
SearchSourceFields,
} from './search_source';
import { ISearchSetup, ISearchStart, TSearchStrategyProvider, TSearchStrategiesMap } from './types'; import { ISearchSetup, ISearchStart, TSearchStrategyProvider, TSearchStrategiesMap } from './types';
import { TStrategyTypes } from './strategy_types'; import { TStrategyTypes } from './strategy_types';
import { getEsClient, LegacyApiCaller } from './legacy'; import { getEsClient, LegacyApiCaller } from './legacy';
@ -171,8 +166,10 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
}, },
search, search,
searchSource: { searchSource: {
create: (fields?: SearchSourceFields) => new SearchSource(fields, searchSourceDependencies), create: createSearchSource(dependencies.indexPatterns, searchSourceDependencies),
fromJSON: createSearchSourceFromJSON(dependencies.indexPatterns, searchSourceDependencies), createEmpty: () => {
return new SearchSource({}, searchSourceDependencies);
},
}, },
setInterceptor: (searchInterceptor: SearchInterceptor) => { setInterceptor: (searchInterceptor: SearchInterceptor) => {
// TODO: should an intercepror have a destroy method? // TODO: should an intercepror have a destroy method?

View file

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { createSearchSourceFromJSON } from './create_search_source'; import { createSearchSource as createSearchSourceFactory } from './create_search_source';
import { IIndexPattern } from '../../../common/index_patterns'; import { IIndexPattern } from '../../../common/index_patterns';
import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { IndexPatternsContract } from '../../index_patterns/index_patterns';
import { Filter } from '../../../common/es_query/filters'; import { Filter } from '../../../common/es_query/filters';
@ -27,7 +27,7 @@ describe('createSearchSource', () => {
const indexPatternMock: IIndexPattern = {} as IIndexPattern; const indexPatternMock: IIndexPattern = {} as IIndexPattern;
let indexPatternContractMock: jest.Mocked<IndexPatternsContract>; let indexPatternContractMock: jest.Mocked<IndexPatternsContract>;
let dependencies: any; let dependencies: any;
let createSearchSource: ReturnType<typeof createSearchSourceFromJSON>; let createSearchSource: ReturnType<typeof createSearchSourceFactory>;
beforeEach(() => { beforeEach(() => {
const core = coreMock.createStart(); const core = coreMock.createStart();
@ -43,27 +43,17 @@ describe('createSearchSource', () => {
get: jest.fn().mockReturnValue(Promise.resolve(indexPatternMock)), get: jest.fn().mockReturnValue(Promise.resolve(indexPatternMock)),
} as unknown) as jest.Mocked<IndexPatternsContract>; } as unknown) as jest.Mocked<IndexPatternsContract>;
createSearchSource = createSearchSourceFromJSON(indexPatternContractMock, dependencies); createSearchSource = createSearchSourceFactory(indexPatternContractMock, dependencies);
}); });
test('should fail if JSON is invalid', () => { it('should set fields', async () => {
expect(createSearchSource('{', [])).rejects.toThrow(); const searchSource = await createSearchSource({
expect(createSearchSource('0', [])).rejects.toThrow();
expect(createSearchSource('"abcdefg"', [])).rejects.toThrow();
});
test('should set fields', async () => {
const searchSource = await createSearchSource(
JSON.stringify({
highlightAll: true, highlightAll: true,
query: { query: {
query: '', query: '',
language: 'kuery', language: 'kuery',
}, },
}), });
[]
);
expect(searchSource.getOwnField('highlightAll')).toBe(true); expect(searchSource.getOwnField('highlightAll')).toBe(true);
expect(searchSource.getOwnField('query')).toEqual({ expect(searchSource.getOwnField('query')).toEqual({
query: '', query: '',
@ -71,27 +61,8 @@ describe('createSearchSource', () => {
}); });
}); });
test('should resolve referenced index pattern', async () => { it('should set filters and resolve referenced index patterns', async () => {
const searchSource = await createSearchSource( const searchSource = await createSearchSource({
JSON.stringify({
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
}),
[
{
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: [ filter: [
{ {
meta: { meta: {
@ -103,34 +74,19 @@ describe('createSearchSource', () => {
params: { params: {
query: "Men's Clothing", query: "Men's Clothing",
}, },
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', index: '123-456',
}, },
query: { query: {
match_phrase: { match_phrase: {
'category.keyword': "Men's Clothing", 'category.keyword': "Men's Clothing",
}, },
}, },
$state: {
store: 'appState',
},
}, },
], ],
}), });
[
{
id: '123-456',
type: 'index-pattern',
name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
},
]
);
const filters = searchSource.getOwnField('filter') as Filter[]; const filters = searchSource.getOwnField('filter') as Filter[];
expect(filters[0]).toMatchInlineSnapshot(` expect(filters[0]).toMatchInlineSnapshot(`
Object { Object {
"$state": Object {
"store": "appState",
},
"meta": Object { "meta": Object {
"alias": null, "alias": null,
"disabled": false, "disabled": false,
@ -151,15 +107,11 @@ describe('createSearchSource', () => {
`); `);
}); });
test('should migrate legacy queries on the fly', async () => { it('should migrate legacy queries on the fly', async () => {
const searchSource = await createSearchSource( const searchSource = await createSearchSource({
JSON.stringify({
highlightAll: true, highlightAll: true,
query: 'a:b', query: 'a:b' as any,
}), });
[]
);
expect(searchSource.getOwnField('query')).toEqual({ expect(searchSource.getOwnField('query')).toEqual({
query: 'a:b', query: 'a:b',
language: 'lucene', language: 'lucene',

View file

@ -16,11 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { transform, defaults, isFunction } from 'lodash';
import { SavedObjectReference } from 'kibana/public';
import { migrateLegacyQuery } from '../../../../kibana_legacy/public'; import { migrateLegacyQuery } from '../../../../kibana_legacy/public';
import { InvalidJSONProperty } from '../../../../kibana_utils/public'; import { SearchSource, SearchSourceDependencies } from './search_source';
import { SearchSourceDependencies, SearchSource, ISearchSource } from './search_source';
import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { IndexPatternsContract } from '../../index_patterns/index_patterns';
import { SearchSourceFields } from './types'; 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 * the start contract of the data plugin as part of the search service
* *
* @param indexPatterns The index patterns contract of the data plugin * @param indexPatterns The index patterns contract of the data plugin
* @param searchSourceDependencies
* *
* @return Wired utility function taking two parameters `searchSourceJson`, the json string * @return Wired utility function taking two parameters `searchSourceJson`, the json string
* returned by `serializeSearchSource` and `references`, a list of references including the ones * returned by `serializeSearchSource` and `references`, a list of references including the ones
@ -40,73 +38,20 @@ import { SearchSourceFields } from './types';
* *
* *
* @public */ * @public */
export const createSearchSourceFromJSON = ( export const createSearchSource = (
indexPatterns: IndexPatternsContract, indexPatterns: IndexPatternsContract,
searchSourceDependencies: SearchSourceDependencies searchSourceDependencies: SearchSourceDependencies
) => async ( ) => async (searchSourceFields: SearchSourceFields = {}) => {
searchSourceJson: string, const fields = { ...searchSourceFields };
references: SavedObjectReference[]
): Promise<ISearchSource> => {
const searchSource = new SearchSource({}, searchSourceDependencies);
// if we have a searchSource, set its values based on the searchSourceJson field // hydrating index pattern
let searchSourceValues: Record<string, unknown>; if (fields.index && typeof fields.index === 'string') {
try { fields.index = await indexPatterns.get(searchSourceFields.index as any);
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. const searchSource = new SearchSource(fields, searchSourceDependencies);
// (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 // todo: move to migration script .. create issue
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);
searchSource.setFields(newFields);
const query = searchSource.getOwnField('query'); const query = searchSource.getOwnField('query');
if (typeof query !== 'undefined') { if (typeof query !== 'undefined') {

View file

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

View file

@ -18,5 +18,8 @@
*/ */
export { SearchSource, ISearchSource, SearchSourceDependencies } from './search_source'; 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 { SortDirection, EsQuerySortValue, SearchSourceFields } from './types';
export { injectReferences } from './inject_references';
export { extractReferences } from './extract_references';
export { parseSearchSourceJSON } from './parse_json';

View file

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

View file

@ -43,12 +43,13 @@ export const searchSourceInstanceMock: MockedKeys<ISearchSource> = {
getSearchRequestBody: jest.fn(), getSearchRequestBody: jest.fn(),
destroy: jest.fn(), destroy: jest.fn(),
history: [], history: [],
getSerializedFields: jest.fn(),
serialize: jest.fn(), serialize: jest.fn(),
}; };
export const searchSourceMock = { export const searchSourceMock = {
create: jest.fn().mockReturnValue(searchSourceInstanceMock), create: jest.fn().mockReturnValue(searchSourceInstanceMock),
fromJSON: jest.fn().mockReturnValue(searchSourceInstanceMock), createEmpty: jest.fn().mockReturnValue(searchSourceInstanceMock),
}; };
export const createSearchSourceMock = (fields?: SearchSourceFields) => export const createSearchSourceMock = (fields?: SearchSourceFields) =>

View file

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

View file

@ -69,9 +69,9 @@
* `appSearchSource`. * `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 { map } from 'rxjs/operators';
import { CoreStart, SavedObjectReference } from 'kibana/public'; import { CoreStart } from 'kibana/public';
import { normalizeSortRequest } from './normalize_sort_request'; import { normalizeSortRequest } from './normalize_sort_request';
import { filterDocvalueFields } from './filter_docvalue_fields'; import { filterDocvalueFields } from './filter_docvalue_fields';
import { fieldWildcardFilter } from '../../../../kibana_utils/public'; import { fieldWildcardFilter } from '../../../../kibana_utils/public';
@ -82,6 +82,7 @@ import { FetchOptions, RequestFailure, getSearchParams, handleResponse } from '.
import { getEsQueryConfig, buildEsQuery, Filter } from '../../../common'; import { getEsQueryConfig, buildEsQuery, Filter } from '../../../common';
import { getHighlightRequest } from '../../../common/field_formats'; import { getHighlightRequest } from '../../../common/field_formats';
import { fetchSoon } from '../legacy'; import { fetchSoon } from '../legacy';
import { extractReferences } from './extract_references';
import { ISearchStartLegacy } from '../types'; import { ISearchStartLegacy } from '../types';
export interface SearchSourceDependencies { export interface SearchSourceDependencies {
@ -450,6 +451,25 @@ export class SearchSource {
return searchRequest; 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. * 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. * 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. * Using `createSearchSource`, the instance can be re-created.
* @public */ * @public */
public serialize() { public serialize() {
const references: SavedObjectReference[] = []; const [searchSourceFields, references] = extractReferences(this.getSerializedFields());
return { searchSourceJSON: JSON.stringify(searchSourceFields), references };
const {
filter: originalFilters,
...searchSourceFields
}: Omit<SearchSourceFields, 'sort' | 'size'> = omit(this.getFields(), ['sort', 'size']);
let serializedSearchSourceFields: Omit<SearchSourceFields, 'sort' | 'size' | 'filter'> & {
indexRefName?: string;
filter?: Array<Omit<Filter, 'meta'> & { 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 };
} }
private getFilters(filterField: SearchSourceFields['filter']): Filter[] { private getFilters(filterField: SearchSourceFields['filter']): Filter[] {

View file

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import { CoreStart, SavedObjectReference } from 'kibana/public'; import { CoreStart } from 'kibana/public';
import { SearchAggsSetup, SearchAggsStart } from './aggs'; import { SearchAggsSetup, SearchAggsStart } from './aggs';
import { ISearch, ISearchGeneric } from './i_search'; import { ISearch, ISearchGeneric } from './i_search';
import { TStrategyTypes } from './strategy_types'; import { TStrategyTypes } from './strategy_types';
@ -82,11 +82,8 @@ export interface ISearchStart {
setInterceptor: (searchInterceptor: SearchInterceptor) => void; setInterceptor: (searchInterceptor: SearchInterceptor) => void;
search: ISearchGeneric; search: ISearchGeneric;
searchSource: { searchSource: {
create: (fields?: SearchSourceFields) => ISearchSource; create: (fields?: SearchSourceFields) => Promise<ISearchSource>;
fromJSON: ( createEmpty: () => ISearchSource;
searchSourceJson: string,
references: SavedObjectReference[]
) => Promise<ISearchSource>;
}; };
__LEGACY: ISearchStartLegacy; __LEGACY: ISearchStartLegacy;
} }

View file

@ -113,8 +113,8 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract) {
async function createSearchSource(indexPattern: IndexPattern, filters: Filter[]) { async function createSearchSource(indexPattern: IndexPattern, filters: Filter[]) {
const { data } = getServices(); const { data } = getServices();
return data.search.searchSource const searchSource = await data.search.searchSource.create();
.create() return searchSource
.setParent(undefined) .setParent(undefined)
.setField('index', indexPattern) .setField('index', indexPattern)
.setField('filter', filters); .setField('filter', filters);

View file

@ -30,7 +30,7 @@ import { MarkdownSimple } from '../../../../../../kibana_react/public';
export function QueryActionsProvider(Promise) { export function QueryActionsProvider(Promise) {
const { filterManager, indexPatterns, data } = getServices(); 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 { fetchSurroundingDocs } = fetchContextProvider(indexPatterns);
const { setPredecessorCount, setQueryParameters, setSuccessorCount } = getQueryParameterActions( const { setPredecessorCount, setQueryParameters, setSuccessorCount } = getQueryParameterActions(
filterManager, filterManager,

View file

@ -1021,7 +1021,7 @@ function discoverController(
}, },
]; ];
$scope.vis = visualizations.createVis('histogram', { $scope.vis = await visualizations.createVis('histogram', {
title: savedSearch.title, title: savedSearch.title,
params: { params: {
addLegend: false, addLegend: false,
@ -1029,8 +1029,7 @@ function discoverController(
}, },
data: { data: {
aggs: visStateAggs, aggs: visStateAggs,
indexPattern: $scope.searchSource.getField('index').id, searchSource: $scope.searchSource.getSerializedFields(),
searchSource: $scope.searchSource,
}, },
}); });

View file

@ -33,9 +33,7 @@ const createSetupContract = (): Setup => {
const createStartContract = (): Start => { const createStartContract = (): Start => {
const startContract: Start = { const startContract: Start = {
savedSearches: { savedSearchLoader: {} as any,
createLoader: jest.fn(),
},
}; };
return startContract; return startContract;
}; };

View file

@ -39,7 +39,7 @@ import { KibanaLegacySetup, AngularRenderedAppUpdater } from 'src/plugins/kibana
import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public';
import { Start as InspectorPublicPluginStart } from 'src/plugins/inspector/public'; import { Start as InspectorPublicPluginStart } from 'src/plugins/inspector/public';
import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/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 { createKbnUrlTracker } from '../../kibana_utils/public';
import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types'; import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types';
@ -73,13 +73,7 @@ export interface DiscoverSetup {
} }
export interface DiscoverStart { export interface DiscoverStart {
savedSearches: { savedSearchLoader: SavedObjectLoader;
/**
* Create a {@link SavedObjectLoader | loader} to handle the saved searches type.
* @param services
*/
createLoader(services: SavedObjectKibanaServices): SavedObjectLoader;
};
} }
/** /**
@ -264,9 +258,13 @@ export class DiscoverPlugin
}; };
return { return {
savedSearches: { savedSearchLoader: createSavedSearchesLoader({
createLoader: createSavedSearchesLoader, savedObjectsClient: core.savedObjects.client,
}, indexPatterns: plugins.data.indexPatterns,
search: plugins.data.search,
chrome: core.chrome,
overlays: core.overlays,
}),
}; };
} }

View file

@ -25,7 +25,7 @@ import {
DataPublicPluginStart, DataPublicPluginStart,
} from 'src/plugins/data/public'; } from 'src/plugins/data/public';
export function createSearchSource( export async function createSearchSource(
{ create }: DataPublicPluginStart['search']['searchSource'], { create }: DataPublicPluginStart['search']['searchSource'],
initialState: SearchSourceFields | null, initialState: SearchSourceFields | null,
indexPattern: IndexPattern, indexPattern: IndexPattern,
@ -34,7 +34,7 @@ export function createSearchSource(
filters: PhraseFilter[] = [], filters: PhraseFilter[] = [],
timefilter: TimefilterContract timefilter: TimefilterContract
) { ) {
const searchSource = create(initialState || {}); const searchSource = await create(initialState || {});
// Do not not inherit from rootSearchSource to avoid picking up time and globals // Do not not inherit from rootSearchSource to avoid picking up time and globals
searchSource.setParent(undefined); searchSource.setParent(undefined);

View file

@ -147,7 +147,7 @@ export class ListControl extends Control<PhraseFilterManager> {
direction: 'desc', direction: 'desc',
query, query,
}); });
const searchSource = createSearchSource( const searchSource = await createSearchSource(
this.searchSource, this.searchSource,
initialSearchSourceState, initialSearchSourceState,
indexPattern, indexPattern,

View file

@ -84,7 +84,7 @@ export class RangeControl extends Control<RangeFilterManager> {
const fieldName = this.filterManager.fieldName; const fieldName = this.filterManager.fieldName;
const aggs = minMaxAgg(indexPattern.fields.getByName(fieldName)); const aggs = minMaxAgg(indexPattern.fields.getByName(fieldName));
const searchSource = createSearchSource( const searchSource = await createSearchSource(
this.searchSource, this.searchSource,
null, null,
indexPattern, indexPattern,

View file

@ -19,7 +19,11 @@
import _ from 'lodash'; import _ from 'lodash';
import { EsResponse, SavedObject, SavedObjectConfig, SavedObjectKibanaServices } from '../../types'; import { EsResponse, SavedObject, SavedObjectConfig, SavedObjectKibanaServices } from '../../types';
import { expandShorthand, SavedObjectNotFound } from '../../../../kibana_utils/public'; 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 * 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); _.assign(savedObject, savedObject._source);
savedObject.lastSavedTitle = savedObject.title; savedObject.lastSavedTitle = savedObject.title;
if (config.searchSource) { if (meta.searchSourceJSON) {
try { try {
savedObject.searchSource = await dependencies.search.searchSource.fromJSON( let searchSourceValues = parseSearchSourceJSON(meta.searchSourceJSON);
meta.searchSourceJSON,
if (config.searchSource) {
searchSourceValues = injectSearchSourceReferences(
searchSourceValues as any,
resp.references resp.references
); );
savedObject.searchSource = await dependencies.search.searchSource.create(
searchSourceValues
);
} else {
savedObject.searchSourceFields = searchSourceValues;
}
} catch (error) { } catch (error) {
if ( if (
error.constructor.name === 'SavedObjectNotFound' && error.constructor.name === 'SavedObjectNotFound' &&

View file

@ -55,7 +55,7 @@ export function buildSavedObject(
savedObject.defaults = config.defaults || {}; savedObject.defaults = config.defaults || {};
// optional search source which this object configures // optional search source which this object configures
savedObject.searchSource = config.searchSource savedObject.searchSource = config.searchSource
? services.search.searchSource.create() ? services.search.searchSource.createEmpty()
: undefined; : undefined;
// the id of the document // the id of the document
savedObject.id = config.id || void 0; savedObject.id = config.id || void 0;

View file

@ -19,6 +19,7 @@
import _ from 'lodash'; import _ from 'lodash';
import { SavedObject, SavedObjectConfig } from '../../types'; import { SavedObject, SavedObjectConfig } from '../../types';
import { expandShorthand } from '../../../../kibana_utils/public'; import { expandShorthand } from '../../../../kibana_utils/public';
import { extractSearchSourceReferences } from '../../../../data/public';
export function serializeSavedObject(savedObject: SavedObject, config: SavedObjectConfig) { export function serializeSavedObject(savedObject: SavedObject, config: SavedObjectConfig) {
// mapping definition for the fields that this object will expose // mapping definition for the fields that this object will expose
@ -48,6 +49,15 @@ export function serializeSavedObject(savedObject: SavedObject, config: SavedObje
references.push(...searchSourceReferences); 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) { if (savedObject.unresolvedIndexPatternReference) {
references.push(savedObject.unresolvedIndexPatternReference); references.push(savedObject.unresolvedIndexPatternReference);
} }

View file

@ -111,6 +111,7 @@ describe('Saved Object', () => {
searchSource: { searchSource: {
...dataStartMock.search.searchSource, ...dataStartMock.search.searchSource,
create: createSearchSourceMock, create: createSearchSourceMock,
createEmpty: createSearchSourceMock,
}, },
}, },
} as unknown) as SavedObjectKibanaServices); } as unknown) as SavedObjectKibanaServices);
@ -572,9 +573,15 @@ describe('Saved Object', () => {
}); });
it('passes references to search source parsing function', async () => { 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 }); const savedObject = new SavedObjectClass({ type: 'dashboard', searchSource: true });
await savedObject.init!(); return savedObject.init!().then(async () => {
const searchSourceJSON = JSON.stringify({ const searchSourceJSON = JSON.stringify({
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
filter: [ filter: [
@ -605,13 +612,11 @@ describe('Saved Object', () => {
}, },
], ],
}; };
const result = await savedObject.applyESResp(response); await savedObject.applyESResp(response);
expect(dataStartMock.search.searchSource.create).toBeCalledWith({
expect(result._source).toEqual({ filter: [{ meta: { index: 'my-index-2' } }],
kibanaSavedObjectMeta: { index: 'my-index-1',
searchSourceJSON: });
'{"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index","filter":[{"meta":{"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index"}}]}',
},
}); });
}); });
}); });

View file

@ -29,6 +29,7 @@ import {
IIndexPattern, IIndexPattern,
IndexPatternsContract, IndexPatternsContract,
ISearchSource, ISearchSource,
SearchSourceFields,
} from '../../data/public'; } from '../../data/public';
export interface SavedObject { export interface SavedObject {
@ -52,6 +53,7 @@ export interface SavedObject {
migrationVersion?: Record<string, any>; migrationVersion?: Record<string, any>;
save: (saveOptions: SavedObjectSaveOpts) => Promise<string>; save: (saveOptions: SavedObjectSaveOpts) => Promise<string>;
searchSource?: ISearchSource; searchSource?: ISearchSource;
searchSourceFields?: SearchSourceFields;
showInRecentlyAccessed: boolean; showInRecentlyAccessed: boolean;
title: string; title: string;
unresolvedIndexPatternReference?: SavedObjectReference; unresolvedIndexPatternReference?: SavedObjectReference;

View file

@ -21,7 +21,12 @@ import { i18n } from '@kbn/i18n';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { OverlayStart, SavedObjectReference } from 'src/core/public'; import { OverlayStart, SavedObjectReference } from 'src/core/public';
import { SavedObject, SavedObjectLoader } from '../../../saved_objects/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<string, any>; type SavedObjectsRawDoc = Record<string, any>;
@ -207,13 +212,17 @@ export async function resolveIndexPatternConflicts(
return reference; return reference;
}); });
const serializedSearchSourceWithInjectedReferences = injectSearchSourceReferences(
serializedSearchSource,
replacedReferences
);
if (!allResolved) { if (!allResolved) {
// The user decided to skip this conflict so do nothing // The user decided to skip this conflict so do nothing
return; return;
} }
obj.searchSource = await dependencies.search.searchSource.fromJSON( obj.searchSource = await dependencies.search.searchSource.create(
JSON.stringify(serializedSearchSource), serializedSearchSourceWithInjectedReferences
replacedReferences
); );
if (await saveObject(obj, overwriteAll)) { if (await saveObject(obj, overwriteAll)) {
importCount++; importCount++;

View file

@ -280,7 +280,7 @@ exports[`SavedObjectsTable import should show the flyout 1`] = `
"search": [MockFunction], "search": [MockFunction],
"searchSource": Object { "searchSource": Object {
"create": [MockFunction], "create": [MockFunction],
"fromJSON": [MockFunction], "createEmpty": [MockFunction],
}, },
"setInterceptor": [MockFunction], "setInterceptor": [MockFunction],
} }

View file

@ -25,7 +25,7 @@ export const registerServices = async (
registry: ISavedObjectsManagementServiceRegistry, registry: ISavedObjectsManagementServiceRegistry,
getStartServices: StartServicesAccessor<StartDependencies, SavedObjectsManagementPluginStart> getStartServices: StartServicesAccessor<StartDependencies, SavedObjectsManagementPluginStart>
) => { ) => {
const [coreStart, { dashboard, data, visualizations, discover }] = await getStartServices(); const [, { dashboard, visualizations, discover }] = await getStartServices();
if (dashboard) { if (dashboard) {
registry.register({ registry.register({
@ -47,13 +47,7 @@ export const registerServices = async (
registry.register({ registry.register({
id: 'savedSearches', id: 'savedSearches',
title: 'searches', title: 'searches',
service: discover.savedSearches.createLoader({ service: discover.savedSearchLoader,
savedObjectsClient: coreStart.savedObjects.client,
indexPatterns: data.indexPatterns,
search: data.search,
chrome: coreStart.chrome,
overlays: coreStart.overlays,
}),
}); });
} }
}; };

View file

@ -122,7 +122,9 @@ export class VisualizeEmbeddableFactory
try { try {
const savedObject = await savedVisualizations.get(savedObjectId); 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); return createVisEmbeddableFromObject(this.deps)(vis, input, parent);
} catch (e) { } catch (e) {
console.error(e); // eslint-disable-line no-console console.error(e); // eslint-disable-line no-console

View file

@ -398,6 +398,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
return aggs; return aggs;
}, },
} as any, } as any,
searchSource: {} as any,
}, },
isHierarchical: () => { isHierarchical: () => {
return false; return false;
@ -473,6 +474,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
return aggs; return aggs;
}, },
} as any, } as any,
searchSource: {} as any,
}, },
isHierarchical: () => { isHierarchical: () => {
return false; return false;

View file

@ -43,6 +43,7 @@ import {
setAggs, setAggs,
setChrome, setChrome,
setOverlays, setOverlays,
setSavedSearchLoader,
} from './services'; } from './services';
import { import {
VISUALIZE_EMBEDDABLE_TYPE, VISUALIZE_EMBEDDABLE_TYPE,
@ -70,6 +71,7 @@ import {
convertFromSerializedVis, convertFromSerializedVis,
convertToSerializedVis, convertToSerializedVis,
} from './saved_visualizations/_saved_vis'; } from './saved_visualizations/_saved_vis';
import { createSavedSearchesLoader } from '../../discover/public';
/** /**
* Interface for this plugin's returned setup/start contracts. * Interface for this plugin's returned setup/start contracts.
@ -81,7 +83,7 @@ export type VisualizationsSetup = TypesSetup;
export interface VisualizationsStart extends TypesStart { export interface VisualizationsStart extends TypesStart {
savedVisualizationsLoader: SavedVisualizationsLoader; savedVisualizationsLoader: SavedVisualizationsLoader;
createVis: (visType: string, visState?: SerializedVis) => Vis; createVis: (visType: string, visState: SerializedVis) => Promise<Vis>;
convertToSerializedVis: typeof convertToSerializedVis; convertToSerializedVis: typeof convertToSerializedVis;
convertFromSerializedVis: typeof convertFromSerializedVis; convertFromSerializedVis: typeof convertFromSerializedVis;
showNewVisModal: typeof showNewVisModal; showNewVisModal: typeof showNewVisModal;
@ -174,7 +176,14 @@ export class VisualizationsPlugin
visualizationTypes: types, visualizationTypes: types,
}); });
setSavedVisualizationsLoader(savedVisualizationsLoader); setSavedVisualizationsLoader(savedVisualizationsLoader);
const savedSearchLoader = createSavedSearchesLoader({
savedObjectsClient: core.savedObjects.client,
indexPatterns: data.indexPatterns,
search: data.search,
chrome: core.chrome,
overlays: core.overlays,
});
setSavedSearchLoader(savedSearchLoader);
return { return {
...types, ...types,
showNewVisModal, showNewVisModal,
@ -183,7 +192,11 @@ export class VisualizationsPlugin
* @param {IIndexPattern} indexPattern - index pattern to use * @param {IIndexPattern} indexPattern - index pattern to use
* @param {VisState} visState - visualization configuration * @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, convertToSerializedVis,
convertFromSerializedVis, convertFromSerializedVis,
savedVisualizationsLoader, savedVisualizationsLoader,

View file

@ -32,32 +32,25 @@ import {
// @ts-ignore // @ts-ignore
import { updateOldState } from '../legacy/vis_update_state'; import { updateOldState } from '../legacy/vis_update_state';
import { extractReferences, injectReferences } from './saved_visualization_references'; 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 { ISavedVis, SerializedVis } from '../types';
import { createSavedSearchesLoader } from '../../../../plugins/discover/public'; import { createSavedSearchesLoader } from '../../../discover/public';
import { getChrome, getOverlays, getIndexPatterns, getSavedObjects, getSearch } from '../services';
export const convertToSerializedVis = async (savedVis: ISavedVis): Promise<SerializedVis> => { export const convertToSerializedVis = (savedVis: ISavedVis): SerializedVis => {
const { visState } = savedVis; const { id, title, description, visState, uiStateJSON, searchSourceFields } = savedVis;
const searchSource =
savedVis.searchSource && (await getSearchSource(savedVis.searchSource, savedVis.savedSearchId));
const indexPattern = const aggs = searchSourceFields && searchSourceFields.index ? visState.aggs || [] : visState.aggs;
searchSource && searchSource.getField('index') ? searchSource.getField('index')!.id : undefined;
const aggs = indexPattern ? visState.aggs || [] : visState.aggs;
return { return {
id: savedVis.id, id,
title: savedVis.title, title,
type: visState.type, type: visState.type,
description: savedVis.description, description,
params: visState.params, params: visState.params,
uiState: JSON.parse(savedVis.uiStateJSON || '{}'), uiState: JSON.parse(uiStateJSON || '{}'),
data: { data: {
indexPattern,
aggs, aggs,
searchSource, searchSource: searchSourceFields!,
savedSearchId: savedVis.savedSearchId, savedSearchId: savedVis.savedSearchId,
}, },
}; };
@ -74,36 +67,14 @@ export const convertFromSerializedVis = (vis: SerializedVis): ISavedVis => {
params: vis.params, params: vis.params,
}, },
uiStateJSON: JSON.stringify(vis.uiState), uiStateJSON: JSON.stringify(vis.uiState),
searchSource: vis.data.searchSource!, searchSourceFields: vis.data.searchSource,
savedSearchId: vis.data.savedSearchId, 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) { export function createSavedVisClass(services: SavedObjectKibanaServices) {
const SavedObjectClass = createSavedObjectClass(services); const SavedObjectClass = createSavedObjectClass(services);
const savedSearch = createSavedSearchesLoader(services);
class SavedVis extends SavedObjectClass { class SavedVis extends SavedObjectClass {
public static type: string = 'visualization'; public static type: string = 'visualization';
@ -117,7 +88,6 @@ export function createSavedVisClass(services: SavedObjectKibanaServices) {
}; };
// Order these fields to the top, the rest are alphabetical // Order these fields to the top, the rest are alphabetical
public static fieldOrder = ['title', 'description']; public static fieldOrder = ['title', 'description'];
public static searchSource = true;
constructor(opts: Record<string, unknown> | string = {}) { constructor(opts: Record<string, unknown> | string = {}) {
if (typeof opts !== 'object') { if (typeof opts !== 'object') {
@ -128,7 +98,6 @@ export function createSavedVisClass(services: SavedObjectKibanaServices) {
super({ super({
type: SavedVis.type, type: SavedVis.type,
mapping: SavedVis.mapping, mapping: SavedVis.mapping,
searchSource: SavedVis.searchSource,
extractReferences, extractReferences,
injectReferences, injectReferences,
id: (opts.id as string) || '', id: (opts.id as string) || '',
@ -144,11 +113,11 @@ export function createSavedVisClass(services: SavedObjectKibanaServices) {
afterESResp: async (savedObject: SavedObject) => { afterESResp: async (savedObject: SavedObject) => {
const savedVis = (savedObject as any) as ISavedVis; const savedVis = (savedObject as any) as ISavedVis;
savedVis.visState = await updateOldState(savedVis.visState); savedVis.visState = await updateOldState(savedVis.visState);
if (savedVis.savedSearchId && savedVis.searchSource) { if (savedVis.searchSourceFields?.index) {
savedObject.searchSource = await getSearchSource( await services.indexPatterns.get(savedVis.searchSourceFields.index as any);
savedVis.searchSource, }
savedVis.savedSearchId if (savedVis.savedSearchId) {
); await savedSearch.get(savedVis.savedSearchId);
} }
return (savedVis as any) as SavedObject; return (savedVis as any) as SavedObject;
}, },

View file

@ -18,6 +18,7 @@
*/ */
import { SavedObjectAttributes, SavedObjectReference } from '../../../../core/public'; import { SavedObjectAttributes, SavedObjectReference } from '../../../../core/public';
import { VisSavedObject } from '../types'; import { VisSavedObject } from '../types';
import { injectSearchSourceReferences, extractSearchSourceReferences } from '../../../data/public';
export function extractReferences({ export function extractReferences({
attributes, attributes,
@ -29,6 +30,14 @@ export function extractReferences({
const updatedAttributes = { ...attributes }; const updatedAttributes = { ...attributes };
const updatedReferences = [...references]; 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 // Extract saved search
if (updatedAttributes.savedSearchId) { if (updatedAttributes.savedSearchId) {
updatedReferences.push({ updatedReferences.push({
@ -66,6 +75,12 @@ export function extractReferences({
} }
export function injectReferences(savedObject: VisSavedObject, references: SavedObjectReference[]) { export function injectReferences(savedObject: VisSavedObject, references: SavedObjectReference[]) {
if (savedObject.searchSourceFields) {
savedObject.searchSourceFields = injectSearchSourceReferences(
savedObject.searchSourceFields as any,
references
);
}
if (savedObject.savedSearchRefName) { if (savedObject.savedSearchRefName) {
const savedSearchReference = references.find( const savedSearchReference = references.find(
reference => reference.name === savedObject.savedSearchRefName reference => reference.name === savedObject.savedSearchRefName

View file

@ -38,6 +38,7 @@ import { UsageCollectionSetup } from '../../../plugins/usage_collection/public';
import { ExpressionsStart } from '../../../plugins/expressions/public'; import { ExpressionsStart } from '../../../plugins/expressions/public';
import { UiActionsStart } from '../../../plugins/ui_actions/public'; import { UiActionsStart } from '../../../plugins/ui_actions/public';
import { SavedVisualizationsLoader } from './saved_visualizations'; import { SavedVisualizationsLoader } from './saved_visualizations';
import { SavedObjectLoader } from '../../saved_objects/public';
export const [getUISettings, setUISettings] = createGetterSetter<IUiSettingsClient>('UISettings'); export const [getUISettings, setUISettings] = createGetterSetter<IUiSettingsClient>('UISettings');
@ -84,3 +85,7 @@ export const [getAggs, setAggs] = createGetterSetter<DataPublicPluginStart['sear
export const [getOverlays, setOverlays] = createGetterSetter<OverlayStart>('Overlays'); export const [getOverlays, setOverlays] = createGetterSetter<OverlayStart>('Overlays');
export const [getChrome, setChrome] = createGetterSetter<ChromeStart>('Chrome'); export const [getChrome, setChrome] = createGetterSetter<ChromeStart>('Chrome');
export const [getSavedSearchLoader, setSavedSearchLoader] = createGetterSetter<SavedObjectLoader>(
'savedSearchLoader'
);

View file

@ -18,7 +18,7 @@
*/ */
import { SavedObject } from '../../../plugins/saved_objects/public'; 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'; import { SerializedVis, Vis, VisParams } from './vis';
export { Vis, SerializedVis, VisParams }; export { Vis, SerializedVis, VisParams };
@ -45,7 +45,7 @@ export interface ISavedVis {
title: string; title: string;
description?: string; description?: string;
visState: SavedVisState; visState: SavedVisState;
searchSource?: ISearchSource; searchSourceFields?: SearchSourceFields;
uiStateJSON?: string; uiStateJSON?: string;
savedSearchRefName?: string; savedSearchRefName?: string;
savedSearchId?: string; savedSearchId?: string;

View file

@ -18,8 +18,6 @@
*/ */
import { Vis } from './vis'; import { Vis } from './vis';
// @ts-ignore
import fixturesStubbedLogstashIndexPatternProvider from '../../../fixtures/stubbed_logstash_index_pattern';
jest.mock('./services', () => { jest.mock('./services', () => {
class MockVisualizationController { class MockVisualizationController {
@ -36,7 +34,10 @@ jest.mock('./services', () => {
// eslint-disable-next-line // eslint-disable-next-line
const { BaseVisType } = require('./vis_types/base_vis_type'); 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({ const visType = new BaseVisType({
name: 'pie', name: 'pie',
title: 'pie', title: 'pie',
@ -51,6 +52,13 @@ jest.mock('./services', () => {
aggs: cfg.map((aggConfig: any) => ({ ...aggConfig, toJSON: () => aggConfig })), 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' } }, { type: 'terms' as any, schema: 'segment', params: { field: 'geo.src' } },
], ],
searchSource: { searchSource: {
getField: (name: string) => { index: '123',
if (name === 'index') {
return fixturesStubbedLogstashIndexPatternProvider();
}
},
createCopy: jest.fn(),
}, },
}, },
params: { isDonut: true }, params: { isDonut: true },
}; };
beforeEach(function() { beforeEach(async function() {
vis = new Vis('test', stateFixture as any); vis = new Vis('test', stateFixture as any);
await vis.setState(stateFixture as any);
}); });
const verifyVis = function(visToVerify: Vis) { const verifyVis = function(visToVerify: Vis) {

View file

@ -28,21 +28,22 @@
*/ */
import { isFunction, defaults, cloneDeep } from 'lodash'; import { isFunction, defaults, cloneDeep } from 'lodash';
import { Assign } from '@kbn/utility-types';
import { PersistedState } from './persisted_state'; import { PersistedState } from './persisted_state';
import { getTypes, getAggs } from './services'; import { getTypes, getAggs, getSearch, getSavedSearchLoader } from './services';
import { VisType } from './vis_types'; import { VisType } from './vis_types';
import { import {
IAggConfigs, IAggConfigs,
IndexPattern, IndexPattern,
ISearchSource, ISearchSource,
AggConfigOptions, AggConfigOptions,
SearchSourceFields,
} from '../../../plugins/data/public'; } from '../../../plugins/data/public';
export interface SerializedVisData { export interface SerializedVisData {
expression?: string; expression?: string;
aggs: AggConfigOptions[]; aggs: AggConfigOptions[];
indexPattern?: string; searchSource: SearchSourceFields;
searchSource?: ISearchSource;
savedSearchId?: string; savedSearchId?: string;
} }
@ -68,6 +69,19 @@ export interface VisParams {
[key: string]: any; [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<SerializedVis, { data: Partial<SerializedVisData> }>;
export class Vis { export class Vis {
public readonly type: VisType; public readonly type: VisType;
public readonly id?: string; public readonly id?: string;
@ -86,8 +100,6 @@ export class Vis {
this.params = this.getParams(visState.params); this.params = this.getParams(visState.params);
this.uiState = new PersistedState(visState.uiState); this.uiState = new PersistedState(visState.uiState);
this.id = visState.id; this.id = visState.id;
this.setState(visState || {});
} }
private getType(visType: string) { private getType(visType: string) {
@ -102,7 +114,7 @@ export class Vis {
return defaults({}, cloneDeep(params || {}), cloneDeep(this.type.visConfig.defaults || {})); return defaults({}, cloneDeep(params || {}), cloneDeep(this.type.visConfig.defaults || {}));
} }
setState(state: SerializedVis) { async setState(state: PartialVisState) {
let typeChanged = false; let typeChanged = false;
if (state.type && this.type.name !== state.type) { if (state.type && this.type.name !== state.type) {
// @ts-ignore // @ts-ignore
@ -120,19 +132,24 @@ export class Vis {
} }
if (state.data && state.data.searchSource) { 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'); this.data.indexPattern = this.data.searchSource.getField('index');
} }
if (state.data && state.data.savedSearchId) { if (state.data && state.data.savedSearchId) {
this.data.savedSearchId = state.data.savedSearchId; this.data.savedSearchId = state.data.savedSearchId;
} if (this.data.searchSource) {
if (state.data && state.data.aggs) { this.data.searchSource = await getSearchSource(
const configStates = this.initializeDefaultsFromSchemas( this.data.searchSource,
cloneDeep(state.data.aggs), this.data.savedSearchId
this.type.schemas.all || []
); );
this.data.indexPattern = this.data.searchSource.getField('index');
}
}
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 (!this.data.indexPattern) {
if (state.data.aggs.length) { if (aggs.length) {
throw new Error('trying to initialize aggs without index pattern'); throw new Error('trying to initialize aggs without index pattern');
} }
return; return;
@ -142,22 +159,31 @@ export class Vis {
} }
clone() { 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 { serialize(): SerializedVis {
const aggs = this.data.aggs ? this.data.aggs.aggs.map(agg => agg.toJSON()) : []; const aggs = this.data.aggs ? this.data.aggs.aggs.map(agg => agg.toJSON()) : [];
const indexPattern = this.data.searchSource && this.data.searchSource.getField('index');
return { return {
id: this.id, id: this.id,
title: this.title, title: this.title,
description: this.description,
type: this.type.name, type: this.type.name,
params: cloneDeep(this.params) as any, params: cloneDeep(this.params) as any,
uiState: this.uiState.toJSON(), uiState: this.uiState.toJSON(),
data: { data: {
aggs: aggs as any, aggs: aggs as any,
indexPattern: indexPattern ? indexPattern.id : undefined, searchSource: this.data.searchSource ? this.data.searchSource.getSerializedFields() : {},
searchSource: this.data.searchSource!.createCopy(),
savedSearchId: this.data.savedSearchId, savedSearchId: this.data.savedSearchId,
}, },
}; };

View file

@ -645,8 +645,7 @@ function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlState
title: savedVis.title, title: savedVis.title,
type: savedVis.type || stateContainer.getState().vis.type, type: savedVis.type || stateContainer.getState().vis.type,
}); });
savedVis.searchSource.setField('query', stateContainer.getState().query); savedVis.searchSourceFields = searchSource.getSerializedFields();
savedVis.searchSource.setField('filter', stateContainer.getState().filters);
savedVis.visState = stateContainer.getState().vis; savedVis.visState = stateContainer.getState().vis;
savedVis.uiStateJSON = angular.toJson($scope.uiState.toJSON()); savedVis.uiStateJSON = angular.toJson($scope.uiState.toJSON());
$appStatus.dirty = false; $appStatus.dirty = false;

View file

@ -45,9 +45,9 @@ const getResolvedResults = deps => {
return savedVis => { return savedVis => {
results.savedVis = savedVis; results.savedVis = savedVis;
const serializedVis = visualizations.convertToSerializedVis(savedVis);
return visualizations return visualizations
.convertToSerializedVis(savedVis) .createVis(serializedVis.type, serializedVis)
.then(serializedVis => visualizations.createVis(serializedVis.type, serializedVis))
.then(vis => { .then(vis => {
if (vis.type.setup) { if (vis.type.setup) {
return vis.type.setup(vis).catch(() => vis); return vis.type.setup(vis).catch(() => vis);
@ -171,6 +171,10 @@ export function initVisualizeApp(app, deps) {
return data.indexPatterns return data.indexPatterns
.ensureDefaultIndexPattern(history) .ensureDefaultIndexPattern(history)
.then(() => savedVisualizations.get($route.current.params)) .then(() => savedVisualizations.get($route.current.params))
.then(savedVis => {
savedVis.searchSourceFields = { index: $route.current.params.indexPattern };
return savedVis;
})
.then(getResolvedResults(deps)) .then(getResolvedResults(deps))
.then(delay) .then(delay)
.catch( .catch(

View file

@ -399,7 +399,7 @@ export class ESSearchSource extends AbstractESSource {
} }
const searchService = getSearchService(); const searchService = getSearchService();
const searchSource = searchService.searchSource.create(); const searchSource = searchService.searchSource.createEmpty();
searchSource.setField('index', indexPattern); searchSource.setField('index', indexPattern);
searchSource.setField('size', 1); searchSource.setField('size', 1);

View file

@ -125,7 +125,7 @@ export class AbstractESSource extends AbstractVectorSource {
allFilters.push(getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters)); allFilters.push(getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters));
} }
const searchService = getSearchService(); const searchService = getSearchService();
const searchSource = searchService.searchSource.create(initialSearchContext); const searchSource = await searchService.searchSource.create(initialSearchContext);
searchSource.setField('index', indexPattern); searchSource.setField('index', indexPattern);
searchSource.setField('size', limit); searchSource.setField('size', limit);
@ -135,7 +135,7 @@ export class AbstractESSource extends AbstractVectorSource {
} }
if (searchFilters.sourceQuery) { if (searchFilters.sourceQuery) {
const layerSearchSource = searchService.searchSource.create(); const layerSearchSource = searchService.searchSource.createEmpty();
layerSearchSource.setField('index', indexPattern); layerSearchSource.setField('index', indexPattern);
layerSearchSource.setField('query', searchFilters.sourceQuery); layerSearchSource.setField('query', searchFilters.sourceQuery);
@ -296,7 +296,7 @@ export class AbstractESSource extends AbstractVectorSource {
const indexPattern = await this.getIndexPattern(); const indexPattern = await this.getIndexPattern();
const searchService = getSearchService(); const searchService = getSearchService();
const searchSource = searchService.searchSource.create(); const searchSource = searchService.searchSource.createEmpty();
searchSource.setField('index', indexPattern); searchSource.setField('index', indexPattern);
searchSource.setField('size', 0); searchSource.setField('size', 0);