From 2da04e72f50c5f47da67fc9700fd5e785f6872c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 2 Sep 2014 07:05:24 +0200 Subject: [PATCH] More progress on influxdb query editor, templating, refactoring, unit tests, #740, #507, #586 --- src/app/controllers/influxTargetCtrl.js | 15 ++- src/app/directives/templateParamSelector.js | 2 +- .../partials/influxdb/annotation_editor.html | 4 +- src/app/partials/influxdb/editor.html | 24 ++--- src/app/partials/templating_editor.html | 2 +- .../services/influxdb/influxQueryBuilder.js | 81 ++++++++++++++++ .../services/influxdb/influxdbDatasource.js | 95 +++---------------- src/app/services/templateSrv.js | 4 + src/app/services/templateValuesSrv.js | 8 +- src/test/specs/helpers.js | 27 ++++-- src/test/specs/influxQueryBuilder-specs.js | 54 +++++++++++ src/test/specs/influxdb-datasource-specs.js | 1 + src/test/specs/templateValuesSrv-specs.js | 36 ++++++- src/test/test-main.js | 5 +- 14 files changed, 246 insertions(+), 112 deletions(-) create mode 100644 src/app/services/influxdb/influxQueryBuilder.js create mode 100644 src/test/specs/influxQueryBuilder-specs.js diff --git a/src/app/controllers/influxTargetCtrl.js b/src/app/controllers/influxTargetCtrl.js index bb34df8f608..6b27be78a0c 100644 --- a/src/app/controllers/influxTargetCtrl.js +++ b/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); }); diff --git a/src/app/directives/templateParamSelector.js b/src/app/directives/templateParamSelector.js index 106f5f9ee0b..f05d6959198 100644 --- a/src/app/directives/templateParamSelector.js +++ b/src/app/directives/templateParamSelector.js @@ -11,7 +11,7 @@ function (angular, app, _, $) { .module('grafana.directives') .directive('templateParamSelector', function($compile) { var inputTemplate = ''; var buttonTemplate = '{{variable.current.text}}'; diff --git a/src/app/partials/influxdb/annotation_editor.html b/src/app/partials/influxdb/annotation_editor.html index 9bc2bdbca21..2bcfb549411 100644 --- a/src/app/partials/influxdb/annotation_editor.html +++ b/src/app/partials/influxdb/annotation_editor.html @@ -1,8 +1,8 @@
-
InfluxDB Query Example: select text from events where [[timeFilter]]
+
InfluxDB Query Example: select text from events where [[$timeFilter]]
- +
diff --git a/src/app/partials/influxdb/editor.html b/src/app/partials/influxdb/editor.html index 995ba362121..da0a525303a 100644 --- a/src/app/partials/influxdb/editor.html +++ b/src/app/partials/influxdb/editor.html @@ -72,6 +72,16 @@ data-min-length=0 data-items=100 ng-blur="seriesBlur()"> + +
  • + alias +
  • + +
  • + +
  • +
    @@ -102,16 +112,6 @@ - -
  • - alias -
  • - -
  • - -
  • -
  • @@ -129,7 +129,7 @@
  • - @@ -142,7 +142,7 @@
  • -
  • diff --git a/src/app/partials/templating_editor.html b/src/app/partials/templating_editor.html index 659f0ee1bde..22ab029061e 100644 --- a/src/app/partials/templating_editor.html +++ b/src/app/partials/templating_editor.html @@ -101,7 +101,7 @@
    - +
    diff --git a/src/app/services/influxdb/influxQueryBuilder.js b/src/app/services/influxdb/influxQueryBuilder.js new file mode 100644 index 00000000000..eaa19fceb9d --- /dev/null +++ b/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; +}); diff --git a/src/app/services/influxdb/influxdbDatasource.js b/src/app/services/influxdb/influxdbDatasource.js index 0db4f3626ba..d2a375521a5 100644 --- a/src/app/services/influxdb/influxdbDatasource.js +++ b/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; + // build query + var queryBuilder = new InfluxQueryBuilder(target); + var query = queryBuilder.build(); - 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"); + // replace templated variables + templateSrv.setGrafanaVariable('$timeFilter', timeFilter); + templateSrv.setGrafanaVariable('$interval', (target.interval || options.interval)); + query = templateSrv.replace(query); - if (lowerCaseQueryElements[1].indexOf(',') !== -1) { - groupByField = lowerCaseQueryElements[1].replace(',', ''); - } + var alias = target.alias ? templateSrv.replace(target.alias) : ''; - 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; - } - - query += ' group by time(' + (target.interval || options.interval) + ')'; - - if (target.groupby_field_add) { - query += ',' + target.groupby_field; - } - - 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(); diff --git a/src/app/services/templateSrv.js b/src/app/services/templateSrv.js index 1cf1e832606..720b732ebc8 100644 --- a/src/app/services/templateSrv.js +++ b/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; diff --git a/src/app/services/templateValuesSrv.js b/src/app/services/templateValuesSrv.js index 687d1897069..4293f567195 100644 --- a/src/app/services/templateValuesSrv.js +++ b/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) { diff --git a/src/test/specs/helpers.js b/src/test/specs/helpers.js index 9b08c02e2f6..7293678b6a2 100644 --- a/src/test/specs/helpers.js +++ b/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, diff --git a/src/test/specs/influxQueryBuilder-specs.js b/src/test/specs/influxQueryBuilder-specs.js new file mode 100644 index 00000000000..789eb342a98 --- /dev/null +++ b/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'); + }); + + }); + + + }); + +}); diff --git a/src/test/specs/influxdb-datasource-specs.js b/src/test/specs/influxdb-datasource-specs.js index 68ae33fbc18..9b531065115 100644 --- a/src/test/specs/influxdb-datasource-specs.js +++ b/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() { diff --git a/src/test/specs/templateValuesSrv-specs.js b/src/test/specs/templateValuesSrv-specs.js index ec5e0376439..d0825a2239c 100644 --- a/src/test/specs/templateValuesSrv-specs.js +++ b/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'}]; diff --git a/src/test/test-main.js b/src/test/test-main.js index d75fe2ebd99..04f25f91005 100644 --- a/src/test/test-main.js +++ b/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(); });