diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.createsearchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.createsearchsource.md
new file mode 100644
index 0000000000000..5c5aa348eecdf
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.createsearchsource.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [createSearchSource](./kibana-plugin-plugins-data-public.createsearchsource.md)
+
+## createSearchSource variable
+
+Deserializes a json string and a set of referenced objects to a `SearchSource` instance. Use this method to re-create the search source serialized using `searchSource.serialize`.
+
+This function is a factory function that returns the actual utility when calling it with the required service dependency (index patterns contract). A pre-wired version is also exposed in the start contract of the data plugin as part of the search service
+
+Signature:
+
+```typescript
+createSearchSource: (indexPatterns: Pick) => (searchSourceJson: string, references: SavedObjectReference[]) => Promise
+```
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 6964c070097c5..fc0dab94a0f65 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
@@ -102,6 +102,7 @@
| [castEsToKbnFieldTypeName](./kibana-plugin-plugins-data-public.castestokbnfieldtypename.md) | Get the KbnFieldType name for an esType string |
| [connectToQueryState](./kibana-plugin-plugins-data-public.connecttoquerystate.md) | Helper to setup two-way syncing of global data and a state container |
| [createSavedQueryService](./kibana-plugin-plugins-data-public.createsavedqueryservice.md) | |
+| [createSearchSource](./kibana-plugin-plugins-data-public.createsearchsource.md) | Deserializes a json string and a set of referenced objects to a SearchSource
instance. Use this method to re-create the search source serialized using searchSource.serialize
.This function is a factory function that returns the actual utility when calling it with the required service dependency (index patterns contract). A pre-wired version is also exposed in the start contract of the data plugin as part of the search service |
| [ES\_SEARCH\_STRATEGY](./kibana-plugin-plugins-data-public.es_search_strategy.md) | |
| [esFilters](./kibana-plugin-plugins-data-public.esfilters.md) | |
| [esKuery](./kibana-plugin-plugins-data-public.eskuery.md) | |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md
index 8e1dbb6e2671d..5f2fc809a5590 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md
@@ -38,6 +38,7 @@ export declare class SearchSource
| [getParent()](./kibana-plugin-plugins-data-public.searchsource.getparent.md) | | Get the parent of this SearchSource {undefined\|searchSource} |
| [getSearchRequestBody()](./kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md) | | |
| [onRequestStart(handler)](./kibana-plugin-plugins-data-public.searchsource.onrequeststart.md) | | Add a handler that will be notified whenever requests start |
+| [serialize()](./kibana-plugin-plugins-data-public.searchsource.serialize.md) | | 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.The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named kibanaSavedObjectMeta.searchSourceJSON.index
and kibanaSavedObjectMeta.searchSourceJSON.filter[<number>].meta.index
.Using createSearchSource
, the instance can be re-created. |
| [setField(field, value)](./kibana-plugin-plugins-data-public.searchsource.setfield.md) | | |
| [setFields(newFields)](./kibana-plugin-plugins-data-public.searchsource.setfields.md) | | |
| [setParent(parent, options)](./kibana-plugin-plugins-data-public.searchsource.setparent.md) | | Set a searchSource that this source should inherit from |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md
new file mode 100644
index 0000000000000..52d25dec01dfd
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [serialize](./kibana-plugin-plugins-data-public.searchsource.serialize.md)
+
+## SearchSource.serialize() method
+
+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.
+
+The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named `kibanaSavedObjectMeta.searchSourceJSON.index` and `kibanaSavedObjectMeta.searchSourceJSON.filter[].meta.index`.
+
+Using `createSearchSource`, the instance can be re-created.
+
+Signature:
+
+```typescript
+serialize(): {
+ searchSourceJSON: string;
+ references: SavedObjectReference[];
+ };
+```
+Returns:
+
+`{
+ searchSourceJSON: string;
+ references: SavedObjectReference[];
+ }`
+
diff --git a/src/legacy/core_plugins/kibana/public/discover/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/build_services.ts
index 180ff13cdddc0..a3a99a0ded523 100644
--- a/src/legacy/core_plugins/kibana/public/discover/build_services.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/build_services.ts
@@ -72,6 +72,7 @@ export async function buildServices(
const services = {
savedObjectsClient: core.savedObjects.client,
indexPatterns: plugins.data.indexPatterns,
+ search: plugins.data.search,
chrome: core.chrome,
overlays: core.overlays,
};
diff --git a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts
index 7261b2ba03372..705be68a141e7 100644
--- a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts
+++ b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts
@@ -56,6 +56,7 @@ export const savedObjectManagementRegistry: ISavedObjectsManagementRegistry = {
const services = {
savedObjectsClient: npStart.core.savedObjects.client,
indexPatterns: npStart.plugins.data.indexPatterns,
+ search: npStart.plugins.data.search,
chrome: npStart.core.chrome,
overlays: npStart.core.overlays,
};
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js
index 5d14c4609b918..0d16e0ae35dd6 100644
--- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js
+++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js
@@ -519,7 +519,8 @@ describe('Flyout', () => {
expect(resolveIndexPatternConflicts).toHaveBeenCalledWith(
component.instance().resolutions,
mockConflictedIndexPatterns,
- true
+ true,
+ defaultProps.indexPatterns
);
expect(saveObjects).toHaveBeenCalledWith(
mockConflictedSavedObjectsLinkedToSavedSearches,
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js
index 105c279218375..da2221bb54203 100644
--- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js
+++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/flyout.js
@@ -358,7 +358,8 @@ export class Flyout extends Component {
importCount += await resolveIndexPatternConflicts(
resolutions,
conflictedIndexPatterns,
- isOverwriteAllChecked
+ isOverwriteAllChecked,
+ this.props.indexPatterns
);
}
this.setState({
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts
index 8243aa69ac082..dc6d2643145ff 100644
--- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts
+++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.test.ts
@@ -84,7 +84,7 @@ describe('resolveSavedObjects', () => {
},
} as unknown) as IndexPatternsContract;
- const services = [
+ const services = ([
{
type: 'search',
get: async () => {
@@ -124,7 +124,7 @@ describe('resolveSavedObjects', () => {
};
},
},
- ] as SavedObjectLoader[];
+ ] as unknown) as SavedObjectLoader[];
const overwriteAll = false;
@@ -176,7 +176,7 @@ describe('resolveSavedObjects', () => {
},
} as unknown) as IndexPatternsContract;
- const services = [
+ const services = ([
{
type: 'search',
get: async () => {
@@ -217,7 +217,7 @@ describe('resolveSavedObjects', () => {
};
},
},
- ] as SavedObjectLoader[];
+ ] as unknown) as SavedObjectLoader[];
const overwriteAll = false;
@@ -237,33 +237,38 @@ describe('resolveSavedObjects', () => {
describe('resolveIndexPatternConflicts', () => {
it('should resave resolutions', async () => {
- const hydrateIndexPattern = jest.fn();
const save = jest.fn();
- const conflictedIndexPatterns = [
+ const conflictedIndexPatterns = ([
{
obj: {
- searchSource: {
- getOwnField: (field: string) => {
- return field === 'index' ? '1' : undefined;
+ save,
+ },
+ doc: {
+ _source: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: JSON.stringify({
+ index: '1',
+ }),
},
},
- hydrateIndexPattern,
- save,
},
},
{
obj: {
- searchSource: {
- getOwnField: (field: string) => {
- return field === 'index' ? '3' : undefined;
+ save,
+ },
+ doc: {
+ _source: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: JSON.stringify({
+ index: '3',
+ }),
},
},
- hydrateIndexPattern,
- save,
},
},
- ];
+ ] as unknown) as Array<{ obj: SavedObject; doc: any }>;
const resolutions = [
{
@@ -282,43 +287,49 @@ describe('resolveSavedObjects', () => {
const overwriteAll = false;
- await resolveIndexPatternConflicts(resolutions, conflictedIndexPatterns, overwriteAll);
- expect(hydrateIndexPattern.mock.calls.length).toBe(2);
+ await resolveIndexPatternConflicts(resolutions, conflictedIndexPatterns, overwriteAll, ({
+ get: (id: string) => Promise.resolve({ id }),
+ } as unknown) as IndexPatternsContract);
+ expect(conflictedIndexPatterns[0].obj.searchSource!.getField('index')!.id).toEqual('2');
+ expect(conflictedIndexPatterns[1].obj.searchSource!.getField('index')!.id).toEqual('4');
expect(save.mock.calls.length).toBe(2);
expect(save).toHaveBeenCalledWith({ confirmOverwrite: !overwriteAll });
- expect(hydrateIndexPattern).toHaveBeenCalledWith('2');
- expect(hydrateIndexPattern).toHaveBeenCalledWith('4');
});
it('should resolve filter index conflicts', async () => {
- const hydrateIndexPattern = jest.fn();
const save = jest.fn();
- const conflictedIndexPatterns = [
+ const conflictedIndexPatterns = ([
{
obj: {
- searchSource: {
- getOwnField: (field: string) => {
- return field === 'index' ? '1' : [{ meta: { index: 'filterIndex' } }];
+ save,
+ },
+ doc: {
+ _source: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: JSON.stringify({
+ index: '1',
+ filter: [{ meta: { index: 'filterIndex' } }],
+ }),
},
- setField: jest.fn(),
},
- hydrateIndexPattern,
- save,
},
},
{
obj: {
- searchSource: {
- getOwnField: (field: string) => {
- return field === 'index' ? '3' : undefined;
+ save,
+ },
+ doc: {
+ _source: {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: JSON.stringify({
+ index: '3',
+ }),
},
},
- hydrateIndexPattern,
- save,
},
},
- ];
+ ] as unknown) as Array<{ obj: SavedObject; doc: any }>;
const resolutions = [
{
@@ -337,9 +348,11 @@ describe('resolveSavedObjects', () => {
const overwriteAll = false;
- await resolveIndexPatternConflicts(resolutions, conflictedIndexPatterns, overwriteAll);
+ await resolveIndexPatternConflicts(resolutions, conflictedIndexPatterns, overwriteAll, ({
+ get: (id: string) => Promise.resolve({ id }),
+ } as unknown) as IndexPatternsContract);
- expect(conflictedIndexPatterns[0].obj.searchSource.setField).toHaveBeenCalledWith('filter', [
+ expect(conflictedIndexPatterns[0].obj.searchSource!.getField('filter')).toEqual([
{ meta: { index: 'newFilterIndex' } },
]);
expect(save.mock.calls.length).toBe(2);
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts
index 902de654f5f85..d9473367f7502 100644
--- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts
+++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/resolve_saved_objects.ts
@@ -18,12 +18,17 @@
*/
import { i18n } from '@kbn/i18n';
-import { OverlayStart } from 'src/core/public';
+import { cloneDeep } from 'lodash';
+import { OverlayStart, SavedObjectReference } from 'src/core/public';
import {
SavedObject,
SavedObjectLoader,
} from '../../../../../../../../plugins/saved_objects/public';
-import { IndexPatternsContract, IIndexPattern } from '../../../../../../../../plugins/data/public';
+import {
+ IndexPatternsContract,
+ IIndexPattern,
+ createSearchSource,
+} from '../../../../../../../../plugins/data/public';
type SavedObjectsRawDoc = Record;
@@ -126,7 +131,7 @@ async function importIndexPattern(
async function importDocument(obj: SavedObject, doc: SavedObjectsRawDoc, overwriteAll: boolean) {
await obj.applyESResp({
references: doc._references || [],
- ...doc,
+ ...cloneDeep(doc),
});
return await obj.save({ confirmOverwrite: !overwriteAll });
}
@@ -160,41 +165,57 @@ async function awaitEachItemInParallel(list: T[], op: (item: T) => R) {
export async function resolveIndexPatternConflicts(
resolutions: Array<{ oldId: string; newId: string }>,
conflictedIndexPatterns: any[],
- overwriteAll: boolean
+ overwriteAll: boolean,
+ indexPatterns: IndexPatternsContract
) {
let importCount = 0;
- await awaitEachItemInParallel(conflictedIndexPatterns, async ({ obj }) => {
- // Resolve search index reference:
- let oldIndexId = obj.searchSource.getOwnField('index');
- // Depending on the object, this can either be the raw id or the actual index pattern object
- if (typeof oldIndexId !== 'string') {
- oldIndexId = oldIndexId.id;
- }
- let resolution = resolutions.find(({ oldId }) => oldId === oldIndexId);
- if (resolution) {
- const newIndexId = resolution.newId;
- await obj.hydrateIndexPattern(newIndexId);
+ await awaitEachItemInParallel(conflictedIndexPatterns, async ({ obj, doc }) => {
+ const serializedSearchSource = JSON.parse(
+ doc._source.kibanaSavedObjectMeta?.searchSourceJSON || '{}'
+ );
+ const oldIndexId = serializedSearchSource.index;
+ let allResolved = true;
+ const inlineResolution = resolutions.find(({ oldId }) => oldId === oldIndexId);
+ if (inlineResolution) {
+ serializedSearchSource.index = inlineResolution.newId;
+ } else {
+ allResolved = false;
}
// Resolve filter index reference:
- const filter = (obj.searchSource.getOwnField('filter') || []).map((f: any) => {
+ const filter = (serializedSearchSource.filter || []).map((f: any) => {
if (!(f.meta && f.meta.index)) {
return f;
}
- resolution = resolutions.find(({ oldId }) => oldId === f.meta.index);
+ const resolution = resolutions.find(({ oldId }) => oldId === f.meta.index);
return resolution ? { ...f, ...{ meta: { ...f.meta, index: resolution.newId } } } : f;
});
if (filter.length > 0) {
- obj.searchSource.setField('filter', filter);
+ serializedSearchSource.filter = filter;
}
- if (!resolution) {
+ const replacedReferences = (doc._references || []).map((reference: SavedObjectReference) => {
+ const resolution = resolutions.find(({ oldId }) => oldId === reference.id);
+ if (resolution) {
+ return { ...reference, id: resolution.newId };
+ } else {
+ allResolved = false;
+ }
+
+ return reference;
+ });
+
+ if (!allResolved) {
// The user decided to skip this conflict so do nothing
return;
}
+ obj.searchSource = await createSearchSource(indexPatterns)(
+ JSON.stringify(serializedSearchSource),
+ replacedReferences
+ );
if (await saveObject(obj, overwriteAll)) {
importCount++;
}
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js
index 7c9ab32ab2f72..a710d3e318749 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js
@@ -71,6 +71,7 @@ const getResolvedResults = deps => {
return createSavedSearchesLoader({
savedObjectsClient: core.savedObjects.client,
indexPatterns: data.indexPatterns,
+ search: data.search,
chrome: core.chrome,
overlays: core.overlays,
}).get(results.vis.data.savedSearchId);
diff --git a/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts b/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts
index 201b21f932988..e7f431a178ea0 100644
--- a/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts
+++ b/src/legacy/core_plugins/timelion/public/services/saved_sheets.ts
@@ -28,6 +28,7 @@ const savedObjectsClient = npStart.core.savedObjects.client;
const services = {
savedObjectsClient,
indexPatterns: npStart.plugins.data.indexPatterns,
+ search: npStart.plugins.data.search,
chrome: npStart.core.chrome,
overlays: npStart.core.overlays,
};
diff --git a/src/legacy/ui/public/new_platform/set_services.ts b/src/legacy/ui/public/new_platform/set_services.ts
index 8cf015d5dff5c..400f31e73ffa1 100644
--- a/src/legacy/ui/public/new_platform/set_services.ts
+++ b/src/legacy/ui/public/new_platform/set_services.ts
@@ -72,9 +72,11 @@ export function setStartServices(npStart: NpStart) {
visualizationsServices.setAggs(npStart.plugins.data.search.aggs);
visualizationsServices.setOverlays(npStart.core.overlays);
visualizationsServices.setChrome(npStart.core.chrome);
+ visualizationsServices.setSearch(npStart.plugins.data.search);
const savedVisualizationsLoader = createSavedVisLoader({
savedObjectsClient: npStart.core.savedObjects.client,
indexPatterns: npStart.plugins.data.indexPatterns,
+ search: npStart.plugins.data.search,
chrome: npStart.core.chrome,
overlays: npStart.core.overlays,
visualizationTypes: visualizationsServices.getTypes(),
diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx
index c98fa612dc7af..322d734d9f39f 100644
--- a/src/plugins/dashboard/public/plugin.tsx
+++ b/src/plugins/dashboard/public/plugin.tsx
@@ -284,7 +284,7 @@ export class DashboardPlugin
const { notifications } = core;
const {
uiActions,
- data: { indexPatterns },
+ data: { indexPatterns, search },
} = plugins;
const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, core.uiSettings);
@@ -300,6 +300,7 @@ export class DashboardPlugin
const savedDashboardLoader = createSavedDashboardLoader({
savedObjectsClient: core.savedObjects.client,
indexPatterns,
+ search,
chrome: core.chrome,
overlays: core.overlays,
});
diff --git a/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts
index 2a1e64fa88a02..09357072a13a6 100644
--- a/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts
+++ b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts
@@ -18,13 +18,14 @@
*/
import { SavedObjectsClientContract, ChromeStart, OverlayStart } from 'kibana/public';
-import { IndexPatternsContract } from '../../../../plugins/data/public';
+import { DataPublicPluginStart, IndexPatternsContract } from '../../../../plugins/data/public';
import { SavedObjectLoader } from '../../../../plugins/saved_objects/public';
import { createSavedDashboardClass } from './saved_dashboard';
interface Services {
savedObjectsClient: SavedObjectsClientContract;
indexPatterns: IndexPatternsContract;
+ search: DataPublicPluginStart['search'];
chrome: ChromeStart;
overlays: OverlayStart;
}
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index efafea44167d4..06a46065baa84 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -366,6 +366,7 @@ export {
SearchStrategyProvider,
ISearchSource,
SearchSource,
+ createSearchSource,
SearchSourceFields,
EsQuerySortValue,
SortDirection,
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index 15067077afc43..2ebe377b3b32f 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -155,7 +155,7 @@ export class DataPublicPlugin implements Plugin({ timefilter: { timefil
// @public (undocumented)
export const createSavedQueryService: (savedObjectsClient: Pick) => SavedQueryService;
+// @public
+export const createSearchSource: (indexPatterns: Pick) => (searchSourceJson: string, references: SavedObjectReference[]) => Promise;
+
// Warning: (ae-missing-release-tag) "CustomFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -1667,6 +1671,10 @@ export class SearchSource {
// (undocumented)
history: SearchRequest[];
onRequestStart(handler: (searchSource: ISearchSource, options?: FetchOptions) => Promise): void;
+ serialize(): {
+ searchSourceJSON: string;
+ references: SavedObjectReference[];
+ };
// (undocumented)
setField(field: K, value: SearchSourceFields[K]): this;
// (undocumented)
@@ -1881,21 +1889,21 @@ export type TSearchStrategyProvider = (context: ISearc
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:413: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 "createFiltersFromEvent" needs to be exported by the entry point index.d.ts
diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts
index 1687d749f46e2..cce973d632f41 100644
--- a/src/plugins/data/public/search/index.ts
+++ b/src/plugins/data/public/search/index.ts
@@ -54,6 +54,7 @@ export {
SearchSourceFields,
EsQuerySortValue,
SortDirection,
+ createSearchSource,
} from './search_source';
export { SearchInterceptor } from './search_interceptor';
diff --git a/src/plugins/data/public/search/mocks.ts b/src/plugins/data/public/search/mocks.ts
index b70e889066a45..cb1c625a72959 100644
--- a/src/plugins/data/public/search/mocks.ts
+++ b/src/plugins/data/public/search/mocks.ts
@@ -33,6 +33,7 @@ export const searchStartMock: jest.Mocked = {
aggs: searchAggsStartMock(),
setInterceptor: jest.fn(),
search: jest.fn(),
+ createSearchSource: jest.fn(),
__LEGACY: {
AggConfig: jest.fn() as any,
AggType: jest.fn(),
diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts
index 42f31ef450d28..6124682184821 100644
--- a/src/plugins/data/public/search/search_service.ts
+++ b/src/plugins/data/public/search/search_service.ts
@@ -25,6 +25,8 @@ import { TStrategyTypes } from './strategy_types';
import { getEsClient, LegacyApiCaller } from './es_client';
import { ES_SEARCH_STRATEGY, DEFAULT_SEARCH_STRATEGY } from '../../common/search';
import { esSearchStrategyProvider } from './es_search/es_search_strategy';
+import { IndexPatternsContract } from '../index_patterns/index_patterns';
+import { createSearchSource } from './search_source';
import { QuerySetup } from '../query/query_service';
import { GetInternalStartServicesFn } from '../types';
import { SearchInterceptor } from './search_interceptor';
@@ -108,7 +110,7 @@ export class SearchService implements Plugin {
};
}
- public start(core: CoreStart): ISearchStart {
+ public start(core: CoreStart, indexPatterns: IndexPatternsContract): ISearchStart {
/**
* A global object that intercepts all searches and provides convenience methods for cancelling
* all pending search requests, as well as getting the number of pending search requests.
@@ -145,6 +147,7 @@ export class SearchService implements Plugin {
// TODO: should an intercepror have a destroy method?
this.searchInterceptor = searchInterceptor;
},
+ createSearchSource: createSearchSource(indexPatterns),
__LEGACY: {
esClient: this.esClient!,
AggConfig,
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
new file mode 100644
index 0000000000000..d49ce5a0d11f8
--- /dev/null
+++ b/src/plugins/data/public/search/search_source/create_search_source.test.ts
@@ -0,0 +1,151 @@
+/*
+ * 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 { 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';
+
+describe('createSearchSource', function() {
+ let createSearchSource: ReturnType;
+ const indexPatternMock: IIndexPattern = {} as IIndexPattern;
+ let indexPatternContractMock: jest.Mocked;
+
+ beforeEach(() => {
+ indexPatternContractMock = ({
+ get: jest.fn().mockReturnValue(Promise.resolve(indexPatternMock)),
+ } as unknown) as jest.Mocked;
+ createSearchSource = createSearchSourceFactory(indexPatternContractMock);
+ });
+
+ it('should fail if JSON is invalid', () => {
+ expect(createSearchSource('{', [])).rejects.toThrow();
+ expect(createSearchSource('0', [])).rejects.toThrow();
+ expect(createSearchSource('"abcdefg"', [])).rejects.toThrow();
+ });
+
+ it('should set fields', async () => {
+ const searchSource = await createSearchSource(
+ JSON.stringify({
+ highlightAll: true,
+ query: {
+ query: '',
+ language: 'kuery',
+ },
+ }),
+ []
+ );
+ expect(searchSource.getOwnField('highlightAll')).toBe(true);
+ expect(searchSource.getOwnField('query')).toEqual({
+ query: '',
+ language: 'kuery',
+ });
+ });
+
+ it('should resolve referenced index pattern', async () => {
+ 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);
+ });
+
+ it('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',
+ },
+ query: {
+ match_phrase: {
+ '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[];
+ expect(filters[0]).toMatchInlineSnapshot(`
+ Object {
+ "$state": Object {
+ "store": "appState",
+ },
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "index": "123-456",
+ "key": "category.keyword",
+ "negate": false,
+ "params": Object {
+ "query": "Men's Clothing",
+ },
+ "type": "phrase",
+ },
+ "query": Object {
+ "match_phrase": Object {
+ "category.keyword": "Men's Clothing",
+ },
+ },
+ }
+ `);
+ });
+
+ it('should migrate legacy queries on the fly', async () => {
+ const searchSource = await createSearchSource(
+ JSON.stringify({
+ highlightAll: true,
+ query: 'a:b',
+ }),
+ []
+ );
+ 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
new file mode 100644
index 0000000000000..35b7ac4eb9762
--- /dev/null
+++ b/src/plugins/data/public/search/search_source/create_search_source.ts
@@ -0,0 +1,113 @@
+/*
+ * 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 _ from 'lodash';
+import { SavedObjectReference } from 'kibana/public';
+import { migrateLegacyQuery } from '../../../../kibana_legacy/public';
+import { InvalidJSONProperty } from '../../../../kibana_utils/public';
+import { SearchSource } from './search_source';
+import { IndexPatternsContract } from '../../index_patterns/index_patterns';
+import { SearchSourceFields } from './types';
+
+/**
+ * Deserializes a json string and a set of referenced objects to a `SearchSource` instance.
+ * Use this method to re-create the search source serialized using `searchSource.serialize`.
+ *
+ * This function is a factory function that returns the actual utility when calling it with the
+ * required service dependency (index patterns contract). A pre-wired version is also exposed in
+ * the start contract of the data plugin as part of the search service
+ *
+ * @param indexPatterns The index patterns contract of the data plugin
+ *
+ * @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`.
+ *
+ * @public */
+export const createSearchSource = (indexPatterns: IndexPatternsContract) => async (
+ searchSourceJson: string,
+ references: SavedObjectReference[]
+) => {
+ const searchSource = new SearchSource();
+
+ // 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}`
+ );
+ }
+
+ // 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);
+
+ searchSource.setFields(newFields);
+ const query = searchSource.getOwnField('query');
+
+ if (typeof query !== 'undefined') {
+ searchSource.setField('query', migrateLegacyQuery(query));
+ }
+
+ return searchSource;
+};
diff --git a/src/plugins/data/public/search/search_source/index.ts b/src/plugins/data/public/search/search_source/index.ts
index 10f1b2bc332e1..0e9f530d0968a 100644
--- a/src/plugins/data/public/search/search_source/index.ts
+++ b/src/plugins/data/public/search/search_source/index.ts
@@ -18,4 +18,5 @@
*/
export * from './search_source';
+export { createSearchSource } from './create_search_source';
export { SortDirection, EsQuerySortValue, SearchSourceFields } from './types';
diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts
index 700bea741bd6a..1ef7c1187a9e0 100644
--- a/src/plugins/data/public/search/search_source/mocks.ts
+++ b/src/plugins/data/public/search/search_source/mocks.ts
@@ -37,4 +37,5 @@ export const searchSourceMock: MockedKeys = {
getSearchRequestBody: jest.fn(),
destroy: jest.fn(),
history: [],
+ serialize: jest.fn(),
};
diff --git a/src/plugins/data/public/search/search_source/search_source.test.ts b/src/plugins/data/public/search/search_source/search_source.test.ts
index fcd116a3f4121..6bad093d31402 100644
--- a/src/plugins/data/public/search/search_source/search_source.test.ts
+++ b/src/plugins/data/public/search/search_source/search_source.test.ts
@@ -18,7 +18,7 @@
*/
import { SearchSource } from './search_source';
-import { IndexPattern } from '../..';
+import { IndexPattern, SortDirection } from '../..';
import { mockDataServices } from '../aggs/test_helpers';
jest.mock('../fetch', () => ({
@@ -150,4 +150,77 @@ describe('SearchSource', function() {
expect(parentFn).toBeCalledWith(searchSource, options);
});
});
+
+ describe('#serialize', function() {
+ it('should reference index patterns', () => {
+ const indexPattern123 = { id: '123' } as IndexPattern;
+ const searchSource = new SearchSource();
+ searchSource.setField('index', indexPattern123);
+ const { searchSourceJSON, references } = searchSource.serialize();
+ expect(references[0].id).toEqual('123');
+ expect(references[0].type).toEqual('index-pattern');
+ expect(JSON.parse(searchSourceJSON).indexRefName).toEqual(references[0].name);
+ });
+
+ it('should add other fields', () => {
+ const searchSource = new SearchSource();
+ searchSource.setField('highlightAll', true);
+ searchSource.setField('from', 123456);
+ const { searchSourceJSON } = searchSource.serialize();
+ expect(JSON.parse(searchSourceJSON).highlightAll).toEqual(true);
+ expect(JSON.parse(searchSourceJSON).from).toEqual(123456);
+ });
+
+ it('should omit sort and size', () => {
+ const searchSource = new SearchSource();
+ searchSource.setField('highlightAll', true);
+ searchSource.setField('from', 123456);
+ searchSource.setField('sort', { field: SortDirection.asc });
+ searchSource.setField('size', 200);
+ const { searchSourceJSON } = searchSource.serialize();
+ expect(Object.keys(JSON.parse(searchSourceJSON))).toEqual(['highlightAll', 'from']);
+ });
+
+ it('should serialize filters', () => {
+ const searchSource = new SearchSource();
+ const filter = [
+ {
+ query: 'query',
+ meta: {
+ alias: 'alias',
+ disabled: false,
+ negate: false,
+ },
+ },
+ ];
+ searchSource.setField('filter', filter);
+ const { searchSourceJSON } = searchSource.serialize();
+ expect(JSON.parse(searchSourceJSON).filter).toEqual(filter);
+ });
+
+ it('should reference index patterns in filters separately from index field', () => {
+ const searchSource = new SearchSource();
+ const indexPattern123 = { id: '123' } as IndexPattern;
+ searchSource.setField('index', indexPattern123);
+ const filter = [
+ {
+ query: 'query',
+ meta: {
+ alias: 'alias',
+ disabled: false,
+ negate: false,
+ index: '456',
+ },
+ },
+ ];
+ searchSource.setField('filter', filter);
+ const { searchSourceJSON, references } = searchSource.serialize();
+ expect(references[0].id).toEqual('123');
+ expect(references[0].type).toEqual('index-pattern');
+ expect(JSON.parse(searchSourceJSON).indexRefName).toEqual(references[0].name);
+ expect(references[1].id).toEqual('456');
+ expect(references[1].type).toEqual('index-pattern');
+ expect(JSON.parse(searchSourceJSON).filter[0].meta.indexRefName).toEqual(references[1].name);
+ });
+ });
});
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 0c3321f03dabc..c70db7bb82ef7 100644
--- a/src/plugins/data/public/search/search_source/search_source.ts
+++ b/src/plugins/data/public/search/search_source/search_source.ts
@@ -70,6 +70,7 @@
*/
import _ from 'lodash';
+import { SavedObjectReference } from 'kibana/public';
import { normalizeSortRequest } from './normalize_sort_request';
import { filterDocvalueFields } from './filter_docvalue_fields';
import { fieldWildcardFilter } from '../../../../kibana_utils/public';
@@ -419,4 +420,85 @@ export class SearchSource {
return searchRequest;
}
+
+ /**
+ * 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.
+ *
+ * The references returned by this function can be mixed with other references in the same object,
+ * however make sure there are no name-collisions. The references will be named `kibanaSavedObjectMeta.searchSourceJSON.index`
+ * and `kibanaSavedObjectMeta.searchSourceJSON.filter[].meta.index`.
+ *
+ * Using `createSearchSource`, the instance can be re-created.
+ * @param searchSource The search source to serialize
+ * @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 };
+ }
+
+ private getFilters(filterField: SearchSourceFields['filter']): Filter[] {
+ if (!filterField) {
+ return [];
+ }
+
+ if (Array.isArray(filterField)) {
+ return filterField;
+ }
+
+ if (_.isFunction(filterField)) {
+ return this.getFilters(filterField());
+ }
+
+ return [filterField];
+ }
}
diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts
index 03cbfa9f8ed84..ba6e44f47b75e 100644
--- a/src/plugins/data/public/search/types.ts
+++ b/src/plugins/data/public/search/types.ts
@@ -18,6 +18,7 @@
*/
import { CoreStart } from 'kibana/public';
+import { createSearchSource } from './search_source';
import { SearchAggsSetup, SearchAggsStart, SearchAggsStartLegacy } from './aggs';
import { ISearch, ISearchGeneric } from './i_search';
import { TStrategyTypes } from './strategy_types';
@@ -89,5 +90,6 @@ export interface ISearchStart {
aggs: SearchAggsStart;
setInterceptor: (searchInterceptor: SearchInterceptor) => void;
search: ISearchGeneric;
+ createSearchSource: ReturnType;
__LEGACY: ISearchStartLegacy & SearchAggsStartLegacy;
}
diff --git a/src/plugins/saved_objects/public/plugin.ts b/src/plugins/saved_objects/public/plugin.ts
index 0f5773c00283e..7927238e12066 100644
--- a/src/plugins/saved_objects/public/plugin.ts
+++ b/src/plugins/saved_objects/public/plugin.ts
@@ -39,6 +39,7 @@ export class SavedObjectsPublicPlugin
SavedObjectClass: createSavedObjectClass({
indexPatterns: data.indexPatterns,
savedObjectsClient: core.savedObjects.client,
+ search: data.search,
chrome: core.chrome,
overlays: core.overlays,
}),
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 2e965eaf1989b..9776887b6d741 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
@@ -18,9 +18,8 @@
*/
import _ from 'lodash';
import { EsResponse, SavedObject, SavedObjectConfig } from '../../types';
-import { parseSearchSource } from './parse_search_source';
import { expandShorthand, SavedObjectNotFound } from '../../../../kibana_utils/public';
-import { IndexPattern } from '../../../../data/public';
+import { DataPublicPluginStart, IndexPattern } from '../../../../data/public';
/**
* A given response of and ElasticSearch containing a plain saved object is applied to the given
@@ -29,13 +28,13 @@ import { IndexPattern } from '../../../../data/public';
export async function applyESResp(
resp: EsResponse,
savedObject: SavedObject,
- config: SavedObjectConfig
+ config: SavedObjectConfig,
+ createSearchSource: DataPublicPluginStart['search']['createSearchSource']
) {
const mapping = expandShorthand(config.mapping);
const esType = config.type || '';
savedObject._source = _.cloneDeep(resp._source);
const injectReferences = config.injectReferences;
- const hydrateIndexPattern = savedObject.hydrateIndexPattern!;
if (typeof resp.found === 'boolean' && !resp.found) {
throw new SavedObjectNotFound(esType, savedObject.id || '');
}
@@ -64,13 +63,34 @@ export async function applyESResp(
_.assign(savedObject, savedObject._source);
savedObject.lastSavedTitle = savedObject.title;
- await parseSearchSource(savedObject, esType, meta.searchSourceJSON, resp.references);
- await hydrateIndexPattern();
+ if (config.searchSource) {
+ try {
+ savedObject.searchSource = await createSearchSource(meta.searchSourceJSON, resp.references);
+ } catch (error) {
+ if (
+ error.constructor.name === 'SavedObjectNotFound' &&
+ error.savedObjectType === 'index-pattern'
+ ) {
+ // if parsing the search source fails because the index pattern wasn't found,
+ // remember the reference - this is required for error handling on legacy imports
+ savedObject.unresolvedIndexPatternReference = {
+ name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ id: JSON.parse(meta.searchSourceJSON).index,
+ type: 'index-pattern',
+ };
+ }
+
+ throw error;
+ }
+ }
+
if (injectReferences && resp.references && resp.references.length > 0) {
injectReferences(savedObject, resp.references);
}
+
if (typeof config.afterESResp === 'function') {
savedObject = await config.afterESResp(savedObject);
}
+
return savedObject;
}
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 b9043890e2775..e8faef4e9e040 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
@@ -81,7 +81,8 @@ export function buildSavedObject(
*/
savedObject.init = _.once(() => intializeSavedObject(savedObject, savedObjectsClient, config));
- savedObject.applyESResp = (resp: EsResponse) => applyESResp(resp, savedObject, config);
+ savedObject.applyESResp = (resp: EsResponse) =>
+ applyESResp(resp, savedObject, config, services.search.createSearchSource);
/**
* Serialize this object
diff --git a/src/plugins/saved_objects/public/saved_object/helpers/hydrate_index_pattern.ts b/src/plugins/saved_objects/public/saved_object/helpers/hydrate_index_pattern.ts
index b55538e4073ba..84275cf35befb 100644
--- a/src/plugins/saved_objects/public/saved_object/helpers/hydrate_index_pattern.ts
+++ b/src/plugins/saved_objects/public/saved_object/helpers/hydrate_index_pattern.ts
@@ -31,25 +31,19 @@ export async function hydrateIndexPattern(
indexPatterns: IndexPatternsContract,
config: SavedObjectConfig
) {
- const clearSavedIndexPattern = !!config.clearSavedIndexPattern;
const indexPattern = config.indexPattern;
if (!savedObject.searchSource) {
return null;
}
- if (clearSavedIndexPattern) {
- savedObject.searchSource!.setField('index', undefined);
- return null;
- }
-
- const index = id || indexPattern || savedObject.searchSource!.getOwnField('index');
+ const index = id || indexPattern || savedObject.searchSource.getOwnField('index');
if (typeof index !== 'string' || !index) {
return null;
}
const indexObj = await indexPatterns.get(index);
- savedObject.searchSource!.setField('index', indexObj);
+ savedObject.searchSource.setField('index', indexObj);
return indexObj;
}
diff --git a/src/plugins/saved_objects/public/saved_object/helpers/parse_search_source.ts b/src/plugins/saved_objects/public/saved_object/helpers/parse_search_source.ts
deleted file mode 100644
index cdb191f9e7df8..0000000000000
--- a/src/plugins/saved_objects/public/saved_object/helpers/parse_search_source.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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 _ from 'lodash';
-import { migrateLegacyQuery } from '../../../../kibana_legacy/public';
-import { SavedObject } from '../../types';
-import { InvalidJSONProperty } from '../../../../kibana_utils/public';
-
-export function parseSearchSource(
- savedObject: SavedObject,
- esType: string,
- searchSourceJson: string,
- references: any[]
-) {
- if (!savedObject.searchSource) return;
-
- // 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 ${esType} "${savedObject.id}". ${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 searchSourceJSON in ${esType} "${savedObject.id}".`);
- }
-
- // Inject index id if a reference is saved
- if (searchSourceValues.indexRefName) {
- const reference = references.find(
- (ref: Record) => ref.name === searchSourceValues.indexRefName
- );
- if (!reference) {
- throw new Error(
- `Could not find reference for ${
- searchSourceValues.indexRefName
- } on ${savedObject.getEsType()} ${savedObject.id}`
- );
- }
- searchSourceValues.index = reference.id;
- delete searchSourceValues.indexRefName;
- }
-
- if (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
- } on ${savedObject.getEsType()}`
- );
- }
- filterRow.meta.index = reference.id;
- delete filterRow.meta.indexRefName;
- });
- }
-
- const searchSourceFields = savedObject.searchSource.getFields();
- const fnProps = _.transform(
- searchSourceFields,
- function(dynamic: Record, val: any, name: string | undefined) {
- if (_.isFunction(val) && name) dynamic[name] = val;
- },
- {}
- );
-
- savedObject.searchSource.setFields(_.defaults(searchSourceValues, fnProps));
- const query = savedObject.searchSource.getOwnField('query');
-
- if (typeof query !== 'undefined') {
- savedObject.searchSource.setField('query', migrateLegacyQuery(query));
- }
-}
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 8a020ca03aea3..78f9eeb8b5fb1 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
@@ -17,7 +17,6 @@
* under the License.
*/
import _ from 'lodash';
-import angular from 'angular';
import { SavedObject, SavedObjectConfig } from '../../types';
import { expandShorthand } from '../../../../kibana_utils/public';
@@ -41,57 +40,16 @@ export function serializeSavedObject(savedObject: SavedObject, config: SavedObje
});
if (savedObject.searchSource) {
- let searchSourceFields: Record = _.omit(savedObject.searchSource.getFields(), [
- 'sort',
- 'size',
- ]);
- if (searchSourceFields.index) {
- // searchSourceFields.index will normally be an IndexPattern, but can be a string in two scenarios:
- // (1) `init()` (and by extension `hydrateIndexPattern()`) hasn't been called on Saved Object
- // (2) The IndexPattern doesn't exist, so we fail to resolve it in `hydrateIndexPattern()`
- const indexId =
- typeof searchSourceFields.index === 'string'
- ? searchSourceFields.index
- : searchSourceFields.index.id;
- 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.map((filterRow: any, i: number) => {
- 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,
- },
- };
- }),
- };
- }
- attributes.kibanaSavedObjectMeta = {
- searchSourceJSON: angular.toJson(searchSourceFields),
- };
+ const {
+ searchSourceJSON,
+ references: searchSourceReferences,
+ } = savedObject.searchSource.serialize();
+ attributes.kibanaSavedObjectMeta = { searchSourceJSON };
+ references.push(...searchSourceReferences);
+ }
+
+ if (savedObject.unresolvedIndexPatternReference) {
+ references.push(savedObject.unresolvedIndexPatternReference);
}
return { attributes, references };
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 08389e9e3c97f..60c66f84080b2 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
@@ -103,9 +103,11 @@ describe('Saved Object', () => {
}
beforeEach(() => {
+ (dataStartMock.search.createSearchSource as jest.Mock).mockReset();
SavedObjectClass = createSavedObjectClass({
savedObjectsClient: savedObjectsClientStub,
indexPatterns: dataStartMock.indexPatterns,
+ search: dataStartMock.search,
} as SavedObjectKibanaServices);
});
@@ -269,7 +271,7 @@ describe('Saved Object', () => {
);
});
- it('when index exists in searchSourceJSON', () => {
+ it('when search source references saved object', () => {
const id = '123';
stubESResponse(getMockedDocResponse(id));
return createInitializedSavedObject({ type: 'dashboard', searchSource: true }).then(
@@ -409,18 +411,17 @@ describe('Saved Object', () => {
});
});
- it('throws error invalid JSON is detected', async () => {
+ it('forwards thrown exceptions from createSearchSource', async () => {
+ (dataStartMock.search.createSearchSource as jest.Mock).mockImplementation(() => {
+ throw new InvalidJSONProperty('');
+ });
const savedObject = await createInitializedSavedObject({
type: 'dashboard',
searchSource: true,
});
const response = {
found: true,
- _source: {
- kibanaSavedObjectMeta: {
- searchSourceJSON: '"{\\n \\"filter\\": []\\n}"',
- },
- },
+ _source: {},
};
try {
@@ -586,23 +587,24 @@ describe('Saved Object', () => {
});
});
- it('injects references from searchSourceJSON', async () => {
+ it('passes references to search source parsing function', async () => {
const savedObject = new SavedObjectClass({ type: 'dashboard', searchSource: true });
return savedObject.init!().then(() => {
+ const searchSourceJSON = JSON.stringify({
+ indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
+ filter: [
+ {
+ meta: {
+ indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
+ },
+ },
+ ],
+ });
const response = {
found: true,
_source: {
kibanaSavedObjectMeta: {
- searchSourceJSON: JSON.stringify({
- indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
- filter: [
- {
- meta: {
- indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
- },
- },
- ],
- }),
+ searchSourceJSON,
},
},
references: [
@@ -619,16 +621,10 @@ describe('Saved Object', () => {
],
};
savedObject.applyESResp(response);
- expect(savedObject.searchSource!.getFields()).toEqual({
- index: 'my-index-1',
- filter: [
- {
- meta: {
- index: 'my-index-2',
- },
- },
- ],
- });
+ expect(dataStartMock.search.createSearchSource).toBeCalledWith(
+ searchSourceJSON,
+ response.references
+ );
});
});
});
diff --git a/src/plugins/saved_objects/public/types.ts b/src/plugins/saved_objects/public/types.ts
index 99088df84ec36..3184038040952 100644
--- a/src/plugins/saved_objects/public/types.ts
+++ b/src/plugins/saved_objects/public/types.ts
@@ -24,7 +24,12 @@ import {
SavedObjectAttributes,
SavedObjectReference,
} from 'kibana/public';
-import { IIndexPattern, IndexPatternsContract, ISearchSource } from '../../data/public';
+import {
+ DataPublicPluginStart,
+ IIndexPattern,
+ IndexPatternsContract,
+ ISearchSource,
+} from '../../data/public';
export interface SavedObject {
_serialize: () => { attributes: SavedObjectAttributes; references: SavedObjectReference[] };
@@ -49,6 +54,7 @@ export interface SavedObject {
searchSource?: ISearchSource;
showInRecentlyAccessed: boolean;
title: string;
+ unresolvedIndexPatternReference?: SavedObjectReference;
}
export interface SavedObjectSaveOpts {
@@ -65,6 +71,7 @@ export interface SavedObjectCreationOpts {
export interface SavedObjectKibanaServices {
savedObjectsClient: SavedObjectsClientContract;
indexPatterns: IndexPatternsContract;
+ search: DataPublicPluginStart['search'];
chrome: ChromeStart;
overlays: OverlayStart;
}
@@ -72,7 +79,6 @@ export interface SavedObjectKibanaServices {
export interface SavedObjectConfig {
// is only used by visualize
afterESResp?: (savedObject: SavedObject) => Promise;
- clearSavedIndexPattern?: boolean;
defaults?: any;
extractReferences?: (opts: {
attributes: SavedObjectAttributes;
diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts
index 216defcee9016..8fcb84b19a9be 100644
--- a/src/plugins/visualizations/public/plugin.ts
+++ b/src/plugins/visualizations/public/plugin.ts
@@ -26,6 +26,7 @@ import {
setCapabilities,
setHttp,
setIndexPatterns,
+ setSearch,
setSavedObjects,
setUsageCollector,
setFilterManager,
@@ -140,6 +141,7 @@ export class VisualizationsPlugin
setHttp(core.http);
setSavedObjects(core.savedObjects);
setIndexPatterns(data.indexPatterns);
+ setSearch(data.search);
setFilterManager(data.query.filterManager);
setExpressions(expressions);
setUiActions(uiActions);
@@ -150,6 +152,7 @@ export class VisualizationsPlugin
const savedVisualizationsLoader = createSavedVisLoader({
savedObjectsClient: core.savedObjects.client,
indexPatterns: data.indexPatterns,
+ search: data.search,
chrome: core.chrome,
overlays: core.overlays,
visualizationTypes: types,
diff --git a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts
index bc96e08f4b9da..c99c7a4c2caa1 100644
--- a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts
+++ b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts
@@ -35,7 +35,7 @@ import { extractReferences, injectReferences } from './saved_visualization_refer
import { IIndexPattern, ISearchSource, SearchSource } from '../../../../plugins/data/public';
import { ISavedVis, SerializedVis } from '../types';
import { createSavedSearchesLoader } from '../../../../plugins/discover/public';
-import { getChrome, getOverlays, getIndexPatterns, getSavedObjects } from '../services';
+import { getChrome, getOverlays, getIndexPatterns, getSavedObjects, getSearch } from '../services';
export const convertToSerializedVis = async (savedVis: ISavedVis): Promise => {
const { visState } = savedVis;
@@ -87,6 +87,7 @@ const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?:
const savedSearch = await createSavedSearchesLoader({
savedObjectsClient: getSavedObjects().client,
indexPatterns: getIndexPatterns(),
+ search: getSearch(),
chrome: getChrome(),
overlays: getOverlays(),
}).get(savedSearchId);
diff --git a/src/plugins/visualizations/public/services.ts b/src/plugins/visualizations/public/services.ts
index c4668fa4b0c79..618c61dff176a 100644
--- a/src/plugins/visualizations/public/services.ts
+++ b/src/plugins/visualizations/public/services.ts
@@ -63,6 +63,8 @@ export const [getIndexPatterns, setIndexPatterns] = createGetterSetter('Search');
+
export const [getUsageCollector, setUsageCollector] = createGetterSetter(
'UsageCollection'
);
diff --git a/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js b/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js
index 252d602e8f564..bc636c0b200f8 100644
--- a/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js
+++ b/x-pack/legacy/plugins/maps/public/angular/services/gis_map_saved_object_loader.js
@@ -17,6 +17,7 @@ module.service('gisMapSavedObjectLoader', function() {
const services = {
savedObjectsClient,
indexPatterns: npStart.plugins.data.indexPatterns,
+ search: npStart.plugins.data.search,
chrome: npStart.core.chrome,
overlays: npStart.core.overlays,
};
diff --git a/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts b/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts
index f5f9e98fe659c..feff17b813112 100644
--- a/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts
+++ b/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts
@@ -29,6 +29,7 @@ export const useSearchItems = (defaultSavedObjectId: string | undefined) => {
const savedSearches = createSavedSearchesLoader({
savedObjectsClient,
indexPatterns,
+ search: appDeps.data.search,
chrome: appDeps.chrome,
overlays: appDeps.overlays,
});