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

400 lines (341 loc) 11.5 kB
import _ from 'lodash'; import angular from 'angular'; import 'ui/promises'; import RequestQueueProvider from '../_request_queue'; import ErrorHandlersProvider from '../_error_handlers'; import FetchProvider from '../fetch'; import DecorateQueryProvider from './_decorate_query'; import FieldWildcardProvider from '../../field_wildcard'; export default function SourceAbstractFactory(Private, Promise, PromiseEmitter) { let requestQueue = Private(RequestQueueProvider); let errorHandlers = Private(ErrorHandlersProvider); let courierFetch = Private(FetchProvider); let { fieldWildcardFilter } = Private(FieldWildcardProvider); function SourceAbstract(initialState, strategy) { let self = this; self._instanceid = _.uniqueId('data_source'); self._state = (function () { // state can be serialized as JSON, and passed back in to restore if (initialState) { if (typeof initialState === 'string') { return JSON.parse(initialState); } else { return _.cloneDeep(initialState); } } else { return {}; } }()); // set internal state values self._methods.forEach(function (name) { self[name] = function (val) { if (val == null) { delete self._state[name]; } else { self._state[name] = val; } return self; }; }); self.history = []; self._fetchStrategy = strategy; } /***** * PUBLIC API *****/ /** * Get values from the state * @param {string} name - The name of the property desired * @return {any} - the value found */ SourceAbstract.prototype.get = function (name) { let self = this; while (self) { if (self._state[name] !== void 0) return self._state[name]; self = self.getParent(); } }; /** * Get the value from our own state, don't traverse up the chain * @param {string} name - The name of the property desired * @return {any} - the value found */ SourceAbstract.prototype.getOwn = function (name) { if (this._state[name] !== void 0) return this._state[name]; }; /** * Change the entire state of a SourceAbstract * @param {object|string} state - The SourceAbstract's new state, or a * string of the state value to set */ SourceAbstract.prototype.set = function (state, val) { let self = this; if (typeof state === 'string') { // the getter and setter methods check for undefined explicitly // to identify getters and null to identify deletion if (val === undefined) { val = null; } self[state](val); } else { self._state = state; } return self; }; /** * Create a new dataSource object of the same type * as this, which inherits this dataSource's properties * @return {SourceAbstract} */ SourceAbstract.prototype.extend = function () { return (new this.Class()).inherits(this); }; /** * return a simple, encodable object representing the state of the SourceAbstract * @return {[type]} [description] */ SourceAbstract.prototype.toJSON = function () { return _.clone(this._state); }; /** * Create a string representation of the object * @return {[type]} [description] */ SourceAbstract.prototype.toString = function () { return angular.toJson(this.toJSON()); }; /** * Put a request in to the courier that this Source should * be fetched on the next run of the courier * @return {Promise} */ SourceAbstract.prototype.onResults = function (handler) { let self = this; return new PromiseEmitter(function (resolve, reject) { const defer = Promise.defer(); defer.promise.then(resolve, reject); self._createRequest(defer); }, handler); }; /** * Noop */ SourceAbstract.prototype.getParent = function () { return this._parent; }; /** * similar to onResults, but allows a seperate loopy code path * for error handling. * * @return {Promise} */ SourceAbstract.prototype.onError = function (handler) { let self = this; return new PromiseEmitter(function (resolve, reject) { const defer = Promise.defer(); defer.promise.then(resolve, reject); errorHandlers.push({ source: self, defer: defer }); }, handler); }; /** * Fetch just this source ASAP * * ONLY USE IF YOU WILL BE USING THE RESULTS * provided by the returned promise, otherwise * call #fetchQueued() * * @async */ SourceAbstract.prototype.fetch = function () { let self = this; let req = _.first(self._myStartableQueued()); if (!req) { req = self._createRequest(); } courierFetch.these([req]); return req.defer.promise; }; /** * Fetch all pending requests for this source ASAP * @async */ SourceAbstract.prototype.fetchQueued = function () { return courierFetch.these(this._myStartableQueued()); }; /** * Cancel all pending requests for this dataSource * @return {undefined} */ SourceAbstract.prototype.cancelQueued = function () { requestQueue .get(this._fetchStrategy) .filter(req => req.source === this) .forEach(req => req.abort()); }; /** * Completely destroy the SearchSource. * @return {undefined} */ SourceAbstract.prototype.destroy = function () { this.cancelQueued(); }; /***** * PRIVATE API *****/ SourceAbstract.prototype._myStartableQueued = function () { return requestQueue .getStartable(this._fetchStrategy) .filter(req => req.source === this); }; SourceAbstract.prototype._createRequest = function () { throw new Error('_createRequest must be implemented by subclass'); }; /** * Walk the inheritance chain of a source and return it's * flat representaion (taking into account merging rules) * @returns {Promise} * @resolved {Object|null} - the flat state of the SourceAbstract */ SourceAbstract.prototype._flatten = function () { let type = this._getType(); // the merged state of this dataSource and it's ancestors let flatState = {}; // function used to write each property from each state object in the chain to flat state let root = this; // start the chain at this source let current = this; // call the ittr and return it's promise return (function ittr() { // itterate the _state object (not array) and // pass each key:value pair to source._mergeProp. if _mergeProp // returns a promise, then wait for it to complete and call _mergeProp again return Promise.all(_.map(current._state, function ittr(value, key) { if (Promise.is(value)) { return value.then(function (value) { return ittr(value, key); }); } let prom = root._mergeProp(flatState, value, key); return Promise.is(prom) ? prom : null; })) .then(function () { // move to this sources parent let parent = current.getParent(); // keep calling until we reach the top parent if (parent) { current = parent; return ittr(); } }); }()) .then(function () { if (type === 'search') { // This is down here to prevent the circular dependency let decorateQuery = Private(DecorateQueryProvider); flatState.body = flatState.body || {}; // defaults for the query if (!flatState.body.query) { flatState.body.query = { 'match_all': {} }; } if (flatState.body.size > 0) { let computedFields = flatState.index.getComputedFields(); flatState.body.stored_fields = computedFields.storedFields; flatState.body.script_fields = flatState.body.script_fields || {}; flatState.body.docvalue_fields = flatState.body.docvalue_fields || []; _.extend(flatState.body.script_fields, computedFields.scriptFields); flatState.body.docvalue_fields = _.union(flatState.body.docvalue_fields, computedFields.docvalueFields); if (flatState.body._source) { // exclude source fields for this index pattern specified by the user const filter = fieldWildcardFilter(flatState.body._source.excludes); flatState.body.docvalue_fields = flatState.body.docvalue_fields.filter(filter); } } decorateQuery(flatState.body.query); /** * Create a filter that can be reversed for filters with negate set * @param {boolean} reverse This will reverse the filter. If true then * anything where negate is set will come * through otherwise it will filter out * @returns {function} */ let filterNegate = function (reverse) { return function (filter) { if (_.isUndefined(filter.meta) || _.isUndefined(filter.meta.negate)) return !reverse; return filter.meta && filter.meta.negate === reverse; }; }; /** * Translate a filter into a query to support es 3+ * @param {Object} filter - The filter to translate * @return {Object} the query version of that filter */ let translateToQuery = function (filter) { if (!filter) return; if (filter.query) { return filter.query; } return filter; }; /** * Clean out any invalid attributes from the filters * @param {object} filter The filter to clean * @returns {object} */ let cleanFilter = function (filter) { return _.omit(filter, ['meta']); }; // switch to filtered query if there are filters if (flatState.filters) { if (flatState.filters.length) { _.each(flatState.filters, function (filter) { if (filter.query) { decorateQuery(filter.query); } }); flatState.body.query = { bool: { must: ( [flatState.body.query].concat( (flatState.filters || []) .filter(filterNegate(false)) .map(translateToQuery) .map(cleanFilter) ) ), must_not: ( (flatState.filters || []) .filter(filterNegate(true)) .map(translateToQuery) .map(cleanFilter) ) } }; } delete flatState.filters; } // re-write filters within filter aggregations (function recurse(aggBranch) { if (!aggBranch) return; Object.keys(aggBranch).forEach(function (id) { const agg = aggBranch[id]; if (agg.filters) { // translate filters aggregations const filters = agg.filters.filters; Object.keys(filters).forEach(function (filterId) { filters[filterId] = translateToQuery(filters[filterId]); }); } recurse(agg.aggs || agg.aggregations); }); }(flatState.body.aggs || flatState.body.aggregations)); } return flatState; }); }; return SourceAbstract; };