making searchsource serializable (#115131)

This commit is contained in:
Peter Pisljar 2021-12-04 10:45:45 +01:00 committed by GitHub
parent 8533ceded4
commit f52ca1598f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 166 additions and 63 deletions

View file

@ -8,7 +8,7 @@
import { migrateLegacyQuery } from './migrate_legacy_query';
import { SearchSource, SearchSourceDependencies } from './search_source';
import { IndexPatternsContract } from '../..';
import { IndexPatternsContract, SerializedSearchSourceFields } from '../..';
import { SearchSourceFields } from './types';
/**
@ -28,16 +28,30 @@ import { SearchSourceFields } from './types';
*
*
* @public */
export const createSearchSource =
(indexPatterns: IndexPatternsContract, searchSourceDependencies: SearchSourceDependencies) =>
async (searchSourceFields: SearchSourceFields = {}) => {
const fields = { ...searchSourceFields };
export const createSearchSource = (
indexPatterns: IndexPatternsContract,
searchSourceDependencies: SearchSourceDependencies
) => {
const createFields = async (searchSourceFields: SerializedSearchSourceFields = {}) => {
const { index, parent, ...restOfFields } = searchSourceFields;
const fields: SearchSourceFields = {
...restOfFields,
};
// hydrating index pattern
if (fields.index && typeof fields.index === 'string') {
fields.index = await indexPatterns.get(searchSourceFields.index as any);
if (searchSourceFields.index) {
fields.index = await indexPatterns.get(searchSourceFields.index);
}
if (searchSourceFields.parent) {
fields.parent = await createFields(searchSourceFields.parent);
}
return fields;
};
const createSearchSourceFn = async (searchSourceFields: SerializedSearchSourceFields = {}) => {
const fields = await createFields(searchSourceFields);
const searchSource = new SearchSource(fields, searchSourceDependencies);
// todo: move to migration script .. create issue
@ -49,3 +63,6 @@ export const createSearchSource =
return searchSource;
};
return createSearchSourceFn;
};

View file

@ -8,17 +8,17 @@
import { SavedObjectReference } from 'src/core/types';
import { Filter } from '@kbn/es-query';
import { SearchSourceFields } from './types';
import { SerializedSearchSourceFields } from './types';
import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../../../../data/common';
export const extractReferences = (
state: SearchSourceFields
): [SearchSourceFields & { indexRefName?: string }, SavedObjectReference[]] => {
let searchSourceFields: SearchSourceFields & { indexRefName?: string } = { ...state };
state: SerializedSearchSourceFields
): [SerializedSearchSourceFields & { indexRefName?: string }, SavedObjectReference[]] => {
let searchSourceFields: SerializedSearchSourceFields & { indexRefName?: string } = { ...state };
const references: SavedObjectReference[] = [];
if (searchSourceFields.index) {
const indexId = searchSourceFields.index.id || (searchSourceFields.index as any as string);
const indexId = searchSourceFields.index;
const refName = 'kibanaSavedObjectMeta.searchSourceJSON.index';
references.push({
name: refName,

View file

@ -7,12 +7,12 @@
*/
import { SavedObjectReference } from 'src/core/types';
import { SearchSourceFields } from './types';
import { SerializedSearchSourceFields } from './types';
import { injectReferences } from './inject_references';
describe('injectSearchSourceReferences', () => {
let searchSourceJSON: SearchSourceFields & { indexRefName: string };
let searchSourceJSON: SerializedSearchSourceFields & { indexRefName: string };
let references: SavedObjectReference[];
beforeEach(() => {

View file

@ -7,13 +7,13 @@
*/
import { SavedObjectReference } from 'src/core/types';
import { SearchSourceFields } from './types';
import { SerializedSearchSourceFields } from './types';
export const injectReferences = (
searchSourceFields: SearchSourceFields & { indexRefName: string },
searchSourceFields: SerializedSearchSourceFields & { indexRefName: string },
references: SavedObjectReference[]
) => {
const searchSourceReturnFields: SearchSourceFields = { ...searchSourceFields };
const searchSourceReturnFields: SerializedSearchSourceFields = { ...searchSourceFields };
// Inject index id if a reference is saved
if (searchSourceFields.indexRefName) {
const reference = references.find((ref) => ref.name === searchSourceFields.indexRefName);

View file

@ -6,12 +6,12 @@
* Side Public License, v 1.
*/
import { SearchSourceFields } from './types';
import { SerializedSearchSourceFields } from './types';
import { InvalidJSONProperty } from '../../../../kibana_utils/common';
export const parseSearchSourceJSON = (searchSourceJSON: string) => {
// if we have a searchSource, set its values based on the searchSourceJson field
let searchSourceValues: SearchSourceFields;
let searchSourceValues: SerializedSearchSourceFields;
try {
searchSourceValues = JSON.parse(searchSourceJSON);
} catch (e) {

View file

@ -944,7 +944,6 @@ describe('SearchSource', () => {
},
`
Object {
"index": undefined,
"parent": Object {
"from": 123,
"index": "123",

View file

@ -75,7 +75,13 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { buildEsQuery, Filter } from '@kbn/es-query';
import { normalizeSortRequest } from './normalize_sort_request';
import { fieldWildcardFilter } from '../../../../kibana_utils/common';
import { IIndexPattern, IndexPattern, IndexPatternField } from '../..';
import {
AggConfigSerialized,
IIndexPattern,
IndexPattern,
IndexPatternField,
SerializedSearchSourceFields,
} from '../..';
import {
AggConfigs,
EsQuerySortValue,
@ -846,12 +852,26 @@ export class SearchSource {
/**
* serializes search source fields (which can later be passed to {@link ISearchStartSearchSource})
*/
public getSerializedFields(recurse = false) {
const { filter: originalFilters, size: omit, ...searchSourceFields } = this.getFields();
let serializedSearchSourceFields: SearchSourceFields = {
public getSerializedFields(recurse = false): SerializedSearchSourceFields {
const {
filter: originalFilters,
aggs: searchSourceAggs,
parent,
size: omit,
sort,
index,
...searchSourceFields
} = this.getFields();
let serializedSearchSourceFields: SerializedSearchSourceFields = {
...searchSourceFields,
index: (searchSourceFields.index ? searchSourceFields.index.id : undefined) as any,
};
if (index) {
serializedSearchSourceFields.index = index.id;
}
if (sort) {
serializedSearchSourceFields.sort = !Array.isArray(sort) ? [sort] : sort;
}
if (originalFilters) {
const filters = this.getFilters(originalFilters);
serializedSearchSourceFields = {
@ -859,6 +879,17 @@ export class SearchSource {
filter: filters,
};
}
if (searchSourceAggs) {
let aggs = searchSourceAggs;
if (typeof aggs === 'function') {
aggs = (searchSourceAggs as Function)();
}
if (aggs instanceof AggConfigs) {
serializedSearchSourceFields.aggs = aggs.getAll().map((agg) => agg.serialize());
} else {
serializedSearchSourceFields.aggs = aggs as AggConfigSerialized[];
}
}
if (recurse && this.getParent()) {
serializedSearchSourceFields.parent = this.getParent()!.getSerializedFields(recurse);
}

View file

@ -5,8 +5,10 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IAggConfigs } from 'src/plugins/data/public';
import { AggConfigSerialized, IAggConfigs } from 'src/plugins/data/public';
import { SerializableRecord } from '@kbn/utility-types';
import { Query } from '../..';
import { Filter } from '../../es_query';
import { IndexPattern } from '../..';
@ -27,7 +29,7 @@ export interface ISearchStartSearchSource {
* creates {@link SearchSource} based on provided serialized {@link SearchSourceFields}
* @param fields
*/
create: (fields?: SearchSourceFields) => Promise<ISearchSource>;
create: (fields?: SerializedSearchSourceFields) => Promise<ISearchSource>;
/**
* creates empty {@link SearchSource}
*/
@ -112,6 +114,53 @@ export interface SearchSourceFields {
parent?: SearchSourceFields;
}
export interface SerializedSearchSourceFields {
type?: string;
/**
* {@link Query}
*/
query?: Query;
/**
* {@link Filter}
*/
filter?: Filter[];
/**
* {@link EsQuerySortValue}
*/
sort?: EsQuerySortValue[];
highlight?: SerializableRecord;
highlightAll?: boolean;
trackTotalHits?: boolean | number;
// todo: needs aggconfigs serializable type
/**
* {@link AggConfigs}
*/
aggs?: AggConfigSerialized[];
from?: number;
size?: number;
source?: boolean | estypes.Fields;
version?: boolean;
/**
* Retrieve fields via the search Fields API
*/
fields?: SearchFieldValue[];
/**
* Retreive fields directly from _source (legacy behavior)
*
* @deprecated It is recommended to use `fields` wherever possible.
*/
fieldsFromSource?: estypes.Fields;
/**
* {@link IndexPatternService}
*/
index?: string;
searchAfter?: EsQuerySearchAfter;
timeout?: string;
terminate_after?: number;
parent?: SerializedSearchSourceFields;
}
export interface SearchSourceOptions {
callParentStartHandlers?: boolean;
}

View file

@ -187,6 +187,7 @@ export type {
ISearchSource,
SearchRequest,
SearchSourceFields,
SerializedSearchSourceFields,
// errors
IEsError,
Reason,

View file

@ -27,6 +27,7 @@ export type {
SearchRequest,
SearchSourceDependencies,
SearchSourceFields,
SerializedSearchSourceFields,
} from '../../common/search';
export {
ES_SEARCH_STRATEGY,

View file

@ -9,7 +9,7 @@
import type { Capabilities } from 'kibana/public';
import type { IUiSettingsClient } from 'kibana/public';
import type { DataPublicPluginStart } from 'src/plugins/data/public';
import type { Filter, ISearchSource, SearchSourceFields } from 'src/plugins/data/common';
import type { Filter, ISearchSource, SerializedSearchSourceFields } from 'src/plugins/data/common';
import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../common';
import type { SavedSearch, SortOrder } from '../services/saved_searches';
import { getSortForSearchSource } from '../components/doc_table';
@ -55,7 +55,7 @@ export async function getSharingData(
}
return {
getSearchSource: (absoluteTime?: boolean): SearchSourceFields => {
getSearchSource: (absoluteTime?: boolean): SerializedSearchSourceFields => {
const timeFilter = absoluteTime
? data.query.timefilter.timefilter.createFilter(index)
: data.query.timefilter.timefilter.createRelativeFilter(index);

View file

@ -8,7 +8,7 @@
import { Filter } from '@kbn/es-query';
import {
SearchSourceFields,
SerializedSearchSourceFields,
IndexPattern,
TimefilterContract,
DataPublicPluginStart,
@ -16,7 +16,7 @@ import {
export async function createSearchSource(
{ create }: DataPublicPluginStart['search']['searchSource'],
initialState: SearchSourceFields | null,
initialState: SerializedSearchSourceFields | null,
indexPattern: IndexPattern,
aggs: any,
useTimeFilter: boolean,

View file

@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n';
import {
IndexPatternField,
TimefilterContract,
SearchSourceFields,
SerializedSearchSourceFields,
DataPublicPluginStart,
} from 'src/plugins/data/public';
import { Control, noValuesDisableMsg, noIndexPatternMsg } from './control';
@ -127,7 +127,7 @@ export class ListControl extends Control<PhraseFilterManager> {
const fieldName = this.filterManager.fieldName;
const settings = await this.getSettings();
const initialSearchSourceState: SearchSourceFields = {
const initialSearchSourceState: SerializedSearchSourceFields = {
timeout: `${settings.autocompleteTimeout}ms`,
terminate_after: Number(settings.autocompleteTerminateAfter),
};

View file

@ -18,7 +18,7 @@ import {
IndexPattern,
IndexPatternsContract,
ISearchSource,
SearchSourceFields,
SerializedSearchSourceFields,
} from '../../data/public';
/** @deprecated */
@ -43,7 +43,7 @@ export interface SavedObject {
migrationVersion?: Record<string, any>;
save: (saveOptions: SavedObjectSaveOpts) => Promise<string>;
searchSource?: ISearchSource;
searchSourceFields?: SearchSourceFields;
searchSourceFields?: SerializedSearchSourceFields;
showInRecentlyAccessed: boolean;
title: string;
unresolvedIndexPatternReference?: SavedObjectReference;

View file

@ -9,7 +9,7 @@
import type { SavedObjectsMigrationVersion } from 'kibana/public';
import {
IAggConfigs,
SearchSourceFields,
SerializedSearchSourceFields,
TimefilterContract,
AggConfigSerialized,
} from '../../../plugins/data/public';
@ -33,7 +33,7 @@ export interface ISavedVis {
title: string;
description?: string;
visState: SavedVisState;
searchSourceFields?: SearchSourceFields;
searchSourceFields?: SerializedSearchSourceFields;
uiStateJSON?: string;
savedSearchRefName?: string;
savedSearchId?: string;

View file

@ -15,7 +15,7 @@ import { SavedVisState, VisSavedObject } from '../../types';
import {
extractSearchSourceReferences,
injectSearchSourceReferences,
SearchSourceFields,
SerializedSearchSourceFields,
} from '../../../../data/public';
import { extractTimeSeriesReferences, injectTimeSeriesReferences } from './timeseries_references';
@ -33,7 +33,7 @@ export function extractReferences({
if (updatedAttributes.searchSourceFields) {
const [searchSource, searchSourceReferences] = extractSearchSourceReferences(
updatedAttributes.searchSourceFields as SearchSourceFields
updatedAttributes.searchSourceFields as SerializedSearchSourceFields
);
updatedAttributes.searchSourceFields = searchSource as SavedObjectAttribute;
searchSourceReferences.forEach((r) => updatedReferences.push(r));

View file

@ -27,7 +27,7 @@ import {
IndexPattern,
ISearchSource,
AggConfigSerialized,
SearchSourceFields,
SerializedSearchSourceFields,
} from '../../data/public';
import { BaseVisType } from './vis_types';
import { VisParams } from '../common/types';
@ -37,7 +37,7 @@ import { getSavedSearch, throwErrorOnSavedSearchUrlConflict } from '../../discov
export interface SerializedVisData {
expression?: string;
aggs: AggConfigSerialized[];
searchSource: SearchSourceFields;
searchSource: SerializedSearchSourceFields;
savedSearchId?: string;
}

View file

@ -13,7 +13,7 @@ import {
VisualizeEmbeddableContract,
VisualizeInput,
} from 'src/plugins/visualizations/public';
import { SearchSourceFields } from 'src/plugins/data/public';
import { SerializedSearchSourceFields } from 'src/plugins/data/public';
import { cloneDeep } from 'lodash';
import { ExpressionValueError } from 'src/plugins/expressions/public';
import {
@ -118,7 +118,7 @@ export const getVisualizationInstance = async (
const savedVis: VisSavedObject = await visualizations.getSavedVisualization(opts);
if (typeof opts !== 'string') {
savedVis.searchSourceFields = { index: opts?.indexPattern } as SearchSourceFields;
savedVis.searchSourceFields = { index: opts?.indexPattern } as SerializedSearchSourceFields;
}
const serializedVis = visualizations.convertToSerializedVis(savedVis);
let vis = await visualizations.createVis(serializedVis.type, serializedVis);

View file

@ -103,13 +103,17 @@ export const useVisualizeAppState = (
const { aggs, ...visState } = currentAppState.vis;
const query = currentAppState.query;
const filter = currentAppState.filters;
const visSearchSource = instance.vis.data.searchSource?.getFields() || {};
const visSearchSource = instance.vis.data.searchSource?.getSerializedFields() || {};
instance.vis
.setState({
...visState,
data: {
aggs,
searchSource: { ...visSearchSource, query, filter },
searchSource: {
...visSearchSource,
query,
filter,
},
savedSearchId: instance.vis.data.savedSearchId,
},
})

View file

@ -29,7 +29,6 @@ export const usePackQueryErrors = ({
['scheduledQueryErrors', { actionId, interval }],
async () => {
const searchSource = await data.search.searchSource.create({
index: logsDataView,
fields: ['*'],
sort: [
{
@ -69,6 +68,7 @@ export const usePackQueryErrors = ({
size: 1000,
});
searchSource.setField('index', logsDataView);
return searchSource.fetch$().toPromise();
},
{

View file

@ -30,9 +30,8 @@ export const usePackQueryLastResults = ({
['scheduledQueryLastResults', { actionId }],
async () => {
const lastResultsSearchSource = await data.search.searchSource.create({
index: logsDataView,
size: 1,
sort: { '@timestamp': SortDirection.desc },
sort: [{ '@timestamp': SortDirection.desc }],
query: {
// @ts-expect-error update types
bool: {
@ -47,16 +46,14 @@ export const usePackQueryLastResults = ({
},
});
lastResultsSearchSource.setField('index', logsDataView);
const lastResultsResponse = await lastResultsSearchSource.fetch$().toPromise();
const timestamp = lastResultsResponse.rawResponse?.hits?.hits[0]?.fields?.['@timestamp'][0];
if (timestamp) {
const aggsSearchSource = await data.search.searchSource.create({
index: logsDataView,
size: 1,
aggs: {
unique_agents: { cardinality: { field: 'agent.id' } },
},
query: {
// @ts-expect-error update types
bool: {
@ -79,6 +76,10 @@ export const usePackQueryLastResults = ({
},
});
aggsSearchSource.setField('index', logsDataView);
aggsSearchSource.setField('aggs', {
unique_agents: { cardinality: { field: 'agent.id' } },
});
const aggsResponse = await aggsSearchSource.fetch$().toPromise();
return {

View file

@ -5,11 +5,11 @@
* 2.0.
*/
import type { SearchSourceFields } from 'src/plugins/data/common';
import type { SerializedSearchSourceFields } from 'src/plugins/data/common';
import type { BaseParams, BasePayload } from '../base';
interface BaseParamsCSV {
searchSource: SearchSourceFields;
searchSource: SerializedSearchSourceFields;
columns?: string[];
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { SearchSourceFields } from 'src/plugins/data/common';
import type { SerializedSearchSourceFields } from 'src/plugins/data/common';
export interface FakeRequest {
headers: Record<string, string>;
@ -14,7 +14,7 @@ export interface FakeRequest {
export interface JobParamsDownloadCSV {
browserTimezone: string;
title: string;
searchSource: SearchSourceFields;
searchSource: SerializedSearchSourceFields;
columns?: string[];
}

View file

@ -6,7 +6,7 @@
*/
import expect from '@kbn/expect';
import { SearchSourceFields } from 'src/plugins/data/common';
import { SerializedSearchSourceFields } from 'src/plugins/data/common';
import { ReportApiJSON } from '../../../plugins/reporting/common/types';
import { FtrProviderContext } from '../ftr_provider_context';
@ -51,7 +51,7 @@ export default function ({ getService }: FtrProviderContext) {
],
},
},
} as unknown as SearchSourceFields,
} as unknown as SerializedSearchSourceFields,
});
expect(status).to.be(200);

View file

@ -6,7 +6,7 @@
*/
import expect from '@kbn/expect';
import { SearchSourceFields } from 'src/plugins/data/common';
import { SerializedSearchSourceFields } from 'src/plugins/data/common';
import { FtrProviderContext } from '../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
@ -33,7 +33,7 @@ export default function ({ getService }: FtrProviderContext) {
query: { query: '', language: 'kuery' },
index: '5193f870-d861-11e9-a311-0fa548c5f953',
filter: [],
} as unknown as SearchSourceFields,
} as unknown as SerializedSearchSourceFields,
browserTimezone: 'UTC',
title: 'testfooyu78yt90-',
}
@ -50,7 +50,7 @@ export default function ({ getService }: FtrProviderContext) {
query: { query: '', language: 'kuery' },
index: '5193f870-d861-11e9-a311-0fa548c5f953',
filter: [],
} as unknown as SearchSourceFields,
} as unknown as SerializedSearchSourceFields,
browserTimezone: 'UTC',
title: 'testfooyu78yt90-',
}
@ -166,7 +166,7 @@ export default function ({ getService }: FtrProviderContext) {
const res = await reportingAPI.generateCsv(
{
browserTimezone: 'UTC',
searchSource: {} as SearchSourceFields,
searchSource: {} as SerializedSearchSourceFields,
objectType: 'search',
title: 'test disallowed',
version: '7.14.0',
@ -187,7 +187,7 @@ export default function ({ getService }: FtrProviderContext) {
version: true,
fields: [{ field: '*', include_unmapped: 'true' }],
index: '5193f870-d861-11e9-a311-0fa548c5f953',
} as unknown as SearchSourceFields,
} as unknown as SerializedSearchSourceFields,
columns: [],
version: '7.13.0',
},