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

283 lines (227 loc) 9.58 kB
define(function (require) { return function TabbedAggResponseWriterProvider(Private) { var _ = require('lodash'); var Table = Private(require('ui/agg_response/tabify/_table')); var TableGroup = Private(require('ui/agg_response/tabify/_table_group')); var getColumns = Private(require('ui/agg_response/tabify/_get_columns')); var AggConfigResult = require('ui/Vis/AggConfigResult'); _.class(SplitAcr).inherits(AggConfigResult); function SplitAcr(agg, parent, key) { SplitAcr.Super.call(this, agg, parent, key, key); } /** * Writer class that collects information about an aggregation response and * produces a table, or a series of tables. * * @param {Vis} vis - the vis object to which the aggregation response correlates */ function TabbedAggResponseWriter(vis, opts) { this.vis = vis; this.opts = opts || {}; this.rowBuffer = []; var visIsHier = vis.isHierarchical(); // do the options allow for splitting? we will only split if true and // tabify calls the split method. this.canSplit = this.opts.canSplit !== false; // should we allow partial rows to be included in the tables? if a // partial row is found, it is filled with empty strings '' this.partialRows = this.opts.partialRows == null ? visIsHier : this.opts.partialRows; // if true, we will not place metric columns after every bucket // even if the vis is hierarchical. if false, and the vis is // hierarchical, then we will display metric columns after // every bucket col this.minimalColumns = visIsHier ? !!this.opts.minimalColumns : true; // true if we can expect metrics to have been calculated // for every bucket this.metricsForAllBuckets = visIsHier; // if true, values will be wrapped in aggConfigResult objects which link them // to their aggConfig and enable the filterbar and tooltip formatters this.asAggConfigResults = !!this.opts.asAggConfigResults; this.columns = getColumns(vis, this.minimalColumns); this.aggStack = _.pluck(this.columns, 'aggConfig'); this.root = new TableGroup(); this.acrStack = []; this.splitStack = [this.root]; } /** * Create a Table of TableGroup object, link it to it's parent (if any), and determine if * it's the root * * @param {boolean} group - is this a TableGroup or just a normal Table * @param {AggConfig} agg - the aggregation that create this table, only applies to groups * @param {any} key - the bucketKey that this table relates to * @return {Table/TableGroup} table - the created table */ TabbedAggResponseWriter.prototype._table = function (group, agg, key) { var Class = (group) ? TableGroup : Table; var table = new Class(); var parent = this.splitStack[0]; if (group) { table.aggConfig = agg; table.key = key; table.title = (table.fieldFormatter()(key)) + ': ' + agg.makeLabel(); } // link the parent and child table.$parent = parent; parent.tables.push(table); return table; }; /** * Enter into a split table, called for each bucket of a splitting agg. The new table * is either created or located using the agg and key arguments, and then the block is * executed with the table as it's this context. Within this function, you should * walk into the remaining branches and end up writing some rows to the table. * * @param {aggConfig} agg - the aggConfig that created this split * @param {Buckets} buckets - the buckets produces by the agg * @param {function} block - a function to execute for each sub bucket */ TabbedAggResponseWriter.prototype.split = function (agg, buckets, block) { var self = this; if (!self.canSplit) { throw new Error('attempted to split when splitting is disabled'); } self._removeAggFromColumns(agg); buckets.forEach(function (bucket, key) { // find the existing split that we should extend var tableGroup = _.find(self.splitStack[0].tables, { aggConfig: agg, key: key }); // create the split if it doesn't exist yet if (!tableGroup) tableGroup = self._table(true, agg, key); var splitAcr = false; if (self.asAggConfigResults) { splitAcr = self._injectParentSplit(agg, key); } // push the split onto the stack so that it will receive written tables self.splitStack.unshift(tableGroup); // call the block if (_.isFunction(block)) block.call(self, bucket, key); // remove the split from the stack self.splitStack.shift(); splitAcr && _.pull(self.acrStack, splitAcr); }); }; TabbedAggResponseWriter.prototype._removeAggFromColumns = function (agg) { var i = _.findIndex(this.columns, function (col) { return col.aggConfig === agg; }); // we must have already removed this column if (i === -1) return; this.columns.splice(i, 1); if (this.minimalColumns) return; // hierarchical vis creats additional columns for each bucket // we will remove those too var mCol = this.columns.splice(i, 1).pop(); var mI = _.findIndex(this.aggStack, function (agg) { return agg === mCol.aggConfig; }); if (mI > -1) this.aggStack.splice(mI, 1); }; /** * When a split is found while building the aggConfigResult tree, we * want to push the split into the tree at another point. Since each * branch in the tree is a double-linked list we need do some special * shit to pull this off. * * @private * @param {AggConfig} - The agg which produced the split bucket * @param {any} - The value which identifies the bucket * @return {SplitAcr} - the AggConfigResult created for the split bucket */ TabbedAggResponseWriter.prototype._injectParentSplit = function (agg, key) { var oldList = this.acrStack; var newList = this.acrStack = []; // walk from right to left through the old stack // and move things to the new stack var injected = false; if (!oldList.length) { injected = new SplitAcr(agg, null, key); newList.unshift(injected); return injected; } // walk from right to left, emptying the previous list while (oldList.length) { var acr = oldList.pop(); // ignore other splits if (acr instanceof SplitAcr) { newList.unshift(acr); continue; } // inject the split if (!injected) { injected = new SplitAcr(agg, newList[0], key); newList.unshift(injected); } var newAcr = new AggConfigResult(acr.aggConfig, newList[0], acr.value, acr.aggConfig.getKey(acr)); newList.unshift(newAcr); // and replace the acr in the row buffer if its there var rowI = this.rowBuffer.indexOf(acr); if (rowI > -1) { this.rowBuffer[rowI] = newAcr; } } return injected; }; /** * Push a value into the row, then run a block. Once the block is * complete the value is pulled from the stack. * * @param {any} value - the value that should be added to the row * @param {function} block - the function to run while this value is in the row * @return {any} - the value that was added */ TabbedAggResponseWriter.prototype.cell = function (agg, value, block) { if (this.asAggConfigResults) { value = new AggConfigResult(agg, this.acrStack[0], value, value); } var staskResult = this.asAggConfigResults && value.type === 'bucket'; this.rowBuffer.push(value); if (staskResult) this.acrStack.unshift(value); if (_.isFunction(block)) block.call(this); this.rowBuffer.pop(value); if (staskResult) this.acrStack.shift(); return value; }; /** * Create a new row by reading the row buffer. This will do nothing if * the row is incomplete and the vis this data came from is NOT flagged as * hierarchical. * * @param {array} [buffer] - optional buffer to use in place of the stored rowBuffer * @return {undefined} */ TabbedAggResponseWriter.prototype.row = function (buffer) { var cells = buffer || this.rowBuffer.slice(0); if (!this.partialRows && cells.length < this.columns.length) { return; } var split = this.splitStack[0]; var table = split.tables[0] || this._table(false); while (cells.length < this.columns.length) cells.push(''); table.rows.push(cells); return table; }; /** * Get the actual response * * @return {object} - the final table-tree */ TabbedAggResponseWriter.prototype.response = function () { var columns = this.columns; // give the columns some metadata columns.map(function (col) { col.title = col.aggConfig.makeLabel(); }); // walk the tree and write the columns to each table (function step(table, group) { if (table.tables) table.tables.forEach(step); else table.columns = columns.slice(0); }(this.root)); if (this.canSplit) return this.root; var table = this.root.tables[0]; if (!table) return; delete table.$parent; return table; }; return TabbedAggResponseWriter; }; });