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

373 lines (323 loc) 10.7 kB
define(function (require) { return function IntervalHelperProvider(Private, timefilter, config) { var _ = require('lodash'); var moment = require('moment'); var dateMath = require('ui/utils/dateMath'); var parseInterval = require('ui/utils/parse_interval'); var calcAuto = Private(require('ui/time_buckets/calc_auto_interval')); var calcEsInterval = Private(require('ui/time_buckets/calc_es_interval')); var tzOffset = moment().format('Z'); function isValidMoment(m) { return m && ('isValid' in m) && m.isValid(); } /** * Helper class for wrapping the concept of an "Interval", * which describes a timespan that will seperate moments. * * @param {state} object - one of "" * @param {[type]} display [description] */ function TimeBuckets() { return TimeBuckets.__cached__(this); } /**** * PUBLIC API ****/ /** * Set the bounds that these buckets are expected to cover. * This is required to support interval "auto" as well * as interval scaling. * * @param {object} input - an object with properties min and max, * representing the edges for the time span * we should cover * * @returns {undefined} */ TimeBuckets.prototype.setBounds = function (input) { if (!input) return this.clearBounds(); var bounds; if (_.isPlainObject(input)) { // accept the response from timefilter.getActiveBounds() bounds = [input.min, input.max]; } else { bounds = _.isArray(input) ? input : []; } var moments = _(bounds) .map(_.ary(moment, 1)) .sortBy(Number); var valid = moments.size() === 2 && moments.every(isValidMoment); if (!valid) { this.clearBounds(); throw new Error('invalid bounds set: ' + input); } this._lb = moments.shift(); this._ub = moments.pop(); if (this.getDuration().asSeconds() < 0) { throw new TypeError('Intervals must be positive'); } }; /** * Clear the stored bounds * * @return {undefined} */ TimeBuckets.prototype.clearBounds = function () { this._lb = this._ub = null; }; /** * Check to see if we have received bounds yet * * @return {Boolean} */ TimeBuckets.prototype.hasBounds = function () { return isValidMoment(this._ub) && isValidMoment(this._lb); }; /** * Return the current bounds, if we have any. * * THIS DOES NOT CLONE THE BOUNDS, so editting them * may have unexpected side-effects. Always * call bounds.min.clone() before editting * * @return {object|undefined} - If bounds are not defined, this * returns undefined, else it returns the bounds * for these buckets. This object has two props, * min and max. Each property will be a moment() * object * */ TimeBuckets.prototype.getBounds = function () { if (!this.hasBounds()) return; return { min: this._lb, max: this._ub }; }; /** * Get a moment duration object representing * the distance between the bounds, if the bounds * are set. * * @return {moment.duration|undefined} */ TimeBuckets.prototype.getDuration = function () { if (!this.hasBounds()) return; return moment.duration(this._ub - this._lb, 'ms'); }; /** * Update the interval at which buckets should be * generated. * * Input can be one of the following: * - Any object from src/ui/agg_types/buckets/_interval_options.js * - "auto" * - Pass a valid moment unit * - a moment.duration object. * * @param {object|string|moment.duration} input - see desc */ TimeBuckets.prototype.setInterval = function (input) { var interval = input; // selection object -> val if (_.isObject(input)) { interval = input.val; } if (!interval || interval === 'auto') { this._i = 'auto'; return; } if (_.isString(interval)) { input = interval; interval = parseInterval(interval); if (+interval === 0) { interval = null; } } // if the value wasn't converted to a duration, and isn't // already a duration, we have a problem if (!moment.isDuration(interval)) { throw new TypeError('"' + input + '" is not a valid interval.'); } this._i = interval; }; /** * Get the interval for the buckets. If the * number of buckets created by the interval set * is larger than config:histogram:maxBars then the * interval will be scaled up. If the number of buckets * created is less than one, the interval is scaled back. * * The interval object returned is a moment.duration * object that has been decorated with the following * properties. * * interval.description: a text description of the interval. * designed to be used list "field per {{ desc }}". * - "minute" * - "10 days" * - "3 years" * * interval.expr: the elasticsearch expression that creates this * interval. If the interval does not properly form an elasticsearch * expression it will be forced into one. * * interval.scaled: the interval was adjusted to * accomidate the maxBars setting. * * interval.scale: the numer that y-values should be * multiplied by * * interval.scaleDescription: a description that reflects * the values which will be produced by using the * interval.scale. * * * @return {[type]} [description] */ TimeBuckets.prototype.getInterval = function () { var self = this; var duration = self.getDuration(); return decorateInterval(maybeScaleInterval(readInterval())); // either pull the interval from state or calculate the auto-interval function readInterval() { var interval = self._i; if (moment.isDuration(interval)) return interval; return calcAuto.near(config.get('histogram:barTarget'), duration); } // check to see if the interval should be scaled, and scale it if so function maybeScaleInterval(interval) { if (!self.hasBounds()) return interval; var maxLength = config.get('histogram:maxBars'); var approxLen = duration / interval; var scaled; if (approxLen > maxLength) { scaled = calcAuto.lessThan(maxLength, duration); } else { return interval; } if (+scaled === +interval) return interval; decorateInterval(interval); return _.assign(scaled, { preScaled: interval, scale: interval / scaled, scaled: true }); } // append some TimeBuckets specific props to the interval function decorateInterval(interval) { var esInterval = calcEsInterval(interval); interval.esValue = esInterval.value; interval.esUnit = esInterval.unit; interval.expression = esInterval.expression; interval.overflow = duration > interval ? moment.duration(interval - duration) : false; var prettyUnits = moment.normalizeUnits(esInterval.unit); if (esInterval.value === 1) { interval.description = prettyUnits; } else { interval.description = esInterval.value + ' ' + prettyUnits + 's'; } return interval; } }; /** * Get a date format string that will represent dates that * progress at our interval. * * Since our interval can be as small as 1ms, the default * date format is usually way too much. with `dateFormat:scaled` * users can modify how dates are formatted within series * produced by TimeBuckets * * @return {string} */ TimeBuckets.prototype.getScaledDateFormat = function () { var interval = this.getInterval(); var rules = config.get('dateFormat:scaled'); for (var i = rules.length - 1; i >= 0; i--) { var rule = rules[i]; if (!rule[0] || interval >= moment.duration(rule[0])) { return rule[1]; } } return config.get('dateFormat'); }; TimeBuckets.__cached__ = function (self) { var cache = {}; var sameMoment = same(moment.isMoment); var sameDuration = same(moment.isDuration); var desc = { __cached__: { value: self }, }; var breakers = { setBounds: 'bounds', clearBounds: 'bounds', setInterval: 'interval' }; var resources = { bounds: { setup: function () { return [self._lb, self._ub]; }, changes: function (prev) { return !sameMoment(prev[0], self._lb) || !sameMoment(prev[1], self._ub); } }, interval: { setup: function () { return self._i; }, changes: function (prev) { return !sameDuration(prev, this._i); } } }; function cachedGetter(prop) { return { value: function cachedGetter() { if (cache.hasOwnProperty(prop)) { return cache[prop]; } return cache[prop] = self[prop](); } }; } function cacheBreaker(prop) { var resource = resources[breakers[prop]]; var setup = resource.setup; var changes = resource.changes; var deps = resource.deps; var fn = self[prop]; return { value: function cacheBreaker(input) { var prev = setup.call(self); var ret = fn.apply(self, arguments); if (changes.call(self, prev)) { cache = {}; } return ret; } }; } function same(checkType) { return function (a, b) { if (a === b) return true; if (checkType(a) === checkType(b)) return +a === +b; return false; }; } _.forOwn(TimeBuckets.prototype, function (fn, prop) { if (prop[0] === '_') return; if (breakers.hasOwnProperty(prop)) { desc[prop] = cacheBreaker(prop); } else { desc[prop] = cachedGetter(prop); } }); return Object.create(self, desc); }; return TimeBuckets; }; });