"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.searchSourceRequiredUiSettings = exports.SearchSource = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _saferLodashSet = require("@kbn/safer-lodash-set"); var _lodash = require("lodash"); var _operators = require("rxjs/operators"); var _rxjs = require("rxjs"); var _esQuery = require("@kbn/es-query"); var _common = require("@kbn/kibana-utils-plugin/common"); var _common2 = require("@kbn/field-formats-plugin/common"); var _common3 = require("@kbn/expressions-plugin/common"); var _normalize_sort_request = require("./normalize_sort_request"); var _ = require("../.."); var _fetch = require("./fetch"); var _inspect = require("./inspect"); var _extract_references = require("./extract_references"); var _expressions = require("../expressions"); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0 and the Server Side Public License, v 1; you may not use this file except * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ /** * @name SearchSource * * @description A promise-based stream of search results that can inherit from other search sources. * * Because filters/queries in Kibana have different levels of persistence and come from different * places, it is important to keep track of where filters come from for when they are saved back to * the savedObject store in the Kibana index. To do this, we create trees of searchSource objects * that can have associated query parameters (index, query, filter, etc) which can also inherit from * other searchSource objects. * * At query time, all of the searchSource objects that have subscribers are "flattened", at which * point the query params from the searchSource are collected while traversing up the inheritance * chain. At each link in the chain a decision about how to merge the query params is made until a * single set of query parameters is created for each active searchSource (a searchSource with * subscribers). * * That set of query parameters is then sent to elasticsearch. This is how the filter hierarchy * works in Kibana. * * Visualize, starting from a new search: * * - the `savedVis.searchSource` is set as the `appSearchSource`. * - The `savedVis.searchSource` would normally inherit from the `appSearchSource`, but now it is * upgraded to inherit from the `rootSearchSource`. * - Any interaction with the visualization will still apply filters to the `appSearchSource`, so * they will be stored directly on the `savedVis.searchSource`. * - Any interaction with the time filter will be written to the `rootSearchSource`, so those * filters will not be saved by the `savedVis`. * - When the `savedVis` is saved to elasticsearch, it takes with it all the filters that are * defined on it directly, but none of the ones that it inherits from other places. * * Visualize, starting from an existing search: * * - The `savedVis` loads the `savedSearch` on which it is built. * - The `savedVis.searchSource` is set to inherit from the `saveSearch.searchSource` and set as * the `appSearchSource`. * - The `savedSearch.searchSource`, is set to inherit from the `rootSearchSource`. * - Then the `savedVis` is written to elasticsearch it will be flattened and only include the * filters created in the visualize application and will reconnect the filters from the * `savedSearch` at runtime to prevent losing the relationship * * Dashboard search sources: * * - Each panel in a dashboard has a search source. * - The `savedDashboard` also has a searchsource, and it is set as the `appSearchSource`. * - Each panel's search source inherits from the `appSearchSource`, meaning that they inherit from * the dashboard search source. * - When a filter is added to the search box, or via a visualization, it is written to the * `appSearchSource`. */ /** @internal */ const searchSourceRequiredUiSettings = ['dateFormat:tz', _.UI_SETTINGS.COURIER_CUSTOM_REQUEST_PREFERENCE, _.UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX, _.UI_SETTINGS.COURIER_MAX_CONCURRENT_SHARD_REQUESTS, _.UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE, _.UI_SETTINGS.DOC_HIGHLIGHT, _.UI_SETTINGS.META_FIELDS, _.UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS, _.UI_SETTINGS.QUERY_STRING_OPTIONS, _.UI_SETTINGS.SEARCH_INCLUDE_FROZEN, _.UI_SETTINGS.SORT_OPTIONS]; exports.searchSourceRequiredUiSettings = searchSourceRequiredUiSettings; /** @public **/ class SearchSource { constructor(fields = {}, dependencies) { (0, _defineProperty2.default)(this, "id", (0, _lodash.uniqueId)('data_source')); (0, _defineProperty2.default)(this, "shouldOverwriteDataViewType", false); (0, _defineProperty2.default)(this, "overwriteDataViewType", void 0); (0, _defineProperty2.default)(this, "parent", void 0); (0, _defineProperty2.default)(this, "requestStartHandlers", []); (0, _defineProperty2.default)(this, "inheritOptions", {}); (0, _defineProperty2.default)(this, "history", []); (0, _defineProperty2.default)(this, "fields", void 0); (0, _defineProperty2.default)(this, "dependencies", void 0); (0, _defineProperty2.default)(this, "getFieldName", fld => typeof fld === 'string' ? fld : fld.field); const { parent, ...currentFields } = fields; this.fields = currentFields; this.dependencies = dependencies; if (parent) { this.setParent(new SearchSource(parent, dependencies)); } } /** *** * PUBLIC API *****/ /** * Used to make the search source overwrite the actual data view type for the * specific requests done. This should only be needed very rarely, since it means * e.g. we'd be treating a rollup index pattern as a regular one. Be very sure * you understand the consequences of using this method before using it. * * @param overwriteType If `false` is passed in it will disable the overwrite, otherwise * the passed in value will be used as the data view type for this search source. */ setOverwriteDataViewType(overwriteType) { if (overwriteType === false) { this.shouldOverwriteDataViewType = false; this.overwriteDataViewType = undefined; } else { this.shouldOverwriteDataViewType = true; this.overwriteDataViewType = overwriteType; } } /** * sets value to a single search source field * @param field: field name * @param value: value for the field */ setField(field, value) { if (value == null) { return this.removeField(field); } this.fields[field] = value; return this; } /** * remove field * @param field: field name */ removeField(field) { delete this.fields[field]; return this; } /** * Internal, do not use. Overrides all search source fields with the new field array. * * @private * @param newFields New field array. */ setFields(newFields) { this.fields = newFields; return this; } /** * returns search source id */ getId() { return this.id; } /** * returns all search source fields */ getFields() { return { ...this.fields }; } /** * Gets a single field from the fields */ getField(field, recurse = true) { if (!recurse || this.fields[field] !== void 0) { return this.fields[field]; } const parent = this.getParent(); return parent && parent.getField(field); } getActiveIndexFilter() { var _queryString$reduce, _filters; const { filter: originalFilters, query } = this.getFields(); let filters = []; if (originalFilters) { filters = this.getFilters(originalFilters); } const queryString = Array.isArray(query) ? query.map(q => q.query) : (0, _esQuery.isOfQueryType)(query) ? query === null || query === void 0 ? void 0 : query.query : undefined; const indexPatternFromQuery = typeof queryString === 'string' ? this.parseActiveIndexPatternFromQueryString(queryString) : (_queryString$reduce = queryString === null || queryString === void 0 ? void 0 : queryString.reduce((acc, currStr) => { return acc.concat(this.parseActiveIndexPatternFromQueryString(currStr)); }, [])) !== null && _queryString$reduce !== void 0 ? _queryString$reduce : []; const activeIndexPattern = (_filters = filters) === null || _filters === void 0 ? void 0 : _filters.reduce((acc, f) => { var _f$meta$params; const isPhraseFilterType = (0, _esQuery.isPhraseFilter)(f); const isPhrasesFilterType = (0, _esQuery.isPhrasesFilter)(f); const filtersToChange = isPhraseFilterType ? (_f$meta$params = f.meta.params) === null || _f$meta$params === void 0 ? void 0 : _f$meta$params.query : f.meta.params; const filtersArray = Array.isArray(filtersToChange) ? filtersToChange : [filtersToChange]; if (isPhraseFilterType || isPhrasesFilterType) { if (f.meta.key === '_index' && f.meta.disabled === false) { if (f.meta.negate === false) { return (0, _lodash.concat)(acc, filtersArray); } else { return (0, _lodash.difference)(acc, filtersArray); } } else { return acc; } } else { return acc; } }, indexPatternFromQuery); const dedupActiveIndexPattern = new Set([...activeIndexPattern]); return [...dedupActiveIndexPattern]; } /** * Get the field from our own fields, don't traverse up the chain */ getOwnField(field) { return this.getField(field, false); } /** * @deprecated Don't use. */ create() { return new SearchSource({}, this.dependencies); } /** * creates a copy of this search source (without its children) */ createCopy() { const newSearchSource = new SearchSource({}, this.dependencies); newSearchSource.setFields({ ...this.fields }); // when serializing the internal fields we lose the internal classes used in the index // pattern, so we have to set it again to workaround this behavior newSearchSource.setField('index', this.getField('index')); newSearchSource.setParent(this.getParent()); return newSearchSource; } /** * creates a new child search source * @param options */ createChild(options = {}) { const childSearchSource = new SearchSource({}, this.dependencies); childSearchSource.setParent(this, options); return childSearchSource; } /** * Set a searchSource that this source should inherit from * @param {SearchSource} parent - the parent searchSource * @param {SearchSourceOptions} options - the inherit options */ setParent(parent, options = {}) { this.parent = parent; this.inheritOptions = options; return this; } /** * Get the parent of this SearchSource */ getParent() { return this.parent; } /** * Fetch this source from Elasticsearch, returning an observable over the response(s) * @param options */ fetch$(options = {}) { const s$ = (0, _rxjs.defer)(() => this.requestIsStarting(options)).pipe((0, _operators.switchMap)(() => { const searchRequest = this.flatten(); this.history = [searchRequest]; if (searchRequest.index) { options.indexPattern = searchRequest.index; } return this.fetchSearch$(searchRequest, options); }), (0, _operators.tap)(response => { // TODO: Remove casting when https://github.com/elastic/elasticsearch-js/issues/1287 is resolved if (!response || response.error) { throw new _fetch.RequestFailure(null, response); } }), (0, _operators.shareReplay)()); return this.inspectSearch(s$, options); } /** * Fetch this source and reject the returned Promise on error * @deprecated Use the `fetch$` method instead */ async fetch(options = {}) { const r = await (0, _rxjs.lastValueFrom)(this.fetch$(options)); return r.rawResponse; } /** * Add a handler that will be notified whenever requests start * @param {Function} handler */ onRequestStart(handler) { this.requestStartHandlers.push(handler); } /** * Returns body contents of the search request, often referred as query DSL. */ getSearchRequestBody() { return this.flatten().body; } /** * Completely destroy the SearchSource. */ destroy() { this.requestStartHandlers.length = 0; } /** **** * PRIVATE APIS ******/ inspectSearch(s$, options) { const { id, title, description, adapter } = options.inspector || { title: '' }; const requestResponder = adapter === null || adapter === void 0 ? void 0 : adapter.start(title, { id, description, searchSessionId: options.sessionId }); const trackRequestBody = () => { try { requestResponder === null || requestResponder === void 0 ? void 0 : requestResponder.json(this.getSearchRequestBody()); } catch (e) {} // eslint-disable-line no-empty }; // Track request stats on first emit, swallow errors const first$ = s$.pipe((0, _operators.first)(undefined, null), (0, _operators.tap)(() => { requestResponder === null || requestResponder === void 0 ? void 0 : requestResponder.stats((0, _inspect.getRequestInspectorStats)(this)); trackRequestBody(); }), (0, _operators.catchError)(() => { trackRequestBody(); return _rxjs.EMPTY; }), (0, _operators.finalize)(() => { first$.unsubscribe(); })).subscribe(); // Track response stats on last emit, as well as errors const last$ = s$.pipe((0, _operators.catchError)(e => { requestResponder === null || requestResponder === void 0 ? void 0 : requestResponder.error({ json: e }); return _rxjs.EMPTY; }), (0, _operators.last)(undefined, null), (0, _operators.tap)(finalResponse => { if (finalResponse) { const resp = finalResponse.rawResponse; requestResponder === null || requestResponder === void 0 ? void 0 : requestResponder.stats((0, _inspect.getResponseInspectorStats)(resp, this)); requestResponder === null || requestResponder === void 0 ? void 0 : requestResponder.ok({ json: finalResponse }); } }), (0, _operators.finalize)(() => { last$.unsubscribe(); })).subscribe(); return s$; } hasPostFlightRequests() { const aggs = this.getField('aggs'); if (aggs instanceof _.AggConfigs) { return aggs.aggs.some(agg => agg.enabled && typeof agg.type.postFlightRequest === 'function'); } else { return false; } } postFlightTransform(response) { const aggs = this.getField('aggs'); if (aggs instanceof _.AggConfigs) { return aggs.postFlightTransform(response); } else { return response; } } async fetchOthers(response, options) { const aggs = this.getField('aggs'); if (aggs instanceof _.AggConfigs) { for (const agg of aggs.aggs) { if (agg.enabled && typeof agg.type.postFlightRequest === 'function') { var _options$inspector; response = await agg.type.postFlightRequest(response, aggs, agg, this, (_options$inspector = options.inspector) === null || _options$inspector === void 0 ? void 0 : _options$inspector.adapter, options.abortSignal, options.sessionId, options.disableShardFailureWarning); } } } return response; } /** * Run a search using the search service */ fetchSearch$(searchRequest, options) { const { search, getConfig, onResponse } = this.dependencies; const params = (0, _fetch.getSearchParamsFromRequest)(searchRequest, { getConfig }); return search({ params, indexType: searchRequest.indexType }, options).pipe((0, _operators.switchMap)(response => { // For testing timeout messages in UI, uncomment the next line // response.rawResponse.timed_out = true; return new _rxjs.Observable(obs => { if ((0, _.isErrorResponse)(response)) { obs.error(response); } else if ((0, _.isPartialResponse)(response)) { obs.next(this.postFlightTransform(response)); } else { if (!this.hasPostFlightRequests()) { obs.next(this.postFlightTransform(response)); obs.complete(); } else { // Treat the complete response as partial, then run the postFlightRequests. obs.next({ ...this.postFlightTransform(response), isPartial: true, isRunning: true }); const sub = (0, _rxjs.from)(this.fetchOthers(response.rawResponse, options)).subscribe({ next: responseWithOther => { obs.next(this.postFlightTransform({ ...response, rawResponse: responseWithOther })); }, error: e => { obs.error(e); sub.unsubscribe(); }, complete: () => { obs.complete(); sub.unsubscribe(); } }); } } }); }), (0, _operators.map)(response => { if (!(0, _.isCompleteResponse)(response)) { return response; } return onResponse(searchRequest, response, options); })); } /** * Called by requests of this search source when they are started * @param options */ requestIsStarting(options = {}) { const handlers = [...this.requestStartHandlers]; // If callParentStartHandlers has been set to true, we also call all // handlers of parent search sources. if (this.inheritOptions.callParentStartHandlers) { let searchSource = this.getParent(); while (searchSource) { handlers.push(...searchSource.requestStartHandlers); searchSource = searchSource.getParent(); } } return Promise.all(handlers.map(fn => fn(this, options))); } /** * Used to merge properties into the data within ._flatten(). * The data is passed in and modified by the function * * @param {object} data - the current merged data * @param {*} val - the value at `key` * @param {*} key - The key of `val` */ mergeProp(data, val, key) { val = typeof val === 'function' ? val(this) : val; if (val == null || !key) return; const addToRoot = (rootKey, value) => { data[rootKey] = value; }; /** * Add the key and val to the body of the request */ const addToBody = (bodyKey, value) => { // ignore if we already have a value if (data.body[bodyKey] == null) { data.body[bodyKey] = value; } }; const { getConfig } = this.dependencies; switch (key) { case 'filter': return addToRoot('filters', (data.filters || []).concat(val)); case 'query': return addToRoot(key, (data[key] || []).concat(val)); case 'fields': // This will pass the passed in parameters to the new fields API. // Also if will only return scripted fields that are part of the specified // array of fields. If you specify the wildcard `*` as an array element // the fields API will return all fields, and all scripted fields will be returned. // NOTE: While the fields API supports wildcards within names, e.g. `user.*` // scripted fields won't be considered for this. return addToBody('fields', val); case 'fieldsFromSource': // preserves legacy behavior const fields = [...new Set((data[key] || []).concat(val))]; return addToRoot(key, fields); case 'index': case 'type': case 'highlightAll': return key && data[key] == null && addToRoot(key, val); case 'searchAfter': return addToBody('search_after', val); case 'trackTotalHits': return addToBody('track_total_hits', val); case 'source': return addToBody('_source', val); case 'sort': const sort = (0, _normalize_sort_request.normalizeSortRequest)(val, this.getField('index'), getConfig(_.UI_SETTINGS.SORT_OPTIONS)); return addToBody(key, sort); case 'pit': return addToRoot(key, val); case 'aggs': if (val instanceof _.AggConfigs) { return addToBody('aggs', val.toDsl()); } else { return addToBody('aggs', val); } default: return addToBody(key, val); } } /** * Walk the inheritance chain of a source and return its * flat representation (taking into account merging rules) * @resolved {Object|null} - the flat data of the SearchSource */ mergeProps(root = this, searchRequest = { body: {} }) { Object.entries(this.fields).forEach(([key, value]) => { this.mergeProp(searchRequest, value, key); }); if (this.parent) { this.parent.mergeProps(root, searchRequest); } return searchRequest; } getIndexType(index) { return this.shouldOverwriteDataViewType ? this.overwriteDataViewType : index === null || index === void 0 ? void 0 : index.type; } getFieldsWithoutSourceFilters(index, bodyFields) { var _sourceFilters$exclud; if (!index) { return bodyFields; } const { fields } = index; const sourceFilters = index.getSourceFiltering(); if (!sourceFilters || ((_sourceFilters$exclud = sourceFilters.excludes) === null || _sourceFilters$exclud === void 0 ? void 0 : _sourceFilters$exclud.length) === 0 || bodyFields.length === 0) { return bodyFields; } const sourceFiltersValues = sourceFilters.excludes; const wildcardField = bodyFields.find(el => el === '*' || el.field === '*'); const filter = (0, _common.fieldWildcardFilter)(sourceFiltersValues, this.dependencies.getConfig(_.UI_SETTINGS.META_FIELDS)); const filterSourceFields = fieldName => fieldName && filter(fieldName); if (!wildcardField) { // we already have an explicit list of fields, so we just remove source filters from that list return bodyFields.filter(fld => filterSourceFields(this.getFieldName(fld))); } // we need to get the list of fields from an index pattern return fields.filter(fld => filterSourceFields(fld.name)).map(fld => ({ field: fld.name })); } getFieldFromDocValueFieldsOrIndexPattern(docvaluesIndex, fld, index) { if (typeof fld === 'string') { return fld; } const fieldName = this.getFieldName(fld); const field = { ...docvaluesIndex[fieldName], ...fld }; if (!index) { return field; } const { fields } = index; const dateFields = fields.getByType('date'); const dateField = dateFields.find(indexPatternField => indexPatternField.name === fieldName); if (!dateField) { return field; } const { esTypes } = dateField; if (esTypes !== null && esTypes !== void 0 && esTypes.includes('date_nanos')) { field.format = 'strict_date_optional_time_nanos'; } else if (esTypes !== null && esTypes !== void 0 && esTypes.includes('date')) { field.format = 'strict_date_optional_time'; } return field; } flatten() { var _getConfig, _body$sort; const { getConfig } = this.dependencies; const searchRequest = this.mergeProps(); searchRequest.body = searchRequest.body || {}; const { body, index, query, filters, highlightAll, pit } = searchRequest; searchRequest.indexType = this.getIndexType(index); const metaFields = (_getConfig = getConfig(_.UI_SETTINGS.META_FIELDS)) !== null && _getConfig !== void 0 ? _getConfig : []; // get some special field types from the index pattern const { docvalueFields, scriptFields, storedFields, runtimeFields } = index ? index.getComputedFields() : { docvalueFields: [], scriptFields: {}, storedFields: ['*'], runtimeFields: {} }; const fieldListProvided = !!body.fields; // set defaults let fieldsFromSource = searchRequest.fieldsFromSource || []; body.fields = body.fields || []; body.script_fields = { ...body.script_fields, ...scriptFields }; body.stored_fields = storedFields; body.runtime_mappings = runtimeFields || {}; // apply source filters from index pattern if specified by the user let filteredDocvalueFields = docvalueFields; if (index) { const sourceFilters = index.getSourceFiltering(); if (!body.hasOwnProperty('_source')) { body._source = sourceFilters; } const filter = (0, _common.fieldWildcardFilter)(body._source.excludes, metaFields); // also apply filters to provided fields & default docvalueFields body.fields = body.fields.filter(fld => filter(this.getFieldName(fld))); fieldsFromSource = fieldsFromSource.filter(fld => filter(this.getFieldName(fld))); filteredDocvalueFields = filteredDocvalueFields.filter(fld => filter(this.getFieldName(fld))); } // specific fields were provided, so we need to exclude any others if (fieldListProvided || fieldsFromSource.length) { const bodyFieldNames = body.fields.map(field => this.getFieldName(field)); const uniqFieldNames = [...new Set([...bodyFieldNames, ...fieldsFromSource])]; if (!uniqFieldNames.includes('*')) { // filter down script_fields to only include items specified body.script_fields = (0, _lodash.pick)(body.script_fields, Object.keys(body.script_fields).filter(f => uniqFieldNames.includes(f))); } // request the remaining fields from stored_fields just in case, since the // fields API does not handle stored fields const remainingFields = (0, _lodash.difference)(uniqFieldNames, [...Object.keys(body.script_fields), ...Object.keys(body.runtime_mappings)]).filter(remainingField => { if (!remainingField) return false; if (!body._source || !body._source.excludes) return true; return !body._source.excludes.includes(remainingField); }); body.stored_fields = [...new Set(remainingFields)]; // only include unique values if (fieldsFromSource.length) { if (!(0, _lodash.isEqual)(remainingFields, fieldsFromSource)) { (0, _saferLodashSet.setWith)(body, '_source.includes', remainingFields, nsValue => (0, _lodash.isObject)(nsValue) ? {} : nsValue); } // if items that are in the docvalueFields are provided, we should // make sure those are added to the fields API unless they are // already set in docvalue_fields body.fields = [...body.fields, ...filteredDocvalueFields.filter(fld => { return fieldsFromSource.includes(this.getFieldName(fld)) && !(body.docvalue_fields || []).map(d => this.getFieldName(d)).includes(this.getFieldName(fld)); })]; // delete fields array if it is still set to the empty default if (!fieldListProvided && body.fields.length === 0) delete body.fields; } else { // remove _source, since everything's coming from fields API, scripted, or stored fields body._source = false; // if items that are in the docvalueFields are provided, we should // inject the format from the computed fields if one isn't given const docvaluesIndex = (0, _lodash.keyBy)(filteredDocvalueFields, 'field'); const bodyFields = this.getFieldsWithoutSourceFilters(index, body.fields); const uniqueFieldNames = new Set(); const uniqueFields = []; for (const field of bodyFields.concat(filteredDocvalueFields)) { const fieldName = this.getFieldName(field); if (metaFields.includes(fieldName) || uniqueFieldNames.has(fieldName)) { continue; } uniqueFieldNames.add(fieldName); if (Object.keys(docvaluesIndex).includes(fieldName)) { // either provide the field object from computed docvalues, // or merge the user-provided field with the one in docvalues uniqueFields.push(typeof field === 'string' ? docvaluesIndex[field] : this.getFieldFromDocValueFieldsOrIndexPattern(docvaluesIndex, field, index)); } else { uniqueFields.push(field); } } body.fields = uniqueFields; } } else { body.fields = filteredDocvalueFields; } // If sorting by _score, build queries in the "must" clause instead of "filter" clause to enable scoring const filtersInMustClause = ((_body$sort = body.sort) !== null && _body$sort !== void 0 ? _body$sort : []).some(sort => sort.hasOwnProperty('_score')); const esQueryConfigs = { ...(0, _.getEsQueryConfig)({ get: getConfig }), filtersInMustClause }; body.query = (0, _esQuery.buildEsQuery)(index, query, filters, esQueryConfigs); // For testing shard failure messages in UI, uncomment the next block and switch to `kibana*` data view // body.query = { // error_query: { // indices: [ // { // name: 'kibana_sample_data_logs', // shard_ids: [0, 1], // error_type: 'exception', // message: 'Testing shard failures!', // }, // ], // }, // }; if (highlightAll && body.query) { body.highlight = (0, _common2.getHighlightRequest)(getConfig(_.UI_SETTINGS.DOC_HIGHLIGHT)); delete searchRequest.highlightAll; } if (pit) { body.pit = pit; } return searchRequest; } /** * serializes search source fields (which can later be passed to {@link ISearchStartSearchSource}) */ getSerializedFields(recurse = false, includeFields = true) { const { filter: originalFilters, aggs: searchSourceAggs, parent, size: omit, sort, index, ...searchSourceFields } = this.getFields(); let serializedSearchSourceFields = { ...searchSourceFields }; if (index) { serializedSearchSourceFields.index = index.isPersisted() ? index.id : index.toSpec(includeFields); } if (sort) { serializedSearchSourceFields.sort = !Array.isArray(sort) ? [sort] : sort; } if (originalFilters) { const filters = this.getFilters(originalFilters); serializedSearchSourceFields = { ...serializedSearchSourceFields, filter: filters }; } if (searchSourceAggs) { let aggs = searchSourceAggs; if (typeof aggs === 'function') { aggs = searchSourceAggs(); } if (aggs instanceof _.AggConfigs) { serializedSearchSourceFields.aggs = aggs.getAll().map(agg => agg.serialize()); } else { serializedSearchSourceFields.aggs = aggs; } } if (recurse && this.getParent()) { serializedSearchSourceFields.parent = this.getParent().getSerializedFields(recurse); } return serializedSearchSourceFields; } /** * Serializes the instance to a JSON string and a set of referenced objects. * Use this method to get a representation of the search source which can be stored in a saved object. * * 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. * @public */ serialize() { const [searchSourceFields, references] = (0, _extract_references.extractReferences)(this.getSerializedFields()); return { searchSourceJSON: JSON.stringify(searchSourceFields), references }; } getFilters(filterField) { if (!filterField) { return []; } if (Array.isArray(filterField)) { return filterField; } if ((0, _lodash.isFunction)(filterField)) { return this.getFilters(filterField()); } return [filterField]; } /** * Generates an expression abstract syntax tree using the fields set in the current search source and its ancestors. * The produced expression from the returned AST will return the `datatable` structure. * If the `asDatatable` option is truthy or omitted, the generator will use the `esdsl` function to perform the search. * When the `aggs` field is present, it will use the `esaggs` function instead. */ toExpressionAst({ asDatatable = true } = {}) { const searchRequest = this.mergeProps(); const { body, index, query } = searchRequest; const filters = typeof searchRequest.filters === 'function' ? searchRequest.filters() : searchRequest.filters; const ast = (0, _common3.buildExpression)([(0, _common3.buildExpressionFunction)('kibana_context', { q: query === null || query === void 0 ? void 0 : query.map(_expressions.queryToAst), filters: filters && (0, _expressions.filtersToAst)(filters) })]).toAst(); if (!asDatatable) { return ast; } const aggsField = this.getField('aggs'); const aggs = typeof aggsField === 'function' ? aggsField() : aggsField; const aggConfigs = aggs instanceof _.AggConfigs ? aggs : index && aggs && this.dependencies.aggs.createAggConfigs(index, aggs); if (aggConfigs) { ast.chain.push(...aggConfigs.toExpressionAst().chain); } else { ast.chain.push((0, _common3.buildExpressionFunction)('esdsl', { size: body === null || body === void 0 ? void 0 : body.size, dsl: JSON.stringify({}), index: index === null || index === void 0 ? void 0 : index.id }).toAst()); } return ast; } parseActiveIndexPatternFromQueryString(queryString) { let m; const indexPatternSet = new Set(); const regex = /\s?(_index)\s?:\s?[\'\"]?(\w+\-?\*?)[\'\"]?\s?(\w+)?/g; while ((m = regex.exec(queryString)) !== null) { // This is necessary to avoid infinite loops with zero-width matches if (m.index === regex.lastIndex) { regex.lastIndex++; } m.forEach((match, groupIndex) => { if (groupIndex === 2) { indexPatternSet.add(match); } }); } return [...indexPatternSet]; } } exports.SearchSource = SearchSource;