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/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index f0ffd14dd67..c2be3d90d13 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -156,7 +156,7 @@ 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; - data[0].lines = { show: false }; + data[0].lines = { show: false, dashes: true }; data[0].bars = { show: true }; } diff --git a/src/app/panels/graph/module.html b/src/app/panels/graph/module.html index 102cad84a40..0d418e5e8cd 100644 --- a/src/app/panels/graph/module.html +++ b/src/app/panels/graph/module.html @@ -27,7 +27,7 @@ -
+
diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index d8a31f59312..557503023a1 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', @@ -19,6 +6,7 @@ define([ 'kbn', 'moment', './timeSeries', + './seriesOverridesCtrl', 'services/panelSrv', 'services/annotationsSrv', 'services/datasourceSrv', @@ -30,10 +18,9 @@ define([ 'jquery.flot.stackpercent' ], 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) { @@ -363,49 +350,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) { $scope.panel.seriesOverrides.push({}); }; + $scope.removeSeriesOverride = function(override) { + $scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override); + }; + panelSrv.init($scope); }); - angular - .module('grafana.directives') - .directive('seriesOverrideOption', function($compile) { - var template = - ''; - - return { - scope: true, - link: function($scope, elem, attrs) { - var $template = $(template); - elem.append($template); - var $link = $(elem).find('a'); - - $scope.options = $scope.$eval(attrs.options); - $scope.options.unshift(null); - - $scope.options = _.map($scope.options, function(option, index) { - return { - text: option === null ? '' : String(option), - value: option, - click: 'setValue(' + index + ')' - }; - }); - - $scope.setValue = function(index) { - var value = $scope.options[index].value; - if (value === null) { - $link.html(''); - } - else { - $link.html(value); - } - }; - - $compile(elem.contents())($scope); - } - }; - }); - }); diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js new file mode 100644 index 00000000000..2123510400b --- /dev/null +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -0,0 +1,66 @@ +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.removeOverride = function(option) { + delete $scope.override[option.propertyName]; + $scope.updateCurrentOverrides(); + }; + + $scope.updateCurrentOverrides = function() { + $scope.currentOverrides = []; + _.each($scope.overrideMenu, function(option) { + if (!_.isUndefined($scope.override[option.propertyName])) { + $scope.currentOverrides.push({ + name: option.text, + propertyName: option.propertyName, + value: String($scope.override[option.propertyName]) + }); + } + }); + }; + + $scope.addOverrideOption('Bars', 'bars', [true, false]); + $scope.addOverrideOption('Lines', 'lines', [true, false]); + $scope.addOverrideOption('Points', 'points', [true, false]); + $scope.addOverrideOption('Line fill', 'fill', [1,2,3,4,5,6,7,8]); + $scope.updateCurrentOverrides(); + + }); + +}); diff --git a/src/app/panels/graph/styleEditor.html b/src/app/panels/graph/styleEditor.html index c1b68e25beb..c436ba10ceb 100644 --- a/src/app/panels/graph/styleEditor.html +++ b/src/app/panels/graph/styleEditor.html @@ -66,36 +66,43 @@
Series specific overrides
- - - - - - - - - - - - - - - - - - - - -
Series alias/regexBarsLinesPointsFillWidthRadiusStaircaseStackY-axisZ-indexColor
- - - - - - - -
- +
+
+
+ + + +
    +
  • + alias or regex +
  • +
  • + +
  • +
  • + + + + {{option.name}}: {{option.value}} +
  • + +
+
+
+
+
+ +
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/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js new file mode 100644 index 00000000000..f8b5878ce66 --- /dev/null +++ b/src/test/specs/grafanaGraph-specs.js @@ -0,0 +1,68 @@ +define([ + './helpers', + 'angular', + 'jquery', + 'directives/grafanaGraph' +], function(helpers, angular, $) { + '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: [] + }; + scope.dashboard = { timezone: 'browser' }; + scope.range = { + from: new Date('2014-08-09 10:00:00'), + to: new Date('2014-09-09 13:00:00') + }; + + setupFunc(scope); + + $compile(element)(scope); + scope.$digest(); + $.plot = ctx.plotSpy = sinon.spy(); + + scope.$emit('render', []); + 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); + }); + + }); + + }); +}); + 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..04f0dcdca99 --- /dev/null +++ b/src/test/specs/seriesOverridesCtrl-specs.js @@ -0,0 +1,40 @@ +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')); + + 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'); + }); + }); + + }); + +}); + diff --git a/src/test/test-main.js b/src/test/test-main.js index 9c7cfe73341..8d2014b0664 100644 --- a/src/test/test-main.js +++ b/src/test/test-main.js @@ -122,6 +122,8 @@ require([ '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',