From fe6a7c58bf3b68c8c6d0532d76978c53c9afb0fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 2 Oct 2014 21:48:00 +0200 Subject: [PATCH 001/125] Fixed issue casued by recent angular upgrade and stateful filter, casued template vars in graph titles no update properly, angular was not updated in 1.8.1 so this does affect any released version, Closes #892 --- src/app/filters/all.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/filters/all.js b/src/app/filters/all.js index eb9a736d0ce..e75d70043d8 100755 --- a/src/app/filters/all.js +++ b/src/app/filters/all.js @@ -56,9 +56,13 @@ define(['angular', 'jquery', 'lodash', 'moment'], function (angular, $, _, momen }); module.filter('interpolateTemplateVars', function(templateSrv) { - return function(text) { + function interpolateTemplateVars(text) { return templateSrv.replaceWithText(text); - }; + } + + interpolateTemplateVars.$stateful = true; + + return interpolateTemplateVars; }); }); From e63889d5c4a810f7669ee596cfa969aabf0f9b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 3 Oct 2014 07:31:27 +0200 Subject: [PATCH 002/125] Css fix for metrics tab, help boxes did extend page height so could not scroll if they extended below screen, #891 --- src/app/partials/metrics.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/partials/metrics.html b/src/app/partials/metrics.html index 7478890b925..8d30599744f 100644 --- a/src/app/partials/metrics.html +++ b/src/app/partials/metrics.html @@ -14,4 +14,5 @@ +
From 51bcbdac7551d9e33addfa5b20f4d2b0b67d05e0 Mon Sep 17 00:00:00 2001 From: George Negoita Date: Sat, 4 Oct 2014 02:33:36 +0300 Subject: [PATCH 003/125] second parameter of groupByNode() is 0 indexed --- src/app/services/graphite/gfunc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/services/graphite/gfunc.js b/src/app/services/graphite/gfunc.js index 1acfad8739b..5704d198ec7 100644 --- a/src/app/services/graphite/gfunc.js +++ b/src/app/services/graphite/gfunc.js @@ -193,7 +193,7 @@ function (_) { { name: "node", type: "int", - options: [1,2,3,4,5,6,7,8,9,10,12] + options: [0,1,2,3,4,5,6,7,8,9,10,12] }, { name: "function", From 2d3f3965715414a6097a60e04ec0bc9612fa7041 Mon Sep 17 00:00:00 2001 From: mikhailov Date: Sun, 5 Oct 2014 18:30:22 +0100 Subject: [PATCH 004/125] Extract repetitive CSS inline styles Inline styles slow down rendering proportionally to containers number --- src/app/partials/dashboard.html | 2 +- src/css/less/grafana.less | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/partials/dashboard.html b/src/app/partials/dashboard.html index 2fa9bbcf2eb..eb28680161c 100644 --- a/src/app/partials/dashboard.html +++ b/src/app/partials/dashboard.html @@ -14,7 +14,7 @@
-
+
diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index 6c2c5e4ea61..b020c85c734 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -6,6 +6,12 @@ @import "search.less"; @import "panel.less"; +.row-control-inner { + padding:0px; + margin:0px; + position:relative; +} + .hide-controls { padding: 0; .row-control-inner { From 6abad666dbe5175b7b784edf0691c982e21b6e8d Mon Sep 17 00:00:00 2001 From: Anatoly Mikhailov Date: Sun, 5 Oct 2014 20:54:53 +0100 Subject: [PATCH 005/125] Debounce/Throttling Searching event New backend call to each keydown may hit the back-end performance. 500ms is reasonable delay to avoid too many requests while user is typing a search query --- src/app/partials/search.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/partials/search.html b/src/app/partials/search.html index ccddfb14474..2d220b206fd 100644 --- a/src/app/partials/search.html +++ b/src/app/partials/search.html @@ -22,7 +22,7 @@ + ng-keydown="keyDown($event)" ng-model="query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="search()" /> tags
From 67fde17209413e44da5f69884f2177deaf00810c Mon Sep 17 00:00:00 2001 From: Anatoly Mikhailov Date: Sun, 5 Oct 2014 23:24:41 +0100 Subject: [PATCH 006/125] Speed searching up a bit Reuse ElasticSearch hits length via hits.total and cache it rahter than calculation length every iteration. --- .../services/elasticsearch/es-datasource.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/app/services/elasticsearch/es-datasource.js b/src/app/services/elasticsearch/es-datasource.js index f9ec01dbd4c..94a623002b5 100644 --- a/src/app/services/elasticsearch/es-datasource.js +++ b/src/app/services/elasticsearch/es-datasource.js @@ -224,7 +224,7 @@ function (angular, _, config, kbn, moment) { var endsInOpen = function(string, opener, closer) { var character; var count = 0; - for (var i=0; i Date: Mon, 6 Oct 2014 06:22:55 +0200 Subject: [PATCH 007/125] correct positioning for multiple highlighted stacked and staircase points, fix to #901 --- src/app/directives/grafanaGraph.tooltip.js | 44 +++++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/app/directives/grafanaGraph.tooltip.js b/src/app/directives/grafanaGraph.tooltip.js index 9c98643a20e..49f02e86980 100644 --- a/src/app/directives/grafanaGraph.tooltip.js +++ b/src/app/directives/grafanaGraph.tooltip.js @@ -20,7 +20,18 @@ function ($, kbn) { } }); - function findHoverIndex(posX, series) { + function findHoverIndexFromDataPoints(posX, series,last) { + var ps=series.datapoints.pointsize; + var initial=last*ps; + for (var j = initial; j < series.datapoints.points.length; j+=ps) { + if (series.datapoints.points[j] > posX) { + return Math.max(j - ps, 0)/ps; + } + } + return j/ps - 1; + } + + function findHoverIndexFromData(posX, series) { for (var j = 0; j < series.data.length; j++) { if (series.data[j][0] > posX) { return Math.max(j - 1, 0); @@ -62,20 +73,23 @@ function ($, kbn) { seriesHtml = ''; series = data[0]; - hoverIndex = findHoverIndex(pos.x, series); + hoverIndex = findHoverIndexFromData(pos.x, series); + var lasthoverIndex=0; + if(!scope.panel.steppedLine) { + lasthoverIndex=hoverIndex; + } //now we know the current X (j) position for X and Y values timestamp = dashboard.formatDate(series.data[hoverIndex][0]); var last_value = 0; //needed for stacked values - for (i = data.length-1; i >= 0; --i) { - //stacked values should be added in reverse order + for (i = 0; i < data.length; i++) { series = data[i]; seriesInfo = series.info; format = scope.panel.y_formats[seriesInfo.yaxis - 1]; if (scope.panel.stack) { - if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') { + if (scope.panel.tooltip.value_type === 'individual') { value = series.data[hoverIndex][1]; } else { last_value += series.data[hoverIndex][1]; @@ -96,7 +110,25 @@ function ($, kbn) { //pre-pending new values seriesHtml = group + ': ' + value + '
' + seriesHtml; - plot.highlight(i, hoverIndex); + //Highlighting multiple Points depending on the plot type + if (scope.panel.steppedLine || (scope.panel.stack && scope.panel.nullPointMode == "null")) { + //stacked and steppedLine plots can have series with different length. + //Stacked series can increase its length on each new stacked serie if null points found, + //to speed the index search we begin always on the las found hoverIndex. + var newhoverIndex=findHoverIndexFromDataPoints(pos.x, series,lasthoverIndex); + //update lasthoverIndex depends also on the plot type. + if(!scope.panel.steppedLine) { + //on stacked graphs new will be always greater than last + lasthoverIndex=newhoverIndex; + } else { + //if steppeLine, not always series increases its length, so we should begin + //to search correct index from the original hoverIndex on each serie. + lasthoverIndex=hoverIndex; + } + plot.highlight(i, newhoverIndex); + } else { + plot.highlight(i, hoverIndex); + } } showTooltip(timestamp, seriesHtml, pos); From 231a599f09ac6ac953e738f9729e5b96a5cd72bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 6 Oct 2014 09:27:18 -0400 Subject: [PATCH 008/125] legend html markup cleanup --- src/app/panels/graph/legend.html | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/app/panels/graph/legend.html b/src/app/panels/graph/legend.html index 31458060803..42fbda69de8 100755 --- a/src/app/panels/graph/legend.html +++ b/src/app/panels/graph/legend.html @@ -1,28 +1,20 @@
-
+
- - +
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
From 1b0cddfa7205759c5fac72093cdafd40d9c964e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 6 Oct 2014 10:20:26 -0400 Subject: [PATCH 009/125] Graph: Tooltip refactoring for testability --- src/app/components/kbn.js | 16 ++ src/app/components/timeSeries.js | 18 +- src/app/directives/grafanaGraph.js | 5 +- src/app/directives/grafanaGraph.tooltip.js | 226 +++++++++++---------- src/app/panels/graph/module.js | 2 +- src/css/less/graph.less | 2 + src/test/specs/graph-tooltip-specs.js | 118 +++++++---- 7 files changed, 232 insertions(+), 155 deletions(-) diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index cb084b1c166..567e7c78f23 100644 --- a/src/app/components/kbn.js +++ b/src/app/components/kbn.js @@ -316,6 +316,10 @@ function($, _, moment) { kbn.formatFuncCreator = function(factor, extArray) { return function(size, decimals, scaledDecimals) { + if (size === null) { + return ""; + } + var steps = 0; while (Math.abs(size) >= factor) { @@ -331,6 +335,10 @@ function($, _, moment) { }; kbn.toFixed = function(value, decimals) { + if (value === null) { + return ""; + } + var factor = decimals ? Math.pow(10, decimals) : 1; var formatted = String(Math.round(value * factor) / factor); @@ -359,6 +367,8 @@ function($, _, moment) { kbn.valueFormats.none = kbn.toFixed; kbn.valueFormats.ms = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 1000) { return kbn.toFixed(size, decimals) + " ms"; } @@ -383,6 +393,8 @@ function($, _, moment) { }; kbn.valueFormats.s = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 600) { return kbn.toFixed(size, decimals) + " s"; } @@ -407,6 +419,8 @@ function($, _, moment) { }; kbn.valueFormats['µs'] = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 1000) { return kbn.toFixed(size, decimals) + " µs"; } @@ -419,6 +433,8 @@ function($, _, moment) { }; kbn.valueFormats.ns = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 1000) { return kbn.toFixed(size, decimals) + " ns"; } diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index a448a93649c..b05e9333364 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -5,10 +5,15 @@ define([ function (_, kbn) { 'use strict'; + function defaultValueFormater(value) { + return kbn.valueFormats.none(value, 2, 2); + } + function TimeSeries(opts) { this.datapoints = opts.datapoints; this.info = opts.info; this.label = opts.info.alias; + this.valueFormater = defaultValueFormater; } function matchSeriesOverride(aliasOrRegex, seriesAlias) { @@ -108,11 +113,14 @@ function (_, kbn) { }; TimeSeries.prototype.updateLegendValues = function(formater, decimals, scaledDecimals) { - this.info.avg = this.info.avg != null ? formater(this.info.avg, decimals, scaledDecimals) : null; - this.info.current = this.info.current != null ? formater(this.info.current, decimals, scaledDecimals) : null; - this.info.min = this.info.min != null ? formater(this.info.min, decimals, scaledDecimals) : null; - this.info.max = this.info.max != null ? formater(this.info.max, decimals, scaledDecimals) : null; - this.info.total = this.info.total != null ? formater(this.info.total, decimals, scaledDecimals) : null; + this.valueFormater = function(value) { + return formater(value, decimals, scaledDecimals); + }; + this.info.avg = this.valueFormater(this.info.avg); + this.info.current = this.valueFormater(this.info.current); + this.info.min = this.valueFormater(this.info.min); + this.info.max = this.valueFormater(this.info.max); + this.info.total = this.valueFormater(this.info.total); }; return TimeSeries; diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 685ac7e0b83..a267255a540 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -6,7 +6,7 @@ define([ 'lodash', './grafanaGraph.tooltip' ], -function (angular, $, kbn, moment, _, graphTooltip) { +function (angular, $, kbn, moment, _, GraphTooltip) { 'use strict'; var module = angular.module('grafana.directives'); @@ -105,6 +105,7 @@ function (angular, $, kbn, moment, _, graphTooltip) { function updateLegendValues(plot) { var yaxis = plot.getYAxes(); + console.log("value"); for (var i = 0; i < data.length; i++) { var series = data[i]; @@ -416,7 +417,7 @@ function (angular, $, kbn, moment, _, graphTooltip) { elem.html(''); } - graphTooltip.register(elem, dashboard, scope, $rootScope); + new GraphTooltip(elem, dashboard, scope); elem.bind("plotselected", function (event, ranges) { scope.$apply(function() { diff --git a/src/app/directives/grafanaGraph.tooltip.js b/src/app/directives/grafanaGraph.tooltip.js index 49f02e86980..6d10fbb89c0 100644 --- a/src/app/directives/grafanaGraph.tooltip.js +++ b/src/app/directives/grafanaGraph.tooltip.js @@ -1,14 +1,105 @@ define([ 'jquery', - 'kbn', ], -function ($, kbn) { +function ($) { 'use strict'; - function registerTooltipFeatures(elem, dashboard, scope) { + function GraphTooltip(elem, dashboard, scope) { + var self = this; var $tooltip = $('
'); + this.findHoverIndexFromDataPoints = function(posX, series,last) { + var ps = series.datapoints.pointsize; + var initial = last*ps; + var len = series.datapoints.points.length; + for (var j = initial; j < len; j += ps) { + if (series.datapoints.points[j] > posX) { + return Math.max(j - ps, 0)/ps; + } + } + return j/ps - 1; + }; + + this.findHoverIndexFromData = function(posX, series) { + var len = series.data.length; + for (var j = 0; j < len; j++) { + if (series.data[j][0] > posX) { + return Math.max(j - 1, 0); + } + } + return j - 1; + }; + + this.showTooltip = function(title, innerHtml, pos) { + var body = '
'+ title + '
' ; + body += innerHtml + '
'; + $tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY); + }; + + this.getMultiSeriesPlotHoverInfo = function(seriesList, pos) { + var value, seriesInfo, i, series, hoverIndex; + var results = []; + + var pointCount = seriesList[0].data.length; + for (i = 1; i < seriesList.length; i++) { + if (seriesList[i].data.length !== pointCount) { + results.pointCountMismatch = true; + return results; + } + } + + series = seriesList[0]; + hoverIndex = this.findHoverIndexFromData(pos.x, series); + var lasthoverIndex = 0; + if(!scope.panel.steppedLine) { + lasthoverIndex = hoverIndex; + } + + //now we know the current X (j) position for X and Y values + results.time = series.data[hoverIndex][0]; + var last_value = 0; //needed for stacked values + + for (i = 0; i < seriesList.length; i++) { + series = seriesList[i]; + seriesInfo = series.info; + + if (scope.panel.stack) { + if (scope.panel.tooltip.value_type === 'individual') { + value = series.data[hoverIndex][1]; + } else { + last_value += series.data[hoverIndex][1]; + value = last_value; + } + } else { + value = series.data[hoverIndex][1]; + } + + // Highlighting multiple Points depending on the plot type + if (scope.panel.steppedLine || (scope.panel.stack && scope.panel.nullPointMode == "null")) { + // stacked and steppedLine plots can have series with different length. + // Stacked series can increase its length on each new stacked serie if null points found, + // to speed the index search we begin always on the las found hoverIndex. + var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series,lasthoverIndex); + // update lasthoverIndex depends also on the plot type. + if(!scope.panel.steppedLine) { + // on stacked graphs new will be always greater than last + lasthoverIndex = newhoverIndex; + } else { + // if steppeLine, not always series increases its length, so we should begin + // to search correct index from the original hoverIndex on each serie. + lasthoverIndex = hoverIndex; + } + + results.push({ value: value, hoverIndex: newhoverIndex }); + } else { + results.push({ value: value, hoverIndex: hoverIndex }); + } + } + + return results; + }; + elem.mouseleave(function () { if (scope.panel.tooltip.shared || dashboard.sharedCrosshair) { var plot = elem.data().plot; @@ -20,36 +111,10 @@ function ($, kbn) { } }); - function findHoverIndexFromDataPoints(posX, series,last) { - var ps=series.datapoints.pointsize; - var initial=last*ps; - for (var j = initial; j < series.datapoints.points.length; j+=ps) { - if (series.datapoints.points[j] > posX) { - return Math.max(j - ps, 0)/ps; - } - } - return j/ps - 1; - } - - function findHoverIndexFromData(posX, series) { - for (var j = 0; j < series.data.length; j++) { - if (series.data[j][0] > posX) { - return Math.max(j - 1, 0); - } - } - return j - 1; - } - - function showTooltip(title, innerHtml, pos) { - var body = '
'+ title + '
' ; - body += innerHtml + '
'; - $tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY); - } - elem.bind("plothover", function (event, pos, item) { var plot = elem.data().plot; var data = plot.getData(); - var group, value, timestamp, seriesInfo, format, i, series, hoverIndex, seriesHtml; + var group, value, timestamp, hoverInfo, i, series, seriesHtml; if(dashboard.sharedCrosshair){ scope.appEvent('setCrosshair', { pos: pos, scope: scope }); @@ -58,86 +123,34 @@ function ($, kbn) { if (scope.panel.tooltip.shared) { plot.unhighlight(); - //check if all series has same length if so, only one x index will - //be checked and only for exact timestamp values - var pointCount = data[0].data.length; - for (i = 1; i < data.length; i++) { - if (data[i].data.length !== pointCount) { - showTooltip('Shared tooltip error', '
    ' + - '
  • Series point counts are not the same
  • ' + - '
  • Set null point mode to null or null as zero
  • ' + - '
  • For influxdb users set fill(0) in your query
', pos); - return; - } + var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(data, pos); + if (seriesHoverInfo.pointCountMismatch) { + self.showTooltip('Shared tooltip error', '
    ' + + '
  • Series point counts are not the same
  • ' + + '
  • Set null point mode to null or null as zero
  • ' + + '
  • For influxdb users set fill(0) in your query
', pos); + return; } seriesHtml = ''; - series = data[0]; - hoverIndex = findHoverIndexFromData(pos.x, series); - var lasthoverIndex=0; - if(!scope.panel.steppedLine) { - lasthoverIndex=hoverIndex; - } + timestamp = dashboard.formatDate(seriesHoverInfo.time); - //now we know the current X (j) position for X and Y values - timestamp = dashboard.formatDate(series.data[hoverIndex][0]); - var last_value = 0; //needed for stacked values - - for (i = 0; i < data.length; i++) { + for (i = 0; i < seriesHoverInfo.length; i++) { series = data[i]; - seriesInfo = series.info; - format = scope.panel.y_formats[seriesInfo.yaxis - 1]; + hoverInfo = seriesHoverInfo[i]; + value = series.valueFormater(hoverInfo.value); - if (scope.panel.stack) { - if (scope.panel.tooltip.value_type === 'individual') { - value = series.data[hoverIndex][1]; - } else { - last_value += series.data[hoverIndex][1]; - value = last_value; - } - } else { - value = series.data[hoverIndex][1]; - } - - value = kbn.valueFormats[format](value, series.yaxis.tickDecimals); - - if (seriesInfo.alias) { - group = ' ' + seriesInfo.alias; - } else { - group = kbn.query_color_dot(series.color, 15) + ' '; - } - - //pre-pending new values + group = ' ' + series.label; seriesHtml = group + ': ' + value + '
' + seriesHtml; - - //Highlighting multiple Points depending on the plot type - if (scope.panel.steppedLine || (scope.panel.stack && scope.panel.nullPointMode == "null")) { - //stacked and steppedLine plots can have series with different length. - //Stacked series can increase its length on each new stacked serie if null points found, - //to speed the index search we begin always on the las found hoverIndex. - var newhoverIndex=findHoverIndexFromDataPoints(pos.x, series,lasthoverIndex); - //update lasthoverIndex depends also on the plot type. - if(!scope.panel.steppedLine) { - //on stacked graphs new will be always greater than last - lasthoverIndex=newhoverIndex; - } else { - //if steppeLine, not always series increases its length, so we should begin - //to search correct index from the original hoverIndex on each serie. - lasthoverIndex=hoverIndex; - } - plot.highlight(i, newhoverIndex); - } else { - plot.highlight(i, hoverIndex); - } + plot.highlight(i, hoverInfo.hoverIndex); } - showTooltip(timestamp, seriesHtml, pos); - return; + self.showTooltip(timestamp, seriesHtml, pos); } - if (item) { - seriesInfo = item.series.info; - format = scope.panel.y_formats[seriesInfo.yaxis - 1]; - group = ' ' + seriesInfo.alias; + // single series tooltip + else if (item) { + series = item.series; + group = ' ' + series.label; if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') { value = item.datapoint[1] - item.datapoint[2]; @@ -146,19 +159,18 @@ function ($, kbn) { value = item.datapoint[1]; } - value = kbn.valueFormats[format](value, item.series.yaxis.tickDecimals); + value = series.valueFormater(value); timestamp = dashboard.formatDate(item.datapoint[0]); group += ': ' + value + ''; - showTooltip(timestamp, group, pos); - } else { + self.showTooltip(timestamp, group, pos); + } + // no hit + else { $tooltip.detach(); } }); - } - return { - register: registerTooltipFeatures - }; + return GraphTooltip; }); diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index bc15b851f9d..6f5a174056f 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -244,7 +244,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { var seriesInfo = { alias: alias, - color: color, + color: color, }; $scope.legend.push(seriesInfo); diff --git a/src/css/less/graph.less b/src/css/less/graph.less index 01a697ddb2e..c0c3f371325 100644 --- a/src/css/less/graph.less +++ b/src/css/less/graph.less @@ -169,6 +169,8 @@ } .graph-tooltip { + white-space: nowrap; + .graph-tooltip-time { text-align: center; font-weight: bold; diff --git a/src/test/specs/graph-tooltip-specs.js b/src/test/specs/graph-tooltip-specs.js index 7f20ad8dbc9..2277426bc81 100644 --- a/src/test/specs/graph-tooltip-specs.js +++ b/src/test/specs/graph-tooltip-specs.js @@ -1,55 +1,93 @@ define([ 'jquery', 'directives/grafanaGraph.tooltip' -], function($, tooltip) { +], function($, GraphTooltip) { 'use strict'; - describe('graph tooltip', function() { - var elem = $('
'); - var dashboard = { - formatDate: sinon.stub().returns('date'), - }; - var scope = { - appEvent: sinon.spy(), - onAppEvent: sinon.spy(), - panel: { - tooltip: { - shared: true - }, - y_formats: ['ms', 'none'], - stack: true - } - }; + var scope = { + appEvent: sinon.spy(), + onAppEvent: sinon.spy(), + }; - var data = [ - { - data: [[10,10], [12,20]], - info: { yaxis: 1 }, - yaxis: { tickDecimals: 2 }, + var elem = $('
'); + var dashboard = { }; + + function describeSharedTooltip(desc, fn) { + var ctx = {}; + ctx.scope = scope; + ctx.scope.panel = { + tooltip: { + shared: true }, - { - data: [[10,10], [12,20]], - info: { yaxis: 1 }, - yaxis: { tickDecimals: 2 }, - } - ]; - - var plot = { - getData: sinon.stub().returns(data), - highlight: sinon.stub(), - unhighlight: sinon.stub() + stack: false }; - elem.data('plot', plot); + ctx.setup = function(setupFn) { + ctx.setupFn = setupFn; + }; - beforeEach(function() { - tooltip.register(elem, dashboard, scope); - elem.trigger('plothover', [{}, {x: 13}, {}]); + describe(desc, function() { + beforeEach(function() { + ctx.setupFn(); + var tooltip = new GraphTooltip(elem, dashboard, scope); + ctx.results = tooltip.getMultiSeriesPlotHoverInfo(ctx.data, ctx.pos); + }); + + fn(ctx); + }); + } + + describeSharedTooltip("steppedLine false, stack false", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { data: [[10, 15], [12, 20]], }, + { data: [[10, 2], [12, 3]], } + ]; + ctx.pos = { x: 11 }; }); - it('should add tooltip', function() { - var tooltipHtml = $(".graph-tooltip").text(); - expect(tooltipHtml).to.be('date : 40.00 ms : 20.00 ms'); + it('should return 2 series', function() { + expect(ctx.results.length).to.be(2); + }); + it('should add time to results array', function() { + expect(ctx.results.time).to.be(10); + }); + it('should set value and hoverIndex', function() { + expect(ctx.results[0].value).to.be(15); + expect(ctx.results[1].value).to.be(2); + expect(ctx.results[0].hoverIndex).to.be(0); + }); + }); + + describeSharedTooltip("steppedLine false, stack true, individual false", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { data: [[10, 15], [12, 20]], }, + { data: [[10, 2], [12, 3]], } + ]; + ctx.scope.panel.stack = true; + ctx.pos = { x: 11 }; + }); + + it('should show stacked value', function() { + expect(ctx.results[1].value).to.be(17); + }); + + }); + + describeSharedTooltip("steppedLine false, stack true, individual true", function(ctx) { + ctx.setup(function() { + ctx.data = [ + { data: [[10, 15], [12, 20]], }, + { data: [[10, 2], [12, 3]], } + ]; + ctx.scope.panel.stack = true; + ctx.scope.panel.tooltip.value_type = 'individual'; + ctx.pos = { x: 11 }; + }); + + it('should not show stacked value', function() { + expect(ctx.results[1].value).to.be(2); }); }); From 4285c751b3a437c457793e70ee72eb1052d341ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 6 Oct 2014 12:17:48 -0400 Subject: [PATCH 010/125] Graph: fixed value formating for tooltip, need original data. flotcharts copys the data, so changes to value formats func after plot call does not affect the plot.getData() series --- src/app/components/timeSeries.js | 27 +++++++++++----------- src/app/directives/grafanaGraph.js | 6 ++--- src/app/directives/grafanaGraph.tooltip.js | 16 ++++++------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index b05e9333364..2d1f918c5bf 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -5,15 +5,11 @@ define([ function (_, kbn) { 'use strict'; - function defaultValueFormater(value) { - return kbn.valueFormats.none(value, 2, 2); - } - function TimeSeries(opts) { this.datapoints = opts.datapoints; this.info = opts.info; this.label = opts.info.alias; - this.valueFormater = defaultValueFormater; + this.valueFormater = kbn.valueFormats.none; } function matchSeriesOverride(aliasOrRegex, seriesAlias) { @@ -113,14 +109,19 @@ function (_, kbn) { }; TimeSeries.prototype.updateLegendValues = function(formater, decimals, scaledDecimals) { - this.valueFormater = function(value) { - return formater(value, decimals, scaledDecimals); - }; - this.info.avg = this.valueFormater(this.info.avg); - this.info.current = this.valueFormater(this.info.current); - this.info.min = this.valueFormater(this.info.min); - this.info.max = this.valueFormater(this.info.max); - this.info.total = this.valueFormater(this.info.total); + this.valueFormater = formater; + this.decimals = decimals; + this.scaledDecimals = scaledDecimals; + + this.info.avg = this.formatValue(this.info.avg); + this.info.current = this.formatValue(this.info.current); + this.info.min = this.formatValue(this.info.min); + this.info.max = this.formatValue(this.info.max); + this.info.total = this.formatValue(this.info.total); + }; + + TimeSeries.prototype.formatValue = function(value) { + return this.valueFormater(value, this.decimals, this.scaledDecimals); }; return TimeSeries; diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index a267255a540..97d5bb6aebd 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -105,7 +105,6 @@ function (angular, $, kbn, moment, _, GraphTooltip) { function updateLegendValues(plot) { var yaxis = plot.getYAxes(); - console.log("value"); for (var i = 0; i < data.length; i++) { var series = data[i]; @@ -113,7 +112,6 @@ function (angular, $, kbn, moment, _, GraphTooltip) { var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]]; series.updateLegendValues(formater, axis.tickDecimals, axis.scaledDecimals); } - } // Function for rendering panel @@ -417,7 +415,9 @@ function (angular, $, kbn, moment, _, GraphTooltip) { elem.html(''); } - new GraphTooltip(elem, dashboard, scope); + new GraphTooltip(elem, dashboard, scope, function() { + return data; + }); elem.bind("plotselected", function (event, ranges) { scope.$apply(function() { diff --git a/src/app/directives/grafanaGraph.tooltip.js b/src/app/directives/grafanaGraph.tooltip.js index 6d10fbb89c0..3e263a392de 100644 --- a/src/app/directives/grafanaGraph.tooltip.js +++ b/src/app/directives/grafanaGraph.tooltip.js @@ -4,7 +4,7 @@ define([ function ($) { 'use strict'; - function GraphTooltip(elem, dashboard, scope) { + function GraphTooltip(elem, dashboard, scope, getSeriesFn) { var self = this; var $tooltip = $('
'); @@ -38,7 +38,7 @@ function ($) { }; this.getMultiSeriesPlotHoverInfo = function(seriesList, pos) { - var value, seriesInfo, i, series, hoverIndex; + var value, i, series, hoverIndex; var results = []; var pointCount = seriesList[0].data.length; @@ -62,7 +62,6 @@ function ($) { for (i = 0; i < seriesList.length; i++) { series = seriesList[i]; - seriesInfo = series.info; if (scope.panel.stack) { if (scope.panel.tooltip.value_type === 'individual') { @@ -113,7 +112,8 @@ function ($) { elem.bind("plothover", function (event, pos, item) { var plot = elem.data().plot; - var data = plot.getData(); + var plotData = plot.getData(); + var seriesList = getSeriesFn(); var group, value, timestamp, hoverInfo, i, series, seriesHtml; if(dashboard.sharedCrosshair){ @@ -123,7 +123,7 @@ function ($) { if (scope.panel.tooltip.shared) { plot.unhighlight(); - var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(data, pos); + var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos); if (seriesHoverInfo.pointCountMismatch) { self.showTooltip('Shared tooltip error', '
diff --git a/src/vendor/jquery/jquery.flot.fillbelow.js b/src/vendor/jquery/jquery.flot.fillbelow.js index 0153dd85e17..ede2cb5e2d0 100644 --- a/src/vendor/jquery/jquery.flot.fillbelow.js +++ b/src/vendor/jquery/jquery.flot.fillbelow.js @@ -12,7 +12,6 @@ var i; - debugger; for ( i = 0; i < allseries.length; ++i ) { if ( allseries[ i ].id === series.fillBelowTo ) { return allseries[ i ]; From f743288ce06494dcc7d0526f4f4806b1697cc651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 15 Oct 2014 15:47:59 -0400 Subject: [PATCH 035/125] Graph: fix for legends on the side and graph dimensions adaptation --- src/app/components/settings.js | 1 + src/app/directives/grafanaGraph.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 01f40c2e761..38313aeb198 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -17,6 +17,7 @@ function (_, crypto) { window_title_prefix : 'Grafana - ', panels : { 'graph': { path: 'panels/graph' }, + 'stats': { path: 'panels/stats' }, 'text': { path: 'panels/text' } }, plugins : {}, diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index bf7264cc16f..374d5e34eab 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -207,6 +207,8 @@ function (angular, $, kbn, moment, _, GraphTooltip) { } if (shouldDelayDraw(panel)) { + // temp fix for legends on the side, need to render twice to get dimensions right + callPlot(); setTimeout(callPlot, 50); legendSideLastValue = panel.legend.rightSide; } From dd0193a9a813e4ddf3b2e80dad7843e9ef18af08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 15 Oct 2014 15:48:57 -0400 Subject: [PATCH 036/125] Dashboard: removed wip stats panel from settings.js, accidental commit --- src/app/components/settings.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 38313aeb198..01f40c2e761 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -17,7 +17,6 @@ function (_, crypto) { window_title_prefix : 'Grafana - ', panels : { 'graph': { path: 'panels/graph' }, - 'stats': { path: 'panels/stats' }, 'text': { path: 'panels/text' } }, plugins : {}, From 6cd1bc32fede21ef2f835365a4df2c8c53f956ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 15 Oct 2014 17:29:47 -0400 Subject: [PATCH 037/125] StatsPanel: more work on stats panel --- src/app/panels/stats/module.html | 53 +++++++-------- src/app/panels/stats/module.js | 96 +++++++++++++++++++++++---- src/app/panels/stats/statsEditor.html | 27 ++++++++ src/css/less/panel.less | 1 - 4 files changed, 137 insertions(+), 40 deletions(-) create mode 100644 src/app/panels/stats/statsEditor.html diff --git a/src/app/panels/stats/module.html b/src/app/panels/stats/module.html index 8f57380c7c0..64e61cacc69 100644 --- a/src/app/panels/stats/module.html +++ b/src/app/panels/stats/module.html @@ -1,31 +1,32 @@
+
-
- {{mainstat.value}} - ({{mainstat.func}}) -
- - - - - - - - - - - - - - - - - - -
avgminmaxcurrenttotal
- - {{series.info.alias}} - {{series.info.avg}}{{series.info.min}}{{series.info.max}}{{series.info.current}}{{series.info.total}}
+ + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index 08a078f27b5..471d6db4f41 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -25,6 +25,10 @@ function (angular, app, _, TimeSeries, kbn) { { title: 'Metrics', src:'app/partials/metrics.html' + }, + { + title: 'Display Styles', + src:'app/panels/stats/statsEditor.html' } ], fullscreenEdit: true, @@ -32,28 +36,35 @@ function (angular, app, _, TimeSeries, kbn) { // Set and populate defaults var _d = { - targets: [{}] + targets: [{}], + cacheTimeout: null, }; _.defaults($scope.panel, _d); $scope.init = function() { panelSrv.init($scope); + $scope.$on('refresh', $scope.get_data); }; $scope.formatValue = function(value) { return kbn.valueFormats.bytes(value, 0, -7); }; - $scope.get_data = function() { - console.log("stats get data"); + $scope.updateTimeRange = function () { + $scope.range = timeSrv.timeRange(); $scope.rangeUnparsed = timeSrv.timeRange(false); + }; + + $scope.get_data = function() { + $scope.updateTimeRange(); var metricsQuery = { range: $scope.rangeUnparsed, interval: '1min', targets: $scope.panel.targets, maxDataPoints: 100, + cacheTimeout: $scope.panel.cacheTimeout }; return $scope.datasource.query(metricsQuery) @@ -63,20 +74,26 @@ function (angular, app, _, TimeSeries, kbn) { $scope.panelMeta.loading = false; $scope.panelMeta.error = err.message || "Timeseries data request error"; $scope.inspector.error = err; - $scope.render([]); + $scope.render(); }); }; $scope.dataHandler = function(results) { $scope.panelMeta.loading = false; - $scope.series = _.map(results.data, $scope.seriesHandler); + var data= {}; + data.series = _.map(results.data, $scope.seriesHandler); + data.stats = []; - if ($scope.series.length > 0) { - var mainstat = $scope.series[0]; - $scope.mainstat = {}; - $scope.mainstat.value = $scope.formatValue(mainstat.stats.avg); - $scope.mainstat.func = 'avg'; + if (data.series.length > 0) { + var stat = {}; + var firstSeries = data.series[0]; + stat.value = $scope.formatValue(firstSeries.stats.avg); + stat.func = 'avg'; + data.stats.push(stat); } + + $scope.data = data; + $scope.render(); }; $scope.seriesHandler = function(seriesData, index) { @@ -102,11 +119,64 @@ function (angular, app, _, TimeSeries, kbn) { }; $scope.render = function() { - }; - - $scope.openEditor = function() { + $scope.$emit('render'); }; $scope.init(); }); + + module.directive('statsPanel', function() { + + return { + link: function(scope, elem) { + var data; + + console.log('asd'); + scope.$on('render', function() { + data = scope.data; + + if (!data || data.series.length === 0) { + elem.html('no data'); + return; + } + + render(); + }); + + function render() { + var body = ''; + var i, series; + + if (scope.panel.stats) { + body += '
'; + body += '' + data.stats[0].value + ''; + body += ' (' + data.stats[0].func + ')'; + body += '
'; + } + + if (scope.panel.table) { + body += ''; + body += ''; + body += ''; + body += ''; + for (i = 0; i < data.series.length; i++) { + series = data.series[i]; + body += ''; + body += ''; + body += ''; + body += ''; + body += ''; + body += ''; + body += ''; + } + body += '
avgminmaxcurrenttotal
'; + body += series.info.alias + ' ' + series.info.avg + '' + series.info.min + '' + series.info.max + '' + series.info.total + '' + series.info.current + '
'; + } + + elem.html(body); + } + } + }; + }); + }); diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/stats/statsEditor.html new file mode 100644 index 00000000000..cb473e03122 --- /dev/null +++ b/src/app/panels/stats/statsEditor.html @@ -0,0 +1,27 @@ +
+
+
Main options
+ + +
+
+ +
+
+
Series options
+
+
+
    +
  • + {{series.info.alias}} +
  • + +
  • +
  • +
+
+
+
+
+
+ diff --git a/src/css/less/panel.less b/src/css/less/panel.less index 00b7744ece6..a2891fb6291 100644 --- a/src/css/less/panel.less +++ b/src/css/less/panel.less @@ -40,7 +40,6 @@ text-align: center; } - .panel-error { color: @white; position: absolute; From dd03a4b011e391dc846df4adce1b547710058920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 15 Oct 2014 17:37:08 -0400 Subject: [PATCH 038/125] Dashboard: fix for fullscreen mode and small gap sometimes showing the underlying dashboard --- src/css/less/grafana.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index edc5605ee6b..ab9e9ee99b9 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -135,6 +135,7 @@ .main-view-container { height: 0; overflow: hidden; + padding: 0; } } From 5c80f03eae1ba47abd91499913370a6c4852e165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 15 Oct 2014 18:16:04 -0400 Subject: [PATCH 039/125] StatsPanel: small progress on stats panel --- src/app/panels/stats/module.js | 40 +++++++++++++++++---------- src/app/panels/stats/statsEditor.html | 21 +++++++++++++- src/css/less/stats-panel.less | 4 +++ 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index 471d6db4f41..4847c27e102 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -38,6 +38,10 @@ function (angular, app, _, TimeSeries, kbn) { var _d = { targets: [{}], cacheTimeout: null, + format: 'none', + avg: true, + stats: true, + table: true, }; _.defaults($scope.panel, _d); @@ -80,19 +84,7 @@ function (angular, app, _, TimeSeries, kbn) { $scope.dataHandler = function(results) { $scope.panelMeta.loading = false; - var data= {}; - data.series = _.map(results.data, $scope.seriesHandler); - data.stats = []; - - if (data.series.length > 0) { - var stat = {}; - var firstSeries = data.series[0]; - stat.value = $scope.formatValue(firstSeries.stats.avg); - stat.func = 'avg'; - data.stats.push(stat); - } - - $scope.data = data; + $scope.series = _.map(results.data, $scope.seriesHandler); $scope.render(); }; @@ -119,6 +111,22 @@ function (angular, app, _, TimeSeries, kbn) { }; $scope.render = function() { + var data = { + series: $scope.series, + stats: [] + }; + + var main = data.series[0]; + + if ($scope.panel.avg) { + data.stats.push({ value: $scope.formatValue(main.stats.avg), func: 'avg' }); + } + + if ($scope.panel.total) { + data.stats.push({ value: $scope.formatValue(main.stats.total), func: 'total' }); + } + + $scope.data = data; $scope.$emit('render'); }; @@ -149,8 +157,10 @@ function (angular, app, _, TimeSeries, kbn) { if (scope.panel.stats) { body += '
'; - body += '' + data.stats[0].value + ''; - body += ' (' + data.stats[0].func + ')'; + for (i = 0; i < scope.data.stats.length; i++) { + body += '' + data.stats[i].value + ''; + body += ' (' + data.stats[i].func + ')'; + } body += '
'; } diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/stats/statsEditor.html index cb473e03122..297738ef218 100644 --- a/src/app/panels/stats/statsEditor.html +++ b/src/app/panels/stats/statsEditor.html @@ -2,8 +2,23 @@
Main options
- +
+
+
Big values
+ + + + + +
+
+
Formats
+
+ + +
+
@@ -12,6 +27,10 @@
    +
  • + +
  • +
  • {{series.info.alias}}
  • diff --git a/src/css/less/stats-panel.less b/src/css/less/stats-panel.less index 2a9276d42fe..ad1ad017628 100644 --- a/src/css/less/stats-panel.less +++ b/src/css/less/stats-panel.less @@ -8,6 +8,10 @@ font-weight: bold; } +.stats-panel-value:not(:first-child) { + padding-left: 20px; +} + .stats-panel-func { font-size: 1.5em; } From 0a97a2435b1c915989a1c2ed76f6578f12d15fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 16 Oct 2014 11:16:20 -0400 Subject: [PATCH 040/125] StatsPanel: more progress --- src/app/panels/stats/module.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index 4847c27e102..572ef040572 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -52,7 +52,7 @@ function (angular, app, _, TimeSeries, kbn) { }; $scope.formatValue = function(value) { - return kbn.valueFormats.bytes(value, 0, -7); + return kbn.valueFormats[$scope.panel.format](value, 0, -7); }; $scope.updateTimeRange = function () { @@ -105,26 +105,34 @@ function (angular, app, _, TimeSeries, kbn) { }); series.points = series.getFlotPairs('connected'); - series.updateLegendValues(kbn.valueFormats.bytes, 2, -7); return series; }; $scope.render = function() { + var i, series; var data = { series: $scope.series, stats: [] }; + for (i = 0; i < data.series.length; i++) { + series = data.series[i]; + series.updateLegendValues(kbn.valueFormats[$scope.panel.format], 2, -7); + } + var main = data.series[0]; if ($scope.panel.avg) { data.stats.push({ value: $scope.formatValue(main.stats.avg), func: 'avg' }); } - if ($scope.panel.total) { data.stats.push({ value: $scope.formatValue(main.stats.total), func: 'total' }); } + if ($scope.panel.current) { + data.stats.push({ value: $scope.formatValue(main.stats.current), func: 'current' }); + } + $scope.data = data; $scope.$emit('render'); From 9866e0851b5c02cede9597a79b86d5fc8d11a526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 16 Oct 2014 11:21:56 -0400 Subject: [PATCH 041/125] Graph: single series tooltip and unit format change fix, Closes #946 --- src/app/directives/grafanaGraph.tooltip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/directives/grafanaGraph.tooltip.js b/src/app/directives/grafanaGraph.tooltip.js index 4c3f5e434d8..3a4a9b1ac2a 100644 --- a/src/app/directives/grafanaGraph.tooltip.js +++ b/src/app/directives/grafanaGraph.tooltip.js @@ -153,7 +153,7 @@ function ($) { } // single series tooltip else if (item) { - series = item.series; + series = seriesList[item.seriesIndex]; group = ' ' + series.label; if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') { From 7c4d1b7b01b40d5e64f70069557a14ab22420556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 16 Oct 2014 13:44:52 -0400 Subject: [PATCH 042/125] StatsPanel: made big values template based --- src/app/panels/stats/module.js | 51 +++++++++++++++------------ src/app/panels/stats/statsEditor.html | 13 ++++--- src/css/less/stats-panel.less | 4 +-- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index 572ef040572..470faa5f0c4 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -39,12 +39,18 @@ function (angular, app, _, TimeSeries, kbn) { targets: [{}], cacheTimeout: null, format: 'none', - avg: true, - stats: true, - table: true, + stats: { + show: true, + avg: true, + template: '{{value}} {{func}}' + }, + table: { + show: true, + } }; _.defaults($scope.panel, _d); + _.defaults($scope.panel.stats, _d.stats); $scope.init = function() { panelSrv.init($scope); @@ -121,19 +127,6 @@ function (angular, app, _, TimeSeries, kbn) { series.updateLegendValues(kbn.valueFormats[$scope.panel.format], 2, -7); } - var main = data.series[0]; - - if ($scope.panel.avg) { - data.stats.push({ value: $scope.formatValue(main.stats.avg), func: 'avg' }); - } - if ($scope.panel.total) { - data.stats.push({ value: $scope.formatValue(main.stats.total), func: 'total' }); - } - if ($scope.panel.current) { - data.stats.push({ value: $scope.formatValue(main.stats.current), func: 'current' }); - } - - $scope.data = data; $scope.$emit('render'); }; @@ -146,8 +139,9 @@ function (angular, app, _, TimeSeries, kbn) { return { link: function(scope, elem) { var data; + var valueRegex = /\{\{([a-zA-Z]+)\}\}/g; + var smallValueTextRegex = /!(\S+)/g; - console.log('asd'); scope.$on('render', function() { data = scope.data; @@ -159,20 +153,31 @@ function (angular, app, _, TimeSeries, kbn) { render(); }); + function valueTemplateReplaceFunc(match, statType) { + var stats = data.series[0].stats; + var value = scope.formatValue(stats[statType]); + return value; + } + + function smallValueTextReplaceFunc(match, text) { + return '' + text + ''; + } + function render() { + var panel = scope.panel; var body = ''; var i, series; - if (scope.panel.stats) { + if (panel.stats.show) { body += '
    '; - for (i = 0; i < scope.data.stats.length; i++) { - body += '' + data.stats[i].value + ''; - body += ' (' + data.stats[i].func + ')'; - } + body += ''; + var valueHtml = panel.stats.template.replace(valueRegex, valueTemplateReplaceFunc); + body += valueHtml.replace(smallValueTextRegex, smallValueTextReplaceFunc); + body += '
    '; body += '
'; } - if (scope.panel.table) { + if (panel.table.show) { body += ''; body += ''; body += ''; diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/stats/statsEditor.html index 297738ef218..98df3dda258 100644 --- a/src/app/panels/stats/statsEditor.html +++ b/src/app/panels/stats/statsEditor.html @@ -1,16 +1,15 @@
Main options
- - + +
Big values
- - - - - +
+ + +
Formats
diff --git a/src/css/less/stats-panel.less b/src/css/less/stats-panel.less index ad1ad017628..34bbefd806d 100644 --- a/src/css/less/stats-panel.less +++ b/src/css/less/stats-panel.less @@ -12,8 +12,8 @@ padding-left: 20px; } -.stats-panel-func { - font-size: 1.5em; +.stats-panel-value-small { + font-size: 50%; } .stats-panel-table { From 956d93e871a8a87fe9ebe5a4ebe36488639f4496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 17 Oct 2014 10:36:04 -0400 Subject: [PATCH 043/125] StatsPanel: fine tuning colors --- src/app/panels/stats/module.js | 75 ++++++++++++++++++++++++++- src/app/panels/stats/statsEditor.html | 56 +++++++++++++------- 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index 470faa5f0c4..5cb84ecffb3 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -44,6 +44,12 @@ function (angular, app, _, TimeSeries, kbn) { avg: true, template: '{{value}} {{func}}' }, + coloring: { + thresholds: '', + background: false, + value: false, + colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"] + }, table: { show: true, } @@ -51,6 +57,7 @@ function (angular, app, _, TimeSeries, kbn) { _.defaults($scope.panel, _d); _.defaults($scope.panel.stats, _d.stats); + _.defaults($scope.panel.coloring, _d.coloring); $scope.init = function() { panelSrv.init($scope); @@ -115,6 +122,25 @@ function (angular, app, _, TimeSeries, kbn) { return series; }; + $scope.setColoring = function(options) { + if (options.background) { + $scope.panel.coloring.value = false; + $scope.panel.coloring.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; + } + else { + $scope.panel.coloring.background = false; + $scope.panel.coloring.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; + } + $scope.render(); + }; + + $scope.invertColorOrder = function() { + var tmp = $scope.panel.coloring.colors[0]; + $scope.panel.coloring.colors[0] = $scope.panel.coloring.colors[2]; + $scope.panel.coloring.colors[2] = tmp; + $scope.render(); + }; + $scope.render = function() { var i, series; var data = { @@ -127,6 +153,12 @@ function (angular, app, _, TimeSeries, kbn) { series.updateLegendValues(kbn.valueFormats[$scope.panel.format], 2, -7); } + data.thresholds = $scope.panel.coloring.thresholds.split(',').map(function(strVale) { + return Number(strVale.trim()); + }); + + data.colorMap = $scope.panel.coloring.colors; + $scope.data = data; $scope.$emit('render'); }; @@ -144,6 +176,7 @@ function (angular, app, _, TimeSeries, kbn) { scope.$on('render', function() { data = scope.data; + data.mainValue = null; if (!data || data.series.length === 0) { elem.html('no data'); @@ -153,10 +186,33 @@ function (angular, app, _, TimeSeries, kbn) { render(); }); + function applyColoringThresholds(value, valueString) { + if (!scope.panel.coloring.value) { + return valueString; + } + + var color = getColorForValue(value); + if (color) { + return ''+ valueString + ''; + } + + return valueString; + } + + function getColorForValue(value) { + for (var i = data.thresholds.length - 1; i >= 0 ; i--) { + if (value > data.thresholds[i]) { + return data.colorMap[i]; + } + } + return null; + } + function valueTemplateReplaceFunc(match, statType) { var stats = data.series[0].stats; - var value = scope.formatValue(stats[statType]); - return value; + data.mainValue = stats[statType]; + var valueFormated = scope.formatValue(data.mainValue); + return applyColoringThresholds(data.mainValue, valueFormated); } function smallValueTextReplaceFunc(match, text) { @@ -177,6 +233,21 @@ function (angular, app, _, TimeSeries, kbn) { body += '
'; } + if (panel.coloring.background && data.mainValue) { + var color = getColorForValue(data.mainValue); + if (color) { + elem.parents('.panel-container').css('background-color', color); + if (scope.fullscreen) { + elem.css('background-color', color); + } else { + elem.css('background-color', ''); + } + } + } else { + elem.parents('.panel-container').css('background-color', ''); + elem.css('background-color', ''); + } + if (panel.table.show) { body += '
avgminmaxcurrenttotal
'; body += ''; diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/stats/statsEditor.html index 98df3dda258..c947e331370 100644 --- a/src/app/panels/stats/statsEditor.html +++ b/src/app/panels/stats/statsEditor.html @@ -18,28 +18,44 @@ - - -
-
Series options
-
-
-
    -
  • - -
  • - -
  • - {{series.info.alias}} -
  • - -
  • -
  • -
-
-
+
Coloring
+ + +
+ + +
+
+ + + + + invert order
+ + + + + + + + + + + + + + + + + + + + + + + From cae6626b0655adbdcead8f3d6dd7ff30bb5c8742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 17 Oct 2014 10:38:15 -0400 Subject: [PATCH 044/125] StatsPanel: small change to stats panel, #951 --- src/app/panels/stats/statsEditor.html | 46 +++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/stats/statsEditor.html index c947e331370..70798d65adf 100644 --- a/src/app/panels/stats/statsEditor.html +++ b/src/app/panels/stats/statsEditor.html @@ -36,26 +36,26 @@
- - - - - - - - - - - - - - - - - - - - - - - +
+
+
Series options
+
+
+
    +
  • + +
  • + +
  • + {{series.info.alias}} +
  • + +
  • +
  • +
+
+
+
+
+
+ From e3e08cf8e79852adefc03fdc167e27b2e0113712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 18 Oct 2014 13:31:15 -0400 Subject: [PATCH 045/125] Graph: fix for second y axis tick unit labels wrapping on the next line, Fixes #505 --- CHANGELOG.md | 1 + src/vendor/jquery/jquery.flot.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 682904a08d8..a7b4bbd366a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ **Fixes** - [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other) +- [Issue #505](https://github.com/grafana/grafana/issues/505). Graph: fix for second y axis tick unit labels wrapping on the next line ======= # 1.8.1 (2014-09-30) diff --git a/src/vendor/jquery/jquery.flot.js b/src/vendor/jquery/jquery.flot.js index 87a1b17c742..dc9e3677851 100644 --- a/src/vendor/jquery/jquery.flot.js +++ b/src/vendor/jquery/jquery.flot.js @@ -1416,7 +1416,8 @@ Licensed under the MIT license. var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); - labelWidth = Math.max(labelWidth, info.width); + /// Grafana fix, add +1 to label width + labelWidth = Math.max(labelWidth, info.width + 1); labelHeight = Math.max(labelHeight, info.height); } From 16e79809828b423c9ecfedad33eb4c4ec724d2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 19 Oct 2014 12:30:41 -0400 Subject: [PATCH 046/125] Help: added help modal, accessed by shortcut '?', the help modal only contains a list of all shortcuts right now but will be extended in the future, #952 --- CHANGELOG.md | 1 + src/app/partials/help_modal.html | 46 +++++++++++++++++++ .../dashboard/dashboardKeyBindings.js | 20 +++++++- src/app/services/keyboardManager.js | 2 +- src/css/less/grafana.less | 6 +++ 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/app/partials/help_modal.html diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b4bbd366a..dd27a6a6667 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ **Misc** - [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory +- [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts **Fixes** - [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other) diff --git a/src/app/partials/help_modal.html b/src/app/partials/help_modal.html new file mode 100644 index 00000000000..8fca3680fa0 --- /dev/null +++ b/src/app/partials/help_modal.html @@ -0,0 +1,46 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Dashboard wide shortcuts
ESCExit fullscreen edit/view mode, close search or any editor view
CTRL+FOpen dashboard search view (also contains import/playlist controls)
CTRL+SSave dashboard
CTRL+HHide row controls
CTRL+ZZoom out
CTRL+RRefresh (Fetches new data and rerenders panels)
+
+ +
+ + diff --git a/src/app/services/dashboard/dashboardKeyBindings.js b/src/app/services/dashboard/dashboardKeyBindings.js index 987bb5a71ad..c87b07db622 100644 --- a/src/app/services/dashboard/dashboardKeyBindings.js +++ b/src/app/services/dashboard/dashboardKeyBindings.js @@ -8,7 +8,7 @@ function(angular, $) { var module = angular.module('grafana.services'); - module.service('dashboardKeybindings', function($rootScope, keyboardManager) { + module.service('dashboardKeybindings', function($rootScope, keyboardManager, $modal, $q) { this.shortcuts = function(scope) { @@ -22,6 +22,24 @@ function(angular, $) { keyboardManager.unbind('esc'); }); + var helpModalScope = null; + keyboardManager.bind('shift+¿', function() { + if (helpModalScope) { return; } + + helpModalScope = $rootScope.$new(); + var helpModal = $modal({ + template: './app/partials/help_modal.html', + persist: false, + show: false, + scope: helpModalScope, + keyboard: false + }); + + helpModalScope.$on('$destroy', function() { helpModalScope = null; }); + $q.when(helpModal).then(function(modalEl) { modalEl.modal('show'); }); + + }, { inputDisabled: true }); + keyboardManager.bind('ctrl+f', function() { scope.appEvent('show-dash-editor', { src: 'app/partials/search.html' }); }, { inputDisabled: true }); diff --git a/src/app/services/keyboardManager.js b/src/app/services/keyboardManager.js index 9875d74964f..72124fe76af 100644 --- a/src/app/services/keyboardManager.js +++ b/src/app/services/keyboardManager.js @@ -277,4 +277,4 @@ function (angular) { return keyboardManagerService; }]); -}); \ No newline at end of file +}); diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index ab9e9ee99b9..1f676b97531 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -552,3 +552,9 @@ select.grafana-target-segment-input { .grafana-tip { padding-left: 5px; } + +.shortcut-table { + td { padding: 3px; } + th:last-child { text-align: left; } + td:first-child { text-align: right; } +} From 31a4d9204cde9dc901d6a9c6bff69390af3dbbce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 19 Oct 2014 19:36:59 -0400 Subject: [PATCH 047/125] SingleStatPanel: Added graph to single stat panel, #951 --- src/app/components/settings.js | 1 + src/app/directives/panelMenu.js | 4 +- src/app/panels/stats/module.html | 32 +---- src/app/panels/stats/module.js | 176 ++++--------------------- src/app/panels/stats/statsDirective.js | 170 ++++++++++++++++++++++++ src/app/panels/stats/statsEditor.html | 34 +++-- src/css/less/stats-panel.less | 10 ++ 7 files changed, 234 insertions(+), 193 deletions(-) create mode 100644 src/app/panels/stats/statsDirective.js diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 01f40c2e761..38313aeb198 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -17,6 +17,7 @@ function (_, crypto) { window_title_prefix : 'Grafana - ', panels : { 'graph': { path: 'panels/graph' }, + 'stats': { path: 'panels/stats' }, 'text': { path: 'panels/text' } }, plugins : {}, diff --git a/src/app/directives/panelMenu.js b/src/app/directives/panelMenu.js index f8ea42c6873..2a68dd30865 100644 --- a/src/app/directives/panelMenu.js +++ b/src/app/directives/panelMenu.js @@ -9,7 +9,7 @@ function (angular, $, _) { angular .module('grafana.directives') .directive('panelMenu', function($compile) { - var linkTemplate = '{{panel.title | interpolateTemplateVars}}'; + var linkTemplate = '{{panel.title | interpolateTemplateVars}}'; function createMenuTemplate($scope) { var template = '
'; @@ -113,7 +113,7 @@ function (angular, $, _) { dismiss(2500); }; - if ($scope.panelMeta.titlePos) { + if ($scope.panelMeta.titlePos && $scope.panel.title) { elem.css('text-align', 'left'); $link.css('padding-left', '10px'); } diff --git a/src/app/panels/stats/module.html b/src/app/panels/stats/module.html index 64e61cacc69..353cdd29c5a 100644 --- a/src/app/panels/stats/module.html +++ b/src/app/panels/stats/module.html @@ -1,40 +1,14 @@
-
- - - - - - - - - - - - - - - - - - - - - - - - - - +
- - Panel settings + + Singlestat
diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index 5cb84ecffb3..d5fdc9e38ac 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -5,14 +5,15 @@ define([ 'components/timeSeries', 'kbn', 'services/panelSrv', + './statsDirective', ], function (angular, app, _, TimeSeries, kbn) { 'use strict'; - var module = angular.module('grafana.panels.stats', []); + var module = angular.module('grafana.panels.stats'); app.useModule(module); - module.controller('StatsCtrl', function($scope, panelSrv, timeSrv, $rootScope) { + module.controller('StatsCtrl', function($scope, panelSrv, timeSrv) { $scope.panelMeta = { titlePos: 'left', @@ -27,7 +28,7 @@ function (angular, app, _, TimeSeries, kbn) { src:'app/partials/metrics.html' }, { - title: 'Display Styles', + title: 'Options', src:'app/panels/stats/statsEditor.html' } ], @@ -39,25 +40,20 @@ function (angular, app, _, TimeSeries, kbn) { targets: [{}], cacheTimeout: null, format: 'none', - stats: { - show: true, - avg: true, - template: '{{value}} {{func}}' - }, - coloring: { - thresholds: '', - background: false, - value: false, - colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"] - }, - table: { - show: true, + template: '{{avg}} !(avg)', + thresholds: '', + colorBackground: false, + colorValue: false, + colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + sparkline: { + show: false, + full: false, + lineColor: 'rgb(31, 120, 193)', + fillColor: 'rgb(31, 120, 193)', } }; _.defaults($scope.panel, _d); - _.defaults($scope.panel.stats, _d.stats); - _.defaults($scope.panel.coloring, _d.coloring); $scope.init = function() { panelSrv.init($scope); @@ -101,43 +97,33 @@ function (angular, app, _, TimeSeries, kbn) { $scope.render(); }; - $scope.seriesHandler = function(seriesData, index) { - var datapoints = seriesData.datapoints; - var alias = seriesData.target; - var color = $rootScope.colors[index]; - - var seriesInfo = { - alias: alias, - enable: true, - color: color - }; - + $scope.seriesHandler = function(seriesData) { var series = new TimeSeries({ - datapoints: datapoints, - info: seriesInfo, + datapoints: seriesData.datapoints, + info: { alias: seriesData.target }, }); - series.points = series.getFlotPairs('connected'); + series.data = series.getFlotPairs('connected'); return series; }; $scope.setColoring = function(options) { if (options.background) { - $scope.panel.coloring.value = false; - $scope.panel.coloring.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; + $scope.panel.colorValue = false; + $scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; } else { - $scope.panel.coloring.background = false; - $scope.panel.coloring.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; + $scope.panel.colorBackground = false; + $scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; } $scope.render(); }; $scope.invertColorOrder = function() { - var tmp = $scope.panel.coloring.colors[0]; - $scope.panel.coloring.colors[0] = $scope.panel.coloring.colors[2]; - $scope.panel.coloring.colors[2] = tmp; + var tmp = $scope.panel.colors[0]; + $scope.panel.colors[0] = $scope.panel.colors[2]; + $scope.panel.colors[2] = tmp; $scope.render(); }; @@ -153,11 +139,11 @@ function (angular, app, _, TimeSeries, kbn) { series.updateLegendValues(kbn.valueFormats[$scope.panel.format], 2, -7); } - data.thresholds = $scope.panel.coloring.thresholds.split(',').map(function(strVale) { + data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) { return Number(strVale.trim()); }); - data.colorMap = $scope.panel.coloring.colors; + data.colorMap = $scope.panel.colors; $scope.data = data; $scope.$emit('render'); @@ -165,112 +151,4 @@ function (angular, app, _, TimeSeries, kbn) { $scope.init(); }); - - module.directive('statsPanel', function() { - - return { - link: function(scope, elem) { - var data; - var valueRegex = /\{\{([a-zA-Z]+)\}\}/g; - var smallValueTextRegex = /!(\S+)/g; - - scope.$on('render', function() { - data = scope.data; - data.mainValue = null; - - if (!data || data.series.length === 0) { - elem.html('no data'); - return; - } - - render(); - }); - - function applyColoringThresholds(value, valueString) { - if (!scope.panel.coloring.value) { - return valueString; - } - - var color = getColorForValue(value); - if (color) { - return ''+ valueString + ''; - } - - return valueString; - } - - function getColorForValue(value) { - for (var i = data.thresholds.length - 1; i >= 0 ; i--) { - if (value > data.thresholds[i]) { - return data.colorMap[i]; - } - } - return null; - } - - function valueTemplateReplaceFunc(match, statType) { - var stats = data.series[0].stats; - data.mainValue = stats[statType]; - var valueFormated = scope.formatValue(data.mainValue); - return applyColoringThresholds(data.mainValue, valueFormated); - } - - function smallValueTextReplaceFunc(match, text) { - return '' + text + ''; - } - - function render() { - var panel = scope.panel; - var body = ''; - var i, series; - - if (panel.stats.show) { - body += '
'; - body += ''; - var valueHtml = panel.stats.template.replace(valueRegex, valueTemplateReplaceFunc); - body += valueHtml.replace(smallValueTextRegex, smallValueTextReplaceFunc); - body += '
'; - body += '
'; - } - - if (panel.coloring.background && data.mainValue) { - var color = getColorForValue(data.mainValue); - if (color) { - elem.parents('.panel-container').css('background-color', color); - if (scope.fullscreen) { - elem.css('background-color', color); - } else { - elem.css('background-color', ''); - } - } - } else { - elem.parents('.panel-container').css('background-color', ''); - elem.css('background-color', ''); - } - - if (panel.table.show) { - body += ''; - body += ''; - body += ''; - body += ''; - for (i = 0; i < data.series.length; i++) { - series = data.series[i]; - body += ''; - body += ''; - body += ''; - body += ''; - body += ''; - body += ''; - body += ''; - } - body += '
avgminmaxcurrenttotal
'; - body += series.info.alias + ' ' + series.info.avg + '' + series.info.min + '' + series.info.max + '' + series.info.total + '' + series.info.current + '
'; - } - - elem.html(body); - } - } - }; - }); - }); diff --git a/src/app/panels/stats/statsDirective.js b/src/app/panels/stats/statsDirective.js new file mode 100644 index 00000000000..f486445b73c --- /dev/null +++ b/src/app/panels/stats/statsDirective.js @@ -0,0 +1,170 @@ +define([ + 'angular', + 'app', + 'lodash', + 'kbn', + 'jquery', + 'jquery.flot', + 'jquery.flot.time', +], +function (angular, app, _, kbn, $) { + 'use strict'; + + var module = angular.module('grafana.panels.stats', []); + app.useModule(module); + + module.directive('statsPanel', function() { + + return { + link: function(scope, elem) { + var data; + var valueRegex = /\{\{([a-zA-Z]+)\}\}/g; + var smallValueTextRegex = /!(\S+)/g; + var $panelContainer = elem.parents('.panel-container'); + + scope.$on('render', function() { + data = scope.data; + data.mainValue = null; + + if (!data || data.series.length === 0) { + elem.html('no data'); + return; + } + + render(); + }); + + function setElementHeight() { + try { + var height = scope.height || scope.panel.height || scope.row.height; + if (_.isString(height)) { + height = parseInt(height.replace('px', ''), 10); + } + + height -= scope.panel.title ? 24 : 9; // subtract panel title bar + + elem.css('height', height + 'px'); + + return true; + } catch(e) { // IE throws errors sometimes + return false; + } + } + + function applyColoringThresholds(value, valueString) { + if (!scope.panel.colorValue) { + return valueString; + } + + var color = getColorForValue(value); + if (color) { + return ''+ valueString + ''; + } + + return valueString; + } + + function getColorForValue(value) { + for (var i = data.thresholds.length - 1; i >= 0 ; i--) { + if (value > data.thresholds[i]) { + return data.colorMap[i]; + } + } + return null; + } + + function valueTemplateReplaceFunc(match, statType) { + var stats = data.series[0].stats; + data.mainValue = stats[statType]; + var valueFormated = scope.formatValue(data.mainValue); + return applyColoringThresholds(data.mainValue, valueFormated); + } + + function smallValueTextReplaceFunc(match, text) { + return '' + text + ''; + } + + function render() { + setElementHeight(); + + var panel = scope.panel; + var body = ''; + + body += '
'; + body += ''; + var valueHtml = panel.template.replace(valueRegex, valueTemplateReplaceFunc); + body += valueHtml.replace(smallValueTextRegex, smallValueTextReplaceFunc); + body += '
'; + body += '
'; + + if (panel.colorBackground && data.mainValue) { + var color = getColorForValue(data.mainValue); + if (color) { + $panelContainer.css('background-color', color); + if (scope.fullscreen) { + elem.css('background-color', color); + } else { + elem.css('background-color', ''); + } + } + } else { + $panelContainer.css('background-color', ''); + elem.css('background-color', ''); + } + + var width = elem.width() + 20; + var height = elem.height() || 100; + + var plotCanvas = $('
'); + var plotCss = {}; + plotCss.position = 'absolute'; + + if (panel.sparkline.full) { + plotCss.bottom = '5px'; + plotCss.left = '-5px'; + plotCss.width = (width - 10) + 'px'; + plotCss.height = (height - 45) + 'px'; + } + else { + plotCss.bottom = "0px"; + plotCss.left = "-5px"; + plotCss.width = (width - 10) + 'px'; + plotCss.height = Math.floor(height * 0.3) + "px"; + } + + plotCanvas.css(plotCss); + + var options = { + legend: { show: false }, + series: { + lines: { + show: true, + fill: 1, + lineWidth: 1, + fillColor: panel.sparkline.fillColor, + }, + }, + yaxes: { show: false }, + xaxis: { + show: false, + mode: "time", + min: scope.range.from.getTime(), + max: scope.range.to.getTime(), + }, + grid: { hoverable: false, show: false }, + }; + + elem.html(body); + elem.append(plotCanvas); + + data.series[0].color = panel.sparkline.lineColor; + + setTimeout(function() { + $.plot(plotCanvas, [data.series[0]], options); + }, 200); + } + } + }; + }); + +}); diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/stats/statsEditor.html index 70798d65adf..3646470c84a 100644 --- a/src/app/panels/stats/statsEditor.html +++ b/src/app/panels/stats/statsEditor.html @@ -1,14 +1,9 @@
-
-
Main options
- - -
-
+
Big values
- +
@@ -20,22 +15,35 @@
Coloring
- - + +
- +
- - - + + + invert order
+
+
+
Spark lines
+
+ + +
+ + + +
+
+
Series options
diff --git a/src/css/less/stats-panel.less b/src/css/less/stats-panel.less index 34bbefd806d..29e162c77ea 100644 --- a/src/css/less/stats-panel.less +++ b/src/css/less/stats-panel.less @@ -1,6 +1,16 @@ +.stats-panel { + position: relative; + display: table; + width: 100%; +} + .stats-panel-value-container { padding: 20px; + display: table-cell; + vertical-align: middle; text-align: center; + position: relative; + z-index: 1; } .stats-panel-value { From b85fe623894197ef045ef4f99a6e92774f3a65ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 20 Oct 2014 10:06:34 -0400 Subject: [PATCH 048/125] Graph: series override dropdown menu select did not work after recent commit that added typeahead to this dropdown --- src/app/directives/dropdown.typeahead.js | 11 +++++++++++ src/app/panels/graph/seriesOverridesCtrl.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/app/directives/dropdown.typeahead.js b/src/app/directives/dropdown.typeahead.js index e02b90b2576..f46de052c06 100644 --- a/src/app/directives/dropdown.typeahead.js +++ b/src/app/directives/dropdown.typeahead.js @@ -37,6 +37,17 @@ function (angular, app, _, $) { return memo; }, []); + $scope.menuItemSelected = function(optionIndex, valueIndex) { + var option = $scope.menuItems[optionIndex]; + var result = { + $item: option.submenu[valueIndex], + $optionIndex: optionIndex, + $valueIndex: valueIndex + }; + + $scope.dropdownTypeaheadOnSelect(result); + }; + $input.attr('data-provide', 'typeahead'); $input.typeahead({ source: typeaheadValues, diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index bf46b086bea..b677bf19e17 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -23,7 +23,7 @@ define([ option.submenu = _.map(values, function(value, index) { return { text: String(value), - click: 'setOverride(' + option.index + ',' + index + ')' + click: 'menuItemSelected(' + option.index + ',' + index + ')' }; }); From 8a80ea26b866b77c4d333660b9b16c3dd8d3558b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 20 Oct 2014 10:10:18 -0400 Subject: [PATCH 049/125] Graph: fix for series override controller unit test --- src/test/specs/seriesOverridesCtrl-specs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/specs/seriesOverridesCtrl-specs.js b/src/test/specs/seriesOverridesCtrl-specs.js index e211b6dab35..6a96c77416d 100644 --- a/src/test/specs/seriesOverridesCtrl-specs.js +++ b/src/test/specs/seriesOverridesCtrl-specs.js @@ -18,7 +18,7 @@ define([ 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)'); + expect(ctx.scope.overrideMenu[1].submenu[1].click).to.be('menuItemSelected(1,1)'); }); }); From ff254ce08daaf2ee92573719269539ef444c89fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 20 Oct 2014 11:33:07 -0400 Subject: [PATCH 050/125] Help: fix for help modal shortcut, and added missing shortcut, #952 --- src/app/partials/help_modal.html | 4 ++++ src/app/services/dashboard/dashboardKeyBindings.js | 2 +- src/app/services/keyboardManager.js | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/partials/help_modal.html b/src/app/partials/help_modal.html index 8fca3680fa0..866d44898eb 100644 --- a/src/app/partials/help_modal.html +++ b/src/app/partials/help_modal.html @@ -36,6 +36,10 @@ CTRL+R Refresh (Fetches new data and rerenders panels) + + CTRL+O + Enable/Disable shared graph crosshair +
diff --git a/src/app/services/dashboard/dashboardKeyBindings.js b/src/app/services/dashboard/dashboardKeyBindings.js index c87b07db622..d5e0c1609c0 100644 --- a/src/app/services/dashboard/dashboardKeyBindings.js +++ b/src/app/services/dashboard/dashboardKeyBindings.js @@ -23,7 +23,7 @@ function(angular, $) { }); var helpModalScope = null; - keyboardManager.bind('shift+¿', function() { + keyboardManager.bind('shift+?', function() { if (helpModalScope) { return; } helpModalScope = $rootScope.$new(); diff --git a/src/app/services/keyboardManager.js b/src/app/services/keyboardManager.js index 72124fe76af..10d5db03f45 100644 --- a/src/app/services/keyboardManager.js +++ b/src/app/services/keyboardManager.js @@ -61,6 +61,7 @@ function (angular) { else if (e.which) { code = e.which; } + var character = String.fromCharCode(code).toLowerCase(); if (code === 188) { @@ -93,6 +94,9 @@ function (angular) { ",": "<", ".": ">", "/": "?", + "»": "?", + "«": "?", + "¿": "?", "\\": "|" }; // Special Keys - and their codes From 6c83699e6f33d4b13d05ae407a23516aaef997bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 20 Oct 2014 12:14:58 -0400 Subject: [PATCH 051/125] Panel: css fix for panel fullscreen/edit mode for latest chrome 38 and Firefox 33, #954 --- src/app/dashboards/default.json | 1 - src/app/dashboards/scripted.js | 11 +++++++++++ src/app/dashboards/template_vars.json | 1 - src/app/panels/graph/module.js | 4 ---- src/css/less/grafana.less | 8 +++++--- src/css/less/submenu.less | 2 +- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/app/dashboards/default.json b/src/app/dashboards/default.json index 241e663d5ea..931d4c6f802 100644 --- a/src/app/dashboards/default.json +++ b/src/app/dashboards/default.json @@ -101,7 +101,6 @@ "legend_counts": true, "timezone": "browser", "percentage": false, - "zerofill": true, "nullPointMode": "connected", "steppedLine": false, "tooltip": { diff --git a/src/app/dashboards/scripted.js b/src/app/dashboards/scripted.js index b8f428a9834..f4a438b7854 100644 --- a/src/app/dashboards/scripted.js +++ b/src/app/dashboards/scripted.js @@ -68,6 +68,17 @@ for (var i = 0; i < rows; i++) { 'target': "randomWalk('random walk2')" } ], + seriesOverrides: [ + { + alias: '/random/', + yaxis: 2, + fill: 0, + linewidth: 5 + } + ], + tooltip: { + shared: true + } } ] }); diff --git a/src/app/dashboards/template_vars.json b/src/app/dashboards/template_vars.json index affe7727ce2..43e9e37836c 100644 --- a/src/app/dashboards/template_vars.json +++ b/src/app/dashboards/template_vars.json @@ -65,7 +65,6 @@ "avg": false }, "percentage": false, - "zerofill": true, "nullPointMode": "connected", "steppedLine": false, "tooltip": { diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index a1dbc942553..54a2a028fe6 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -151,10 +151,6 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { * queries */ percentage : false, - /** @scratch /panels/histogram/3 - * zerofill:: Improves the accuracy of line charts at a small performance cost. - */ - zerofill : true, nullPointMode : 'connected', diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index 1f676b97531..668feefdef0 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -115,7 +115,7 @@ position: fixed; left: 0px; right: 0px; - top: 54px; + top: 51px; height: 100%; padding: 0 10px; background: @grafanaPanelBackground; @@ -133,9 +133,11 @@ .dashboard-fullscreen { .main-view-container { - height: 0; overflow: hidden; - padding: 0; + height: 0; + .row-control-inner { + display: none; + } } } diff --git a/src/css/less/submenu.less b/src/css/less/submenu.less index b762d7acfbd..52bd1c81e92 100644 --- a/src/css/less/submenu.less +++ b/src/css/less/submenu.less @@ -1,6 +1,6 @@ .submenu-controls-visible:not(.hide-controls) { .panel-fullscreen { - top: 91px; + top: 88px; } } From 6c70122e55b8359dc2457a904ae0036524947c5c Mon Sep 17 00:00:00 2001 From: Adrien Samson Date: Wed, 22 Oct 2014 10:26:43 +0200 Subject: [PATCH 052/125] Add alignToFrom param to summarize --- src/app/services/graphite/gfunc.js | 10 +++++++--- src/test/specs/gfunc-specs.js | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/services/graphite/gfunc.js b/src/app/services/graphite/gfunc.js index 22b79527cbd..81bf9713e79 100644 --- a/src/app/services/graphite/gfunc.js +++ b/src/app/services/graphite/gfunc.js @@ -339,8 +339,12 @@ function (_) { addFuncDef({ name: 'summarize', category: categories.Transform, - params: [{ name: "interval", type: "string" }, { name: "func", type: "select", options: ['sum', 'avg', 'min', 'max', 'last'] }], - defaultParams: ['1h', 'sum'] + params: [ + { name: "interval", type: "string" }, + { name: "func", type: "select", options: ['sum', 'avg', 'min', 'max', 'last'] }, + { name: "alignToFrom", type: "boolean", optional: true, options: ['false', 'true'] }, + ], + defaultParams: ['1h', 'sum', 'false'] }); addFuncDef({ @@ -543,7 +547,7 @@ function (_) { var parameters = _.map(this.params, function(value, index) { var paramType = this.def.params[index].type; - if (paramType === 'int' || paramType === 'value_or_series') { + if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') { return value; } diff --git a/src/test/specs/gfunc-specs.js b/src/test/specs/gfunc-specs.js index e0714d8376f..f0650c16077 100644 --- a/src/test/specs/gfunc-specs.js +++ b/src/test/specs/gfunc-specs.js @@ -79,7 +79,7 @@ define([ var func = gfunc.createFuncInstance('summarize', { withDefaultParams: true }); func.updateParam('1h', 0); expect(func.params[0]).to.be('1h'); - expect(func.text).to.be('summarize(1h, sum)'); + expect(func.text).to.be('summarize(1h, sum, false)'); }); it('should parse numbers as float', function() { From 79c5d48a3cd73ec1f5ec106af89b08321b1f1745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 23 Oct 2014 12:36:25 +0200 Subject: [PATCH 053/125] Timepicker: Fix for Zoom out top menu link still visible when timepicker was disabled, Fixes #963 --- src/app/panels/timepicker/module.html | 29 ++++++++++++++++---------- src/app/partials/dashboard_topnav.html | 6 ------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/app/panels/timepicker/module.html b/src/app/panels/timepicker/module.html index 9371eb16f2e..c4cd4069e91 100644 --- a/src/app/panels/timepicker/module.html +++ b/src/app/panels/timepicker/module.html @@ -10,19 +10,26 @@ }
-
'; - - if (panel.colorBackground && data.mainValue) { - var color = getColorForValue(data.mainValue); - if (color) { - $panelContainer.css('background-color', color); - if (scope.fullscreen) { - elem.css('background-color', color); - } else { - elem.css('background-color', ''); - } - } - } else { - $panelContainer.css('background-color', ''); - elem.css('background-color', ''); - } - var width = elem.width() + 20; var height = elem.height() || 100; @@ -154,14 +128,48 @@ function (angular, app, _, kbn, $) { grid: { hoverable: false, show: false }, }; - elem.html(body); elem.append(plotCanvas); data.series[0].color = panel.sparkline.lineColor; setTimeout(function() { $.plot(plotCanvas, [data.series[0]], options); - }, 200); + }, 10); + } + + function render() { + setElementHeight(); + + var panel = scope.panel; + var body = ''; + + body += '
'; + body += ''; + var valueHtml = panel.template.replace(valueRegex, valueTemplateReplaceFunc); + body += valueHtml.replace(smallValueTextRegex, smallValueTextReplaceFunc); + body += '
'; + body += '
'; + + if (panel.colorBackground && data.mainValue) { + var color = getColorForValue(data.mainValue); + if (color) { + $panelContainer.css('background-color', color); + if (scope.fullscreen) { + elem.css('background-color', color); + } else { + elem.css('background-color', ''); + } + } + } else { + $panelContainer.css('background-color', ''); + elem.css('background-color', ''); + } + + elem.html(body); + + if (panel.sparkline.show) { + addSparkline(); + } } } }; From 08e816a5392b3652dee668d40123abf596f94fbb Mon Sep 17 00:00:00 2001 From: Mark Bell Date: Thu, 23 Oct 2014 10:35:57 -0400 Subject: [PATCH 055/125] Add support for regex based series merging in InfluxDB --- .../services/influxdb/influxQueryBuilder.js | 2 +- src/test/specs/influxQueryBuilder-specs.js | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/app/services/influxdb/influxQueryBuilder.js b/src/app/services/influxdb/influxQueryBuilder.js index eb5334fd4e1..5ee7746af6b 100644 --- a/src/app/services/influxdb/influxQueryBuilder.js +++ b/src/app/services/influxdb/influxQueryBuilder.js @@ -18,7 +18,7 @@ function () { var query = 'select '; var seriesName = target.series; - if(!seriesName.match('^/.*/')) { + if(!seriesName.match('^/.*/') && !seriesName.match(/^merge\(.*\)/)) { seriesName = '"' + seriesName+ '"'; } diff --git a/src/test/specs/influxQueryBuilder-specs.js b/src/test/specs/influxQueryBuilder-specs.js index 4f18bf2905e..b4e052a23af 100644 --- a/src/test/specs/influxQueryBuilder-specs.js +++ b/src/test/specs/influxQueryBuilder-specs.js @@ -44,6 +44,35 @@ define([ }); + describe('merge function detection', function() { + it('should not quote wrap regex merged series', function() { + var builder = new InfluxQueryBuilder({ + series: 'merge(/^google.test/)', + column: 'value', + function: 'mean' + }); + + var query = builder.build(); + + expect(query).to.be('select mean(value) from merge(/^google.test/) where $timeFilter ' + + 'group by time($interval) order asc'); + }); + + it('should quote wrap series names that start with "merge"', function() { + var builder = new InfluxQueryBuilder({ + series: 'merge.google.test', + column: 'value', + function: 'mean' + }); + + var query = builder.build(); + + expect(query).to.be('select mean(value) from "merge.google.test" where $timeFilter ' + + 'group by time($interval) order asc'); + }); + + }); + }); }); From 1cfc4d2f314ab7ad2d20f767bf2c31b127ba6dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 24 Oct 2014 11:48:08 +0200 Subject: [PATCH 056/125] ScriptedDashboard: bugfix for scripted dashboards and collapsed rows, Fixes #965 --- src/app/routes/dashboard-from-script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/routes/dashboard-from-script.js b/src/app/routes/dashboard-from-script.js index fa3abd36e81..286dca7d7d9 100644 --- a/src/app/routes/dashboard-from-script.js +++ b/src/app/routes/dashboard-from-script.js @@ -16,6 +16,7 @@ function (angular, $, config, _, kbn, moment) { .when('/dashboard/script/:jsFile', { templateUrl: 'app/partials/dashboard.html', controller : 'DashFromScriptProvider', + reloadOnSearch: false, }); }); From 02861142cbb9fe8190f5d8abf282695903b7f4c7 Mon Sep 17 00:00:00 2001 From: Max Stepanov Date: Fri, 24 Oct 2014 20:06:39 +0300 Subject: [PATCH 057/125] Replace all $interval occurrences in query string because template --- src/app/services/influxdb/influxdbDatasource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/services/influxdb/influxdbDatasource.js b/src/app/services/influxdb/influxdbDatasource.js index 1f44dbe78ef..287e2286f07 100644 --- a/src/app/services/influxdb/influxdbDatasource.js +++ b/src/app/services/influxdb/influxdbDatasource.js @@ -44,7 +44,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { // replace grafana variables query = query.replace('$timeFilter', timeFilter); - query = query.replace('$interval', (target.interval || options.interval)); + query = query.replace(/\$interval/g, (target.interval || options.interval)); // replace templated variables query = templateSrv.replace(query); From 5cec936128c782c81f634056b648a953c412167e Mon Sep 17 00:00:00 2001 From: Mark Bell Date: Fri, 24 Oct 2014 14:51:45 -0400 Subject: [PATCH 058/125] When get_data fails the legend should be cleared --- src/app/panels/graph/module.js | 1 + src/test/specs/graph-ctrl-specs.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 54a2a028fe6..87098a2d4d7 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -203,6 +203,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { $scope.panelMeta.loading = false; $scope.panelMeta.error = err.message || "Timeseries data request error"; $scope.inspector.error = err; + $scope.legend = []; $scope.render([]); }); }; diff --git a/src/test/specs/graph-ctrl-specs.js b/src/test/specs/graph-ctrl-specs.js index e1ddcbc571e..b2cf60d5d5d 100644 --- a/src/test/specs/graph-ctrl-specs.js +++ b/src/test/specs/graph-ctrl-specs.js @@ -36,6 +36,20 @@ define([ var data = ctx.scope.render.getCall(0).args[0]; expect(data.length).to.be(2); }); + + describe('get_data failure following success', function() { + beforeEach(function() { + ctx.datasource.query = sinon.stub().returns(ctx.$q.reject('Datasource Error')); + ctx.scope.get_data(); + ctx.scope.$digest(); + }); + + it('should clear the legend data', function() { + expect(ctx.scope.legend).to.eql([]); + }); + + }); + }); }); From e3942b343829435f9561e191920836ddafab5fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 25 Oct 2014 12:00:56 +0200 Subject: [PATCH 059/125] Graph: multi series tooltip fix for long series names, and metric value row alignment, Fixes #983, #984 --- src/app/directives/grafanaGraph.tooltip.js | 10 ++++++---- src/css/less/graph.less | 11 ++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/app/directives/grafanaGraph.tooltip.js b/src/app/directives/grafanaGraph.tooltip.js index 3a4a9b1ac2a..e0fede2f62d 100644 --- a/src/app/directives/grafanaGraph.tooltip.js +++ b/src/app/directives/grafanaGraph.tooltip.js @@ -144,8 +144,9 @@ function ($) { hoverInfo = seriesHoverInfo[i]; value = series.formatValue(hoverInfo.value); - group = ' ' + series.label; - seriesHtml = group + ': ' + value + '
' + seriesHtml; + seriesHtml += '
'; + seriesHtml += ' ' + series.label + ':
'; + seriesHtml += '
' + value + '
'; plot.highlight(i, hoverInfo.hoverIndex); } @@ -154,7 +155,8 @@ function ($) { // single series tooltip else if (item) { series = seriesList[item.seriesIndex]; - group = ' ' + series.label; + group = '
'; + group += ' ' + series.label + ':
'; if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') { value = item.datapoint[1] - item.datapoint[2]; @@ -165,7 +167,7 @@ function ($) { value = series.formatValue(value); timestamp = dashboard.formatDate(item.datapoint[0]); - group += ': ' + value + '
'; + group += '
' + value + '
'; self.showTooltip(timestamp, group, pos); } diff --git a/src/css/less/graph.less b/src/css/less/graph.less index c0c3f371325..0da3014410f 100644 --- a/src/css/less/graph.less +++ b/src/css/less/graph.less @@ -178,9 +178,18 @@ top: -3px; } + .graph-tooltip-list-item { + display: table-row; + } + + .graph-tooltip-series-name { + display: table-cell; + } + .graph-tooltip-value { + display: table-cell; font-weight: bold; - float: right; padding-left: 10px; + text-align: right; } } From 3cd33b6ffcc59b13ae057020450e259c8d32aa76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 26 Oct 2014 08:45:42 +0100 Subject: [PATCH 060/125] Tech: added cache busting for requirejs fetches, will only affect config.js for optimized builds, should solve user issues where browsers cache config.js which causes initial confusing setup/config issues. --- src/app/components/require.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/components/require.config.js b/src/app/components/require.config.js index 5d8da32846f..882583083c6 100644 --- a/src/app/components/require.config.js +++ b/src/app/components/require.config.js @@ -3,6 +3,7 @@ */ require.config({ baseUrl: 'app', + urlArgs: 'bust=' + (new Date().getTime()), paths: { config: ['../config', '../config.sample'], From 60a2d9f6242aa12abc78a1c5586c2f62d80316f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 27 Oct 2014 10:42:13 +0100 Subject: [PATCH 061/125] Dashboard: fix for collapsed rows became invisible when hide controls was enabled, Fixes #987 --- CHANGELOG.md | 1 + src/css/less/grafana.less | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd27a6a6667..2f055665198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ **Fixes** - [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other) - [Issue #505](https://github.com/grafana/grafana/issues/505). Graph: fix for second y axis tick unit labels wrapping on the next line +- [Issue #987](https://github.com/grafana/grafana/issues/987). Dashboard: Collapsed rows became invisible when hide controls was enabled ======= # 1.8.1 (2014-09-30) diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index 668feefdef0..31363ee4a31 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -15,7 +15,7 @@ .hide-controls { padding: 0; - .row-control-inner { + .row-tab { display: none; } .submenu-controls { From 4b79a5e9dab915dadc6088af52823ea68f449db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 28 Oct 2014 12:41:17 +0100 Subject: [PATCH 062/125] OpenTSDB: merged PR #930 adding counter max and counter reset options to OpenTSDB query editor --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f055665198..38ff0cdb732 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ - [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory - [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts +**OpenTSDB** +- [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc + **Fixes** - [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other) - [Issue #505](https://github.com/grafana/grafana/issues/505). Graph: fix for second y axis tick unit labels wrapping on the next line From a982dd17653203c64ca63682605feebe58988d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 28 Oct 2014 15:40:10 +0100 Subject: [PATCH 063/125] ScriptedDashboard: using grafana services in scripted dashboard --- ' | 84 ++++++++++++++++++ src/app/dashboards/scripted_gen_and_save.js | 95 +++++++++++++++++++++ src/app/routes/dashboard-from-script.js | 16 ++-- 3 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 ' create mode 100644 src/app/dashboards/scripted_gen_and_save.js diff --git a/' b/' new file mode 100644 index 00000000000..86791d43fde --- /dev/null +++ b/' @@ -0,0 +1,84 @@ +/* global _ */ + +/* + * Complex scripted dashboard + * This script generates a dashboard object that Grafana can load. It also takes a number of user + * supplied URL parameters (int ARGS variable) + * + * Return a dashboard object, or a function + * + * For async scripts, return a function, this function must take a single callback function as argument, + * call this callback function with the dashboard object (look at scripted_async.js for an example) + */ + +'use strict'; + +// accessable variables in this scope +var window, document, ARGS, $, jQuery, moment, kbn, services, _; + +// default datasource +var datasource = services.datasourceSrv.default; +// get datasource used for saving dashboards +var dashboardDB = services.datasourceSrv.getGrafanaDB(); + +var targets = []; + +function getTargets(path) { + return datasource.metricFindQuery(path + '.*').then(function(result) { + if (!result) { + return null; + } + + if (targets.length === 10) { + return null; + } + + var promises = _.map(result, function(metric) { + if (metric.expandable) { + return getTargets(path + "." + metric.text); + } + else { + targets.push(path + '.' + metric.text); + } + return null; + }); + + return services.$q.when(promises); + }); +} + +function createDashboard(target, index) { + // Intialize a skeleton with nothing but a rows array and service object + var dashboard = { rows : [] }; + dashboard.title = 'Scripted dash ' + index; + dashboard.time = { + from: "now-6h", + to: "now" + }; + + dashboard.rows.push({ + title: 'Chart', + height: '300px', + panels: [ + { + title: 'Events', + type: 'graph', + span: 12, + targets: [ {target: target} ] + } + ] + }); + +} + +return function(callback) { + + getTargets('apps').then(function(results) { + console.log('targets: ', targets); + _.each(targets, function(target, index) { + var dashboard = createDashboard(target); + }); + }); + +}; + diff --git a/src/app/dashboards/scripted_gen_and_save.js b/src/app/dashboards/scripted_gen_and_save.js new file mode 100644 index 00000000000..d874b3fc28e --- /dev/null +++ b/src/app/dashboards/scripted_gen_and_save.js @@ -0,0 +1,95 @@ +/* global _ */ + +/* + * Complex scripted dashboard + * This script generates a dashboard object that Grafana can load. It also takes a number of user + * supplied URL parameters (int ARGS variable) + * + * Return a dashboard object, or a function + * + * For async scripts, return a function, this function must take a single callback function as argument, + * call this callback function with the dashboard object (look at scripted_async.js for an example) + */ + +'use strict'; + +// accessable variables in this scope +var window, document, ARGS, $, jQuery, moment, kbn, services, _; + +// default datasource +var datasource = services.datasourceSrv.default; +// get datasource used for saving dashboards +var dashboardDB = services.datasourceSrv.getGrafanaDB(); + +var targets = []; + +function getTargets(path) { + return datasource.metricFindQuery(path + '.*').then(function(result) { + if (!result) { + return null; + } + + if (targets.length === 10) { + return null; + } + + var promises = _.map(result, function(metric) { + if (metric.expandable) { + return getTargets(path + "." + metric.text); + } + else { + targets.push(path + '.' + metric.text); + } + return null; + }); + + return services.$q.all(promises); + }); +} + +function createDashboard(target, index) { + // Intialize a skeleton with nothing but a rows array and service object + var dashboard = { rows : [] }; + dashboard.title = 'Scripted dash ' + index; + dashboard.time = { + from: "now-6h", + to: "now" + }; + + dashboard.rows.push({ + title: 'Chart', + height: '300px', + panels: [ + { + title: 'Events', + type: 'graph', + span: 12, + targets: [ {target: target} ] + } + ] + }); + + return dashboard; +} + +function saveDashboard(dashboard) { + var model = services.dashboardSrv.create(dashboard); + dashboardDB.saveDashboard(model); +} + +return function(callback) { + + getTargets('apps').then(function() { + console.log('targets: ', targets); + _.each(targets, function(target, index) { + var dashboard = createDashboard(target, index); + saveDashboard(dashboard); + + if (index === targets.length - 1) { + callback(dashboard); + } + }); + }); + +}; + diff --git a/src/app/routes/dashboard-from-script.js b/src/app/routes/dashboard-from-script.js index 286dca7d7d9..b73a49b0cbe 100644 --- a/src/app/routes/dashboard-from-script.js +++ b/src/app/routes/dashboard-from-script.js @@ -20,18 +20,24 @@ function (angular, $, config, _, kbn, moment) { }); }); - module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, alertSrv, $q) { + module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, $q, dashboardSrv, datasourceSrv, $timeout) { var execute_script = function(result) { + var services = { + dashboardSrv: dashboardSrv, + datasourceSrv: datasourceSrv, + $q: $q, + }; + /*jshint -W054 */ - var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', result.data); - var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $); + var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', 'services', result.data); + var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $, services); // Handle async dashboard scripts if (_.isFunction(script_result)) { var deferred = $q.defer(); script_result(function(dashboard) { - $rootScope.$apply(function() { + $timeout(function() { deferred.resolve({ data: dashboard }); }); }); @@ -48,7 +54,7 @@ function (angular, $, config, _, kbn, moment) { .then(execute_script) .then(null,function(err) { console.log('Script dashboard error '+ err); - alertSrv.set('Error', "Could not load scripts/"+file+". Please make sure it exists and returns a valid dashboard", 'error'); + $scope.appEvent('alert-error', ["Script Error", "Please make sure it exists and returns a valid dashboard"]); return false; }); }; From a7b0f6dc9fcbb4f3ad1449e7ebde42ded7f67fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 28 Oct 2014 16:16:52 +0100 Subject: [PATCH 064/125] ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example), Closes #991 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38ff0cdb732..24772741c14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ **Misc** - [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory - [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts +- [Issue #991](https://github.com/grafana/grafana/issues/991). ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example) **OpenTSDB** - [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc From e5bb7f7c2ddda5eff1c72d9abf0651b17c736f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 28 Oct 2014 19:00:46 +0100 Subject: [PATCH 065/125] CustomDatasource: fixed build optimization issue with custom datasources, Fixes #994 --- src/plugins/datasource.example.js | 2 +- tasks/options/ngmin.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/datasource.example.js b/src/plugins/datasource.example.js index d85409cf2a5..fcc160ec569 100644 --- a/src/plugins/datasource.example.js +++ b/src/plugins/datasource.example.js @@ -19,7 +19,7 @@ function (angular, _, kbn) { this.url = datasource.url; } - CustomDatasource.prototype.query = function(filterSrv, options) { + CustomDatasource.prototype.query = function(options) { // get from & to in seconds var from = kbn.parseDate(options.range.from).getTime() / 1000; var to = kbn.parseDate(options.range.to).getTime() / 1000; diff --git a/tasks/options/ngmin.js b/tasks/options/ngmin.js index 39dbb7a540a..82e7dee4f8d 100644 --- a/tasks/options/ngmin.js +++ b/tasks/options/ngmin.js @@ -10,6 +10,7 @@ module.exports = function(config) { 'app/filters/**/*.js', 'app/panels/**/*.js', 'app/routes/**/*.js', + 'plugins/**/*.js', 'app/app.js', 'vendor/angular/**/*.js', ], From cdb4b3cc7ddc576bd63f564a8779f589d373f366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 29 Oct 2014 09:51:44 +0100 Subject: [PATCH 066/125] Panel: fix fullscreen/edit view and page refresh when panel is in collapsed/hidden row, Fixes #992 --- src/app/services/dashboard/dashboardViewStateSrv.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/app/services/dashboard/dashboardViewStateSrv.js b/src/app/services/dashboard/dashboardViewStateSrv.js index 2f85da5d27a..1ff95e8d4d3 100644 --- a/src/app/services/dashboard/dashboardViewStateSrv.js +++ b/src/app/services/dashboard/dashboardViewStateSrv.js @@ -32,8 +32,16 @@ function (angular, _, $) { }); this.update(this.getQueryStringState(), true); + this.expandRowForPanel(); } + DashboardViewState.prototype.expandRowForPanel = function() { + if (!this.state.panelId) { return; } + + var panelInfo = this.$scope.dashboard.getPanelInfoById(this.state.panelId); + panelInfo.row.collapse = false; + }; + DashboardViewState.prototype.needsSync = function(urlState) { return _.isEqual(this.state, urlState) === false; }; From c88bfbbf820a9a7c92ace85460a5037b4f684469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 2 Nov 2014 11:36:11 +0100 Subject: [PATCH 067/125] SingleStatPanel: editor cleanup --- src/app/panels/stats/statsEditor.html | 37 ++++++--------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/stats/statsEditor.html index 3646470c84a..0406fecd7de 100644 --- a/src/app/panels/stats/statsEditor.html +++ b/src/app/panels/stats/statsEditor.html @@ -34,36 +34,15 @@
Spark lines
-
- - -
- - - -
-
- -
-
-
Series options
-
-
-
    -
  • - -
  • - -
  • - {{series.info.alias}} -
  • - -
  • -
  • -
-
-
+ + +
+ + +
+
+ From 74e03092411eb70c5219637c066fdb457455a982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 2 Nov 2014 11:56:51 +0100 Subject: [PATCH 068/125] Fixes #912, URL parameters are lost when switching to fullscreen/edit mode --- .../services/dashboard/dashboardViewStateSrv.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/app/services/dashboard/dashboardViewStateSrv.js b/src/app/services/dashboard/dashboardViewStateSrv.js index 1ff95e8d4d3..6eafcc8dd66 100644 --- a/src/app/services/dashboard/dashboardViewStateSrv.js +++ b/src/app/services/dashboard/dashboardViewStateSrv.js @@ -47,19 +47,12 @@ function (angular, _, $) { }; DashboardViewState.prototype.getQueryStringState = function() { - var queryParams = $location.search(); - var urlState = { - panelId: parseInt(queryParams.panelId) || null, - fullscreen: queryParams.fullscreen ? true : false, - edit: queryParams.edit ? true : false, - }; + var state = $location.search(); + state.panelId = parseInt(state.panelId) || null; + state.fullscreen = state.fullscreen ? true : false; + state.edit = state.edit ? true : false; - _.each(queryParams, function(value, key) { - if (key.indexOf('var-') !== 0) { return; } - urlState[key] = value; - }); - - return urlState; + return state; }; DashboardViewState.prototype.serializeToUrl = function() { From 10ea140358af04c5b018e27a8169898fb418c9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 3 Nov 2014 08:56:13 +0100 Subject: [PATCH 069/125] MetricEditors: Ability to reorder metric queries in metrics tag, Closes #716, Closes #856 --- ' | 84 ------------------------- CHANGELOG.md | 1 + src/app/controllers/graphiteTarget.js | 4 ++ src/app/controllers/influxTargetCtrl.js | 9 ++- src/app/partials/graphite/editor.html | 13 ++++ src/app/partials/influxdb/editor.html | 38 +++++------ 6 files changed, 44 insertions(+), 105 deletions(-) delete mode 100644 ' diff --git a/' b/' deleted file mode 100644 index 86791d43fde..00000000000 --- a/' +++ /dev/null @@ -1,84 +0,0 @@ -/* global _ */ - -/* - * Complex scripted dashboard - * This script generates a dashboard object that Grafana can load. It also takes a number of user - * supplied URL parameters (int ARGS variable) - * - * Return a dashboard object, or a function - * - * For async scripts, return a function, this function must take a single callback function as argument, - * call this callback function with the dashboard object (look at scripted_async.js for an example) - */ - -'use strict'; - -// accessable variables in this scope -var window, document, ARGS, $, jQuery, moment, kbn, services, _; - -// default datasource -var datasource = services.datasourceSrv.default; -// get datasource used for saving dashboards -var dashboardDB = services.datasourceSrv.getGrafanaDB(); - -var targets = []; - -function getTargets(path) { - return datasource.metricFindQuery(path + '.*').then(function(result) { - if (!result) { - return null; - } - - if (targets.length === 10) { - return null; - } - - var promises = _.map(result, function(metric) { - if (metric.expandable) { - return getTargets(path + "." + metric.text); - } - else { - targets.push(path + '.' + metric.text); - } - return null; - }); - - return services.$q.when(promises); - }); -} - -function createDashboard(target, index) { - // Intialize a skeleton with nothing but a rows array and service object - var dashboard = { rows : [] }; - dashboard.title = 'Scripted dash ' + index; - dashboard.time = { - from: "now-6h", - to: "now" - }; - - dashboard.rows.push({ - title: 'Chart', - height: '300px', - panels: [ - { - title: 'Events', - type: 'graph', - span: 12, - targets: [ {target: target} ] - } - ] - }); - -} - -return function(callback) { - - getTargets('apps').then(function(results) { - console.log('targets: ', targets); - _.each(targets, function(target, index) { - var dashboard = createDashboard(target); - }); - }); - -}; - diff --git a/CHANGELOG.md b/CHANGELOG.md index 24772741c14..b29e659c472 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory - [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts - [Issue #991](https://github.com/grafana/grafana/issues/991). ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example) +- [Issue #716](https://github.com/grafana/grafana/issues/716). MetricsEditors: Ability to reorder metric queries **OpenTSDB** - [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc diff --git a/src/app/controllers/graphiteTarget.js b/src/app/controllers/graphiteTarget.js index 3d6368a7793..3d899ac152f 100644 --- a/src/app/controllers/graphiteTarget.js +++ b/src/app/controllers/graphiteTarget.js @@ -275,6 +275,10 @@ function (angular, _, config, gfunc, Parser) { } }; + $scope.moveMetricQuery = function(fromIndex, toIndex) { + _.move($scope.panel.targets, fromIndex, toIndex); + }; + $scope.duplicate = function() { var clone = angular.copy($scope.target); $scope.panel.targets.push(clone); diff --git a/src/app/controllers/influxTargetCtrl.js b/src/app/controllers/influxTargetCtrl.js index 175bf268098..e2b1a5234d4 100644 --- a/src/app/controllers/influxTargetCtrl.js +++ b/src/app/controllers/influxTargetCtrl.js @@ -1,7 +1,8 @@ define([ - 'angular' + 'angular', + 'lodash' ], -function (angular) { +function (angular, _) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -96,6 +97,10 @@ function (angular) { } }; + $scope.moveMetricQuery = function(fromIndex, toIndex) { + _.move($scope.panel.targets, fromIndex, toIndex); + }; + $scope.duplicate = function() { var clone = angular.copy($scope.target); $scope.panel.targets.push(clone); diff --git a/src/app/partials/graphite/editor.html b/src/app/partials/graphite/editor.html index f54c830d46b..77ef46a7e39 100755 --- a/src/app/partials/graphite/editor.html +++ b/src/app/partials/graphite/editor.html @@ -30,6 +30,19 @@ ng-click="duplicate()"> Duplicate + +
  • + + Move up + +
  • +
  • + + Move down + +
  • diff --git a/src/app/partials/influxdb/editor.html b/src/app/partials/influxdb/editor.html index fdc14166b7e..7fe41c1d268 100644 --- a/src/app/partials/influxdb/editor.html +++ b/src/app/partials/influxdb/editor.html @@ -15,26 +15,26 @@ tabindex="1"> - -
  • -
  • - - - -
  • - + + +
  • + + + +
  • + -
      -
    • - - - + From ecdcd1061245559f62ad1e347c9f1f943619216c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 3 Nov 2014 10:21:12 +0100 Subject: [PATCH 070/125] Graph: Series hide/show toggle changed to be default exclusive, so clicking on a series name will show only that series. (SHIFT or meta)+click will toggle hide/show. Closes #1007 --- CHANGELOG.md | 4 +++- src/app/panels/graph/module.js | 14 +++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b29e659c472..e316f74ba8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,9 @@ - [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory - [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts - [Issue #991](https://github.com/grafana/grafana/issues/991). ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example) -- [Issue #716](https://github.com/grafana/grafana/issues/716). MetricsEditors: Ability to reorder metric queries + +**Changes** +- [Issue #1007](https://github.com/grafana/grafana/issues/1007). Graph: Series hide/show toggle changed to be default exclusive, so clicking on a series name will show only that series. (SHIFT or meta)+click will toggle hide/show. **OpenTSDB** - [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 87098a2d4d7..c24b61dffce 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -276,14 +276,14 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { }; $scope.toggleSeries = function(serie, event) { - if ($scope.hiddenSeries[serie.alias]) { - delete $scope.hiddenSeries[serie.alias]; - } - else { - $scope.hiddenSeries[serie.alias] = true; - } - if (event.ctrlKey || event.metaKey || event.shiftKey) { + if ($scope.hiddenSeries[serie.alias]) { + delete $scope.hiddenSeries[serie.alias]; + } + else { + $scope.hiddenSeries[serie.alias] = true; + } + } else { $scope.toggleSeriesExclusiveMode(serie); } From 2a962bf8fde735cf6ce8a1d0d3547272522bb2ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 6 Nov 2014 09:56:50 +0100 Subject: [PATCH 071/125] Singlestat: progress on singlestat panel --- src/app/panels/graph/module.js | 7 ------- src/app/panels/stats/module.js | 22 +++++++++++++--------- src/app/panels/stats/statsDirective.js | 26 +++++++++----------------- src/app/panels/stats/statsEditor.html | 17 ++++++++++++++--- src/app/services/panelSrv.js | 8 ++++++++ 5 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index fa7bed1cf65..ae50e372c7c 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -347,13 +347,6 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { $scope.render(); }; - $scope.toggleEditorHelp = function(index) { - if ($scope.editorHelpIndex === index) { - $scope.editorHelpIndex = null; - return; - } - $scope.editorHelpIndex = index; - }; panelSrv.init($scope); }); diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index d5fdc9e38ac..f85a1df62e5 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -40,7 +40,9 @@ function (angular, app, _, TimeSeries, kbn) { targets: [{}], cacheTimeout: null, format: 'none', - template: '{{avg}} !(avg)', + prefix: '', + postfix: '', + valueName: 'avg', thresholds: '', colorBackground: false, colorValue: false, @@ -103,7 +105,7 @@ function (angular, app, _, TimeSeries, kbn) { info: { alias: seriesData.target }, }); - series.data = series.getFlotPairs('connected'); + series.flotpairs = series.getFlotPairs('connected'); return series; }; @@ -128,15 +130,17 @@ function (angular, app, _, TimeSeries, kbn) { }; $scope.render = function() { - var i, series; - var data = { - series: $scope.series, - stats: [] - }; + var data = {}; - for (i = 0; i < data.series.length; i++) { - series = data.series[i]; + if (!$scope.series || $scope.series.length === 0) { + data.series = { mainValue: null, datapoints: [] }; + } + else { + var series = $scope.series[0]; series.updateLegendValues(kbn.valueFormats[$scope.panel.format], 2, -7); + data.mainValue = series.stats[$scope.panel.valueName]; + data.mainValueFormated = $scope.formatValue(data.mainValue); + data.flotpairs = series.flotpairs; } data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) { diff --git a/src/app/panels/stats/statsDirective.js b/src/app/panels/stats/statsDirective.js index 19d7e39b477..cc8b1c43e6a 100644 --- a/src/app/panels/stats/statsDirective.js +++ b/src/app/panels/stats/statsDirective.js @@ -18,15 +18,12 @@ function (angular, app, _, kbn, $) { return { link: function(scope, elem) { var data; - var valueRegex = /\{\{([a-zA-Z]+)\}\}/g; - var smallValueTextRegex = /!(\S+)/g; var $panelContainer = elem.parents('.panel-container'); scope.$on('render', function() { data = scope.data; - data.mainValue = null; - if (!data || data.series.length === 0) { + if (!data || data.flotpairs.length === 0) { elem.html('no data'); return; } @@ -73,15 +70,8 @@ function (angular, app, _, kbn, $) { return null; } - function valueTemplateReplaceFunc(match, statType) { - var stats = data.series[0].stats; - data.mainValue = stats[statType]; - var valueFormated = scope.formatValue(data.mainValue); - return applyColoringThresholds(data.mainValue, valueFormated); - } - - function smallValueTextReplaceFunc(match, text) { - return '' + text + ''; + function getBigValueHtml() { + return applyColoringThresholds(data.mainValue, data.mainValueFormated); } function addSparkline() { @@ -130,10 +120,13 @@ function (angular, app, _, kbn, $) { elem.append(plotCanvas); - data.series[0].color = panel.sparkline.lineColor; + var plotSeries = { + data: data.flotpairs, + color: panel.sparkline.lineColor + }; setTimeout(function() { - $.plot(plotCanvas, [data.series[0]], options); + $.plot(plotCanvas, [plotSeries], options); }, 10); } @@ -145,8 +138,7 @@ function (angular, app, _, kbn, $) { body += '
      '; body += ''; - var valueHtml = panel.template.replace(valueRegex, valueTemplateReplaceFunc); - body += valueHtml.replace(smallValueTextRegex, smallValueTextReplaceFunc); + body += getBigValueHtml(); body += '
      '; body += '
    '; diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/stats/statsEditor.html index 0406fecd7de..7ca1ab107af 100644 --- a/src/app/panels/stats/statsEditor.html +++ b/src/app/panels/stats/statsEditor.html @@ -1,9 +1,17 @@
    -
    Big values
    +
    Big value
    - - + + +
    +
    + + +
    +
    + +
    @@ -39,6 +47,9 @@
    +
    +
    +
    diff --git a/src/app/services/panelSrv.js b/src/app/services/panelSrv.js index 4563f3ad6c8..faa37b4e057 100644 --- a/src/app/services/panelSrv.js +++ b/src/app/services/panelSrv.js @@ -99,6 +99,14 @@ function (angular, _) { $scope.get_data(); }; + $scope.toggleEditorHelp = function(index) { + if ($scope.editorHelpIndex === index) { + $scope.editorHelpIndex = null; + return; + } + $scope.editorHelpIndex = index; + }; + $scope.toggleFullscreen = function(edit) { $scope.dashboardViewState.update({ fullscreen: true, edit: edit, panelId: $scope.panel.id }); }; From 7ff8931deffb140ff78400d80c94709727391b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 6 Nov 2014 10:47:46 +0100 Subject: [PATCH 072/125] SingleStatPanel: added font size options for value, prefix and postfix, #951 --- src/app/directives/spectrumPicker.js | 6 ++++- src/app/panels/graph/module.js | 1 - src/app/panels/stats/module.js | 3 +++ src/app/panels/stats/statsDirective.js | 35 +++++++++++++++++--------- src/app/panels/stats/statsEditor.html | 21 ++++++++++++++-- src/css/less/stats-panel.less | 11 ++------ 6 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/app/directives/spectrumPicker.js b/src/app/directives/spectrumPicker.js index daec6b043aa..c6d3de6f884 100644 --- a/src/app/directives/spectrumPicker.js +++ b/src/app/directives/spectrumPicker.js @@ -32,7 +32,11 @@ function (angular) { }; input.spectrum(options); + + scope.$on('$destroy', function() { + input.spectrum('destroy'); + }); } }; }); -}); \ No newline at end of file +}); diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index ae50e372c7c..9afb77fd947 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -347,7 +347,6 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { $scope.render(); }; - panelSrv.init($scope); }); diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index f85a1df62e5..1705fd0b9bd 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -43,6 +43,9 @@ function (angular, app, _, TimeSeries, kbn) { prefix: '', postfix: '', valueName: 'avg', + prefixFontSize: '50%', + valueFontSize: '100%', + postfixFontSize: '50%', thresholds: '', colorBackground: false, colorValue: false, diff --git a/src/app/panels/stats/statsDirective.js b/src/app/panels/stats/statsDirective.js index cc8b1c43e6a..7049471b999 100644 --- a/src/app/panels/stats/statsDirective.js +++ b/src/app/panels/stats/statsDirective.js @@ -17,11 +17,12 @@ function (angular, app, _, kbn, $) { return { link: function(scope, elem) { - var data; + var data, panel; var $panelContainer = elem.parents('.panel-container'); scope.$on('render', function() { data = scope.data; + panel = scope.panel; if (!data || data.flotpairs.length === 0) { elem.html('no data'); @@ -33,12 +34,12 @@ function (angular, app, _, kbn, $) { function setElementHeight() { try { - var height = scope.height || scope.panel.height || scope.row.height; + var height = scope.height || panel.height || scope.row.height; if (_.isString(height)) { height = parseInt(height.replace('px', ''), 10); } - height -= scope.panel.title ? 24 : 9; // subtract panel title bar + height -= panel.title ? 24 : 9; // subtract panel title bar elem.css('height', height + 'px'); @@ -49,7 +50,7 @@ function (angular, app, _, kbn, $) { } function applyColoringThresholds(value, valueString) { - if (!scope.panel.colorValue) { + if (!panel.colorValue) { return valueString; } @@ -70,8 +71,24 @@ function (angular, app, _, kbn, $) { return null; } + function getSpan(className, fontSize, value) { + return '' + + value + ''; + } + function getBigValueHtml() { - return applyColoringThresholds(data.mainValue, data.mainValueFormated); + var body = '
    '; + + if (panel.prefix) { body += getSpan('stats-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } + + var value = applyColoringThresholds(data.mainValue, data.mainValueFormated); + body += getSpan('stats-panel-value', panel.valueFontSize, value); + + if (panel.postfix) { body += getSpan('stats-panel-postfix', panel.postfixFontSize, panel.postfix); } + + body += '
    '; + + return body; } function addSparkline() { @@ -134,13 +151,7 @@ function (angular, app, _, kbn, $) { setElementHeight(); var panel = scope.panel; - var body = ''; - - body += '
    '; - body += ''; - body += getBigValueHtml(); - body += '
    '; - body += '
    '; + var body = getBigValueHtml(); if (panel.colorBackground && data.mainValue) { var color = getColorForValue(data.mainValue); diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/stats/statsEditor.html index 7ca1ab107af..dcb5a837b22 100644 --- a/src/app/panels/stats/statsEditor.html +++ b/src/app/panels/stats/statsEditor.html @@ -3,7 +3,7 @@
    Big value
    - +
    @@ -11,9 +11,26 @@
    - +
    + +
    +
    Big value font size
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    Formats
    diff --git a/src/css/less/stats-panel.less b/src/css/less/stats-panel.less index 29e162c77ea..1028da75e18 100644 --- a/src/css/less/stats-panel.less +++ b/src/css/less/stats-panel.less @@ -11,19 +11,12 @@ text-align: center; position: relative; z-index: 1; -} - -.stats-panel-value { font-size: 3em; font-weight: bold; } -.stats-panel-value:not(:first-child) { - padding-left: 20px; -} - -.stats-panel-value-small { - font-size: 50%; +.stats-panel-prefix { + padding-right: 20px; } .stats-panel-table { From 37176fa42d978acb3ca530b8b79f0cdf697dfcb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 6 Nov 2014 12:17:46 +0100 Subject: [PATCH 073/125] SingleStatPanel: Finnaly solved automatic decimal precision calculation for singlestat panel, #951 --- src/app/components/kbn.js | 8 +++--- src/app/panels/stats/module.js | 47 +++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index 567e7c78f23..071b60561d2 100644 --- a/src/app/components/kbn.js +++ b/src/app/components/kbn.js @@ -327,18 +327,20 @@ function($, _, moment) { size /= factor; } if (steps > 0) { - decimals = scaledDecimals + (3 * steps); + scaledDecimals = scaledDecimals + (3 * steps); } - return kbn.toFixed(size, decimals) + extArray[steps]; + return kbn.toFixed(size, scaledDecimals, decimals) + extArray[steps]; }; }; - kbn.toFixed = function(value, decimals) { + kbn.toFixed = function(value, decimals, fallbackDecimals) { if (value === null) { return ""; } + decimals = decimals || fallbackDecimals; + var factor = decimals ? Math.pow(10, decimals) : 1; var formatted = String(Math.round(value * factor) / factor); diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index 1705fd0b9bd..31ebf0aeba6 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -65,10 +65,6 @@ function (angular, app, _, TimeSeries, kbn) { $scope.$on('refresh', $scope.get_data); }; - $scope.formatValue = function(value) { - return kbn.valueFormats[$scope.panel.format](value, 0, -7); - }; - $scope.updateTimeRange = function () { $scope.range = timeSrv.timeRange(); $scope.rangeUnparsed = timeSrv.timeRange(false); @@ -132,6 +128,43 @@ function (angular, app, _, TimeSeries, kbn) { $scope.render(); }; + $scope.getDecimalsForValue = function(value) { + var opts = {}; + + var delta = value / 2; + var dec = -Math.floor(Math.log(delta) / Math.LN10); + + var magn = Math.pow(10, -dec), + norm = delta / magn, // norm is between 1.0 and 10.0 + size; + + if (norm < 1.5) { + size = 1; + } else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25) { + size = 2.5; + ++dec; + } + } else if (norm < 7.5) { + size = 5; + } else { + size = 10; + } + + size *= magn; + + if (opts.minTickSize != null && size < opts.minTickSize) { + size = opts.minTickSize; + } + + var result = {}; + result.decimals = Math.max(0, dec); + result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10); + return result; + }; + $scope.render = function() { var data = {}; @@ -140,9 +173,11 @@ function (angular, app, _, TimeSeries, kbn) { } else { var series = $scope.series[0]; - series.updateLegendValues(kbn.valueFormats[$scope.panel.format], 2, -7); data.mainValue = series.stats[$scope.panel.valueName]; - data.mainValueFormated = $scope.formatValue(data.mainValue); + var decimalInfo = $scope.getDecimalsForValue(data.mainValue); + var formatFunc = kbn.valueFormats[$scope.panel.format]; + + data.mainValueFormated = formatFunc(data.mainValue, decimalInfo.decimals, decimalInfo.scaledDecimals); data.flotpairs = series.flotpairs; } From fa813024ca1f241880114f7b052da79abe74216c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 6 Nov 2014 12:30:42 +0100 Subject: [PATCH 074/125] SingleStatPanel: various fixes --- src/app/panels/stats/module.js | 4 +++- src/app/panels/stats/statsDirective.js | 12 +++--------- src/app/services/dashboard/dashboardSrv.js | 5 +++++ src/app/services/dashboard/dashboardViewStateSrv.js | 4 +++- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index 31ebf0aeba6..a98a636f370 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -169,7 +169,9 @@ function (angular, app, _, TimeSeries, kbn) { var data = {}; if (!$scope.series || $scope.series.length === 0) { - data.series = { mainValue: null, datapoints: [] }; + data.flotpairs = []; + data.mainValue = Number.NaN; + data.mainValueFormated = 'NaN'; } else { var series = $scope.series[0]; diff --git a/src/app/panels/stats/statsDirective.js b/src/app/panels/stats/statsDirective.js index 7049471b999..27cc61456b1 100644 --- a/src/app/panels/stats/statsDirective.js +++ b/src/app/panels/stats/statsDirective.js @@ -21,14 +21,6 @@ function (angular, app, _, kbn, $) { var $panelContainer = elem.parents('.panel-container'); scope.$on('render', function() { - data = scope.data; - panel = scope.panel; - - if (!data || data.flotpairs.length === 0) { - elem.html('no data'); - return; - } - render(); }); @@ -148,9 +140,11 @@ function (angular, app, _, kbn, $) { } function render() { + data = scope.data; + panel = scope.panel; + setElementHeight(); - var panel = scope.panel; var body = getBigValueHtml(); if (panel.colorBackground && data.mainValue) { diff --git a/src/app/services/dashboard/dashboardSrv.js b/src/app/services/dashboard/dashboardSrv.js index 687a4931397..7ebe337b432 100644 --- a/src/app/services/dashboard/dashboardSrv.js +++ b/src/app/services/dashboard/dashboardSrv.js @@ -99,10 +99,15 @@ function (angular, $, kbn, _, moment) { result.panel = panel; result.row = row; result.index = index; + return; } }); }); + if (!result.panel) { + return null; + } + return result; }; diff --git a/src/app/services/dashboard/dashboardViewStateSrv.js b/src/app/services/dashboard/dashboardViewStateSrv.js index 6eafcc8dd66..1176ca9450b 100644 --- a/src/app/services/dashboard/dashboardViewStateSrv.js +++ b/src/app/services/dashboard/dashboardViewStateSrv.js @@ -39,7 +39,9 @@ function (angular, _, $) { if (!this.state.panelId) { return; } var panelInfo = this.$scope.dashboard.getPanelInfoById(this.state.panelId); - panelInfo.row.collapse = false; + if (panelInfo) { + panelInfo.row.collapse = false; + } }; DashboardViewState.prototype.needsSync = function(urlState) { From ee183d4574663c838857a72b8d4e7d9b07d78b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 6 Nov 2014 13:57:16 +0100 Subject: [PATCH 075/125] SingleStatPanel: some more tweaks and polish --- src/app/panels/stats/module.js | 2 +- src/app/panels/stats/statsEditor.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/panels/stats/module.js b/src/app/panels/stats/module.js index a98a636f370..778b55b284a 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/stats/module.js @@ -54,7 +54,7 @@ function (angular, app, _, TimeSeries, kbn) { show: false, full: false, lineColor: 'rgb(31, 120, 193)', - fillColor: 'rgb(31, 120, 193)', + fillColor: 'rgba(31, 118, 189, 0.18)', } }; diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/stats/statsEditor.html index dcb5a837b22..885b1f20077 100644 --- a/src/app/panels/stats/statsEditor.html +++ b/src/app/panels/stats/statsEditor.html @@ -43,8 +43,8 @@
    - - + +
    From f0b7099be38b4ce4165734d6e19509918a08ce7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 6 Nov 2014 14:07:32 +0100 Subject: [PATCH 076/125] SingleStatPanel: renamed panel to singlestat --- src/app/components/settings.js | 2 +- .../statsEditor.html => singlestat/editor.html} | 0 src/app/panels/{stats => singlestat}/module.html | 4 ++-- src/app/panels/{stats => singlestat}/module.js | 8 ++++---- .../singleStatPanel.js} | 12 ++++++------ src/css/less/grafana.less | 2 +- src/css/less/{stats-panel.less => singlestat.less} | 8 ++++---- 7 files changed, 18 insertions(+), 18 deletions(-) rename src/app/panels/{stats/statsEditor.html => singlestat/editor.html} (100%) rename src/app/panels/{stats => singlestat}/module.html (86%) rename src/app/panels/{stats => singlestat}/module.js (96%) rename src/app/panels/{stats/statsDirective.js => singlestat/singleStatPanel.js} (89%) rename src/css/less/{stats-panel.less => singlestat.less} (85%) diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 38313aeb198..532b2e2b133 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -17,7 +17,7 @@ function (_, crypto) { window_title_prefix : 'Grafana - ', panels : { 'graph': { path: 'panels/graph' }, - 'stats': { path: 'panels/stats' }, + 'singlestat': { path: 'panels/singlestat' }, 'text': { path: 'panels/text' } }, plugins : {}, diff --git a/src/app/panels/stats/statsEditor.html b/src/app/panels/singlestat/editor.html similarity index 100% rename from src/app/panels/stats/statsEditor.html rename to src/app/panels/singlestat/editor.html diff --git a/src/app/panels/stats/module.html b/src/app/panels/singlestat/module.html similarity index 86% rename from src/app/panels/stats/module.html rename to src/app/panels/singlestat/module.html index 353cdd29c5a..b27d9917353 100644 --- a/src/app/panels/stats/module.html +++ b/src/app/panels/singlestat/module.html @@ -1,6 +1,6 @@ -
    +
    -
    +
    diff --git a/src/app/panels/stats/module.js b/src/app/panels/singlestat/module.js similarity index 96% rename from src/app/panels/stats/module.js rename to src/app/panels/singlestat/module.js index 778b55b284a..1dba78d1ced 100644 --- a/src/app/panels/stats/module.js +++ b/src/app/panels/singlestat/module.js @@ -5,15 +5,15 @@ define([ 'components/timeSeries', 'kbn', 'services/panelSrv', - './statsDirective', + './singleStatPanel', ], function (angular, app, _, TimeSeries, kbn) { 'use strict'; - var module = angular.module('grafana.panels.stats'); + var module = angular.module('grafana.panels.singlestat'); app.useModule(module); - module.controller('StatsCtrl', function($scope, panelSrv, timeSrv) { + module.controller('SingleStatCtrl', function($scope, panelSrv, timeSrv) { $scope.panelMeta = { titlePos: 'left', @@ -29,7 +29,7 @@ function (angular, app, _, TimeSeries, kbn) { }, { title: 'Options', - src:'app/panels/stats/statsEditor.html' + src:'app/panels/singlestat/editor.html' } ], fullscreenEdit: true, diff --git a/src/app/panels/stats/statsDirective.js b/src/app/panels/singlestat/singleStatPanel.js similarity index 89% rename from src/app/panels/stats/statsDirective.js rename to src/app/panels/singlestat/singleStatPanel.js index 27cc61456b1..74823ae83e5 100644 --- a/src/app/panels/stats/statsDirective.js +++ b/src/app/panels/singlestat/singleStatPanel.js @@ -10,10 +10,10 @@ define([ function (angular, app, _, kbn, $) { 'use strict'; - var module = angular.module('grafana.panels.stats', []); + var module = angular.module('grafana.panels.singlestat', []); app.useModule(module); - module.directive('statsPanel', function() { + module.directive('singlestatPanel', function() { return { link: function(scope, elem) { @@ -69,14 +69,14 @@ function (angular, app, _, kbn, $) { } function getBigValueHtml() { - var body = '
    '; + var body = '
    '; - if (panel.prefix) { body += getSpan('stats-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } + if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } var value = applyColoringThresholds(data.mainValue, data.mainValueFormated); - body += getSpan('stats-panel-value', panel.valueFontSize, value); + body += getSpan('singlestat-panel-value', panel.valueFontSize, value); - if (panel.postfix) { body += getSpan('stats-panel-postfix', panel.postfixFontSize, panel.postfix); } + if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); } body += '
    '; diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index 7b534bcce40..f24d2e21d87 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -6,7 +6,7 @@ @import "search.less"; @import "panel.less"; @import "forms.less"; -@import "stats-panel.less"; +@import "singlestat.less"; .row-control-inner { padding:0px; diff --git a/src/css/less/stats-panel.less b/src/css/less/singlestat.less similarity index 85% rename from src/css/less/stats-panel.less rename to src/css/less/singlestat.less index 1028da75e18..368be2b3538 100644 --- a/src/css/less/stats-panel.less +++ b/src/css/less/singlestat.less @@ -1,10 +1,10 @@ -.stats-panel { +.singlestat-panel { position: relative; display: table; width: 100%; } -.stats-panel-value-container { +.singlestat-panel-value-container { padding: 20px; display: table-cell; vertical-align: middle; @@ -15,11 +15,11 @@ font-weight: bold; } -.stats-panel-prefix { +.singlestat-panel-prefix { padding-right: 20px; } -.stats-panel-table { +.singlestat-panel-table { width: 100%; td { padding: 5px 10px; From c0539e483ecaef2a752357d5082f4422b826757c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 6 Nov 2014 14:10:25 +0100 Subject: [PATCH 077/125] Added singlestat panel to changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e316f74ba8c..640e974e779 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ - [Issue #850](https://github.com/grafana/grafana/issues/850). Graph: Shared tooltip that shows multiple series & crosshair line, thx @toni-moreno - [Issue #940](https://github.com/grafana/grafana/issues/940). Graph: New series style override option "Fill below to", useful to visualize max & min as a shadow for the mean +**New Panels** +- [Issue #951](https://github.com/grafana/grafana/issues/951). SingleStat: New singlestat panel + **Misc** - [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory - [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts From cc8961360aeaaf14b8c906a16c271c283498787f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 6 Nov 2014 16:25:46 +0100 Subject: [PATCH 078/125] Graphite: added maxDataPoints override option and a help section that describes how graphite point consolidation work, Closes #5 --- src/app/panels/graph/module.js | 7 +++- src/app/panels/singlestat/module.js | 8 +++-- src/app/partials/graphite/editor.html | 47 ++++++++++++++++++++++----- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 9afb77fd947..86546da2d1d 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -178,7 +178,12 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { $scope.updateTimeRange = function () { $scope.range = timeSrv.timeRange(); $scope.rangeUnparsed = timeSrv.timeRange(false); - $scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12)); + if ($scope.panel.maxDataPoints) { + $scope.resolution = $scope.panel.maxDataPoints; + } + else { + $scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12)); + } $scope.interval = kbn.calculateInterval($scope.range, $scope.resolution, $scope.panel.interval); }; diff --git a/src/app/panels/singlestat/module.js b/src/app/panels/singlestat/module.js index 1dba78d1ced..8c6a77defa8 100644 --- a/src/app/panels/singlestat/module.js +++ b/src/app/panels/singlestat/module.js @@ -37,6 +37,8 @@ function (angular, app, _, TimeSeries, kbn) { // Set and populate defaults var _d = { + maxDataPoints: 100, + interval: null, targets: [{}], cacheTimeout: null, format: 'none', @@ -68,6 +70,8 @@ function (angular, app, _, TimeSeries, kbn) { $scope.updateTimeRange = function () { $scope.range = timeSrv.timeRange(); $scope.rangeUnparsed = timeSrv.timeRange(false); + $scope.resolution = $scope.panel.maxDataPoints; + $scope.interval = kbn.calculateInterval($scope.range, $scope.resolution, $scope.panel.interval); }; $scope.get_data = function() { @@ -75,9 +79,9 @@ function (angular, app, _, TimeSeries, kbn) { var metricsQuery = { range: $scope.rangeUnparsed, - interval: '1min', + interval: $scope.interval, targets: $scope.panel.targets, - maxDataPoints: 100, + maxDataPoints: $scope.resolution, cacheTimeout: $scope.panel.cacheTimeout }; diff --git a/src/app/partials/graphite/editor.html b/src/app/partials/graphite/editor.html index 77ef46a7e39..b904331e77c 100755 --- a/src/app/partials/graphite/editor.html +++ b/src/app/partials/graphite/editor.html @@ -96,16 +96,29 @@
  • - cacheTimeout + Cache timeout
  • + class="input-mini grafana-target-segment-input" + ng-model="panel.cacheTimeout" + bs-tooltip="'Graphite parameter to overwride memcache default timeout (unit is seconds)'" + data-placement="right" + spellcheck='false' + placeholder="60"> +
  • +
  • + Max data points +
  • +
  • +
  • @@ -135,6 +148,11 @@ templating +
  • + + max data points + +
  • @@ -190,7 +208,18 @@
    +
    +
    Max data points
    +
      +
    • Every graphite request is issued with a maxDataPoints parameter
    • +
    • Graphite uses this parameter to consolidate the real number of values down to this number
    • +
    • If there are more real values, then by default they will be consolidated using averages
    • +
    • This could hide real peaks and max values in your series
    • +
    • You can change how point consolidation is made using the consolidateBy graphite function
    • +
    • Point consolidation will effect series legend values (min,max,total,current)
    • +
    • If you override maxDataPoint and set a high value performance can be severely effected
    • +
    +
    +
    - - From d150bc1e52b60e7751a68e405d2bb6169d99442f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Nov 2014 06:34:47 +0100 Subject: [PATCH 079/125] Graphite: added mapSeries and reduceSeries functions, Closes #1018 --- src/app/services/graphite/gfunc.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/app/services/graphite/gfunc.js b/src/app/services/graphite/gfunc.js index 81bf9713e79..b875eec9719 100644 --- a/src/app/services/graphite/gfunc.js +++ b/src/app/services/graphite/gfunc.js @@ -100,6 +100,27 @@ function (_) { category: categories.Combine, }); + addFuncDef({ + name: 'mapSeries', + shortName: 'map', + params: [{ name: "node", type: 'int' }], + defaultParams: [3], + category: categories.Combine, + }); + + addFuncDef({ + name: 'reduceSeries', + shortName: 'reduce', + params: [ + { name: "function", type: 'string', options: ['asPercent', 'diffSeries', 'divideSeries'] }, + { name: "reduceNode", type: 'int', options: [0,1,2,3,4,5,6,7,8,9,10,11,12,13] }, + { name: "reduceMatchers", type: 'string' }, + { name: "reduceMatchers", type: 'string' }, + ], + defaultParams: ['asPercent', 2, 'used_bytes', 'total_bytes'], + category: categories.Combine, + }); + addFuncDef({ name: 'sumSeries', shortName: 'sum', From e530e4d4bc3368148ae19b984fccfc966d82a4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Nov 2014 12:14:46 +0100 Subject: [PATCH 080/125] Tech: rewrite of how the legend is implement, performance increase, and will make future legend enhancements easier --- src/app/directives/grafanaGraph.js | 4 - src/app/panels/graph/legend.html | 50 ------------ src/app/panels/graph/legend.js | 98 ++++++++++++++++++++++++ src/app/panels/graph/legend.popover.html | 27 +++++++ src/app/panels/graph/module.html | 27 +++---- src/app/panels/graph/module.js | 6 +- src/app/services/all.js | 1 + src/app/services/popoverSrv.js | 45 +++++++++++ src/css/less/graph.less | 3 - 9 files changed, 186 insertions(+), 75 deletions(-) delete mode 100755 src/app/panels/graph/legend.html create mode 100644 src/app/panels/graph/legend.js create mode 100644 src/app/panels/graph/legend.popover.html create mode 100644 src/app/services/popoverSrv.js diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 374d5e34eab..fb506e59b11 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -46,10 +46,6 @@ function (angular, $, kbn, moment, _, GraphTooltip) { scope.get_data(); }); - scope.$on('toggleLegend', function() { - render_panel(); - }); - // Receive render events scope.$on('render',function(event, renderData) { data = renderData || data; diff --git a/src/app/panels/graph/legend.html b/src/app/panels/graph/legend.html deleted file mode 100755 index 42fbda69de8..00000000000 --- a/src/app/panels/graph/legend.html +++ /dev/null @@ -1,50 +0,0 @@ -
    - -
    -
    - -
    - -
    -
    -
    -
    -
    -
    - -
    - - - diff --git a/src/app/panels/graph/legend.js b/src/app/panels/graph/legend.js new file mode 100644 index 00000000000..3632ee0deb1 --- /dev/null +++ b/src/app/panels/graph/legend.js @@ -0,0 +1,98 @@ +define([ + 'angular', + 'app', + 'lodash', + 'kbn', + 'jquery', + 'jquery.flot', + 'jquery.flot.time', +], +function (angular, app, _, kbn, $) { + 'use strict'; + + var module = angular.module('grafana.panels.graph'); + + module.directive('graphLegend', function(popoverSrv) { + + return { + link: function(scope, elem) { + var $container = $('
    '); + var firstRender = true; + var panel = scope.panel; + var data; + var i; + + scope.$on('render', function(event, renderData) { + data = renderData || data; + if (data) { + render(); + } + }); + + function getSeriesIndexForElement(el) { + return el.parents('[data-series-index]').data('series-index'); + } + + function openColorSelector(e) { + var el = $(e.currentTarget); + var index = getSeriesIndexForElement(el); + var seriesInfo = data[index].info; + var popoverScope = scope.$new(); + popoverScope.series = seriesInfo; + popoverSrv.show({ + element: $(':first-child', el), + templateUrl: 'app/panels/graph/legend.popover.html', + scope: popoverScope + }); + } + + function toggleSeries(e) { + var el = $(e.currentTarget); + var index = getSeriesIndexForElement(el); + var seriesInfo = data[index].info; + scope.toggleSeries(seriesInfo, e); + } + + function render() { + if (firstRender) { + elem.append($container); + $container.on('click', '.graph-legend-icon', openColorSelector); + $container.on('click', '.graph-legend-alias', toggleSeries); + firstRender = false; + } + + $container.empty(); + + $container.toggleClass('graph-legend-table', panel.legend.alignAsTable); + + for (i = 0; i < data.length; i++) { + var series = data[i]; + var html = '
    '; + html += '
    '; + html += ''; + html += '
    '; + + html += '
    '; + html += '' + series.label + ''; + html += '
    '; + + if (panel.legend.values) { + if (panel.legend.min) { html += '
    ' + series.info.min + '
    '; } + if (panel.legend.max) { html += '
    ' + series.info.max + '
    '; } + if (panel.legend.avg) { html += '
    ' + series.info.avg + '
    '; } + if (panel.legend.current) { html += '
    ' + series.info.current + '
    '; } + if (panel.legend.total) { html += '
    ' + series.info.total + '
    '; } + } + + html += '
    '; + $container.append($(html)); + } + } + } + }; + }); + +}); diff --git a/src/app/panels/graph/legend.popover.html b/src/app/panels/graph/legend.popover.html new file mode 100644 index 00000000000..ea029429b1c --- /dev/null +++ b/src/app/panels/graph/legend.popover.html @@ -0,0 +1,27 @@ +
    + × + +
    + + + +
    + +
    + + +
    +
    + diff --git a/src/app/panels/graph/module.html b/src/app/panels/graph/module.html index 1f185049800..77aa8a038f9 100644 --- a/src/app/panels/graph/module.html +++ b/src/app/panels/graph/module.html @@ -1,25 +1,22 @@
    -
    -
    +
    +
    -
    - No datapoints Can be caused by timezone mismatch between browser and graphite server - Datapoints outside time range Can be caused by timezone mismatch between browser and graphite server -
    +
    + No datapoints Can be caused by timezone mismatch between browser and graphite server + Datapoints outside time range Can be caused by timezone mismatch between browser and graphite server +
    -
    -
    +
    +
    -
    +
    -
    -
    -
    +
    +
    -
    +
    diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 86546da2d1d..0da63bef1d0 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -7,6 +7,7 @@ define([ 'moment', 'components/timeSeries', './seriesOverridesCtrl', + './legend', 'services/panelSrv', 'services/annotationsSrv', 'services/datasourceSrv', @@ -23,7 +24,6 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { 'use strict'; var module = angular.module('grafana.panels.graph'); - app.useModule(module); module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv) { @@ -270,7 +270,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { }; $scope.render = function(data) { - $scope.$emit('render', data); + $scope.$broadcast('render', data); }; $scope.changeSeriesColor = function(series, color) { @@ -291,7 +291,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { $scope.toggleSeriesExclusiveMode(serie); } - $scope.$emit('toggleLegend', $scope.legend); + $scope.render(); }; $scope.toggleSeriesExclusiveMode = function(serie) { diff --git a/src/app/services/all.js b/src/app/services/all.js index 71fafd222c2..efb8c79fb15 100644 --- a/src/app/services/all.js +++ b/src/app/services/all.js @@ -9,6 +9,7 @@ define([ './timer', './keyboardManager', './annotationsSrv', + './popoverSrv', './playlistSrv', './unsavedChangesSrv', './dashboard/dashboardKeyBindings', diff --git a/src/app/services/popoverSrv.js b/src/app/services/popoverSrv.js new file mode 100644 index 00000000000..2bd6df0021a --- /dev/null +++ b/src/app/services/popoverSrv.js @@ -0,0 +1,45 @@ +define([ + 'angular', +], +function (angular) { + 'use strict'; + + var module = angular.module('grafana.services'); + + module.service('popoverSrv', function($templateCache, $timeout, $q, $http, $compile) { + + this.getTemplate = function(url) { + return $q.when($templateCache.get(url) || $http.get(url, {cache: true})); + }; + + this.show = function(options) { + var popover = options.element.data('popover'); + if (popover) { + popover.scope.$destroy(); + popover.destroy(); + return; + } + + this.getTemplate(options.templateUrl).then(function(result) { + var template = result.data; + + options.element.popover({ + content: template, + placement: 'bottom', + html: true + }); + + popover = options.element.data('popover'); + popover.hasContent = function () { + return template; + }; + + popover.toggle(); + popover.scope = options.scope; + $compile(popover.$tip)(popover.scope); + }); + }; + + }); + +}); diff --git a/src/css/less/graph.less b/src/css/less/graph.less index 0da3014410f..196b5e88c68 100644 --- a/src/css/less/graph.less +++ b/src/css/less/graph.less @@ -60,9 +60,6 @@ padding-left: 0; &.pull-right { float: none; - .graph-legend-alias::after { - content: 'y\00B2'; - } } } From 6861dc137f92620683dc75eaffcfe98ae2979f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Nov 2014 13:39:47 +0100 Subject: [PATCH 081/125] Graph: fix for legend show/hide toggle, broken by recent legend rewrite --- src/app/components/timeSeries.js | 2 ++ src/app/panels/graph/axisEditor.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index 4422e744d45..6a4e03ae80b 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -70,6 +70,7 @@ function (_, kbn) { this.stats.min = Number.MAX_VALUE; this.stats.avg = null; this.stats.current = null; + this.allIsNull = true; var ignoreNulls = fillStyle === 'connected'; var nullAsZero = fillStyle === 'null as zero'; @@ -89,6 +90,7 @@ function (_, kbn) { if (_.isNumber(currentValue)) { this.stats.total += currentValue; + this.allIsNull = false; } if (currentValue > this.stats.max) { diff --git a/src/app/panels/graph/axisEditor.html b/src/app/panels/graph/axisEditor.html index 3aec233f6be..1c25fb26aab 100644 --- a/src/app/panels/graph/axisEditor.html +++ b/src/app/panels/graph/axisEditor.html @@ -40,7 +40,7 @@
    Legend styles
    - + From e16a51ad06d242d4a07eabd2c7c3220977eefa47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Nov 2014 13:54:19 +0100 Subject: [PATCH 082/125] Singlestat: fixed decimal precision bug, #951 --- src/app/components/kbn.js | 8 +++----- src/app/panels/singlestat/module.js | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index 071b60561d2..567e7c78f23 100644 --- a/src/app/components/kbn.js +++ b/src/app/components/kbn.js @@ -327,20 +327,18 @@ function($, _, moment) { size /= factor; } if (steps > 0) { - scaledDecimals = scaledDecimals + (3 * steps); + decimals = scaledDecimals + (3 * steps); } - return kbn.toFixed(size, scaledDecimals, decimals) + extArray[steps]; + return kbn.toFixed(size, decimals) + extArray[steps]; }; }; - kbn.toFixed = function(value, decimals, fallbackDecimals) { + kbn.toFixed = function(value, decimals) { if (value === null) { return ""; } - decimals = decimals || fallbackDecimals; - var factor = decimals ? Math.pow(10, decimals) : 1; var formatted = String(Math.round(value * factor) / factor); diff --git a/src/app/panels/singlestat/module.js b/src/app/panels/singlestat/module.js index 8c6a77defa8..24ac3ff9454 100644 --- a/src/app/panels/singlestat/module.js +++ b/src/app/panels/singlestat/module.js @@ -166,6 +166,8 @@ function (angular, app, _, TimeSeries, kbn) { var result = {}; result.decimals = Math.max(0, dec); result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10); + console.log(result); + return result; }; From 7ba4f6b93fa2eba566a8d57b952b947172dfb456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Nov 2014 13:56:28 +0100 Subject: [PATCH 083/125] Singlestat: removed debug console.log --- src/app/panels/singlestat/module.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/panels/singlestat/module.js b/src/app/panels/singlestat/module.js index 24ac3ff9454..87d81e6ed07 100644 --- a/src/app/panels/singlestat/module.js +++ b/src/app/panels/singlestat/module.js @@ -166,7 +166,6 @@ function (angular, app, _, TimeSeries, kbn) { var result = {}; result.decimals = Math.max(0, dec); result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10); - console.log(result); return result; }; From cd21fa70167898efb414732b453b107dcb46b67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Nov 2014 13:00:29 +0100 Subject: [PATCH 084/125] Graph: new legend table display style, #1030 --- src/app/panels/graph/axisEditor.html | 1 + src/app/panels/graph/legend.js | 14 +++++++++ src/css/less/graph.less | 46 ++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/src/app/panels/graph/axisEditor.html b/src/app/panels/graph/axisEditor.html index 1c25fb26aab..6470c12ed89 100644 --- a/src/app/panels/graph/axisEditor.html +++ b/src/app/panels/graph/axisEditor.html @@ -43,6 +43,7 @@ +
    diff --git a/src/app/panels/graph/legend.js b/src/app/panels/graph/legend.js index 3632ee0deb1..9a8b8e37a7e 100644 --- a/src/app/panels/graph/legend.js +++ b/src/app/panels/graph/legend.js @@ -65,6 +65,20 @@ function (angular, app, _, kbn, $) { $container.toggleClass('graph-legend-table', panel.legend.alignAsTable); + if (panel.legend.bigTableMode) { + $container.toggleClass('graph-legend-big-table', true); + var header = ''; + header += ''; + header += ''; + header += 'min'; + header += 'max'; + header += 'avg'; + header += 'current'; + header += 'total'; + header += ''; + $container.append($(header)); + } + for (i = 0; i < data.length; i++) { var series = data[i]; var html = '
    -
    diff --git a/src/app/panels/graph/legend.js b/src/app/panels/graph/legend.js index 9a8b8e37a7e..c01548afd9d 100644 --- a/src/app/panels/graph/legend.js +++ b/src/app/panels/graph/legend.js @@ -65,16 +65,17 @@ function (angular, app, _, kbn, $) { $container.toggleClass('graph-legend-table', panel.legend.alignAsTable); - if (panel.legend.bigTableMode) { - $container.toggleClass('graph-legend-big-table', true); + if (panel.legend.alignAsTable) { var header = ''; header += ''; header += ''; - header += 'min'; - header += 'max'; - header += 'avg'; - header += 'current'; - header += 'total'; + if (panel.legend.values) { + if (panel.legend.min) { header += 'min
    '; } + if (panel.legend.max) { header += 'max
    '; } + if (panel.legend.avg) { header += 'avg
    '; } + if (panel.legend.current) { header += 'current
    '; } + if (panel.legend.total) { header += 'total
    '; } + } header += ''; $container.append($(header)); } diff --git a/src/css/less/graph.less b/src/css/less/graph.less index bfcf2acf25c..438cb5d2379 100644 --- a/src/css/less/graph.less +++ b/src/css/less/graph.less @@ -53,6 +53,8 @@ .graph-legend-table { display: table; + width: 100%; + margin: 0; .graph-legend-series { display: table-row; @@ -63,73 +65,53 @@ } } - .graph-legend-alias { - float: none; - display: table-cell; - white-space: nowrap; - } - - .graph-legend-icon { - display: table-cell; - float: none; - white-space: nowrap; - padding: 0 4px; - top: 2px; - } - - .graph-legend-value { - float: none; - display: table-cell; - white-space: nowrap; - padding-left: 15px; - } -} - -.graph-legend-big-table { - width: 100%; - margin: 0; - td, .graph-legend-alias, .graph-legend-icon, .graph-legend-value { - padding: 2px 10px; + float: none; + display: table-cell; white-space: nowrap; + padding: 2px 10px; text-align: right; border-bottom: 1px solid @grafanaListBorderBottom; - - &:first-child { - text-align: left; - } - &:last-child td { - border: none; - } } .graph-legend-icon { width: 5px; - padding-right: 0; + padding: 0; + top: 0; + .icon-minus { + position: relative; + top: 2px; + } + } + + .graph-legend-value { + padding-left: 15px; } .graph-legend-alias { + padding-left: 7px; text-align: left; width: 95%; } + .graph-legend-series:nth-child(odd) { + background-color: @grafanaListAccent; + } + + .graph-legend-value { + &.current, &.max, &.min, &.total, &.avg { + &:before { + content: ''; + } + } + } + th { text-align: right; padding: 5px 10px; font-weight: bold; color: @blue } - - .graph-legend-series:nth-child(odd) { - background-color: @grafanaListAccent; - } - .graph-legend-value { - &.current, &.max, &.min, &.total, &.avg { - &:before { - content: ''; - } - } - } } .graph-legend-rightside { From 6aa0208316748f90502dcdf3e7d06d9ffd14a3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Nov 2014 14:23:18 +0100 Subject: [PATCH 086/125] Updated changelog with #1030 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 640e974e779..e1d540de7f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [Issue #877](https://github.com/grafana/grafana/issues/877). Graph: Smart auto decimal precision when using scaled unit formats - [Issue #850](https://github.com/grafana/grafana/issues/850). Graph: Shared tooltip that shows multiple series & crosshair line, thx @toni-moreno - [Issue #940](https://github.com/grafana/grafana/issues/940). Graph: New series style override option "Fill below to", useful to visualize max & min as a shadow for the mean +- [Issue #1030](https://github.com/grafana/grafana/issues/1030). Graph: Legend table display/look changed, now includes column headers for min/max/avg, and full width (unless on right side) **New Panels** - [Issue #951](https://github.com/grafana/grafana/issues/951). SingleStat: New singlestat panel From 834daeecd0db4576a765080cbd474282f543ebc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 8 Nov 2014 11:35:42 +0100 Subject: [PATCH 087/125] OpenTSDB: updated change log with templating support PR #917 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1d540de7f1..e12311e7e81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ **OpenTSDB** - [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc +- [Issue #917](https://github.com/grafana/grafana/issues/917). OpenTSDB: Templating support for OpenTSDB series name and tags **Fixes** - [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other) From 81b1939f92ccef2e3e88f938af81079b4be3a9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 8 Nov 2014 11:44:23 +0100 Subject: [PATCH 088/125] Graph: small fix to legend table mode --- CHANGELOG.md | 2 +- src/app/directives/panelMenu.js | 1 + src/app/panels/graph/axisEditor.html | 8 ++++---- src/app/panels/graph/legend.js | 2 +- src/app/services/dashboard/dashboardKeyBindings.js | 5 +++++ src/css/less/variables.dark.less | 2 +- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e12311e7e81..849e1a171bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ **OpenTSDB** - [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc -- [Issue #917](https://github.com/grafana/grafana/issues/917). OpenTSDB: Templating support for OpenTSDB series name and tags +- [Issue #917](https://github.com/grafana/grafana/issues/917). OpenTSDB: Templating support for OpenTSDB series name and tags, thx @mchataigner **Fixes** - [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other) diff --git a/src/app/directives/panelMenu.js b/src/app/directives/panelMenu.js index 2a68dd30865..18062d19af7 100644 --- a/src/app/directives/panelMenu.js +++ b/src/app/directives/panelMenu.js @@ -22,6 +22,7 @@ function (angular, $, _) { template += '
    '; template += '
    '; + template += ''; _.each($scope.panelMeta.menu, function(item) { template += '
    Legend styles
    - - - + + + -
    +
    Legend values
    diff --git a/src/app/panels/graph/legend.js b/src/app/panels/graph/legend.js index c01548afd9d..8c5ff52e44b 100644 --- a/src/app/panels/graph/legend.js +++ b/src/app/panels/graph/legend.js @@ -63,7 +63,7 @@ function (angular, app, _, kbn, $) { $container.empty(); - $container.toggleClass('graph-legend-table', panel.legend.alignAsTable); + $container.toggleClass('graph-legend-table', panel.legend.alignAsTable === true); if (panel.legend.alignAsTable) { var header = ''; diff --git a/src/app/services/dashboard/dashboardKeyBindings.js b/src/app/services/dashboard/dashboardKeyBindings.js index d5e0c1609c0..c723c85db19 100644 --- a/src/app/services/dashboard/dashboardKeyBindings.js +++ b/src/app/services/dashboard/dashboardKeyBindings.js @@ -50,6 +50,11 @@ function(angular, $) { scope.dashboard.emit_refresh('refresh'); }, { inputDisabled: true }); + keyboardManager.bind('ctrl+l', function() { + scope.dashboard.hideAllLegends = !scope.dashboard.hideAllLegends; + scope.dashboard.emit_refresh('refresh'); + }, { inputDisabled: true }); + keyboardManager.bind('ctrl+h', function() { var current = scope.dashboard.hideControls; scope.dashboard.hideControls = !current; diff --git a/src/css/less/variables.dark.less b/src/css/less/variables.dark.less index 42ab0bba861..d2acddb13ac 100644 --- a/src/css/less/variables.dark.less +++ b/src/css/less/variables.dark.less @@ -146,7 +146,7 @@ // Dropdowns // ------------------------- -@dropdownBackground: @heroUnitBackground; +@dropdownBackground: @grafanaTargetFuncBackground; @dropdownBorder: rgba(0,0,0,.2); @dropdownDividerTop: transparent; @dropdownDividerBottom: #222; From d12f4a4aee87e3c6a56b7b23ad398364e6565615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 8 Nov 2014 16:27:49 +0100 Subject: [PATCH 089/125] Panels: refactoring panel meta model & menu, will open up panel specific menu actions --- src/app/components/panelmeta.js | 45 +++++++ src/app/controllers/dashboardCtrl.js | 8 -- src/app/directives/panelMenu.js | 6 +- src/app/panels/graph/module.html | 4 +- src/app/panels/graph/module.js | 128 +++++-------------- src/app/panels/singlestat/module.html | 4 +- src/app/panels/singlestat/module.js | 28 ++-- src/app/panels/singlestat/singleStatPanel.js | 4 +- src/app/panels/text/module.js | 11 +- src/app/partials/paneleditor.html | 12 +- src/app/services/panelSrv.js | 39 +----- src/css/less/panel.less | 4 + src/css/less/variables.dark.less | 2 +- 13 files changed, 109 insertions(+), 186 deletions(-) create mode 100644 src/app/components/panelmeta.js diff --git a/src/app/components/panelmeta.js b/src/app/components/panelmeta.js new file mode 100644 index 00000000000..1039641f742 --- /dev/null +++ b/src/app/components/panelmeta.js @@ -0,0 +1,45 @@ +define([ +], +function () { + "use strict"; + + function PanelMeta(options) { + this.description = options.description; + this.titlePos = options.titlePos; + this.fullscreen = options.fullscreen; + this.menu = []; + this.editorTabs = []; + this.extendedMenu = []; + + if (options.fullscreen) { + this.addMenuItem('view', 'icon-eye-open', 'toggleFullscreen(false)'); + } + + this.addMenuItem('edit', 'icon-cog', 'editPanel()'); + this.addMenuItem('duplicate', 'icon-copy', 'duplicatePanel()'); + this.addMenuItem('share', 'icon-share', 'sharePanel()'); + + this.addEditorTab('General', 'app/partials/panelgeneral.html'); + + if (options.metricsEditor) { + this.addEditorTab('General', 'app/partials/metrics.html'); + } + + this.addExtendedMenuItem('Panel JSON', '', 'editPanelJson()'); + } + + PanelMeta.prototype.addMenuItem = function(text, icon, click) { + this.menu.push({text: text, icon: icon, click: click}); + }; + + PanelMeta.prototype.addExtendedMenuItem = function(text, icon, click) { + this.extendedMenu.push({text: text, icon: icon, click: click}); + }; + + PanelMeta.prototype.addEditorTab = function(title, src) { + this.editorTabs.push({title: title, src: src}); + }; + + return PanelMeta; + +}); diff --git a/src/app/controllers/dashboardCtrl.js b/src/app/controllers/dashboardCtrl.js index 294862d5fae..d69978dcdea 100644 --- a/src/app/controllers/dashboardCtrl.js +++ b/src/app/controllers/dashboardCtrl.js @@ -109,14 +109,6 @@ function (angular, $, config, _) { $scope.submenuEnabled = $scope.dashboard.templating.enable || $scope.dashboard.annotations.enable; }; - $scope.setEditorTabs = function(panelMeta) { - $scope.editorTabs = ['General','Panel']; - if(!_.isUndefined(panelMeta.editorTabs)) { - $scope.editorTabs = _.union($scope.editorTabs,_.pluck(panelMeta.editorTabs,'title')); - } - return $scope.editorTabs; - }; - $scope.onDrop = function(panelId, row, dropTarget) { var info = $scope.dashboard.getPanelInfoById(panelId); if (dropTarget) { diff --git a/src/app/directives/panelMenu.js b/src/app/directives/panelMenu.js index 18062d19af7..94d8d3cc070 100644 --- a/src/app/directives/panelMenu.js +++ b/src/app/directives/panelMenu.js @@ -22,7 +22,7 @@ function (angular, $, _) { template += '
    '; template += '
    '; - template += ''; + template += ''; _.each($scope.panelMeta.menu, function(item) { template += '
    -
    +
    -
    +
    diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 0da63bef1d0..3e7506cccb4 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -6,6 +6,7 @@ define([ 'kbn', 'moment', 'components/timeSeries', + 'components/panelmeta', './seriesOverridesCtrl', './legend', 'services/panelSrv', @@ -20,68 +21,35 @@ define([ 'jquery.flot.fillbelow', 'jquery.flot.crosshair' ], -function (angular, app, $, _, kbn, moment, TimeSeries) { +function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { 'use strict'; var module = angular.module('grafana.panels.graph'); module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv) { - $scope.panelMeta = { - editorTabs: [], - fullEditorTabs : [ - { - title: 'General', - src:'app/partials/panelgeneral.html' - }, - { - title: 'Metrics', - src:'app/partials/metrics.html' - }, - { - title:'Axes & Grid', - src:'app/panels/graph/axisEditor.html' - }, - { - title:'Display Styles', - src:'app/panels/graph/styleEditor.html' - } - ], - fullscreenEdit: true, - fullscreenView: true, - description : "Graphing" - }; + $scope.panelMeta = new PanelMeta({ + description: 'Graph panel', + fullscreen: true, + metricsEditor: true + }); + + $scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html'); + $scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html'); // Set and populate defaults var _d = { - + // datasource name, null = default datasource datasource: null, - - /** @scratch /panels/histogram/3 - * renderer:: sets client side (flot) or native graphite png renderer (png) - */ + // sets client side (flot) or native graphite png renderer (png) renderer: 'flot', - /** @scratch /panels/histogram/3 - * x-axis:: Show the x-axis - */ + // Show/hide the x-axis 'x-axis' : true, - /** @scratch /panels/histogram/3 - * y-axis:: Show the y-axis - */ + // Show/hide y-axis 'y-axis' : true, - /** @scratch /panels/histogram/3 - * scale:: Scale the y-axis by this factor - */ - scale : 1, - /** @scratch /panels/histogram/3 - * y_formats :: 'none','bytes','bits','bps','short', 's', 'ms' - */ + // y axis formats, [left axis,right axis] y_formats : ['short', 'short'], - /** @scratch /panels/histogram/5 - * grid object:: Min and max y-axis values - * grid.min::: Minimum y-axis value - * grid.ma1::: Maximum y-axis value - */ + // grid options grid : { leftMax: null, rightMax: null, @@ -92,48 +60,23 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { threshold1Color: 'rgba(216, 200, 27, 0.27)', threshold2Color: 'rgba(234, 112, 112, 0.22)' }, - - annotate : { - enable : false, - }, - - /** @scratch /panels/histogram/3 - * resolution:: If auto_int is true, shoot for this many bars. - */ - resolution : 100, - - /** @scratch /panels/histogram/3 - * ==== Drawing options - * lines:: Show line chart - */ + // show/hide lines lines : true, - /** @scratch /panels/histogram/3 - * fill:: Area fill factor for line charts, 1-10 - */ + // fill factor fill : 0, - /** @scratch /panels/histogram/3 - * linewidth:: Weight of lines in pixels - */ + // line width in pixels linewidth : 1, - /** @scratch /panels/histogram/3 - * points:: Show points on chart - */ + // show hide points points : false, - /** @scratch /panels/histogram/3 - * pointradius:: Size of points in pixels - */ + // point radius in pixels pointradius : 5, - /** @scratch /panels/histogram/3 - * bars:: Show bars on chart - */ + // show hide bars bars : false, - /** @scratch /panels/histogram/3 - * stack:: Stack multiple series - */ + // enable/disable stacking stack : false, - /** @scratch /panels/histogram/3 - * legend:: Display the legend - */ + // stack percentage mode + percentage : false, + // legend options legend: { show: true, // disable/enable legend values: false, // disable/enable legend values @@ -143,27 +86,20 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { total: false, avg: false }, - /** @scratch /panels/histogram/3 - * ==== Transformations - /** @scratch /panels/histogram/3 - * percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple - * queries - */ - percentage : false, - + // how null points should be handled nullPointMode : 'connected', - + // staircase line mode steppedLine: false, - + // tooltip options tooltip : { value_type: 'cumulative', shared: false, }, - + // metric queries targets: [{}], - + // series color overrides aliasColors: {}, - + // other style overrides seriesOverrides: [], }; diff --git a/src/app/panels/singlestat/module.html b/src/app/panels/singlestat/module.html index b27d9917353..e59fb82f303 100644 --- a/src/app/panels/singlestat/module.html +++ b/src/app/panels/singlestat/module.html @@ -12,13 +12,13 @@
    -
    +
    -
    +
    diff --git a/src/app/panels/singlestat/module.js b/src/app/panels/singlestat/module.js index 87d81e6ed07..1c1c2a3d0bb 100644 --- a/src/app/panels/singlestat/module.js +++ b/src/app/panels/singlestat/module.js @@ -4,10 +4,11 @@ define([ 'lodash', 'components/timeSeries', 'kbn', + 'components/panelmeta', 'services/panelSrv', './singleStatPanel', ], -function (angular, app, _, TimeSeries, kbn) { +function (angular, app, _, TimeSeries, kbn, PanelMeta) { 'use strict'; var module = angular.module('grafana.panels.singlestat'); @@ -15,25 +16,14 @@ function (angular, app, _, TimeSeries, kbn) { module.controller('SingleStatCtrl', function($scope, panelSrv, timeSrv) { - $scope.panelMeta = { + $scope.panelMeta = new PanelMeta({ + description: 'Singlestat panel', titlePos: 'left', - description : "A stats values panel", - fullEditorTabs : [ - { - title: 'General', - src:'app/partials/panelgeneral.html' - }, - { - title: 'Metrics', - src:'app/partials/metrics.html' - }, - { - title: 'Options', - src:'app/panels/singlestat/editor.html' - } - ], - fullscreenEdit: true, - }; + fullscreen: true, + metricsEditor: true + }); + + $scope.panelMeta.addEditorTab('Options', 'app/panels/singlestat/editor.html'); // Set and populate defaults var _d = { diff --git a/src/app/panels/singlestat/singleStatPanel.js b/src/app/panels/singlestat/singleStatPanel.js index 74823ae83e5..90a718ca118 100644 --- a/src/app/panels/singlestat/singleStatPanel.js +++ b/src/app/panels/singlestat/singleStatPanel.js @@ -2,12 +2,10 @@ define([ 'angular', 'app', 'lodash', - 'kbn', 'jquery', 'jquery.flot', - 'jquery.flot.time', ], -function (angular, app, _, kbn, $) { +function (angular, app, _, $) { 'use strict'; var module = angular.module('grafana.panels.singlestat', []); diff --git a/src/app/panels/text/module.js b/src/app/panels/text/module.js index 1401cff50b2..ceb6bcf745c 100644 --- a/src/app/panels/text/module.js +++ b/src/app/panels/text/module.js @@ -3,8 +3,9 @@ define([ 'app', 'lodash', 'require', + 'components/panelmeta', ], -function (angular, app, _, require) { +function (angular, app, _, require, PanelMeta) { 'use strict'; var module = angular.module('grafana.panels.text', []); @@ -14,13 +15,15 @@ function (angular, app, _, require) { module.controller('text', function($scope, templateSrv, $sce, panelSrv) { - $scope.panelMeta = { + $scope.panelMeta = new PanelMeta({ description : "A static text panel that can use plain text, markdown, or (sanitized) HTML" - }; + }); + + $scope.panelMeta.addEditorTab('Edit text', 'app/panels/text/editor.html'); // Set and populate defaults var _d = { - title: 'default title', + title : 'default title', mode : "markdown", // 'html', 'markdown', 'text' content : "", style: {}, diff --git a/src/app/partials/paneleditor.html b/src/app/partials/paneleditor.html index d1a5c5eaab8..cd2555fbf8a 100644 --- a/src/app/partials/paneleditor.html +++ b/src/app/partials/paneleditor.html @@ -5,22 +5,14 @@
    -
    +
    -
    -
    -
    - -
    -
    -
    - -
    +
    diff --git a/src/app/services/panelSrv.js b/src/app/services/panelSrv.js index faa37b4e057..9c429438b08 100644 --- a/src/app/services/panelSrv.js +++ b/src/app/services/panelSrv.js @@ -11,44 +11,10 @@ function (angular, _) { this.init = function($scope) { if (!$scope.panel.span) { $scope.panel.span = 12; } - var menu = [ - { - text: "view", - icon: "icon-eye-open", - click: 'toggleFullscreen(false)', - condition: $scope.panelMeta.fullscreenView - }, - { - text: 'edit', - icon: 'icon-cogs', - click: 'editPanel()', - condition: true, - }, - { - text: 'duplicate', - icon: 'icon-copy', - click: 'duplicatePanel(panel)', - condition: true - }, - { - text: 'json', - icon: 'icon-code', - click: 'editPanelJson()', - condition: true - }, - { - text: 'share', - icon: 'icon-share', - click: 'sharePanel()', - condition: true - }, - ]; - $scope.inspector = {}; - $scope.panelMeta.menu = _.where(menu, { condition: true }); $scope.editPanel = function() { - if ($scope.panelMeta.fullscreenEdit) { + if ($scope.panelMeta.fullscreen) { $scope.toggleFullscreen(true); } else { @@ -118,9 +84,6 @@ function (angular, _) { // Post init phase $scope.fullscreen = false; $scope.editor = { index: 1 }; - if ($scope.panelMeta.fullEditorTabs) { - $scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs, 'title'); - } $scope.datasources = datasourceSrv.getMetricSources(); $scope.setDatasource($scope.panel.datasource); diff --git a/src/css/less/panel.less b/src/css/less/panel.less index a2891fb6291..8dd1dccdd45 100644 --- a/src/css/less/panel.less +++ b/src/css/less/panel.less @@ -93,6 +93,10 @@ border: none; } } + + .dropdown-menu { + text-align: left; + } } .panel-highlight { diff --git a/src/css/less/variables.dark.less b/src/css/less/variables.dark.less index d2acddb13ac..42ab0bba861 100644 --- a/src/css/less/variables.dark.less +++ b/src/css/less/variables.dark.less @@ -146,7 +146,7 @@ // Dropdowns // ------------------------- -@dropdownBackground: @grafanaTargetFuncBackground; +@dropdownBackground: @heroUnitBackground; @dropdownBorder: rgba(0,0,0,.2); @dropdownDividerTop: transparent; @dropdownDividerBottom: #222; From 2ab19148c1924e22385c344be78fcc4be952aa38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 8 Nov 2014 18:12:05 +0100 Subject: [PATCH 090/125] Graph: toggle legend on/off from panel menu, #941, no keyboard shortcut yet, but could come --- src/app/directives/panelMenu.js | 4 ++-- src/app/panels/graph/module.js | 19 +++++++++---------- .../dashboard/dashboardKeyBindings.js | 3 +-- src/app/services/dashboard/dashboardSrv.js | 1 + src/test/specs/graph-ctrl-specs.js | 9 --------- 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/app/directives/panelMenu.js b/src/app/directives/panelMenu.js index 94d8d3cc070..d5faf68ca5b 100644 --- a/src/app/directives/panelMenu.js +++ b/src/app/directives/panelMenu.js @@ -97,7 +97,7 @@ function (angular, $, _) { $menu = $(menuTemplate); $menu.css('left', menuLeftPos); $menu.mouseleave(function() { - //dismiss(1000); + dismiss(1000); }); menuScope = $scope.$new(); @@ -111,7 +111,7 @@ function (angular, $, _) { $(".panel-container").removeClass('panel-highlight'); $panelContainer.toggleClass('panel-highlight'); - //dismiss(2500); + dismiss(2500); }; if ($scope.panelMeta.titlePos && $scope.panel.title) { diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 3e7506cccb4..ce9d06d3a77 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -37,6 +37,8 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html'); $scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html'); + $scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()'); + // Set and populate defaults var _d = { // datasource name, null = default datasource @@ -143,14 +145,12 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.panelMeta.loading = false; $scope.panelMeta.error = err.message || "Timeseries data request error"; $scope.inspector.error = err; - $scope.legend = []; $scope.render([]); }); }; $scope.dataHandler = function(results) { $scope.panelMeta.loading = false; - $scope.legend = []; // png renderer returns just a url if (_.isString(results)) { @@ -180,16 +180,9 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { var alias = seriesData.target; var color = $scope.panel.aliasColors[alias] || $rootScope.colors[index]; - var seriesInfo = { - alias: alias, - color: color, - }; - - $scope.legend.push(seriesInfo); - var series = new TimeSeries({ datapoints: datapoints, - info: seriesInfo, + info: {alias: alias, color: color}, }); if (datapoints && datapoints.length > 0) { @@ -288,6 +281,12 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.render(); }; + // Called from panel menu + $scope.toggleLegend = function() { + $scope.panel.legend.show = !$scope.panel.legend.show; + $scope.get_data(); + }; + panelSrv.init($scope); }); diff --git a/src/app/services/dashboard/dashboardKeyBindings.js b/src/app/services/dashboard/dashboardKeyBindings.js index c723c85db19..95e61dbea8d 100644 --- a/src/app/services/dashboard/dashboardKeyBindings.js +++ b/src/app/services/dashboard/dashboardKeyBindings.js @@ -51,8 +51,7 @@ function(angular, $) { }, { inputDisabled: true }); keyboardManager.bind('ctrl+l', function() { - scope.dashboard.hideAllLegends = !scope.dashboard.hideAllLegends; - scope.dashboard.emit_refresh('refresh'); + scope.$broadcast('toggle-all-legends'); }, { inputDisabled: true }); keyboardManager.bind('ctrl+h', function() { diff --git a/src/app/services/dashboard/dashboardSrv.js b/src/app/services/dashboard/dashboardSrv.js index 7ebe337b432..e8f0a808279 100644 --- a/src/app/services/dashboard/dashboardSrv.js +++ b/src/app/services/dashboard/dashboardSrv.js @@ -35,6 +35,7 @@ function (angular, $, kbn, _, moment) { this.annotations = this._ensureListExist(data.annotations); this.refresh = data.refresh; this.version = data.version || 0; + this.hideAllLegends = data.hideAllLegends || false; if (this.nav.length === 0) { this.nav.push({ type: 'timepicker' }); diff --git a/src/test/specs/graph-ctrl-specs.js b/src/test/specs/graph-ctrl-specs.js index b2cf60d5d5d..fded5b93ee2 100644 --- a/src/test/specs/graph-ctrl-specs.js +++ b/src/test/specs/graph-ctrl-specs.js @@ -27,11 +27,6 @@ define([ ctx.scope.$digest(); }); - it('should build legend model', function() { - expect(ctx.scope.legend[0].alias).to.be('test.cpu1'); - expect(ctx.scope.legend[1].alias).to.be('test.cpu2'); - }); - it('should send time series to render', function() { var data = ctx.scope.render.getCall(0).args[0]; expect(data.length).to.be(2); @@ -44,10 +39,6 @@ define([ ctx.scope.$digest(); }); - it('should clear the legend data', function() { - expect(ctx.scope.legend).to.eql([]); - }); - }); }); From 815ef05dafbfa4209974c1b9c3fd3809272bb9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 8 Nov 2014 19:16:22 +0100 Subject: [PATCH 091/125] Graph: refactoring some stuff with legend values --- src/app/components/timeSeries.js | 22 +++++++--------------- src/app/directives/grafanaGraph.js | 2 +- src/app/panels/graph/legend.js | 24 +++++++++++++++--------- src/app/panels/graph/module.js | 19 +++++++++++-------- src/test/specs/grafanaGraph-specs.js | 14 +++++++------- src/test/specs/timeSeries-specs.js | 18 +++++++++--------- 6 files changed, 50 insertions(+), 49 deletions(-) diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index 6a4e03ae80b..f845965c59e 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -7,9 +7,10 @@ function (_, kbn) { function TimeSeries(opts) { this.datapoints = opts.datapoints; - this.info = opts.info; - this.label = opts.info.alias; - this.id = opts.info.alias; + this.label = opts.alias; + this.id = opts.alias; + this.alias = opts.alias; + this.color = opts.color; this.valueFormater = kbn.valueFormats.none; this.stats = {}; } @@ -33,13 +34,13 @@ function (_, kbn) { this.lines = {}; this.points = {}; this.bars = {}; - this.info.yaxis = 1; + this.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)) { + if (!matchSeriesOverride(override.alias, this.alias)) { continue; } if (override.lines !== void 0) { this.lines.show = override.lines; } @@ -54,7 +55,7 @@ function (_, kbn) { if (override.fillBelowTo !== void 0) { this.fillBelowTo = override.fillBelowTo; } if (override.yaxis !== void 0) { - this.info.yaxis = override.yaxis; + this.yaxis = override.yaxis; } } }; @@ -62,9 +63,6 @@ function (_, kbn) { TimeSeries.prototype.getFlotPairs = function (fillStyle) { var result = []; - this.color = this.info.color; - this.yaxis = this.info.yaxis; - this.stats.total = 0; this.stats.max = Number.MIN_VALUE; this.stats.min = Number.MAX_VALUE; @@ -123,12 +121,6 @@ function (_, kbn) { this.valueFormater = formater; this.decimals = decimals; this.scaledDecimals = scaledDecimals; - - this.info.avg = this.formatValue(this.stats.avg); - this.info.current = this.formatValue(this.stats.current); - this.info.min = this.formatValue(this.stats.min); - this.info.max = this.formatValue(this.stats.max); - this.info.total = this.formatValue(this.stats.total); }; TimeSeries.prototype.formatValue = function(value) { diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index fb506e59b11..5791d346a38 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -175,7 +175,7 @@ function (angular, $, kbn, moment, _, GraphTooltip) { series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats); // if hidden remove points and disable stack - if (scope.hiddenSeries[series.info.alias]) { + if (scope.hiddenSeries[series.alias]) { series.data = []; series.stack = false; } diff --git a/src/app/panels/graph/legend.js b/src/app/panels/graph/legend.js index 8c5ff52e44b..558420053f4 100644 --- a/src/app/panels/graph/legend.js +++ b/src/app/panels/graph/legend.js @@ -36,7 +36,7 @@ function (angular, app, _, kbn, $) { function openColorSelector(e) { var el = $(e.currentTarget); var index = getSeriesIndexForElement(el); - var seriesInfo = data[index].info; + var seriesInfo = data[index]; var popoverScope = scope.$new(); popoverScope.series = seriesInfo; popoverSrv.show({ @@ -49,7 +49,7 @@ function (angular, app, _, kbn, $) { function toggleSeries(e) { var el = $(e.currentTarget); var index = getSeriesIndexForElement(el); - var seriesInfo = data[index].info; + var seriesInfo = data[index]; scope.toggleSeries(seriesInfo, e); } @@ -83,8 +83,8 @@ function (angular, app, _, kbn, $) { for (i = 0; i < data.length; i++) { var series = data[i]; var html = '
    '; html += ''; + var avg = series.formatValue(series.stats.avg); + var current = series.formatValue(series.stats.current); + var min = series.formatValue(series.stats.min); + var max = series.formatValue(series.stats.max); + var total = series.formatValue(series.stats.total); + if (panel.legend.values) { - if (panel.legend.min) { html += '
    ' + series.info.min + '
    '; } - if (panel.legend.max) { html += '
    ' + series.info.max + '
    '; } - if (panel.legend.avg) { html += '
    ' + series.info.avg + '
    '; } - if (panel.legend.current) { html += '
    ' + series.info.current + '
    '; } - if (panel.legend.total) { html += '
    ' + series.info.total + '
    '; } + if (panel.legend.min) { html += '
    ' + min + '
    '; } + if (panel.legend.max) { html += '
    ' + max + '
    '; } + if (panel.legend.avg) { html += '
    ' + avg + '
    '; } + if (panel.legend.current) { html += '
    ' + current + '
    '; } + if (panel.legend.total) { html += '
    ' + total + '
    '; } } html += '
    '; diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index ce9d06d3a77..adcb822b2de 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -112,6 +112,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { _.defaults($scope.panel.legend, _d.legend); $scope.hiddenSeries = {}; + $scope.seriesList = []; $scope.updateTimeRange = function () { $scope.range = timeSrv.timeRange(); @@ -145,6 +146,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.panelMeta.loading = false; $scope.panelMeta.error = err.message || "Timeseries data request error"; $scope.inspector.error = err; + $scope.seriesList = []; $scope.render([]); }); }; @@ -162,16 +164,16 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.datapointsCount = 0; $scope.datapointsOutside = false; - var data = _.map(results.data, $scope.seriesHandler); + $scope.seriesList = _.map(results.data, $scope.seriesHandler); $scope.datapointsWarning = $scope.datapointsCount === 0 || $scope.datapointsOutside; $scope.annotationsPromise .then(function(annotations) { - data.annotations = annotations; - $scope.render(data); + $scope.seriesList.annotations = annotations; + $scope.render($scope.seriesList); }, function() { - $scope.render(data); + $scope.render($scope.seriesList); }); }; @@ -182,7 +184,8 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { var series = new TimeSeries({ datapoints: datapoints, - info: {alias: alias, color: color}, + alias: alias, + color: color, }); if (datapoints && datapoints.length > 0) { @@ -231,7 +234,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { } // check if every other series is hidden - var alreadyExclusive = _.every($scope.legend, function(value) { + var alreadyExclusive = _.every($scope.seriesList, function(value) { if (value.alias === serie.alias) { return true; } @@ -241,13 +244,13 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { if (alreadyExclusive) { // remove all hidden series - _.each($scope.legend, function(value) { + _.each($scope.seriesList, function(value) { delete $scope.hiddenSeries[value.alias]; }); } else { // hide all but this serie - _.each($scope.legend, function(value) { + _.each($scope.seriesList, function(value) { if (value.alias === serie.alias) { return; } diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js index ebd464c0c30..c61cddf9059 100644 --- a/src/test/specs/grafanaGraph-specs.js +++ b/src/test/specs/grafanaGraph-specs.js @@ -47,11 +47,11 @@ define([ ctx.data = []; ctx.data.push(new TimeSeries({ datapoints: [[1,1],[2,2]], - info: { alias: 'series1', enable: true } + alias: 'series1' })); ctx.data.push(new TimeSeries({ datapoints: [[1,1],[2,2]], - info: { alias: 'series2', enable: true } + alias: 'series2' })); setupFunc(scope, ctx.data); @@ -131,7 +131,7 @@ define([ scope.panel.bars = true; data[0] = new TimeSeries({ datapoints: [[1,10],[2,20]], - info: { alias: 'series1', enable: true } + alias: 'series1', }); }); @@ -148,7 +148,7 @@ define([ { alias: 'test', fill: 0, points: true } ]; - data[1].info.alias = 'test'; + data[1].alias = 'test'; }); it('should match second series and fill zero, and enable points', function() { @@ -164,8 +164,8 @@ define([ }); 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'); + expect(ctx.plotData[0].alias).to.be('series2'); + expect(ctx.plotData[1].alias).to.be('series1'); }); }); @@ -175,7 +175,7 @@ define([ }); it('should remove datapoints and disable stack', function() { - expect(ctx.plotData[0].info.alias).to.be('series1'); + expect(ctx.plotData[0].alias).to.be('series1'); expect(ctx.plotData[1].data.length).to.be(0); expect(ctx.plotData[1].stack).to.be(false); }); diff --git a/src/test/specs/timeSeries-specs.js b/src/test/specs/timeSeries-specs.js index a521ac3debc..a2c52dadf57 100644 --- a/src/test/specs/timeSeries-specs.js +++ b/src/test/specs/timeSeries-specs.js @@ -7,7 +7,7 @@ define([ var points, series; var yAxisFormats = ['short', 'ms']; var testData = { - info: { alias: 'test' }, + alias: 'test', datapoints: [ [1,2],[null,3],[10,4],[8,5] ] @@ -36,7 +36,7 @@ define([ describe('fill & points', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', fill: 0, points: true }]); }); @@ -48,7 +48,7 @@ define([ describe('series option overrides, bars, true & lines false', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', bars: true, lines: false }]); }); @@ -60,7 +60,7 @@ define([ describe('series option overrides, linewidth, stack', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', linewidth: 5, stack: false }]); }); @@ -72,7 +72,7 @@ define([ describe('series option overrides, fill below to', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', fillBelowTo: 'min' }]); }); @@ -83,7 +83,7 @@ define([ describe('series option overrides, pointradius, steppedLine', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', pointradius: 5, steppedLine: true }]); }); @@ -95,7 +95,7 @@ define([ describe('override match on regex', function() { beforeEach(function() { - series.info.alias = 'test_01'; + series.alias = 'test_01'; series.applySeriesOverrides([{ alias: '/.*01/', lines: false }]); }); @@ -106,12 +106,12 @@ define([ describe('override series y-axis, and z-index', function() { beforeEach(function() { - series.info.alias = 'test'; + series.alias = 'test'; series.applySeriesOverrides([{ alias: 'test', yaxis: 2, zindex: 2 }]); }); it('should set yaxis', function() { - expect(series.info.yaxis).to.be(2); + expect(series.yaxis).to.be(2); }); it('should set zindex', function() { From ef92272bee4afeca500c1d3c8a63b79d33e78ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 8 Nov 2014 19:28:16 +0100 Subject: [PATCH 092/125] Panel: fixed menu position after moving json to new dropdown --- src/app/directives/panelMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/directives/panelMenu.js b/src/app/directives/panelMenu.js index d5faf68ca5b..b7318f7b9b3 100644 --- a/src/app/directives/panelMenu.js +++ b/src/app/directives/panelMenu.js @@ -43,7 +43,7 @@ function (angular, $, _) { link: function($scope, elem) { var $link = $(linkTemplate); var $panelContainer = elem.parents(".panel-container"); - var menuWidth = $scope.panelMeta.menu.length === 5 ? 246 : 201; + var menuWidth = $scope.panelMeta.menu.length === 4 ? 236 : 191; var menuScope = null; var timeout = null; var $menu = null; From 3aa619b617477d841b848b620f1097ed2abaf9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 9 Nov 2014 09:44:16 +0100 Subject: [PATCH 093/125] Panel: fixed duplicate panel, broken after yesterdays panel menu model refactoring --- src/app/controllers/row.js | 4 ---- src/app/services/panelSrv.js | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/controllers/row.js b/src/app/controllers/row.js index 8a1a6f917ae..4d7f288b16e 100644 --- a/src/app/controllers/row.js +++ b/src/app/controllers/row.js @@ -94,10 +94,6 @@ function (angular, app, _) { }); }; - $scope.duplicatePanel = function(panel, row) { - $scope.dashboard.duplicatePanel(panel, row || $scope.row); - }; - $scope.reset_panel = function(type) { var defaultSpan = 12; var _as = 12 - $scope.dashboard.rowSpan($scope.row); diff --git a/src/app/services/panelSrv.js b/src/app/services/panelSrv.js index 9c429438b08..3cc945624b3 100644 --- a/src/app/services/panelSrv.js +++ b/src/app/services/panelSrv.js @@ -33,6 +33,10 @@ function (angular, _) { $scope.appEvent('show-json-editor', { object: $scope.panel, updateHandler: $scope.replacePanel }); }; + $scope.duplicatePanel = function() { + $scope.dashboard.duplicatePanel($scope.panel, $scope.row); + }; + $scope.updateColumnSpan = function(span) { $scope.panel.span = Math.min(Math.max($scope.panel.span + span, 1), 12); From 27ec0d532e2d3d6e31b41695ec818f25e020f113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 10 Nov 2014 10:40:45 +0100 Subject: [PATCH 094/125] began work on custom confirm modal --- src/app/controllers/row.js | 9 ++++++--- src/app/panels/graph/legend.js | 4 ++-- src/app/partials/confirm_modal.html | 20 ++++++++++++++++++++ src/app/services/alertSrv.js | 25 ++++++++++++++++++++++++- 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 src/app/partials/confirm_modal.html diff --git a/src/app/controllers/row.js b/src/app/controllers/row.js index 4d7f288b16e..e18529ee519 100644 --- a/src/app/controllers/row.js +++ b/src/app/controllers/row.js @@ -76,9 +76,12 @@ function (angular, app, _) { }; $scope.remove_panel_from_row = function(row, panel) { - if (confirm('Are you sure you want to remove this ' + panel.type + ' panel?')) { - row.panels = _.without(row.panels,panel); - } + $scope.appEvent('confirm-modal', { + title: 'Are you sure you want to remove this panel?', + onConfirm: function() { + row.panels = _.without(row.panels, panel); + } + }); }; $scope.replacePanel = function(newPanel, oldPanel) { diff --git a/src/app/panels/graph/legend.js b/src/app/panels/graph/legend.js index 558420053f4..393e5d3dff4 100644 --- a/src/app/panels/graph/legend.js +++ b/src/app/panels/graph/legend.js @@ -22,8 +22,8 @@ function (angular, app, _, kbn, $) { var data; var i; - scope.$on('render', function(event, renderData) { - data = renderData || data; + scope.$on('render', function() { + data = scope.seriesList; if (data) { render(); } diff --git a/src/app/partials/confirm_modal.html b/src/app/partials/confirm_modal.html new file mode 100644 index 00000000000..6968fdf82a9 --- /dev/null +++ b/src/app/partials/confirm_modal.html @@ -0,0 +1,20 @@ +'; template += '
    '; - template += ''; + template += ''; _.each($scope.panelMeta.menu, function(item) { template += '
    + + + + diff --git a/src/app/services/timeSrv.js b/src/app/services/timeSrv.js index 5a28532eb5a..564f30de7fd 100644 --- a/src/app/services/timeSrv.js +++ b/src/app/services/timeSrv.js @@ -95,6 +95,18 @@ define([ $timeout(this.refreshDashboard, 0); }; + this.timeRangeForUrl = function() { + var range = this.timeRange(false); + if (_.isString(range.to) && range.to.indexOf('now')) { + range = this.timeRange(); + } + + if (_.isDate(range.from)) { range.from = range.from.getTime(); } + if (_.isDate(range.to)) { range.to = range.to.getTime(); } + + return range; + }; + this.timeRange = function(parse) { var _t = this.time; if(_.isUndefined(_t) || _.isUndefined(_t.from)) { diff --git a/src/test/specs/sharePanelCtrl-specs.js b/src/test/specs/sharePanelCtrl-specs.js index 8c4eb2a2443..8c83f008ed5 100644 --- a/src/test/specs/sharePanelCtrl-specs.js +++ b/src/test/specs/sharePanelCtrl-specs.js @@ -7,6 +7,12 @@ define([ describe('SharePanelCtrl', function() { var ctx = new helpers.ControllerTestContext(); + function setTime(range) { + ctx.timeSrv.timeRangeForUrl = sinon.stub().returns(range); + } + + setTime({ from: 'now-1h', to: 'now' }); + beforeEach(module('grafana.controllers')); beforeEach(ctx.providePhase()); @@ -14,10 +20,12 @@ define([ describe('shareUrl with current time range and panel', function() { + it('should generate share url relative time', function() { ctx.$location.path('/test'); ctx.scope.panel = { id: 22 }; - ctx.timeSrv.time = { from: 'now-1h', to: 'now' }; + + setTime({ from: 'now-1h', to: 'now' }); ctx.scope.buildUrl(); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now&panelId=22&fullscreen'); @@ -26,26 +34,17 @@ define([ it('should generate share url absolute time', function() { ctx.$location.path('/test'); ctx.scope.panel = { id: 22 }; - ctx.timeSrv.time = { from: new Date(1362178800000), to: new Date(1396648800000) }; + setTime({ from: 1362178800000, to: 1396648800000 }); ctx.scope.buildUrl(); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1362178800000&to=1396648800000&panelId=22&fullscreen'); }); - it('should generate share url with time as JSON strings', function() { - ctx.$location.path('/test'); - ctx.scope.panel = { id: 22 }; - ctx.timeSrv.time = { from: "2012-01-31T23:00:00.000Z", to: "2014-04-04T22:00:00.000Z" }; - - ctx.scope.buildUrl(); - expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1328050800000&to=1396648800000&panelId=22&fullscreen'); - }); - it('should remove panel id when toPanel is false', function() { ctx.$location.path('/test'); ctx.scope.panel = { id: 22 }; ctx.scope.toPanel = false; - ctx.timeSrv.time = { from: 'now-1h', to: 'now' }; + setTime({ from: 'now-1h', to: 'now' }); ctx.scope.buildUrl(); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now'); @@ -57,7 +56,7 @@ define([ ctx.scope.includeTemplateVars = true; ctx.scope.toPanel = false; ctx.templateSrv.variables = [{ name: 'app', current: {text: 'mupp' }}, {name: 'server', current: {text: 'srv-01'}}]; - ctx.timeSrv.time = { from: 'now-1h', to: 'now' }; + setTime({ from: 'now-1h', to: 'now' }); ctx.scope.buildUrl(); expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now&var-app=mupp&var-server=srv-01'); From a49a9b3b6462d8c3b0d73ca417016c944e86e212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Nov 2014 12:51:57 +0100 Subject: [PATCH 100/125] Panel: more work on panel links, #1041 --- src/app/components/panellinkeditor/linkSrv.js | 8 +++---- src/app/directives/panelMenu.js | 24 +++++++++++++------ src/css/less/panel.less | 9 +++++++ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/app/components/panellinkeditor/linkSrv.js b/src/app/components/panellinkeditor/linkSrv.js index 8737814556d..eacdf1d2c35 100644 --- a/src/app/components/panellinkeditor/linkSrv.js +++ b/src/app/components/panellinkeditor/linkSrv.js @@ -13,14 +13,14 @@ function (angular, kbn) { var info = {}; if (link.type === 'absolute') { info.target = '_blank'; - info.href = templateSrv.replace(link.url); - info.title = templateSrv.replace(link.title); + info.href = templateSrv.replace(link.url || ''); + info.title = templateSrv.replace(link.title || ''); info.href += '?'; } else { - info.title = templateSrv.replace(link.title); - var slug = kbn.slugifyForUrl(link.dashboard); + info.title = templateSrv.replace(link.title || ''); + var slug = kbn.slugifyForUrl(link.dashboard || ''); info.href = '#dashboard/db/' + slug + '?'; } diff --git a/src/app/directives/panelMenu.js b/src/app/directives/panelMenu.js index 13aed5f6f3e..1a340b19bfa 100644 --- a/src/app/directives/panelMenu.js +++ b/src/app/directives/panelMenu.js @@ -9,7 +9,11 @@ function (angular, $, _) { angular .module('grafana.directives') .directive('panelMenu', function($compile, linkSrv) { - var linkTemplate = '{{panel.title | interpolateTemplateVars}}'; + var linkTemplate = + '' + + '{{panel.title | interpolateTemplateVars}}' + + '' + + ''; function createMenuTemplate($scope) { var template = '
    '; @@ -39,13 +43,15 @@ function (angular, $, _) { } function getExtendedMenu($scope) { - var menu =angular.copy($scope.panelMeta.extendedMenu); - if (!$scope.panel.links) { return; } + var menu = angular.copy($scope.panelMeta.extendedMenu); + + if ($scope.panel.links) { + _.each($scope.panel.links, function(link) { + var info = linkSrv.getPanelLinkAnchorInfo(link); + menu.push({text: info.title, href: info.href, target: info.target }); + }); + } - _.each($scope.panel.links, function(link) { - var info = linkSrv.getPanelLinkAnchorInfo(link); - menu.push({text: info.title, href: info.href, target: info.target }); - }); return menu; } @@ -61,6 +67,10 @@ function (angular, $, _) { elem.append($link); + $scope.$watchCollection('panel.links', function(newValue) { + $link.toggleClass('has-panel-links', newValue ? newValue.length > 0 : false); + }); + function dismiss(time) { clearTimeout(timeout); timeout = null; diff --git a/src/css/less/panel.less b/src/css/less/panel.less index 8dd1dccdd45..5c3dd93c6cf 100644 --- a/src/css/less/panel.less +++ b/src/css/less/panel.less @@ -27,6 +27,15 @@ font-weight: bold; position: relative; cursor: context-menu; + + &.has-panel-links { + .panel-title-text:after { + content: "\f0c1"; + font-family:'FontAwesome'; + font-size: 80%; + padding-left: 10px; + } + } } .panel-loading { From c12d830162ab428e27f508279aeacb12d93ca80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Nov 2014 13:38:27 +0100 Subject: [PATCH 101/125] Panel: added search typeahead for dashboard links, #1041 --- src/app/app.js | 2 ++ src/app/controllers/dashboardCtrl.js | 1 - src/app/directives/all.js | 1 - src/app/features/all.js | 3 +++ .../panellinkeditor/linkSrv.js | 0 .../panellinkeditor/module.html | 5 ++++- .../panellinkeditor/module.js | 17 +++++++++++++++-- .../services/dashboard/dashboardKeyBindings.js | 1 - tasks/options/requirejs.js | 7 ++++--- 9 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 src/app/features/all.js rename src/app/{components => features}/panellinkeditor/linkSrv.js (100%) rename src/app/{components => features}/panellinkeditor/module.html (90%) rename src/app/{components => features}/panellinkeditor/module.js (57%) diff --git a/src/app/app.js b/src/app/app.js index 0b75d459435..bb03d47686d 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -78,6 +78,8 @@ function (angular, $, _, appLevelRequire, config) { }); var preBootRequires = [ + 'services/all', + 'features/all', 'controllers/all', 'directives/all', 'filters/all', diff --git a/src/app/controllers/dashboardCtrl.js b/src/app/controllers/dashboardCtrl.js index d69978dcdea..0aee19d82e4 100644 --- a/src/app/controllers/dashboardCtrl.js +++ b/src/app/controllers/dashboardCtrl.js @@ -3,7 +3,6 @@ define([ 'jquery', 'config', 'lodash', - 'services/all', ], function (angular, $, config, _) { "use strict"; diff --git a/src/app/directives/all.js b/src/app/directives/all.js index 15c2a5c80f7..7f7836728a6 100644 --- a/src/app/directives/all.js +++ b/src/app/directives/all.js @@ -19,6 +19,5 @@ define([ './graphiteSegment', './grafanaVersionCheck', './dropdown.typeahead', - 'components/panellinkeditor/module', './influxdbFuncEditor' ], function () {}); diff --git a/src/app/features/all.js b/src/app/features/all.js new file mode 100644 index 00000000000..5e11ae038f6 --- /dev/null +++ b/src/app/features/all.js @@ -0,0 +1,3 @@ +define([ + './panellinkeditor/module', +], function () {}); diff --git a/src/app/components/panellinkeditor/linkSrv.js b/src/app/features/panellinkeditor/linkSrv.js similarity index 100% rename from src/app/components/panellinkeditor/linkSrv.js rename to src/app/features/panellinkeditor/linkSrv.js diff --git a/src/app/components/panellinkeditor/module.html b/src/app/features/panellinkeditor/module.html similarity index 90% rename from src/app/components/panellinkeditor/module.html rename to src/app/features/panellinkeditor/module.html index 2cbdc50e49b..11558f7c90c 100644 --- a/src/app/components/panellinkeditor/module.html +++ b/src/app/features/panellinkeditor/module.html @@ -21,7 +21,10 @@
  • dashboard
  • - +
  • url
  • diff --git a/src/app/components/panellinkeditor/module.js b/src/app/features/panellinkeditor/module.js similarity index 57% rename from src/app/components/panellinkeditor/module.js rename to src/app/features/panellinkeditor/module.js index fe6f9684f7f..3052466d1d1 100644 --- a/src/app/components/panellinkeditor/module.js +++ b/src/app/features/panellinkeditor/module.js @@ -15,11 +15,11 @@ function (angular, _) { }, restrict: 'E', controller: 'PanelLinkEditorCtrl', - templateUrl: 'app/components/panellinkeditor/module.html', + templateUrl: 'app/features/panellinkeditor/module.html', link: function() { } }; - }).controller('PanelLinkEditorCtrl', function($scope) { + }).controller('PanelLinkEditorCtrl', function($scope, datasourceSrv) { $scope.panel.links = $scope.panel.links || []; @@ -30,6 +30,19 @@ function (angular, _) { }); }; + $scope.searchDashboards = function(query, callback) { + var ds = datasourceSrv.getGrafanaDB(); + if (ds === null) { return; } + + ds.searchDashboards(query).then(function(result) { + var dashboards = _.map(result.dashboards, function(dash) { + return dash.title; + }); + + callback(dashboards); + }); + }; + $scope.deleteLink = function(link) { $scope.panel.links = _.without($scope.panel.links, link); }; diff --git a/src/app/services/dashboard/dashboardKeyBindings.js b/src/app/services/dashboard/dashboardKeyBindings.js index 95e61dbea8d..1557a3848d3 100644 --- a/src/app/services/dashboard/dashboardKeyBindings.js +++ b/src/app/services/dashboard/dashboardKeyBindings.js @@ -1,7 +1,6 @@ define([ 'angular', 'jquery', - 'services/all' ], function(angular, $) { "use strict"; diff --git a/tasks/options/requirejs.js b/tasks/options/requirejs.js index a5e9008ac3c..a889b9fd154 100644 --- a/tasks/options/requirejs.js +++ b/tasks/options/requirejs.js @@ -54,12 +54,13 @@ module.exports = function(config,grunt) { 'timepicker', 'datepicker', 'lodash', - 'filters/all', 'jquery.flot', - 'services/all', 'angular-strap', - 'directives/all', 'angular-dragdrop', + 'services/all', + 'features/all', + 'directives/all', + 'filters/all', 'controllers/all', 'routes/all', 'components/partials', From 1e6a5ff8ec0cddcd9d38a33e52da9374c784aa1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Nov 2014 13:44:25 +0100 Subject: [PATCH 102/125] Updated changelog with #1041 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 849e1a171bf..0f409928f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory - [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts - [Issue #991](https://github.com/grafana/grafana/issues/991). ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example) +- [Issue #1041](https://github.com/grafana/grafana/issues/1041). Panel: All panels can now have links to other dashboards or absolute links, these links are available in the panel menu. **Changes** - [Issue #1007](https://github.com/grafana/grafana/issues/1007). Graph: Series hide/show toggle changed to be default exclusive, so clicking on a series name will show only that series. (SHIFT or meta)+click will toggle hide/show. From 4ec59e82111898a256611cb1c4d69473d5b71264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Nov 2014 13:57:35 +0100 Subject: [PATCH 103/125] Tech: cleanup unused angular-strap directives, Closes #1029 --- src/app/directives/panelMenu.js | 2 +- src/app/panels/timepicker/module.js | 4 +- src/vendor/angular/angular-strap.js | 486 +--------------------------- 3 files changed, 4 insertions(+), 488 deletions(-) diff --git a/src/app/directives/panelMenu.js b/src/app/directives/panelMenu.js index 1a340b19bfa..287d0994f14 100644 --- a/src/app/directives/panelMenu.js +++ b/src/app/directives/panelMenu.js @@ -26,7 +26,7 @@ function (angular, $, _) { template += '
    '; template += '
    '; - template += ''; + template += ''; _.each($scope.panelMeta.menu, function(item) { template += '×'); - } - if (attrs.closeAfter) - closeAlert(attrs.closeAfter); - } else { - scope.$watch(attrs.bsAlert, function (newValue, oldValue) { - value = newValue; - element.html((newValue.title ? '' + newValue.title + ' ' : '') + newValue.content || ''); - if (!!newValue.closed) { - element.hide(); - } - $compile(element.contents())(scope); - if (newValue.type || oldValue.type) { - oldValue.type && element.removeClass('alert-' + oldValue.type); - newValue.type && element.addClass('alert-' + newValue.type); - } - if (angular.isDefined(newValue.closeAfter)) - closeAlert(newValue.closeAfter); - else if (attrs.closeAfter) - closeAlert(attrs.closeAfter); - if (angular.isUndefined(attrs.closeButton) || attrs.closeButton !== '0' && attrs.closeButton !== 'false') { - element.prepend(''); - } - }, true); - } - element.addClass('alert').alert(); - if (element.hasClass('fade')) { - element.removeClass('in'); - setTimeout(function () { - element.addClass('in'); - }); - } - var parentArray = attrs.ngRepeat && attrs.ngRepeat.split(' in ').pop(); - element.on('close', function (ev) { - var removeElement; - if (parentArray) { - ev.preventDefault(); - element.removeClass('in'); - removeElement = function () { - element.trigger('closed'); - if (scope.$parent) { - scope.$parent.$apply(function () { - var path = parentArray.split('.'); - var curr = scope.$parent; - for (var i = 0; i < path.length; ++i) { - if (curr) { - curr = curr[path[i]]; - } - } - if (curr) { - curr.splice(scope.$index, 1); - } - }); - } - }; - $.support.transition && element.hasClass('fade') ? element.on($.support.transition.end, removeElement) : removeElement(); - } else if (value) { - ev.preventDefault(); - element.removeClass('in'); - removeElement = function () { - element.trigger('closed'); - scope.$apply(function () { - value.closed = true; - }); - }; - $.support.transition && element.hasClass('fade') ? element.on($.support.transition.end, removeElement) : removeElement(); - } else { - } - }); - } - }; - } -]); -'use strict'; -angular.module('$strap.directives').directive('bsButton', [ - '$parse', - '$timeout', - function ($parse, $timeout) { - return { - restrict: 'A', - require: '?ngModel', - link: function postLink(scope, element, attrs, controller) { - if (controller) { - if (!element.parent('[data-toggle="buttons-checkbox"], [data-toggle="buttons-radio"]').length) { - element.attr('data-toggle', 'button'); - } - var startValue = !!scope.$eval(attrs.ngModel); - if (startValue) { - element.addClass('active'); - } - scope.$watch(attrs.ngModel, function (newValue, oldValue) { - var bNew = !!newValue, bOld = !!oldValue; - if (bNew !== bOld) { - $.fn.button.Constructor.prototype.toggle.call(button); - } else if (bNew && !startValue) { - element.addClass('active'); - } - }); - } - if (!element.hasClass('btn')) { - element.on('click.button.data-api', function (ev) { - element.button('toggle'); - }); - } - element.button(); - var button = element.data('button'); - button.toggle = function () { - if (!controller) { - return $.fn.button.Constructor.prototype.toggle.call(this); - } - var $parent = element.parent('[data-toggle="buttons-radio"]'); - if ($parent.length) { - element.siblings('[ng-model]').each(function (k, v) { - $parse($(v).attr('ng-model')).assign(scope, false); - }); - scope.$digest(); - if (!controller.$modelValue) { - controller.$setViewValue(!controller.$modelValue); - scope.$digest(); - } - } else { - scope.$apply(function () { - controller.$setViewValue(!controller.$modelValue); - }); - } - }; - } - }; - } -]).directive('bsButtonsCheckbox', [ - '$parse', - function ($parse) { - return { - restrict: 'A', - require: '?ngModel', - compile: function compile(tElement, tAttrs, transclude) { - tElement.attr('data-toggle', 'buttons-checkbox').find('a, button').each(function (k, v) { - $(v).attr('bs-button', ''); - }); - } - }; - } -]).directive('bsButtonsRadio', [ - '$timeout', - function ($timeout) { - return { - restrict: 'A', - require: '?ngModel', - compile: function compile(tElement, tAttrs, transclude) { - tElement.attr('data-toggle', 'buttons-radio'); - if (!tAttrs.ngModel) { - tElement.find('a, button').each(function (k, v) { - $(v).attr('bs-button', ''); - }); - } - return function postLink(scope, iElement, iAttrs, controller) { - if (controller) { - $timeout(function () { - iElement.find('[value]').button().filter('[value="' + controller.$viewValue + '"]').addClass('active'); - }); - iElement.on('click.button.data-api', function (ev) { - scope.$apply(function () { - controller.$setViewValue($(ev.target).closest('button').attr('value')); - }); - }); - scope.$watch(iAttrs.ngModel, function (newValue, oldValue) { - if (newValue !== oldValue) { - var $btn = iElement.find('[value="' + scope.$eval(iAttrs.ngModel) + '"]'); - if ($btn.length) { - $btn.button('toggle'); - } - } - }); - } - }; - } - }; - } -]); -'use strict'; -angular.module('$strap.directives').directive('bsButtonSelect', [ - '$parse', - '$timeout', - function ($parse, $timeout) { - return { - restrict: 'A', - require: '?ngModel', - link: function postLink(scope, element, attrs, ctrl) { - var getter = $parse(attrs.bsButtonSelect), setter = getter.assign; - if (ctrl) { - element.text(scope.$eval(attrs.ngModel)); - scope.$watch(attrs.ngModel, function (newValue, oldValue) { - element.text(newValue); - }); - } - var values, value, index, newValue; - element.bind('click', function (ev) { - values = getter(scope); - value = ctrl ? scope.$eval(attrs.ngModel) : element.text(); - index = values.indexOf(value); - newValue = index > values.length - 2 ? values[0] : values[index + 1]; - scope.$apply(function () { - element.text(newValue); - if (ctrl) { - ctrl.$setViewValue(newValue); - } - }); - }); - } - }; - } -]); -'use strict'; angular.module('$strap.directives').directive('bsDatepicker', [ '$timeout', '$strapConfig', @@ -379,46 +150,6 @@ angular.module('$strap.directives').directive('bsDatepicker', [ } ]); 'use strict'; -angular.module('$strap.directives').directive('bsDropdown', [ - '$parse', - '$compile', - '$timeout', - function ($parse, $compile, $timeout) { - var buildTemplate = function (items, ul) { - if (!ul) - ul = [ - '' - ]; - angular.forEach(items, function (item, index) { - if (item.divider) - return ul.splice(index + 1, 0, '
  • '); - var li = '' + '
    ' + (item.text || '') + ''; - if (item.submenu && item.submenu.length) - li += buildTemplate(item.submenu).join('\n'); - li += ''; - ul.splice(index + 1, 0, li); - }); - return ul; - }; - return { - restrict: 'EA', - scope: true, - link: function postLink(scope, iElement, iAttrs) { - var getter = $parse(iAttrs.bsDropdown), items = getter(scope); - $timeout(function () { - if (!angular.isArray(items)) { - } - var dropdown = angular.element(buildTemplate(items).join('')); - dropdown.insertAfter(iElement); - $compile(iElement.next('ul.dropdown-menu'))(scope); - }); - iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown'); - } - }; - } -]); -'use strict'; angular.module('$strap.directives').factory('$modal', [ '$rootScope', '$compile', @@ -483,183 +214,7 @@ angular.module('$strap.directives').factory('$modal', [ }; return ModalFactory; } -]).directive('bsModal', [ - '$q', - '$modal', - function ($q, $modal) { - return { - restrict: 'A', - scope: true, - link: function postLink(scope, iElement, iAttrs, controller) { - var options = { - template: scope.$eval(iAttrs.bsModal), - persist: true, - show: false, - scope: scope - }; - angular.forEach([ - 'modalClass', - 'backdrop', - 'keyboard' - ], function (key) { - if (angular.isDefined(iAttrs[key])) - options[key] = iAttrs[key]; - }); - $q.when($modal(options)).then(function onSuccess(modal) { - iElement.attr('data-target', '#' + modal.attr('id')).attr('data-toggle', 'modal'); - }); - } - }; - } -]); -'use strict'; -angular.module('$strap.directives').directive('bsNavbar', [ - '$location', - function ($location) { - return { - restrict: 'A', - link: function postLink(scope, element, attrs, controller) { - scope.$watch(function () { - return $location.path(); - }, function (newValue, oldValue) { - $('li[data-match-route]', element).each(function (k, li) { - var $li = angular.element(li), pattern = $li.attr('data-match-route'), regexp = new RegExp('^' + pattern + '$', ['i']); - if (regexp.test(newValue)) { - $li.addClass('active').find('.collapse.in').collapse('hide'); - } else { - $li.removeClass('active'); - } - }); - }); - } - }; - } -]); -'use strict'; -angular.module('$strap.directives').directive('bsPopover', [ - '$parse', - '$compile', - '$http', - '$timeout', - '$q', - '$templateCache', - function ($parse, $compile, $http, $timeout, $q, $templateCache) { - $('body').on('keyup', function (ev) { - if (ev.keyCode === 27) { - $('.popover.in').each(function () { - $(this).popover('hide'); - }); - } - }); - return { - restrict: 'A', - scope: true, - link: function postLink(scope, element, attr, ctrl) { - var getter = $parse(attr.bsPopover), setter = getter.assign, value = getter(scope), options = {}; - if (angular.isObject(value)) { - options = value; - } - $q.when(options.content || $templateCache.get(value) || $http.get(value, { cache: true })).then(function onSuccess(template) { - if (angular.isObject(template)) { - template = template.data; - } - if (!!attr.unique) { - element.on('show', function (ev) { - $('.popover.in').each(function () { - var $this = $(this), popover = $this.data('popover'); - if (popover && !popover.$element.is(element)) { - $this.popover('hide'); - } - }); - }); - } - if (!!attr.hide) { - scope.$watch(attr.hide, function (newValue, oldValue) { - if (!!newValue) { - popover.hide(); - } else if (newValue !== oldValue) { - popover.show(); - } - }); - } - if (!!attr.show) { - scope.$watch(attr.show, function (newValue, oldValue) { - if (!!newValue) { - $timeout(function () { - popover.show(); - }); - } else if (newValue !== oldValue) { - popover.hide(); - } - }); - } - element.popover(angular.extend({}, options, { - content: template, - html: true - })); - var popover = element.data('popover'); - popover.hasContent = function () { - return this.getTitle() || template; - }; - popover.getPosition = function () { - var r = $.fn.popover.Constructor.prototype.getPosition.apply(this, arguments); - $compile(this.$tip)(scope); - scope.$digest(); - this.$tip.data('popover', this); - return r; - }; - scope.$popover = function (name) { - popover(name); - }; - angular.forEach([ - 'show', - 'hide' - ], function (name) { - scope[name] = function () { - popover[name](); - }; - }); - scope.dismiss = scope.hide; - angular.forEach([ - 'show', - 'shown', - 'hide', - 'hidden' - ], function (name) { - element.on(name, function (ev) { - scope.$emit('popover-' + name, ev); - }); - }); - }); - } - }; - } -]); -'use strict'; -angular.module('$strap.directives').directive('bsSelect', [ - '$timeout', - function ($timeout) { - var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/; - return { - restrict: 'A', - require: '?ngModel', - link: function postLink(scope, element, attrs, controller) { - var options = scope.$eval(attrs.bsSelect) || {}; - $timeout(function () { - element.selectpicker(options); - element.next().removeClass('ng-scope'); - }); - if (controller) { - scope.$watch(attrs.ngModel, function (newValue, oldValue) { - if (!angular.equals(newValue, oldValue)) { - element.selectpicker('refresh'); - } - }); - } - } - }; - } -]); +]) 'use strict'; angular.module('$strap.directives').directive('bsTabs', [ '$parse', @@ -731,45 +286,6 @@ angular.module('$strap.directives').directive('bsTabs', [ } ]); 'use strict'; -angular.module('$strap.directives').directive('bsTimepicker', [ - '$timeout', - '$strapConfig', - function ($timeout, $strapConfig) { - var TIME_REGEXP = '((?:(?:[0-1][0-9])|(?:[2][0-3])|(?:[0-9])):(?:[0-5][0-9])(?::[0-5][0-9])?(?:\\s?(?:am|AM|pm|PM))?)'; - return { - restrict: 'A', - require: '?ngModel', - link: function postLink(scope, element, attrs, controller) { - if (controller) { - element.on('changeTime.timepicker', function (ev) { - $timeout(function () { - controller.$setViewValue(element.val()); - }); - }); - var timeRegExp = new RegExp('^' + TIME_REGEXP + '$', ['i']); - controller.$parsers.unshift(function (viewValue) { - if (!viewValue || timeRegExp.test(viewValue)) { - controller.$setValidity('time', true); - return viewValue; - } else { - controller.$setValidity('time', false); - return; - } - }); - } - element.attr('data-toggle', 'timepicker'); - element.parent().addClass('bootstrap-timepicker'); - element.timepicker($strapConfig.timepicker || {}); - var timepicker = element.data('timepicker'); - var component = element.siblings('[data-toggle="timepicker"]'); - if (component.length) { - component.on('click', $.proxy(timepicker.showWidget, timepicker)); - } - } - }; - } -]); -'use strict'; angular.module('$strap.directives').directive('bsTooltip', [ '$parse', '$compile', From 98037ca0c65614dcda57a41a6b6b0364e58e4cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Nov 2014 14:55:23 +0100 Subject: [PATCH 104/125] SinglestatPanel: fixed issue when value is zero, #1039 --- src/app/panels/singlestat/module.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/panels/singlestat/module.js b/src/app/panels/singlestat/module.js index 1c1c2a3d0bb..93c6b67f242 100644 --- a/src/app/panels/singlestat/module.js +++ b/src/app/panels/singlestat/module.js @@ -124,6 +124,9 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) { $scope.getDecimalsForValue = function(value) { var opts = {}; + if (value === 0) { + return { decimals: 0, scaledDecimals: 0 }; + } var delta = value / 2; var dec = -Math.floor(Math.log(delta) / Math.LN10); From 17114778b7b15e265e233da20a9d5a91c649b4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Nov 2014 15:00:31 +0100 Subject: [PATCH 105/125] Panel: fixed bug introduced in recent commit that caused blank title in optimized build, #1041 --- tasks/options/ngmin.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks/options/ngmin.js b/tasks/options/ngmin.js index 82e7dee4f8d..1a25ebee279 100644 --- a/tasks/options/ngmin.js +++ b/tasks/options/ngmin.js @@ -8,6 +8,7 @@ module.exports = function(config) { 'app/directives/**/*.js', 'app/services/**/*.js', 'app/filters/**/*.js', + 'app/features/**/*.js', 'app/panels/**/*.js', 'app/routes/**/*.js', 'plugins/**/*.js', From 9e26d3e85d6dda694eb7700a7055855aeb80c85e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Nov 2014 16:30:20 +0100 Subject: [PATCH 106/125] SinglestatPanel: increased scaled decimal offset by 2 for, improves decimal precision when units gets scaled, #1043, #951 --- src/app/panels/singlestat/module.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/panels/singlestat/module.js b/src/app/panels/singlestat/module.js index 93c6b67f242..4564abe8acc 100644 --- a/src/app/panels/singlestat/module.js +++ b/src/app/panels/singlestat/module.js @@ -158,7 +158,7 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) { var result = {}; result.decimals = Math.max(0, dec); - result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10); + result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2; return result; }; From 01bd66204673f44445ac3de121d09784fb350ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Nov 2014 21:06:01 +0100 Subject: [PATCH 107/125] Graph: legend fix for bug when running optimized build, Fixes #1045 --- src/app/panels/graph/legend.popover.html | 3 +-- src/app/panels/singlestat/module.js | 2 +- src/app/services/popoverSrv.js | 5 +++-- tasks/options/ngtemplates.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/panels/graph/legend.popover.html b/src/app/panels/graph/legend.popover.html index ea029429b1c..631f4cfeabc 100644 --- a/src/app/panels/graph/legend.popover.html +++ b/src/app/panels/graph/legend.popover.html @@ -20,8 +20,7 @@ class="pointer" ng-class="{'icon-circle-blank': color === series.color,'icon-circle': color !== series.color}" ng-style="{color:color}" - ng-click="changeSeriesColor(series, color);dismiss();"> - + ng-click="changeSeriesColor(series, color);dismiss();"> 
    diff --git a/src/app/panels/singlestat/module.js b/src/app/panels/singlestat/module.js index 4564abe8acc..32eb2dd90ff 100644 --- a/src/app/panels/singlestat/module.js +++ b/src/app/panels/singlestat/module.js @@ -158,7 +158,7 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) { var result = {}; result.decimals = Math.max(0, dec); - result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2; + result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN11) + 2; return result; }; diff --git a/src/app/services/popoverSrv.js b/src/app/services/popoverSrv.js index 2bd6df0021a..a1e60525e2d 100644 --- a/src/app/services/popoverSrv.js +++ b/src/app/services/popoverSrv.js @@ -1,7 +1,8 @@ define([ 'angular', + 'lodash', ], -function (angular) { +function (angular, _) { 'use strict'; var module = angular.module('grafana.services'); @@ -21,7 +22,7 @@ function (angular) { } this.getTemplate(options.templateUrl).then(function(result) { - var template = result.data; + var template = _.isString(result) ? result : result.data; options.element.popover({ content: template, diff --git a/tasks/options/ngtemplates.js b/tasks/options/ngtemplates.js index 7971a102e6c..bf6eb86930a 100644 --- a/tasks/options/ngtemplates.js +++ b/tasks/options/ngtemplates.js @@ -15,4 +15,4 @@ module.exports = function(config) { } } }; -}; \ No newline at end of file +}; From 54fafb3a7668f34cd5e7d021e50268e02ee4af23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Nov 2014 21:09:50 +0100 Subject: [PATCH 108/125] Graph: fix to typeahead for series overrides, (bug introduced in recent commit), Fixes #1046 --- src/app/panels/graph/seriesOverridesCtrl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index b677bf19e17..04d66ee5e73 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -53,8 +53,8 @@ define([ }; $scope.getSeriesNames = function() { - return _.map($scope.legend, function(info) { - return info.alias; + return _.map($scope.seriesList, function(series) { + return series.alias; }); }; From ebcf2c3f6854cbe2903ad8c97af18018c5b42c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Nov 2014 08:39:04 +0100 Subject: [PATCH 109/125] InfluxDB: Support for sub second resolution graphs, Closes #714, #728, #752 --- CHANGELOG.md | 3 +++ src/app/components/timeSeries.js | 4 ++-- src/app/panels/graph/module.js | 2 +- src/app/services/graphite/graphiteDatasource.js | 13 ++++++++++++- src/app/services/influxdb/influxdbDatasource.js | 1 - src/app/services/opentsdb/opentsdbDatasource.js | 2 +- src/test/specs/grafanaGraph-specs.js | 2 +- src/test/specs/graphiteDatasource-specs.js | 2 +- src/test/specs/influxdb-datasource-specs.js | 6 +++--- 9 files changed, 24 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f409928f74..2b68728cb44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ - [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc - [Issue #917](https://github.com/grafana/grafana/issues/917). OpenTSDB: Templating support for OpenTSDB series name and tags, thx @mchataigner +**InfluxDB** +- [Issue #714](https://github.com/grafana/grafana/issues/714). InfluxDB: Support for sub second resolution graphs + **Fixes** - [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other) - [Issue #505](https://github.com/grafana/grafana/issues/505). Graph: fix for second y axis tick unit labels wrapping on the next line diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index f845965c59e..6d54239a0bc 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -99,11 +99,11 @@ function (_, kbn) { this.stats.min = currentValue; } - result.push([currentTime * 1000, currentValue]); + result.push([currentTime, currentValue]); } if (this.datapoints.length >= 2) { - this.stats.timeStep = (this.datapoints[1][1] - this.datapoints[0][1]) * 1000; + this.stats.timeStep = this.datapoints[1][1] - this.datapoints[0][1]; } if (this.stats.max === Number.MIN_VALUE) { this.stats.max = null; } diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index adcb822b2de..9af80bcf7e2 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -189,7 +189,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { }); if (datapoints && datapoints.length > 0) { - var last = moment.utc(datapoints[datapoints.length - 1][1] * 1000); + var last = moment.utc(datapoints[datapoints.length - 1][1]); var from = moment.utc($scope.range.from); if (last - from < -10000) { $scope.datapointsOutside = true; diff --git a/src/app/services/graphite/graphiteDatasource.js b/src/app/services/graphite/graphiteDatasource.js index 20ef5cbfa3a..10d3e0dae7f 100644 --- a/src/app/services/graphite/graphiteDatasource.js +++ b/src/app/services/graphite/graphiteDatasource.js @@ -53,13 +53,24 @@ function (angular, _, $, config, kbn, moment) { httpOptions.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; } - return this.doGraphiteRequest(httpOptions); + return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs); } catch(err) { return $q.reject(err); } }; + GraphiteDatasource.prototype.convertDataPointsToMs = function(result) { + if (!result || !result.data) { return []; } + for (var i = 0; i < result.data.length; i++) { + var series = result.data[i]; + for (var y = 0; y < series.datapoints.length; y++) { + series.datapoints[y][1] *= 1000; + } + } + return result; + }; + GraphiteDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) { // Graphite metric as annotation if (annotation.target) { diff --git a/src/app/services/influxdb/influxdbDatasource.js b/src/app/services/influxdb/influxdbDatasource.js index 287e2286f07..43e245bfc4c 100644 --- a/src/app/services/influxdb/influxdbDatasource.js +++ b/src/app/services/influxdb/influxdbDatasource.js @@ -146,7 +146,6 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { InfluxDatasource.prototype._seriesQuery = function(query) { return this._influxRequest('GET', '/series', { q: query, - time_precision: 's', }); }; diff --git a/src/app/services/opentsdb/opentsdbDatasource.js b/src/app/services/opentsdb/opentsdbDatasource.js index 17232d54047..96784cbc992 100644 --- a/src/app/services/opentsdb/opentsdbDatasource.js +++ b/src/app/services/opentsdb/opentsdbDatasource.js @@ -100,7 +100,7 @@ function (angular, _, kbn) { // TSDB returns datapoints has a hash of ts => value. // Can't use _.pairs(invert()) because it stringifies keys/values _.each(md.dps, function (v, k) { - dps.push([v, k]); + dps.push([v, k * 1000]); }); return { target: metricLabel, datapoints: dps }; diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js index c61cddf9059..5344917ab31 100644 --- a/src/test/specs/grafanaGraph-specs.js +++ b/src/test/specs/grafanaGraph-specs.js @@ -136,7 +136,7 @@ define([ }); it('should set barWidth', function() { - expect(ctx.plotOptions.series.bars.barWidth).to.be(10000/1.5); + expect(ctx.plotOptions.series.bars.barWidth).to.be(10/1.5); }); }); diff --git a/src/test/specs/graphiteDatasource-specs.js b/src/test/specs/graphiteDatasource-specs.js index 99270867266..f112492aebc 100644 --- a/src/test/specs/graphiteDatasource-specs.js +++ b/src/test/specs/graphiteDatasource-specs.js @@ -21,7 +21,7 @@ define([ maxDataPoints: 500, }; - var response = [{ target: 'prod1.count', points: [[10, 1], [12,1]], }]; + var response = [{ target: 'prod1.count', datapoints: [[10, 1], [12,1]], }]; var results; var request; diff --git a/src/test/specs/influxdb-datasource-specs.js b/src/test/specs/influxdb-datasource-specs.js index 3851b0cd898..c0d25e1e81f 100644 --- a/src/test/specs/influxdb-datasource-specs.js +++ b/src/test/specs/influxdb-datasource-specs.js @@ -17,7 +17,7 @@ define([ describe('When querying influxdb with one target using query editor target spec', function() { var results; var urlExpected = "/series?p=mupp&q=select+mean(value)+from+%22test%22"+ - "+where+time+%3E+now()+-+1h+group+by+time(1s)+order+asc&time_precision=s"; + "+where+time+%3E+now()+-+1h+group+by+time(1s)+order+asc"; var query = { range: { from: 'now-1h', to: 'now' }, targets: [{ series: 'test', column: 'value', function: 'mean' }], @@ -50,7 +50,7 @@ define([ describe('When querying influxdb with one raw query', function() { var results; var urlExpected = "/series?p=mupp&q=select+value+from+series"+ - "+where+time+%3E+now()+-+1h&time_precision=s"; + "+where+time+%3E+now()+-+1h"; var query = { range: { from: 'now-1h', to: 'now' }, targets: [{ query: "select value from series where $timeFilter", rawQuery: true }] @@ -73,7 +73,7 @@ define([ describe('When issuing annotation query', function() { var results; var urlExpected = "/series?p=mupp&q=select+title+from+events.backend_01"+ - "+where+time+%3E+now()+-+1h&time_precision=s"; + "+where+time+%3E+now()+-+1h"; var range = { from: 'now-1h', to: 'now' }; var annotation = { query: 'select title from events.$server where $timeFilter' }; From 882a477c0fc2ff4e2a900c0c082a54cdab3a75ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Nov 2014 09:09:05 +0100 Subject: [PATCH 110/125] Graph: increase decimal precision in tooltip and legend values when unit scaling is applied, #1043 --- src/app/directives/grafanaGraph.js | 2 +- src/app/panels/graph/legend.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 5791d346a38..66902f5ee24 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -106,7 +106,7 @@ function (angular, $, kbn, moment, _, GraphTooltip) { var series = data[i]; var axis = yaxis[series.yaxis - 1]; var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]]; - series.updateLegendValues(formater, axis.tickDecimals, axis.scaledDecimals); + series.updateLegendValues(formater, axis.tickDecimals, axis.scaledDecimals+ 2); if(!scope.$$phase) { scope.$digest(); } } } diff --git a/src/app/panels/graph/legend.js b/src/app/panels/graph/legend.js index af695d28f2b..9944ef7ee5b 100644 --- a/src/app/panels/graph/legend.js +++ b/src/app/panels/graph/legend.js @@ -53,11 +53,16 @@ function (angular, app, _, kbn, $) { scope.toggleSeries(seriesInfo, e); } + function sortLegend(e) { + + } + function render() { if (firstRender) { elem.append($container); $container.on('click', '.graph-legend-icon', openColorSelector); $container.on('click', '.graph-legend-alias', toggleSeries); + $container.on('click', 'th', sortLegend); firstRender = false; } From 0b966b7a287a1b954775b984d0cc560b5d9e0eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Nov 2014 10:11:01 +0100 Subject: [PATCH 111/125] Graph: legend sorting while in table mode implemented, panel persisted property, Closes #1030 --- src/app/directives/grafanaGraph.js | 2 +- src/app/panels/graph/legend.js | 56 ++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 66902f5ee24..fd58473a1d5 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -106,7 +106,7 @@ function (angular, $, kbn, moment, _, GraphTooltip) { var series = data[i]; var axis = yaxis[series.yaxis - 1]; var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]]; - series.updateLegendValues(formater, axis.tickDecimals, axis.scaledDecimals+ 2); + series.updateLegendValues(formater, axis.tickDecimals, axis.scaledDecimals + 2); if(!scope.$$phase) { scope.$digest(); } } } diff --git a/src/app/panels/graph/legend.js b/src/app/panels/graph/legend.js index 9944ef7ee5b..65a82760bf2 100644 --- a/src/app/panels/graph/legend.js +++ b/src/app/panels/graph/legend.js @@ -20,6 +20,7 @@ function (angular, app, _, kbn, $) { var firstRender = true; var panel = scope.panel; var data; + var seriesList; var i; scope.$on('render', function() { @@ -36,7 +37,7 @@ function (angular, app, _, kbn, $) { function openColorSelector(e) { var el = $(e.currentTarget); var index = getSeriesIndexForElement(el); - var seriesInfo = data[index]; + var seriesInfo = seriesList[index]; var popoverScope = scope.$new(); popoverScope.series = seriesInfo; popoverSrv.show({ @@ -49,12 +50,35 @@ function (angular, app, _, kbn, $) { function toggleSeries(e) { var el = $(e.currentTarget); var index = getSeriesIndexForElement(el); - var seriesInfo = data[index]; + var seriesInfo = seriesList[index]; scope.toggleSeries(seriesInfo, e); } function sortLegend(e) { + var el = $(e.currentTarget); + if (panel.legend.sortDesc === false) { + panel.legend.sort = null; + panel.legend.sortDesc = null; + render(); + return; + } + + panel.legend.sortDesc = !panel.legend.sortDesc; + panel.legend.sort = el.data('stat'); + render(); + } + + function getTableHeaderHtml(statName) { + if (!panel.legend[statName]) { return ""; } + var html = '' + statName; + + if (panel.legend.sort === statName) { + var cssClass = panel.legend.sortDesc ? 'icon-caret-down' : 'icon-caret-up' ; + html += ' '; + } + + return html + ''; } function render() { @@ -66,27 +90,37 @@ function (angular, app, _, kbn, $) { firstRender = false; } + seriesList = data; + $container.empty(); $container.toggleClass('graph-legend-table', panel.legend.alignAsTable === true); if (panel.legend.alignAsTable) { var header = ''; - header += ''; - header += ''; + header += ''; if (panel.legend.values) { - if (panel.legend.min) { header += 'min
    '; } - if (panel.legend.max) { header += 'max
    '; } - if (panel.legend.avg) { header += 'avg
    '; } - if (panel.legend.current) { header += 'current
    '; } - if (panel.legend.total) { header += 'total
    '; } + header += getTableHeaderHtml('min'); + header += getTableHeaderHtml('max'); + header += getTableHeaderHtml('avg'); + header += getTableHeaderHtml('current'); + header += getTableHeaderHtml('total'); } header += ''; $container.append($(header)); } - for (i = 0; i < data.length; i++) { - var series = data[i]; + if (panel.legend.sort) { + seriesList = _.sortBy(seriesList, function(series) { + return series.stats[panel.legend.sort]; + }); + if (panel.legend.sortDesc) { + seriesList = seriesList.reverse(); + } + } + + for (i = 0; i < seriesList.length; i++) { + var series = seriesList[i]; var html = '
    ' + - '{{panel.title | interpolateTemplateVars}}' + + '{{panel.title | interpolateTemplateVars}}' + '' + ''; diff --git a/src/app/services/annotationsSrv.js b/src/app/services/annotationsSrv.js index 25c76caeb77..fdc5e9ca745 100644 --- a/src/app/services/annotationsSrv.js +++ b/src/app/services/annotationsSrv.js @@ -58,7 +58,7 @@ define([ function errorHandler(err) { console.log('Annotation error: ', err); - var message = err.message || "Aannotation query failed"; + var message = err.message || "Annotation query failed"; alertSrv.set('Annotations error', message,'error'); } From 2fb176a2444b18a889898d19d89cd9e9843cba85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Nov 2014 12:57:22 +0100 Subject: [PATCH 115/125] Graphite: fix for graphite query editor, when adding functions and the function selection dropdown get push down the selection did not work properly, Fixes #1038 --- src/app/directives/addGraphiteFunc.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/directives/addGraphiteFunc.js b/src/app/directives/addGraphiteFunc.js index e66689969ca..b585e7d003f 100644 --- a/src/app/directives/addGraphiteFunc.js +++ b/src/app/directives/addGraphiteFunc.js @@ -68,13 +68,12 @@ function (angular, app, _, $, gfunc) { }); $input.blur(function() { - $input.hide(); - $input.val(''); - $button.show(); - $button.focus(); // clicking the function dropdown menu wont // work if you remove class at once setTimeout(function() { + $input.val(''); + $input.hide(); + $button.show(); elem.removeClass('open'); }, 200); }); From 3e2c898881447e09d4f2d7abefc2d5cabe491f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Nov 2014 13:40:22 +0100 Subject: [PATCH 116/125] Graph: legend fix for current legend value --- src/app/panels/graph/legend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/panels/graph/legend.js b/src/app/panels/graph/legend.js index 8c9cd3fad3f..f1367df8617 100644 --- a/src/app/panels/graph/legend.js +++ b/src/app/panels/graph/legend.js @@ -147,7 +147,7 @@ function (angular, app, _, kbn, $) { if (panel.legend.min) { html += '
    ' + min + '
    '; } if (panel.legend.max) { html += '
    ' + max + '
    '; } if (panel.legend.avg) { html += '
    ' + avg + '
    '; } - if (panel.legend.current) { html += '
    ' + current + '
    '; } + if (panel.legend.current) { html += '
    ' + current + '
    '; } if (panel.legend.total) { html += '
    ' + total + '
    '; } } From 4eb4974909d4af4cf55f49553585a2722101654d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Nov 2014 13:47:06 +0100 Subject: [PATCH 117/125] Graph: change to current legend value handling, if last value is null, current will pick next to last value, Closes #190 --- src/app/components/timeSeries.js | 3 +++ src/test/specs/timeSeries-specs.js | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index 6d54239a0bc..f9329141640 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -112,6 +112,9 @@ function (_, kbn) { if (result.length) { this.stats.avg = (this.stats.total / result.length); this.stats.current = result[result.length-1][1]; + if (this.stats.current === null && result.length > 1) { + this.stats.current = result[result.length-2][1]; + } } return result; diff --git a/src/test/specs/timeSeries-specs.js b/src/test/specs/timeSeries-specs.js index a2c52dadf57..e68ce16d54e 100644 --- a/src/test/specs/timeSeries-specs.js +++ b/src/test/specs/timeSeries-specs.js @@ -26,6 +26,15 @@ define([ expect(points.length).to.be(4); expect(points[1][1]).to.be(0); }); + + it('if last is null current should pick next to last', function() { + series = new TimeSeries({ + datapoints: [[10,1], [null, 2]] + }); + series.getFlotPairs('null', yAxisFormats); + expect(series.stats.current).to.be(10); + }); + }); describe('series overrides', function() { From f2a6657b72222c59b9d9adf03be028cf14cb1507 Mon Sep 17 00:00:00 2001 From: Andreas Stenius Date: Thu, 13 Nov 2014 14:29:42 +0100 Subject: [PATCH 118/125] panelmeta: fix metrics tab title. --- src/app/components/panelmeta.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/panelmeta.js b/src/app/components/panelmeta.js index 1039641f742..4fd97e9d02d 100644 --- a/src/app/components/panelmeta.js +++ b/src/app/components/panelmeta.js @@ -22,7 +22,7 @@ function () { this.addEditorTab('General', 'app/partials/panelgeneral.html'); if (options.metricsEditor) { - this.addEditorTab('General', 'app/partials/metrics.html'); + this.addEditorTab('Metrics', 'app/partials/metrics.html'); } this.addExtendedMenuItem('Panel JSON', '', 'editPanelJson()'); From b172e7afdc20a3f1bb742684d20a33cb6a0c72df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Nov 2014 13:52:42 +0100 Subject: [PATCH 119/125] SinglestatPanel: mini fix --- src/app/panels/singlestat/module.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/panels/singlestat/module.js b/src/app/panels/singlestat/module.js index 32eb2dd90ff..e5297b30069 100644 --- a/src/app/panels/singlestat/module.js +++ b/src/app/panels/singlestat/module.js @@ -95,7 +95,7 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) { $scope.seriesHandler = function(seriesData) { var series = new TimeSeries({ datapoints: seriesData.datapoints, - info: { alias: seriesData.target }, + alias: seriesData.target, }); series.flotpairs = series.getFlotPairs('connected'); From 104493e7254f7f79f37b2342b11ec416b0b88c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Nov 2014 15:22:12 +0100 Subject: [PATCH 120/125] SharePanel: fix for interval variables and auto value, Fixes #1056 --- src/app/services/templateValuesSrv.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/services/templateValuesSrv.js b/src/app/services/templateValuesSrv.js index 9b80c336f4b..51b11dfb3fa 100644 --- a/src/app/services/templateValuesSrv.js +++ b/src/app/services/templateValuesSrv.js @@ -30,6 +30,7 @@ function (angular, _, kbn) { var option = _.findWhere(variable.options, { text: urlValue }); option = option || { text: urlValue, value: urlValue }; this.setVariableValue(variable, option, true); + this.updateAutoInterval(variable); } else if (variable.refresh) { this.updateOptions(variable); From d4adaaaf2b8a9c1af829f1307be18f8863486751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Nov 2014 08:19:49 +0100 Subject: [PATCH 121/125] Graphite: annotations stop working after recent change to millisecond resolution, Fixes #1061 --- src/app/services/graphite/graphiteDatasource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/services/graphite/graphiteDatasource.js b/src/app/services/graphite/graphiteDatasource.js index 10d3e0dae7f..ee1312f7bd7 100644 --- a/src/app/services/graphite/graphiteDatasource.js +++ b/src/app/services/graphite/graphiteDatasource.js @@ -95,7 +95,7 @@ function (angular, _, $, config, kbn, moment) { list.push({ annotation: annotation, - time: datapoint[1] * 1000, + time: datapoint[1], title: target.target }); } From 873d3d7c4a611a964a64a56fc51076bc49d57841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Nov 2014 09:33:03 +0100 Subject: [PATCH 122/125] SinglestatPanel: Added integration with drilldown link feature, if a drilldown link is present the entire singlestat panel will act as a link, with hover tooltip containing the link name, #951, #1041 --- src/app/directives/panelMenu.js | 3 +- src/app/panels/singlestat/module.js | 1 + src/app/panels/singlestat/singleStatPanel.js | 33 ++++++++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/app/directives/panelMenu.js b/src/app/directives/panelMenu.js index 892062b9bd5..a4eb3bd210d 100644 --- a/src/app/directives/panelMenu.js +++ b/src/app/directives/panelMenu.js @@ -68,7 +68,8 @@ function (angular, $, _) { elem.append($link); $scope.$watchCollection('panel.links', function(newValue) { - $link.toggleClass('has-panel-links', newValue ? newValue.length > 0 : false); + var showIcon = (newValue ? newValue.length > 0 : false) && $scope.panel.title !== ''; + $link.toggleClass('has-panel-links', showIcon); }); function dismiss(time) { diff --git a/src/app/panels/singlestat/module.js b/src/app/panels/singlestat/module.js index e5297b30069..c17b5afea38 100644 --- a/src/app/panels/singlestat/module.js +++ b/src/app/panels/singlestat/module.js @@ -27,6 +27,7 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) { // Set and populate defaults var _d = { + links: [], maxDataPoints: 100, interval: null, targets: [{}], diff --git a/src/app/panels/singlestat/singleStatPanel.js b/src/app/panels/singlestat/singleStatPanel.js index 2c9f8b2613b..ccfd3ad8045 100644 --- a/src/app/panels/singlestat/singleStatPanel.js +++ b/src/app/panels/singlestat/singleStatPanel.js @@ -11,7 +11,7 @@ function (angular, app, _, $) { var module = angular.module('grafana.panels.singlestat', []); app.useModule(module); - module.directive('singlestatPanel', function() { + module.directive('singlestatPanel', function($location, linkSrv, $timeout) { return { link: function(scope, elem) { @@ -100,7 +100,7 @@ function (angular, app, _, $) { plotCss.bottom = "0px"; plotCss.left = "-5px"; plotCss.width = (width - 10) + 'px'; - plotCss.height = Math.floor(height * 0.3) + "px"; + plotCss.height = Math.floor(height * 0.25) + "px"; } plotCanvas.css(plotCss); @@ -167,7 +167,36 @@ function (angular, app, _, $) { if (panel.sparkline.show) { addSparkline(); } + + elem.toggleClass('pointer', panel.links.length > 0); } + + // drilldown link tooltip + var drilldownTooltip = $('
    gello
    "'); + + elem.mouseleave(function() { + if (panel.links.length === 0) { return;} + drilldownTooltip.detach(); + }); + + elem.click(function() { + if (panel.links.length === 0) { return; } + + var linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0]); + if (linkInfo.href[0] === '#') { linkInfo.href = linkInfo.href.substring(1); } + + $timeout(function() { $location.path(linkInfo.href); }); + + drilldownTooltip.detach(); + }); + + elem.mousemove(function(e) { + if (panel.links.length === 0) { return;} + + drilldownTooltip.text('click to go to: ' + panel.links[0].title); + + drilldownTooltip.place_tt(e.clientX+20, e.clientY-15); + }); } }; }); From 8ad83b8d58da9c8cef85aa1b98e8d67289ee11be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Nov 2014 11:36:20 +0100 Subject: [PATCH 123/125] SinglestatPanel: small fix for drilldown link click while fullscreen mode, #1041 --- src/app/panels/singlestat/singleStatPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/panels/singlestat/singleStatPanel.js b/src/app/panels/singlestat/singleStatPanel.js index ccfd3ad8045..86d56d16595 100644 --- a/src/app/panels/singlestat/singleStatPanel.js +++ b/src/app/panels/singlestat/singleStatPanel.js @@ -185,7 +185,7 @@ function (angular, app, _, $) { var linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0]); if (linkInfo.href[0] === '#') { linkInfo.href = linkInfo.href.substring(1); } - $timeout(function() { $location.path(linkInfo.href); }); + $timeout(function() { $location.search({}); $location.path(linkInfo.href); }); drilldownTooltip.detach(); }); From c11ce99bb311be25e4fd168b845d93efe80dd2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Nov 2014 11:41:36 +0100 Subject: [PATCH 124/125] SinglestatPanel: another small fix for drilldown link click while fullscreen mode, #1041 --- src/app/panels/singlestat/singleStatPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/panels/singlestat/singleStatPanel.js b/src/app/panels/singlestat/singleStatPanel.js index 86d56d16595..d1131f29e94 100644 --- a/src/app/panels/singlestat/singleStatPanel.js +++ b/src/app/panels/singlestat/singleStatPanel.js @@ -185,7 +185,7 @@ function (angular, app, _, $) { var linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0]); if (linkInfo.href[0] === '#') { linkInfo.href = linkInfo.href.substring(1); } - $timeout(function() { $location.search({}); $location.path(linkInfo.href); }); + $timeout(function() { $location.url(linkInfo.href); }); drilldownTooltip.detach(); }); From 640c558446b573b656074ef6f5f3e9d4e3862466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 14 Nov 2014 12:12:09 +0100 Subject: [PATCH 125/125] Graph: added export graph time series data as csv file feature, accessed from panel menu dropdown, #861 --- CHANGELOG.md | 1 + src/app/components/kbn.js | 11 +++++++++++ src/app/panels/graph/module.js | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b68728cb44..090af032f30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [Issue #850](https://github.com/grafana/grafana/issues/850). Graph: Shared tooltip that shows multiple series & crosshair line, thx @toni-moreno - [Issue #940](https://github.com/grafana/grafana/issues/940). Graph: New series style override option "Fill below to", useful to visualize max & min as a shadow for the mean - [Issue #1030](https://github.com/grafana/grafana/issues/1030). Graph: Legend table display/look changed, now includes column headers for min/max/avg, and full width (unless on right side) +- [Issue #861](https://github.com/grafana/grafana/issues/861). Graph: Export graph time series data as csv file **New Panels** - [Issue #951](https://github.com/grafana/grafana/issues/951). SingleStat: New singlestat panel diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index 567e7c78f23..ecef4bd164e 100644 --- a/src/app/components/kbn.js +++ b/src/app/components/kbn.js @@ -459,6 +459,17 @@ function($, _, moment) { .replace(/ +/g,'-'); }; + kbn.exportSeriesListToCsv = function(seriesList) { + var text = 'Series;Time;Value\n'; + _.each(seriesList, function(series) { + _.each(series.datapoints, function(dp) { + text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n'; + }); + }); + var blob = new Blob([text], { type: "text/csv;charset=utf-8" }); + window.saveAs(blob, 'grafana_data_export.csv'); + }; + kbn.stringToJsRegex = function(str) { if (str[0] !== '/') { return new RegExp(str); diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 9e4ee0fd336..90b6957b8b5 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -30,6 +30,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html'); $scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html'); + $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()'); $scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()'); // Set and populate defaults @@ -283,6 +284,10 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.get_data(); }; + $scope.exportCsv = function() { + kbn.exportSeriesListToCsv($scope.seriesList); + }; + panelSrv.init($scope); });