UNPKG

kibana-123

Version:

Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elastic

286 lines (255 loc) 9.61 kB
/** * @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`. */ import _ from 'lodash'; import NormalizeSortRequestProvider from './_normalize_sort_request'; import rootSearchSource from './_root_search_source'; import AbstractDataSourceProvider from './_abstract'; import SearchRequestProvider from '../fetch/request/search'; import SegmentedRequestProvider from '../fetch/request/segmented'; import SearchStrategyProvider from '../fetch/strategy/search'; export default function SearchSourceFactory(Promise, Private, config) { let SourceAbstract = Private(AbstractDataSourceProvider); let SearchRequest = Private(SearchRequestProvider); let SegmentedRequest = Private(SegmentedRequestProvider); let searchStrategy = Private(SearchStrategyProvider); let normalizeSortRequest = Private(NormalizeSortRequestProvider); let forIp = Symbol('for which index pattern?'); function isIndexPattern(val) { return Boolean(val && typeof val.toIndexList === 'function'); } _.class(SearchSource).inherits(SourceAbstract); function SearchSource(initialState) { SearchSource.Super.call(this, initialState, searchStrategy); } /***** * PUBLIC API *****/ /** * List of the editable state properties that turn into a * chainable API * * @type {Array} */ SearchSource.prototype._methods = [ 'type', 'query', 'filter', 'sort', 'highlight', 'aggs', 'from', 'size', 'source' ]; SearchSource.prototype.index = function (indexPattern) { let state = this._state; let hasSource = state.source; let sourceCameFromIp = hasSource && state.source.hasOwnProperty(forIp); let sourceIsForOurIp = sourceCameFromIp && state.source[forIp] === state.index; if (sourceIsForOurIp) { delete state.source; } if (indexPattern === undefined) return state.index; if (indexPattern === null) return delete state.index; if (!isIndexPattern(indexPattern)) { throw new TypeError('expected indexPattern to be an IndexPattern duck.'); } state.index = indexPattern; if (!state.source) { // imply source filtering based on the index pattern, but allow overriding // it by simply setting another value for "source". When index is changed state.source = function () { return indexPattern.getSourceFiltering(); }; state.source[forIp] = indexPattern; } return this; }; SearchSource.prototype.extend = function () { return (new SearchSource()).inherits(this); }; /** * Set a searchSource that this source should inherit from * @param {SearchSource} searchSource - the parent searchSource * @return {this} - chainable */ SearchSource.prototype.inherits = function (parent) { this._parent = parent; return this; }; /** * Get the parent of this SearchSource * @return {undefined|searchSource} */ SearchSource.prototype.getParent = function (onlyHardLinked) { let self = this; if (self._parent === false) return; if (self._parent) return self._parent; return onlyHardLinked ? undefined : Private(rootSearchSource).get(); }; /** * Temporarily prevent this Search from being fetched... not a fan but it's easy */ SearchSource.prototype.disable = function () { this._fetchDisabled = true; }; /** * Reverse of SourceAbstract#disable(), only need to call this if source was previously disabled */ SearchSource.prototype.enable = function () { this._fetchDisabled = false; }; SearchSource.prototype.onBeginSegmentedFetch = function (initFunction) { let self = this; return Promise.try(function addRequest() { let req = new SegmentedRequest(self, Promise.defer(), initFunction); // return promises created by the completion handler so that // errors will bubble properly return req.defer.promise.then(addRequest); }); }; /****** * PRIVATE APIS ******/ /** * Gets the type of the DataSource * @return {string} */ SearchSource.prototype._getType = function () { return 'search'; }; /** * Create a common search request object, which should * be put into the pending request queye, for this search * source * * @param {Deferred} defer - the deferred object that should be resolved * when the request is complete * @return {SearchRequest} */ SearchSource.prototype._createRequest = function (defer) { return new SearchRequest(this, defer); }; /** * Used to merge properties into the state within ._flatten(). * The state is passed in and modified by the function * * @param {object} state - the current merged state * @param {*} val - the value at `key` * @param {*} key - The key of `val` * @return {undefined} */ SearchSource.prototype._mergeProp = function (state, val, key) { if (typeof val === 'function') { let source = this; return Promise.cast(val(this)) .then(function (newVal) { return source._mergeProp(state, newVal, key); }); } if (val == null || !key || !_.isString(key)) return; switch (key) { case 'filter': let verifiedFilters = val; if (config.get('courier:ignoreFilterIfFieldNotInIndex')) { if (!_.isArray(val)) val = [val]; verifiedFilters = val.filter(function (el) { if ('meta' in el && 'index' in state) { const field = state.index.fields.byName[el.meta.key]; if (!field) return false; } return true; }); } // user a shallow flatten to detect if val is an array, and pull the values out if it is state.filters = _([ state.filters || [], verifiedFilters ]) .flatten() // Yo Dawg! I heard you needed to filter out your filters .reject(function (filter) { return !filter || _.get(filter, 'meta.disabled'); }) .value(); return; case 'index': case 'type': case 'id': if (key && state[key] == null) { state[key] = val; } return; case 'source': key = '_source'; addToBody(); break; case 'sort': val = normalizeSortRequest(val, this.get('index')); addToBody(); break; default: addToBody(); } /** * Add the key and val to the body of the resuest */ function addToBody() { state.body = state.body || {}; // ignore if we already have a value if (state.body[key] == null) { if (key === 'query' && _.isString(val)) { val = { query_string: { query: val }}; } state.body[key] = val; } } }; return SearchSource; };