Procházet zdrojové kódy

Merge branch 'master' into valuepanel

Torkel Ödegaard před 11 roky
rodič
revize
ed8dd03fa1

+ 84 - 0
'

@@ -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);
+    });
+  });
+
+};
+

+ 5 - 0
CHANGELOG.md

@@ -11,10 +11,15 @@
 **Misc**
 **Misc**
 - [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory
 - [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 #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
 
 
 **Fixes**
 **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 #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 #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)
 # 1.8.1 (2014-09-30)

+ 1 - 0
src/app/components/require.config.js

@@ -3,6 +3,7 @@
  */
  */
 require.config({
 require.config({
   baseUrl: 'app',
   baseUrl: 'app',
+  urlArgs: 'bust=' + (new Date().getTime()),
 
 
   paths: {
   paths: {
     config:                   ['../config', '../config.sample'],
     config:                   ['../config', '../config.sample'],

+ 95 - 0
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);
+      }
+    });
+  });
+
+};
+

+ 6 - 4
src/app/directives/grafanaGraph.tooltip.js

@@ -144,8 +144,9 @@ function ($) {
           hoverInfo = seriesHoverInfo[i];
           hoverInfo = seriesHoverInfo[i];
           value = series.formatValue(hoverInfo.value);
           value = series.formatValue(hoverInfo.value);
 
 
-          group = '<i class="icon-minus" style="color:' + series.color +';"></i> ' + series.label;
-          seriesHtml = group + ': <span class="graph-tooltip-value">' + value + '</span><br>' + seriesHtml;
+          seriesHtml += '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
+          seriesHtml += '<i class="icon-minus" style="color:' + series.color +';"></i> ' + series.label + ':</div>';
+          seriesHtml += '<div class="graph-tooltip-value">' + value + '</div></div>';
           plot.highlight(i, hoverInfo.hoverIndex);
           plot.highlight(i, hoverInfo.hoverIndex);
         }
         }
 
 
@@ -154,7 +155,8 @@ function ($) {
       // single series tooltip
       // single series tooltip
       else if (item) {
       else if (item) {
         series = seriesList[item.seriesIndex];
         series = seriesList[item.seriesIndex];
-        group = '<i class="icon-minus" style="color:' + item.series.color +';"></i> ' + series.label;
+        group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
+        group += '<i class="icon-minus" style="color:' + item.series.color +';"></i> ' + series.label + ':</div>';
 
 
         if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
         if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
           value = item.datapoint[1] - item.datapoint[2];
           value = item.datapoint[1] - item.datapoint[2];
@@ -165,7 +167,7 @@ function ($) {
 
 
         value = series.formatValue(value);
         value = series.formatValue(value);
         timestamp = dashboard.formatDate(item.datapoint[0]);
         timestamp = dashboard.formatDate(item.datapoint[0]);
-        group += ': <span class="graph-tooltip-value">' + value + '</span><br>';
+        group += '<div class="graph-tooltip-value">' + value + '</div>';
 
 
         self.showTooltip(timestamp, group, pos);
         self.showTooltip(timestamp, group, pos);
       }
       }

+ 1 - 0
src/app/panels/graph/module.js

@@ -202,6 +202,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
           $scope.panelMeta.loading = false;
           $scope.panelMeta.loading = false;
           $scope.panelMeta.error = err.message || "Timeseries data request error";
           $scope.panelMeta.error = err.message || "Timeseries data request error";
           $scope.inspector.error = err;
           $scope.inspector.error = err;
+          $scope.legend = [];
           $scope.render([]);
           $scope.render([]);
         });
         });
     };
     };

+ 26 - 0
src/app/partials/opentsdb/editor.html

@@ -89,6 +89,32 @@
                    ng-model="target.isCounter"
                    ng-model="target.isCounter"
                    ng-change="targetBlur()">
                    ng-change="targetBlur()">
           </li>
           </li>
+          <li class="grafana-target-segment" ng-hide="!target.isCounter">
+            Counter Max:
+          </li>
+          <li ng-hide="!target.isCounter">
+            <input type="text"
+                   class="grafana-target-segment-input input-medium"
+                   ng-disabled="!target.shouldComputeRate"
+                   ng-model="target.counterMax"
+                   spellcheck='false'
+                   placeholder="Counter max value"
+                   ng-blur="targetBlur()"
+                   />
+          </li>
+          <li class="grafana-target-segment" ng-hide="!target.isCounter">
+            Counter Reset Value:
+          </li>
+          <li ng-hide="!target.isCounter">
+            <input type="text"
+                   class="grafana-target-segment-input input-medium"
+                   ng-disabled="!target.shouldComputeRate"
+                   ng-model="target.counterResetValue"
+                   spellcheck='false'
+                   placeholder="Counter reset value"
+                   ng-blur="targetBlur()"
+                   />
+          </li>
           <li class="grafana-target-segment">
           <li class="grafana-target-segment">
             Alias:
             Alias:
           </li>
           </li>

+ 12 - 5
src/app/routes/dashboard-from-script.js

@@ -16,21 +16,28 @@ function (angular, $, config, _, kbn, moment) {
       .when('/dashboard/script/:jsFile', {
       .when('/dashboard/script/:jsFile', {
         templateUrl: 'app/partials/dashboard.html',
         templateUrl: 'app/partials/dashboard.html',
         controller : 'DashFromScriptProvider',
         controller : 'DashFromScriptProvider',
+        reloadOnSearch: false,
       });
       });
   });
   });
 
 
-  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 execute_script = function(result) {
+      var services = {
+        dashboardSrv: dashboardSrv,
+        datasourceSrv: datasourceSrv,
+        $q: $q,
+      };
+
       /*jshint -W054 */
       /*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
       // Handle async dashboard scripts
       if (_.isFunction(script_result)) {
       if (_.isFunction(script_result)) {
         var deferred = $q.defer();
         var deferred = $q.defer();
         script_result(function(dashboard) {
         script_result(function(dashboard) {
-          $rootScope.$apply(function() {
+          $timeout(function() {
             deferred.resolve({ data: dashboard });
             deferred.resolve({ data: dashboard });
           });
           });
         });
         });
@@ -47,7 +54,7 @@ function (angular, $, config, _, kbn, moment) {
       .then(execute_script)
       .then(execute_script)
       .then(null,function(err) {
       .then(null,function(err) {
         console.log('Script dashboard error '+ err);
         console.log('Script dashboard error '+ err);
-        alertSrv.set('Error', "Could not load <i>scripts/"+file+"</i>. 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;
         return false;
       });
       });
     };
     };

+ 1 - 1
src/app/services/influxdb/influxQueryBuilder.js

@@ -18,7 +18,7 @@ function () {
     var query = 'select ';
     var query = 'select ';
     var seriesName = target.series;
     var seriesName = target.series;
 
 
-    if(!seriesName.match('^/.*/')) {
+    if(!seriesName.match('^/.*/') && !seriesName.match(/^merge\(.*\)/)) {
       seriesName = '"' + seriesName+ '"';
       seriesName = '"' + seriesName+ '"';
     }
     }
 
 

+ 1 - 1
src/app/services/influxdb/influxdbDatasource.js

@@ -44,7 +44,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
 
 
         // replace grafana variables
         // replace grafana variables
         query = query.replace('$timeFilter', timeFilter);
         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
         // replace templated variables
         query = templateSrv.replace(query);
         query = templateSrv.replace(query);

+ 18 - 4
src/app/services/opentsdb/opentsdbDatasource.js

@@ -1,14 +1,15 @@
 define([
 define([
   'angular',
   'angular',
   'lodash',
   'lodash',
-  'kbn'
+  'kbn',
+  'moment'
 ],
 ],
 function (angular, _, kbn) {
 function (angular, _, kbn) {
   'use strict';
   'use strict';
 
 
   var module = angular.module('grafana.services');
   var module = angular.module('grafana.services');
 
 
-  module.factory('OpenTSDBDatasource', function($q, $http) {
+  module.factory('OpenTSDBDatasource', function($q, $http, templateSrv) {
 
 
     function OpenTSDBDatasource(datasource) {
     function OpenTSDBDatasource(datasource) {
       this.type = 'opentsdb';
       this.type = 'opentsdb';
@@ -123,12 +124,12 @@ function (angular, _, kbn) {
       }
       }
 
 
       var query = {
       var query = {
-        metric: target.metric,
+        metric: templateSrv.replace(target.metric),
         aggregator: "avg"
         aggregator: "avg"
       };
       };
 
 
       if (target.aggregator) {
       if (target.aggregator) {
-        query.aggregator = target.aggregator;
+        query.aggregator = templateSrv.replace(target.aggregator);
       }
       }
 
 
       if (target.shouldComputeRate) {
       if (target.shouldComputeRate) {
@@ -136,6 +137,14 @@ function (angular, _, kbn) {
         query.rateOptions = {
         query.rateOptions = {
           counter: !!target.isCounter
           counter: !!target.isCounter
         };
         };
+
+        if (target.counterMax && target.counterMax.length) {
+          query.rateOptions.counterMax = parseInt(target.counterMax);
+        }
+
+        if (target.counterResetValue && target.counterResetValue.length) {
+          query.rateOptions.resetValue = parseInt(target.counterResetValue);
+        }
       }
       }
 
 
       if (target.shouldDownsample) {
       if (target.shouldDownsample) {
@@ -143,6 +152,11 @@ function (angular, _, kbn) {
       }
       }
 
 
       query.tags = angular.copy(target.tags);
       query.tags = angular.copy(target.tags);
+      if(query.tags){
+        for(var key in query.tags){
+          query.tags[key] = templateSrv.replace(query.tags[key]);
+        }
+      }
 
 
       return query;
       return query;
     }
     }

+ 1 - 1
src/css/less/grafana.less

@@ -16,7 +16,7 @@
 
 
 .hide-controls {
 .hide-controls {
   padding: 0;
   padding: 0;
-  .row-control-inner {
+  .row-tab {
     display: none;
     display: none;
   }
   }
   .submenu-controls {
   .submenu-controls {

+ 10 - 1
src/css/less/graph.less

@@ -178,9 +178,18 @@
     top: -3px;
     top: -3px;
   }
   }
 
 
+  .graph-tooltip-list-item {
+    display: table-row;
+  }
+
+  .graph-tooltip-series-name {
+    display: table-cell;
+  }
+
   .graph-tooltip-value {
   .graph-tooltip-value {
+    display: table-cell;
     font-weight: bold;
     font-weight: bold;
-    float: right;
     padding-left: 10px;
     padding-left: 10px;
+    text-align: right;
   }
   }
 }
 }

+ 1 - 1
src/plugins/datasource.example.js

@@ -19,7 +19,7 @@ function (angular, _, kbn) {
       this.url = datasource.url;
       this.url = datasource.url;
     }
     }
 
 
-    CustomDatasource.prototype.query = function(filterSrv, options) {
+    CustomDatasource.prototype.query = function(options) {
       // get from & to in seconds
       // get from & to in seconds
       var from = kbn.parseDate(options.range.from).getTime() / 1000;
       var from = kbn.parseDate(options.range.from).getTime() / 1000;
       var to = kbn.parseDate(options.range.to).getTime() / 1000;
       var to = kbn.parseDate(options.range.to).getTime() / 1000;

+ 14 - 0
src/test/specs/graph-ctrl-specs.js

@@ -36,6 +36,20 @@ define([
         var data = ctx.scope.render.getCall(0).args[0];
         var data = ctx.scope.render.getCall(0).args[0];
         expect(data.length).to.be(2);
         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([]);
+        });
+
+      });
+
     });
     });
 
 
   });
   });

+ 29 - 0
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'); 
+      });
+
+    });
+
   });
   });
 
 
 });
 });

+ 1 - 0
tasks/options/ngmin.js

@@ -10,6 +10,7 @@ module.exports = function(config) {
         'app/filters/**/*.js',
         'app/filters/**/*.js',
         'app/panels/**/*.js',
         'app/panels/**/*.js',
         'app/routes/**/*.js',
         'app/routes/**/*.js',
+        'plugins/**/*.js',
         'app/app.js',
         'app/app.js',
         'vendor/angular/**/*.js',
         'vendor/angular/**/*.js',
       ],
       ],