Explorar el Código

More progress on influxdb query editor, templating, refactoring, unit tests, #740, #507, #586

Torkel Ödegaard hace 11 años
padre
commit
2da04e72f5

+ 12 - 3
src/app/controllers/influxTargetCtrl.js

@@ -11,8 +11,17 @@ function (angular) {
   module.controller('InfluxTargetCtrl', function($scope, $timeout) {
 
     $scope.init = function() {
-      $scope.target.function = $scope.target.function || 'mean';
-      $scope.target.column = $scope.target.column || 'value';
+      var target = $scope.target;
+
+      target.function = target.function || 'mean';
+      target.column = target.column || 'value';
+
+      if (target.condition_value) {
+        target.condition_expression = target.condition_key + ' ' + target.condition_op + ' ' + target.condition_value;
+        delete target.condition_key;
+        delete target.condition_op;
+        delete target.condition_value;
+      }
 
       $scope.rawQuery = false;
 
@@ -24,7 +33,7 @@ function (angular) {
       ];
 
       $scope.operators = ['=', '=~', '>', '<', '!~', '<>'];
-      $scope.oldSeries = $scope.target.series;
+      $scope.oldSeries = target.series;
       $scope.$on('typeahead-updated', function() {
         $timeout($scope.get_data);
       });

+ 1 - 1
src/app/directives/templateParamSelector.js

@@ -11,7 +11,7 @@ function (angular, app, _, $) {
     .module('grafana.directives')
     .directive('templateParamSelector', function($compile) {
       var inputTemplate = '<input type="text" data-provide="typeahead" ' +
-                            ' class="grafana-target-segment-input input-medium"' +
+                            ' class="grafana-target-text-input input-medium"' +
                             ' spellcheck="false" style="display:none"></input>';
 
       var buttonTemplate = '<a  class="grafana-target-segment tabindex="1">{{variable.current.text}}</a>';

+ 2 - 2
src/app/partials/influxdb/annotation_editor.html

@@ -1,8 +1,8 @@
 <div class="editor-row">
 	<div class="section">
-		<h5>InfluxDB Query <tip>Example: select text from events where [[timeFilter]]</tip></h5>
+		<h5>InfluxDB Query <tip>Example: select text from events where [[$timeFilter]]</tip></h5>
 		<div class="editor-option">
-			<input type="text" class="span10" ng-model='currentAnnotation.query' placeholder="select text from events where [[timeFilter]]"></input>
+			<input type="text" class="span10" ng-model='currentAnnotation.query' placeholder="select text from events where [[$timeFilter]]"></input>
 		</div>
 	</div>
 </div>

+ 12 - 12
src/app/partials/influxdb/editor.html

@@ -72,6 +72,16 @@
                    data-min-length=0 data-items=100
                    ng-blur="seriesBlur()">
           </li>
+
+					<li class="grafana-target-segment">
+						alias
+					</li>
+
+					<li>
+						<input type="text" class="input-medium grafana-target-text-input" ng-model="target.alias"
+						spellcheck='false' placeholder="alias" ng-blur="get_data()">
+					</li>
+
         </ul>
 
         <div class="clearfix"></div>
@@ -102,16 +112,6 @@
 						<span influxdb-func-editor class="grafana-target-segment grafana-target-function">
 						</span>
 					</li>
-
-					<li class="grafana-target-segment">
-						alias
-					</li>
-
-					<li>
-						<input type="text" class="input-medium grafana-target-segment-input" ng-model="target.alias"
-									 spellcheck='false' placeholder="alias" ng-blur="get_data()">
-					</li>
-
 					<li>
 						<a class="grafana-target-segment" ng-click="target.condition_filter = !target.condition_filter; get_data();"
 								bs-tooltip="'Add a where clause'" role="menuitem" data-placement="right">
@@ -129,7 +129,7 @@
 					</li>
 
 					<li>
-						<input type="text" class="input-mini grafana-target-segment-input" ng-model="target.interval"
+						<input type="text" class="input-mini grafana-target-text-input" ng-model="target.interval"
 									 spellcheck='false' placeholder="{{interval}}" data-placement="right"
 									 bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
 									 ng-model-onblur ng-change="get_data()" >
@@ -142,7 +142,7 @@
 					</li>
 
 					<li ng-show="target.groupby_field_add">
-						<input type="text" class="input-small grafana-target-segment-input" ng-model="target.groupby_field"
+						<input type="text" class="input-small grafana-target-text-input" ng-model="target.groupby_field"
 									 placeholder="column" spellcheck="false" bs-typeahead="listColumns" data-min-length=0 ng-blur="get_data()">
 					</li>
 

+ 1 - 1
src/app/partials/templating_editor.html

@@ -101,7 +101,7 @@
 							</div>
 							<div class="editor-option" ng-show="current.includeAll">
 								<label class="small">All format</label>
-								<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex all values', 'comma list', 'custom']" ng-change="typeChanged()"></select>
+								<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex all values', 'comma list', 'custom']" ng-change="typeChanged()"></select>
 							</div>
 							<div class="editor-option" ng-show="current.includeAll">
 								<label class="small">All value</label>

+ 81 - 0
src/app/services/influxdb/influxQueryBuilder.js

@@ -0,0 +1,81 @@
+define([
+],
+function () {
+  'use strict';
+
+  function InfluxQueryBuilder(target) {
+    this.target = target;
+  }
+
+  var p = InfluxQueryBuilder.prototype;
+
+  p.build = function() {
+    return this.target.rawQuery ? this._modifyRawQuery() : this._buildQuery();
+  };
+
+  p._buildQuery = function() {
+    var target = this.target;
+    var query = 'select ';
+    var seriesName = target.series;
+
+    if(!seriesName.match('^/.*/')) {
+      seriesName = '"' + seriesName+ '"';
+    }
+
+    if (target.groupby_field_add) {
+      query += target.groupby_field + ', ';
+    }
+
+    query +=  target.function + '(' + target.column + ')';
+    query += ' from ' + seriesName + ' where [[$timeFilter]]';
+
+    if (target.condition_filter) {
+      query += ' and ' + target.condition_expression;
+    }
+
+    query += ' group by time([[$interval]])';
+
+    if (target.groupby_field_add) {
+      query += ', ' + target.groupby_field;
+      this.groupByField = target.groupby_field;
+    }
+
+    query += " order asc";
+
+    return query;
+  };
+
+  p._modifyRawQuery = function () {
+    var query = this.target.query.replace(";", "");
+
+    var queryElements = query.split(" ");
+    var lowerCaseQueryElements = query.toLowerCase().split(" ");
+    var whereIndex = lowerCaseQueryElements.indexOf("where");
+    var groupByIndex = lowerCaseQueryElements.indexOf("group");
+    var orderIndex = lowerCaseQueryElements.indexOf("order");
+
+    if (lowerCaseQueryElements[1].indexOf(',') !== -1) {
+      this.groupByField = lowerCaseQueryElements[1].replace(',', '');
+    }
+
+    if (whereIndex !== -1) {
+      queryElements.splice(whereIndex + 1, 0, '[[$timeFilter]]', "and");
+    }
+    else {
+      if (groupByIndex !== -1) {
+        queryElements.splice(groupByIndex, 0, "where", '[[$timeFilter]]');
+      }
+      else if (orderIndex !== -1) {
+        queryElements.splice(orderIndex, 0, "where", '[[$timeFilter]]');
+      }
+      else {
+        queryElements.push("where");
+        queryElements.push('[[$timeFilter]]');
+      }
+    }
+
+    return queryElements.join(" ");
+  };
+
+  return InfluxQueryBuilder;
+});

+ 15 - 80
src/app/services/influxdb/influxdbDatasource.js

@@ -2,9 +2,10 @@ define([
   'angular',
   'lodash',
   'kbn',
-  './influxSeries'
+  './influxSeries',
+  './influxQueryBuilder'
 ],
-function (angular, _, kbn, InfluxSeries) {
+function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
   'use strict';
 
   var module = angular.module('grafana.services');
@@ -32,90 +33,25 @@ function (angular, _, kbn, InfluxSeries) {
     }
 
     InfluxDatasource.prototype.query = function(options) {
-      var promises = _.map(options.targets, function(target) {
-        var query;
-        var alias = '';
+      var timeFilter = getTimeFilter(options);
 
+      var promises = _.map(options.targets, function(target) {
         if (target.hide || !((target.series && target.column) || target.query)) {
           return [];
         }
 
-        var timeFilter = getTimeFilter(options);
-        var groupByField;
-
-        if (target.rawQuery) {
-          query = target.query;
-          query = query.replace(";", "");
-          var queryElements = query.split(" ");
-          var lowerCaseQueryElements = query.toLowerCase().split(" ");
-          var whereIndex = lowerCaseQueryElements.indexOf("where");
-          var groupByIndex = lowerCaseQueryElements.indexOf("group");
-          var orderIndex = lowerCaseQueryElements.indexOf("order");
-
-          if (lowerCaseQueryElements[1].indexOf(',') !== -1) {
-            groupByField = lowerCaseQueryElements[1].replace(',', '');
-          }
-
-          if (whereIndex !== -1) {
-            queryElements.splice(whereIndex + 1, 0, timeFilter, "and");
-          }
-          else {
-            if (groupByIndex !== -1) {
-              queryElements.splice(groupByIndex, 0, "where", timeFilter);
-            }
-            else if (orderIndex !== -1) {
-              queryElements.splice(orderIndex, 0, "where", timeFilter);
-            }
-            else {
-              queryElements.push("where");
-              queryElements.push(timeFilter);
-            }
-          }
-
-          query = queryElements.join(" ");
-          query = templateSrv.replace(query);
-        }
-        else {
-          query = 'select ';
-          var seriesName = target.series;
-
-          if(!seriesName.match('^/.*/')) {
-            seriesName = '"' + seriesName+ '"';
-          }
-
-          if (target.groupby_field_add) {
-            query += target.groupby_field + ', ';
-          }
-
-          query +=  target.function + '(' + target.column + ')';
-          query += ' from ' + seriesName + ' where ' + timeFilter;
-
-          if (target.condition_filter) {
-            query += ' and ' + target.condition_expression;
-          }
+        // build query
+        var queryBuilder = new InfluxQueryBuilder(target);
+        var query = queryBuilder.build();
 
-          query += ' group by time(' + (target.interval || options.interval) + ')';
+        // replace templated variables
+        templateSrv.setGrafanaVariable('$timeFilter', timeFilter);
+        templateSrv.setGrafanaVariable('$interval', (target.interval || options.interval));
+        query = templateSrv.replace(query);
 
-          if (target.groupby_field_add) {
-            query += ',' + target.groupby_field;
-          }
+        var alias = target.alias ? templateSrv.replace(target.alias) : '';
 
-          query += " order asc";
-
-          query = templateSrv.replace(query);
-
-          if (target.groupby_field_add) {
-            groupByField = target.groupby_field;
-          }
-
-          target.query = query;
-        }
-
-        if (target.alias) {
-          alias = templateSrv.replace(target.alias);
-        }
-
-        var handleResponse = _.partial(handleInfluxQueryResponse, alias, groupByField);
+        var handleResponse = _.partial(handleInfluxQueryResponse, alias, queryBuilder.groupByField);
         return this._seriesQuery(query).then(handleResponse);
 
       }, this);
@@ -123,12 +59,11 @@ function (angular, _, kbn, InfluxSeries) {
       return $q.all(promises).then(function(results) {
         return { data: _.flatten(results) };
       });
-
     };
 
     InfluxDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) {
       var timeFilter = getTimeFilter({ range: rangeUnparsed });
-      var query = _.template(annotation.query, { timeFilter: timeFilter }, this.templateSettings);
+      var query = _.template(annotation.query, { timeFilter: timeFilter, "$timeFilter": timeFilter }, this.templateSettings);
 
       return this._seriesQuery(query).then(function(results) {
         return new InfluxSeries({ seriesList: results, annotation: annotation }).getAnnotations();

+ 4 - 0
src/app/services/templateSrv.js

@@ -34,6 +34,10 @@ function (angular, _) {
       this._templateData = _templateData;
     };
 
+    this.setGrafanaVariable = function(name, value) {
+      this._templateData[name] = value;
+    };
+
     this.replace = function(target) {
       if (!target || target.indexOf('[[') === -1) {
         return target;

+ 5 - 3
src/app/services/templateValuesSrv.js

@@ -84,7 +84,7 @@ function (angular, _, kbn) {
 
     this.metricNamesToVariableValues = function(variable, metricNames) {
       var regex, options, i, matches;
-      options = [];
+      options = {}; // use object hash to remove duplicates
 
       if (variable.regex) {
         regex = kbn.stringToJsRegex(variable.regex);
@@ -101,10 +101,12 @@ function (angular, _, kbn) {
           }
         }
 
-        options.push({text: value, value: value});
+        options[value] = value;
       }
 
-      return options;
+      return _.map(_.keys(options), function(key) {
+        return { text: key, value: key };
+      });
     };
 
     this.addAllOption = function(variable) {

+ 20 - 7
src/test/specs/helpers.js

@@ -1,6 +1,7 @@
 define([
-    'kbn'
-], function(kbn) {
+    'kbn',
+    'lodash'
+], function(kbn, _) {
   'use strict';
 
   function ControllerTestContext() {
@@ -47,10 +48,17 @@ define([
 
   function ServiceTestContext() {
     var self = this;
+    self.templateSrv = new TemplateSrvStub();
+
+    this.providePhase = function() {
+     return module(function($provide) {
+        $provide.value('templateSrv', self.templateSrv);
+      });
+    };
 
     this.createService = function(name) {
-      return inject([name, '$q', '$rootScope', '$httpBackend', function(InfluxDatasource, $q, $rootScope, $httpBackend) {
-        self.service = InfluxDatasource;
+      return inject([name, '$q', '$rootScope', '$httpBackend', function(service, $q, $rootScope, $httpBackend) {
+        self.service = service;
         self.$q = $q;
         self.$rootScope = $rootScope;
         self.$httpBackend =  $httpBackend;
@@ -82,11 +90,16 @@ define([
 
   function TemplateSrvStub() {
     this.variables = [];
-    this.replace = function() {};
+    this.templateSettings = { interpolate : /\[\[([\s\S]+?)\]\]/g };
+    this.data = {};
+    this.replace = function(text) {
+      return _.template(text, this.data,  this.templateSettings);
+    };
+    this.setGrafanaVariable = function(name, value) {
+      this.data[name] = value;
+    };
   }
 
-
-
   return {
     ControllerTestContext: ControllerTestContext,
     TimeSrvStub: TimeSrvStub,

+ 54 - 0
src/test/specs/influxQueryBuilder-specs.js

@@ -0,0 +1,54 @@
+define([
+  'services/influxdb/influxQueryBuilder'
+], function(InfluxQueryBuilder) {
+  'use strict';
+
+  describe('InfluxQueryBuilder', function() {
+
+    describe('series with conditon and group by', function() {
+      var builder = new InfluxQueryBuilder({
+        series: 'google.test',
+        column: 'value',
+        function: 'mean',
+        condition_filter: true,
+        condition_expression: "code=1",
+        groupby_field_add: true,
+        groupby_field: 'code'
+      });
+
+      var query = builder.build();
+
+      it('should generate correct query', function() {
+        expect(query).to.be('select code, mean(value) from "google.test" where [[$timeFilter]] and code=1 ' +
+          'group by time([[$interval]]), code order asc');
+      });
+
+      it('should expose groupByFiled', function() {
+        expect(builder.groupByField).to.be('code');
+      });
+
+    });
+
+    describe('old style raw query', function() {
+      var builder = new InfluxQueryBuilder({
+        query: 'select host, mean(value) from asd.asd where time > now() - 1h group by time(1s), code order asc',
+        rawQuery: true
+      });
+
+      var query = builder.build();
+
+      it('should generate correct query', function() {
+        expect(query).to.be('select host, mean(value) from asd.asd where [[$timeFilter]] and time > now() - 1h ' +
+          ' group by time(1s), code order asc');
+      });
+
+      it('should expose groupByFiled', function() {
+        expect(builder.groupByField).to.be('host');
+      });
+
+    });
+
+
+  });
+
+});

+ 1 - 0
src/test/specs/influxdb-datasource-specs.js

@@ -8,6 +8,7 @@ define([
     var ctx = new helpers.ServiceTestContext();
 
     beforeEach(module('grafana.services'));
+    beforeEach(ctx.providePhase());
     beforeEach(ctx.createService('InfluxDatasource'));
 
     describe('When querying influxdb with one target using query editor target spec', function() {

+ 35 - 1
src/test/specs/templateValuesSrv-specs.js

@@ -140,6 +140,18 @@ define([
       });
     });
 
+   describeUpdateVariable('regex pattern remove duplicates', function(ctx) {
+      ctx.setup(function() {
+        ctx.variable = { type: 'query', query: 'apps.*', name: 'test' };
+        ctx.variable.regex = 'backend_01';
+        ctx.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_01.counters.req'}];
+      });
+
+      it('should return matches options', function() {
+        expect(ctx.variable.options.length).to.be(1);
+      });
+    });
+
     describeUpdateVariable('and existing value still exists in options', function(ctx) {
       ctx.setup(function() {
         ctx.variable = { type: 'query', query: 'apps.*', name: 'test' };
@@ -163,7 +175,29 @@ define([
       });
     });
 
-    describeUpdateVariable('with include all regex wildcard', function(ctx) {
+    describeUpdateVariable('with include all wildcard', function(ctx) {
+      ctx.setup(function() {
+        ctx.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'wildcard' };
+        ctx.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];
+      });
+
+      it('should add All wildcard option', function() {
+        expect(ctx.variable.options[0].value).to.be('*');
+      });
+    });
+
+    describeUpdateVariable('with include all wildcard', function(ctx) {
+      ctx.setup(function() {
+        ctx.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'regex wildcard' };
+        ctx.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];
+      });
+
+      it('should add All wildcard option', function() {
+        expect(ctx.variable.options[0].value).to.be('.*');
+      });
+    });
+
+    describeUpdateVariable('with include all regex values', function(ctx) {
       ctx.setup(function() {
         ctx.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'wildcard' };
         ctx.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];

+ 3 - 2
src/test/test-main.js

@@ -121,6 +121,8 @@ require([
     'specs/timeSeries-specs',
     'specs/row-ctrl-specs',
     'specs/graphiteTargetCtrl-specs',
+    'specs/influxSeries-specs',
+    'specs/influxQueryBuilder-specs',
     'specs/influxdb-datasource-specs',
     'specs/graph-ctrl-specs',
     'specs/grafanaGraph-specs',
@@ -130,8 +132,7 @@ require([
     'specs/templateValuesSrv-specs',
     'specs/kbn-format-specs',
     'specs/dashboardSrv-specs',
-    'specs/dashboardViewStateSrv-specs',
-    'specs/influxSeries-specs'
+    'specs/dashboardViewStateSrv-specs'
   ], function () {
     window.__karma__.start();
   });