From b9ed2e53fb5dc5e97af6649ec855f8e6cb4b15a4 Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Wed, 22 Oct 2014 03:43:06 +0300 Subject: [PATCH 01/45] almost there --- .../apps/discover/controllers/discover.js | 4 +- src/kibana/components/vislib/lib/data.js | 2 + src/kibana/components/vislib/lib/dispatch.js | 157 +++++++++--------- .../components/vislib/lib/handler/handler.js | 43 ++--- src/kibana/components/vislib/vis.js | 51 ++++++ .../vislib/visualizations/_chart.js | 2 +- .../vislib/visualizations/column_chart.js | 102 ++++-------- .../vislib/visualizations/pie_chart.js | 2 +- 8 files changed, 185 insertions(+), 178 deletions(-) diff --git a/src/kibana/apps/discover/controllers/discover.js b/src/kibana/apps/discover/controllers/discover.js index a21d700cb4d8f6..a754d0e8233126 100644 --- a/src/kibana/apps/discover/controllers/discover.js +++ b/src/kibana/apps/discover/controllers/discover.js @@ -639,8 +639,8 @@ define(function (require) { type: 'histogram', vislibParams: { addLegend: false, - addEvents: true, - addBrushing: true, +// addEvents: true, +// addBrushing: true, }, listeners: { click: function (e) { diff --git a/src/kibana/components/vislib/lib/data.js b/src/kibana/components/vislib/lib/data.js index 46371139e2f15a..6fa0bedbd09b5d 100644 --- a/src/kibana/components/vislib/lib/data.js +++ b/src/kibana/components/vislib/lib/data.js @@ -22,6 +22,8 @@ define(function (require) { } this.data = data; + this.labels = getLabels(data); + this.color = color(this.labels); this._normalizeOrdered(); this._attr = attr; diff --git a/src/kibana/components/vislib/lib/dispatch.js b/src/kibana/components/vislib/lib/dispatch.js index 3fb2599b49fa6c..1972d8479a3d3f 100644 --- a/src/kibana/components/vislib/lib/dispatch.js +++ b/src/kibana/components/vislib/lib/dispatch.js @@ -8,24 +8,26 @@ define(function (require) { * @class Dispatch * @constructor * @param handler {Object} Reference to Handler Class Object - * @param chartData {Object} Elasticsearch data object */ - function Dispatch(handler, chartData) { + function Dispatch(handler) { if (!(this instanceof Dispatch)) { - return new Dispatch(handler, chartData); + return new Dispatch(handler); } - var type = handler._attr.type; + + var self = this; this.handler = handler; - this.chartData = chartData; - this.color = type === 'pie' ? handler.data.getPieColorFunc() : handler.data.getColorFunc(); - this._attr = _.defaults(handler._attr || {}, { - yValue: function (d) { - return d.y; - }, - dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout') - }); + this.dispatch = d3.dispatch('brush', 'click', 'hover'); + this.listeners = Object.keys(this.dispatch); + + // Add eventResponse data to be returned on dispatch + this.listeners.forEach(function (listener) { + this.dispatch.on(listener, function (d, i) { + self.eventResponse(d, i); + d3.event.stopPropagation(); + }); + }, this); } /** @@ -37,56 +39,51 @@ define(function (require) { * e: (d3.event|*), handler: (Object|*)}} Event response object */ Dispatch.prototype.eventResponse = function (d, i) { - var label = d.label; - var getYValue = this._attr.yValue; - var color = this.color; - var chartData = this.chartData; - var attr = this._attr; + var data = d3.event.target.nearestViewportElement.__data__; + var isSeries = !!(data.series); + var isSlices = !!(data.slices); + var series = isSeries ? data.series : undefined; + var slices = isSlices ? data.slices : undefined; var handler = this.handler; + var color = handler.data.color; return { - value: getYValue(d, i), + value: d.y, point: d, - label: label, - color: color(label), + label: d.label, + color: color(d.label), pointIndex: i, - series: chartData.series, - config: attr, - data: chartData, + series: series, + slices: slices, + config: handler._attr, + data: data, e: d3.event, handler: handler }; }; /** - * Response to click and hover events for pie charts + * Mouse over Behavior * - * @param d {Object} Data point - * @param i {Number} Index number of data point - * @returns {{value: (d.value|*), point: *, label: (d.name|*), color: *, pointIndex: *, children: *, parent: *, - * appConfig: *, config: *, data: (Object|*), e: (d3.event|*), handler: (Object|*)}} Event response object + * @method mouseOverBar + * @param target {Object} Reference to this object + * @returns {D3.Selection} this object with '.hover' class true */ - Dispatch.prototype.pieResponse = function (d, i) { - var label = d.name; - var color = this.color; - var chartData = this.chartData; - var attr = this._attr; - var handler = this.handler; + Dispatch.prototype.onMouseOver = function () { + return d3.select(this).classed('hover', true) + .style('stroke', '#333') + .style('cursor', 'pointer'); + }; - return { - value: d.value, - point: d, - label: label, - color: color(label), - pointIndex: i, - children: d.children ? d.children : undefined, - parent: d.parent ? d.parent : undefined, - appConfig: d.appConfig, - config: attr, - data: chartData, - e: d3.event, - handler: handler - }; + /** + * Mouse out Behavior + * + * @method mouseOutBar + * @param target {Object} Reference to this object + * @returns {D3.Selection} this object with '.hover' class false + */ + Dispatch.prototype.onMouseOut = function () { + return d3.select(this).classed('hover', false).style('stroke', null); }; /** @@ -96,38 +93,38 @@ define(function (require) { * @param svg {HTMLElement} Reference to SVG * @returns {*} Returns a D3 brush function and a SVG with a brush group attached */ - Dispatch.prototype.addBrush = function (xScale, svg) { - var dispatch = this._attr.dispatch; - var attr = this._attr; - var chartData = this.chartData; - var isBrush = this._attr.addBrushing; - var height = this._attr.height; - var margin = this._attr.margin; - - // Brush scale - var brush = d3.svg.brush() - .x(xScale) - .on('brushend', function brushEnd() { - // response returned on brush - return dispatch.brush({ - range: brush.extent(), - config: attr, - e: d3.event, - data: chartData - }); - }); - - // if `addBrushing` is true, add brush canvas - if (isBrush) { - svg.append('g') - .attr('class', 'brush') - .call(brush) - .selectAll('rect') - .attr('height', height - margin.top - margin.bottom); - } - - return brush; - }; +// Dispatch.prototype.addBrush = function (xScale, svg) { +// var dispatch = this._attr.dispatch; +// var attr = this._attr; +// var chartData = this.chartData; +// var isBrush = this._attr.addBrushing; +// var height = this._attr.height; +// var margin = this._attr.margin; +// +// // Brush scale +// var brush = d3.svg.brush() +// .x(xScale) +// .on('brushend', function brushEnd() { +// // response returned on brush +// return dispatch.brush({ +// range: brush.extent(), +// config: attr, +// e: d3.event, +// data: chartData +// }); +// }); +// +// // if `addBrushing` is true, add brush canvas +// if (isBrush) { +// svg.append('g') +// .attr('class', 'brush') +// .call(brush) +// .selectAll('rect') +// .attr('height', height - margin.top - margin.bottom); +// } +// +// return brush; +// }; return Dispatch; }; diff --git a/src/kibana/components/vislib/lib/handler/handler.js b/src/kibana/components/vislib/lib/handler/handler.js index 0c055a2544ef9e..075af9f5a825a1 100644 --- a/src/kibana/components/vislib/lib/handler/handler.js +++ b/src/kibana/components/vislib/lib/handler/handler.js @@ -23,6 +23,8 @@ define(function (require) { this.vis = vis; this.el = vis.el; this.ChartClass = vis.ChartClass; + this.charts = []; + this._attr = _.defaults(vis._attr || {}, { 'margin' : { top: 10, right: 3, bottom: 5, left: 3 } }); @@ -65,28 +67,29 @@ define(function (require) { }); d3.select(this.el) - .selectAll('.chart') - .each(function (chartData) { - var chart = new self.ChartClass(self, this, chartData); - - d3.rebind(chart, chart._attr.dispatch, 'on'); - - // Bubble events up to the Vis Class and Events Class - chart.on('click', function (e) { - self.vis.emit('click', e); - }); - - chart.on('hover', function (e) { - self.vis.emit('hover', e); - }); - - chart.on('brush', function (e) { - self.vis.emit('brush', e); + .selectAll('.chart') + .each(function (chartData) { + var chart = new self.ChartClass(self, this, chartData); + var listeners = self.vis._listeners; + var keys = Object.keys(listeners); + + // Copy dispatch.on methods to chart object + d3.rebind(chart, chart.events.dispatch, 'on'); + + // if there are listeners, dispatch listeners to chart + if (keys.length) { + keys.forEach(function (key) { + listeners[key].forEach(function (obj, i) { + chart.on(key + '.' + i, function (e) { + obj.handler.call(this, arguments); + }); + }); }); + } - charts.push(chart); - chart.render(); - }); + charts.push(chart); + chart.render(); + }); }; /** diff --git a/src/kibana/components/vislib/vis.js b/src/kibana/components/vislib/vis.js index 2ac42bf35e28e7..bad4aec241a194 100644 --- a/src/kibana/components/vislib/vis.js +++ b/src/kibana/components/vislib/vis.js @@ -114,6 +114,57 @@ define(function (require) { return this._attr[name]; }; + Vis.prototype.on = function (event, handler) { + + // Adds handler to _listeners[listener] array + var ret = Events.prototype.on.call(this, event, handler); + var handlerIndex; + + // Check if the charts array is available + if (this.handler && this.handler.charts) { + handlerIndex = this._listeners[event].length - 1; + + // Dispatch listener to chart + this.handler.charts.forEach(function (chart) { + chart.on(event + '.' + handlerIndex, function (e) { + handler.call(this, arguments); + }); + }); + } + + return ret; + }; + + /* + * To turn off event listeners, need to pass null as handler to + * d3.dispatch. In addition, we need to track down the particular handler + * from which to turn off. + */ + Vis.prototype.off = function (event, handler) { + var ret = Events.prototype.off.call(this, event, handler); + var handlerIndex; + + if (this._listeners[event] && this.handler.charts) { + + // if no handler, set all listener handlers to null + if (!handler) { + this.handler.charts.forEach(function (chart) { + chart.on(event, null); + }); + } else { + + // if handler, get index of handler and set to null. + handlerIndex = _.findIndex(this._listeners[event], handler); + + this.handler.charts.forEach(function (chart) { + chart.on(event + '.' + handlerIndex, null); + }); + } + } + + return ret; + }; + return Vis; }; }); \ No newline at end of file diff --git a/src/kibana/components/vislib/visualizations/_chart.js b/src/kibana/components/vislib/visualizations/_chart.js index aa093ffd768830..445ba9ce1258fb 100644 --- a/src/kibana/components/vislib/visualizations/_chart.js +++ b/src/kibana/components/vislib/visualizations/_chart.js @@ -24,7 +24,7 @@ define(function (require) { this.chartEl = el; this.chartData = chartData; - var events = this.events = new Dispatch(handler, chartData); + var events = this.events = new Dispatch(handler); if (handler._attr.addTooltip) { var $el = this.handler.el; diff --git a/src/kibana/components/vislib/visualizations/column_chart.js b/src/kibana/components/vislib/visualizations/column_chart.js index 727f0523417e7b..7aab1025192dd6 100644 --- a/src/kibana/components/vislib/visualizations/column_chart.js +++ b/src/kibana/components/vislib/visualizations/column_chart.js @@ -23,17 +23,6 @@ define(function (require) { return new ColumnChart(handler, chartEl, chartData); } - // TODO: refactor - var raw; - var fieldIndex; - - if (handler.data.data.raw) { - raw = handler.data.data.raw.columns; - fieldIndex = _.findIndex(raw, {'categoryName': 'group'}); - } - - this.fieldFormatter = (raw && raw[fieldIndex]) ? raw[fieldIndex].field.format.convert : function (d) { return d; }; - ColumnChart.Super.apply(this, arguments); // Column chart specific attributes @@ -105,10 +94,10 @@ define(function (require) { .enter() .append('rect') .attr('class', function (d) { - return self.colorToClass(color(self.fieldFormatter(d.label))); + return self.colorToClass(color(d.label)); }) .attr('fill', function (d) { - return color(self.fieldFormatter(d.label)); + return color(d.label); }); bars @@ -150,72 +139,42 @@ define(function (require) { * @param brush {Function} D3 brush function * @returns {HTMLElement} rect with event listeners attached */ - ColumnChart.prototype.addBarEvents = function (svg, bars, brush) { - var self = this; + ColumnChart.prototype.addBarEvents = function (svg, bars) { var events = this.events; - var dispatch = this.events._attr.dispatch; - var addBrush = this._attr.addBrushing; - var xScale = this.handler.xAxis.xScale; + var dispatch = this.events.dispatch; +// var addBrush = this._attr.addBrushing; +// var xScale = this.handler.xAxis.xScale; +// var height = this._attr.height; +// var margin = this._attr.margin; bars .on('mouseover.bar', function (d, i) { - self.mouseOverBar(this); - dispatch.hover(events.eventResponse(d, i)); - d3.event.stopPropagation(); - }) - .on('mousedown.bar', function () { - if (addBrush) { - var bar = d3.select(this); - var startX = d3.mouse(svg.node()); - var startXInv = xScale.invert(startX[0]); - - // Reset the brush value - brush.extent([startXInv, startXInv]); - - // Magic! - // Need to call brush on svg to see brush when brushing - // while on top of bars. - // Need to call brush on bar to allow the click event to be registered - svg.call(brush); - bar.call(brush); - } + events.onMouseOver.call(this, arguments); + dispatch.hover.call(this, d, i); }) +// .on('mousedown.bar', function () { +// var bar = d3.select(this); +// var startX = d3.mouse(svg.node()); +// var startXInv = xScale.invert(startX[0]); +// +// // Reset the brush value +// brush.extent([startXInv, startXInv]); +// +// // Magic! +// // Need to call brush on svg to see brush when brushing +// // while on top of bars. +// // Need to call brush on bar to allow the click event to be registered +// svg.call(brush); +// bar.call(brush); +// }) .on('click.bar', function (d, i) { - dispatch.click(events.eventResponse(d, i)); - d3.event.stopPropagation(); + dispatch.click.call(this, events.eventResponse.call(this, d, i)); }) .on('mouseout.bar', function () { - self.mouseOutBar(this); + events.onMouseOut.call(this, arguments); }); }; - /** - * Mouseover Behavior - * - * @method mouseOverBar - * @param that {Object} Reference to this object - * @returns {D3.Selection} this object with '.hover' class true - */ - ColumnChart.prototype.mouseOverBar = function (that) { - return d3.select(that) - .classed('hover', true) - .style('stroke', '#333') - .style('cursor', 'pointer'); - }; - - /** - * Mouseout Behavior - * - * @method mouseOutBar - * @param that {Object} Reference to this object - * @returns {D3.Selection} this object with '.hover' class false - */ - ColumnChart.prototype.mouseOutBar = function (that) { - return d3.select(that) - .classed('hover', false) - .style('stroke', null); - }; - /** * Renders d3 visualization * @@ -231,13 +190,11 @@ define(function (require) { var elHeight = this._attr.height = $elem.height(); var minWidth = 20; var minHeight = 20; - var isEvents = this._attr.addEvents; var div; var svg; var width; var height; var layers; - var brush; var bars; return function (selection) { @@ -259,12 +216,9 @@ define(function (require) { .append('g') .attr('transform', 'translate(0,' + margin.top + ')'); - brush = self.events.addBrush(xScale, svg); bars = self.addBars(svg, layers); - if (isEvents) { - self.addBarEvents(svg, bars, brush); - } + self.addBarEvents(svg, bars); var line = svg.append('line') .attr('x1', 0) diff --git a/src/kibana/components/vislib/visualizations/pie_chart.js b/src/kibana/components/vislib/visualizations/pie_chart.js index 85ee0b5094ace4..c60374c6047ed5 100644 --- a/src/kibana/components/vislib/visualizations/pie_chart.js +++ b/src/kibana/components/vislib/visualizations/pie_chart.js @@ -29,7 +29,7 @@ define(function (require) { this._attr = _.defaults(handler._attr || {}, { isDonut: handler._attr.isDonut || false, getSize: function (d) { return d.size; }, - dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout') + dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseover', 'mouseout') }); } From 8eb169687585b642af86e97e26c8bad4d10a4c7a Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Wed, 22 Oct 2014 21:19:28 +0300 Subject: [PATCH 02/45] troubleshooting --- .../apps/discover/controllers/discover.js | 2 - src/kibana/components/vislib/lib/dispatch.js | 21 +++----- .../components/vislib/lib/handler/handler.js | 6 ++- src/kibana/components/vislib/lib/tooltip.js | 6 +-- src/kibana/components/vislib/vis.js | 52 +++++++++++++------ .../vislib/visualizations/column_chart.js | 6 ++- 6 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/kibana/apps/discover/controllers/discover.js b/src/kibana/apps/discover/controllers/discover.js index a754d0e8233126..ba994a5174baa1 100644 --- a/src/kibana/apps/discover/controllers/discover.js +++ b/src/kibana/apps/discover/controllers/discover.js @@ -639,8 +639,6 @@ define(function (require) { type: 'histogram', vislibParams: { addLegend: false, -// addEvents: true, -// addBrushing: true, }, listeners: { click: function (e) { diff --git a/src/kibana/components/vislib/lib/dispatch.js b/src/kibana/components/vislib/lib/dispatch.js index 1972d8479a3d3f..a0994cf35c927f 100644 --- a/src/kibana/components/vislib/lib/dispatch.js +++ b/src/kibana/components/vislib/lib/dispatch.js @@ -15,19 +15,9 @@ define(function (require) { return new Dispatch(handler); } - var self = this; - this.handler = handler; - this.dispatch = d3.dispatch('brush', 'click', 'hover'); - this.listeners = Object.keys(this.dispatch); - - // Add eventResponse data to be returned on dispatch - this.listeners.forEach(function (listener) { - this.dispatch.on(listener, function (d, i) { - self.eventResponse(d, i); - d3.event.stopPropagation(); - }); - }, this); + this.dispatch = d3.dispatch('brush', 'click', 'hover', 'mouseup', + 'mousedown', 'mouseover'); } /** @@ -35,7 +25,8 @@ define(function (require) { * * @param d {Object} Data point * @param i {Number} Index number of data point - * @returns {{value: *, point: *, label: *, color: *, pointIndex: *, series: *, config: *, data: (Object|*), + * @returns {{value: *, point: *, label: *, color: *, pointIndex: *, + * series: *, config: *, data: (Object|*), * e: (d3.event|*), handler: (Object|*)}} Event response object */ Dispatch.prototype.eventResponse = function (d, i) { @@ -71,8 +62,8 @@ define(function (require) { */ Dispatch.prototype.onMouseOver = function () { return d3.select(this).classed('hover', true) - .style('stroke', '#333') - .style('cursor', 'pointer'); + .style('stroke', '#333') + .style('cursor', 'pointer'); }; /** diff --git a/src/kibana/components/vislib/lib/handler/handler.js b/src/kibana/components/vislib/lib/handler/handler.js index 075af9f5a825a1..db1786a51ae73c 100644 --- a/src/kibana/components/vislib/lib/handler/handler.js +++ b/src/kibana/components/vislib/lib/handler/handler.js @@ -76,12 +76,16 @@ define(function (require) { // Copy dispatch.on methods to chart object d3.rebind(chart, chart.events.dispatch, 'on'); + // if listeners.length, chart.on(event,) + // Don't bind handlers individually, use emit instead + // + // if there are listeners, dispatch listeners to chart if (keys.length) { keys.forEach(function (key) { listeners[key].forEach(function (obj, i) { chart.on(key + '.' + i, function (e) { - obj.handler.call(this, arguments); + obj.handler.call(this, e); }); }); }); diff --git a/src/kibana/components/vislib/lib/tooltip.js b/src/kibana/components/vislib/lib/tooltip.js index 7cc260810c6562..c33e8baf4901a7 100644 --- a/src/kibana/components/vislib/lib/tooltip.js +++ b/src/kibana/components/vislib/lib/tooltip.js @@ -1,5 +1,5 @@ define(function (require) { - return function TooltipFactory(d3) { + return function TooltipFactory(d3, Private) { var $ = require('jquery'); require('css!components/vislib/styles/main'); @@ -45,11 +45,11 @@ define(function (require) { var tooltipDiv = d3.select('.' + self.tooltipClass); - selection.each(function (data, i) { + selection.each(function () { var element = d3.select(this); element - .on('mousemove.tip', function (d) { + .on('mousemove.tip', function (d, i) { var placement = self.getTooltipPlacement(d3.event); var events = self.events ? self.events.eventResponse(d, i) : d; diff --git a/src/kibana/components/vislib/vis.js b/src/kibana/components/vislib/vis.js index bad4aec241a194..6c011d8e68a9fa 100644 --- a/src/kibana/components/vislib/vis.js +++ b/src/kibana/components/vislib/vis.js @@ -114,6 +114,13 @@ define(function (require) { return this._attr[name]; }; + /** + * Turns on event listeners. + * + * @param event {String} + * @param handler {Function} + * @returns {*} + */ Vis.prototype.on = function (event, handler) { // Adds handler to _listeners[listener] array @@ -121,24 +128,35 @@ define(function (require) { var handlerIndex; // Check if the charts array is available - if (this.handler && this.handler.charts) { - handlerIndex = this._listeners[event].length - 1; - - // Dispatch listener to chart - this.handler.charts.forEach(function (chart) { - chart.on(event + '.' + handlerIndex, function (e) { - handler.call(this, arguments); - }); - }); - } + // if handler count changed from 0 to 1, call handler object + // this.handler.enable(event) + + // inside handler: if there are charts, bind events to charts + // functionality: track in array that event is enabled + // clean up event handlers every time it destroys the chart + // rebind them everytime it creates the charts + +// if (this.handler && this.handler.charts) { +// handlerIndex = this._listeners[event].length - 1; +// +// // Dispatch listener to chart +// this.handler.charts.forEach(function (chart) { +// chart.on(event + '.' + handlerIndex, function (e) { +// handler.call(this, e); +// }); +// }); +// } return ret; }; - /* - * To turn off event listeners, need to pass null as handler to - * d3.dispatch. In addition, we need to track down the particular handler - * from which to turn off. + /** + * Turns off event listeners. Passes the null value as the handler to + * event listeners. + * + * @param event {String} + * @param handler {Function} + * @returns {*} */ Vis.prototype.off = function (event, handler) { var ret = Events.prototype.off.call(this, event, handler); @@ -146,6 +164,8 @@ define(function (require) { if (this._listeners[event] && this.handler.charts) { + // Once the handler array reaches zero, then set event to null + // if no handler, set all listener handlers to null if (!handler) { this.handler.charts.forEach(function (chart) { @@ -153,8 +173,8 @@ define(function (require) { }); } else { - // if handler, get index of handler and set to null. - handlerIndex = _.findIndex(this._listeners[event], handler); + // if handler, turn off a specific handler + handlerIndex = _.findIndex(this._listeners[event], {'handler': handler}); this.handler.charts.forEach(function (chart) { chart.on(event + '.' + handlerIndex, null); diff --git a/src/kibana/components/vislib/visualizations/column_chart.js b/src/kibana/components/vislib/visualizations/column_chart.js index 7aab1025192dd6..67e96bbbe77bcb 100644 --- a/src/kibana/components/vislib/visualizations/column_chart.js +++ b/src/kibana/components/vislib/visualizations/column_chart.js @@ -150,7 +150,7 @@ define(function (require) { bars .on('mouseover.bar', function (d, i) { events.onMouseOver.call(this, arguments); - dispatch.hover.call(this, d, i); + dispatch.hover.call(this, events.eventResponse(d, i)); }) // .on('mousedown.bar', function () { // var bar = d3.select(this); @@ -168,10 +168,12 @@ define(function (require) { // bar.call(brush); // }) .on('click.bar', function (d, i) { - dispatch.click.call(this, events.eventResponse.call(this, d, i)); + dispatch.click.call(this, events.eventResponse(d, i)); + d3.event.stopPropagation(); }) .on('mouseout.bar', function () { events.onMouseOut.call(this, arguments); + d3.event.stopPropagation(); }); }; From f345ebd8bc64af4a51838af1f2b0e29149bff34c Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Wed, 22 Oct 2014 21:34:08 +0300 Subject: [PATCH 03/45] troubleshooting --- src/kibana/components/vislib/lib/handler/handler.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/kibana/components/vislib/lib/handler/handler.js b/src/kibana/components/vislib/lib/handler/handler.js index db1786a51ae73c..b9b6c0c4da13a9 100644 --- a/src/kibana/components/vislib/lib/handler/handler.js +++ b/src/kibana/components/vislib/lib/handler/handler.js @@ -78,15 +78,10 @@ define(function (require) { // if listeners.length, chart.on(event,) // Don't bind handlers individually, use emit instead - // - - // if there are listeners, dispatch listeners to chart if (keys.length) { keys.forEach(function (key) { - listeners[key].forEach(function (obj, i) { - chart.on(key + '.' + i, function (e) { - obj.handler.call(this, e); - }); + chart.on(key, function (e) { + self.vis.emit(key, e); }); }); } From 8bd26e5cdcebf0ca385e2e8781a281b64d989f3c Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Wed, 22 Oct 2014 22:40:43 +0300 Subject: [PATCH 04/45] more changes, experimenting --- src/kibana/components/vislib/lib/handler/handler.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/kibana/components/vislib/lib/handler/handler.js b/src/kibana/components/vislib/lib/handler/handler.js index b9b6c0c4da13a9..39cc1ede9d63cc 100644 --- a/src/kibana/components/vislib/lib/handler/handler.js +++ b/src/kibana/components/vislib/lib/handler/handler.js @@ -70,14 +70,11 @@ define(function (require) { .selectAll('.chart') .each(function (chartData) { var chart = new self.ChartClass(self, this, chartData); - var listeners = self.vis._listeners; - var keys = Object.keys(listeners); + var keys = Object.keys(self.vis._listeners); // Copy dispatch.on methods to chart object d3.rebind(chart, chart.events.dispatch, 'on'); - // if listeners.length, chart.on(event,) - // Don't bind handlers individually, use emit instead if (keys.length) { keys.forEach(function (key) { chart.on(key, function (e) { From 0b67b7886ebba868bd50ee4600d96f779f02339d Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Fri, 24 Oct 2014 19:09:26 +0300 Subject: [PATCH 05/45] working thru the issues --- src/kibana/components/vislib/lib/dispatch.js | 62 ++++++++------- .../components/vislib/lib/handler/handler.js | 48 +++++++++--- src/kibana/components/vislib/vis.js | 77 +++++++++---------- .../vislib/visualizations/column_chart.js | 44 ++++++----- 4 files changed, 129 insertions(+), 102 deletions(-) diff --git a/src/kibana/components/vislib/lib/dispatch.js b/src/kibana/components/vislib/lib/dispatch.js index a0994cf35c927f..991a584c2e8af4 100644 --- a/src/kibana/components/vislib/lib/dispatch.js +++ b/src/kibana/components/vislib/lib/dispatch.js @@ -84,38 +84,36 @@ define(function (require) { * @param svg {HTMLElement} Reference to SVG * @returns {*} Returns a D3 brush function and a SVG with a brush group attached */ -// Dispatch.prototype.addBrush = function (xScale, svg) { -// var dispatch = this._attr.dispatch; -// var attr = this._attr; -// var chartData = this.chartData; -// var isBrush = this._attr.addBrushing; -// var height = this._attr.height; -// var margin = this._attr.margin; -// -// // Brush scale -// var brush = d3.svg.brush() -// .x(xScale) -// .on('brushend', function brushEnd() { -// // response returned on brush -// return dispatch.brush({ -// range: brush.extent(), -// config: attr, -// e: d3.event, -// data: chartData -// }); -// }); -// -// // if `addBrushing` is true, add brush canvas -// if (isBrush) { -// svg.append('g') -// .attr('class', 'brush') -// .call(brush) -// .selectAll('rect') -// .attr('height', height - margin.top - margin.bottom); -// } -// -// return brush; -// }; + Dispatch.prototype.addBrush = function (xScale, svg) { + var dispatch = this.dispatch; + var attr = this.handler._attr; + var chartData = this.handler.chartData; + var height = attr.height; + var margin = attr.margin; + + // Brush scale + var brush = d3.svg.brush() + .x(xScale) + .on('brushend', function brushEnd() { + return dispatch.brush({ + range: brush.extent(), + config: attr, + e: d3.event, + data: chartData + }); + }); + + // if `addBrushing` is true, add brush canvas + if (dispatch.on('brush')) { + svg.insert('g', 'g') + .attr('class', 'brush') + .call(brush) + .selectAll('rect') + .attr('height', height - margin.top - margin.bottom); + + return brush; + } + }; return Dispatch; }; diff --git a/src/kibana/components/vislib/lib/handler/handler.js b/src/kibana/components/vislib/lib/handler/handler.js index 39cc1ede9d63cc..5bb15289c7a870 100644 --- a/src/kibana/components/vislib/lib/handler/handler.js +++ b/src/kibana/components/vislib/lib/handler/handler.js @@ -60,27 +60,31 @@ define(function (require) { var self = this; var charts = this.charts = []; - _.forEach(this.renderArray, function (property) { + this.renderArray.forEach(function (property) { if (typeof property.render === 'function') { property.render(); } }); + // render the chart(s) d3.select(this.el) .selectAll('.chart') .each(function (chartData) { var chart = new self.ChartClass(self, this, chartData); - var keys = Object.keys(self.vis._listeners); + var enabledEvents; - // Copy dispatch.on methods to chart object - d3.rebind(chart, chart.events.dispatch, 'on'); + if (chart.events.dispatch) { + enabledEvents = self.vis.eventTypes.enabled; - if (keys.length) { - keys.forEach(function (key) { - chart.on(key, function (e) { - self.vis.emit(key, e); + // Copy dispatch.on methods to chart object + d3.rebind(chart, chart.events.dispatch, 'on'); + + // Bind events to chart(s) + if (enabledEvents.length) { + enabledEvents.forEach(function (event) { + self.enable(event, chart); }); - }); + } } charts.push(chart); @@ -88,6 +92,32 @@ define(function (require) { }); }; + + // inside handler: if there are charts, bind events to charts + // functionality: track in array that event is enabled + // clean up event handlers every time it destroys the chart + // rebind them every time it creates the charts + /** + * + * @param event + * @param chart + */ + Handler.prototype.enable = function (event, chart) { + return chart.on(event, function (e) { + this.vis.emit(event, e); + }.bind(this)); + }; + + /** + * + * @param event + * @param chart + * @returns {*} + */ + Handler.prototype.disable = function (event, chart) { + return chart.on(event, null); + }; + /** * Removes all DOM elements from the HTML element provided * diff --git a/src/kibana/components/vislib/vis.js b/src/kibana/components/vislib/vis.js index 6c011d8e68a9fa..c187bfc4bb69d8 100644 --- a/src/kibana/components/vislib/vis.js +++ b/src/kibana/components/vislib/vis.js @@ -27,6 +27,9 @@ define(function (require) { this.el = $el.get ? $el.get(0) : $el; this.ChartClass = chartTypes[config.type]; this._attr = _.defaults(config || {}, {}); + this.eventTypes = { + enabled: [] + }; // bind the resize function so it can be used as an event handler this.resize = _.bind(this.resize, this); @@ -122,63 +125,53 @@ define(function (require) { * @returns {*} */ Vis.prototype.on = function (event, handler) { - - // Adds handler to _listeners[listener] array - var ret = Events.prototype.on.call(this, event, handler); - var handlerIndex; + var ret = Events.prototype.on.call(this, event, handler); // Adds event to _listeners array + var listeners = this._listeners[event].length; + var charts = (this.handler && this.handler.charts); + var chartCount = charts ? charts.length : 0; + var enabledEvents = this.eventTypes.enabled; + var eventAbsent = (enabledEvents.indexOf(event) === -1); // Check if the charts array is available - // if handler count changed from 0 to 1, call handler object + // if chart count changed from 0 to 1, call handler object // this.handler.enable(event) + if (listeners === 1 && chartCount > 0) { + charts.forEach(function (chart) { + this.handler.enable(event, chart); + }, this); + } - // inside handler: if there are charts, bind events to charts - // functionality: track in array that event is enabled - // clean up event handlers every time it destroys the chart - // rebind them everytime it creates the charts - -// if (this.handler && this.handler.charts) { -// handlerIndex = this._listeners[event].length - 1; -// -// // Dispatch listener to chart -// this.handler.charts.forEach(function (chart) { -// chart.on(event + '.' + handlerIndex, function (e) { -// handler.call(this, e); -// }); -// }); -// } + // update the eventType as enabled + if (eventAbsent) { + enabledEvents.push(event); + } return ret; }; /** - * Turns off event listeners. Passes the null value as the handler to - * event listeners. + * Turns off event listeners. * * @param event {String} * @param handler {Function} * @returns {*} */ Vis.prototype.off = function (event, handler) { - var ret = Events.prototype.off.call(this, event, handler); - var handlerIndex; - - if (this._listeners[event] && this.handler.charts) { - - // Once the handler array reaches zero, then set event to null - - // if no handler, set all listener handlers to null - if (!handler) { - this.handler.charts.forEach(function (chart) { - chart.on(event, null); - }); + var ret = Events.prototype.off.call(this, event, handler); // Removes event from _listeners array + var listeners = (!!this._listeners[event] && this._listeners[event].length !== 0); + var charts = (this.handler && this.handler.charts); + var chartCount = charts ? charts.length : 0; + var eventIndex = this.eventTypes.enabled.indexOf(event); + var eventPresent = (eventIndex !== -1); + + // Once the handler array reaches zero, then turn off event + if (!listeners && eventPresent) { + if (chartCount > 0) { + charts.forEach(function (chart) { + this.handler.disable(event, chart); + }, this); } else { - - // if handler, turn off a specific handler - handlerIndex = _.findIndex(this._listeners[event], {'handler': handler}); - - this.handler.charts.forEach(function (chart) { - chart.on(event + '.' + handlerIndex, null); - }); + this.eventTypes.enabled.splice(eventIndex, 1); } } @@ -187,4 +180,4 @@ define(function (require) { return Vis; }; -}); \ No newline at end of file +}); diff --git a/src/kibana/components/vislib/visualizations/column_chart.js b/src/kibana/components/vislib/visualizations/column_chart.js index 67e96bbbe77bcb..d8098f2786cde0 100644 --- a/src/kibana/components/vislib/visualizations/column_chart.js +++ b/src/kibana/components/vislib/visualizations/column_chart.js @@ -139,35 +139,21 @@ define(function (require) { * @param brush {Function} D3 brush function * @returns {HTMLElement} rect with event listeners attached */ - ColumnChart.prototype.addBarEvents = function (svg, bars) { + ColumnChart.prototype.addBarEvents = function (svg, bars, brush) { var events = this.events; var dispatch = this.events.dispatch; // var addBrush = this._attr.addBrushing; -// var xScale = this.handler.xAxis.xScale; + var xScale = this.handler.xAxis.xScale; // var height = this._attr.height; // var margin = this._attr.margin; bars .on('mouseover.bar', function (d, i) { - events.onMouseOver.call(this, arguments); dispatch.hover.call(this, events.eventResponse(d, i)); + d3.event.stopPropagation(); }) -// .on('mousedown.bar', function () { -// var bar = d3.select(this); -// var startX = d3.mouse(svg.node()); -// var startXInv = xScale.invert(startX[0]); -// -// // Reset the brush value -// brush.extent([startXInv, startXInv]); -// -// // Magic! -// // Need to call brush on svg to see brush when brushing -// // while on top of bars. -// // Need to call brush on bar to allow the click event to be registered -// svg.call(brush); -// bar.call(brush); -// }) .on('click.bar', function (d, i) { + events.onMouseOver.call(this, arguments); dispatch.click.call(this, events.eventResponse(d, i)); d3.event.stopPropagation(); }) @@ -175,6 +161,25 @@ define(function (require) { events.onMouseOut.call(this, arguments); d3.event.stopPropagation(); }); + + if (dispatch.on('brush')) { + bars + .on('mousedown.bar', function () { + var bar = d3.select(this); + var startX = d3.mouse(svg.node()); + var startXInv = xScale.invert(startX[0]); + + // Reset the brush value + brush.extent([startXInv, startXInv]); + + // Magic! + // Need to call brush on svg to see brush when brushing + // while on top of bars. + // Need to call brush on bar to allow the click event to be registered + svg.call(brush); + bar.call(brush); + }); + } }; /** @@ -220,7 +225,8 @@ define(function (require) { bars = self.addBars(svg, layers); - self.addBarEvents(svg, bars); + var brush = self.events.addBrush(xScale, svg); + self.addBarEvents(svg, bars, brush); var line = svg.append('line') .attr('x1', 0) From 0bc4caf162dd5dbcc79dcf3752ca16e8fc46589c Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Sat, 25 Oct 2014 01:29:48 +0300 Subject: [PATCH 06/45] reorganizing dispatch events class --- src/kibana/components/vislib/lib/dispatch.js | 13 +++- .../components/vislib/lib/handler/handler.js | 27 +++++-- src/kibana/components/vislib/vis.js | 22 +++--- .../vislib/visualizations/column_chart.js | 77 ++++++++++++------- 4 files changed, 90 insertions(+), 49 deletions(-) diff --git a/src/kibana/components/vislib/lib/dispatch.js b/src/kibana/components/vislib/lib/dispatch.js index 991a584c2e8af4..fc2f5df056cf76 100644 --- a/src/kibana/components/vislib/lib/dispatch.js +++ b/src/kibana/components/vislib/lib/dispatch.js @@ -53,6 +53,16 @@ define(function (require) { }; }; + Dispatch.prototype.addEvent = function (event, callback) { + return function (selection) { + selection.each(function () { + var element = d3.select(this); + + element.on(event, callback); + }); + }; + }; + /** * Mouse over Behavior * @@ -87,7 +97,6 @@ define(function (require) { Dispatch.prototype.addBrush = function (xScale, svg) { var dispatch = this.dispatch; var attr = this.handler._attr; - var chartData = this.handler.chartData; var height = attr.height; var margin = attr.margin; @@ -99,7 +108,7 @@ define(function (require) { range: brush.extent(), config: attr, e: d3.event, - data: chartData + data: d3.event.sourceEvent.target.__data__ }); }); diff --git a/src/kibana/components/vislib/lib/handler/handler.js b/src/kibana/components/vislib/lib/handler/handler.js index 5bb15289c7a870..492e3cd23c87ab 100644 --- a/src/kibana/components/vislib/lib/handler/handler.js +++ b/src/kibana/components/vislib/lib/handler/handler.js @@ -73,6 +73,12 @@ define(function (require) { var chart = new self.ChartClass(self, this, chartData); var enabledEvents; + /* + * inside handler: if there are charts, bind events to charts + * functionality: track in array that event is enabled + * clean up event handlers every time it destroys the chart + * rebind them every time it creates the charts + */ if (chart.events.dispatch) { enabledEvents = self.vis.eventTypes.enabled; @@ -93,14 +99,15 @@ define(function (require) { }; - // inside handler: if there are charts, bind events to charts - // functionality: track in array that event is enabled - // clean up event handlers every time it destroys the chart - // rebind them every time it creates the charts /** + * Enables events, i.e. binds specific events to the chart + * object(s) `on` method. For example, `click` or `mousedown` events. + * Emits the event to the Events class. * - * @param event - * @param chart + * @method enable + * @param event {String} Event type + * @param chart {Object} Chart + * @returns {*} */ Handler.prototype.enable = function (event, chart) { return chart.on(event, function (e) { @@ -109,9 +116,13 @@ define(function (require) { }; /** + * Disables events. According to the D3 documentation for event handling: + * https://github.com/mbostock/d3/wiki/Selections#on, to remove all + * listeners for a particular event type, pass null as the listener. * - * @param event - * @param chart + * @method disable + * @param event {String} Event type + * @param chart {Object} Chart * @returns {*} */ Handler.prototype.disable = function (event, chart) { diff --git a/src/kibana/components/vislib/vis.js b/src/kibana/components/vislib/vis.js index c187bfc4bb69d8..bf1c0a603fd35c 100644 --- a/src/kibana/components/vislib/vis.js +++ b/src/kibana/components/vislib/vis.js @@ -121,27 +121,27 @@ define(function (require) { * Turns on event listeners. * * @param event {String} - * @param handler {Function} + * @param listener{Function} * @returns {*} */ - Vis.prototype.on = function (event, handler) { - var ret = Events.prototype.on.call(this, event, handler); // Adds event to _listeners array + Vis.prototype.on = function (event, listener) { + var ret = Events.prototype.on.call(this, event, listener); // Adds event to _listeners array var listeners = this._listeners[event].length; var charts = (this.handler && this.handler.charts); var chartCount = charts ? charts.length : 0; var enabledEvents = this.eventTypes.enabled; var eventAbsent = (enabledEvents.indexOf(event) === -1); - // Check if the charts array is available - // if chart count changed from 0 to 1, call handler object - // this.handler.enable(event) + // if this is the first listener added for the event + // and charts are available, bind the event to the chart(s) + // `on` method if (listeners === 1 && chartCount > 0) { charts.forEach(function (chart) { this.handler.enable(event, chart); }, this); } - // update the eventType as enabled + // Keep track of enabled events if (eventAbsent) { enabledEvents.push(event); } @@ -153,18 +153,18 @@ define(function (require) { * Turns off event listeners. * * @param event {String} - * @param handler {Function} + * @param listener{Function} * @returns {*} */ - Vis.prototype.off = function (event, handler) { - var ret = Events.prototype.off.call(this, event, handler); // Removes event from _listeners array + Vis.prototype.off = function (event, listener) { + var ret = Events.prototype.off.call(this, event, listener); // Removes event from _listeners array var listeners = (!!this._listeners[event] && this._listeners[event].length !== 0); var charts = (this.handler && this.handler.charts); var chartCount = charts ? charts.length : 0; var eventIndex = this.eventTypes.enabled.indexOf(event); var eventPresent = (eventIndex !== -1); - // Once the handler array reaches zero, then turn off event + // Once the listener array reaches zero, turn off event if (!listeners && eventPresent) { if (chartCount > 0) { charts.forEach(function (chart) { diff --git a/src/kibana/components/vislib/visualizations/column_chart.js b/src/kibana/components/vislib/visualizations/column_chart.js index ee2b0b23471ac5..f454195bde4daa 100644 --- a/src/kibana/components/vislib/visualizations/column_chart.js +++ b/src/kibana/components/vislib/visualizations/column_chart.js @@ -219,37 +219,31 @@ define(function (require) { * Adds Events to SVG rect * * @method addBarEvents + * @param element {d3.UpdateSelection} target * @param svg {HTMLElement} chart SVG - * @param bars {D3.UpdateSelection} SVG rect - * @param brush {Function} D3 brush function * @returns {HTMLElement} rect with event listeners attached */ - ColumnChart.prototype.addBarEvents = function (svg, bars, brush) { - var events = this.events; + ColumnChart.prototype.addBarEvents = function (element, svg) { + var addEvent = this.events.addEvent; + var mouseOver = addEvent('mouseover.bar', this.mouseOver()); + var mouseOut = addEvent('mouseout.bar', this.mouseOut()); + var brush = addEvent('mousedown.bar', this.brush(svg)); + var click = addEvent('click.bar', this.click()); + + return element + .call(mouseOver) + .call(mouseOut) + .call(brush) + .call(click); + }; + + ColumnChart.prototype.brush = function (svg) { var dispatch = this.events.dispatch; -// var addBrush = this._attr.addBrushing; var xScale = this.handler.xAxis.xScale; -// var height = this._attr.height; -// var margin = this._attr.margin; - - bars - .on('mouseover.bar', function (d, i) { - dispatch.hover.call(this, events.eventResponse(d, i)); - d3.event.stopPropagation(); - }) - .on('click.bar', function (d, i) { - events.onMouseOver.call(this, arguments); - dispatch.click.call(this, events.eventResponse(d, i)); - d3.event.stopPropagation(); - }) - .on('mouseout.bar', function () { - events.onMouseOut.call(this, arguments); - d3.event.stopPropagation(); - }); + var brush = this.events.addBrush(xScale, svg); if (dispatch.on('brush')) { - bars - .on('mousedown.bar', function () { + return function () { var bar = d3.select(this); var startX = d3.mouse(svg.node()); var startXInv = xScale.invert(startX[0]); @@ -263,10 +257,38 @@ define(function (require) { // Need to call brush on bar to allow the click event to be registered svg.call(brush); bar.call(brush); - }); + }; } }; + ColumnChart.prototype.mouseOver = function () { + var self = this; + + return function (d, i) { + self.events.onMouseOver.call(this, arguments); + self.events.dispatch.hover.call(this, self.events.eventResponse(d, i)); + d3.event.stopPropagation(); + }; + }; + + ColumnChart.prototype.mouseOut = function () { + var self = this; + + return function () { + self.events.onMouseOut.call(this, arguments); + d3.event.stopPropagation(); + }; + }; + + ColumnChart.prototype.click = function () { + var self = this; + + return function (d, i) { + self.events.dispatch.click.call(this, self.events.eventResponse(d, i)); + d3.event.stopPropagation(); + }; + }; + /** * Renders d3 visualization * @@ -275,7 +297,6 @@ define(function (require) { */ ColumnChart.prototype.draw = function () { var self = this; - var xScale = this.handler.xAxis.xScale; var $elem = $(this.chartEl); var margin = this._attr.margin; var elWidth = this._attr.width = $elem.width(); @@ -310,8 +331,8 @@ define(function (require) { bars = self.addBars(svg, layers); - var brush = self.events.addBrush(xScale, svg); - self.addBarEvents(svg, bars, brush); + // Adds event listeners + self.addBarEvents(bars, svg); var line = svg.append('line') .attr('x1', 0) From 58d83b8bdb010285ba448ab2ee2fd230bc13e121 Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Mon, 27 Oct 2014 19:32:38 +0200 Subject: [PATCH 07/45] adding more comments --- src/kibana/components/vislib/lib/handler/handler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kibana/components/vislib/lib/handler/handler.js b/src/kibana/components/vislib/lib/handler/handler.js index 492e3cd23c87ab..1dbbe2f16fe360 100644 --- a/src/kibana/components/vislib/lib/handler/handler.js +++ b/src/kibana/components/vislib/lib/handler/handler.js @@ -116,7 +116,8 @@ define(function (require) { }; /** - * Disables events. According to the D3 documentation for event handling: + * Disables events by passing null to the event listener. + * According to the D3 documentation for event handling: * https://github.com/mbostock/d3/wiki/Selections#on, to remove all * listeners for a particular event type, pass null as the listener. * From 5cf67cf2f1f01995ca8098e923962575784a267d Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Tue, 28 Oct 2014 17:30:52 +0200 Subject: [PATCH 08/45] making changes to get pie charts working --- src/kibana/components/vislib/lib/data.js | 18 ++++++++++++- src/kibana/components/vislib/lib/dispatch.js | 1 + .../vislib/visualizations/pie_chart.js | 25 ++++++++----------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/kibana/components/vislib/lib/data.js b/src/kibana/components/vislib/lib/data.js index 917c5c63288b38..35ccc21ed992ea 100644 --- a/src/kibana/components/vislib/lib/data.js +++ b/src/kibana/components/vislib/lib/data.js @@ -34,7 +34,8 @@ define(function (require) { } this.data = data; - this.labels = getLabels(data); + this.type = this.getDataType(); + this.labels = (this.type === 'series') ? getLabels(data) : this.pieNames(); this.color = color(this.labels); this._normalizeOrdered(); @@ -48,6 +49,21 @@ define(function (require) { }); } + Data.prototype.getDataType = function () { + var data = this.getVisData(); + var type; + + data.forEach(function (obj) { + if (obj.series) { + type = 'series'; + } else if (obj.slices) { + type = 'slices'; + } + }); + + return type; + }; + /** * Returns an array of the actual x and y data value objects * from data with series keys diff --git a/src/kibana/components/vislib/lib/dispatch.js b/src/kibana/components/vislib/lib/dispatch.js index f7545b81f34570..47bcd4359191d9 100644 --- a/src/kibana/components/vislib/lib/dispatch.js +++ b/src/kibana/components/vislib/lib/dispatch.js @@ -40,6 +40,7 @@ define(function (require) { var isPercentage = (handler._attr.mode === 'percentage'); if (isSeries) { + // Find object with the actual d value and add it to the point object var object = _.find(series, { 'label': d.label }); d.value = +object.values[i].y; diff --git a/src/kibana/components/vislib/visualizations/pie_chart.js b/src/kibana/components/vislib/visualizations/pie_chart.js index c60374c6047ed5..6828c1af27a737 100644 --- a/src/kibana/components/vislib/visualizations/pie_chart.js +++ b/src/kibana/components/vislib/visualizations/pie_chart.js @@ -24,12 +24,9 @@ define(function (require) { } PieChart.Super.apply(this, arguments); - this.columns = handler.data.data.raw.columns; - this._attr = _.defaults(handler._attr || {}, { isDonut: handler._attr.isDonut || false, - getSize: function (d) { return d.size; }, - dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseover', 'mouseout') + getSize: function (d) { return d.size; } }); } @@ -42,7 +39,7 @@ define(function (require) { */ PieChart.prototype.addPathEvents = function (path) { var events = this.events; - var dispatch = this.events._attr.dispatch; + var dispatch = this.events.dispatch; path .on('mouseover.pie', function mouseOverPie(d, i) { @@ -50,11 +47,11 @@ define(function (require) { .classed('hover', true) .style('cursor', 'pointer'); - dispatch.hover(events.pieResponse(d, i)); + dispatch.hover(events.eventResponse(d, i)); d3.event.stopPropagation(); }) .on('click.pie', function clickPie(d, i) { - dispatch.click(events.pieResponse(d, i)); + dispatch.click(events.eventResponse(d, i)); d3.event.stopPropagation(); }) .on('mouseout.pie', function mouseOutPie() { @@ -109,7 +106,9 @@ define(function (require) { var isTooltip = this._attr.addTooltip; var self = this; var path; - var fieldFormatter; + var fieldFormatter = function (label) { + return label; + }; path = svg .datum(slices) @@ -121,18 +120,14 @@ define(function (require) { .attr('class', function (d) { if (d.depth === 0) { return; } - fieldFormatter = self.columns[d.depth - 1].field ? - self.columns[d.depth - 1].field.format.convert : - function (d) { return d; }; + fieldFormatter = d.aggConfig ? d.aggConfig.params.field.format.convert : fieldFormatter; return self.colorToClass(color(fieldFormatter(d.name))); }) .style('stroke', '#fff') .style('fill', function (d) { if (d.depth === 0) { return 'none'; } - fieldFormatter = self.columns[d.depth - 1].field ? - self.columns[d.depth - 1].field.format.convert : - function (d) { return d; }; + fieldFormatter = d.aggConfig ? d.aggConfig.params.field.format.convert : fieldFormatter; return color(fieldFormatter(d.name)); }); @@ -186,4 +181,4 @@ define(function (require) { return PieChart; }; -}); \ No newline at end of file +}); From a150ef51f587b0e85e1176cad6a9df0c27668aeb Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Tue, 28 Oct 2014 18:03:55 +0200 Subject: [PATCH 09/45] fixing area chart and line chart --- src/kibana/components/vislib/lib/dispatch.js | 8 ++++++++ src/kibana/components/vislib/visualizations/area_chart.js | 2 +- src/kibana/components/vislib/visualizations/line_chart.js | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/kibana/components/vislib/lib/dispatch.js b/src/kibana/components/vislib/lib/dispatch.js index 47bcd4359191d9..65d2c4fe824647 100644 --- a/src/kibana/components/vislib/lib/dispatch.js +++ b/src/kibana/components/vislib/lib/dispatch.js @@ -67,6 +67,14 @@ define(function (require) { }; }; + /** + * Returns a function that adds events and listeners to a D3 selection + * + * @method addEvent + * @param event {String} + * @param callback {Function} + * @returns {Function} + */ Dispatch.prototype.addEvent = function (event, callback) { return function (selection) { selection.each(function () { diff --git a/src/kibana/components/vislib/visualizations/area_chart.js b/src/kibana/components/vislib/visualizations/area_chart.js index f4a709881296e3..fed69b59073891 100644 --- a/src/kibana/components/vislib/visualizations/area_chart.js +++ b/src/kibana/components/vislib/visualizations/area_chart.js @@ -141,7 +141,7 @@ define(function (require) { */ AreaChart.prototype.addCircleEvents = function (circles) { var events = this.events; - var dispatch = this.events._attr.dispatch; + var dispatch = this.events.dispatch; circles .on('mouseover.circle', function mouseOverCircle(d, i) { diff --git a/src/kibana/components/vislib/visualizations/line_chart.js b/src/kibana/components/vislib/visualizations/line_chart.js index 8457c06a53070a..c9ab1e7aece904 100644 --- a/src/kibana/components/vislib/visualizations/line_chart.js +++ b/src/kibana/components/vislib/visualizations/line_chart.js @@ -41,7 +41,7 @@ define(function (require) { */ LineChart.prototype.addCircleEvents = function (circles) { var events = this.events; - var dispatch = this.events._attr.dispatch; + var dispatch = this.events.dispatch; circles .on('mouseover.circle', function mouseOverCircle(d, i) { From 0372e81d63b1b0728a1e966918fa8f613d2cf652 Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Thu, 30 Oct 2014 18:59:32 +0200 Subject: [PATCH 10/45] restructured the less and css files into separate files which are imported into a main file, removed event code from visualizations and moved them into the Dispatch class to reduce code redundancy, fixed issues with legend not toggling --- src/kibana/components/vislib/lib/dispatch.js | 98 ++++- src/kibana/components/vislib/lib/legend.js | 13 +- .../components/vislib/styles/_error.less | 12 + .../components/vislib/styles/_layout.less | 144 ++++++ .../components/vislib/styles/_legend.less | 59 +++ src/kibana/components/vislib/styles/_svg.less | 63 +++ .../components/vislib/styles/_tooltip.less | 36 ++ src/kibana/components/vislib/styles/main.less | 415 +----------------- src/kibana/components/vislib/vis.js | 1 + .../vislib/visualizations/area_chart.js | 44 +- .../vislib/visualizations/column_chart.js | 79 +--- .../vislib/visualizations/line_chart.js | 39 +- .../vislib/visualizations/pie_chart.js | 34 +- tasks/config/less.js | 1 - 14 files changed, 446 insertions(+), 592 deletions(-) create mode 100644 src/kibana/components/vislib/styles/_error.less create mode 100644 src/kibana/components/vislib/styles/_layout.less create mode 100644 src/kibana/components/vislib/styles/_legend.less create mode 100644 src/kibana/components/vislib/styles/_svg.less create mode 100644 src/kibana/components/vislib/styles/_tooltip.less diff --git a/src/kibana/components/vislib/lib/dispatch.js b/src/kibana/components/vislib/lib/dispatch.js index 65d2c4fe824647..e394fb8f990266 100644 --- a/src/kibana/components/vislib/lib/dispatch.js +++ b/src/kibana/components/vislib/lib/dispatch.js @@ -31,6 +31,7 @@ define(function (require) { */ Dispatch.prototype.eventResponse = function (d, i) { var data = d3.event.target.nearestViewportElement.__data__; + var label = d.label ? d.label : d.name; var isSeries = !!(data.series); var isSlices = !!(data.slices); var series = isSeries ? data.series : undefined; @@ -55,8 +56,8 @@ define(function (require) { return { value: d.y, point: d, - label: d.label, - color: color(d.label), + label: label, + color: color(label), pointIndex: i, series: series, slices: slices, @@ -80,31 +81,96 @@ define(function (require) { selection.each(function () { var element = d3.select(this); - element.on(event, callback); + if (typeof callback === 'function') { + return element.on(event, callback); + } }); }; }; /** - * Mouse over Behavior * - * @method mouseOverBar - * @returns {D3.Selection} this object with '.hover' class true + * @method addHoverEvent + * @returns {Function} */ - Dispatch.prototype.onMouseOver = function () { - return d3.select(this).classed('hover', true) - .style('stroke', '#333') - .style('cursor', 'pointer'); + Dispatch.prototype.addHoverEvent = function () { + var self = this; + var isClickable = (this.dispatch.on('click')); + var addEvent = this.addEvent; + + function hover(d, i) { + d3.event.stopPropagation(); + + // Add pointer if item is clickable + if (isClickable) { + self.addMousePointer.call(this, arguments); + } + + self.dispatch.hover.call(this, self.eventResponse(d, i)); + } + + return addEvent('mouseover', hover); }; /** - * Mouse out Behavior * - * @method mouseOutBar - * @returns {D3.Selection} this object with '.hover' class false + * @method addClickEvent + * @returns {Function} + */ + Dispatch.prototype.addClickEvent = function () { + var self = this; + var addEvent = this.addEvent; + + function click(d, i) { + d3.event.stopPropagation(); + self.dispatch.click.call(this, self.eventResponse(d, i)); + } + + return addEvent('click', click); + }; + + /** + * + * @param svg + * @returns {Function} + */ + Dispatch.prototype.addBrushEvent = function (svg) { + var dispatch = this.dispatch; + var xScale = this.handler.xAxis.xScale; + var isBrushable = (dispatch.on('brush')); + var brush = this.createBrush(xScale, svg); + var addEvent = this.addEvent; + + function brushEnd() { + var bar = d3.select(this); + var startX = d3.mouse(svg.node()); + var startXInv = xScale.invert(startX[0]); + + // Reset the brush value + brush.extent([startXInv, startXInv]); + + // Magic! + // Need to call brush on svg to see brush when brushing + // while on top of bars. + // Need to call brush on bar to allow the click event to be registered + svg.call(brush); + bar.call(brush); + } + + if (isBrushable) { + return addEvent('mousedown', brushEnd); + } + }; + + + /** + * Mouse over Behavior + * + * @method addMousePointer + * @returns {D3.Selection} */ - Dispatch.prototype.onMouseOut = function () { - return d3.select(this).classed('hover', false).style('stroke', null); + Dispatch.prototype.addMousePointer = function () { + return d3.select(this).style('cursor', 'pointer'); }; /** @@ -114,7 +180,7 @@ define(function (require) { * @param svg {HTMLElement} Reference to SVG * @returns {*} Returns a D3 brush function and a SVG with a brush group attached */ - Dispatch.prototype.addBrush = function (xScale, svg) { + Dispatch.prototype.createBrush = function (xScale, svg) { var dispatch = this.dispatch; var attr = this.handler._attr; var height = attr.height; diff --git a/src/kibana/components/vislib/lib/legend.js b/src/kibana/components/vislib/lib/legend.js index 872b5b0161a232..25fa86e19b777b 100644 --- a/src/kibana/components/vislib/lib/legend.js +++ b/src/kibana/components/vislib/lib/legend.js @@ -105,26 +105,25 @@ define(function (require) { var visEl = d3.select(this.el); var legendDiv = visEl.select('.' + this._attr.legendClass); var items = this.labels; - var headerIcon = visEl.select('.legend-toggle'); + var headerIcon = '.legend-toggle'; var self = this; this.header(legendDiv, this); this.list(legendDiv, items, this); // toggle - headerIcon + visEl.select(headerIcon) .on('click', function legendClick() { if (self._attr.isOpen) { // close legend - visEl.select('ul.legend-ul') - .classed('hidden', true); + visEl.select('ul.legend-ul').classed('hidden', true); self._attr.isOpen = false; + // need to add reference to resize function on toggle self.vis.resize(); } else { // open legend - visEl.select('ul.legend-ul') - .classed('hidden', false); + visEl.select('ul.legend-ul').classed('hidden', false); self._attr.isOpen = true; // need to add reference to resize function on toggle @@ -146,7 +145,7 @@ define(function (require) { * The default opacity of elements in charts may be modified by the * chart constructor, and so may differ from that of the legend */ - visEl.select('.chart') + visEl.selectAll('.chart') .selectAll('.color') .style('opacity', self._attr.defaultOpacity); diff --git a/src/kibana/components/vislib/styles/_error.less b/src/kibana/components/vislib/styles/_error.less new file mode 100644 index 00000000000000..0163b15bd22ea4 --- /dev/null +++ b/src/kibana/components/vislib/styles/_error.less @@ -0,0 +1,12 @@ +@import (reference) "lesshat.less"; + +.error { + .flex(1 1 100%); + text-align: center; + + p { + margin-top: 15%; + font-size: 18px; + text-wrap: wrap; + } +} diff --git a/src/kibana/components/vislib/styles/_layout.less b/src/kibana/components/vislib/styles/_layout.less new file mode 100644 index 00000000000000..6bc0783fd2f193 --- /dev/null +++ b/src/kibana/components/vislib/styles/_layout.less @@ -0,0 +1,144 @@ +@import (reference) "lesshat.less"; + +.visualize-chart { + .display(flex); + .flex(1 1 100%); +} + +.vis-wrapper { + .display(flex); + .flex(1 1 100%); + .flex-direction(row); + margin: 10px 0 0 6px; +} + +/* YAxis logic */ +.y-axis-col-wrapper { + .display(flex); + .flex-direction(column); +} + +.y-axis-col { + .display(flex); + .flex-direction(row); + .flex(1 0 50px); +} + +.y-axis-spacer-block { + min-height: 45px; +} + +.y-axis-div-wrapper { + .display(flex); + .flex-direction(column); + width: 38px; + min-height: 20px; +} + +.y-axis-div { + .flex(1 1 25px); + min-width: 14px; + min-height: 14px; +} + +.y-axis-title { + min-height: 14px; + min-width: 14px; +} + +.y-axis-chart-title { + .display(flex); + .flex-direction(column); + min-height: 14px; + width: 14px; +} + +.y-axis-title text { + font-size: 11px; +} + +.chart-title { + .flex(1 1 100%); + min-height: 14px; + min-width: 14px; +} + +.chart-title text { + font-size: 11px; + fill: #848e96; +} + +.vis-col-wrapper { + .display(flex); + .flex(1 0 20px); + .flex-direction(column); +} + +.chart-wrapper { + .display(flex); + .flex(1 0 20px); + overflow: visible; + margin: 0 5px 0 0; +} + +.chart-wrapper-column { + .display(flex); + .flex(1 0 20px); + .flex-direction(row); +} + +.chart-wrapper-row { + .display(flex); + .flex-direction(column); + .flex(1 0 100%); +} + +.chart { + .flex(1 1 100%); + overflow: visible; +} + +.chart-row { + .flex(1 1); +} + +.chart-column { + .flex(1 1); +} + +.x-axis-wrapper { + .display(flex); + .flex-direction(column); + min-height: 45px; + overflow: visible; +} + +.x-axis-div-wrapper { + .display(flex); + .flex-direction(row); + min-height: 15px; +} + +.x-axis-chart-title { + .display(flex); + .flex-direction(row); + min-height: 15px; + max-height: 15px; + min-width: 20px; +} + +.x-axis-title { + min-height: 15px; + max-height: 15px; + min-width: 20px; +} + +.x-axis-title text { + font-size: 11px; +} + +.x-axis-div { + margin-top: -5px; + min-height: 20px; + min-width: 20px; +} diff --git a/src/kibana/components/vislib/styles/_legend.less b/src/kibana/components/vislib/styles/_legend.less new file mode 100644 index 00000000000000..d2907c487ffd33 --- /dev/null +++ b/src/kibana/components/vislib/styles/_legend.less @@ -0,0 +1,59 @@ +@import (reference) "lesshat.less"; + +.legend-col-wrapper { + .flex(0 1 auto); + z-index: 10; + overflow-x: hidden; + overflow-y: auto; + min-width: 40px; + + .header { + width: 100%; + height: 26px; + margin: 0 0 14px 0; + visibility: visible; + } + + .legend-ul { + list-style-type: none; + margin: 0 0 0 14px; + padding: 0; + visibility: visible; + .display(flex); + .flex-direction(column); + + li.color { + min-height: 22px; + margin: 0 18px 0 18px; + padding: 3px 0 3px 0; + text-align: left; + font-size: 12px; + line-height: 13px; + color: #666; + cursor: default; + text-align: left; + .flex-grow(2); + word-wrap: break-word; + max-width: 150px; + + .dots { + width: 10px; + height: 10px; + border-radius: 50%; + float: left; + margin: 1px 0 0 -14px; + } + } + } + + .legend-ul.hidden { + visibility: hidden; + } + + .legend-toggle { + position: relative; + float: right; + right: 9px; + top: 9px; + } +} diff --git a/src/kibana/components/vislib/styles/_svg.less b/src/kibana/components/vislib/styles/_svg.less new file mode 100644 index 00000000000000..e15f23a2e8cafa --- /dev/null +++ b/src/kibana/components/vislib/styles/_svg.less @@ -0,0 +1,63 @@ +/* Axis Styles */ +.axis { + shape-rendering: crispEdges; + stroke-width: 1px; + + line, path { + stroke: #ddd; + fill: none; + shape-rendering: crispEdges; + } +} + +.x.axis path { + display: none; +} + +.tick text { + font-size: 7pt; + fill: #848e96; +} + +/* Brush Styling */ +.brush .extent { + stroke: #fff; + fill-opacity: .125; + shape-rendering: crispEdges; +} + +/* SVG Element Default Styling */ +rect { + stroke: none; + opacity: 1; + + &:hover { + stroke: #333; + } +} + +circle { + opacity: 0; + + &:hover { + opacity: 1; + stroke: #333; + } +} + +path { + &:hover { + opacity: 1; + } +} + +/* Visualization Styling */ +.line { + circle { + opacity: 1; + } + + &:hover { + stroke: #333; + } +} diff --git a/src/kibana/components/vislib/styles/_tooltip.less b/src/kibana/components/vislib/styles/_tooltip.less new file mode 100644 index 00000000000000..20d6b1a4347597 --- /dev/null +++ b/src/kibana/components/vislib/styles/_tooltip.less @@ -0,0 +1,36 @@ +@import (reference) "../../../styles/main"; + +.vis-tooltip { + visibility: hidden; + line-height: 1.1; + font-size: 12px; + font-weight: normal; + padding: 8px; + background: rgba(70, 82, 93, 0.95); + color: @gray-lighter; + border-radius: 4px; + position: fixed; + z-index: 120; + word-wrap: break-word; + max-width: 40%; + + table { + td,th { + padding: 2px 4px; + } + + // if there is a header, give it a border that matches + // those in the body + thead tr { + border-bottom: 1px solid @gray; + } + + // only apply to tr in the body, not the header + tbody tr { + border-top: 1px solid @gray; + &:first-child { + border-top: none; + } + } + } +} diff --git a/src/kibana/components/vislib/styles/main.less b/src/kibana/components/vislib/styles/main.less index 3f8f1f7b112f14..e7533d8832239f 100644 --- a/src/kibana/components/vislib/styles/main.less +++ b/src/kibana/components/vislib/styles/main.less @@ -1,410 +1,5 @@ -@import (reference) "../../../styles/main.less"; -@import (reference) "lesshat.less"; - -/* vislib styles */ -.arc path { - stroke: #fff; -} - -div.columns { - .display(flex); - .flex-direction(row); - .flex(1 1 100%); - margin: 0; - padding: 0; -} - -div.rows { - .display(flex); - .flex-direction(column); - .flex(1 1 100%); - margin: 0; - padding: 0; -} - -.row-labels, .column-labels { - margin: 0; - padding: 10; -} - -visualize { - margin:0; - padding:0; -} - -.visualize-chart { - .display(flex); - .flex(1 1 100%); -} - -.visualize-wrapper { - .display(flex); - .flex-direction(row); -} - -.y-axis-wrapper { - .display(flex); - .flex(1 1); - .flex-direction(column); -} - -.y-axis-filler-div { - .flex(1 1); -} - -div.x-axis-label { - position: absolute; - width: 100%; - text-align: center; - bottom: 15px; - font-size: 7pt; - color: #848e96; -} - -div.y-axis-label { - position: absolute; - left: -25px; - top: 42.5%; - font-size: 7pt; - color: #848e96; - -webkit-transform: rotate(270deg); - -moz-transform: rotate(270deg); - -o-transform: rotate(270deg); - -ms-transform: rotate(270deg); - transform: rotate(270deg); -} - -/* legend */ -.legend-col-wrapper { - .flex(0 1 auto); - z-index: 10; - overflow-x: hidden; - overflow-y: auto; - min-width: 40px; -} - -.header { - width: 100%; - height: 26px; - margin: 0 0 14px 0; -} - -.legend { - width: 100%; - height: 26px; - margin: 0 0 14px 0; -} - -.legend-ul { - list-style-type: none; - margin: 0 0 0 14px; - padding: 0; - visibility: visible; - .display(flex); - .flex-direction(column); -} - -.legend-ul.hidden { - visibility: hidden; -} - -li.color { - min-height: 22px; - margin: 0 18px 0 18px; - padding: 3px 0 3px 0; - text-align: left; - font-size: 12px; - line-height: 13px; - color: #666; - cursor: default; - text-align: left; - .flex-grow(2); - word-wrap: break-word; - max-width: 150px; -} -.wordwrap { - word-wrap: break-word; - max-width: 150px; -} - -.dots { - width: 10px; - height: 10px; - border-radius: 50%; - float: left; - margin: 1px 0 0 -14px; -} - -.legend-toggle { - position: relative; - float: right; - right: 9px; - top: 9px; -} - -.column-labels { - color: #777; - font-size: 10pt; - font-weight: normal; - display: block; - margin: 0; - padding: 0; - padding-left: 1.0em; - line-height: 2.0em; -} - -/* histogram axis and label styles */ -.vis-canvas { -} - -.chart-bkgd { - fill: #ffffff; -} - -p.rows-label, p.columns-label { - display: block; - background: #eff3f4; - padding: 0; - margin: 0; - font-size: 9pt; - fill: #46525d; - text-align: center; - line-height: 1.9em; -} - -.charts-label { - font-size: 8pt; - fill: #848e96; -} - -.x-axis-label, .y-axis-label { - font-size: 7pt; - fill: #848e96; -} - -.tick text { - font-size: 7pt; - fill: #848e96; -} - -.axis { - shape-rendering: crispEdges; - stroke-width: 1px; -} - -.axis line, .axis path { - stroke: #ddd; - fill: none; - shape-rendering: crispEdges; -} - -.legend-box { - fill: #ffffff; -} - -.brush .extent { - stroke: #fff; - fill-opacity: .125; - shape-rendering: crispEdges; -} - -.layer .rect:hover { - stroke: 3px; -} - -.vis-tooltip { - visibility: hidden; - line-height: 1.1; - font-size: 12px; - font-weight: normal; - padding: 8px; - background: rgba(70, 82, 93, 0.95); - color: @gray-lighter; - border-radius: 4px; - position: fixed; - z-index: 120; - word-wrap: break-word; - max-width: 40%; - - table { - td,th { - padding: 2px 4px; - } - - // if there is a header, give it a border that matches - // those in the body - thead tr { - border-bottom: 1px solid @gray; - } - - // only apply to tr in the body, not the header - tbody tr { - border-top: 1px solid @gray; - &:first-child { - border-top: none; - } - } - } -} - -.rect { - stroke: transparent; - stroke-width: 2; -} -.rect.hover { - stroke: #333; -} - -/* Flex Box Layout */ -.vis-wrapper { - .display(flex); - .flex(1 1 100%); - .flex-direction(row); - margin: 10px 0 0 6px; -} - -.error { - .flex(1 1 100%); - text-align: center; - - p { - margin-top: 15%; - font-size: 18px; - text-wrap: wrap; - } -} - -/* YAxis logic */ -.y-axis-col-wrapper { - .display(flex); - .flex-direction(column); -} - -.y-axis-col { - .display(flex); - .flex-direction(row); - .flex(1 0 50px); -} - -.y-axis-spacer-block { - min-height: 45px; -} - -.y-axis-div-wrapper { - .display(flex); - .flex-direction(column); - width: 38px; - min-height: 20px; -} - -.y-axis-div { - .flex(1 1 25px); - min-width: 14px; - min-height: 14px; -} - -.y-axis-title { - min-height: 14px; - min-width: 14px; -} - -.y-axis-chart-title { - .display(flex); - .flex-direction(column); - min-height: 14px; - width: 14px; -} - -.y-axis-title text { - font-size: 11px; -} - -.chart-title { - .flex(1 1 100%); - min-height: 14px; - min-width: 14px; -} - -.chart-title text { - font-size: 11px; - fill: #848e96; -} - -.vis-col-wrapper { - .display(flex); - .flex(1 0 20px); - .flex-direction(column); -} - -.chart-wrapper { - .display(flex); - .flex(1 0 20px); - overflow: visible; - margin: 0 5px 0 0; -} - -.chart-wrapper-column { - .display(flex); - .flex(1 0 20px); - .flex-direction(row); -} - -.chart-wrapper-row { - .display(flex); - .flex-direction(column); - .flex(1 0 100%); -} - -.chart { - .flex(1 1 100%); - overflow: visible; -} - -.chart-row { - .flex(1 1); -} - -.chart-column { - .flex(1 1); -} - -.x-axis-wrapper { - .display(flex); - .flex-direction(column); - min-height: 45px; - overflow: visible; -} - -.x-axis-div-wrapper { - .display(flex); - .flex-direction(row); - min-height: 15px; -} - -.x-axis-chart-title { - .display(flex); - .flex-direction(row); - min-height: 15px; - max-height: 15px; - min-width: 20px; -} - -.x-axis-title { - min-height: 15px; - max-height: 15px; - min-width: 20px; -} - -.x-axis-title text { - font-size: 11px; -} - -.x-axis-div { - margin-top: -5px; - min-height: 20px; - min-width: 20px; -} - -.x.axis path { - display: none; -} +@import "_error"; +@import "_layout"; +@import "_legend"; +@import "_svg"; +@import "_tooltip"; diff --git a/src/kibana/components/vislib/vis.js b/src/kibana/components/vislib/vis.js index bf1c0a603fd35c..b5ed0773907aa0 100644 --- a/src/kibana/components/vislib/vis.js +++ b/src/kibana/components/vislib/vis.js @@ -84,6 +84,7 @@ define(function (require) { /** * Destroys the visualization * Removes chart and all elements associated with it. + * Removes chart and all elements associated with it. * Remove event listeners and pass destroy call down to owned objects. * * @method destroy diff --git a/src/kibana/components/vislib/visualizations/area_chart.js b/src/kibana/components/vislib/visualizations/area_chart.js index fed69b59073891..b6c1964baf6f87 100644 --- a/src/kibana/components/vislib/visualizations/area_chart.js +++ b/src/kibana/components/vislib/visualizations/area_chart.js @@ -136,38 +136,15 @@ define(function (require) { * Adds Events to SVG circles * * @method addCircleEvents - * @param circles {D3.UpdateSelection} SVG circles - * @returns {HTMLElement} circles with event listeners attached + * @param element {D3.UpdateSelection} SVG circles + * @returns {D3.Selection} circles with event listeners attached */ - AreaChart.prototype.addCircleEvents = function (circles) { + AreaChart.prototype.addCircleEvents = function (element) { var events = this.events; - var dispatch = this.events.dispatch; - circles - .on('mouseover.circle', function mouseOverCircle(d, i) { - var circle = this; - - d3.select(circle) - .classed('hover', true) - .style('stroke', '#333') - .style('cursor', 'pointer') - .style('opacity', 1); - - dispatch.hover(events.eventResponse(d, i)); - d3.event.stopPropagation(); - }) - .on('click.circle', function clickCircle(d, i) { - dispatch.click(events.eventResponse(d, i)); - d3.event.stopPropagation(); - }) - .on('mouseout.circle', function mouseOutCircle() { - var circle = this; - - d3.select(circle) - .classed('hover', false) - .style('stroke', null) - .style('opacity', 0); - }); + return element + .call(events.addHoverEvent()) + .call(events.addClickEvent()); }; /** @@ -179,7 +156,6 @@ define(function (require) { * @returns {D3.UpdateSelection} SVG with circles added */ AreaChart.prototype.addCircles = function (svg, data) { - var self = this; var color = this.handler.data.getColorFunc(); var xScale = this.handler.xAxis.xScale; var yScale = this.handler.yAxis.yScale; @@ -196,7 +172,7 @@ define(function (require) { .data(data) .enter() .append('g') - .attr('class', 'points'); + .attr('class', 'points area'); // Append the bars circles = layer @@ -237,8 +213,7 @@ define(function (require) { } return yScale(d.y0 + d.y); }) - .attr('r', circleRadius) - .style('opacity', 0); + .attr('r', circleRadius); // Add tooltip if (isTooltip) { @@ -328,9 +303,6 @@ define(function (require) { // add clipPath to hide circles when they go out of bounds self.addClipPath(svg, width, height); - // addBrush canvas - self.events.addBrush(xScale, svg); - // add path path = self.addPath(svg, layers); diff --git a/src/kibana/components/vislib/visualizations/column_chart.js b/src/kibana/components/vislib/visualizations/column_chart.js index 36268f5d35d644..939b7987124a66 100644 --- a/src/kibana/components/vislib/visualizations/column_chart.js +++ b/src/kibana/components/vislib/visualizations/column_chart.js @@ -217,76 +217,27 @@ define(function (require) { /** * Adds Events to SVG rect + * Visualization is only brushable when a brush event is added + * If a brush event is added, then a function should be returned. * * @method addBarEvents - * @param element {d3.UpdateSelection} target - * @param svg {HTMLElement} chart SVG - * @returns {HTMLElement} rect with event listeners attached + * @param element {D3.UpdateSelection} target + * @param svg {D3.UpdateSelection} chart SVG + * @returns {D3.Selection} rect with event listeners attached */ ColumnChart.prototype.addBarEvents = function (element, svg) { - var addEvent = this.events.addEvent; - var mouseOver = addEvent('mouseover.bar', this.mouseOver()); - var mouseOut = addEvent('mouseout.bar', this.mouseOut()); - var brush = addEvent('mousedown.bar', this.brush(svg)); - var click = addEvent('click.bar', this.click()); - - return element - .call(mouseOver) - .call(mouseOut) - .call(brush) - .call(click); - }; - - ColumnChart.prototype.brush = function (svg) { - var dispatch = this.events.dispatch; - var xScale = this.handler.xAxis.xScale; - var brush = this.events.addBrush(xScale, svg); - - if (dispatch.on('brush')) { - return function () { - var bar = d3.select(this); - var startX = d3.mouse(svg.node()); - var startXInv = xScale.invert(startX[0]); - - // Reset the brush value - brush.extent([startXInv, startXInv]); - - // Magic! - // Need to call brush on svg to see brush when brushing - // while on top of bars. - // Need to call brush on bar to allow the click event to be registered - svg.call(brush); - bar.call(brush); - }; + var events = this.events; + var isBrushable = (typeof events.addBrushEvent(svg) === 'function'); + var brush = isBrushable ? events.addBrushEvent(svg) : undefined; + var hover = events.addHoverEvent(); + var click = events.addClickEvent(); + var attachedEvents = element.call(hover).call(click); + + if (isBrushable) { + attachedEvents.call(brush); } - }; - - ColumnChart.prototype.mouseOver = function () { - var self = this; - - return function (d, i) { - self.events.onMouseOver.call(this, arguments); - self.events.dispatch.hover.call(this, self.events.eventResponse(d, i)); - d3.event.stopPropagation(); - }; - }; - ColumnChart.prototype.mouseOut = function () { - var self = this; - - return function () { - self.events.onMouseOut.call(this, arguments); - d3.event.stopPropagation(); - }; - }; - - ColumnChart.prototype.click = function () { - var self = this; - - return function (d, i) { - self.events.dispatch.click.call(this, self.events.eventResponse(d, i)); - d3.event.stopPropagation(); - }; + return attachedEvents; }; /** diff --git a/src/kibana/components/vislib/visualizations/line_chart.js b/src/kibana/components/vislib/visualizations/line_chart.js index c9ab1e7aece904..229eaccca75331 100644 --- a/src/kibana/components/vislib/visualizations/line_chart.js +++ b/src/kibana/components/vislib/visualizations/line_chart.js @@ -36,36 +36,15 @@ define(function (require) { * Adds Events to SVG circle * * @method addCircleEvents - * @param circles {D3.UpdateSelection} Reference to SVG circle - * @returns {D3.UpdateSelection} SVG circles with event listeners attached + * @param element{D3.UpdateSelection} Reference to SVG circle + * @returns {D3.Selection} SVG circles with event listeners attached */ - LineChart.prototype.addCircleEvents = function (circles) { + LineChart.prototype.addCircleEvents = function (element) { var events = this.events; - var dispatch = this.events.dispatch; - circles - .on('mouseover.circle', function mouseOverCircle(d, i) { - var circle = this; - - d3.select(circle) - .classed('hover', true) - .style('stroke', '#333') - .style('cursor', 'pointer'); - - dispatch.hover(events.eventResponse(d, i)); - d3.event.stopPropagation(); - }) - .on('click.circle', function clickCircle(d, i) { - dispatch.click(events.eventResponse(d, i)); - d3.event.stopPropagation(); - }) - .on('mouseout.circle', function mouseOutCircle() { - var circle = this; - - d3.select(circle) - .classed('hover', false) - .style('stroke', null); - }); + return element + .call(events.addHoverEvent()) + .call(events.addClickEvent()); }; /** @@ -93,7 +72,7 @@ define(function (require) { .data(data) .enter() .append('g') - .attr('class', 'points'); + .attr('class', 'points line'); circles = layer .selectAll('rect') @@ -274,12 +253,8 @@ define(function (require) { .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); self.addClipPath(svg, width, height); - - self.events.addBrush(xScale, svg); - lines = self.addLines(svg, data.series); circles = self.addCircles(svg, layers); - self.addCircleEvents(circles); var line = svg diff --git a/src/kibana/components/vislib/visualizations/pie_chart.js b/src/kibana/components/vislib/visualizations/pie_chart.js index ccd2407c40cb32..b024e7a89bfd5c 100644 --- a/src/kibana/components/vislib/visualizations/pie_chart.js +++ b/src/kibana/components/vislib/visualizations/pie_chart.js @@ -34,30 +34,15 @@ define(function (require) { * Adds Events to SVG paths * * @method addPathEvents - * @param path {D3.Selection} Reference to SVG path + * @param element {D3.Selection} Reference to SVG path * @returns {D3.Selection} SVG path with event listeners attached */ - PieChart.prototype.addPathEvents = function (path) { + PieChart.prototype.addPathEvents = function (element) { var events = this.events; - var dispatch = this.events.dispatch; - path - .on('mouseover.pie', function mouseOverPie(d, i) { - d3.select(this) - .classed('hover', true) - .style('cursor', 'pointer'); - - dispatch.hover(events.eventResponse(d, i)); - d3.event.stopPropagation(); - }) - .on('click.pie', function clickPie(d, i) { - dispatch.click(events.eventResponse(d, i)); - d3.event.stopPropagation(); - }) - .on('mouseout.pie', function mouseOutPie() { - d3.select(this) - .classed('hover', false); - }); + return element + .call(events.addHoverEvent()) + .call(events.addClickEvent()); }; /** @@ -148,7 +133,6 @@ define(function (require) { */ PieChart.prototype.draw = function () { var self = this; - var isEvents = this._attr.addEvents; return function (selection) { selection.each(function (data) { @@ -159,6 +143,7 @@ define(function (require) { var height = $(el).height(); var minWidth = 20; var minHeight = 20; + var path; if (width <= minWidth || height <= minHeight) { throw new errors.ContainerTooSmall(); @@ -170,11 +155,8 @@ define(function (require) { .append('g') .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); - var path = self.addPath(width, height, svg, slices); - - if (isEvents) { - self.addPathEvents(path); - } + path = self.addPath(width, height, svg, slices); + self.addPathEvents(path); return svg; }); diff --git a/tasks/config/less.js b/tasks/config/less.js index 0f3d4b56ec1d20..27c8c4fd7522b7 100644 --- a/tasks/config/less.js +++ b/tasks/config/less.js @@ -6,7 +6,6 @@ module.exports = { '<%= src %>/kibana/components/*/*.less', '<%= src %>/kibana/styles/main.less', '<%= src %>/kibana/components/vislib/styles/main.less', - '<%= src %>/kibana/components/**/*.less', '<%= plugins %>/dashboard/styles/main.less', '<%= plugins %>/discover/styles/main.less', '<%= plugins %>/settings/styles/main.less', From 95635f88b625d6bc2eaf4200c7a33114d9ede9b9 Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Thu, 30 Oct 2014 20:25:19 +0200 Subject: [PATCH 11/45] fixing issue where brush svg was being appended twice --- src/kibana/components/vislib/visualizations/column_chart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kibana/components/vislib/visualizations/column_chart.js b/src/kibana/components/vislib/visualizations/column_chart.js index 939b7987124a66..1465f2b13d0027 100644 --- a/src/kibana/components/vislib/visualizations/column_chart.js +++ b/src/kibana/components/vislib/visualizations/column_chart.js @@ -227,7 +227,7 @@ define(function (require) { */ ColumnChart.prototype.addBarEvents = function (element, svg) { var events = this.events; - var isBrushable = (typeof events.addBrushEvent(svg) === 'function'); + var isBrushable = (typeof events.dispatch.on('brush') === 'function'); var brush = isBrushable ? events.addBrushEvent(svg) : undefined; var hover = events.addHoverEvent(); var click = events.addClickEvent(); From adcd177ebb2e9338c7129d57b944f841760ee11b Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Thu, 30 Oct 2014 23:54:56 +0200 Subject: [PATCH 12/45] adding tests for the on and off methods to vis.js --- src/kibana/components/vislib/vis.js | 5 +- test/unit/specs/vislib/lib/handler.js | 8 ++ test/unit/specs/vislib/vis.js | 177 +++++++++++++++++++++++++- 3 files changed, 187 insertions(+), 3 deletions(-) diff --git a/src/kibana/components/vislib/vis.js b/src/kibana/components/vislib/vis.js index b5ed0773907aa0..e126ed975bcf1e 100644 --- a/src/kibana/components/vislib/vis.js +++ b/src/kibana/components/vislib/vis.js @@ -171,9 +171,10 @@ define(function (require) { charts.forEach(function (chart) { this.handler.disable(event, chart); }, this); - } else { - this.eventTypes.enabled.splice(eventIndex, 1); } + + // Remove event from enabled array + this.eventTypes.enabled.splice(eventIndex, 1); } return ret; diff --git a/test/unit/specs/vislib/lib/handler.js b/test/unit/specs/vislib/lib/handler.js index 96976a1140f763..371cb98d6bc65a 100644 --- a/test/unit/specs/vislib/lib/handler.js +++ b/test/unit/specs/vislib/lib/handler.js @@ -150,6 +150,14 @@ define(function (require) { // }); // }); + describe('enable Method', function () { + + }); + + describe('disable Method', function () { + + }); + describe('removeAll Method', function () { beforeEach(function () { inject(function () { diff --git a/test/unit/specs/vislib/vis.js b/test/unit/specs/vislib/vis.js index eaaf9bdbeac030..46369876359382 100644 --- a/test/unit/specs/vislib/vis.js +++ b/test/unit/specs/vislib/vis.js @@ -6,6 +6,8 @@ define(function (require) { angular.module('VisFactory', ['kibana']); describe('VisLib Vis Test Suite', function () { + var beforeEvent = 'click'; + var afterEvent = 'brush'; var Vis; var vis; var el; @@ -87,8 +89,10 @@ define(function (require) { }); afterEach(function () { - el.remove(); + vis.off(beforeEvent); + vis.off(afterEvent); vis.destroy(); + el.remove(); }); describe('render Method', function () { @@ -145,5 +149,176 @@ define(function (require) { expect(vis.get('type')).to.be('histogram'); }); }); + + describe('on Method', function () { + var events = [ + beforeEvent, + afterEvent + ]; + var listeners; + var listener1; + var listener2; + + beforeEach(function () { + listeners = []; + listener1 = function (e) { + console.log(e, 'listener1'); + }; + listener2 = function (e) { + console.log(e, 'listener2'); + }; + listeners.push(listener1); + listeners.push(listener2); + + // Add event and listeners to chart + listeners.forEach(function (listener) { + vis.on(beforeEvent, listener); + }); + + // Render chart + vis.render(data); + + // Add event after charts have rendered + listeners.forEach(function (listener) { + vis.on(afterEvent, listener); + }); + }); + + afterEach(function () { + vis.off(beforeEvent); + vis.off(afterEvent); + }); + + it('should add an event and its listeners to the _listeners object', function () { + // Test for presence of beforeEvent in _listener object + expect(vis._listeners[beforeEvent] instanceof Array).to.be(true); + + vis._listeners[beforeEvent].forEach(function (listener, i) { + expect(typeof listener.handler).to.be('function'); + expect(listener.handler).to.be(listeners[i]); + }); + + vis._listeners[afterEvent].forEach(function (listener, i) { + expect(typeof listener.handler).to.be('function'); + expect(listener.handler).to.be(listeners[i]); + }); + }); + + it('should add an event to the eventTypes.enabled array', function () { + vis.eventTypes.enabled.forEach(function (eventType, i) { + expect(eventType).to.be(events[i]); + }); + }); + + it('should attach an event and its listeners to the chart', function () { + var charts = vis.handler.charts; + + charts.forEach(function (chart, i) { + expect(typeof chart.on(beforeEvent) === 'function'); + expect(typeof chart.on(afterEvent) === 'function'); + expect(chart.on(beforeEvent) === listeners[i]); + expect(chart.on(afterEvent) === listeners[i]); + }); + }); + }); + + describe('off Method', function () { + var listeners; + var listener1; + var listener2; + + beforeEach(function () { + listeners = []; + listener1 = function (e) { + console.log(e, 'listener1'); + }; + listener2 = function (e) { + console.log(e, 'listener2'); + }; + listeners.push(listener1); + listeners.push(listener2); + + // Add event and listeners to chart + listeners.forEach(function (listener) { + vis.on(beforeEvent, listener); + }); + + // Turn off event listener before chart rendered + vis.off(beforeEvent, listener1); + + // Render chart + vis.render(data); + + // Add event after charts have rendered + listeners.forEach(function (listener) { + vis.on(afterEvent, listener); + }); + + // Turn off event listener after chart is rendered + vis.off(afterEvent, listener1); + }); + + afterEach(function () { + vis.off(beforeEvent); + vis.off(afterEvent); + }); + + it('should remove a listener from the _listeners[event] array', function () { + var charts = vis.handler.charts; + + expect(vis._listeners[beforeEvent].length).to.be(1); + expect(vis._listeners[afterEvent].length).to.be(1); + + // should still have the 2 events in the eventTypes enabled array + expect(vis.eventTypes.enabled.length).to.be(2); + + // Test that listener that was not removed is still present + vis._listeners[beforeEvent].forEach(function (listener) { + expect(typeof listener.handler).to.be('function'); + expect(listener.handler).to.be(listener2); + }); + + vis._listeners[afterEvent].forEach(function (listener) { + expect(typeof listener.handler).to.be('function'); + expect(listener.handler).to.be(listener2); + }); + + // Events should still be attached to charts + charts.forEach(function (chart) { + expect(typeof chart.on(beforeEvent)).to.be('function'); + expect(typeof chart.on(afterEvent)).to.be('function'); + }); + }); + + it('should remove the event and all listeners when only event passed an argument', function () { + var charts = vis.handler.charts; + vis.off(afterEvent); + + // should remove 'brush' from _listeners object + expect(vis._listeners[afterEvent]).to.be(undefined); + + // should remove 'brush' from eventTypes.enabled array + expect(vis.eventTypes.enabled.length).to.be(1); + + // should remove the event from the charts + charts.forEach(function (chart) { + expect(typeof chart.on(afterEvent)).to.be('undefined'); + }); + }); + + it('should remove the event from the eventTypes.enabled array as well as ' + + 'from the chart when the _listeners array has a length of 0', function () { + var charts = vis.handler.charts; + vis.off(afterEvent, listener2); + + expect(vis._listeners[afterEvent].length).to.be(0); + expect(vis.eventTypes.enabled.length).to.be(1); + + charts.forEach(function (chart) { + expect(typeof chart.on(afterEvent)).to.be('undefined'); + }); + }); + }); + }); }); From b1be135432d343c22aac21e1489cfa7d46acb4e2 Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Fri, 31 Oct 2014 00:12:50 +0200 Subject: [PATCH 13/45] adding tests for handler.enable and handler.disable methods --- test/unit/specs/vislib/lib/handler.js | 124 ++++++++++++++++---------- 1 file changed, 77 insertions(+), 47 deletions(-) diff --git a/test/unit/specs/vislib/lib/handler.js b/test/unit/specs/vislib/lib/handler.js index 371cb98d6bc65a..d8c1e0927121be 100644 --- a/test/unit/specs/vislib/lib/handler.js +++ b/test/unit/specs/vislib/lib/handler.js @@ -8,15 +8,6 @@ define(function (require) { angular.module('HandlerFactory', ['kibana']); describe('VisLib Handler Test Suite', function () { - var Vis; - var Data; - var Handler; - var handler; - var ColumnHandler; - var vis; - var el; - var example; - var config; var data = { hits : 621, label : '', @@ -81,7 +72,15 @@ define(function (require) { xAxisLabel: 'Date Histogram', yAxisLabel: 'Count' }; - + var Vis; + var Data; + var Handler; + var handler; + var ColumnHandler; + var vis; + var el; + var config; + var events; beforeEach(function () { module('VisFactory'); @@ -106,62 +105,93 @@ define(function (require) { addLegend: true }; - vis = new Vis(el[0][0], config); - vis.data = data; + events = [ + 'click', + 'brush' + ]; - handler = ColumnHandler(vis); - -// handler.render(data); + vis = new Vis(el[0][0], config); + vis.render(data); }); }); afterEach(function () { + vis.destroy(); el.remove(); }); -// describe('render Method', function () { -// it('should instantiate all constructors ', function () { -// expect(!!handler.layout).to.be(true); -// expect(!!handler.legend).to.be(true); -// expect(!!handler.tooltip).to.be(true); -// expect(!!handler.xAxis).to.be(true); -// expect(!!handler.yAxis).to.be(true); -// expect(!!handler.axisTitle).to.be(true); -// expect(!!handler.chartTitle).to.be(true); -// }); -// -// it('should append all DOM Elements for the visualization', function () { -// expect($('.vis-wrapper').length).to.be(1); -// expect($('.y-axis-col-wrapper').length).to.be(1); -// expect($('.vis-col-wrapper').length).to.be(1); -// expect($('.legend-col-wrapper').length).to.be(1); -// expect($('.k4tip').length).to.be(1); -// expect($('.y-axis-col').length).to.be(1); -// expect($('.y-axis-title').length).to.be(1); -// expect($('.y-axis-chart-title').length).to.be(0); -// expect($('.y-axis-div-wrapper').length).to.be(1); -// expect($('.y-axis-spacer-block').length).to.be(1); -// expect($('.chart-wrapper').length).to.be(1); -// expect($('.x-axis-wrapper').length).to.be(1); -// expect($('.x-axis-div-wrapper').length).to.be(1); -// expect($('.x-axis-chart-title').length).to.be(0); -// expect($('.x-axis-title').length).to.be(1); -// expect($('svg').length).to.be(5); -// }); -// }); + describe('render Method', function () { + it('should instantiate all constructors ', function () { + expect(!!vis.handler.layout).to.be(true); + expect(!!vis.handler.xAxis).to.be(true); + expect(!!vis.handler.yAxis).to.be(true); + expect(!!vis.handler.axisTitle).to.be(true); + expect(!!vis.handler.chartTitle).to.be(true); + }); + + it('should append all DOM Elements for the visualization', function () { + expect($('.vis-wrapper').length).to.be(1); + expect($('.y-axis-col-wrapper').length).to.be(1); + expect($('.vis-col-wrapper').length).to.be(1); + expect($('.y-axis-col').length).to.be(1); + expect($('.y-axis-title').length).to.be(1); + expect($('.y-axis-chart-title').length).to.be(0); + expect($('.y-axis-div-wrapper').length).to.be(1); + expect($('.y-axis-spacer-block').length).to.be(1); + expect($('.chart-wrapper').length).to.be(1); + expect($('.x-axis-wrapper').length).to.be(1); + expect($('.x-axis-div-wrapper').length).to.be(1); + expect($('.x-axis-chart-title').length).to.be(0); + expect($('.x-axis-title').length).to.be(1); + expect($('svg').length).to.be(5); + }); + }); describe('enable Method', function () { + var charts; + + beforeEach(function () { + charts = vis.handler.charts; + charts.forEach(function (chart) { + events.forEach(function (event) { + vis.handler.enable(event, chart); + }); + }); + }); + + it('should add events to chart and emit to the Events class', function () { + charts.forEach(function (chart, i) { + expect(typeof chart.on(events[i])).to.be('function'); + }); + }); }); describe('disable Method', function () { + var charts; + + beforeEach(function () { + charts = vis.handler.charts; + + charts.forEach(function (chart) { + events.forEach(function (event) { + vis.handler.disable(event, chart); + }); + }); + }); + + it('should remove events from the chart', function () { + charts.forEach(function (chart, i) { + expect(typeof chart.on(events[i])).to.be('undefined'); + }); + }); }); describe('removeAll Method', function () { beforeEach(function () { inject(function () { - handler.removeAll(el[0][0]); + vis.handler.removeAll(el[0][0]); }); }); @@ -172,7 +202,7 @@ define(function (require) { describe('error Method', function () { beforeEach(function () { - handler.error('This is an error!'); + vis.handler.error('This is an error!'); }); it('should return an error classed DOM element with a text message', function () { From 0b3199ae55b09f1276773b0b8bb717b1fcd1816e Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Fri, 31 Oct 2014 00:15:24 +0200 Subject: [PATCH 14/45] commenting out render method testing in handler, needs refactoring --- test/unit/specs/vislib/lib/handler.js | 48 +++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/test/unit/specs/vislib/lib/handler.js b/test/unit/specs/vislib/lib/handler.js index d8c1e0927121be..5892b8d4309c62 100644 --- a/test/unit/specs/vislib/lib/handler.js +++ b/test/unit/specs/vislib/lib/handler.js @@ -121,30 +121,30 @@ define(function (require) { }); describe('render Method', function () { - it('should instantiate all constructors ', function () { - expect(!!vis.handler.layout).to.be(true); - expect(!!vis.handler.xAxis).to.be(true); - expect(!!vis.handler.yAxis).to.be(true); - expect(!!vis.handler.axisTitle).to.be(true); - expect(!!vis.handler.chartTitle).to.be(true); - }); - - it('should append all DOM Elements for the visualization', function () { - expect($('.vis-wrapper').length).to.be(1); - expect($('.y-axis-col-wrapper').length).to.be(1); - expect($('.vis-col-wrapper').length).to.be(1); - expect($('.y-axis-col').length).to.be(1); - expect($('.y-axis-title').length).to.be(1); - expect($('.y-axis-chart-title').length).to.be(0); - expect($('.y-axis-div-wrapper').length).to.be(1); - expect($('.y-axis-spacer-block').length).to.be(1); - expect($('.chart-wrapper').length).to.be(1); - expect($('.x-axis-wrapper').length).to.be(1); - expect($('.x-axis-div-wrapper').length).to.be(1); - expect($('.x-axis-chart-title').length).to.be(0); - expect($('.x-axis-title').length).to.be(1); - expect($('svg').length).to.be(5); - }); + //it('should instantiate all constructors ', function () { + // expect(!!vis.handler.layout).to.be(true); + // expect(!!vis.handler.xAxis).to.be(true); + // expect(!!vis.handler.yAxis).to.be(true); + // expect(!!vis.handler.axisTitle).to.be(true); + // expect(!!vis.handler.chartTitle).to.be(true); + //}); + // + //it('should append all DOM Elements for the visualization', function () { + // expect($('.vis-wrapper').length).to.be(1); + // expect($('.y-axis-col-wrapper').length).to.be(1); + // expect($('.vis-col-wrapper').length).to.be(1); + // expect($('.y-axis-col').length).to.be(1); + // expect($('.y-axis-title').length).to.be(1); + // expect($('.y-axis-chart-title').length).to.be(0); + // expect($('.y-axis-div-wrapper').length).to.be(1); + // expect($('.y-axis-spacer-block').length).to.be(1); + // expect($('.chart-wrapper').length).to.be(1); + // expect($('.x-axis-wrapper').length).to.be(1); + // expect($('.x-axis-div-wrapper').length).to.be(1); + // expect($('.x-axis-chart-title').length).to.be(0); + // expect($('.x-axis-title').length).to.be(1); + // expect($('svg').length).to.be(5); + //}); }); describe('enable Method', function () { From b1a1003546a2a4e0d24bac8401078bf30294a1f3 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Fri, 31 Oct 2014 17:15:53 -0700 Subject: [PATCH 15/45] Close #1532 - Remove warbler from dist Gemfile --- Gemfile | 9 +++++++ Gemfile.lock | 59 +++++++++++++++++++++++++++++++++++++++++ src/server/Gemfile | 2 -- src/server/Gemfile.lock | 22 --------------- 4 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 Gemfile create mode 100644 Gemfile.lock diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000000000..4c7990e562ee19 --- /dev/null +++ b/Gemfile @@ -0,0 +1,9 @@ +source "https://rubygems.org" + +gem 'sinatra', :require => 'sinatra/base' +gem 'sinatra-contrib' +gem 'puma' +gem 'warbler' +gem 'elasticsearch' +gem 'rack-reverse-proxy', :require => 'rack/reverse_proxy' +gem 'colorize' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000000000..9d834b63133d57 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,59 @@ +GEM + remote: https://rubygems.org/ + specs: + backports (3.6.0) + colorize (0.7.3) + elasticsearch (1.0.4) + elasticsearch-api (= 1.0.4) + elasticsearch-transport (= 1.0.4) + elasticsearch-api (1.0.4) + multi_json + elasticsearch-transport (1.0.4) + faraday + multi_json + faraday (0.9.0) + multipart-post (>= 1.2, < 3) + jruby-jars (1.7.13) + jruby-rack (1.1.16) + multi_json (1.10.1) + multipart-post (2.0.0) + puma (2.9.0) + rack (>= 1.1, < 2.0) + rack (1.5.2) + rack-protection (1.5.3) + rack + rack-reverse-proxy (0.4.4) + rack (>= 1.0.0) + rack-test (0.6.2) + rack (>= 1.0) + rake (10.1.0) + rubyzip (1.1.6) + sinatra (1.4.5) + rack (~> 1.4) + rack-protection (~> 1.4) + tilt (~> 1.3, >= 1.3.4) + sinatra-contrib (1.4.2) + backports (>= 2.0) + multi_json + rack-protection + rack-test + sinatra (~> 1.4.0) + tilt (~> 1.3) + tilt (1.4.1) + warbler (1.4.4) + jruby-jars (>= 1.5.6, < 2.0) + jruby-rack (>= 1.0.0) + rake (>= 0.9.6) + rubyzip (>= 0.9, < 1.2) + +PLATFORMS + ruby + +DEPENDENCIES + colorize + elasticsearch + puma + rack-reverse-proxy + sinatra + sinatra-contrib + warbler diff --git a/src/server/Gemfile b/src/server/Gemfile index 4c7990e562ee19..b9a532874cf683 100644 --- a/src/server/Gemfile +++ b/src/server/Gemfile @@ -3,7 +3,5 @@ source "https://rubygems.org" gem 'sinatra', :require => 'sinatra/base' gem 'sinatra-contrib' gem 'puma' -gem 'warbler' -gem 'elasticsearch' gem 'rack-reverse-proxy', :require => 'rack/reverse_proxy' gem 'colorize' diff --git a/src/server/Gemfile.lock b/src/server/Gemfile.lock index c2fff6b16174c9..4dca9f0b96deb3 100644 --- a/src/server/Gemfile.lock +++ b/src/server/Gemfile.lock @@ -3,20 +3,7 @@ GEM specs: backports (3.6.0) colorize (0.7.3) - elasticsearch (1.0.4) - elasticsearch-api (= 1.0.4) - elasticsearch-transport (= 1.0.4) - elasticsearch-api (1.0.4) - multi_json - elasticsearch-transport (1.0.4) - faraday - multi_json - faraday (0.9.0) - multipart-post (>= 1.2, < 3) - jruby-jars (1.7.13) - jruby-rack (1.1.16) multi_json (1.10.1) - multipart-post (2.0.0) puma (2.9.0) rack (>= 1.1, < 2.0) puma (2.9.0-java) @@ -28,8 +15,6 @@ GEM rack (>= 1.0.0) rack-test (0.6.2) rack (>= 1.0) - rake (10.1.0) - rubyzip (1.1.6) sinatra (1.4.5) rack (~> 1.4) rack-protection (~> 1.4) @@ -42,11 +27,6 @@ GEM sinatra (~> 1.4.0) tilt (~> 1.3) tilt (1.4.1) - warbler (1.4.4) - jruby-jars (>= 1.5.6, < 2.0) - jruby-rack (>= 1.0.0) - rake (>= 0.9.6) - rubyzip (>= 0.9, < 1.2) PLATFORMS java @@ -54,9 +34,7 @@ PLATFORMS DEPENDENCIES colorize - elasticsearch puma rack-reverse-proxy sinatra sinatra-contrib - warbler From d9d49effb01256feff5e8acdb55b2443c5668b53 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 07:52:49 -0700 Subject: [PATCH 16/45] [aggResp/tabify/buckets] properly calculate bucket count for array or object buckets --- .../components/agg_response/tabify/_buckets.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/kibana/components/agg_response/tabify/_buckets.js b/src/kibana/components/agg_response/tabify/_buckets.js index 8e51703e18ce8d..60805384194bb9 100644 --- a/src/kibana/components/agg_response/tabify/_buckets.js +++ b/src/kibana/components/agg_response/tabify/_buckets.js @@ -5,15 +5,25 @@ define(function (require) { function Buckets(aggResp) { aggResp = aggResp || false; this.buckets = aggResp.buckets || []; - this.length = this.buckets.length; this.objectMode = _.isPlainObject(this.buckets); + + if (this.objectMode) { + this._keys = _.keys(this.buckets); + this.length = this._keys.length; + } else { + this.length = this.buckets.length; + } } Buckets.prototype.forEach = function (fn) { + var buckets = this.buckets; + if (this.objectMode) { - _.forOwn(this.buckets, fn); + this._keys.forEach(function (key) { + fn(buckets[key], key); + }); } else { - this.buckets.forEach(function (bucket) { + buckets.forEach(function (bucket) { fn(bucket, bucket.key); }); } From aa17e2686e54f6c9b66ecdcd7fa70841e07dde98 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 09:08:55 -0700 Subject: [PATCH 17/45] [aggResp/tabify] only use the count if the agg is actually count --- .../components/agg_response/tabify/tabify_agg_response.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kibana/components/agg_response/tabify/tabify_agg_response.js b/src/kibana/components/agg_response/tabify/tabify_agg_response.js index 4544ade7d80d78..b1702da396b2ed 100644 --- a/src/kibana/components/agg_response/tabify/tabify_agg_response.js +++ b/src/kibana/components/agg_response/tabify/tabify_agg_response.js @@ -56,7 +56,8 @@ define(function (require) { } break; case 'metrics': - write.cell(aggResp ? metricValue(aggResp) : bucketCount(bucket), function () { + var value = (agg.type.name === 'count') ? bucketCount(bucket) : metricValue(aggResp); + write.cell(value, function () { if (!write.aggStack.length) { // row complete write.row(); From c4e83b7e65f4813f37f9a5717e97c316bcd41523 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 09:09:41 -0700 Subject: [PATCH 18/45] [aggResp/ResponseWriter] privitize the table function, only exposed for tests --- .../components/agg_response/tabify/_response_writer.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/kibana/components/agg_response/tabify/_response_writer.js b/src/kibana/components/agg_response/tabify/_response_writer.js index e90995e83069ed..907ce3253a2fed 100644 --- a/src/kibana/components/agg_response/tabify/_response_writer.js +++ b/src/kibana/components/agg_response/tabify/_response_writer.js @@ -33,7 +33,7 @@ define(function (require) { * @param {any} key - the bucketKey that this table relates to * @return {Table/TableGroup} table - the created table */ - TabbedAggResponseWriter.prototype.table = function (group, agg, key) { + TabbedAggResponseWriter.prototype._table = function (group, agg, key) { var Class = (group) ? TableGroup : Table; var table = new Class(); @@ -76,7 +76,7 @@ define(function (require) { // 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); + if (!TableGroup) TableGroup = self._table(true, agg, key); // push the split onto the stack so that it will receive written tables self.splitStack.unshift(TableGroup); @@ -119,10 +119,11 @@ define(function (require) { } var split = this.splitStack[0]; - var table = split.tables[0] || this.table(false); + var table = split.tables[0] || this._table(false); while (cells.length < this.columns.length) cells.push(''); table.rows.push(cells); + return table; }; /** From 6f6e2a858adad5932001f55a7732e2022bd18311 Mon Sep 17 00:00:00 2001 From: Shelby Sturgis Date: Mon, 3 Nov 2014 19:18:49 +0200 Subject: [PATCH 19/45] fixing conflict due to merge with master in legend.js --- src/kibana/components/vislib/lib/legend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kibana/components/vislib/lib/legend.js b/src/kibana/components/vislib/lib/legend.js index 367c63272bd3ca..339cf877b4127a 100644 --- a/src/kibana/components/vislib/lib/legend.js +++ b/src/kibana/components/vislib/lib/legend.js @@ -112,7 +112,7 @@ define(function (require) { var headerIcon = visEl.select('.legend-toggle'); // toggle - visEl.select(headerIcon) + headerIcon .on('click', function legendClick() { if (self._attr.isOpen) { // close legend From 665cbc013c3c0f29a14314b68a875d60c93ae430 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 10:31:31 -0700 Subject: [PATCH 20/45] [aggResponse/tabify] added tests --- .../agg_response/tabify/_response_writer.js | 26 +- .../agg_response/tabify/_buckets.js | 59 ++++ .../agg_response/tabify/_get_columns.js | 124 +++++++ .../agg_response/tabify/_integration.js | 114 +++++++ .../agg_response/tabify/_response_writer.js | 312 ++++++++++++++++++ .../components/agg_response/tabify/_table.js | 110 ++++++ .../agg_response/tabify/_table_group.js | 18 + .../tabify/tabify_agg_response.js | 40 +-- 8 files changed, 767 insertions(+), 36 deletions(-) create mode 100644 test/unit/specs/components/agg_response/tabify/_buckets.js create mode 100644 test/unit/specs/components/agg_response/tabify/_get_columns.js create mode 100644 test/unit/specs/components/agg_response/tabify/_integration.js create mode 100644 test/unit/specs/components/agg_response/tabify/_response_writer.js create mode 100644 test/unit/specs/components/agg_response/tabify/_table.js create mode 100644 test/unit/specs/components/agg_response/tabify/_table_group.js diff --git a/src/kibana/components/agg_response/tabify/_response_writer.js b/src/kibana/components/agg_response/tabify/_response_writer.js index 907ce3253a2fed..8a3167c9dcce88 100644 --- a/src/kibana/components/agg_response/tabify/_response_writer.js +++ b/src/kibana/components/agg_response/tabify/_response_writer.js @@ -68,9 +68,7 @@ define(function (require) { throw new Error('attempted to split when splitting is disabled'); } - _.pull(self.columns, _.find(self.columns, function (col) { - return col.aggConfig === agg; - })); + self._removeAggFromColumns(agg); buckets.forEach(function (bucket, key) { // find the existing split that we should extend @@ -87,6 +85,28 @@ define(function (require) { }); }; + 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.vis.isHierarchical()) 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); + }; + /** * Push a value into the row, then run a block. Once the block is * complete the value is pulled from the stack. diff --git a/test/unit/specs/components/agg_response/tabify/_buckets.js b/test/unit/specs/components/agg_response/tabify/_buckets.js new file mode 100644 index 00000000000000..8ce32253ee4eb0 --- /dev/null +++ b/test/unit/specs/components/agg_response/tabify/_buckets.js @@ -0,0 +1,59 @@ +define(function (require) { + return ['Buckets wrapper', function () { + var Buckets; + + beforeEach(module('kibana')); + beforeEach(inject(function (Private, $injector) { + Buckets = Private(require('components/agg_response/tabify/_buckets')); + })); + + + function test(aggResp, count, keys) { + it('reads the length', function () { + var buckets = new Buckets(aggResp); + expect(buckets).to.have.length(count); + }); + + it('itterates properly, passing in the key', function () { + var buckets = new Buckets(aggResp); + var keysSent = []; + buckets.forEach(function (bucket, key) { + keysSent.push(key); + }); + + expect(keysSent).to.have.length(count); + expect(keysSent).to.eql(keys); + }); + } + + describe('with object style buckets', function () { + var aggResp = { + buckets: { + '0-100': {}, + '100-200': {}, + '200-300': {} + } + }; + + var count = 3; + var keys = ['0-100', '100-200', '200-300']; + + test(aggResp, count, keys); + }); + + describe('with array style buckets', function () { + var aggResp = { + buckets: [ + { key: '0-100', value: {} }, + { key: '100-200', value: {} }, + { key: '200-300', value: {} } + ] + }; + + var count = 3; + var keys = ['0-100', '100-200', '200-300']; + + test(aggResp, count, keys); + }); + }]; +}); \ No newline at end of file diff --git a/test/unit/specs/components/agg_response/tabify/_get_columns.js b/test/unit/specs/components/agg_response/tabify/_get_columns.js new file mode 100644 index 00000000000000..9e92765b105863 --- /dev/null +++ b/test/unit/specs/components/agg_response/tabify/_get_columns.js @@ -0,0 +1,124 @@ +define(function (require) { + return ['get columns', function () { + var getColumns; + var Vis; + var indexPattern; + + beforeEach(module('kibana')); + beforeEach(inject(function (Private, $injector) { + getColumns = Private(require('components/agg_response/tabify/_get_columns')); + Vis = Private(require('components/vis/vis')); + indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); + })); + + it('should inject a count metric if no aggs exist', function () { + var vis = new Vis(indexPattern, { + type: 'pie' + }); + while (vis.aggs.length) vis.aggs.pop(); + var columns = getColumns(vis); + + expect(columns).to.have.length(1); + expect(columns[0]).to.have.property('aggConfig'); + expect(columns[0].aggConfig.type).to.have.property('name', 'count'); + }); + + it('should inject a count metric if only buckets exist', function () { + var vis = new Vis(indexPattern, { + type: 'pie', + aggs: [ + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } } + ] + }); + + var columns = getColumns(vis); + + expect(columns).to.have.length(2); + expect(columns[1]).to.have.property('aggConfig'); + expect(columns[1].aggConfig.type).to.have.property('name', 'count'); + }); + + it('should inject the metric after each bucket if the vis is hierarchical', function () { + var vis = new Vis(indexPattern, { + type: 'pie', + aggs: [ + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } } + ] + }); + + var columns = getColumns(vis); + + expect(columns).to.have.length(8); + columns.forEach(function (column, i) { + expect(column).to.have.property('aggConfig'); + expect(column.aggConfig.type).to.have.property('name', i % 2 ? 'count' : 'date_histogram'); + }); + }); + + it('should inject the multiple metrics after each bucket if the vis is hierarchical', function () { + var vis = new Vis(indexPattern, { + type: 'pie', + aggs: [ + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'sum', schema: 'metric', params: { field: 'bytes' } }, + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } } + ] + }); + + var columns = getColumns(vis); + + function checkColumns(column, i) { + expect(column).to.have.property('aggConfig'); + switch (i) { + case 0: + expect(column.aggConfig.type).to.have.property('name', 'date_histogram'); + break; + case 1: + expect(column.aggConfig.type).to.have.property('name', 'avg'); + break; + case 2: + expect(column.aggConfig.type).to.have.property('name', 'sum'); + break; + } + } + + expect(columns).to.have.length(12); + for (var i = 0; i < columns.length; i += 3) { + var counts = { buckets: 0, metrics: 0 }; + columns.slice(i, i + 3).forEach(checkColumns); + } + }); + + it('should put all metrics at the end of the columns if the vis is not hierarchical', function () { + var vis = new Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'sum', schema: 'metric', params: { field: 'bytes' } }, + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } } + ] + }); + + var columns = getColumns(vis); + expect(columns).to.have.length(6); + + // sum should be last + expect(columns.pop().aggConfig.type).to.have.property('name', 'sum'); + // avg should be before that + expect(columns.pop().aggConfig.type).to.have.property('name', 'avg'); + // the rest are date_histograms + while (columns.length) { + expect(columns.pop().aggConfig.type).to.have.property('name', 'date_histogram'); + } + }); + }]; +}); \ No newline at end of file diff --git a/test/unit/specs/components/agg_response/tabify/_integration.js b/test/unit/specs/components/agg_response/tabify/_integration.js new file mode 100644 index 00000000000000..163e1e232ea9ed --- /dev/null +++ b/test/unit/specs/components/agg_response/tabify/_integration.js @@ -0,0 +1,114 @@ +define(function (require) { + return ['tabifyAggResponse Simple Integration', function () { + var _ = require('lodash'); + var fixtures = require('fixtures/fake_hierarchical_data'); + + var Vis; + var Buckets; + var indexPattern; + var tabifyAggResponse; + + beforeEach(module('kibana')); + beforeEach(inject(function (Private, $injector) { + tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); + Vis = Private(require('components/vis/vis')); + indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); + })); + + function normalizeIds(vis) { + vis.aggs.forEach(function (agg, i) { + agg.id = 'agg_' + (i + 1); + }); + } + + it('transforms a simple response properly', function () { + var vis = new Vis(indexPattern, { + type: 'histogram', + aggs: [] + }); + normalizeIds(vis); + + var resp = tabifyAggResponse(vis, fixtures.metricOnly, { canSplit: false }); + + expect(resp).to.not.have.property('tables'); + expect(resp).to.have.property('rows').and.property('columns'); + expect(resp.rows).to.have.length(1); + expect(resp.columns).to.have.length(1); + + expect(resp.rows[0]).to.eql([1000]); + expect(resp.columns[0]).to.have.property('aggConfig', vis.aggs[0]); + }); + + it('transforms a complex response properly', function () { + var vis = new Vis(indexPattern, { + type: 'pie', + aggs: [ + { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { type: 'terms', schema: 'split', params: { field: 'extension' } }, + { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, + { type: 'terms', schema: 'segment', params: { field: 'machine.os' } } + ] + }); + normalizeIds(vis); + + var avg = vis.aggs[0]; + var ext = vis.aggs[1]; + var src = vis.aggs[2]; + var os = vis.aggs[3]; + var esResp = _.cloneDeep(fixtures.threeTermBuckets); + // remove the buckets for css in MX + esResp.aggregations.agg_2.buckets[1].agg_3.buckets[0].agg_4.buckets = []; + var resp = tabifyAggResponse(vis, esResp); + + function verifyExtensionSplit(tableGroup, key) { + expect(tableGroup).to.have.property('tables'); + expect(tableGroup).to.have.property('aggConfig', ext); + expect(tableGroup).to.have.property('key', key); + expect(tableGroup.tables).to.have.length(1); + + tableGroup.tables.forEach(function (table) { + verifyTable(table, key); + }); + } + + function verifyTable(table, splitKey) { + expect(table.columns).to.have.length(4); + expect(table.columns[0]).to.have.property('aggConfig', src); + expect(table.columns[1]).to.have.property('aggConfig', avg); + expect(table.columns[2]).to.have.property('aggConfig', os); + expect(table.columns[3]).to.have.property('aggConfig', avg); + + table.rows.forEach(function (row) { + expect(row).to.have.length(4); + + // two character country code + expect(row[0]).to.be.a('string'); + expect(row[0]).to.have.length(2); + + // average bytes + expect(row[1]).to.be.a('number'); + expect(row[1] === 0 || row[1] > 1000).to.be.ok(); + + if (splitKey === 'css' && row[0] === 'MX') { + // removed these buckets, we should get empty values + expect(row[2]).to.be(''); + expect(row[3]).to.be(''); + } else { + // os + expect(row[2]).to.match(/^(win|mac|linux)$/); + + // average bytes + expect(row[3]).to.be.a('number'); + expect(row[3] === 0 || row[3] > 1000).to.be.ok(); + } + }); + } + + expect(resp).to.have.property('tables'); + expect(resp.tables).to.have.length(3); + verifyExtensionSplit(resp.tables[0], 'png'); + verifyExtensionSplit(resp.tables[1], 'css'); + verifyExtensionSplit(resp.tables[2], 'html'); + }); + }]; +}); \ No newline at end of file diff --git a/test/unit/specs/components/agg_response/tabify/_response_writer.js b/test/unit/specs/components/agg_response/tabify/_response_writer.js new file mode 100644 index 00000000000000..f86e6cb017b43f --- /dev/null +++ b/test/unit/specs/components/agg_response/tabify/_response_writer.js @@ -0,0 +1,312 @@ +define(function (require) { + return ['ResponseWriter class', function () { + var _ = require('lodash'); + var Vis; + var Table; + var Buckets; + var TableGroup; + var indexPattern; + var ResponseWriter; + + beforeEach(module('kibana')); + beforeEach(inject(function (Private, $injector) { + ResponseWriter = Private(require('components/agg_response/tabify/_response_writer')); + TableGroup = Private(require('components/agg_response/tabify/_table_group')); + Buckets = Private(require('components/agg_response/tabify/_buckets')); + Table = Private(require('components/agg_response/tabify/_table')); + Vis = Private(require('components/vis/vis')); + indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); + })); + + describe('Constructor', function () { + it('gets the columns for the vis', function () { + var aggs = [ + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'terms', schema: 'segment', params: { field: 'extension' } }, + { type: 'avg', schema: 'metric', params: { field: '@timestamp' } } + ]; + + var flatVis = new Vis(indexPattern, { type: 'histogram', aggs: aggs }); + var hierVis = new Vis(indexPattern, { type: 'pie', aggs: aggs }); + + var writer = new ResponseWriter(flatVis); + expect(writer.columns).to.be.an('array'); + expect(writer.columns).to.have.length(3); + + writer = new ResponseWriter(hierVis); + expect(writer.columns).to.be.an('array'); + expect(writer.columns).to.have.length(4); + }); + + it('collects the aggConfigs from each column in aggStack', function () { + var vis = new Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'date_histogram', schema: 'segment', params: { field: '@timestamp' } }, + { type: 'terms', schema: 'segment', params: { field: 'extension' } }, + { type: 'avg', schema: 'metric', params: { field: '@timestamp' } } + ] + }); + + var writer = new ResponseWriter(vis); + expect(writer.aggStack).to.be.an('array'); + expect(writer.aggStack).to.have.length(3); + expect(writer.aggStack[0].type.name).to.be('date_histogram'); + expect(writer.aggStack[1].type.name).to.be('terms'); + expect(writer.aggStack[2].type.name).to.be('avg'); + }); + + it('sets canSplit=true by default', function () { + var vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); + var writer = new ResponseWriter(vis); + expect(writer).to.have.property('canSplit', true); + }); + + it('sets canSplit=false when config says to', function () { + var vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); + var writer = new ResponseWriter(vis, { canSplit: false }); + expect(writer).to.have.property('canSplit', false); + }); + + it('starts off with a root TableGroup', function () { + var vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); + + var writer = new ResponseWriter(vis); + expect(writer.root).to.be.a(TableGroup); + expect(writer.splitStack).to.be.an('array'); + expect(writer.splitStack).to.have.length(1); + expect(writer.splitStack[0]).to.be(writer.root); + }); + }); + + describe('#response()', function () { + it('returns the root TableGroup if splitting', function () { + var vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); + var writer = new ResponseWriter(vis); + expect(writer.response()).to.be(writer.root); + }); + + it('returns the first table if not splitting', function () { + var vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); + var writer = new ResponseWriter(vis, { canSplit: false }); + var table = writer._table(); + expect(writer.response()).to.be(table); + }); + + it('adds columns to all of the tables', function () { + var vis = new Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'terms', params: { field: '_type' }, schema: 'split' }, + { type: 'count', schema: 'metric' } + ] + }); + var buckets = new Buckets({ buckets: [ { key: 'nginx' }, { key: 'apache' } ] }); + var writer = new ResponseWriter(vis); + var tables = []; + + writer.split(vis.aggs[0], buckets, function () { + writer.cell(100, function () { + tables.push(writer.row()); + }); + }); + + tables.forEach(function (table) { + expect(table.columns == null).to.be(true); + }); + + var resp = writer.response(); + expect(resp).to.be.a(TableGroup); + expect(resp.tables).to.have.length(2); + + var nginx = resp.tables.shift(); + expect(nginx).to.have.property('aggConfig', vis.aggs[0]); + expect(nginx).to.have.property('key', 'nginx'); + expect(nginx.tables).to.have.length(1); + nginx.tables.forEach(function (table) { + expect(_.contains(tables, table)).to.be(true); + }); + + var apache = resp.tables.shift(); + expect(apache).to.have.property('aggConfig', vis.aggs[0]); + expect(apache).to.have.property('key', 'apache'); + expect(apache.tables).to.have.length(1); + apache.tables.forEach(function (table) { + expect(_.contains(tables, table)).to.be(true); + }); + + tables.forEach(function (table) { + expect(table.columns).to.be.an('array'); + expect(table.columns).to.have.length(1); + expect(table.columns[0].aggConfig.type.name).to.be('count'); + }); + }); + }); + + describe('#split()', function () { + it('creates a table group, pushes that group onto the splitStack, calls the block, and removes the group from the stack', + function () { + var vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); + var writer = new ResponseWriter(vis); + + var table = writer._table(); + writer.cell(1, function () { + writer.cell(2, function () { + writer.cell(3, function () { + writer.row(); + }); + }); + }); + + + expect(table.rows).to.have.length(1); + expect(table.rows[0]).to.eql([1, 2, 3]); + }); + + it('with break if the user has specified that splitting is to be disabled', function () { + var vis = new Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'terms', schema: 'split', params: { field: '_type' } }, + { type: 'count', schema: 'metric' } + ] + }); + var agg = vis.aggs.bySchemaName.split[0]; + var buckets = new Buckets({ buckets: [ { key: 'apache' } ]}); + var writer = new ResponseWriter(vis, { canSplit: false }); + + expect(function () { + writer.split(agg, buckets, _.noop); + }).to.throwException(/splitting is disabled/); + }); + }); + + describe('#cell()', function () { + it('logs a cell in the ResponseWriters row buffer, calls the block arg, then removes the value from the buffer', + function () { + var vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); + var writer = new ResponseWriter(vis); + + expect(writer.rowBuffer).to.have.length(0); + writer.cell(500, function () { + expect(writer.rowBuffer).to.have.length(1); + expect(writer.rowBuffer[0]).to.be(500); + }); + expect(writer.rowBuffer).to.have.length(0); + }); + }); + + describe('#row()', function () { + it('writes the ResponseWriters internal rowBuffer into a table', function () { + var vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); + var writer = new ResponseWriter(vis); + + var table = writer._table(); + writer.cell(1, function () { + writer.cell(2, function () { + writer.cell(3, function () { + writer.row(); + }); + }); + }); + + expect(table.rows).to.have.length(1); + expect(table.rows[0]).to.eql([1, 2, 3]); + }); + + it('always writes to the table group at the top of the split stack', function () { + var vis = new Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'terms', schema: 'split', params: { field: '_type' } }, + { type: 'terms', schema: 'split', params: { field: 'extension' } }, + { type: 'terms', schema: 'split', params: { field: 'machine.os' } }, + { type: 'count', schema: 'metric' } + ] + }); + var splits = vis.aggs.bySchemaName.split; + + var type = splits[0]; + var typeBuckets = new Buckets({ buckets: [ { key: 'nginx' }, { key: 'apache' } ] }); + + var ext = splits[1]; + var extBuckets = new Buckets({ buckets: [ { key: 'jpg' }, { key: 'png' } ] }); + + var os = splits[2]; + var osBuckets = new Buckets({ buckets: [ { key: 'windows' }, { key: 'mac' } ] }); + + var writer = new ResponseWriter(vis); + writer.split(type, typeBuckets, function () { + writer.split(ext, extBuckets, function () { + writer.split(os, osBuckets, function (bucket, key) { + writer.cell(key === 'windows' ? 1 : 2, function () { + writer.row(); + }); + }); + }); + }); + + var resp = writer.response(); + var sum = 0; + var tables = 0; + (function recurse(t) { + if (t.tables) { + // table group + t.tables.forEach(function (tt) { + recurse(tt); + }); + } else { + tables += 1; + // table + t.rows.forEach(function (row) { + row.forEach(function (cell) { + sum += cell; + }); + }); + } + }(resp)); + + expect(tables).to.be(8); + expect(sum).to.be(12); + }); + + it('writes partial rows for hierarchical vis', function () { + var vis = new Vis(indexPattern, { + type: 'pie', + aggs: [ + { type: 'terms', schema: 'segment', params: { field: '_type' }}, + { type: 'count', schema: 'metric' } + ] + }); + + var writer = new ResponseWriter(vis); + var table = writer._table(); + writer.cell('apache', function () { + writer.row(); + }); + + expect(table.rows).to.have.length(1); + expect(table.rows[0]).to.eql(['apache', '']); + }); + + it('skips partial rows for non-hierarchical vis', function () { + var vis = new Vis(indexPattern, { + type: 'histogram', + aggs: [ + { type: 'terms', schema: 'segment', params: { field: '_type' }}, + { type: 'count', schema: 'metric' } + ] + }); + + var writer = new ResponseWriter(vis); + var table = writer._table(); + writer.cell('apache', function () { + writer.row(); + }); + + expect(table.rows).to.have.length(0); + }); + }); + + }]; +}); \ No newline at end of file diff --git a/test/unit/specs/components/agg_response/tabify/_table.js b/test/unit/specs/components/agg_response/tabify/_table.js new file mode 100644 index 00000000000000..d0176e26b26780 --- /dev/null +++ b/test/unit/specs/components/agg_response/tabify/_table.js @@ -0,0 +1,110 @@ +define(function (require) { + return ['Table class', function () { + var Table; + + beforeEach(module('kibana')); + beforeEach(inject(function (Private, $injector) { + Table = Private(require('components/agg_response/tabify/_table')); + })); + + it('exposes rows array, but not the columns', function () { + var table = new Table(); + expect(table.rows).to.be.an('array'); + expect(table.columns == null).to.be.ok(); + }); + + describe('#aggConfig', function () { + it('accepts a column from the table and returns its agg config', function () { + var table = new Table(); + var football = {}; + var column = { + aggConfig: football + }; + + expect(table.aggConfig(column)).to.be(football); + }); + + it('throws a TypeError if the column is malformed', function () { + expect(function () { + var notAColumn = {}; + (new Table()).aggConfig(notAColumn); + }).to.throwException(TypeError); + }); + }); + + describe('#title', function () { + it('returns nothing if the table is not part of a table group', function () { + var table = new Table(); + expect(table.title()).to.be(''); + }); + + it('returns the title of the TableGroup if the table is part of one', function () { + var table = new Table(); + table.$parent = { + title: 'TableGroup Title', + tables: [table] + }; + + expect(table.title()).to.be('TableGroup Title'); + }); + }); + + describe('#field', function () { + it('accepts a column from the table and returns its field', function () { + var table = new Table(); + var football = {}; + var column = { + aggConfig: { + params: { + field: football + } + } + }; + + expect(table.field(column)).to.be(football); + }); + + it('returns nothing if the columns does not have a field', function () { + var table = new Table(); + var column = { + aggConfig: { + params: {} + } + }; + + expect(table.field(column)).to.not.be.ok(); + }); + }); + + describe('#fieldFormatter', function () { + it('accepts a column from the table and returns its field', function () { + var table = new Table(); + var football = {}; + var column = { + aggConfig: { + params: { + field: { + format: { + convert: football + } + } + } + } + }; + + expect(table.fieldFormatter(column)).to.be(football); + }); + + it('returns nothing if the columns does not have a field', function () { + var table = new Table(); + var column = { + aggConfig: { + params: {} + } + }; + + expect(table.field(column)).to.not.be.ok(); + }); + }); + }]; +}); \ No newline at end of file diff --git a/test/unit/specs/components/agg_response/tabify/_table_group.js b/test/unit/specs/components/agg_response/tabify/_table_group.js new file mode 100644 index 00000000000000..7a1c2e9ded7e6a --- /dev/null +++ b/test/unit/specs/components/agg_response/tabify/_table_group.js @@ -0,0 +1,18 @@ +define(function (require) { + return ['Table Group class', function () { + var TableGroup; + + beforeEach(module('kibana')); + beforeEach(inject(function (Private, $injector) { + TableGroup = Private(require('components/agg_response/tabify/_table_group')); + })); + + it('exposes tables array and empty aggConfig, key and title', function () { + var tableGroup = new TableGroup(); + expect(tableGroup.tables).to.be.an('array'); + expect(tableGroup.aggConfig).to.be(null); + expect(tableGroup.key).to.be(null); + expect(tableGroup.title).to.be(null); + }); + }]; +}); \ No newline at end of file diff --git a/test/unit/specs/components/agg_response/tabify/tabify_agg_response.js b/test/unit/specs/components/agg_response/tabify/tabify_agg_response.js index 3308b2527036ba..2db1f859eef15d 100644 --- a/test/unit/specs/components/agg_response/tabify/tabify_agg_response.js +++ b/test/unit/specs/components/agg_response/tabify/tabify_agg_response.js @@ -1,37 +1,11 @@ define(function (require) { - var _ = require('lodash'); - describe('Tabify Agg Response', function () { - describe('result of a hierarchical response', function () { - var aggId = _.partial(_.uniqueId, '_agg_fixture'); - var bucketKey = _.partial(_.uniqueId, '_bucket_key'); - var docCount = _.partial(_.random, 0, 1000); - - var tabifyAggResponse; - var indexPattern; - var Vis; - - beforeEach(module('kibana')); - beforeEach(inject(function (Private) { - tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); - indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); - Vis = Private(require('components/vis/vis')); - })); - - describe('with no aggs', function () { - }); - - describe('with one bucket and no metric', function () { - }); - - describe('with one bucket and one metric', function () { - }); - - describe('with three buckets and one metric', function () { - }); - - describe('with three buckets and three metrics', function () { - }); - }); + run(require('specs/components/agg_response/tabify/_get_columns')); + run(require('specs/components/agg_response/tabify/_buckets')); + run(require('specs/components/agg_response/tabify/_table')); + run(require('specs/components/agg_response/tabify/_table_group')); + run(require('specs/components/agg_response/tabify/_response_writer')); + run(require('specs/components/agg_response/tabify/_integration')); + function run(module) { describe(module[0], module[1]); } }); }); \ No newline at end of file From 0521d16e3d05476d7bb4208258e627619cef4dbd Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Mon, 3 Nov 2014 11:21:39 -0700 Subject: [PATCH 21/45] fixing the path for the gem file --- tasks/install_gems.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/install_gems.js b/tasks/install_gems.js index 65a9b62fe67922..c4c3ef131306be 100644 --- a/tasks/install_gems.js +++ b/tasks/install_gems.js @@ -3,7 +3,7 @@ var join = require('path').join; module.exports = function (grunt) { grunt.registerTask('install_gems', 'Install Ruby Gems', function () { var done = this.async(); - var gemfile = join(grunt.config.get('src'), 'server', 'Gemfile'); + var gemfile = join(grunt.config.get('root'), 'Gemfile'); var jrubyPath = grunt.config.get('jrubyPath'); var jruby = jrubyPath + '/bin/jruby -S'; var command = jruby + ' gem install bundler && ' + jruby + ' bundle install --gemfile ' + gemfile; From 3c39cc9b0bceee4d46ac034b28eaca41cd0abdbb Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 11:45:09 -0700 Subject: [PATCH 22/45] update the hipchat token --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4f90cbd4614dae..35aea93519879e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,10 @@ script: notifications: email: - rashid.khan@elasticsearch.com + - spencer.alger@elasticsearch.com hipchat: rooms: - secure: a2FERvICecrUAR62vP4vrUCTG3haRzf6kSzDDzGu6SICEXWLRrK0xeNQDpdwDAfzFmaIJ6txpkmInvEFeNPYNngTgEDyfhqdIa/lW0Ermdg+1hL0dK6QJiVmT1V6LDB2mgtaTTmfontxJqq7P2tmr0zz8ny4Eqq3lUnwPxYFNNo= + secure: AFhp0iboaeshokQnzRvoKPElb/JOvZ09X9mi58KbxI4iYlQ1nysvudV1b7YG8wxV9av3twgvfbcgzAoHyKhC2MZJDLzDHnZFn8r2OQXzjYpFxJuhAVirO4Weo0wi2Z78/OkxQo87gWYFa24qc8urR/Dfor4WdoWfCsJU8tQ7t5I= format: html on_success: change template: From 99a7a4964c0f3c5e31c055d4810c4b862ce78e5c Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 12:05:26 -0700 Subject: [PATCH 23/45] [aggResponse/tabify] return nothing if no tables were created --- src/kibana/components/agg_response/tabify/_response_writer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/kibana/components/agg_response/tabify/_response_writer.js b/src/kibana/components/agg_response/tabify/_response_writer.js index 8a3167c9dcce88..1fb6e3be5f05e8 100644 --- a/src/kibana/components/agg_response/tabify/_response_writer.js +++ b/src/kibana/components/agg_response/tabify/_response_writer.js @@ -168,6 +168,8 @@ define(function (require) { if (this.canSplit) return this.root; var table = this.root.tables[0]; + if (!table) return; + delete table.$parent; return table; }; From 66bacfaa3c507abefe2693a7d08da17c22c3dd9a Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 12:06:04 -0700 Subject: [PATCH 24/45] [aggTable] added tests for the table portion --- .../stubbed_logstash_index_pattern.js | 30 +++--- test/unit/index.html | 3 +- test/unit/specs/components/agg_table/index.js | 97 +++++++++++++++++++ test/utils/stub_index_pattern.js | 2 +- 4 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 test/unit/specs/components/agg_table/index.js diff --git a/test/unit/fixtures/stubbed_logstash_index_pattern.js b/test/unit/fixtures/stubbed_logstash_index_pattern.js index 193a9c01e481e8..ad72d53e633a75 100644 --- a/test/unit/fixtures/stubbed_logstash_index_pattern.js +++ b/test/unit/fixtures/stubbed_logstash_index_pattern.js @@ -1,21 +1,23 @@ define(function (require) { return function stubbedLogstashIndexPatternService(Private) { var StubIndexPattern = Private(require('test_utils/stub_index_pattern')); + var fieldFormats = Private(require('components/index_patterns/_field_formats')); + return new StubIndexPattern('logstash-*', 'time', [ - { type: 'number', indexed: true, analyzed: true, count: 10, name: 'bytes' }, - { type: 'boolean', indexed: true, analyzed: true, count: 20, name: 'ssl' }, - { type: 'date', indexed: true, analyzed: true, count: 30, name: '@timestamp' }, - { type: 'number', indexed: true, analyzed: true, count: 0, name: 'phpmemory' }, - { type: 'ip', indexed: true, analyzed: true, count: 0, name: 'ip' }, - { type: 'attachment', indexed: true, analyzed: true, count: 0, name: 'request_body' }, - { type: 'string', indexed: true, analyzed: true, count: 0, name: 'extension' }, - { type: 'geo_point', indexed: true, analyzed: true, count: 0, name: 'point' }, - { type: 'geo_shape', indexed: true, analyzed: true, count: 0, name: 'area' }, - { type: 'string', indexed: true, analyzed: true, count: 0, name: 'extension' }, - { type: 'string', indexed: true, analyzed: true, count: 0, name: 'machine.os' }, - { type: 'string', indexed: true, analyzed: true, count: 0, name: 'geo.src' }, - { type: 'string', indexed: true, analyzed: true, count: 0, name: '_type' }, - { type: 'conflict', indexed: false, analyzed: false, count: 0, name: 'custom_user_field' } + { name: 'bytes', type: 'number', indexed: true, analyzed: true, count: 10 }, + { name: 'ssl', type: 'boolean', indexed: true, analyzed: true, count: 20 }, + { name: '@timestamp', type: 'date', indexed: true, analyzed: true, count: 30 }, + { name: 'phpmemory', type: 'number', indexed: true, analyzed: true, count: 0 }, + { name: 'ip', type: 'ip', indexed: true, analyzed: true, count: 0 }, + { name: 'request_body', type: 'attachment', indexed: true, analyzed: true, count: 0 }, + { name: 'extension', type: 'string', indexed: true, analyzed: true, count: 0 }, + { name: 'point', type: 'geo_point', indexed: true, analyzed: true, count: 0 }, + { name: 'area', type: 'geo_shape', indexed: true, analyzed: true, count: 0 }, + { name: 'extension', type: 'string', indexed: true, analyzed: true, count: 0 }, + { name: 'machine.os', type: 'string', indexed: true, analyzed: true, count: 0 }, + { name: 'geo.src', type: 'string', indexed: true, analyzed: true, count: 0 }, + { name: '_type', type: 'string', indexed: true, analyzed: true, count: 0 }, + { name: 'custom_user_field', type: 'conflict', indexed: false, analyzed: false, count: 0 } ]); }; }); diff --git a/test/unit/index.html b/test/unit/index.html index 9a807e6891bf0b..8f339fc654c571 100644 --- a/test/unit/index.html +++ b/test/unit/index.html @@ -158,7 +158,8 @@ 'specs/components/agg_response/hierarchical/_create_raw_data', 'specs/components/agg_response/hierarchical/_array_to_linked_list', 'specs/components/agg_response/hierarchical/_collect_branch', - 'specs/components/agg_response/tabify/tabify_agg_response' + 'specs/components/agg_response/tabify/tabify_agg_response', + 'specs/components/agg_table/index' ], function () { bootstrap(kibana, sinon); }); diff --git a/test/unit/specs/components/agg_table/index.js b/test/unit/specs/components/agg_table/index.js new file mode 100644 index 00000000000000..39e02bcb216131 --- /dev/null +++ b/test/unit/specs/components/agg_table/index.js @@ -0,0 +1,97 @@ +define(function (require) { + describe('AggTable Directive', function () { + var _ = require('lodash'); + var $ = require('jquery'); + var fixtures = require('fixtures/fake_hierarchical_data'); + + var $rootScope; + var $compile; + var tabifyAggResponse; + var Vis; + var indexPattern; + + beforeEach(module('kibana')); + beforeEach(inject(function ($injector, Private) { + tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); + indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); + Vis = Private(require('components/vis/vis')); + + $rootScope = $injector.get('$rootScope'); + $compile = $injector.get('$compile'); + })); + + var $scope; + beforeEach(function () { + $scope = $rootScope.$new(); + }); + afterEach(function () { + $scope.$destroy(); + }); + + + it('renders a simple response properly', function () { + var vis = new Vis(indexPattern, 'table'); + $scope.table = tabifyAggResponse(vis, fixtures.metricOnly, { canSplit: false }); + var $el = $(''); + + $compile($el)($scope); + $scope.$digest(); + + expect($el.find('tbody').size()).to.be(1); + expect($el.find('td').size()).to.be(1); + expect($el.find('td').text()).to.eql(1000); + }); + + it('renders a complex response properly', function () { + var vis = new Vis(indexPattern, { + type: 'pie', + aggs: [ + { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { type: 'terms', schema: 'split', params: { field: 'extension' } }, + { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, + { type: 'terms', schema: 'segment', params: { field: 'machine.os' } } + ] + }); + vis.aggs.forEach(function (agg, i) { + agg.id = 'agg_' + (i + 1); + }); + + $scope.table = tabifyAggResponse(vis, fixtures.threeTermBuckets, { canSplit: false }); + var $el = $('').appendTo(document.body); + $compile($el)($scope); + $scope.$digest(); + + expect($el.find('tbody').size()).to.be(1); + + var $rows = $el.find('tbody tr'); + expect($rows.size()).to.be.greaterThan(0); + + $rows.each(function (i) { + // 4 cells in every row + var $cells = $(this).find('td'); + + expect($cells.size()).to.be(6); + + var txts = $cells.map(function () { + return $(this).text().trim(); + }); + + // two character country code + expect(txts[0]).to.match(/^(png|jpg|gif|html|css)$/); + + // average bytes + expect(txts[1]).to.match(/^\d+$/); + var bytesAsNum = _.parseInt(txts[1]); + expect(bytesAsNum === 0 || bytesAsNum > 1000).to.be.ok(); + + // os + expect(txts[2]).to.match(/^(win|mac|linux)$/); + + // average bytes + expect(txts[1]).to.match(/^\d+$/); + bytesAsNum = _.parseInt(txts[1]); + expect(bytesAsNum === 0 || bytesAsNum > 1000).to.be.ok(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/utils/stub_index_pattern.js b/test/utils/stub_index_pattern.js index 7076fe0a6b547a..1fbbe5fb9177a9 100644 --- a/test/utils/stub_index_pattern.js +++ b/test/utils/stub_index_pattern.js @@ -16,7 +16,7 @@ define(function (require) { Object.defineProperty(field, 'format', { enumerable: false, get: function () { - fieldFormats.defaultByType[field.type]; + return fieldFormats.defaultByType[field.type]; } }); From 0798c41ee1b88e208e5ac9a1fe4e807f3c51e9f1 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 13:25:17 -0700 Subject: [PATCH 25/45] [visType/pie] filter the metric aggs again since slice sizing isn't easily fixable --- src/kibana/filters/_prop_filter.js | 52 +++++++++++++++++++ src/kibana/filters/field_type.js | 33 ++---------- .../plugins/visualize/editor/agg_filter.js | 36 ++----------- 3 files changed, 62 insertions(+), 59 deletions(-) create mode 100644 src/kibana/filters/_prop_filter.js diff --git a/src/kibana/filters/_prop_filter.js b/src/kibana/filters/_prop_filter.js new file mode 100644 index 00000000000000..85ece13849799e --- /dev/null +++ b/src/kibana/filters/_prop_filter.js @@ -0,0 +1,52 @@ +define(function (require) { + var _ = require('lodash'); + + /** + * Filters out a list by a given filter. This is currently used to impelment: + * - fieldType filters a list of fields by their type property + * - aggFilter filters a list of aggs by their name property + * + * @returns {function} - the filter function which can be registered with angular + */ + function propFilter(prop) { + /** + * List filtering function which accepts an array or list of values that a property + * must contain + * + * @param {array} list - array of items to filter + * @param {array|string} filters - the values to match against the list. Can be + * an array, a single value as a string, or a comma + * -seperated list of items + * @return {array} - the filtered list + */ + return function (list, filters) { + if (!filters) return filters; + if (!_.isArray(filters)) filters = filters.split(','); + if (_.contains(filters, '*')) return filters; + + filters = filters.map(function (filter) { + var match = true; + var value = filter; + + if (filter.charAt(0) === '!') { + match = false; + value = filter.substr(1); + } + + return { + match: match, + value: value + }; + }); + + return list.filter(function (item) { + for (var i = 0; i < filters.length; i++) { + var filter = filters[i]; + if ((item[prop] === filter.value) === filter.match) return true; + } + }); + }; + } + + return propFilter; +}); \ No newline at end of file diff --git a/src/kibana/filters/field_type.js b/src/kibana/filters/field_type.js index 126772dec53910..35179a32752661 100644 --- a/src/kibana/filters/field_type.js +++ b/src/kibana/filters/field_type.js @@ -3,34 +3,11 @@ // Or an array of types to get all fields of that type define(function (require) { var _ = require('lodash'); + var propFilter = require('filters/_prop_filter'); require('modules') - .get('kibana') - .filter('fieldType', function () { - return function (fields, types) { - if (!types) return fields; - if (!_.isArray(types)) types = [types]; - if (_.contains(types, '*')) return fields; - - var filters = types.map(function (type) { - var filter = { - match: true, - type: type - }; - - if (type.charAt(0) === '!') { - filter.match = false; - filter.type = type.substr(1); - } - return filter; - }); - - return fields.filter(function (field) { - for (var i = 0; i < filters.length; i++) { - var filter = filters[i]; - if ((field.type === filter.type) === filter.match) return true; - } - }); - }; - }); + .get('kibana') + .filter('fieldType', function () { + return propFilter('type'); + }); }); \ No newline at end of file diff --git a/src/kibana/plugins/visualize/editor/agg_filter.js b/src/kibana/plugins/visualize/editor/agg_filter.js index e839c639329c9e..bc0f5b6ac589d9 100644 --- a/src/kibana/plugins/visualize/editor/agg_filter.js +++ b/src/kibana/plugins/visualize/editor/agg_filter.js @@ -1,36 +1,10 @@ -// Gets all fields of a given type. -// You may also pass "*" to get all types -// Or an array of types to get all fields of that type define(function (require) { var _ = require('lodash'); + var propFilter = require('filters/_prop_filter'); require('modules') - .get('kibana') - .filter('aggFilter', function () { - return function (aggs, names) { - if (!names) return aggs; - if (!_.isArray(names)) names = [names]; - if (_.contains(names, '*')) return aggs; - - var filters = names.map(function (name) { - var filter = { - match: true, - name: name - }; - - if (name.charAt(0) === '!') { - filter.match = false; - filter.name = name.substr(1); - } - return filter; - }); - - return aggs.filter(function (agg) { - for (var i = 0; i < filters.length; i++) { - var filter = filters[i]; - if ((agg.name === filter.name) === filter.match) return true; - } - }); - }; - }); + .get('kibana') + .filter('aggFilter', function () { + return propFilter('name'); + }); }); \ No newline at end of file From c08f4d7fbfd098ad65224f51d40cc70c2f0fa9b9 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 13:30:19 -0700 Subject: [PATCH 26/45] [visType/pie] filter the metric aggs again since slice sizing isn't easily fixable --- src/kibana/plugins/vis_types/pie.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kibana/plugins/vis_types/pie.js b/src/kibana/plugins/vis_types/pie.js index 7b257c270f31f3..93f778ed318951 100644 --- a/src/kibana/plugins/vis_types/pie.js +++ b/src/kibana/plugins/vis_types/pie.js @@ -22,6 +22,7 @@ define(function (require) { title: 'Slice Size', min: 1, max: 1, + aggFilter: ['sum', 'count'], defaults: [ { schema: 'metric', type: 'count' } ] From f114299a09be9b1f8e45862af0017911238f84bf Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 13:49:41 -0700 Subject: [PATCH 27/45] save --- .../components/agg_table/agg_table_group.html | 4 +- src/kibana/filters/_prop_filter.js | 2 +- src/kibana/plugins/vis_types/vislib/pie.js | 1 + .../unit/specs/components/agg_table/_group.js | 74 ++++++++++++++ .../unit/specs/components/agg_table/_table.js | 98 +++++++++++++++++++ test/unit/specs/components/agg_table/index.js | 98 +------------------ 6 files changed, 180 insertions(+), 97 deletions(-) create mode 100644 test/unit/specs/components/agg_table/_group.js create mode 100644 test/unit/specs/components/agg_table/_table.js diff --git a/src/kibana/components/agg_table/agg_table_group.html b/src/kibana/components/agg_table/agg_table_group.html index a9c264fb830e8c..ad1811a39ef725 100644 --- a/src/kibana/components/agg_table/agg_table_group.html +++ b/src/kibana/components/agg_table/agg_table_group.html @@ -2,7 +2,7 @@ - {{ table.title }} + {{ table.title }} @@ -20,7 +20,7 @@ - {{ table.title }} + {{ table.title }} diff --git a/src/kibana/filters/_prop_filter.js b/src/kibana/filters/_prop_filter.js index 85ece13849799e..82040d121da929 100644 --- a/src/kibana/filters/_prop_filter.js +++ b/src/kibana/filters/_prop_filter.js @@ -22,7 +22,7 @@ define(function (require) { return function (list, filters) { if (!filters) return filters; if (!_.isArray(filters)) filters = filters.split(','); - if (_.contains(filters, '*')) return filters; + if (_.contains(filters, '*')) return list; filters = filters.map(function (filter) { var match = true; diff --git a/src/kibana/plugins/vis_types/vislib/pie.js b/src/kibana/plugins/vis_types/vislib/pie.js index e30f4d9f487600..e046e9233f215e 100644 --- a/src/kibana/plugins/vis_types/vislib/pie.js +++ b/src/kibana/plugins/vis_types/vislib/pie.js @@ -22,6 +22,7 @@ define(function (require) { title: 'Slice Size', min: 1, max: 1, + aggFilter: 'sum,count', defaults: [ { schema: 'metric', type: 'count' } ] diff --git a/test/unit/specs/components/agg_table/_group.js b/test/unit/specs/components/agg_table/_group.js new file mode 100644 index 00000000000000..b171899e00fd77 --- /dev/null +++ b/test/unit/specs/components/agg_table/_group.js @@ -0,0 +1,74 @@ +define(function (require) { + return ['AggTableGroup Directive', function () { + var _ = require('lodash'); + var $ = require('jquery'); + var fixtures = require('fixtures/fake_hierarchical_data'); + + var $rootScope; + var $compile; + var tabifyAggResponse; + var Vis; + var indexPattern; + + beforeEach(module('kibana')); + beforeEach(inject(function ($injector, Private) { + tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); + indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); + Vis = Private(require('components/vis/vis')); + + $rootScope = $injector.get('$rootScope'); + $compile = $injector.get('$compile'); + })); + + var $scope; + beforeEach(function () { + $scope = $rootScope.$new(); + }); + afterEach(function () { + $scope.$destroy(); + }); + + + it('renders a simple split response properly', function () { + var vis = new Vis(indexPattern, 'table'); + $scope.group = tabifyAggResponse(vis, fixtures.metricOnly); + var $el = $(''); + + $compile($el)($scope); + $scope.$digest(); + + // should create one sub-tbale + expect($el.find('kbn-agg-table').size()).to.be(1); + }); + + it('renders a complex response properly', function () { + var vis = new Vis(indexPattern, { + type: 'pie', + aggs: [ + { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { type: 'terms', schema: 'split', params: { field: 'extension' } }, + { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, + { type: 'terms', schema: 'segment', params: { field: 'machine.os' } } + ] + }); + vis.aggs.forEach(function (agg, i) { + agg.id = 'agg_' + (i + 1); + }); + + var group = $scope.group = tabifyAggResponse(vis, fixtures.threeTermBuckets); + var $el = $(''); + $compile($el)($scope); + $scope.$digest(); + + var $subTables = $el.find('kbn-agg-table'); + expect($subTables.size()).to.be(3); + + var $subTableHeaders = $el.find('.agg-table-group-header'); + expect($subTableHeaders.size()).to.be(3); + + $subTableHeaders.each(function (i) { + expect($(this).text()).to.be(group.tables[i].title); + }); + }); + }]; +}); \ No newline at end of file diff --git a/test/unit/specs/components/agg_table/_table.js b/test/unit/specs/components/agg_table/_table.js new file mode 100644 index 00000000000000..1614659f33527a --- /dev/null +++ b/test/unit/specs/components/agg_table/_table.js @@ -0,0 +1,98 @@ +define(function (require) { + return ['AggTable Directive', function () { + var _ = require('lodash'); + var $ = require('jquery'); + var fixtures = require('fixtures/fake_hierarchical_data'); + + var $rootScope; + var $compile; + var tabifyAggResponse; + var Vis; + var indexPattern; + + beforeEach(module('kibana')); + beforeEach(inject(function ($injector, Private) { + tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); + indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); + Vis = Private(require('components/vis/vis')); + + $rootScope = $injector.get('$rootScope'); + $compile = $injector.get('$compile'); + })); + + var $scope; + beforeEach(function () { + $scope = $rootScope.$new(); + }); + afterEach(function () { + $scope.$destroy(); + }); + + + it('renders a simple response properly', function () { + var vis = new Vis(indexPattern, 'table'); + $scope.table = tabifyAggResponse(vis, fixtures.metricOnly, { canSplit: false }); + var $el = $(''); + + $compile($el)($scope); + $scope.$digest(); + + expect($el.find('tbody').size()).to.be(1); + expect($el.find('td').size()).to.be(1); + expect($el.find('td').text()).to.eql(1000); + }); + + it('renders a complex response properly', function () { + var vis = new Vis(indexPattern, { + type: 'pie', + aggs: [ + { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + { type: 'terms', schema: 'split', params: { field: 'extension' } }, + { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, + { type: 'terms', schema: 'segment', params: { field: 'machine.os' } } + ] + }); + vis.aggs.forEach(function (agg, i) { + agg.id = 'agg_' + (i + 1); + }); + + $scope.table = tabifyAggResponse(vis, fixtures.threeTermBuckets, { canSplit: false }); + var $el = $(''); + $compile($el)($scope); + $scope.$digest(); + + expect($el.find('tbody').size()).to.be(1); + + var $rows = $el.find('tbody tr'); + expect($rows.size()).to.be.greaterThan(0); + + function validBytes(str) { + expect(str).to.match(/^\d+$/); + var bytesAsNum = _.parseInt(str); + expect(bytesAsNum === 0 || bytesAsNum > 1000).to.be.ok(); + } + + $rows.each(function (i) { + // 6 cells in every row + var $cells = $(this).find('td'); + expect($cells.size()).to.be(6); + + var txts = $cells.map(function () { + return $(this).text().trim(); + }); + + // two character country code + expect(txts[0]).to.match(/^(png|jpg|gif|html|css)$/); + validBytes(txts[1]); + + // country + expect(txts[2]).to.match(/^\w\w$/); + validBytes(txts[3]); + + // os + expect(txts[4]).to.match(/^(win|mac|linux)$/); + validBytes(txts[5]); + }); + }); + }]; +}); \ No newline at end of file diff --git a/test/unit/specs/components/agg_table/index.js b/test/unit/specs/components/agg_table/index.js index 39e02bcb216131..a8b1aad41499c0 100644 --- a/test/unit/specs/components/agg_table/index.js +++ b/test/unit/specs/components/agg_table/index.js @@ -1,97 +1,7 @@ define(function (require) { - describe('AggTable Directive', function () { - var _ = require('lodash'); - var $ = require('jquery'); - var fixtures = require('fixtures/fake_hierarchical_data'); - - var $rootScope; - var $compile; - var tabifyAggResponse; - var Vis; - var indexPattern; - - beforeEach(module('kibana')); - beforeEach(inject(function ($injector, Private) { - tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); - indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); - Vis = Private(require('components/vis/vis')); - - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - })); - - var $scope; - beforeEach(function () { - $scope = $rootScope.$new(); - }); - afterEach(function () { - $scope.$destroy(); - }); - - - it('renders a simple response properly', function () { - var vis = new Vis(indexPattern, 'table'); - $scope.table = tabifyAggResponse(vis, fixtures.metricOnly, { canSplit: false }); - var $el = $(''); - - $compile($el)($scope); - $scope.$digest(); - - expect($el.find('tbody').size()).to.be(1); - expect($el.find('td').size()).to.be(1); - expect($el.find('td').text()).to.eql(1000); - }); - - it('renders a complex response properly', function () { - var vis = new Vis(indexPattern, { - type: 'pie', - aggs: [ - { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, - { type: 'terms', schema: 'split', params: { field: 'extension' } }, - { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, - { type: 'terms', schema: 'segment', params: { field: 'machine.os' } } - ] - }); - vis.aggs.forEach(function (agg, i) { - agg.id = 'agg_' + (i + 1); - }); - - $scope.table = tabifyAggResponse(vis, fixtures.threeTermBuckets, { canSplit: false }); - var $el = $('').appendTo(document.body); - $compile($el)($scope); - $scope.$digest(); - - expect($el.find('tbody').size()).to.be(1); - - var $rows = $el.find('tbody tr'); - expect($rows.size()).to.be.greaterThan(0); - - $rows.each(function (i) { - // 4 cells in every row - var $cells = $(this).find('td'); - - expect($cells.size()).to.be(6); - - var txts = $cells.map(function () { - return $(this).text().trim(); - }); - - // two character country code - expect(txts[0]).to.match(/^(png|jpg|gif|html|css)$/); - - // average bytes - expect(txts[1]).to.match(/^\d+$/); - var bytesAsNum = _.parseInt(txts[1]); - expect(bytesAsNum === 0 || bytesAsNum > 1000).to.be.ok(); - - // os - expect(txts[2]).to.match(/^(win|mac|linux)$/); - - // average bytes - expect(txts[1]).to.match(/^\d+$/); - bytesAsNum = _.parseInt(txts[1]); - expect(bytesAsNum === 0 || bytesAsNum > 1000).to.be.ok(); - }); - }); + describe('AggTable Component', function () { + run(require('specs/components/agg_table/_group')); + run(require('specs/components/agg_table/_table')); + function run(mod) { describe(mod[0], mod[1]); } }); }); \ No newline at end of file From 51e9429e3fbef71c3a8f7041cf318cb81f347e9e Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 13:50:14 -0700 Subject: [PATCH 28/45] [filters] fix the prop filter --- src/kibana/filters/_prop_filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kibana/filters/_prop_filter.js b/src/kibana/filters/_prop_filter.js index 85ece13849799e..82040d121da929 100644 --- a/src/kibana/filters/_prop_filter.js +++ b/src/kibana/filters/_prop_filter.js @@ -22,7 +22,7 @@ define(function (require) { return function (list, filters) { if (!filters) return filters; if (!_.isArray(filters)) filters = filters.split(','); - if (_.contains(filters, '*')) return filters; + if (_.contains(filters, '*')) return list; filters = filters.map(function (filter) { var match = true; From f57a862c23b94faaf9080b9369663d438b5bf0bf Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 15:07:59 -0700 Subject: [PATCH 29/45] [aggTable] tweak the way that sort is cleared --- src/kibana/components/agg_table/agg_table.html | 2 +- src/kibana/components/agg_table/agg_table.js | 6 +++--- test/unit/specs/components/agg_table/_group.js | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/kibana/components/agg_table/agg_table.html b/src/kibana/components/agg_table/agg_table.html index d331c84b7df0b5..253bcd57f875b6 100644 --- a/src/kibana/components/agg_table/agg_table.html +++ b/src/kibana/components/agg_table/agg_table.html @@ -1,5 +1,5 @@ '); + + $scope.group = { + tables: [] + }; + + $compile($el)($scope); + $scope.$digest(); + + var $subTables = $el.find('kbn-agg-table'); + expect($subTables.size()).to.be(0); + }); + it('renders a complex response properly', function () { var vis = new Vis(indexPattern, { type: 'pie', From 8274115e3bc1136a2e8a9f64363d4c7d77418447 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 15:25:05 -0700 Subject: [PATCH 30/45] [aggTable] watch for the proper sort change --- src/kibana/components/agg_table/agg_table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kibana/components/agg_table/agg_table.js b/src/kibana/components/agg_table/agg_table.js index 5d443dd0fdad48..3884a8d5385718 100644 --- a/src/kibana/components/agg_table/agg_table.js +++ b/src/kibana/components/agg_table/agg_table.js @@ -110,7 +110,7 @@ define(function (require) { $scope.$watchMulti([ 'table', 'aggTable.sort.asc', - 'aggTable.sort.field' + 'aggTable.sort.col' ], function () { var table = $scope.table; From 7374b97d7c1dbe749662313f3061d548d6ad980b Mon Sep 17 00:00:00 2001 From: lukasolson Date: Mon, 3 Nov 2014 15:59:21 -0700 Subject: [PATCH 31/45] Fix issue with datepicker not correctly showing selected date if it was in a different month that the current month. Closes #1794. --- src/kibana/components/timepicker/timepicker.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/kibana/components/timepicker/timepicker.html b/src/kibana/components/timepicker/timepicker.html index bbdb971341aa84..cf1a75c7ce9700 100644 --- a/src/kibana/components/timepicker/timepicker.html +++ b/src/kibana/components/timepicker/timepicker.html @@ -106,8 +106,8 @@ -
- +
+
@@ -116,8 +116,8 @@ -
- +
+
From de056cf2595f62e4bfc2fa2ce8ec85ae4e278a0e Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 16:15:36 -0700 Subject: [PATCH 32/45] [aggTable] test all the things --- src/kibana/components/agg_table/agg_table.js | 20 +- .../unit/specs/components/agg_table/_table.js | 189 +++++++++++++++++- 2 files changed, 198 insertions(+), 11 deletions(-) diff --git a/src/kibana/components/agg_table/agg_table.js b/src/kibana/components/agg_table/agg_table.js index 3884a8d5385718..c887211738246c 100644 --- a/src/kibana/components/agg_table/agg_table.js +++ b/src/kibana/components/agg_table/agg_table.js @@ -29,7 +29,6 @@ define(function (require) { self.sort = null; self.csv = { - showOptions: false, separator: config.get('csv:separator'), quoteValues: config.get('csv:quoteValues') }; @@ -75,20 +74,22 @@ define(function (require) { }; self.exportAsCsv = function () { - self.csv.showOptions = false; + saveAs(new Blob(self.toCsv(), { type: 'text/plain' }), self.csv.filename); + }; - var text = ''; + self.toCsv = function () { var rows = $scope.table.rows; var columns = $scope.table.columns; var nonAlphaNumRE = /[^a-zA-Z0-9]/; var allDoubleQuoteRE = /"/g; - var escape = function (val) { + + function escape(val) { val = String(val); if (self.csv.quoteValues && nonAlphaNumRE.test(val)) { val = '"' + val.replace(allDoubleQuoteRE, '""') + '"'; } return val; - }; + } // escape each cell in each row var csvRows = rows.map(function (row, i) { @@ -100,11 +101,9 @@ define(function (require) { return escape(col.title); })); - var blob = new Blob(csvRows.map(function (row) { + return csvRows.map(function (row) { return row.join(self.csv.separator) + '\r\n'; - }), { type: 'text/plain' }); - - saveAs(blob, ($scope.table.title() || 'table') + '.csv'); + }).join(''); }; $scope.$watchMulti([ @@ -136,6 +135,9 @@ define(function (require) { return formatters[i](cell); }); }); + + // update the csv file's title + self.csv.filename = (table.title() || 'table') + '.csv'; }); } }; diff --git a/test/unit/specs/components/agg_table/_table.js b/test/unit/specs/components/agg_table/_table.js index 1614659f33527a..b7a1ab234ed774 100644 --- a/test/unit/specs/components/agg_table/_table.js +++ b/test/unit/specs/components/agg_table/_table.js @@ -32,9 +32,8 @@ define(function (require) { it('renders a simple response properly', function () { var vis = new Vis(indexPattern, 'table'); $scope.table = tabifyAggResponse(vis, fixtures.metricOnly, { canSplit: false }); - var $el = $(''); - $compile($el)($scope); + var $el = $compile('')($scope); $scope.$digest(); expect($el.find('tbody').size()).to.be(1); @@ -42,6 +41,14 @@ define(function (require) { expect($el.find('td').text()).to.eql(1000); }); + it('renders nothing if the table is empty', function () { + $scope.table = null; + var $el = $compile('')($scope); + $scope.$digest(); + + expect($el.find('tbody').size()).to.be(0); + }); + it('renders a complex response properly', function () { var vis = new Vis(indexPattern, { type: 'pie', @@ -94,5 +101,183 @@ define(function (require) { validBytes(txts[5]); }); }); + + describe('aggTable.cycleSort()', function () { + var vis; + beforeEach(function () { + vis = new Vis(indexPattern, { + type: 'table', + aggs: [ + { type: 'count', schema: 'metric' }, + { + type: 'range', + schema: 'bucket', + params: { + field: 'bytes', + ranges: [ + { from: 0, to: 1000 }, + { from: 1000, to: 2000 } + ] + } + } + ] + }); + + vis.aggs.forEach(function (agg, i) { + agg.id = 'agg_' + (i + 1); + }); + }); + + function checkAgainst(aggTable, $el, selector) { + return function (asc, firstCol) { + switch (asc) { + case null: + expect(aggTable.sort == null).to.be(true); + break; + case true: + case false: + expect(aggTable.sort).to.have.property('asc', asc); + break; + } + + var $leftCol = $el.find(selector || 'tr td:first-child'); + firstCol.forEach(function (val, i) { + expect($leftCol.eq(i).text().trim()).to.be(val); + }); + }; + } + + it('sorts by the column passed in', function () { + $scope.table = tabifyAggResponse(vis, fixtures.oneRangeBucket, { canSplit: false }); + var $el = $compile('')($scope); + $scope.$digest(); + + var sortCol = $scope.table.columns[0]; + var $tableScope = $el.isolateScope(); + var aggTable = $tableScope.aggTable; + var check = checkAgainst(aggTable, $el); + + // default state + check(null, [ + '0.0-1000.0', + '1000.0-2000.0' + ]); + + // enable accending + aggTable.cycleSort(sortCol); + $scope.$digest(); + check(true, [ + '0.0-1000.0', + '1000.0-2000.0' + ]); + + // enable descending + aggTable.cycleSort(sortCol); + $scope.$digest(); + check(false, [ + '1000.0-2000.0', + '0.0-1000.0' + ]); + + // disable sort + aggTable.cycleSort(sortCol); + $scope.$digest(); + check(null, [ + '0.0-1000.0', + '1000.0-2000.0' + ]); + }); + + it('sorts new tables by the previous sort rule', function () { + $scope.table = tabifyAggResponse(vis, fixtures.oneRangeBucket, { canSplit: false }); + var $el = $compile('')($scope); + $scope.$digest(); + + var sortCol = $scope.table.columns[0]; + var $tableScope = $el.isolateScope(); + var aggTable = $tableScope.aggTable; + var check = checkAgainst(aggTable, $el); + + // enable accending, then descending + aggTable.cycleSort(sortCol); + aggTable.cycleSort(sortCol); + $scope.$digest(); + check(false, [ + '1000.0-2000.0', + '0.0-1000.0' + ]); + + var prevFormattedRows = $tableScope.formattedRows; + + // change the table and trigger the watchers + $scope.table = tabifyAggResponse(vis, fixtures.oneRangeBucket, { canSplit: false }); + $scope.$digest(); + + // prove that the rows were recreated + expect($tableScope.formattedRows).to.not.be(prevFormattedRows); + + // check that the order is right + check(false, [ + '1000.0-2000.0', + '0.0-1000.0' + ]); + }); + + it('sorts ascending when switching from another column', function () { + $scope.table = tabifyAggResponse(vis, fixtures.oneRangeBucket, { canSplit: false }); + var $el = $compile('')($scope); + $scope.$digest(); + + var $tableScope = $el.isolateScope(); + var aggTable = $tableScope.aggTable; + + var rangeCol = $scope.table.columns[0]; + var countCol = $scope.table.columns[1]; + var checkRange = checkAgainst(aggTable, $el, 'tr td:first-child'); + var checkCount = checkAgainst(aggTable, $el, 'tr td:last-child'); + + // sort count accending + aggTable.cycleSort(countCol); + $scope.$digest(); + checkCount(true, [ + '298', + '606' + ]); + + // switch to sorting range ascending + aggTable.cycleSort(rangeCol); + $scope.$digest(); + checkRange(true, [ + '0.0-1000.0', + '1000.0-2000.0' + ]); + }); + }); + + describe('aggTable.toCsv()', function () { + it('escapes and formats the rows and columns properly', function () { + var $el = $compile('')($scope); + $scope.$digest(); + + var $tableScope = $el.isolateScope(); + var aggTable = $tableScope.aggTable; + + $tableScope.table = { + columns: [ + { title: 'one' }, + { title: 'two' }, + { title: 'with double-quotes(")' } + ], + rows: [ + [1, 2, '"foobar"'] + ] + }; + + expect(aggTable.toCsv()).to.be( + 'one,two,"with double-quotes("")"' + '\r\n' + + '1,2,"""foobar"""' + '\r\n' + ); + }); + }); }]; }); \ No newline at end of file From 643743b8f5523d3cf3903480cc75f94c6e9776df Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 17:05:44 -0700 Subject: [PATCH 33/45] save --- src/kibana/directives/paginate.js | 7 +--- src/kibana/partials/paginate_controls.html | 47 +++++++++++++--------- src/kibana/styles/_pagination.less | 14 ++++--- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/kibana/directives/paginate.js b/src/kibana/directives/paginate.js index a23ffdc1f090f2..d039b1473609cc 100644 --- a/src/kibana/directives/paginate.js +++ b/src/kibana/directives/paginate.js @@ -127,12 +127,7 @@ define(function (require) { // this directive is automatically added by paginate if not found within it's $el return { restrict: 'E', - template: require('text!partials/paginate_controls.html'), - link: function ($scope, $el) { - $scope.$watch('page.count > 1', function (show) { - $el.toggle(show); - }); - } + template: require('text!partials/paginate_controls.html') }; }); diff --git a/src/kibana/partials/paginate_controls.html b/src/kibana/partials/paginate_controls.html index 2e42fc2894b751..485f099a086fb8 100644 --- a/src/kibana/partials/paginate_controls.html +++ b/src/kibana/partials/paginate_controls.html @@ -1,30 +1,37 @@ - +
- +
\ No newline at end of file diff --git a/src/kibana/styles/_pagination.less b/src/kibana/styles/_pagination.less index 37991c069ddc2d..c4b5fa7c7053f6 100644 --- a/src/kibana/styles/_pagination.less +++ b/src/kibana/styles/_pagination.less @@ -8,16 +8,20 @@ paginate { padding: 5px 5px 10px; text-align: center; - .pagination { + .pagination-other-pages { .flex(1, 0, auto); .display(flex); .justify-content(center); - // override some bootstrap styles - margin: 0 auto; - - > li { + &-list { .flex(0, 0, auto); + .display(flex); + margin: 0; + list-style: none; + + > li { + .flex(0, 0, auto); + } } } From ae35f7d1e41cc7c6e608ed3854c650537a34c0a1 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Mon, 3 Nov 2014 17:55:19 -0700 Subject: [PATCH 34/45] [aggTable] format values for table titles --- .../agg_response/tabify/_response_writer.js | 2 +- .../components/agg_response/tabify/_table_group.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/kibana/components/agg_response/tabify/_response_writer.js b/src/kibana/components/agg_response/tabify/_response_writer.js index 1fb6e3be5f05e8..b7ec892312c220 100644 --- a/src/kibana/components/agg_response/tabify/_response_writer.js +++ b/src/kibana/components/agg_response/tabify/_response_writer.js @@ -40,7 +40,7 @@ define(function (require) { if (group) { table.aggConfig = agg; table.key = key; - table.title = agg.makeLabel() + ': ' + key; + table.title = agg.makeLabel() + ': ' + table.fieldFormat(key); } var parent = this.splitStack[0]; diff --git a/src/kibana/components/agg_response/tabify/_table_group.js b/src/kibana/components/agg_response/tabify/_table_group.js index b9c65c386d075e..5c48deccafda7a 100644 --- a/src/kibana/components/agg_response/tabify/_table_group.js +++ b/src/kibana/components/agg_response/tabify/_table_group.js @@ -1,5 +1,7 @@ define(function (require) { return function TableGroupProvider() { + var _ = require('lodash'); + /** * Simple object that wraps multiple tables. It contains information about the aggConfig * and bucket that created this group and a list of the tables within it. @@ -11,6 +13,15 @@ define(function (require) { this.tables = []; } + TableGroup.prototype.field = function () { + return this.aggConfig && this.aggConfig.params && this.aggConfig.params.field; + }; + + TableGroup.prototype.fieldFormat = function () { + var field = this.field(); + return field ? field.format.convert : _.identity; + }; + return TableGroup; }; }); \ No newline at end of file From 943ee36b0e2c28daeddb0c8cf3eadd704fdb834c Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 4 Nov 2014 10:19:29 -0700 Subject: [PATCH 35/45] [aggTable] use the field formatter to display that TableGroups title --- .../agg_response/tabify/_response_writer.js | 2 +- .../components/agg_response/tabify/_table_group.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/kibana/components/agg_response/tabify/_response_writer.js b/src/kibana/components/agg_response/tabify/_response_writer.js index 1fb6e3be5f05e8..96f569017be691 100644 --- a/src/kibana/components/agg_response/tabify/_response_writer.js +++ b/src/kibana/components/agg_response/tabify/_response_writer.js @@ -40,7 +40,7 @@ define(function (require) { if (group) { table.aggConfig = agg; table.key = key; - table.title = agg.makeLabel() + ': ' + key; + table.title = agg.makeLabel() + ': ' + (table.fieldFormat()(key)); } var parent = this.splitStack[0]; diff --git a/src/kibana/components/agg_response/tabify/_table_group.js b/src/kibana/components/agg_response/tabify/_table_group.js index b9c65c386d075e..5c48deccafda7a 100644 --- a/src/kibana/components/agg_response/tabify/_table_group.js +++ b/src/kibana/components/agg_response/tabify/_table_group.js @@ -1,5 +1,7 @@ define(function (require) { return function TableGroupProvider() { + var _ = require('lodash'); + /** * Simple object that wraps multiple tables. It contains information about the aggConfig * and bucket that created this group and a list of the tables within it. @@ -11,6 +13,15 @@ define(function (require) { this.tables = []; } + TableGroup.prototype.field = function () { + return this.aggConfig && this.aggConfig.params && this.aggConfig.params.field; + }; + + TableGroup.prototype.fieldFormat = function () { + var field = this.field(); + return field ? field.format.convert : _.identity; + }; + return TableGroup; }; }); \ No newline at end of file From 0ab60b06a3cfb5110dd4933cdd188a4fb298742c Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Tue, 4 Nov 2014 11:27:12 -0700 Subject: [PATCH 36/45] Styling tweaks Gets rid of the margins and the top border --- src/kibana/plugins/table_vis/table_vis.less | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/kibana/plugins/table_vis/table_vis.less b/src/kibana/plugins/table_vis/table_vis.less index 35d6b07464bfac..04e7ac3a116b8b 100644 --- a/src/kibana/plugins/table_vis/table_vis.less +++ b/src/kibana/plugins/table_vis/table_vis.less @@ -1,5 +1,7 @@ .TableVis { - min-width: 55%; - max-width: 100%; - margin: 20px auto; -} \ No newline at end of file + width: 100%; + + kbn-agg-table-group > .table > tbody > tr > td { + border-top: 0px; + } +} From 616da841c902616d2cc748e28c203e95fb5b9c70 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 4 Nov 2014 11:29:21 -0700 Subject: [PATCH 37/45] [paginate] added pageSize control, and pushing new pageSize up to parent scope --- .../components/agg_table/agg_table.html | 2 +- src/kibana/components/agg_table/agg_table.js | 4 - src/kibana/components/visualize/spy/_table.js | 6 +- src/kibana/controllers/kibana.js | 14 ++- src/kibana/directives/paginate.js | 90 ++++++++++++++----- src/kibana/partials/paginate_controls.html | 43 ++++----- src/kibana/styles/_pagination.less | 16 ++-- 7 files changed, 115 insertions(+), 60 deletions(-) diff --git a/src/kibana/components/agg_table/agg_table.html b/src/kibana/components/agg_table/agg_table.html index 253bcd57f875b6..d52206baef9392 100644 --- a/src/kibana/components/agg_table/agg_table.html +++ b/src/kibana/components/agg_table/agg_table.html @@ -2,7 +2,7 @@ ng-if="formattedRows.length" list="formattedRows" - per-page="aggTable.getPerPage()" + per-page-prop="perPage" class="agg-table">
diff --git a/src/kibana/components/agg_table/agg_table.js b/src/kibana/components/agg_table/agg_table.js index c887211738246c..42df4e13474faa 100644 --- a/src/kibana/components/agg_table/agg_table.js +++ b/src/kibana/components/agg_table/agg_table.js @@ -33,10 +33,6 @@ define(function (require) { quoteValues: config.get('csv:quoteValues') }; - self.getPerPage = function () { - return $scope.perPage || Infinity; - }; - self.getColumnClass = function (col, $first, $last) { var cls = []; var agg = $scope.table.aggConfig(col); diff --git a/src/kibana/components/visualize/spy/_table.js b/src/kibana/components/visualize/spy/_table.js index 3513370697d5ff..75555672f170dd 100644 --- a/src/kibana/components/visualize/spy/_table.js +++ b/src/kibana/components/visualize/spy/_table.js @@ -5,7 +5,6 @@ define(function (require) { var tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); var PER_PAGE_DEFAULT = 10; - var PER_PAGE_EXTENDED = 20; require('components/agg_table/agg_table'); @@ -15,10 +14,7 @@ define(function (require) { order: 1, template: require('text!components/visualize/spy/_table.html'), link: function tableLinkFn($scope, $el) { - $scope.$parent.$watch('spyMode.fill', function (fill) { - $scope.perPage = fill ? PER_PAGE_EXTENDED : PER_PAGE_DEFAULT; - }); - + $scope.perPage = PER_PAGE_DEFAULT; $rootScope.$watchMulti.call($scope, [ 'vis', 'esResp' diff --git a/src/kibana/controllers/kibana.js b/src/kibana/controllers/kibana.js index 949d44f05ccafa..701a4e2687ea43 100644 --- a/src/kibana/controllers/kibana.js +++ b/src/kibana/controllers/kibana.js @@ -53,9 +53,10 @@ define(function (require) { expressions.forEach(function (expr, i) { $scope.$watch(expr, function (newVal, oldVal) { vals.new[i] = newVal; - vals.old[i] = oldVal; if (initQueue) { + vals.old[i] = oldVal; + var qIdx = initQueue.indexOf(expr); if (qIdx !== -1) initQueue.splice(qIdx, 1); if (initQueue.length === 0) { @@ -69,7 +70,16 @@ define(function (require) { fired = true; $scope.$evalAsync(function () { fired = false; - fn(vals.new.slice(0), vals.old.slice(0)); + + if (fn.length) { + fn(vals.new.slice(0), vals.old.slice(0)); + } else { + fn(); + } + + for (var i = 0; i < vals.new.length; i++) { + vals.old[i] = vals.new[i]; + } }); }); }); diff --git a/src/kibana/directives/paginate.js b/src/kibana/directives/paginate.js index a23ffdc1f090f2..3e88727addf21f 100644 --- a/src/kibana/directives/paginate.js +++ b/src/kibana/directives/paginate.js @@ -8,7 +8,6 @@ define(function (require) { return { restrict: 'E', scope: true, - controllerAs: 'paginate', link: { pre: function ($scope, $el) { if ($el.find('paginate-controls').size() === 0) { @@ -19,26 +18,62 @@ define(function (require) { var paginate = $scope.paginate; // add some getters to the controller powered by attributes + paginate.getList = $parse(attrs.list); + paginate.perPageProp = attrs.perPageProp; paginate.otherWidthGetter = $parse(attrs.otherWidth); - paginate.perPageGetter = $parse(attrs.perPage); - paginate.perPageSetter = paginate.perPageGetter.assign; - $scope.$watch(attrs.perPage, function (perPage) { - paginate.perPage = perPage; + paginate.init(); + } + }, + controllerAs: 'paginate', + controller: function ($scope) { + var self = this; + var ALL = Infinity; + + self.sizeOptions = [ + { title: '10', value: 10 }, + { title: '25', value: 25 }, + { title: '100', value: 100 }, + { title: 'All', value: ALL } + ]; + + // setup the watchers, called in the post-link function + self.init = function () { + self.perPage = $scope[self.perPageProp]; + + $scope.$watchMulti([ + 'paginate.perPage', + self.perPageProp, + self.otherWidthGetter + ], function (vals, oldVals) { + var intChanges = vals[0] !== oldVals[0]; + var extChanges = vals[1] !== oldVals[1]; + + if (intChanges) { + if (!setPerPage(self.perPage)) { + // if we are not able to set the external value, + // render now, otherwise wait for the external value + // to trigger the watcher again + self.renderList(); + } + return; + } + + self.perPage = $scope[self.perPageProp]; + if (!self.perPage) { + self.perPage = ALL; + return; + } + + self.renderList(); }); - $scope.$watch('paginate.perPage', paginate.renderList); - $scope.$watch('page', paginate.changePage); - $scope.$watchCollection(attrs.list, function (list) { + $scope.$watch('page', self.changePage); + $scope.$watchCollection(self.getList, function (list) { $scope.list = list; - paginate.renderList(); + self.renderList(); }); - - }, - }, - controller: function ($scope) { - - var self = this; + }; self.goToPage = function (number) { if (number) { @@ -51,8 +86,9 @@ define(function (require) { $scope.pages = []; if (!$scope.list) return; - var perPage = self.perPage || Infinity; - var count = isFinite(perPage) ? Math.ceil($scope.list.length / perPage) : 1; + var perPage = self.perPage; + var shouldSplit = perPage && isFinite(perPage); + var count = shouldSplit ? Math.ceil($scope.list.length / perPage) : 1; _.times(count, function (i) { var page; @@ -120,6 +156,19 @@ define(function (require) { if (other.first) $scope.otherPages.containsFirst = true; } }; + + function setPerPage(val) { + var $ppParent = $scope; + + while ($ppParent && !_.has($ppParent, self.perPageProp)) { + $ppParent = $ppParent.$parent; + } + + if ($ppParent) { + $ppParent[self.perPageProp] = val; + return true; + } + } } }; }) @@ -127,12 +176,7 @@ define(function (require) { // this directive is automatically added by paginate if not found within it's $el return { restrict: 'E', - template: require('text!partials/paginate_controls.html'), - link: function ($scope, $el) { - $scope.$watch('page.count > 1', function (show) { - $el.toggle(show); - }); - } + template: require('text!partials/paginate_controls.html') }; }); diff --git a/src/kibana/partials/paginate_controls.html b/src/kibana/partials/paginate_controls.html index 2e42fc2894b751..7d8889f35ac2fd 100644 --- a/src/kibana/partials/paginate_controls.html +++ b/src/kibana/partials/paginate_controls.html @@ -1,30 +1,33 @@ - +
- +
\ No newline at end of file diff --git a/src/kibana/styles/_pagination.less b/src/kibana/styles/_pagination.less index 37991c069ddc2d..57f3d602f7c516 100644 --- a/src/kibana/styles/_pagination.less +++ b/src/kibana/styles/_pagination.less @@ -8,16 +8,22 @@ paginate { padding: 5px 5px 10px; text-align: center; - .pagination { + .pagination-other-pages { .flex(1, 0, auto); .display(flex); .justify-content(center); - // override some bootstrap styles - margin: 0 auto; - - > li { + &-list { .flex(0, 0, auto); + .display(flex); + .justify-content(center); + padding: 0; + margin: 0; + list-style: none; + + > li { + .flex(0, 0, auto); + } } } From 0991a80dd30c54cd3733e443cbdad85a1effb7b6 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 4 Nov 2014 11:41:07 -0700 Subject: [PATCH 38/45] [aggTable] fix module name spelling --- src/kibana/plugins/table_vis/index.js | 2 +- src/kibana/plugins/table_vis/table_vis.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kibana/plugins/table_vis/index.js b/src/kibana/plugins/table_vis/index.js index becfa7186af242..78d8df2ac7c264 100644 --- a/src/kibana/plugins/table_vis/index.js +++ b/src/kibana/plugins/table_vis/index.js @@ -1,5 +1,5 @@ define(function (require) { - require('registry/vis_types').register(function TableVisPrivateMoudleLoader(Private) { + require('registry/vis_types').register(function TableVisPrivateModuleLoader(Private) { return Private(require('plugins/table_vis/table_vis')); }); }); \ No newline at end of file diff --git a/src/kibana/plugins/table_vis/table_vis.js b/src/kibana/plugins/table_vis/table_vis.js index 821b3119c191e3..eceb2834922114 100644 --- a/src/kibana/plugins/table_vis/table_vis.js +++ b/src/kibana/plugins/table_vis/table_vis.js @@ -1,5 +1,5 @@ define(function (require) { - // we need to load the css ourselved + // we need to load the css ourselves require('css!plugins/table_vis/table_vis.css'); // we also need to load the controller and used by the template From dc83e60389ce7795d99060aad6af54c1045142a7 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 4 Nov 2014 11:42:00 -0700 Subject: [PATCH 39/45] [aggResp/tabify] rename tabify entry module --- src/kibana/components/agg_response/index.js | 3 ++- .../agg_response/tabify/{tabify_agg_response.js => tabify.js} | 0 src/kibana/components/agg_table/agg_table.js | 2 +- src/kibana/components/visualize/spy/_table.js | 2 +- src/kibana/plugins/table_vis/table_vis_controller.js | 2 +- test/unit/index.html | 2 +- test/unit/specs/components/agg_response/tabify/_integration.js | 2 +- test/unit/specs/components/agg_table/_group.js | 2 +- test/unit/specs/components/agg_table/_table.js | 2 +- 9 files changed, 9 insertions(+), 8 deletions(-) rename src/kibana/components/agg_response/tabify/{tabify_agg_response.js => tabify.js} (100%) diff --git a/src/kibana/components/agg_response/index.js b/src/kibana/components/agg_response/index.js index c3412602159ec1..41fc8ff5954220 100644 --- a/src/kibana/components/agg_response/index.js +++ b/src/kibana/components/agg_response/index.js @@ -2,7 +2,8 @@ define(function (require) { return function NormalizeChartDataFactory(Private) { return { flat: Private(require('components/agg_response/flat')), - hierarchical: Private(require('components/agg_response/hierarchical/build_hierarchical_data')) + hierarchical: Private(require('components/agg_response/hierarchical/build_hierarchical_data')), + tabify: Private(require('components/agg_response/tabify/tabify')) }; }; }); \ No newline at end of file diff --git a/src/kibana/components/agg_response/tabify/tabify_agg_response.js b/src/kibana/components/agg_response/tabify/tabify.js similarity index 100% rename from src/kibana/components/agg_response/tabify/tabify_agg_response.js rename to src/kibana/components/agg_response/tabify/tabify.js diff --git a/src/kibana/components/agg_table/agg_table.js b/src/kibana/components/agg_table/agg_table.js index 42df4e13474faa..b894c92e14fc16 100644 --- a/src/kibana/components/agg_table/agg_table.js +++ b/src/kibana/components/agg_table/agg_table.js @@ -8,7 +8,7 @@ define(function (require) { var _ = require('lodash'); var saveAs = require('file_saver'); - var tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); + var tabifyAggResponse = Private(require('components/agg_response/tabify/tabify')); var orderBy = $filter('orderBy'); return { diff --git a/src/kibana/components/visualize/spy/_table.js b/src/kibana/components/visualize/spy/_table.js index 75555672f170dd..3073cf6c58f506 100644 --- a/src/kibana/components/visualize/spy/_table.js +++ b/src/kibana/components/visualize/spy/_table.js @@ -2,7 +2,7 @@ define(function (require) { function VisSpyTableProvider(Notifier, $filter, $rootScope, config, Private) { var _ = require('lodash'); var saveAs = require('file_saver'); - var tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); + var tabifyAggResponse = Private(require('components/agg_response/tabify/tabify')); var PER_PAGE_DEFAULT = 10; diff --git a/src/kibana/plugins/table_vis/table_vis_controller.js b/src/kibana/plugins/table_vis/table_vis_controller.js index cbe03550357096..9963ec8c3066bd 100644 --- a/src/kibana/plugins/table_vis/table_vis_controller.js +++ b/src/kibana/plugins/table_vis/table_vis_controller.js @@ -6,7 +6,7 @@ define(function (require) { // add a controller to tha module, which will transform the esResponse into a // tabular format that we can pass to the table directive module.controller('KbnTableVisController', function ($scope, Private) { - var tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); + var tabifyAggResponse = Private(require('components/agg_response/tabify/tabify')); $scope.$watch('esResponse', function (resp, oldResp) { if (!resp) $scope.tableGroups = null; diff --git a/test/unit/index.html b/test/unit/index.html index 8f339fc654c571..59f60c6b67c759 100644 --- a/test/unit/index.html +++ b/test/unit/index.html @@ -158,7 +158,7 @@ 'specs/components/agg_response/hierarchical/_create_raw_data', 'specs/components/agg_response/hierarchical/_array_to_linked_list', 'specs/components/agg_response/hierarchical/_collect_branch', - 'specs/components/agg_response/tabify/tabify_agg_response', + 'specs/components/agg_response/tabify/tabify', 'specs/components/agg_table/index' ], function () { bootstrap(kibana, sinon); diff --git a/test/unit/specs/components/agg_response/tabify/_integration.js b/test/unit/specs/components/agg_response/tabify/_integration.js index 163e1e232ea9ed..c80a431b8d7bd3 100644 --- a/test/unit/specs/components/agg_response/tabify/_integration.js +++ b/test/unit/specs/components/agg_response/tabify/_integration.js @@ -10,7 +10,7 @@ define(function (require) { beforeEach(module('kibana')); beforeEach(inject(function (Private, $injector) { - tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); + tabifyAggResponse = Private(require('components/agg_response/tabify/tabify')); Vis = Private(require('components/vis/vis')); indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); })); diff --git a/test/unit/specs/components/agg_table/_group.js b/test/unit/specs/components/agg_table/_group.js index 01a55224adc2ef..bb4b5736058823 100644 --- a/test/unit/specs/components/agg_table/_group.js +++ b/test/unit/specs/components/agg_table/_group.js @@ -12,7 +12,7 @@ define(function (require) { beforeEach(module('kibana')); beforeEach(inject(function ($injector, Private) { - tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); + tabifyAggResponse = Private(require('components/agg_response/tabify/tabify')); indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); Vis = Private(require('components/vis/vis')); diff --git a/test/unit/specs/components/agg_table/_table.js b/test/unit/specs/components/agg_table/_table.js index b7a1ab234ed774..04b686bdfe2790 100644 --- a/test/unit/specs/components/agg_table/_table.js +++ b/test/unit/specs/components/agg_table/_table.js @@ -12,7 +12,7 @@ define(function (require) { beforeEach(module('kibana')); beforeEach(inject(function ($injector, Private) { - tabifyAggResponse = Private(require('components/agg_response/tabify/tabify_agg_response')); + tabifyAggResponse = Private(require('components/agg_response/tabify/tabify')); indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern')); Vis = Private(require('components/vis/vis')); From 6f64f8f2aa209fc36800b8ba0d2c5921dea6ff44 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 4 Nov 2014 13:58:23 -0700 Subject: [PATCH 40/45] [tableVis] set the page size from the VisParams --- src/kibana/components/agg_table/agg_table.html | 2 +- src/kibana/components/agg_table/agg_table_group.html | 8 ++++---- src/kibana/components/agg_table/agg_table_group.js | 3 ++- src/kibana/plugins/table_vis/table_vis.html | 2 +- src/kibana/plugins/table_vis/table_vis.js | 6 ++++++ src/kibana/plugins/table_vis/table_vis_config.html | 4 ++++ 6 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 src/kibana/plugins/table_vis/table_vis_config.html diff --git a/src/kibana/components/agg_table/agg_table.html b/src/kibana/components/agg_table/agg_table.html index d52206baef9392..eb9f7a9f806070 100644 --- a/src/kibana/components/agg_table/agg_table.html +++ b/src/kibana/components/agg_table/agg_table.html @@ -25,7 +25,7 @@ - + diff --git a/src/kibana/components/agg_table/agg_table_group.html b/src/kibana/components/agg_table/agg_table_group.html index ad1811a39ef725..69c369d75331d1 100644 --- a/src/kibana/components/agg_table/agg_table_group.html +++ b/src/kibana/components/agg_table/agg_table_group.html @@ -9,8 +9,8 @@ - - + + @@ -27,8 +27,8 @@ - - + + diff --git a/src/kibana/components/agg_table/agg_table_group.js b/src/kibana/components/agg_table/agg_table_group.js index 136706e5d1f256..0de633fe5706d1 100644 --- a/src/kibana/components/agg_table/agg_table_group.js +++ b/src/kibana/components/agg_table/agg_table_group.js @@ -9,7 +9,8 @@ define(function (require) { restrict: 'E', template: require('text!components/agg_table/agg_table_group.html'), scope: { - group: '=' + group: '=', + perPage: '=?' }, compile: function ($el) { // Use the compile function from the RecursionHelper, diff --git a/src/kibana/plugins/table_vis/table_vis.html b/src/kibana/plugins/table_vis/table_vis.html index 2f4e13b3c6b62a..e4477bc15b289f 100644 --- a/src/kibana/plugins/table_vis/table_vis.html +++ b/src/kibana/plugins/table_vis/table_vis.html @@ -1,3 +1,3 @@
- +
\ No newline at end of file diff --git a/src/kibana/plugins/table_vis/table_vis.js b/src/kibana/plugins/table_vis/table_vis.js index eceb2834922114..3f7e2d4f0bbfe6 100644 --- a/src/kibana/plugins/table_vis/table_vis.js +++ b/src/kibana/plugins/table_vis/table_vis.js @@ -24,6 +24,12 @@ define(function (require) { title: 'Data Table', icon: 'fa-table', template: require('text!plugins/table_vis/table_vis.html'), + params: { + defaults: { + perPage: 10 + }, + editor: require('text!plugins/table_vis/table_vis_config.html') + }, schemas: new Schemas([ { group: 'metrics', diff --git a/src/kibana/plugins/table_vis/table_vis_config.html b/src/kibana/plugins/table_vis/table_vis_config.html new file mode 100644 index 00000000000000..f454b04d976abe --- /dev/null +++ b/src/kibana/plugins/table_vis/table_vis_config.html @@ -0,0 +1,4 @@ +
+ + +
\ No newline at end of file From d757d49f64992d166e792fec087134f2be9ac4e1 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 4 Nov 2014 14:26:03 -0700 Subject: [PATCH 41/45] [aggTable] hide the pageSize control in the pagination-controls --- src/kibana/plugins/table_vis/table_vis.less | 10 ++++++++++ src/kibana/plugins/table_vis/table_vis_config.html | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/kibana/plugins/table_vis/table_vis.less b/src/kibana/plugins/table_vis/table_vis.less index 04e7ac3a116b8b..6597ef92cae5eb 100644 --- a/src/kibana/plugins/table_vis/table_vis.less +++ b/src/kibana/plugins/table_vis/table_vis.less @@ -1,7 +1,17 @@ +@import (reference) "lesshat.less"; + .TableVis { width: 100%; kbn-agg-table-group > .table > tbody > tr > td { border-top: 0px; } + + .pagination-other-pages { + .justify-content(flex-end); + } + + .pagination-size { + display: none; + } } diff --git a/src/kibana/plugins/table_vis/table_vis_config.html b/src/kibana/plugins/table_vis/table_vis_config.html index f454b04d976abe..f11c55322dae45 100644 --- a/src/kibana/plugins/table_vis/table_vis_config.html +++ b/src/kibana/plugins/table_vis/table_vis_config.html @@ -1,4 +1,4 @@
- - + +
\ No newline at end of file From 1c187423919c2e1d0c3754d552383f5fc5d3bfdd Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 4 Nov 2014 14:35:38 -0700 Subject: [PATCH 42/45] [pagination] stabalize link styles --- src/kibana/styles/_pagination.less | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/kibana/styles/_pagination.less b/src/kibana/styles/_pagination.less index 57f3d602f7c516..22440da942dbe9 100644 --- a/src/kibana/styles/_pagination.less +++ b/src/kibana/styles/_pagination.less @@ -23,6 +23,21 @@ paginate { > li { .flex(0, 0, auto); + .user-select(none); + + a { + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + + &.active a { + text-decoration: none !important; + font-weight: bold; + color: @text-color; + } } } } From 6db07e8651bb78aac7012a82629ac7aebe9ba454 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 4 Nov 2014 15:02:15 -0700 Subject: [PATCH 43/45] [spy] scroll the content, not the toggle --- src/kibana/components/visualize/spy/_spy.html | 36 ++++++++++--------- src/kibana/components/visualize/spy/spy.js | 3 +- .../components/visualize/visualize.less | 6 +++- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/kibana/components/visualize/spy/_spy.html b/src/kibana/components/visualize/spy/_spy.html index 76aad84b82ccc4..fe2fbddd6cd7e3 100644 --- a/src/kibana/components/visualize/spy/_spy.html +++ b/src/kibana/components/visualize/spy/_spy.html @@ -3,20 +3,22 @@ -
- - -
\ No newline at end of file + \ No newline at end of file diff --git a/src/kibana/components/visualize/spy/spy.js b/src/kibana/components/visualize/spy/spy.js index 54e9839b420666..fde6071e84cc75 100644 --- a/src/kibana/components/visualize/spy/spy.js +++ b/src/kibana/components/visualize/spy/spy.js @@ -15,6 +15,7 @@ define(function (require) { restrict: 'E', template: require('text!components/visualize/spy/_spy.html'), link: function ($scope, $el) { + var $container = $el.find('.visualize-spy-container'); var fullPageSpy = false; // $scope.spyMode = null; // inherited from the parent $scope.modes = modes; @@ -58,7 +59,7 @@ define(function (require) { display: newMode.display, fill: fullPageSpy, $scope: $scope.$new(), - $container: $('
').appendTo($el) + $container: $('
').appendTo($container) }; current.$container.append($compile(newMode.template)(current.$scope)); diff --git a/src/kibana/components/visualize/visualize.less b/src/kibana/components/visualize/visualize.less index 005400a557485d..2d73cb0e740789 100644 --- a/src/kibana/components/visualize/visualize.less +++ b/src/kibana/components/visualize/visualize.less @@ -76,10 +76,14 @@ visualize-spy { .display(flex); .flex-direction(column); - height: 382px; + height: 482px; padding: 10px 10px 0; overflow-y: auto; + header { + padding: 0 0 15px; + } + > .alert { .flex(0 0 auto); } From a56fe750a200ab43e58623b03999d689eca4b7f0 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 4 Nov 2014 16:03:14 -0700 Subject: [PATCH 44/45] [aggResp/tabify] fix the test module name --- .../agg_response/tabify/{tabify_agg_response.js => tabify.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/unit/specs/components/agg_response/tabify/{tabify_agg_response.js => tabify.js} (100%) diff --git a/test/unit/specs/components/agg_response/tabify/tabify_agg_response.js b/test/unit/specs/components/agg_response/tabify/tabify.js similarity index 100% rename from test/unit/specs/components/agg_response/tabify/tabify_agg_response.js rename to test/unit/specs/components/agg_response/tabify/tabify.js From 920e366785b91fc6629258fad9ba828741575126 Mon Sep 17 00:00:00 2001 From: Spencer Alger Date: Tue, 4 Nov 2014 16:08:05 -0700 Subject: [PATCH 45/45] [aggTableGroup] be sure to clear the previous rows/columns --- src/kibana/components/agg_table/agg_table_group.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/kibana/components/agg_table/agg_table_group.js b/src/kibana/components/agg_table/agg_table_group.js index 0de633fe5706d1..949f0c0b5a35bd 100644 --- a/src/kibana/components/agg_table/agg_table_group.js +++ b/src/kibana/components/agg_table/agg_table_group.js @@ -18,14 +18,10 @@ define(function (require) { return compileRecursiveDirective.compile($el, { post: function ($scope) { $scope.$watch('group', function (group) { - if (group && !group.tables.length) { - group = null; - } + // clear the previous "state" + $scope.rows = $scope.columns = false; - if (!group) { - $scope.rows = $scope.columns = false; - return; - } + if (!group || !group.tables.length) return; var firstTable = group.tables[0]; var params = firstTable.aggConfig && firstTable.aggConfig.params;