UNPKG

@spalger/kibana

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

338 lines (291 loc) 10.1 kB
define(function (require) { var _ = require('lodash'); var angular = require('angular'); return function SourceAbstractFactory(Private, Promise, PromiseEmitter) { var requestQueue = Private(require('ui/courier/_request_queue')); var errorHandlers = Private(require('ui/courier/_error_handlers')); var courierFetch = Private(require('ui/courier/fetch/fetch')); function SourceAbstract(initialState, strategy) { var 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) { var 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) { var 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) { var self = this; return new PromiseEmitter(function (resolve, reject, defer) { 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) { var self = this; return new PromiseEmitter(function (resolve, reject, defer) { 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 () { var self = this; var req = _.first(self._myQueued()); 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._myQueued()); }; /** * Cancel all pending requests for this dataSource * @return {undefined} */ SourceAbstract.prototype.cancelQueued = function () { _.invoke(this._myQueued(), 'abort'); }; /** * Completely destroy the SearchSource. * @return {undefined} */ SourceAbstract.prototype.destroy = function () { this.cancelQueued(); }; /***** * PRIVATE API *****/ SourceAbstract.prototype._myQueued = function () { var reqs = requestQueue.get(this._fetchStrategy); return _.where(reqs, { 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 () { var type = this._getType(); // the merged state of this dataSource and it's ancestors var flatState = {}; // function used to write each property from each state object in the chain to flat state var root = this; // start the chain at this source var 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); }); } var prom = root._mergeProp(flatState, value, key); return Promise.is(prom) ? prom : null; })) .then(function () { // move to this sources parent var 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 var decorateQuery = Private(require('ui/courier/data_source/_decorate_query')); flatState.body = flatState.body || {}; // defaults for the query if (!flatState.body.query) { flatState.body.query = { 'match_all': {} }; } if (flatState.body.size === 0) { flatState.search_type = 'count'; } else { var computedFields = flatState.index.getComputedFields(); flatState.body.fields = computedFields.fields; flatState.body.script_fields = flatState.body.script_fields || {}; flatState.body.fielddata_fields = flatState.body.fielddata_fields || []; _.extend(flatState.body.script_fields, computedFields.scriptFields); flatState.body.fielddata_fields = _.union(flatState.body.fielddata_fields, computedFields.fielddataFields); } 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} */ var filterNegate = function (reverse) { return function (filter) { if (_.isUndefined(filter.meta) || _.isUndefined(filter.meta.negate)) return !reverse; return filter.meta && filter.meta.negate === reverse; }; }; /** * Clean out any invalid attributes from the filters * @param {object} filter The filter to clean * @returns {object} */ var 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 = { filtered: { query: flatState.body.query, filter: { bool: { must: _(flatState.filters).filter(filterNegate(false)).map(cleanFilter).value(), must_not: _(flatState.filters).filter(filterNegate(true)).map(cleanFilter).value() } } } }; } delete flatState.filters; } } return flatState; }); }; return SourceAbstract; }; });