diff --git a/package.json b/package.json
index 31c552fe6c2..eecf826776e 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
"grunt-string-replace": "~0.2.4",
"grunt-usemin": "^2.1.1",
"jshint-stylish": "~0.1.5",
- "karma": "~0.12.16",
+ "karma": "~0.12.21",
"karma-chrome-launcher": "~0.1.4",
"karma-coffee-preprocessor": "~0.1.2",
"karma-coverage": "^0.2.5",
diff --git a/src/app/panels/graph/timeSeries.js b/src/app/components/timeSeries.js
similarity index 51%
rename from src/app/panels/graph/timeSeries.js
rename to src/app/components/timeSeries.js
index f1794cd6a30..85c27f1da88 100644
--- a/src/app/panels/graph/timeSeries.js
+++ b/src/app/components/timeSeries.js
@@ -5,15 +5,57 @@ define([
function (_, kbn) {
'use strict';
- var ts = {};
-
- ts.ZeroFilled = function (opts) {
+ function TimeSeries(opts) {
this.datapoints = opts.datapoints;
this.info = opts.info;
this.label = opts.info.alias;
+ }
+
+ function matchSeriesOverride(aliasOrRegex, seriesAlias) {
+ if (!aliasOrRegex) { return false; }
+
+ if (aliasOrRegex[0] === '/') {
+ var match = aliasOrRegex.match(new RegExp('^/(.*?)/(g?i?m?y?)$'));
+ var regex = new RegExp(match[1], match[2]);
+ return seriesAlias.match(regex) != null;
+ }
+
+ return aliasOrRegex === seriesAlias;
+ }
+
+ function translateFillOption(fill) {
+ return fill === 0 ? 0.001 : fill/10;
+ }
+
+ TimeSeries.prototype.applySeriesOverrides = function(overrides) {
+ this.lines = {};
+ this.points = {};
+ this.bars = {};
+ this.info.yaxis = 1;
+ this.zindex = 0;
+ delete this.stack;
+
+ for (var i = 0; i < overrides.length; i++) {
+ var override = overrides[i];
+ if (!matchSeriesOverride(override.alias, this.info.alias)) {
+ continue;
+ }
+ if (override.lines !== void 0) { this.lines.show = override.lines; }
+ if (override.points !== void 0) { this.points.show = override.points; }
+ if (override.bars !== void 0) { this.bars.show = override.bars; }
+ if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); }
+ if (override.stack !== void 0) { this.stack = override.stack; }
+ if (override.linewidth !== void 0) { this.lines.lineWidth = override.linewidth; }
+ if (override.pointradius !== void 0) { this.points.radius = override.pointradius; }
+ if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; }
+ if (override.zindex !== void 0) { this.zindex = override.zindex; }
+ if (override.yaxis !== void 0) {
+ this.info.yaxis = override.yaxis;
+ }
+ }
};
- ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle, yFormats) {
+ TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) {
var result = [];
this.color = this.info.color;
@@ -74,5 +116,6 @@ function (_, kbn) {
return result;
};
- return ts;
-});
\ No newline at end of file
+ return TimeSeries;
+
+});
diff --git a/src/app/directives/addGraphiteFunc.js b/src/app/directives/addGraphiteFunc.js
index ca5943da508..6898b838845 100644
--- a/src/app/directives/addGraphiteFunc.js
+++ b/src/app/directives/addGraphiteFunc.js
@@ -97,4 +97,4 @@ function (angular, app, _, $, gfunc) {
};
});
}
-});
\ No newline at end of file
+});
diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js
index a91967b967b..38ef36bc912 100755
--- a/src/app/directives/grafanaGraph.js
+++ b/src/app/directives/grafanaGraph.js
@@ -118,7 +118,7 @@ function (angular, $, kbn, moment, _) {
lines: {
show: panel.lines,
zero: false,
- fill: panel.fill === 0 ? 0.001 : panel.fill/10,
+ fill: translateFillOption(panel.fill),
lineWidth: panel.linewidth,
steps: panel.steppedLine
},
@@ -154,11 +154,12 @@ function (angular, $, kbn, moment, _) {
};
for (var i = 0; i < data.length; i++) {
- var _d = data[i].getFlotPairs(panel.nullPointMode, panel.y_formats);
- data[i].data = _d;
+ var series = data[i];
+ series.applySeriesOverrides(panel.seriesOverrides);
+ series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats);
}
- if (panel.bars && data.length && data[0].info.timeStep) {
+ if (data.length && data[0].info.timeStep) {
options.series.bars.barWidth = data[0].info.timeStep / 1.5;
}
@@ -167,21 +168,27 @@ function (angular, $, kbn, moment, _) {
addAnnotations(options);
configureAxisOptions(data, options);
+ var sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
+
// if legend is to the right delay plot draw a few milliseconds
// so the legend width calculation can be done
if (shouldDelayDraw(panel)) {
legendSideLastValue = panel.legend.rightSide;
setTimeout(function() {
- plot = $.plot(elem, data, options);
+ plot = $.plot(elem, sortedSeries, options);
addAxisLabels();
}, 50);
}
else {
- plot = $.plot(elem, data, options);
+ plot = $.plot(elem, sortedSeries, options);
addAxisLabels();
}
}
+ function translateFillOption(fill) {
+ return fill === 0 ? 0.001 : fill/10;
+ }
+
function shouldDelayDraw(panel) {
if (panel.legend.rightSide) {
return true;
diff --git a/src/app/panels/graph/legend.html b/src/app/panels/graph/legend.html
index 5524a8047fe..0e5edc459ce 100755
--- a/src/app/panels/graph/legend.html
+++ b/src/app/panels/graph/legend.html
@@ -34,12 +34,12 @@
-
-
-
diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js
index 647e52c257e..6897cc090b0 100644
--- a/src/app/panels/graph/module.js
+++ b/src/app/panels/graph/module.js
@@ -1,16 +1,3 @@
-/** @scratch /panels/5
- * include::panels/histogram.asciidoc[]
- */
-
-/** @scratch /panels/histogram/0
- * == Histogram
- * Status: *Stable*
- *
- * The histogram panel allow for the display of time charts. It includes several modes and tranformations
- * to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
- * fields.
- *
- */
define([
'angular',
'app',
@@ -18,7 +5,8 @@ define([
'lodash',
'kbn',
'moment',
- './timeSeries',
+ 'components/timeSeries',
+ './seriesOverridesCtrl',
'services/panelSrv',
'services/annotationsSrv',
'services/datasourceSrv',
@@ -29,11 +17,10 @@ define([
'jquery.flot.stack',
'jquery.flot.stackpercent'
],
-function (angular, app, $, _, kbn, moment, timeSeries) {
-
+function (angular, app, $, _, kbn, moment, TimeSeries) {
'use strict';
- var module = angular.module('grafana.panels.graph', []);
+ var module = angular.module('grafana.panels.graph');
app.useModule(module);
module.controller('GraphCtrl', function($scope, $rootScope, $timeout, panelSrv, annotationsSrv) {
@@ -179,7 +166,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
targets: [{}],
aliasColors: {},
- aliasYAxis: {},
+
+ seriesOverrides: [],
};
_.defaults($scope.panel,_d);
@@ -258,18 +246,15 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
var color = $scope.panel.aliasColors[alias] || $rootScope.colors[index];
- var yaxis = $scope.panel.aliasYAxis[alias] || 1;
var seriesInfo = {
alias: alias,
color: color,
- enable: true,
- yaxis: yaxis
};
$scope.legend.push(seriesInfo);
- var series = new timeSeries.ZeroFilled({
+ var series = new TimeSeries({
datapoints: datapoints,
info: seriesInfo,
});
@@ -347,8 +332,12 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
};
$scope.toggleYAxis = function(info) {
- info.yaxis = info.yaxis === 2 ? 1 : 2;
- $scope.panel.aliasYAxis[info.alias] = info.yaxis;
+ var override = _.findWhere($scope.panel.seriesOverrides, { alias: info.alias });
+ if (!override) {
+ override = { alias: info.alias };
+ $scope.panel.seriesOverrides.push(override);
+ }
+ override.yaxis = info.yaxis === 2 ? 1 : 2;
$scope.render();
};
@@ -357,6 +346,15 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
$scope.render();
};
+ $scope.addSeriesOverride = function() {
+ $scope.panel.seriesOverrides.push({});
+ };
+
+ $scope.removeSeriesOverride = function(override) {
+ $scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override);
+ $scope.render();
+ };
+
panelSrv.init($scope);
});
diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js
new file mode 100644
index 00000000000..4f54c6d0e6f
--- /dev/null
+++ b/src/app/panels/graph/seriesOverridesCtrl.js
@@ -0,0 +1,80 @@
+define([
+ 'angular',
+ 'app',
+ 'lodash',
+], function(angular, app, _) {
+ 'use strict';
+
+ var module = angular.module('grafana.panels.graph', []);
+ app.useModule(module);
+
+ module.controller('SeriesOverridesCtrl', function($scope) {
+ $scope.overrideMenu = [];
+ $scope.currentOverrides = [];
+ $scope.override = $scope.override || {};
+
+ $scope.addOverrideOption = function(name, propertyName, values) {
+ var option = {};
+ option.text = name;
+ option.propertyName = propertyName;
+ option.index = $scope.overrideMenu.length;
+ option.values = values;
+
+ option.submenu = _.map(values, function(value, index) {
+ return {
+ text: String(value),
+ click: 'setOverride(' + option.index + ',' + index + ')'
+ };
+ });
+
+ $scope.overrideMenu.push(option);
+ };
+
+ $scope.setOverride = function(optionIndex, valueIndex) {
+ var option = $scope.overrideMenu[optionIndex];
+ var value = option.values[valueIndex];
+ $scope.override[option.propertyName] = value;
+ $scope.updateCurrentOverrides();
+ $scope.render();
+ };
+
+ $scope.removeOverride = function(option) {
+ delete $scope.override[option.propertyName];
+ $scope.updateCurrentOverrides();
+ $scope.render();
+ };
+
+ $scope.getSeriesNames = function() {
+ return _.map($scope.legend, function(info) {
+ return info.alias;
+ });
+ };
+
+ $scope.updateCurrentOverrides = function() {
+ $scope.currentOverrides = [];
+ _.each($scope.overrideMenu, function(option) {
+ var value = $scope.override[option.propertyName];
+ if (_.isUndefined(value)) { return; }
+ $scope.currentOverrides.push({
+ name: option.text,
+ propertyName: option.propertyName,
+ value: String(value)
+ });
+ });
+ };
+
+ $scope.addOverrideOption('Bars', 'bars', [true, false]);
+ $scope.addOverrideOption('Lines', 'lines', [true, false]);
+ $scope.addOverrideOption('Line fill', 'fill', [0,1,2,3,4,5,6,7,8,9,10]);
+ $scope.addOverrideOption('Line width', 'linewidth', [0,1,2,3,4,5,6,7,8,9,10]);
+ $scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]);
+ $scope.addOverrideOption('Points', 'points', [true, false]);
+ $scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]);
+ $scope.addOverrideOption('Stack', 'stack', [true, false]);
+ $scope.addOverrideOption('Y-axis', 'yaxis', [1, 2]);
+ $scope.addOverrideOption('Z-index', 'zindex', [-1,-2,-3,0,1,2,3]);
+ $scope.updateCurrentOverrides();
+
+ });
+
+});
diff --git a/src/app/panels/graph/styleEditor.html b/src/app/panels/graph/styleEditor.html
index ec18121cba7..de7318d4500 100644
--- a/src/app/panels/graph/styleEditor.html
+++ b/src/app/panels/graph/styleEditor.html
@@ -1,5 +1,3 @@
-
-
Chart Options
@@ -64,3 +62,49 @@
+
+
+
+
Series specific overrides Regex match example: /server[0-3]/i
+
+
+
+
+
diff --git a/src/app/services/dashboard/dashboardSrv.js b/src/app/services/dashboard/dashboardSrv.js
index ff1e149c3b7..f2d67bf1839 100644
--- a/src/app/services/dashboard/dashboardSrv.js
+++ b/src/app/services/dashboard/dashboardSrv.js
@@ -144,80 +144,97 @@ function (angular, $, kbn, _, moment) {
};
p.updateSchema = function(old) {
- var i, j, row, panel;
var oldVersion = this.version;
- this.version = 3;
+ var panelUpgrades = [];
+ this.version = 4;
- if (oldVersion === 3) {
+ if (oldVersion === 4) {
return;
}
- // Version 3 schema changes
- // ensure panel ids
- var maxId = this.getNextPanelId();
- for (i = 0; i < this.rows.length; i++) {
- row = this.rows[i];
- for (j = 0; j < row.panels.length; j++) {
- panel = row.panels[j];
- if (!panel.id) {
- panel.id = maxId;
- maxId += 1;
+ // version 2 schema changes
+ if (oldVersion < 2) {
+
+ if (old.services) {
+ if (old.services.filter) {
+ this.time = old.services.filter.time;
+ this.templating.list = old.services.filter.list;
}
+ delete this.services;
}
- }
- if (oldVersion === 2) {
- return;
- }
-
- // Version 2 schema changes
- if (old.services) {
- if (old.services.filter) {
- this.time = old.services.filter.time;
- this.templating.list = old.services.filter.list;
- }
- delete this.services;
- }
-
- for (i = 0; i < this.rows.length; i++) {
- row = this.rows[i];
- for (j = 0; j < row.panels.length; j++) {
- panel = row.panels[j];
+ panelUpgrades.push(function(panel) {
+ // rename panel type
if (panel.type === 'graphite') {
panel.type = 'graph';
}
- if (panel.type === 'graph') {
- if (_.isBoolean(panel.legend)) {
- panel.legend = { show: panel.legend };
+ if (panel.type !== 'graph') {
+ return;
+ }
+
+ if (_.isBoolean(panel.legend)) { panel.legend = { show: panel.legend }; }
+
+ if (panel.grid) {
+ if (panel.grid.min) {
+ panel.grid.leftMin = panel.grid.min;
+ delete panel.grid.min;
}
- if (panel.grid) {
- if (panel.grid.min) {
- panel.grid.leftMin = panel.grid.min;
- delete panel.grid.min;
- }
-
- if (panel.grid.max) {
- panel.grid.leftMax = panel.grid.max;
- delete panel.grid.max;
- }
+ if (panel.grid.max) {
+ panel.grid.leftMax = panel.grid.max;
+ delete panel.grid.max;
}
+ }
- if (panel.y_format) {
- panel.y_formats[0] = panel.y_format;
- delete panel.y_format;
- }
+ if (panel.y_format) {
+ panel.y_formats[0] = panel.y_format;
+ delete panel.y_format;
+ }
- if (panel.y2_format) {
- panel.y_formats[1] = panel.y2_format;
- delete panel.y2_format;
- }
+ if (panel.y2_format) {
+ panel.y_formats[1] = panel.y2_format;
+ delete panel.y2_format;
+ }
+ });
+ }
+
+ // schema version 3 changes
+ if (oldVersion < 3) {
+ // ensure panel ids
+ var maxId = this.getNextPanelId();
+ panelUpgrades.push(function(panel) {
+ if (!panel.id) {
+ panel.id = maxId;
+ maxId += 1;
+ }
+ });
+ }
+
+ // schema version 4 changes
+ if (oldVersion < 4) {
+ // move aliasYAxis changes
+ panelUpgrades.push(function(panel) {
+ if (panel.type !== 'graph') { return; }
+ _.each(panel.aliasYAxis, function(value, key) {
+ panel.seriesOverrides = [{ alias: key, yaxis: value }];
+ });
+ delete panel.aliasYAxis;
+ });
+ }
+
+ if (panelUpgrades.length === 0) {
+ return;
+ }
+
+ for (var i = 0; i < this.rows.length; i++) {
+ var row = this.rows[i];
+ for (var j = 0; j < row.panels.length; j++) {
+ for (var k = 0; k < panelUpgrades.length; k++) {
+ panelUpgrades[k](row.panels[j]);
}
}
}
-
- this.version = 3;
};
return {
diff --git a/src/app/services/panelSrv.js b/src/app/services/panelSrv.js
index 3c362818f2d..cae4f14ff98 100644
--- a/src/app/services/panelSrv.js
+++ b/src/app/services/panelSrv.js
@@ -104,7 +104,7 @@ function (angular, _) {
// Post init phase
$scope.fullscreen = false;
- $scope.editor = { index: 1 };
+ $scope.editor = { index: 3 };
if ($scope.panelMeta.fullEditorTabs) {
$scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs, 'title');
}
diff --git a/src/css/less/graph.less b/src/css/less/graph.less
index 79e87ab8f47..8f049a44ae5 100644
--- a/src/css/less/graph.less
+++ b/src/css/less/graph.less
@@ -154,3 +154,17 @@
.annotation-tags {
color: @purple;
}
+
+.graph-series-override {
+ input {
+ float: left;
+ margin-right: 10px;
+ }
+ .graph-series-override-option {
+ float: left;
+ padding: 2px 6px;
+ }
+ .graph-series-override-selector {
+ float: left;
+ }
+}
diff --git a/src/test/specs/dashboardSrv-specs.js b/src/test/specs/dashboardSrv-specs.js
index 5e4e96401de..a2c7c1772f6 100644
--- a/src/test/specs/dashboardSrv-specs.js
+++ b/src/test/specs/dashboardSrv-specs.js
@@ -97,6 +97,7 @@ define([
{
type: 'graphite',
legend: true,
+ aliasYAxis: { test: 2 },
grid: { min: 1, max: 10 }
}
]
@@ -134,8 +135,13 @@ define([
expect(graph.grid.leftMax).to.be(10);
});
+ it('move aliasYAxis to series override', function() {
+ expect(graph.seriesOverrides[0].alias).to.be("test");
+ expect(graph.seriesOverrides[0].yaxis).to.be(2);
+ });
+
it('dashboard schema version should be set to latest', function() {
- expect(model.version).to.be(3);
+ expect(model.version).to.be(4);
});
});
diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js
new file mode 100644
index 00000000000..5f3ad0dd413
--- /dev/null
+++ b/src/test/specs/grafanaGraph-specs.js
@@ -0,0 +1,107 @@
+define([
+ './helpers',
+ 'angular',
+ 'jquery',
+ 'components/timeSeries',
+ 'directives/grafanaGraph'
+], function(helpers, angular, $, TimeSeries) {
+ 'use strict';
+
+ describe('grafanaGraph', function() {
+
+ beforeEach(module('grafana.directives'));
+
+ function graphScenario(desc, func) {
+ describe(desc, function() {
+ var ctx = {};
+ ctx.setup = function (setupFunc) {
+ beforeEach(inject(function($rootScope, $compile) {
+ var scope = $rootScope.$new();
+ var element = angular.element("");
+
+ scope.height = '200px';
+ scope.panel = {
+ legend: {},
+ grid: {},
+ y_formats: [],
+ seriesOverrides: []
+ };
+ scope.dashboard = { timezone: 'browser' };
+ scope.range = {
+ from: new Date('2014-08-09 10:00:00'),
+ to: new Date('2014-09-09 13:00:00')
+ };
+ ctx.data = [];
+ ctx.data.push(new TimeSeries({
+ datapoints: [[1,1],[2,2]],
+ info: { alias: 'series1', enable: true }
+ }));
+ ctx.data.push(new TimeSeries({
+ datapoints: [[1,1],[2,2]],
+ info: { alias: 'series2', enable: true }
+ }));
+
+ setupFunc(scope, ctx.data);
+
+ $compile(element)(scope);
+ scope.$digest();
+ $.plot = ctx.plotSpy = sinon.spy();
+
+ scope.$emit('render', ctx.data);
+ ctx.plotData = ctx.plotSpy.getCall(0).args[1];
+ ctx.plotOptions = ctx.plotSpy.getCall(0).args[2];
+ }));
+ };
+
+ func(ctx);
+ });
+ }
+
+ graphScenario('simple lines options', function(ctx) {
+ ctx.setup(function(scope) {
+ scope.panel.lines = true;
+ scope.panel.fill = 5;
+ scope.panel.linewidth = 3;
+ scope.panel.steppedLine = true;
+ });
+
+ it('should configure plot with correct options', function() {
+ expect(ctx.plotOptions.series.lines.show).to.be(true);
+ expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
+ expect(ctx.plotOptions.series.lines.lineWidth).to.be(3);
+ expect(ctx.plotOptions.series.lines.steps).to.be(true);
+ });
+ });
+
+ graphScenario('series option overrides, fill & points', function(ctx) {
+ ctx.setup(function(scope, data) {
+ scope.panel.lines = true;
+ scope.panel.fill = 5;
+ scope.panel.seriesOverrides = [
+ { alias: 'test', fill: 0, points: true }
+ ];
+
+ data[1].info.alias = 'test';
+ });
+
+ it('should match second series and fill zero, and enable points', function() {
+ expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
+ expect(ctx.plotData[1].lines.fill).to.be(0.001);
+ expect(ctx.plotData[1].points.show).to.be(true);
+ });
+ });
+
+ graphScenario('should order series order according to zindex', function(ctx) {
+ ctx.setup(function(scope) {
+ scope.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }];
+ });
+
+ it('should move zindex 2 last', function() {
+ expect(ctx.plotData[0].info.alias).to.be('series2');
+ expect(ctx.plotData[1].info.alias).to.be('series1');
+ });
+ });
+
+ });
+});
+
diff --git a/src/test/specs/helpers.js b/src/test/specs/helpers.js
index cd31779cc99..de2be527f9f 100644
--- a/src/test/specs/helpers.js
+++ b/src/test/specs/helpers.js
@@ -69,7 +69,7 @@ define([
}
return {
from : kbn.parseDate(this.time.from),
- to : kbn.parseDate(this.time.to)
+ to : kbn.parseDate(this.time.to)
};
};
diff --git a/src/test/specs/seriesOverridesCtrl-specs.js b/src/test/specs/seriesOverridesCtrl-specs.js
new file mode 100644
index 00000000000..e211b6dab35
--- /dev/null
+++ b/src/test/specs/seriesOverridesCtrl-specs.js
@@ -0,0 +1,51 @@
+define([
+ './helpers',
+ 'panels/graph/seriesOverridesCtrl'
+], function(helpers) {
+ 'use strict';
+
+ describe('SeriesOverridesCtrl', function() {
+ var ctx = new helpers.ControllerTestContext();
+
+ beforeEach(module('grafana.services'));
+ beforeEach(module('grafana.panels.graph'));
+
+ beforeEach(ctx.providePhase());
+ beforeEach(ctx.createControllerPhase('SeriesOverridesCtrl'));
+ beforeEach(function() {
+ ctx.scope.render = function() {};
+ });
+
+ describe('Controller should init overrideMenu', function() {
+ it('click should include option and value index', function() {
+ expect(ctx.scope.overrideMenu[1].submenu[1].click).to.be('setOverride(1,1)');
+ });
+ });
+
+ describe('When setting an override', function() {
+ beforeEach(function() {
+ ctx.scope.setOverride(1, 0);
+ });
+
+ it('should set override property', function() {
+ expect(ctx.scope.override.lines).to.be(true);
+ });
+
+ it('should update view model', function() {
+ expect(ctx.scope.currentOverrides[0].name).to.be('Lines');
+ expect(ctx.scope.currentOverrides[0].value).to.be('true');
+ });
+ });
+
+ describe('When removing overide', function() {
+ it('click should include option and value index', function() {
+ ctx.scope.setOverride(1,0);
+ ctx.scope.removeOverride({ propertyName: 'lines' });
+ expect(ctx.scope.currentOverrides.length).to.be(0);
+ });
+ });
+
+ });
+
+});
+
diff --git a/src/test/specs/timeSeries-specs.js b/src/test/specs/timeSeries-specs.js
new file mode 100644
index 00000000000..cf28c1aa450
--- /dev/null
+++ b/src/test/specs/timeSeries-specs.js
@@ -0,0 +1,115 @@
+define([
+ 'components/timeSeries'
+], function(TimeSeries) {
+ 'use strict';
+
+ describe("TimeSeries", function() {
+ var points, series;
+ var yAxisFormats = ['short', 'ms'];
+ var testData = {
+ info: { alias: 'test' },
+ datapoints: [
+ [1,2],[null,3],[10,4],[8,5]
+ ]
+ };
+
+ describe('when getting flot pairs', function() {
+ it('with connected style, should ignore nulls', function() {
+ series = new TimeSeries(testData);
+ points = series.getFlotPairs('connected', yAxisFormats);
+ expect(points.length).to.be(3);
+ });
+
+ it('with null as zero style, should replace nulls with zero', function() {
+ series = new TimeSeries(testData);
+ points = series.getFlotPairs('null as zero', yAxisFormats);
+ expect(points.length).to.be(4);
+ expect(points[1][1]).to.be(0);
+ });
+ });
+
+ describe('series overrides', function() {
+ var series;
+ beforeEach(function() {
+ series = new TimeSeries(testData);
+ });
+
+ describe('fill & points', function() {
+ beforeEach(function() {
+ series.info.alias = 'test';
+ series.applySeriesOverrides([{ alias: 'test', fill: 0, points: true }]);
+ });
+
+ it('should set fill zero, and enable points', function() {
+ expect(series.lines.fill).to.be(0.001);
+ expect(series.points.show).to.be(true);
+ });
+ });
+
+ describe('series option overrides, bars, true & lines false', function() {
+ beforeEach(function() {
+ series.info.alias = 'test';
+ series.applySeriesOverrides([{ alias: 'test', bars: true, lines: false }]);
+ });
+
+ it('should disable lines, and enable bars', function() {
+ expect(series.lines.show).to.be(false);
+ expect(series.bars.show).to.be(true);
+ });
+ });
+
+ describe('series option overrides, linewidth, stack', function() {
+ beforeEach(function() {
+ series.info.alias = 'test';
+ series.applySeriesOverrides([{ alias: 'test', linewidth: 5, stack: false }]);
+ });
+
+ it('should disable stack, and set lineWidth', function() {
+ expect(series.stack).to.be(false);
+ expect(series.lines.lineWidth).to.be(5);
+ });
+ });
+
+ describe('series option overrides, pointradius, steppedLine', function() {
+ beforeEach(function() {
+ series.info.alias = 'test';
+ series.applySeriesOverrides([{ alias: 'test', pointradius: 5, steppedLine: true }]);
+ });
+
+ it('should set pointradius, and set steppedLine', function() {
+ expect(series.points.radius).to.be(5);
+ expect(series.lines.steps).to.be(true);
+ });
+ });
+
+ describe('override match on regex', function() {
+ beforeEach(function() {
+ series.info.alias = 'test_01';
+ series.applySeriesOverrides([{ alias: '/.*01/', lines: false }]);
+ });
+
+ it('should match second series', function() {
+ expect(series.lines.show).to.be(false);
+ });
+ });
+
+ describe('override series y-axis, and z-index', function() {
+ beforeEach(function() {
+ series.info.alias = 'test';
+ series.applySeriesOverrides([{ alias: 'test', yaxis: 2, zindex: 2 }]);
+ });
+
+ it('should set yaxis', function() {
+ expect(series.info.yaxis).to.be(2);
+ });
+
+ it('should set zindex', function() {
+ expect(series.zindex).to.be(2);
+ });
+ });
+
+ });
+
+ });
+
+});
diff --git a/src/test/test-main.js b/src/test/test-main.js
index 9c7cfe73341..4e231c8c1aa 100644
--- a/src/test/test-main.js
+++ b/src/test/test-main.js
@@ -118,10 +118,13 @@ require([
'specs/lexer-specs',
'specs/parser-specs',
'specs/gfunc-specs',
+ 'specs/timeSeries-specs',
'specs/row-ctrl-specs',
'specs/graphiteTargetCtrl-specs',
'specs/influxdb-datasource-specs',
'specs/graph-ctrl-specs',
+ 'specs/grafanaGraph-specs',
+ 'specs/seriesOverridesCtrl-specs',
'specs/filterSrv-specs',
'specs/kbn-format-specs',
'specs/dashboardSrv-specs',