From e70b99a706d10eadfb6ef3fc755a4added45cba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 29 Sep 2015 18:00:15 +0200 Subject: [PATCH 01/17] poc: influxdb editor v3 test --- .../influxdb/partials/query.editor.html | 83 +++--- .../plugins/datasource/influxdb/query_ctrl.js | 12 +- .../plugins/datasource/influxdb/query_part.js | 161 ++++++++++++ .../datasource/influxdb/query_part_editor.js | 244 ++++++++++++++++++ 4 files changed, 452 insertions(+), 48 deletions(-) create mode 100644 public/app/plugins/datasource/influxdb/query_part.js create mode 100644 public/app/plugins/datasource/influxdb/query_part_editor.js diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index c9c7203bed7..8263db8e594 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -1,4 +1,4 @@ -
+
  • @@ -48,26 +48,22 @@
  • +
  • + WHERE +
  • +
  • + +
+
-
-
    -
  • - WHERE -
  • -
  • - -
  • -
-
-
    @@ -75,19 +71,11 @@ SELECT
  • - +
  • -
  • - -
  • -
  • - -
  • -
  • - AS -
  • -
  • - +
  • + +
@@ -108,30 +96,29 @@ GROUP BY
  • - time - - -
  • - -
  • - + +
  • + + + + + + + + + + + + + + + + + + + +
      @@ -146,6 +133,8 @@
    + +
    • diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 75dd972fbaa..061a771c623 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -2,8 +2,10 @@ define([ 'angular', 'lodash', './query_builder', + './query_part', + './query_part_editor', ], -function (angular, _, InfluxQueryBuilder) { +function (angular, _, InfluxQueryBuilder, queryPart) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -17,6 +19,14 @@ function (angular, _, InfluxQueryBuilder) { target.tags = target.tags || []; target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; target.fields = target.fields || [{name: 'value', func: target.function || 'mean'}]; + target.fields[0].parts = [ + queryPart.create('mean', { withDefaultParams: true }), + queryPart.create('derivate', { withDefaultParams: true }), + queryPart.create('math', { withDefaultParams: true }), + queryPart.create('alias', { withDefaultParams: true }), + ]; + + $scope.func = queryPart.create('time', { withDefaultParams: true }); $scope.queryBuilder = new InfluxQueryBuilder(target); diff --git a/public/app/plugins/datasource/influxdb/query_part.js b/public/app/plugins/datasource/influxdb/query_part.js new file mode 100644 index 00000000000..44394a50cdb --- /dev/null +++ b/public/app/plugins/datasource/influxdb/query_part.js @@ -0,0 +1,161 @@ +define([ + 'lodash', + 'jquery' +], +function (_, $) { + 'use strict'; + + var index = []; + var categories = { + Combine: [], + Transform: [], + Calculate: [], + Filter: [], + Special: [] + }; + + function addFuncDef(funcDef) { + funcDef.params = funcDef.params || []; + funcDef.defaultParams = funcDef.defaultParams || []; + + if (funcDef.category) { + funcDef.category.push(funcDef); + } + index[funcDef.name] = funcDef; + index[funcDef.shortName || funcDef.name] = funcDef; + } + + addFuncDef({ + name: 'mean', + category: categories.Transform, + params: [], + defaultParams: [], + }); + + addFuncDef({ + name: 'derivate', + category: categories.Transform, + params: [{ name: "rate", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + defaultParams: ['10s'], + }); + + addFuncDef({ + name: 'time', + category: categories.Transform, + params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + defaultParams: ['$interval'], + }); + + addFuncDef({ + name: 'math', + category: categories.Transform, + params: [{ name: "expr", type: "string"}], + defaultParams: [' / 100'], + }); + + addFuncDef({ + name: 'alias', + category: categories.Transform, + params: [{ name: "name", type: "string"}], + defaultParams: ['alias'], + }); + + _.each(categories, function(funcList, catName) { + categories[catName] = _.sortBy(funcList, 'name'); + }); + + function FuncInstance(funcDef, options) { + this.def = funcDef; + this.params = []; + + if (options && options.withDefaultParams) { + this.params = funcDef.defaultParams.slice(0); + } + + this.updateText(); + } + + FuncInstance.prototype.render = function(metricExp) { + var str = this.def.name + '('; + var parameters = _.map(this.params, function(value, index) { + + var paramType = this.def.params[index].type; + if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') { + return value; + } + else if (paramType === 'int_or_interval' && $.isNumeric(value)) { + return value; + } + + return "'" + value + "'"; + + }, this); + + if (metricExp) { + parameters.unshift(metricExp); + } + + return str + parameters.join(', ') + ')'; + }; + + FuncInstance.prototype._hasMultipleParamsInString = function(strValue, index) { + if (strValue.indexOf(',') === -1) { + return false; + } + + return this.def.params[index + 1] && this.def.params[index + 1].optional; + }; + + FuncInstance.prototype.updateParam = function(strValue, index) { + // handle optional parameters + // if string contains ',' and next param is optional, split and update both + if (this._hasMultipleParamsInString(strValue, index)) { + _.each(strValue.split(','), function(partVal, idx) { + this.updateParam(partVal.trim(), idx); + }, this); + return; + } + + if (strValue === '' && this.def.params[index].optional) { + this.params.splice(index, 1); + } + else { + this.params[index] = strValue; + } + + this.updateText(); + }; + + FuncInstance.prototype.updateText = function () { + if (this.params.length === 0) { + this.text = this.def.name + '()'; + return; + } + + var text = this.def.name + '('; + text += this.params.join(', '); + text += ')'; + this.text = text; + }; + + return { + create: function(funcDef, options) { + if (_.isString(funcDef)) { + if (!index[funcDef]) { + throw { message: 'Method not found ' + name }; + } + funcDef = index[funcDef]; + } + return new FuncInstance(funcDef, options); + }, + + getFuncDef: function(name) { + return index[name]; + }, + + getCategories: function() { + return categories; + } + }; + +}); diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js new file mode 100644 index 00000000000..6c1f268a9d1 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -0,0 +1,244 @@ +define([ + 'angular', + 'lodash', + 'jquery', +], +function (angular, _, $) { + 'use strict'; + + angular + .module('grafana.directives') + .directive('influxQueryPartEditor', function($compile, templateSrv) { + + var funcSpanTemplate = '{{func.def.name}}('; + var paramTemplate = ''; + + var funcControlsTemplate = + '
      ' + + '' + + '' + + '' + + '' + + '
      '; + + return { + restrict: 'A', + link: function postLink($scope, elem) { + var $funcLink = $(funcSpanTemplate); + var $funcControls = $(funcControlsTemplate); + var func = $scope.func; + var funcDef = func.def; + var scheduledRelink = false; + var paramCountAtLink = 0; + + function clickFuncParam(paramIndex) { + /*jshint validthis:true */ + + var $link = $(this); + var $input = $link.next(); + + $input.val(func.params[paramIndex]); + $input.css('width', ($link.width() + 16) + 'px'); + + $link.hide(); + $input.show(); + $input.focus(); + $input.select(); + + var typeahead = $input.data('typeahead'); + if (typeahead) { + $input.val(''); + typeahead.lookup(); + } + } + + function scheduledRelinkIfNeeded() { + if (paramCountAtLink === func.params.length) { + return; + } + + if (!scheduledRelink) { + scheduledRelink = true; + setTimeout(function() { + relink(); + scheduledRelink = false; + }, 200); + } + } + + function inputBlur(paramIndex) { + /*jshint validthis:true */ + var $input = $(this); + var $link = $input.prev(); + var newValue = $input.val(); + + if (newValue !== '' || func.def.params[paramIndex].optional) { + $link.html(templateSrv.highlightVariablesAsHtml(newValue)); + + func.updateParam($input.val(), paramIndex); + scheduledRelinkIfNeeded(); + + $scope.$apply($scope.targetChanged); + } + + $input.hide(); + $link.show(); + } + + function inputKeyPress(paramIndex, e) { + /*jshint validthis:true */ + if(e.which === 13) { + inputBlur.call(this, paramIndex); + } + } + + function inputKeyDown() { + /*jshint validthis:true */ + this.style.width = (3 + this.value.length) * 8 + 'px'; + } + + function addTypeahead($input, paramIndex) { + $input.attr('data-provide', 'typeahead'); + + var options = funcDef.params[paramIndex].options; + if (funcDef.params[paramIndex].type === 'int') { + options = _.map(options, function(val) { return val.toString(); }); + } + + $input.typeahead({ + source: options, + minLength: 0, + items: 20, + updater: function (value) { + setTimeout(function() { + inputBlur.call($input[0], paramIndex); + }, 0); + return value; + } + }); + + var typeahead = $input.data('typeahead'); + typeahead.lookup = function () { + this.query = this.$element.val() || ''; + return this.process(this.source); + }; + } + + function toggleFuncControls() { + var targetDiv = elem.closest('.tight-form'); + + if (elem.hasClass('show-function-controls')) { + elem.removeClass('show-function-controls'); + targetDiv.removeClass('has-open-function'); + $funcControls.hide(); + return; + } + + elem.addClass('show-function-controls'); + targetDiv.addClass('has-open-function'); + + $funcControls.show(); + } + + function addElementsAndCompile() { + $funcControls.appendTo(elem); + $funcLink.appendTo(elem); + + _.each(funcDef.params, function(param, index) { + if (param.optional && func.params.length <= index) { + return; + } + + if (index > 0) { + $(', ').appendTo(elem); + } + + var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]); + var $paramLink = $('' + paramValue + ''); + var $input = $(paramTemplate); + + paramCountAtLink++; + + $paramLink.appendTo(elem); + $input.appendTo(elem); + + $input.blur(_.partial(inputBlur, index)); + $input.keyup(inputKeyDown); + $input.keypress(_.partial(inputKeyPress, index)); + $paramLink.click(_.partial(clickFuncParam, index)); + + if (funcDef.params[index].options) { + addTypeahead($input, index); + } + + }); + + $(')').appendTo(elem); + + $compile(elem.contents())($scope); + } + + function ifJustAddedFocusFistParam() { + if ($scope.func.added) { + $scope.func.added = false; + setTimeout(function() { + elem.find('.graphite-func-param-link').first().click(); + }, 10); + } + } + + function registerFuncControlsToggle() { + $funcLink.click(toggleFuncControls); + } + + function registerFuncControlsActions() { + $funcControls.click(function(e) { + var $target = $(e.target); + if ($target.hasClass('fa-remove')) { + toggleFuncControls(); + $scope.$apply(function() { + $scope.removeFunction($scope.func); + }); + return; + } + + if ($target.hasClass('fa-arrow-left')) { + $scope.$apply(function() { + _.move($scope.functions, $scope.$index, $scope.$index - 1); + $scope.targetChanged(); + }); + return; + } + + if ($target.hasClass('fa-arrow-right')) { + $scope.$apply(function() { + _.move($scope.functions, $scope.$index, $scope.$index + 1); + $scope.targetChanged(); + }); + return; + } + + if ($target.hasClass('fa-question-circle')) { + window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank'); + return; + } + }); + } + + function relink() { + elem.children().remove(); + + addElementsAndCompile(); + ifJustAddedFocusFistParam(); + registerFuncControlsToggle(); + registerFuncControlsActions(); + } + + relink(); + } + }; + + }); + +}); From 2dc8fcd3be17d03b476c4e994c7288d5a38bf710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 29 Sep 2015 21:32:15 +0200 Subject: [PATCH 02/17] poc(influxdb v3 editor): more testing of new influxdb editor approach --- .../influxdb/partials/query.editor.html | 7 ++---- .../plugins/datasource/influxdb/query_ctrl.js | 23 ++++++++++++++----- .../plugins/datasource/influxdb/query_part.js | 7 ++++++ .../datasource/influxdb/query_part_editor.js | 2 -- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 8263db8e594..9fb39ad5abe 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -65,15 +65,12 @@
      -
      +
      • SELECT
      • -
      • - -
      • -
      • +
      • diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 061a771c623..247205ab39d 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -18,14 +18,25 @@ function (angular, _, InfluxQueryBuilder, queryPart) { var target = $scope.target; target.tags = target.tags || []; target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; - target.fields = target.fields || [{name: 'value', func: target.function || 'mean'}]; - target.fields[0].parts = [ - queryPart.create('mean', { withDefaultParams: true }), - queryPart.create('derivate', { withDefaultParams: true }), - queryPart.create('math', { withDefaultParams: true }), - queryPart.create('alias', { withDefaultParams: true }), + target.fields = target.fields || [{name: 'value'}]; + target.select = target.select || [[{type: 'field', params: ['value']}]]; + target.select[0] = [ + {type: 'field', params: ['value']}, + {type: 'mean', params: []}, + {type: 'derivate', params: ['10s']}, + {type: 'math', params: ['/ 100']}, + {type: 'alias', params: ['google']}, ]; + $scope.select = _.map(target.select, function(parts) { + return _.map(parts, function(part) { + var partModel = queryPart.create(part.type); + partModel.params = part.params; + partModel.updateText(); + return partModel; + }); + }); + $scope.func = queryPart.create('time', { withDefaultParams: true }); $scope.queryBuilder = new InfluxQueryBuilder(target); diff --git a/public/app/plugins/datasource/influxdb/query_part.js b/public/app/plugins/datasource/influxdb/query_part.js index 44394a50cdb..37433d54951 100644 --- a/public/app/plugins/datasource/influxdb/query_part.js +++ b/public/app/plugins/datasource/influxdb/query_part.js @@ -25,6 +25,13 @@ function (_, $) { index[funcDef.shortName || funcDef.name] = funcDef; } + addFuncDef({ + name: 'field', + category: categories.Transform, + params: [{type: 'field'}], + defaultParams: ['value'], + }); + addFuncDef({ name: 'mean', category: categories.Transform, diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js index 6c1f268a9d1..fd0dc3e1a6b 100644 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -16,10 +16,8 @@ function (angular, _, $) { var funcControlsTemplate = '
        ' + - '' + '' + '' + - '' + '
        '; return { From f053b416453cf2ed702e7a4b40a769bd9417417d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 30 Sep 2015 14:37:27 +0200 Subject: [PATCH 03/17] feat(influxdb editor): more progress --- .../influxdb/partials/query.editor.html | 13 +- .../influxdb/partials/query_part.html | 6 + .../plugins/datasource/influxdb/query_ctrl.js | 42 ++--- .../plugins/datasource/influxdb/query_part.js | 168 ------------------ .../plugins/datasource/influxdb/query_part.ts | 165 +++++++++++++++++ .../datasource/influxdb/query_part_editor.js | 139 +++------------ 6 files changed, 225 insertions(+), 308 deletions(-) create mode 100644 public/app/plugins/datasource/influxdb/partials/query_part.html delete mode 100644 public/app/plugins/datasource/influxdb/query_part.js create mode 100644 public/app/plugins/datasource/influxdb/query_part.ts diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 9fb39ad5abe..af40de12302 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -65,22 +65,20 @@
        -
        +
        • SELECT
        • -
        • - - +
        • +
        -
        • -
        • +
        @@ -93,8 +91,7 @@ GROUP BY
      • - - +
      • diff --git a/public/app/plugins/datasource/influxdb/partials/query_part.html b/public/app/plugins/datasource/influxdb/partials/query_part.html new file mode 100644 index 00000000000..0a28bee11f7 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/partials/query_part.html @@ -0,0 +1,6 @@ +
        + + +
        + +{{part.def.name}}() diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 247205ab39d..d4c4a7f6181 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -10,7 +10,7 @@ function (angular, _, InfluxQueryBuilder, queryPart) { var module = angular.module('grafana.controllers'); - module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv, $q, uiSegmentSrv) { + module.controller('InfluxQueryCtrl', function($scope, templateSrv, $q, uiSegmentSrv) { $scope.init = function() { if (!$scope.target) { return; } @@ -19,25 +19,14 @@ function (angular, _, InfluxQueryBuilder, queryPart) { target.tags = target.tags || []; target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; target.fields = target.fields || [{name: 'value'}]; - target.select = target.select || [[{type: 'field', params: ['value']}]]; - target.select[0] = [ - {type: 'field', params: ['value']}, - {type: 'mean', params: []}, - {type: 'derivate', params: ['10s']}, - {type: 'math', params: ['/ 100']}, - {type: 'alias', params: ['google']}, - ]; + target.select = target.select || [[ + {name: 'field', params: ['value']}, + {name: 'mean', params: []}, + ]]; - $scope.select = _.map(target.select, function(parts) { - return _.map(parts, function(part) { - var partModel = queryPart.create(part.type); - partModel.params = part.params; - partModel.updateText(); - return partModel; - }); - }); + $scope.updateSelectParts(); - $scope.func = queryPart.create('time', { withDefaultParams: true }); + $scope.groupByParts = queryPart.create({name: 'time', params:['$interval']}); $scope.queryBuilder = new InfluxQueryBuilder(target); @@ -89,14 +78,27 @@ function (angular, _, InfluxQueryBuilder, queryPart) { }; $scope.addSelect = function() { - $scope.target.fields.push({name: "select field", func: 'mean'}); + $scope.target.select.push([ + {name: 'field', params: ['value']}, + {name: 'mean', params: []}, + ]); + $scope.updateSelectParts(); }; $scope.removeSelect = function(index) { - $scope.target.fields.splice(index, 1); + $scope.target.select.splice(index, 1); + $scope.updateSelectParts(); $scope.get_data(); }; + $scope.updateSelectParts = function() { + $scope.selectParts = _.map($scope.target.select, function(parts) { + return _.map(parts, function(part) { + return queryPart.create(part); + }); + }); + }; + $scope.changeFunction = function(func) { $scope.target.function = func; $scope.$parent.get_data(); diff --git a/public/app/plugins/datasource/influxdb/query_part.js b/public/app/plugins/datasource/influxdb/query_part.js deleted file mode 100644 index 37433d54951..00000000000 --- a/public/app/plugins/datasource/influxdb/query_part.js +++ /dev/null @@ -1,168 +0,0 @@ -define([ - 'lodash', - 'jquery' -], -function (_, $) { - 'use strict'; - - var index = []; - var categories = { - Combine: [], - Transform: [], - Calculate: [], - Filter: [], - Special: [] - }; - - function addFuncDef(funcDef) { - funcDef.params = funcDef.params || []; - funcDef.defaultParams = funcDef.defaultParams || []; - - if (funcDef.category) { - funcDef.category.push(funcDef); - } - index[funcDef.name] = funcDef; - index[funcDef.shortName || funcDef.name] = funcDef; - } - - addFuncDef({ - name: 'field', - category: categories.Transform, - params: [{type: 'field'}], - defaultParams: ['value'], - }); - - addFuncDef({ - name: 'mean', - category: categories.Transform, - params: [], - defaultParams: [], - }); - - addFuncDef({ - name: 'derivate', - category: categories.Transform, - params: [{ name: "rate", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], - defaultParams: ['10s'], - }); - - addFuncDef({ - name: 'time', - category: categories.Transform, - params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], - defaultParams: ['$interval'], - }); - - addFuncDef({ - name: 'math', - category: categories.Transform, - params: [{ name: "expr", type: "string"}], - defaultParams: [' / 100'], - }); - - addFuncDef({ - name: 'alias', - category: categories.Transform, - params: [{ name: "name", type: "string"}], - defaultParams: ['alias'], - }); - - _.each(categories, function(funcList, catName) { - categories[catName] = _.sortBy(funcList, 'name'); - }); - - function FuncInstance(funcDef, options) { - this.def = funcDef; - this.params = []; - - if (options && options.withDefaultParams) { - this.params = funcDef.defaultParams.slice(0); - } - - this.updateText(); - } - - FuncInstance.prototype.render = function(metricExp) { - var str = this.def.name + '('; - var parameters = _.map(this.params, function(value, index) { - - var paramType = this.def.params[index].type; - if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') { - return value; - } - else if (paramType === 'int_or_interval' && $.isNumeric(value)) { - return value; - } - - return "'" + value + "'"; - - }, this); - - if (metricExp) { - parameters.unshift(metricExp); - } - - return str + parameters.join(', ') + ')'; - }; - - FuncInstance.prototype._hasMultipleParamsInString = function(strValue, index) { - if (strValue.indexOf(',') === -1) { - return false; - } - - return this.def.params[index + 1] && this.def.params[index + 1].optional; - }; - - FuncInstance.prototype.updateParam = function(strValue, index) { - // handle optional parameters - // if string contains ',' and next param is optional, split and update both - if (this._hasMultipleParamsInString(strValue, index)) { - _.each(strValue.split(','), function(partVal, idx) { - this.updateParam(partVal.trim(), idx); - }, this); - return; - } - - if (strValue === '' && this.def.params[index].optional) { - this.params.splice(index, 1); - } - else { - this.params[index] = strValue; - } - - this.updateText(); - }; - - FuncInstance.prototype.updateText = function () { - if (this.params.length === 0) { - this.text = this.def.name + '()'; - return; - } - - var text = this.def.name + '('; - text += this.params.join(', '); - text += ')'; - this.text = text; - }; - - return { - create: function(funcDef, options) { - if (_.isString(funcDef)) { - if (!index[funcDef]) { - throw { message: 'Method not found ' + name }; - } - funcDef = index[funcDef]; - } - return new FuncInstance(funcDef, options); - }, - - getFuncDef: function(name) { - return index[name]; - }, - - getCategories: function() { - return categories; - } - }; - -}); diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts new file mode 100644 index 00000000000..7e5dfff3277 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -0,0 +1,165 @@ +/// + +import _ = require('lodash'); + +var index = []; +var categories = { + Combine: [], + Transform: [], + Calculate: [], + Filter: [], + Special: [] +}; + +class QueryPartDef { + name: string; + params: any[]; + defaultParams: any[]; + + constructor(options: any) { + this.name = options.name; + this.params = options.params; + this.defaultParams = options.defaultParams; + } + + static register(options: any) { + index[options.name] = new QueryPartDef(options); + } +} + +QueryPartDef.register({ + name: 'field', + category: categories.Transform, + params: [{type: 'field'}], + defaultParams: ['value'], +}); + +QueryPartDef.register({ + name: 'mean', + category: categories.Transform, + params: [], + defaultParams: [], +}); + +QueryPartDef.register({ + name: 'derivate', + category: categories.Transform, + params: [{ name: "rate", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + defaultParams: ['10s'], +}); + +QueryPartDef.register({ + name: 'time', + category: categories.Transform, + params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + defaultParams: ['$interval'], +}); + +QueryPartDef.register({ + name: 'math', + category: categories.Transform, + params: [{ name: "expr", type: "string"}], + defaultParams: [' / 100'], +}); + +QueryPartDef.register({ + name: 'alias', + category: categories.Transform, + params: [{ name: "name", type: "string"}], + defaultParams: ['alias'], +}); + +class QueryPart { + part: any; + def: QueryPartDef; + params: any[]; + text: string; + + constructor(part: any) { + this.part = part; + this.def = index[part.name]; + if (!this.def) { + throw {message: 'Could not find query part ' + part.name}; + } + + this.params = part.params || _.clone(this.def.defaultParams); + } + + render(innerExpr: string) { + var str = this.def.name + '('; + var parameters = _.map(this.params, (value, index) => { + + var paramType = this.def.params[index].type; + if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') { + return value; + } + else if (paramType === 'int_or_interval' && _.isNumber(value)) { + return value; + } + + return "'" + value + "'"; + + }); + + if (innerExpr) { + parameters.unshift(innerExpr); + } + + return str + parameters.join(', ') + ')'; + } + + hasMultipleParamsInString (strValue, index) { + if (strValue.indexOf(',') === -1) { + return false; + } + + return this.def.params[index + 1] && this.def.params[index + 1].optional; + } + + updateParam (strValue, index) { + // handle optional parameters + // if string contains ',' and next param is optional, split and update both + if (this.hasMultipleParamsInString(strValue, index)) { + _.each(strValue.split(','), function(partVal: string, idx) { + this.updateParam(partVal.trim(), idx); + }, this); + return; + } + + if (strValue === '' && this.def.params[index].optional) { + this.params.splice(index, 1); + } + else { + this.params[index] = strValue; + } + + this.part.params = this.params; + this.updateText(); + } + + updateText() { + if (this.params.length === 0) { + this.text = this.def.name + '()'; + return; + } + + var text = this.def.name + '('; + text += this.params.join(', '); + text += ')'; + this.text = text; + } +} + +export = { + create: function(part): any { + return new QueryPart(part); + }, + + getFuncDef: function(name) { + return index[name]; + }, + + getCategories: function() { + return categories; + } +}; diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js index fd0dc3e1a6b..6cfcda5f4e7 100644 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -10,33 +10,26 @@ function (angular, _, $) { .module('grafana.directives') .directive('influxQueryPartEditor', function($compile, templateSrv) { - var funcSpanTemplate = '{{func.def.name}}('; var paramTemplate = ''; - - var funcControlsTemplate = - '
        ' + - '' + - '' + - '
        '; - return { - restrict: 'A', + restrict: 'E', + templateUrl: 'app/plugins/datasource/influxdb/partials/query_part.html', + scope: { + part: "=" + }, link: function postLink($scope, elem) { - var $funcLink = $(funcSpanTemplate); - var $funcControls = $(funcControlsTemplate); - var func = $scope.func; - var funcDef = func.def; - var scheduledRelink = false; - var paramCountAtLink = 0; + var part = $scope.part; + var partDef = part.def; + var $paramsContainer = elem.find('.query-part-parameters'); + var $controlsContainer = elem.find('.tight-form-func-controls'); function clickFuncParam(paramIndex) { /*jshint validthis:true */ - var $link = $(this); var $input = $link.next(); - $input.val(func.params[paramIndex]); + $input.val(part.params[paramIndex]); $input.css('width', ($link.width() + 16) + 'px'); $link.hide(); @@ -51,32 +44,16 @@ function (angular, _, $) { } } - function scheduledRelinkIfNeeded() { - if (paramCountAtLink === func.params.length) { - return; - } - - if (!scheduledRelink) { - scheduledRelink = true; - setTimeout(function() { - relink(); - scheduledRelink = false; - }, 200); - } - } - function inputBlur(paramIndex) { /*jshint validthis:true */ var $input = $(this); var $link = $input.prev(); var newValue = $input.val(); - if (newValue !== '' || func.def.params[paramIndex].optional) { + if (newValue !== '' || part.def.params[paramIndex].optional) { $link.html(templateSrv.highlightVariablesAsHtml(newValue)); - func.updateParam($input.val(), paramIndex); - scheduledRelinkIfNeeded(); - + part.updateParam($input.val(), paramIndex); $scope.$apply($scope.targetChanged); } @@ -99,8 +76,8 @@ function (angular, _, $) { function addTypeahead($input, paramIndex) { $input.attr('data-provide', 'typeahead'); - var options = funcDef.params[paramIndex].options; - if (funcDef.params[paramIndex].type === 'int') { + var options = partDef.params[paramIndex].options; + if (partDef.params[paramIndex].type === 'int') { options = _.map(options, function(val) { return val.toString(); }); } @@ -123,114 +100,52 @@ function (angular, _, $) { }; } - function toggleFuncControls() { + $scope.toggleControls = function() { var targetDiv = elem.closest('.tight-form'); if (elem.hasClass('show-function-controls')) { elem.removeClass('show-function-controls'); targetDiv.removeClass('has-open-function'); - $funcControls.hide(); + $controlsContainer.hide(); return; } elem.addClass('show-function-controls'); targetDiv.addClass('has-open-function'); - - $funcControls.show(); - } + $controlsContainer.show(); + }; function addElementsAndCompile() { - $funcControls.appendTo(elem); - $funcLink.appendTo(elem); - - _.each(funcDef.params, function(param, index) { - if (param.optional && func.params.length <= index) { + _.each(partDef.params, function(param, index) { + if (param.optional && part.params.length <= index) { return; } if (index > 0) { - $(', ').appendTo(elem); + $(', ').appendTo($paramsContainer); } - var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]); - var $paramLink = $('' + paramValue + ''); + var paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]); + var $paramLink = $('' + paramValue + ''); var $input = $(paramTemplate); - paramCountAtLink++; - - $paramLink.appendTo(elem); - $input.appendTo(elem); + $paramLink.appendTo($paramsContainer); + $input.appendTo($paramsContainer); $input.blur(_.partial(inputBlur, index)); $input.keyup(inputKeyDown); $input.keypress(_.partial(inputKeyPress, index)); $paramLink.click(_.partial(clickFuncParam, index)); - if (funcDef.params[index].options) { + if (partDef.params[index].options) { addTypeahead($input, index); } - - }); - - $(')').appendTo(elem); - - $compile(elem.contents())($scope); - } - - function ifJustAddedFocusFistParam() { - if ($scope.func.added) { - $scope.func.added = false; - setTimeout(function() { - elem.find('.graphite-func-param-link').first().click(); - }, 10); - } - } - - function registerFuncControlsToggle() { - $funcLink.click(toggleFuncControls); - } - - function registerFuncControlsActions() { - $funcControls.click(function(e) { - var $target = $(e.target); - if ($target.hasClass('fa-remove')) { - toggleFuncControls(); - $scope.$apply(function() { - $scope.removeFunction($scope.func); - }); - return; - } - - if ($target.hasClass('fa-arrow-left')) { - $scope.$apply(function() { - _.move($scope.functions, $scope.$index, $scope.$index - 1); - $scope.targetChanged(); - }); - return; - } - - if ($target.hasClass('fa-arrow-right')) { - $scope.$apply(function() { - _.move($scope.functions, $scope.$index, $scope.$index + 1); - $scope.targetChanged(); - }); - return; - } - - if ($target.hasClass('fa-question-circle')) { - window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank'); - return; - } }); } function relink() { - elem.children().remove(); - + $paramsContainer.empty(); addElementsAndCompile(); - ifJustAddedFocusFistParam(); - registerFuncControlsToggle(); - registerFuncControlsActions(); } relink(); From 83052352dc5f310190449153310cdd9bb7863e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 30 Sep 2015 17:16:34 +0200 Subject: [PATCH 04/17] feat(influxdb editor): lots of work on new editor, #2856 --- .../datasource/influxdb/influx_query.ts | 141 ++++++++++++++++++ .../influxdb/partials/query.editor.html | 8 +- .../datasource/influxdb/query_builder.js | 3 +- .../plugins/datasource/influxdb/query_ctrl.js | 77 ++-------- .../plugins/datasource/influxdb/query_part.ts | 80 ++++++---- .../influxdb/specs/influx_query_specs.ts | 36 +++++ .../influxdb/specs/query_builder_specs.ts | 66 ++++---- .../influxdb/specs/query_part_specs.ts | 41 +++++ 8 files changed, 317 insertions(+), 135 deletions(-) create mode 100644 public/app/plugins/datasource/influxdb/influx_query.ts create mode 100644 public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts create mode 100644 public/app/plugins/datasource/influxdb/specs/query_part_specs.ts diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts new file mode 100644 index 00000000000..bdc4ffa8e73 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -0,0 +1,141 @@ +/// +/// + +import _ = require('lodash'); +import queryPart = require('./query_part'); + +declare var InfluxQueryBuilder: any; + +class InfluxQuery { + target: any; + selectParts: any[]; + groupByParts: any; + queryBuilder: any; + + constructor(target) { + this.target = target; + + target.tags = target.tags || []; + target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; + target.select = target.select || [[ + {name: 'mean', params: ['value']}, + ]]; + + this.updateSelectParts(); + this.groupByParts = [ + queryPart.create({name: 'time', params: ['$interval']}) + ]; + } + + updateSelectParts() { + this.selectParts = _.map(this.target.select, function(parts: any) { + return _.map(parts, function(part: any) { + return queryPart.create(part); + }); + }); + } + + removeSelect(index: number) { + this.target.select.splice(index, 1); + this.updateSelectParts(); + } + + addSelect() { + this.target.select.push([ + {name: 'mean', params: ['value']}, + ]); + this.updateSelectParts(); + } + + private renderTagCondition(tag, index) { + var str = ""; + var operator = tag.operator; + var value = tag.value; + if (index > 0) { + str = (tag.condition || 'AND') + ' '; + } + + if (!operator) { + if (/^\/.*\/$/.test(tag.value)) { + operator = '=~'; + } else { + operator = '='; + } + } + + // quote value unless regex + if (operator !== '=~' && operator !== '!~') { + value = "'" + value + "'"; + } + + return str + '"' + tag.key + '" ' + operator + ' ' + value; + } + + private getGroupByTimeInterval(interval) { + if (interval === 'auto') { + return '$interval'; + } + return interval; + } + + render() { + var target = this.target; + + if (!target.measurement) { + throw "Metric measurement is missing"; + } + + if (!target.fields) { + target.fields = [{name: 'value', func: target.function || 'mean'}]; + } + + var query = 'SELECT '; + var i, y; + for (i = 0; i < this.selectParts.length; i++) { + let parts = this.selectParts[i]; + var selectText = ""; + for (y = 0; y < parts.length; y++) { + let part = parts[y]; + selectText = part.render(selectText); + } + + if (i > 0) { + query += ', '; + } + query += selectText; + } + + var measurement = target.measurement; + if (!measurement.match('^/.*/') && !measurement.match(/^merge\(.*\)/)) { + measurement = '"' + measurement+ '"'; + } + + query += ' FROM ' + measurement + ' WHERE '; + var conditions = _.map(target.tags, (tag, index) => { + return this.renderTagCondition(tag, index); + }); + + query += conditions.join(' '); + query += (conditions.length > 0 ? ' AND ' : '') + '$timeFilter'; + + query += ' GROUP BY'; + for (i = 0; i < target.groupBy.length; i++) { + var group = target.groupBy[i]; + if (group.type === 'time') { + query += ' time(' + this.getGroupByTimeInterval(group.interval) + ')'; + } else { + query += ', "' + group.key + '"'; + } + } + + if (target.fill) { + query += ' fill(' + target.fill + ')'; + } + + target.query = query; + + return query; + } +} + +export = InfluxQuery; diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index af40de12302..1993c2d7aa9 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -65,7 +65,7 @@
        -
        +
        • SELECT @@ -85,13 +85,13 @@
        -
        +
        • GROUP BY
        • -
        • - +
        • +
        • diff --git a/public/app/plugins/datasource/influxdb/query_builder.js b/public/app/plugins/datasource/influxdb/query_builder.js index 9ab0953b576..84b8b9c692c 100644 --- a/public/app/plugins/datasource/influxdb/query_builder.js +++ b/public/app/plugins/datasource/influxdb/query_builder.js @@ -4,8 +4,9 @@ define([ function (_) { 'use strict'; - function InfluxQueryBuilder(target) { + function InfluxQueryBuilder(target, queryModel) { this.target = target; + this.model = queryModel; if (target.groupByTags) { target.groupBy = [{type: 'time', interval: 'auto'}]; diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index d4c4a7f6181..924c7ab77fe 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -2,10 +2,10 @@ define([ 'angular', 'lodash', './query_builder', - './query_part', + './influx_query', './query_part_editor', ], -function (angular, _, InfluxQueryBuilder, queryPart) { +function (angular, _, InfluxQueryBuilder, InfluxQuery) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -15,29 +15,18 @@ function (angular, _, InfluxQueryBuilder, queryPart) { $scope.init = function() { if (!$scope.target) { return; } - var target = $scope.target; - target.tags = target.tags || []; - target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; - target.fields = target.fields || [{name: 'value'}]; - target.select = target.select || [[ - {name: 'field', params: ['value']}, - {name: 'mean', params: []}, - ]]; + $scope.target = $scope.target; + $scope.queryModel = new InfluxQuery($scope.target); + $scope.queryBuilder = new InfluxQueryBuilder($scope.target); - $scope.updateSelectParts(); - - $scope.groupByParts = queryPart.create({name: 'time', params:['$interval']}); - - $scope.queryBuilder = new InfluxQueryBuilder(target); - - if (!target.measurement) { + if (!$scope.target.measurement) { $scope.measurementSegment = uiSegmentSrv.newSelectMeasurement(); } else { - $scope.measurementSegment = uiSegmentSrv.newSegment(target.measurement); + $scope.measurementSegment = uiSegmentSrv.newSegment($scope.target.measurement); } $scope.tagSegments = []; - _.each(target.tags, function(tag) { + _.each($scope.target.tags, function(tag) { if (!tag.operator) { if (/^\/.*\/$/.test(tag.value)) { tag.operator = "=~"; @@ -78,32 +67,14 @@ function (angular, _, InfluxQueryBuilder, queryPart) { }; $scope.addSelect = function() { - $scope.target.select.push([ - {name: 'field', params: ['value']}, - {name: 'mean', params: []}, - ]); - $scope.updateSelectParts(); + $scope.queryModel.addSelect(); }; $scope.removeSelect = function(index) { - $scope.target.select.splice(index, 1); - $scope.updateSelectParts(); + $scope.queryModel.removeSelect(index); $scope.get_data(); }; - $scope.updateSelectParts = function() { - $scope.selectParts = _.map($scope.target.select, function(parts) { - return _.map(parts, function(part) { - return queryPart.create(part); - }); - }); - }; - - $scope.changeFunction = function(func) { - $scope.target.function = func; - $scope.$parent.get_data(); - }; - $scope.measurementChanged = function() { $scope.target.measurement = $scope.measurementSegment.value; $scope.$parent.get_data(); @@ -125,22 +96,6 @@ function (angular, _, InfluxQueryBuilder, queryPart) { .then($scope.transformToSegments(true), $scope.handleQueryError); }; - $scope.getFunctions = function () { - var functionList = ['count', 'mean', 'sum', 'min', 'max', 'mode', 'distinct', 'median', - 'stddev', 'first', 'last' - ]; - return $q.when(_.map(functionList, function(func) { - return uiSegmentSrv.newSegment(func); - })); - }; - - $scope.getGroupByTimeIntervals = function () { - var times = ['auto', '1s', '10s', '1m', '2m', '5m', '10m', '30m', '1h', '1d']; - return $q.when(_.map(times, function(func) { - return uiSegmentSrv.newSegment(func); - })); - }; - $scope.handleQueryError = function(err) { $scope.parserError = err.message || 'Failed to issue metric query'; return []; @@ -202,18 +157,6 @@ function (angular, _, InfluxQueryBuilder, queryPart) { .then(null, $scope.handleQueryError); }; - $scope.addField = function() { - $scope.target.fields.push({name: $scope.addFieldSegment.value, func: 'mean'}); - _.extend($scope.addFieldSegment, uiSegmentSrv.newPlusButton()); - }; - - $scope.fieldChanged = function(field) { - if (field.name === '-- remove from select --') { - $scope.target.fields = _.without($scope.target.fields, field); - } - $scope.get_data(); - }; - $scope.getTagOptions = function() { var query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS'); diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 7e5dfff3277..9d6d92f569c 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -15,11 +15,13 @@ class QueryPartDef { name: string; params: any[]; defaultParams: any[]; + renderer: any; constructor(options: any) { this.name = options.name; this.params = options.params; this.defaultParams = options.defaultParams; + this.renderer = options.renderer; } static register(options: any) { @@ -27,25 +29,55 @@ class QueryPartDef { } } -QueryPartDef.register({ - name: 'field', - category: categories.Transform, - params: [{type: 'field'}], - defaultParams: ['value'], -}); +function functionRenderer(part, innerExpr) { + var str = part.def.name + '('; + var parameters = _.map(part.params, (value, index) => { + var paramType = part.def.params[index]; + if (paramType.quote === 'single') { + return "'" + value + "'"; + } else if (paramType.quote === 'double') { + return '"' + value + '"'; + } + + return value; + }); + + if (innerExpr) { + parameters.unshift(innerExpr); + } + return str + parameters.join(', ') + ')'; +} + +function aliasRenderer(part, innerExpr) { + return innerExpr + ' AS ' + '"' + part.params[0] + '"'; +} + +function suffixRenderer(part, innerExpr) { + return innerExpr + ' ' + part.params[0]; +} + +function identityRenderer(part, innerExpr) { + return part.params[0]; +} + +function quotedIdentityRenderer(part, innerExpr) { + return '"' + part.params[0] + '"'; +} QueryPartDef.register({ name: 'mean', category: categories.Transform, - params: [], - defaultParams: [], + params: [{type: 'field', quote: 'double'}], + defaultParams: ['value'], + renderer: functionRenderer, }); QueryPartDef.register({ - name: 'derivate', + name: 'derivative', category: categories.Transform, - params: [{ name: "rate", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h']}], defaultParams: ['10s'], + renderer: functionRenderer, }); QueryPartDef.register({ @@ -53,6 +85,7 @@ QueryPartDef.register({ category: categories.Transform, params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], defaultParams: ['$interval'], + renderer: functionRenderer, }); QueryPartDef.register({ @@ -60,13 +93,16 @@ QueryPartDef.register({ category: categories.Transform, params: [{ name: "expr", type: "string"}], defaultParams: [' / 100'], + renderer: suffixRenderer, }); QueryPartDef.register({ name: 'alias', category: categories.Transform, - params: [{ name: "name", type: "string"}], + params: [{ name: "name", type: "string", quote: 'double'}], defaultParams: ['alias'], + renderMode: 'suffix', + renderer: aliasRenderer, }); class QueryPart { @@ -83,29 +119,11 @@ class QueryPart { } this.params = part.params || _.clone(this.def.defaultParams); + this.updateText(); } render(innerExpr: string) { - var str = this.def.name + '('; - var parameters = _.map(this.params, (value, index) => { - - var paramType = this.def.params[index].type; - if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') { - return value; - } - else if (paramType === 'int_or_interval' && _.isNumber(value)) { - return value; - } - - return "'" + value + "'"; - - }); - - if (innerExpr) { - parameters.unshift(innerExpr); - } - - return str + parameters.join(', ') + ')'; + return this.def.renderer(this, innerExpr); } hasMultipleParamsInString (strValue, index) { diff --git a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts new file mode 100644 index 00000000000..9634e327c67 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts @@ -0,0 +1,36 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import InfluxQuery = require('../influx_query'); + +describe.only('InfluxQuery', function() { + + describe('series with mesurement only', function() { + it('should generate correct query', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + }); + + var queryText = query.render(); + expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); + }); + }); + + describe('series with math and alias', function() { + it('should generate correct query', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + select: [ + [ + {name: 'mean', params: ['value']}, + {name: 'math', params: ['/100']}, + {name: 'alias', params: ['text']}, + ] + ] + }); + + var queryText = query.render(); + expect(queryText).to.be('SELECT mean("value") /100 AS "text" FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); + }); + }); + +}); diff --git a/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts b/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts index 65a2f453385..5e06ef77db4 100644 --- a/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts @@ -9,8 +9,8 @@ describe('InfluxQueryBuilder', function() { describe('series with mesurement only', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}] + measurement: 'cpu', + groupBy: [{type: 'time', interval: 'auto'}] }); var query = builder.build(); @@ -22,9 +22,9 @@ describe('InfluxQueryBuilder', function() { describe('series with math expr and as expr', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - fields: [{name: 'test', func: 'max', mathExpr: '*2', asExpr: 'new_name'}], - groupBy: [{type: 'time', interval: 'auto'}] + measurement: 'cpu', + fields: [{name: 'test', func: 'max', mathExpr: '*2', asExpr: 'new_name'}], + groupBy: [{type: 'time', interval: 'auto'}] }); var query = builder.build(); @@ -36,22 +36,22 @@ describe('InfluxQueryBuilder', function() { describe('series with single tag only', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'hostname', value: 'server1'}] + measurement: 'cpu', + groupBy: [{type: 'time', interval: 'auto'}], + tags: [{key: 'hostname', value: 'server1'}] }); var query = builder.build(); expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE "hostname" = \'server1\' AND $timeFilter' - + ' GROUP BY time($interval)'); + + ' GROUP BY time($interval)'); }); it('should switch regex operator with tag value is regex', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'app', value: '/e.*/'}] + measurement: 'cpu', + groupBy: [{type: 'time', interval: 'auto'}], + tags: [{key: 'app', value: '/e.*/'}] }); var query = builder.build(); @@ -62,57 +62,57 @@ describe('InfluxQueryBuilder', function() { describe('series with multiple fields', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - tags: [], - groupBy: [{type: 'time', interval: 'auto'}], - fields: [{ name: 'tx_in', func: 'sum' }, { name: 'tx_out', func: 'mean' }] + measurement: 'cpu', + tags: [], + groupBy: [{type: 'time', interval: 'auto'}], + fields: [{ name: 'tx_in', func: 'sum' }, { name: 'tx_out', func: 'mean' }] }); var query = builder.build(); expect(query).to.be('SELECT sum("tx_in") AS "tx_in", mean("tx_out") AS "tx_out" ' + - 'FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); + 'FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); }); }); describe('series with multiple tags only', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'hostname', value: 'server1'}, {key: 'app', value: 'email', condition: "AND"}] + measurement: 'cpu', + groupBy: [{type: 'time', interval: 'auto'}], + tags: [{key: 'hostname', value: 'server1'}, {key: 'app', value: 'email', condition: "AND"}] }); var query = builder.build(); expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE "hostname" = \'server1\' AND "app" = \'email\' AND ' + - '$timeFilter GROUP BY time($interval)'); + '$timeFilter GROUP BY time($interval)'); }); }); describe('series with tags OR condition', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'hostname', value: 'server1'}, {key: 'hostname', value: 'server2', condition: "OR"}] + measurement: 'cpu', + groupBy: [{type: 'time', interval: 'auto'}], + tags: [{key: 'hostname', value: 'server1'}, {key: 'hostname', value: 'server2', condition: "OR"}] }); var query = builder.build(); expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE "hostname" = \'server1\' OR "hostname" = \'server2\' AND ' + - '$timeFilter GROUP BY time($interval)'); + '$timeFilter GROUP BY time($interval)'); }); }); describe('series with groupByTag', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - tags: [], - groupBy: [{type: 'time', interval: 'auto'}, {type: 'tag', key: 'host'}], + measurement: 'cpu', + tags: [], + groupBy: [{type: 'time', interval: 'auto'}, {type: 'tag', key: 'host'}], }); var query = builder.build(); expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE $timeFilter ' + - 'GROUP BY time($interval), "host"'); + 'GROUP BY time($interval), "host"'); }); }); @@ -126,8 +126,7 @@ describe('InfluxQueryBuilder', function() { it('should handle regex measurement in tag keys query', function() { var builder = new InfluxQueryBuilder({ - measurement: '/.*/', - tags: [] + measurement: '/.*/', tags: [] }); var query = builder.buildExploreQuery('TAG_KEYS'); expect(query).to.be('SHOW TAG KEYS FROM /.*/'); @@ -170,7 +169,10 @@ describe('InfluxQueryBuilder', function() { }); it('should switch to regex operator in tag condition', function() { - var builder = new InfluxQueryBuilder({measurement: 'cpu', tags: [{key: 'host', value: '/server.*/'}]}); + var builder = new InfluxQueryBuilder({ + measurement: 'cpu', + tags: [{key: 'host', value: '/server.*/'}] + }); var query = builder.buildExploreQuery('TAG_VALUES', 'app'); expect(query).to.be('SHOW TAG VALUES FROM "cpu" WITH KEY = "app" WHERE "host" =~ /server.*/'); }); diff --git a/public/app/plugins/datasource/influxdb/specs/query_part_specs.ts b/public/app/plugins/datasource/influxdb/specs/query_part_specs.ts new file mode 100644 index 00000000000..ea354bf1439 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/specs/query_part_specs.ts @@ -0,0 +1,41 @@ + +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import queryPart = require('../query_part'); + +describe('InfluxQueryBuilder', () => { + + describe('series with mesurement only', () => { + it('should handle nested function parts', () => { + var part = queryPart.create({ + name: 'derivative', + params: ['10s'], + }); + + expect(part.text).to.be('derivative(10s)'); + expect(part.render('mean(value)')).to.be('derivative(mean(value), 10s)'); + }); + + it('should handle suffirx parts', () => { + var part = queryPart.create({ + name: 'math', + params: ['/ 100'], + }); + + expect(part.text).to.be('math(/ 100)'); + expect(part.render('mean(value)')).to.be('mean(value) / 100'); + }); + + it('should handle alias parts', () => { + var part = queryPart.create({ + name: 'alias', + params: ['test'], + }); + + expect(part.text).to.be('alias(test)'); + expect(part.render('mean(value)')).to.be('mean(value) AS "test"'); + }); + + }); + +}); From ef2094f817d82218924ee0a0316707312f352e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 1 Oct 2015 15:48:45 +0200 Subject: [PATCH 05/17] feat(influxdb): minor progress on #2802 --- .../influxdb/partials/query.editor.html | 8 +----- .../plugins/datasource/influxdb/query_ctrl.js | 20 ++++++++++++- .../plugins/datasource/influxdb/query_part.ts | 28 ++++++++++++------- .../datasource/influxdb/query_part_editor.js | 3 +- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 1993c2d7aa9..98499303cc8 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -73,13 +73,7 @@
        • -
        -
          -
        • - -
        • -
        • - +
        diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 924c7ab77fe..1eba65c7c87 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -3,9 +3,10 @@ define([ 'lodash', './query_builder', './influx_query', + './query_part', './query_part_editor', ], -function (angular, _, InfluxQueryBuilder, InfluxQuery) { +function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -45,9 +46,26 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery) { }); $scope.fixTagSegments(); + $scope.buildSelectMenu(); $scope.removeTagFilterSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove tag filter --'}); }; + $scope.buildSelectMenu = function() { + var categories = queryPart.getCategories(); + $scope.selectMenu = _.reduce(categories, function(memo, cat, key) { + var menu = {text: key}; + menu.submenu = _.map(cat, function(item) { + return {text: item.name, value: item.name}; + }); + memo.push(menu); + return memo; + }, []); + }; + + $scope.selectMenuAction = function(selectParts, cat, subitem) { + selectParts.push(queryPart.create({name: subitem.value })); + }; + $scope.fixTagSegments = function() { var count = $scope.tagSegments.length; var lastSegment = $scope.tagSegments[Math.max(count-1, 0)]; diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 9d6d92f569c..84dab84806a 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -4,11 +4,10 @@ import _ = require('lodash'); var index = []; var categories = { - Combine: [], - Transform: [], - Calculate: [], - Filter: [], - Special: [] + Aggregations: [], + Transformations: [], + Math: [], + Aliasing: [], }; class QueryPartDef { @@ -26,6 +25,7 @@ class QueryPartDef { static register(options: any) { index[options.name] = new QueryPartDef(options); + options.category.push(index[options.name]); } } @@ -66,7 +66,15 @@ function quotedIdentityRenderer(part, innerExpr) { QueryPartDef.register({ name: 'mean', - category: categories.Transform, + category: categories.Aggregations, + params: [{type: 'field', quote: 'double'}], + defaultParams: ['value'], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + name: 'sum', + category: categories.Aggregations, params: [{type: 'field', quote: 'double'}], defaultParams: ['value'], renderer: functionRenderer, @@ -74,7 +82,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'derivative', - category: categories.Transform, + category: categories.Transformations, params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h']}], defaultParams: ['10s'], renderer: functionRenderer, @@ -82,7 +90,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'time', - category: categories.Transform, + category: categories.Transformations, params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], defaultParams: ['$interval'], renderer: functionRenderer, @@ -90,7 +98,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'math', - category: categories.Transform, + category: categories.Math, params: [{ name: "expr", type: "string"}], defaultParams: [' / 100'], renderer: suffixRenderer, @@ -98,7 +106,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'alias', - category: categories.Transform, + category: categories.Aliasing, params: [{ name: "name", type: "string", quote: 'double'}], defaultParams: ['alias'], renderMode: 'suffix', diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js index 6cfcda5f4e7..6adf41f9c3f 100644 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -16,7 +16,8 @@ function (angular, _, $) { restrict: 'E', templateUrl: 'app/plugins/datasource/influxdb/partials/query_part.html', scope: { - part: "=" + part: "=", + deleteAction: "&", }, link: function postLink($scope, elem) { var part = $scope.part; From 9b4150509c57271891090c1f9418b1ac48db7be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 24 Nov 2015 11:02:49 +0100 Subject: [PATCH 06/17] feat(influxdb): minor progress on new editor --- .../plugins/datasource/influxdb/datasource.js | 9 +++--- .../datasource/influxdb/influx_query.ts | 3 +- .../influxdb/partials/query.editor.html | 31 +------------------ .../plugins/datasource/influxdb/query_part.ts | 17 +++++++--- .../influxdb/specs/influx_query_specs.ts | 3 +- 5 files changed, 23 insertions(+), 40 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.js b/public/app/plugins/datasource/influxdb/datasource.js index 87696c17a2a..d066bc142ea 100644 --- a/public/app/plugins/datasource/influxdb/datasource.js +++ b/public/app/plugins/datasource/influxdb/datasource.js @@ -3,11 +3,11 @@ define([ 'lodash', 'app/core/utils/datemath', './influx_series', - './query_builder', + './influx_query', './directives', './query_ctrl', ], -function (angular, _, dateMath, InfluxSeries, InfluxQueryBuilder) { +function (angular, _, dateMath, InfluxSeries, InfluxQuery) { 'use strict'; var module = angular.module('grafana.services'); @@ -41,8 +41,9 @@ function (angular, _, dateMath, InfluxSeries, InfluxQueryBuilder) { queryTargets.push(target); // build query - var queryBuilder = new InfluxQueryBuilder(target); - var query = queryBuilder.build(); + var queryModel = new InfluxQuery(target); + var query = queryModel.render(); + console.log(query); query = query.replace(/\$interval/g, (target.interval || options.interval)); return query; diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index bdc4ffa8e73..218d7d4b0a5 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -18,7 +18,8 @@ class InfluxQuery { target.tags = target.tags || []; target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; target.select = target.select || [[ - {name: 'mean', params: ['value']}, + {name: 'field', params: ['value']}, + {name: 'mean', params: []}, ]]; this.updateSelectParts(); diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 98499303cc8..59835405da3 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -87,42 +87,13 @@
      • - - - - - - - - - - - - - - - - - - - - -
      - -
        -
      • - -
      • -
      • - +
      - -
      • diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 84dab84806a..c528a6c56dd 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -8,6 +8,7 @@ var categories = { Transformations: [], Math: [], Aliasing: [], + Fields: [], }; class QueryPartDef { @@ -64,19 +65,27 @@ function quotedIdentityRenderer(part, innerExpr) { return '"' + part.params[0] + '"'; } +QueryPartDef.register({ + name: 'field', + category: categories.Fields, + params: [{type: 'field'}], + defaultParams: ['value'], + renderer: quotedIdentityRenderer, +}); + QueryPartDef.register({ name: 'mean', category: categories.Aggregations, - params: [{type: 'field', quote: 'double'}], - defaultParams: ['value'], + params: [], + defaultParams: [], renderer: functionRenderer, }); QueryPartDef.register({ name: 'sum', category: categories.Aggregations, - params: [{type: 'field', quote: 'double'}], - defaultParams: ['value'], + params: [], + defaultParams: [], renderer: functionRenderer, }); diff --git a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts index 9634e327c67..a6f70249a68 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts @@ -21,7 +21,8 @@ describe.only('InfluxQuery', function() { measurement: 'cpu', select: [ [ - {name: 'mean', params: ['value']}, + {name: 'field', params: ['value']}, + {name: 'mean', params: []}, {name: 'math', params: ['/100']}, {name: 'alias', params: ['text']}, ] From 31e2a8b8e9a6c8f7db1faf1b09ea6e9ca02b7644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 24 Nov 2015 17:01:18 +0100 Subject: [PATCH 07/17] feat(influxdb): more work onnew influxdb editor --- .../datasource/influxdb/influx_query.ts | 34 +++++++++++++++---- .../influxdb/partials/query.editor.html | 8 ++--- .../influxdb/partials/query_part.html | 2 +- .../plugins/datasource/influxdb/query_ctrl.js | 34 +++++++------------ .../plugins/datasource/influxdb/query_part.ts | 3 +- .../datasource/influxdb/query_part_editor.js | 5 +-- 6 files changed, 50 insertions(+), 36 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 218d7d4b0a5..ec75bc8d75b 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -6,9 +6,19 @@ import queryPart = require('./query_part'); declare var InfluxQueryBuilder: any; +class InfluxSelectModel { + modelParts: any[]; + persistedParts: any[]; + + constructor(persistedParts: any[]) { + this.persistedParts = persistedParts; + this.modelParts = _.map(persistedParts, queryPart.create); + } +} + class InfluxQuery { target: any; - selectParts: any[]; + selectModels: any[]; groupByParts: any; queryBuilder: any; @@ -29,10 +39,8 @@ class InfluxQuery { } updateSelectParts() { - this.selectParts = _.map(this.target.select, function(parts: any) { - return _.map(parts, function(part: any) { - return queryPart.create(part); - }); + this.selectModels = _.map(this.target.select, function(parts: any) { + return new InfluxSelectModel(parts); }); } @@ -41,6 +49,18 @@ class InfluxQuery { this.updateSelectParts(); } + removeSelectPart(selectModel, part) { + var partIndex = _.indexOf(selectModel.modelParts, part); + selectModel.persistedParts.splice(partIndex, 1); + this.updateSelectParts(); + } + + addSelectPart(selectModel, name) { + var partModel = queryPart.create({name: name}); + selectModel.persistedParts.push(partModel.part); + selectModel.modelParts.push(partModel); + } + addSelect() { this.target.select.push([ {name: 'mean', params: ['value']}, @@ -92,8 +112,8 @@ class InfluxQuery { var query = 'SELECT '; var i, y; - for (i = 0; i < this.selectParts.length; i++) { - let parts = this.selectParts[i]; + for (i = 0; i < this.selectModels.length; i++) { + let parts = this.selectModels[i].modelParts; var selectText = ""; for (y = 0; y < parts.length; y++) { let part = parts[y]; diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 59835405da3..0af29a39981 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -65,15 +65,15 @@
        -
        +
        • SELECT
        • -
        • - +
        • +
        • -
        diff --git a/public/app/plugins/datasource/influxdb/partials/query_part.html b/public/app/plugins/datasource/influxdb/partials/query_part.html index 0a28bee11f7..ea66c010efa 100644 --- a/public/app/plugins/datasource/influxdb/partials/query_part.html +++ b/public/app/plugins/datasource/influxdb/partials/query_part.html @@ -1,6 +1,6 @@
        - +
        {{part.def.name}}() diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 1eba65c7c87..9bfb5b036e4 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -62,8 +62,18 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { }, []); }; - $scope.selectMenuAction = function(selectParts, cat, subitem) { - selectParts.push(queryPart.create({name: subitem.value })); + $scope.selectMenuAction = function(selectModel, cat, subitem) { + $scope.queryModel.addSelectPart(selectModel, subitem.value); + $scope.get_data(); + }; + + $scope.removeSelectPart = function(selectModel, part) { + $scope.queryModel.removeSelectPart(selectModel, part); + $scope.get_data(); + }; + + $scope.selectPartUpdated = function() { + $scope.get_data(); }; $scope.fixTagSegments = function() { @@ -75,27 +85,9 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { } }; - $scope.addGroupBy = function() { - $scope.target.groupBy.push({type: 'tag', key: "select tag"}); - }; - - $scope.removeGroupBy = function(index) { - $scope.target.groupBy.splice(index, 1); - $scope.get_data(); - }; - - $scope.addSelect = function() { - $scope.queryModel.addSelect(); - }; - - $scope.removeSelect = function(index) { - $scope.queryModel.removeSelect(index); - $scope.get_data(); - }; - $scope.measurementChanged = function() { $scope.target.measurement = $scope.measurementSegment.value; - $scope.$parent.get_data(); + $scope.get_data(); }; $scope.getFields = function() { diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index c528a6c56dd..1e750a361bc 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -135,7 +135,8 @@ class QueryPart { throw {message: 'Could not find query part ' + part.name}; } - this.params = part.params || _.clone(this.def.defaultParams); + part.params = part.params || _.clone(this.def.defaultParams); + this.params = part.params; this.updateText(); } diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js index 6adf41f9c3f..c7e16d86ec6 100644 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -17,7 +17,8 @@ function (angular, _, $) { templateUrl: 'app/plugins/datasource/influxdb/partials/query_part.html', scope: { part: "=", - deleteAction: "&", + removeAction: "&", + partUpdated: "&", }, link: function postLink($scope, elem) { var part = $scope.part; @@ -55,7 +56,7 @@ function (angular, _, $) { $link.html(templateSrv.highlightVariablesAsHtml(newValue)); part.updateParam($input.val(), paramIndex); - $scope.$apply($scope.targetChanged); + $scope.$apply($scope.partUpdated); } $input.hide(); From c9ba856c527c316d8a5ecdd1d177dfe861df1ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 25 Nov 2015 10:22:20 +0100 Subject: [PATCH 08/17] feat(influxdb): more work on influxdb editor --- .../datasource/influxdb/influx_query.ts | 36 +++++---- .../influxdb/partials/query.editor.html | 8 +- .../influxdb/partials/query_part.html | 3 +- .../plugins/datasource/influxdb/query_ctrl.js | 8 +- .../plugins/datasource/influxdb/query_part.ts | 74 ++++++++++++++++++- .../datasource/influxdb/query_part_editor.js | 5 ++ .../influxdb/specs/influx_query_specs.ts | 59 +++++++++++++++ 7 files changed, 163 insertions(+), 30 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index ec75bc8d75b..0ba2df1833f 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -6,16 +6,6 @@ import queryPart = require('./query_part'); declare var InfluxQueryBuilder: any; -class InfluxSelectModel { - modelParts: any[]; - persistedParts: any[]; - - constructor(persistedParts: any[]) { - this.persistedParts = persistedParts; - this.modelParts = _.map(persistedParts, queryPart.create); - } -} - class InfluxQuery { target: any; selectModels: any[]; @@ -40,7 +30,15 @@ class InfluxQuery { updateSelectParts() { this.selectModels = _.map(this.target.select, function(parts: any) { - return new InfluxSelectModel(parts); + return _.map(parts, queryPart.create); + }); + } + + updatePersistedParts() { + this.target.select = _.map(this.selectModels, function(selectParts) { + return _.map(selectParts, function(part: any) { + return {name: part.def.name, params: part.params}; + }); }); } @@ -49,16 +47,16 @@ class InfluxQuery { this.updateSelectParts(); } - removeSelectPart(selectModel, part) { - var partIndex = _.indexOf(selectModel.modelParts, part); - selectModel.persistedParts.splice(partIndex, 1); - this.updateSelectParts(); + removeSelectPart(selectParts, part) { + var partIndex = _.indexOf(selectParts, part); + selectParts.splice(partIndex, 1); + this.updatePersistedParts(); } - addSelectPart(selectModel, name) { + addSelectPart(selectParts, name) { var partModel = queryPart.create({name: name}); - selectModel.persistedParts.push(partModel.part); - selectModel.modelParts.push(partModel); + partModel.def.addStrategy(selectParts, partModel); + this.updatePersistedParts(); } addSelect() { @@ -113,7 +111,7 @@ class InfluxQuery { var query = 'SELECT '; var i, y; for (i = 0; i < this.selectModels.length; i++) { - let parts = this.selectModels[i].modelParts; + let parts = this.selectModels[i]; var selectText = ""; for (y = 0; y < parts.length; y++) { let part = parts[y]; diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 0af29a39981..86ab92dbcaa 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -65,15 +65,15 @@
        -
        +
        • SELECT
        • -
        • - +
        • +
        • -
        diff --git a/public/app/plugins/datasource/influxdb/partials/query_part.html b/public/app/plugins/datasource/influxdb/partials/query_part.html index ea66c010efa..27bd44dec92 100644 --- a/public/app/plugins/datasource/influxdb/partials/query_part.html +++ b/public/app/plugins/datasource/influxdb/partials/query_part.html @@ -1,6 +1,5 @@
        - - +
        {{part.def.name}}() diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 9bfb5b036e4..bbc12bfc432 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -62,13 +62,13 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { }, []); }; - $scope.selectMenuAction = function(selectModel, cat, subitem) { - $scope.queryModel.addSelectPart(selectModel, subitem.value); + $scope.addSelectPart = function(selectParts, cat, subitem) { + $scope.queryModel.addSelectPart(selectParts, subitem.value); $scope.get_data(); }; - $scope.removeSelectPart = function(selectModel, part) { - $scope.queryModel.removeSelectPart(selectModel, part); + $scope.removeSelectPart = function(selectParts, part) { + $scope.queryModel.removeSelectPart(selectParts, part); $scope.get_data(); }; diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 1e750a361bc..9df0fddee3e 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -11,17 +11,23 @@ var categories = { Fields: [], }; +var groupByTimeFunctions = []; + class QueryPartDef { name: string; params: any[]; defaultParams: any[]; renderer: any; + category: any; + addStrategy: any; constructor(options: any) { this.name = options.name; this.params = options.params; this.defaultParams = options.defaultParams; this.renderer = options.renderer; + this.category = options.category; + this.addStrategy = options.addStrategy; } static register(options: any) { @@ -65,6 +71,67 @@ function quotedIdentityRenderer(part, innerExpr) { return '"' + part.params[0] + '"'; } +function replaceAggregationAddStrategy(selectParts, partModel) { + // look for existing aggregation + for (var i = 0; i < selectParts.length; i++) { + var part = selectParts[i]; + if (part.def.category === categories.Aggregations) { + selectParts[i] = partModel; + return; + } + } + + selectParts.splice(1, 0, partModel); +} + +function addTransformationStrategy(selectParts, partModel) { + var i; + // look for index to add transformation + for (i = 0; i < selectParts.length; i++) { + var part = selectParts[i]; + if (part.def.category === categories.Math || part.def.category === categories.Aliasing) { + break; + } + } + + selectParts.splice(i, 0, partModel); +} + +function addMathStrategy(selectParts, partModel) { + var partCount = selectParts.length; + if (partCount > 0) { + // if last is math, replace it + if (selectParts[partCount-1].def.name === 'math') { + selectParts[partCount-1] = partModel; + return; + } + // if next to last is math, replace it + if (selectParts[partCount-2].def.name === 'math') { + selectParts[partCount-2] = partModel; + return; + } + // if last is alias add it before + else if (selectParts[partCount-1].def.name === 'alias') { + selectParts.splice(partCount-1, 0, partModel); + return; + } + } + selectParts.push(partModel); +} + +function addAliasStrategy(selectParts, partModel) { + var partCount = selectParts.length; + if (partCount > 0) { + // if last is alias, replace it + if (selectParts[partCount-1].def.name === 'alias') { + selectParts[partCount-1] = partModel; + return; + } + } + selectParts.push(partModel); +} + + QueryPartDef.register({ name: 'field', category: categories.Fields, @@ -75,6 +142,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'mean', + addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, params: [], defaultParams: [], @@ -83,6 +151,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'sum', + addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, params: [], defaultParams: [], @@ -91,6 +160,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'derivative', + addStrategy: addTransformationStrategy, category: categories.Transformations, params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h']}], defaultParams: ['10s'], @@ -99,7 +169,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'time', - category: categories.Transformations, + category: groupByTimeFunctions, params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], defaultParams: ['$interval'], renderer: functionRenderer, @@ -107,6 +177,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'math', + addStrategy: addMathStrategy, category: categories.Math, params: [{ name: "expr", type: "string"}], defaultParams: [' / 100'], @@ -115,6 +186,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'alias', + addStrategy: addAliasStrategy, category: categories.Aliasing, params: [{ name: "name", type: "string", quote: 'double'}], defaultParams: ['alias'], diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js index c7e16d86ec6..9fbe02acf21 100644 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -117,6 +117,11 @@ function (angular, _, $) { $controlsContainer.show(); }; + $scope.removeActionInternal = function() { + $scope.toggleControls(); + $scope.removeAction(); + }; + function addElementsAndCompile() { _.each(partDef.params, function(param, index) { if (param.optional && part.params.length <= index) { diff --git a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts index a6f70249a68..038ad5140c1 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts @@ -34,4 +34,63 @@ describe.only('InfluxQuery', function() { }); }); + describe('when adding select part', function() { + + it('should add mean after after field', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + select: [[{name: 'field', params: ['value']}]] + }); + + query.addSelectPart(query.selectModels[0], 'mean'); + expect(query.target.select[0].length).to.be(2); + expect(query.target.select[0][1].name).to.be('mean'); + }); + + it('should replace sum by mean', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + select: [[{name: 'field', params: ['value']}, {name: 'mean'}]] + }); + + query.addSelectPart(query.selectModels[0], 'sum'); + expect(query.target.select[0].length).to.be(2); + expect(query.target.select[0][1].name).to.be('sum'); + }); + + it('should add math before alias', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + select: [[{name: 'field', params: ['value']}, {name: 'mean'}, {name: 'alias'}]] + }); + + query.addSelectPart(query.selectModels[0], 'math'); + expect(query.target.select[0].length).to.be(4); + expect(query.target.select[0][2].name).to.be('math'); + }); + + it('should add math last', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + select: [[{name: 'field', params: ['value']}, {name: 'mean'}]] + }); + + query.addSelectPart(query.selectModels[0], 'math'); + expect(query.target.select[0].length).to.be(3); + expect(query.target.select[0][2].name).to.be('math'); + }); + + it('should replace math', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + select: [[{name: 'field', params: ['value']}, {name: 'mean'}, {name: 'math'}]] + }); + + query.addSelectPart(query.selectModels[0], 'math'); + expect(query.target.select[0].length).to.be(3); + expect(query.target.select[0][2].name).to.be('math'); + }); + + }); + }); From 5ba19144d5da7e1be35a5eb2f2df63c63e4f1429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 25 Nov 2015 12:30:56 +0100 Subject: [PATCH 09/17] feat(influxdb): more work on query editor --- .../datasource/influxdb/influx_query.ts | 42 ++++++++------- .../influxdb/partials/query.editor.html | 3 ++ .../influxdb/partials/query_part.html | 2 +- .../plugins/datasource/influxdb/query_ctrl.js | 12 ++++- .../plugins/datasource/influxdb/query_part.ts | 53 ++++++++++--------- 5 files changed, 66 insertions(+), 46 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 0ba2df1833f..7675583fe03 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -18,13 +18,13 @@ class InfluxQuery { target.tags = target.tags || []; target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; target.select = target.select || [[ - {name: 'field', params: ['value']}, - {name: 'mean', params: []}, + {type: 'field', params: ['value']}, + {type: 'mean', params: []}, ]]; this.updateSelectParts(); this.groupByParts = [ - queryPart.create({name: 'time', params: ['$interval']}) + queryPart.create({type: 'time', params: ['$interval']}) ]; } @@ -37,7 +37,7 @@ class InfluxQuery { updatePersistedParts() { this.target.select = _.map(this.selectModels, function(selectParts) { return _.map(selectParts, function(part: any) { - return {name: part.def.name, params: part.params}; + return {type: part.def.type, params: part.params}; }); }); } @@ -48,24 +48,26 @@ class InfluxQuery { } removeSelectPart(selectParts, part) { - var partIndex = _.indexOf(selectParts, part); - selectParts.splice(partIndex, 1); + // if we remove the field remove the whole statement + if (part.def.type === 'field') { + if (this.selectModels.length > 1) { + var modelsIndex = _.indexOf(this.selectModels, selectParts); + this.selectModels.splice(modelsIndex, 1); + } + } else { + var partIndex = _.indexOf(selectParts, part); + selectParts.splice(partIndex, 1); + } + this.updatePersistedParts(); } - addSelectPart(selectParts, name) { - var partModel = queryPart.create({name: name}); - partModel.def.addStrategy(selectParts, partModel); + addSelectPart(selectParts, type) { + var partModel = queryPart.create({type: type}); + partModel.def.addStrategy(selectParts, partModel, this); this.updatePersistedParts(); } - addSelect() { - this.target.select.push([ - {name: 'mean', params: ['value']}, - ]); - this.updateSelectParts(); - } - private renderTagCondition(tag, index) { var str = ""; var operator = tag.operator; @@ -100,12 +102,12 @@ class InfluxQuery { render() { var target = this.target; - if (!target.measurement) { - throw "Metric measurement is missing"; + if (target.rawQuery) { + return target.query; } - if (!target.fields) { - target.fields = [{name: 'value', func: target.function || 'mean'}]; + if (!target.measurement) { + throw "Metric measurement is missing"; } var query = 'SELECT '; diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 86ab92dbcaa..854690fbe31 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -73,6 +73,9 @@
      • +
      • + +
      diff --git a/public/app/plugins/datasource/influxdb/partials/query_part.html b/public/app/plugins/datasource/influxdb/partials/query_part.html index 27bd44dec92..0eb0146ec13 100644 --- a/public/app/plugins/datasource/influxdb/partials/query_part.html +++ b/public/app/plugins/datasource/influxdb/partials/query_part.html @@ -2,4 +2,4 @@
      -{{part.def.name}}() +{{part.def.type}}() diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index bbc12bfc432..5a3a64b0bf3 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -55,11 +55,21 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { $scope.selectMenu = _.reduce(categories, function(memo, cat, key) { var menu = {text: key}; menu.submenu = _.map(cat, function(item) { - return {text: item.name, value: item.name}; + return {text: item.type, value: item.type}; }); memo.push(menu); return memo; }, []); + + $scope.groupByMenu = _.reduce(categories, function(memo, cat, key) { + var menu = {text: key}; + menu.submenu = _.map(cat, function(item) { + return {text: item.type, value: item.type}; + }); + memo.push(menu); + return memo; + }, []); + }; $scope.addSelectPart = function(selectParts, cat, subitem) { diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 9df0fddee3e..664dfb3dec6 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -14,7 +14,7 @@ var categories = { var groupByTimeFunctions = []; class QueryPartDef { - name: string; + type: string; params: any[]; defaultParams: any[]; renderer: any; @@ -22,7 +22,7 @@ class QueryPartDef { addStrategy: any; constructor(options: any) { - this.name = options.name; + this.type = options.type; this.params = options.params; this.defaultParams = options.defaultParams; this.renderer = options.renderer; @@ -31,13 +31,13 @@ class QueryPartDef { } static register(options: any) { - index[options.name] = new QueryPartDef(options); - options.category.push(index[options.name]); + index[options.type] = new QueryPartDef(options); + options.category.push(index[options.type]); } } function functionRenderer(part, innerExpr) { - var str = part.def.name + '('; + var str = part.def.type + '('; var parameters = _.map(part.params, (value, index) => { var paramType = part.def.params[index]; if (paramType.quote === 'single') { @@ -101,17 +101,17 @@ function addMathStrategy(selectParts, partModel) { var partCount = selectParts.length; if (partCount > 0) { // if last is math, replace it - if (selectParts[partCount-1].def.name === 'math') { + if (selectParts[partCount-1].def.type === 'math') { selectParts[partCount-1] = partModel; return; } // if next to last is math, replace it - if (selectParts[partCount-2].def.name === 'math') { + if (selectParts[partCount-2].def.type === 'math') { selectParts[partCount-2] = partModel; return; } // if last is alias add it before - else if (selectParts[partCount-1].def.name === 'alias') { + else if (selectParts[partCount-1].def.type === 'alias') { selectParts.splice(partCount-1, 0, partModel); return; } @@ -123,7 +123,7 @@ function addAliasStrategy(selectParts, partModel) { var partCount = selectParts.length; if (partCount > 0) { // if last is alias, replace it - if (selectParts[partCount-1].def.name === 'alias') { + if (selectParts[partCount-1].def.type === 'alias') { selectParts[partCount-1] = partModel; return; } @@ -131,9 +131,18 @@ function addAliasStrategy(selectParts, partModel) { selectParts.push(partModel); } +function addFieldStrategy(selectParts, partModel, query) { + // copy all parts + var parts = _.map(selectParts, function(part: any) { + return new QueryPart({type: part.def.type, params: _.clone(part.params)}); + }); + + query.selectModels.push(parts); +} QueryPartDef.register({ - name: 'field', + type: 'field', + addStrategy: addFieldStrategy, category: categories.Fields, params: [{type: 'field'}], defaultParams: ['value'], @@ -141,7 +150,7 @@ QueryPartDef.register({ }); QueryPartDef.register({ - name: 'mean', + type: 'mean', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, params: [], @@ -150,7 +159,7 @@ QueryPartDef.register({ }); QueryPartDef.register({ - name: 'sum', + type: 'sum', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, params: [], @@ -159,7 +168,7 @@ QueryPartDef.register({ }); QueryPartDef.register({ - name: 'derivative', + type: 'derivative', addStrategy: addTransformationStrategy, category: categories.Transformations, params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h']}], @@ -168,7 +177,7 @@ QueryPartDef.register({ }); QueryPartDef.register({ - name: 'time', + type: 'time', category: groupByTimeFunctions, params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], defaultParams: ['$interval'], @@ -176,7 +185,7 @@ QueryPartDef.register({ }); QueryPartDef.register({ - name: 'math', + type: 'math', addStrategy: addMathStrategy, category: categories.Math, params: [{ name: "expr", type: "string"}], @@ -185,7 +194,7 @@ QueryPartDef.register({ }); QueryPartDef.register({ - name: 'alias', + type: 'alias', addStrategy: addAliasStrategy, category: categories.Aliasing, params: [{ name: "name", type: "string", quote: 'double'}], @@ -202,9 +211,9 @@ class QueryPart { constructor(part: any) { this.part = part; - this.def = index[part.name]; + this.def = index[part.type]; if (!this.def) { - throw {message: 'Could not find query part ' + part.name}; + throw {message: 'Could not find query part ' + part.type}; } part.params = part.params || _.clone(this.def.defaultParams); @@ -247,11 +256,11 @@ class QueryPart { updateText() { if (this.params.length === 0) { - this.text = this.def.name + '()'; + this.text = this.def.type + '()'; return; } - var text = this.def.name + '('; + var text = this.def.type + '('; text += this.params.join(', '); text += ')'; this.text = text; @@ -263,10 +272,6 @@ export = { return new QueryPart(part); }, - getFuncDef: function(name) { - return index[name]; - }, - getCategories: function() { return categories; } From f00320c8b9bc7c974008c0cbb741da338ee0a554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 25 Nov 2015 14:27:22 +0100 Subject: [PATCH 10/17] feat(influxdb): query editor is starting to work, can now add group by parts --- .../datasource/influxdb/influx_query.ts | 48 +++++++++++++------ .../influxdb/partials/query.editor.html | 12 ++--- .../plugins/datasource/influxdb/query_ctrl.js | 45 ++++++++++++----- .../plugins/datasource/influxdb/query_part.ts | 8 ++++ .../influxdb/specs/influx_query_specs.ts | 28 +++++------ .../influxdb/specs/query_part_specs.ts | 8 ++-- 6 files changed, 97 insertions(+), 52 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 7675583fe03..cf5de4c2f49 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -16,22 +16,20 @@ class InfluxQuery { this.target = target; target.tags = target.tags || []; - target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; + target.groupBy = target.groupBy || [{type: 'time', params: ['$interval']}]; target.select = target.select || [[ {type: 'field', params: ['value']}, {type: 'mean', params: []}, ]]; - this.updateSelectParts(); - this.groupByParts = [ - queryPart.create({type: 'time', params: ['$interval']}) - ]; + this.updateProjection(); } - updateSelectParts() { + updateProjection() { this.selectModels = _.map(this.target.select, function(parts: any) { return _.map(parts, queryPart.create); }); + this.groupByParts = _.map(this.target.groupBy, queryPart.create); } updatePersistedParts() { @@ -42,9 +40,32 @@ class InfluxQuery { }); } + hasGroupByTime() { + return false; + } + + hasFill() { + return false; + } + + addGroupBy(value) { + var stringParts = value.match(/^(\w+)\((.*)\)$/); + var typePart = stringParts[1]; + var arg = stringParts[2]; + console.log(value, stringParts); + var partModel = queryPart.create({type: typePart, params: [arg]}); + this.target.groupBy.push(partModel.part); + this.updateProjection(); + } + + removeGroupByPart(part, index) { + this.target.groupBy.splice(index, 1); + this.updateProjection(); + } + removeSelect(index: number) { this.target.select.splice(index, 1); - this.updateSelectParts(); + this.updateProjection(); } removeSelectPart(selectParts, part) { @@ -139,14 +160,13 @@ class InfluxQuery { query += conditions.join(' '); query += (conditions.length > 0 ? ' AND ' : '') + '$timeFilter'; - query += ' GROUP BY'; - for (i = 0; i < target.groupBy.length; i++) { - var group = target.groupBy[i]; - if (group.type === 'time') { - query += ' time(' + this.getGroupByTimeInterval(group.interval) + ')'; - } else { - query += ', "' + group.key + '"'; + query += ' GROUP BY '; + for (i = 0; i < this.groupByParts.length; i++) { + var part = this.groupByParts[i]; + if (i > 0) { + query += ', '; } + query += part.render(''); } if (target.fill) { diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 854690fbe31..c77157324aa 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -73,24 +73,22 @@
    • -
    • - -
    -
    +
    • GROUP BY
    • -
    • - +
    • +
    • -
    • +
    diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 5a3a64b0bf3..774a1d84108 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -19,6 +19,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { $scope.target = $scope.target; $scope.queryModel = new InfluxQuery($scope.target); $scope.queryBuilder = new InfluxQueryBuilder($scope.target); + $scope.groupBySegment = uiSegmentSrv.newPlusButton(); if (!$scope.target.measurement) { $scope.measurementSegment = uiSegmentSrv.newSelectMeasurement(); @@ -60,16 +61,39 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { memo.push(menu); return memo; }, []); + }; - $scope.groupByMenu = _.reduce(categories, function(memo, cat, key) { - var menu = {text: key}; - menu.submenu = _.map(cat, function(item) { - return {text: item.type, value: item.type}; + $scope.getGroupByOptions = function() { + var query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS'); + + return $scope.datasource.metricFindQuery(query) + .then(function(tags) { + var options = []; + if (!$scope.queryModel.hasFill()) { + options.push(uiSegmentSrv.newSegment({value: 'fill(option)'})); + } + if (!$scope.queryModel.hasGroupByTime()) { + options.push(uiSegmentSrv.newSegment({value: 'time($interval)'})); + } + _.each(tags, function(tag) { + options.push(uiSegmentSrv.newSegment({value: 'tag(' + tag.text + ')'})); }); - memo.push(menu); - return memo; - }, []); + return options; + }) + .then(null, $scope.handleQueryError); + }; + $scope.groupByAction = function() { + $scope.queryModel.addGroupBy($scope.groupBySegment.value); + var plusButton = uiSegmentSrv.newPlusButton(); + $scope.groupBySegment.value = plusButton.value; + $scope.groupBySegment.html = plusButton.html; + $scope.get_data(); + }; + + $scope.removeGroupByPart = function(part, index) { + $scope.queryModel.removeGroupByPart(part, index); + $scope.get_data(); }; $scope.addSelectPart = function(selectParts, cat, subitem) { @@ -178,12 +202,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { }; $scope.getTagOptions = function() { - var query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS'); - - return $scope.datasource.metricFindQuery(query) - .then($scope.transformToSegments(false)) - .then(null, $scope.handleQueryError); - }; + }; $scope.setFill = function(fill) { $scope.target.fill = fill; diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 664dfb3dec6..5338b9b48c1 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -184,6 +184,14 @@ QueryPartDef.register({ renderer: functionRenderer, }); +QueryPartDef.register({ + type: 'tag', + category: groupByTimeFunctions, + params: [{name: 'tag', type: 'string'}], + defaultParams: ['tag'], + renderer: quotedIdentityRenderer, +}); + QueryPartDef.register({ type: 'math', addStrategy: addMathStrategy, diff --git a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts index 038ad5140c1..1e6e32ec8e9 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts @@ -21,10 +21,10 @@ describe.only('InfluxQuery', function() { measurement: 'cpu', select: [ [ - {name: 'field', params: ['value']}, - {name: 'mean', params: []}, - {name: 'math', params: ['/100']}, - {name: 'alias', params: ['text']}, + {type: 'field', params: ['value']}, + {type: 'mean', params: []}, + {type: 'math', params: ['/100']}, + {type: 'alias', params: ['text']}, ] ] }); @@ -39,56 +39,56 @@ describe.only('InfluxQuery', function() { it('should add mean after after field', function() { var query = new InfluxQuery({ measurement: 'cpu', - select: [[{name: 'field', params: ['value']}]] + select: [[{type: 'field', params: ['value']}]] }); query.addSelectPart(query.selectModels[0], 'mean'); expect(query.target.select[0].length).to.be(2); - expect(query.target.select[0][1].name).to.be('mean'); + expect(query.target.select[0][1].type).to.be('mean'); }); it('should replace sum by mean', function() { var query = new InfluxQuery({ measurement: 'cpu', - select: [[{name: 'field', params: ['value']}, {name: 'mean'}]] + select: [[{type: 'field', params: ['value']}, {type: 'mean'}]] }); query.addSelectPart(query.selectModels[0], 'sum'); expect(query.target.select[0].length).to.be(2); - expect(query.target.select[0][1].name).to.be('sum'); + expect(query.target.select[0][1].type).to.be('sum'); }); it('should add math before alias', function() { var query = new InfluxQuery({ measurement: 'cpu', - select: [[{name: 'field', params: ['value']}, {name: 'mean'}, {name: 'alias'}]] + select: [[{type: 'field', params: ['value']}, {type: 'mean'}, {type: 'alias'}]] }); query.addSelectPart(query.selectModels[0], 'math'); expect(query.target.select[0].length).to.be(4); - expect(query.target.select[0][2].name).to.be('math'); + expect(query.target.select[0][2].type).to.be('math'); }); it('should add math last', function() { var query = new InfluxQuery({ measurement: 'cpu', - select: [[{name: 'field', params: ['value']}, {name: 'mean'}]] + select: [[{type: 'field', params: ['value']}, {type: 'mean'}]] }); query.addSelectPart(query.selectModels[0], 'math'); expect(query.target.select[0].length).to.be(3); - expect(query.target.select[0][2].name).to.be('math'); + expect(query.target.select[0][2].type).to.be('math'); }); it('should replace math', function() { var query = new InfluxQuery({ measurement: 'cpu', - select: [[{name: 'field', params: ['value']}, {name: 'mean'}, {name: 'math'}]] + select: [[{type: 'field', params: ['value']}, {type: 'mean'}, {type: 'math'}]] }); query.addSelectPart(query.selectModels[0], 'math'); expect(query.target.select[0].length).to.be(3); - expect(query.target.select[0][2].name).to.be('math'); + expect(query.target.select[0][2].type).to.be('math'); }); }); diff --git a/public/app/plugins/datasource/influxdb/specs/query_part_specs.ts b/public/app/plugins/datasource/influxdb/specs/query_part_specs.ts index ea354bf1439..ee939fdab42 100644 --- a/public/app/plugins/datasource/influxdb/specs/query_part_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/query_part_specs.ts @@ -3,12 +3,12 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; import queryPart = require('../query_part'); -describe('InfluxQueryBuilder', () => { +describe('InfluxQueryPart', () => { describe('series with mesurement only', () => { it('should handle nested function parts', () => { var part = queryPart.create({ - name: 'derivative', + type: 'derivative', params: ['10s'], }); @@ -18,7 +18,7 @@ describe('InfluxQueryBuilder', () => { it('should handle suffirx parts', () => { var part = queryPart.create({ - name: 'math', + type: 'math', params: ['/ 100'], }); @@ -28,7 +28,7 @@ describe('InfluxQueryBuilder', () => { it('should handle alias parts', () => { var part = queryPart.create({ - name: 'alias', + type: 'alias', params: ['test'], }); From aa13a80d833d1722b72fb8468d46beaae394bab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 26 Nov 2015 09:28:59 +0100 Subject: [PATCH 11/17] fix(influxdb): fixed issue with metric segment component that caused double events --- public/app/core/directives/metric_segment.js | 35 +++++++++++--------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/public/app/core/directives/metric_segment.js b/public/app/core/directives/metric_segment.js index 9babb74dc2d..d8e3fb90c95 100644 --- a/public/app/core/directives/metric_segment.js +++ b/public/app/core/directives/metric_segment.js @@ -27,6 +27,7 @@ function (_, $, coreModule) { var segment = $scope.segment; var options = null; var cancelBlur = null; + var linkMode = true; $input.appendTo(elem); $button.appendTo(elem); @@ -55,19 +56,21 @@ function (_, $, coreModule) { }); }; - $scope.switchToLink = function(now) { - if (now === true || cancelBlur) { - clearTimeout(cancelBlur); - cancelBlur = null; - $input.hide(); - $button.show(); - $scope.updateVariableValue($input.val()); - } - else { - // need to have long delay because the blur - // happens long before the click event on the typeahead options - cancelBlur = setTimeout($scope.switchToLink, 100); - } + $scope.switchToLink = function() { + if (linkMode) { return; } + + clearTimeout(cancelBlur); + cancelBlur = null; + linkMode = true; + $input.hide(); + $button.show(); + $scope.updateVariableValue($input.val()); + }; + + $scope.inputBlur = function() { + // happens long before the click event on the typeahead options + // need to have long delay because the blur + cancelBlur = setTimeout($scope.switchToLink, 100); }; $scope.source = function(query, callback) { @@ -98,7 +101,7 @@ function (_, $, coreModule) { } $input.val(value); - $scope.switchToLink(true); + $scope.switchToLink(); return value; }; @@ -139,6 +142,8 @@ function (_, $, coreModule) { $input.show(); $input.focus(); + linkMode = false; + var typeahead = $input.data('typeahead'); if (typeahead) { $input.val(''); @@ -146,7 +151,7 @@ function (_, $, coreModule) { } }); - $input.blur($scope.switchToLink); + $input.blur($scope.inputBlur); $compile(elem.contents())($scope); } From 72d9fcdcb428224026d8999f797d66c91ab617d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 27 Nov 2015 16:35:40 +0100 Subject: [PATCH 12/17] feat(influxdb): progress with new influxdb editor --- .../datasource/influxdb/influx_query.ts | 16 +++++++--- .../plugins/datasource/influxdb/query_ctrl.js | 2 +- .../plugins/datasource/influxdb/query_part.ts | 19 +++++++++-- .../influxdb/specs/influx_query_specs.ts | 32 ++++++++++++++++--- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index cf5de4c2f49..a18562f90ed 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -16,7 +16,10 @@ class InfluxQuery { this.target = target; target.tags = target.tags || []; - target.groupBy = target.groupBy || [{type: 'time', params: ['$interval']}]; + target.groupBy = target.groupBy || [ + {type: 'time', params: ['$interval']}, + {type: 'fill', params: ['null']}, + ]; target.select = target.select || [[ {type: 'field', params: ['value']}, {type: 'mean', params: []}, @@ -160,13 +163,18 @@ class InfluxQuery { query += conditions.join(' '); query += (conditions.length > 0 ? ' AND ' : '') + '$timeFilter'; - query += ' GROUP BY '; + var groupBySection = ""; for (i = 0; i < this.groupByParts.length; i++) { var part = this.groupByParts[i]; if (i > 0) { - query += ', '; + // for some reason fill has no seperator + groupBySection += part.def.type === 'fill' ? ' ' : ', '; } - query += part.render(''); + groupBySection += part.render(''); + } + + if (groupBySection.length) { + query += ' GROUP BY ' + groupBySection; } if (target.fill) { diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 774a1d84108..bf89b550db3 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -70,7 +70,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { .then(function(tags) { var options = []; if (!$scope.queryModel.hasFill()) { - options.push(uiSegmentSrv.newSegment({value: 'fill(option)'})); + options.push(uiSegmentSrv.newSegment({value: 'fill(null)'})); } if (!$scope.queryModel.hasGroupByTime()) { options.push(uiSegmentSrv.newSegment({value: 'time($interval)'})); diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 5338b9b48c1..09aae007750 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -71,6 +71,13 @@ function quotedIdentityRenderer(part, innerExpr) { return '"' + part.params[0] + '"'; } +function fieldRenderer(part, innerExpr) { + if (part.params[0] === '*') { + return '*'; + } + return '"' + part.params[0] + '"'; +} + function replaceAggregationAddStrategy(selectParts, partModel) { // look for existing aggregation for (var i = 0; i < selectParts.length; i++) { @@ -146,7 +153,7 @@ QueryPartDef.register({ category: categories.Fields, params: [{type: 'field'}], defaultParams: ['value'], - renderer: quotedIdentityRenderer, + renderer: fieldRenderer, }); QueryPartDef.register({ @@ -184,12 +191,20 @@ QueryPartDef.register({ renderer: functionRenderer, }); +QueryPartDef.register({ + type: 'fill', + category: groupByTimeFunctions, + params: [{ name: "fill", type: "string", options: ['none', 'null', '0', 'previous'] }], + defaultParams: ['null'], + renderer: functionRenderer, +}); + QueryPartDef.register({ type: 'tag', category: groupByTimeFunctions, params: [{name: 'tag', type: 'string'}], defaultParams: ['tag'], - renderer: quotedIdentityRenderer, + renderer: fieldRenderer, }); QueryPartDef.register({ diff --git a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts index 1e6e32ec8e9..7b7ce8e4ae8 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts @@ -4,18 +4,18 @@ import InfluxQuery = require('../influx_query'); describe.only('InfluxQuery', function() { - describe('series with mesurement only', function() { + describe('render series with mesurement only', function() { it('should generate correct query', function() { var query = new InfluxQuery({ measurement: 'cpu', }); var queryText = query.render(); - expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); + expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)'); }); }); - describe('series with math and alias', function() { + describe('render series with math and alias', function() { it('should generate correct query', function() { var query = new InfluxQuery({ measurement: 'cpu', @@ -30,7 +30,31 @@ describe.only('InfluxQuery', function() { }); var queryText = query.render(); - expect(queryText).to.be('SELECT mean("value") /100 AS "text" FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); + expect(queryText).to.be('SELECT mean("value") /100 AS "text" FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)'); + }); + }); + + describe('render series without group by', function() { + it('should generate correct query', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + select: [[{type: 'field', params: ['value']}]], + groupBy: [], + }); + var queryText = query.render(); + expect(queryText).to.be('SELECT "value" FROM "cpu" WHERE $timeFilter'); + }); + }); + + describe('render series without group by and fill', function() { + it('should generate correct query', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + select: [[{type: 'field', params: ['value']}]], + groupBy: [{type: 'time'}, {type: 'fill', params: ['0']}], + }); + var queryText = query.render(); + expect(queryText).to.be('SELECT "value" FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(0)'); }); }); From b3d494d4c821734f2fca8a0e28944496b28c21d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 30 Nov 2015 08:40:11 +0100 Subject: [PATCH 13/17] feat(influxdb): minor fixes to new editor --- .../datasource/influxdb/influx_query.ts | 22 ++++++++++++--- .../plugins/datasource/influxdb/query_part.ts | 2 +- .../influxdb/specs/influx_query_specs.ts | 28 +++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index a18562f90ed..e4b798feb95 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -44,20 +44,34 @@ class InfluxQuery { } hasGroupByTime() { - return false; + return _.find(this.target.groupBy, (g: any) => g.type === 'time'); } hasFill() { - return false; + return _.find(this.target.groupBy, (g: any) => g.type === 'fill'); } addGroupBy(value) { var stringParts = value.match(/^(\w+)\((.*)\)$/); var typePart = stringParts[1]; var arg = stringParts[2]; - console.log(value, stringParts); var partModel = queryPart.create({type: typePart, params: [arg]}); - this.target.groupBy.push(partModel.part); + var partCount = this.target.groupBy.length; + + if (partCount === 0) { + this.target.groupBy.push(partModel.part); + } else if (typePart === 'time') { + this.target.groupBy.splice(0, 0, partModel.part); + } else if (typePart === 'tag') { + if (this.target.groupBy[partCount-1].type === 'fill') { + this.target.groupBy.splice(partCount-1, 0, partModel.part); + } else { + this.target.groupBy.push(partModel.part); + } + } else { + this.target.groupBy.push(partModel.part); + } + this.updateProjection(); } diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 09aae007750..1dd7c76808e 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -186,7 +186,7 @@ QueryPartDef.register({ QueryPartDef.register({ type: 'time', category: groupByTimeFunctions, - params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5m', '10m', '15m', '1h'] }], defaultParams: ['$interval'], renderer: functionRenderer, }); diff --git a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts index 7b7ce8e4ae8..e6745d116a4 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts @@ -58,6 +58,34 @@ describe.only('InfluxQuery', function() { }); }); + describe('when adding group by part', function() { + + it('should add tag before fill', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + groupBy: [{type: 'time'}, {type: 'fill'}] + }); + + query.addGroupBy('tag(host)'); + expect(query.target.groupBy.length).to.be(3); + expect(query.target.groupBy[1].type).to.be('tag'); + expect(query.target.groupBy[1].params[0]).to.be('host'); + expect(query.target.groupBy[2].type).to.be('fill'); + }); + + it('should add tag last if no fill', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + groupBy: [] + }); + + query.addGroupBy('tag(host)'); + expect(query.target.groupBy.length).to.be(1); + expect(query.target.groupBy[0].type).to.be('tag'); + }); + + }); + describe('when adding select part', function() { it('should add mean after after field', function() { From 721b37a08e3f3530a1a37c17f9af08a673d6ea4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 30 Nov 2015 10:14:42 +0100 Subject: [PATCH 14/17] feat(influxdb): new editor now supports field and tag lookup again --- .../datasource/influxdb/influx_query.ts | 22 +++++++++--- .../influxdb/partials/query.editor.html | 4 +-- .../plugins/datasource/influxdb/query_ctrl.js | 19 +++++++---- .../plugins/datasource/influxdb/query_part.ts | 4 +-- .../datasource/influxdb/query_part_editor.js | 34 +++++++++++++------ 5 files changed, 59 insertions(+), 24 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index e4b798feb95..74dc8d9293a 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -1,11 +1,8 @@ /// -/// import _ = require('lodash'); import queryPart = require('./query_part'); -declare var InfluxQueryBuilder: any; - class InfluxQuery { target: any; selectModels: any[]; @@ -76,6 +73,23 @@ class InfluxQuery { } removeGroupByPart(part, index) { + var categories = queryPart.getCategories(); + + if (part.def.type === 'time') { + // remove fill + this.target.groupBy = _.filter(this.target.groupBy, (g: any) => g.type !== 'fill'); + // remove aggregations + this.target.select = _.map(this.target.select, (s: any) => { + return _.filter(s, (part: any) => { + var partModel = queryPart.create(part); + if (partModel.def.category === categories.Aggregations) { + return false; + } + return true; + }); + }); + } + this.target.groupBy.splice(index, 1); this.updateProjection(); } @@ -166,7 +180,7 @@ class InfluxQuery { var measurement = target.measurement; if (!measurement.match('^/.*/') && !measurement.match(/^merge\(.*\)/)) { - measurement = '"' + measurement+ '"'; +measurement = '"' + measurement+ '"'; } query += ' FROM ' + measurement + ' WHERE '; diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index c77157324aa..0aeb8a44224 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -71,7 +71,7 @@ SELECT
  • - +
  • @@ -85,7 +85,7 @@ GROUP BY
  • - +
  • diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index bf89b550db3..38f87ecd84e 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -124,12 +124,6 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { $scope.get_data(); }; - $scope.getFields = function() { - var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS'); - return $scope.datasource.metricFindQuery(fieldsQuery) - .then($scope.transformToSegments(false), $scope.handleQueryError); - }; - $scope.toggleQueryMode = function () { $scope.target.rawQuery = !$scope.target.rawQuery; }; @@ -140,6 +134,19 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { .then($scope.transformToSegments(true), $scope.handleQueryError); }; + $scope.getPartOptions = function(part) { + if (part.def.type === 'field') { + var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS'); + return $scope.datasource.metricFindQuery(fieldsQuery) + .then($scope.transformToSegments(true), $scope.handleQueryError); + } + if (part.def.type === 'tag') { + var tagsQuery = $scope.queryBuilder.buildExploreQuery('TAG_KEYS'); + return $scope.datasource.metricFindQuery(tagsQuery) + .then($scope.transformToSegments(true), $scope.handleQueryError); + } + }; + $scope.handleQueryError = function(err) { $scope.parserError = err.message || 'Failed to issue metric query'; return []; diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 1dd7c76808e..06a24465bb7 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -151,7 +151,7 @@ QueryPartDef.register({ type: 'field', addStrategy: addFieldStrategy, category: categories.Fields, - params: [{type: 'field'}], + params: [{type: 'field', dynamicLookup: true}], defaultParams: ['value'], renderer: fieldRenderer, }); @@ -202,7 +202,7 @@ QueryPartDef.register({ QueryPartDef.register({ type: 'tag', category: groupByTimeFunctions, - params: [{name: 'tag', type: 'string'}], + params: [{name: 'tag', type: 'string', dynamicLookup: true}], defaultParams: ['tag'], renderer: fieldRenderer, }); diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js index 9fbe02acf21..9d81e315989 100644 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -19,6 +19,7 @@ function (angular, _, $) { part: "=", removeAction: "&", partUpdated: "&", + getOptions: "&", }, link: function postLink($scope, elem) { var part = $scope.part; @@ -75,18 +76,32 @@ function (angular, _, $) { this.style.width = (3 + this.value.length) * 8 + 'px'; } - function addTypeahead($input, paramIndex) { - $input.attr('data-provide', 'typeahead'); + function addTypeahead($input, param, paramIndex) { + if (!param.options && !param.dynamicLookup) { + return; + } - var options = partDef.params[paramIndex].options; - if (partDef.params[paramIndex].type === 'int') { + var typeaheadSource = function (query, callback) { + if (param.options) { return param.options; } + + $scope.$apply(function() { + $scope.getOptions().then(function(result) { + var dynamicOptions = _.map(result, function(op) { return op.value; }); + callback(dynamicOptions); + }); + }); + }; + + $input.attr('data-provide', 'typeahead'); + var options = param.options; + if (param.type === 'int') { options = _.map(options, function(val) { return val.toString(); }); } $input.typeahead({ - source: options, + source: typeaheadSource, minLength: 0, - items: 20, + items: 1000, updater: function (value) { setTimeout(function() { inputBlur.call($input[0], paramIndex); @@ -98,7 +113,8 @@ function (angular, _, $) { var typeahead = $input.data('typeahead'); typeahead.lookup = function () { this.query = this.$element.val() || ''; - return this.process(this.source); + var items = this.source(this.query, $.proxy(this.process, this)); + return items ? this.process(items) : items; }; } @@ -144,9 +160,7 @@ function (angular, _, $) { $input.keypress(_.partial(inputKeyPress, index)); $paramLink.click(_.partial(clickFuncParam, index)); - if (partDef.params[index].options) { - addTypeahead($input, index); - } + addTypeahead($input, param, index); }); } From 5a2b9b1f448b4bbf271f1202ee8afdb73785f2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 30 Nov 2015 15:09:18 +0100 Subject: [PATCH 15/17] feat(influxdb): worked on schema upgrade for new influx query editor --- public/app/features/dashboard/dashboardSrv.js | 47 +++++++++- .../datasource/influxdb/influx_query.ts | 7 -- .../plugins/datasource/influxdb/query_part.ts | 9 +- .../influxdb/specs/influx_query_specs.ts | 2 +- public/test/specs/dashboardSrv-specs.js | 87 ++++++++++++++++++- 5 files changed, 139 insertions(+), 13 deletions(-) diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index 698abcc51fd..73919ce0e01 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -229,9 +229,9 @@ function (angular, $, _, moment) { var i, j, k; var oldVersion = this.schemaVersion; var panelUpgrades = []; - this.schemaVersion = 7; + this.schemaVersion = 8; - if (oldVersion === 7) { + if (oldVersion === 8) { return; } @@ -342,6 +342,49 @@ function (angular, $, _, moment) { }); } + if (oldVersion < 8) { + panelUpgrades.push(function(panel) { + _.each(panel.targets, function(target) { + // update old influxdb query schema + if (target.fields && target.tags && target.groupBy) { + if (target.rawQuery) { + delete target.fields; + delete target.fill; + } else { + target.select = _.map(target.fields, function(field) { + var parts = []; + parts.push({type: 'field', params: [field.name]}); + parts.push({type: field.func, params: []}); + if (field.mathExpr) { + parts.push({type: 'math', params: [field.mathExpr]}); + } + if (field.asExpr) { + parts.push({type: 'alias', params: [field.asExpr]}); + } + return parts; + }); + delete target.fields; + _.each(target.groupBy, function(part) { + if (part.type === 'time' && part.interval) { + part.params = [part.interval]; + delete part.interval; + } + if (part.type === 'tag' && part.key) { + part.params = [part.key]; + delete part.key; + } + }); + + if (target.fill) { + target.groupBy.push({type: 'fill', params: [target.fill]}); + delete target.fill; + } + } + } + }); + }); + } + if (panelUpgrades.length === 0) { return; } diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 74dc8d9293a..092eb45c2df 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -144,13 +144,6 @@ class InfluxQuery { return str + '"' + tag.key + '" ' + operator + ' ' + value; } - private getGroupByTimeInterval(interval) { - if (interval === 'auto') { - return '$interval'; - } - return interval; - } - render() { var target = this.target; diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 06a24465bb7..152cb50b5a9 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -40,6 +40,11 @@ function functionRenderer(part, innerExpr) { var str = part.def.type + '('; var parameters = _.map(part.params, (value, index) => { var paramType = part.def.params[index]; + if (paramType.type === 'time') { + if (value === 'auto') { + value = '$interval'; + } + } if (paramType.quote === 'single') { return "'" + value + "'"; } else if (paramType.quote === 'double') { @@ -186,8 +191,8 @@ QueryPartDef.register({ QueryPartDef.register({ type: 'time', category: groupByTimeFunctions, - params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5m', '10m', '15m', '1h'] }], - defaultParams: ['$interval'], + params: [{ name: "interval", type: "time", options: ['auto', '1s', '10s', '1m', '5m', '10m', '15m', '1h'] }], + defaultParams: ['auto'], renderer: functionRenderer, }); diff --git a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts index e6745d116a4..6a89a23efa3 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts @@ -2,7 +2,7 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; import InfluxQuery = require('../influx_query'); -describe.only('InfluxQuery', function() { +describe('InfluxQuery', function() { describe('render series with mesurement only', function() { it('should generate correct query', function() { diff --git a/public/test/specs/dashboardSrv-specs.js b/public/test/specs/dashboardSrv-specs.js index 8da659825b8..5b2fefd384d 100644 --- a/public/test/specs/dashboardSrv-specs.js +++ b/public/test/specs/dashboardSrv-specs.js @@ -204,7 +204,7 @@ define([ }); it('dashboard schema version should be set to latest', function() { - expect(model.schemaVersion).to.be(7); + expect(model.schemaVersion).to.be(8); }); }); @@ -248,5 +248,90 @@ define([ expect(clone.meta).to.be(undefined); }); }); + + describe('when loading dashboard with old influxdb query schema', function() { + var model; + var target; + + beforeEach(function() { + model = _dashboardSrv.create({ + rows: [{ + panels: [{ + type: 'graph', + targets: [{ + "alias": "$tag_datacenter $tag_source $col", + "column": "value", + "measurement": "logins.count", + "fields": [ + { + "func": "mean", + "name": "value", + "mathExpr": "*2", + "asExpr": "value" + }, + { + "name": "one-minute", + "func": "mean", + "mathExpr": "*3", + "asExpr": "one-minute" + } + ], + "tags": [], + "fill": "previous", + "function": "mean", + "groupBy": [ + { + "interval": "auto", + "type": "time" + }, + { + "key": "source", + "type": "tag" + }, + { + "type": "tag", + "key": "datacenter" + } + ], + }] + }] + }] + }); + + target = model.rows[0].panels[0].targets[0]; + }); + + it('should update query schema', function() { + expect(target.fields).to.be(undefined); + expect(target.select.length).to.be(2); + expect(target.select[0].length).to.be(4); + expect(target.select[0][0].type).to.be('field'); + expect(target.select[0][1].type).to.be('mean'); + expect(target.select[0][2].type).to.be('math'); + expect(target.select[0][3].type).to.be('alias'); + }); + + }); + + describe('when creating dashboard model with missing list for annoations or templating', function() { + var model; + + beforeEach(function() { + model = _dashboardSrv.create({ + annotations: { + enable: true, + }, + templating: { + enable: true + } + }); + }); + + it('should add empty list', function() { + expect(model.annotations.list.length).to.be(0); + expect(model.templating.list.length).to.be(0); + }); + }); + }); }); From 4f04eaec3a5b40eaf5268415dd13016352774b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 30 Nov 2015 15:27:38 +0100 Subject: [PATCH 16/17] feat(influxdb): moved query builder tests --- .../datasource/influxdb/query_builder.js | 72 ------------ .../influxdb/specs/influx_query_specs.ts | 68 +++++++++++ .../influxdb/specs/query_builder_specs.ts | 110 ------------------ 3 files changed, 68 insertions(+), 182 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/query_builder.js b/public/app/plugins/datasource/influxdb/query_builder.js index ef1e1671d20..523003bd70d 100644 --- a/public/app/plugins/datasource/influxdb/query_builder.js +++ b/public/app/plugins/datasource/influxdb/query_builder.js @@ -93,77 +93,5 @@ function (_) { return query; }; - p._getGroupByTimeInterval = function(interval) { - if (interval === 'auto') { - return '$interval'; - } - return interval; - }; - - p._buildQuery = function() { - var target = this.target; - - if (!target.measurement) { - throw "Metric measurement is missing"; - } - - if (!target.fields) { - target.fields = [{name: 'value', func: target.function || 'mean'}]; - } - - var query = 'SELECT '; - var i; - for (i = 0; i < target.fields.length; i++) { - var field = target.fields[i]; - if (i > 0) { - query += ', '; - } - query += field.func + '("' + field.name + '")'; - if (field.mathExpr) { - query += field.mathExpr; - } - if (field.asExpr) { - query += ' AS "' + field.asExpr + '"'; - } else { - query += ' AS "' + field.name + '"'; - } - } - - var measurement = target.measurement; - if (!measurement.match('^/.*/') && !measurement.match(/^merge\(.*\)/)) { - measurement = '"' + measurement+ '"'; - } - - query += ' FROM ' + measurement + ' WHERE '; - var conditions = _.map(target.tags, function(tag, index) { - return renderTagCondition(tag, index); - }); - - query += conditions.join(' '); - query += (conditions.length > 0 ? ' AND ' : '') + '$timeFilter'; - - query += ' GROUP BY'; - for (i = 0; i < target.groupBy.length; i++) { - var group = target.groupBy[i]; - if (group.type === 'time') { - query += ' time(' + this._getGroupByTimeInterval(group.interval) + ')'; - } else { - query += ', "' + group.key + '"'; - } - } - - if (target.fill) { - query += ' fill(' + target.fill + ')'; - } - - target.query = query; - - return query; - }; - - p._modifyRawQuery = function () { - return this.target.query.replace(";", ""); - }; - return InfluxQueryBuilder; }); diff --git a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts index 6a89a23efa3..887bdd9c9b6 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts @@ -34,6 +34,74 @@ describe('InfluxQuery', function() { }); }); + describe('series with single tag only', function() { + it('should generate correct query', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + groupBy: [{type: 'time', params: ['auto']}], + tags: [{key: 'hostname', value: 'server1'}] + }); + + var queryText = query.render(); + + expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' AND $timeFilter' + + ' GROUP BY time($interval)'); + }); + + it('should switch regex operator with tag value is regex', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + groupBy: [{type: 'time', params: ['auto']}], + tags: [{key: 'app', value: '/e.*/'}] + }); + + var queryText = query.render(); + expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "app" =~ /e.*/ AND $timeFilter GROUP BY time($interval)'); + }); + }); + + describe('series with multiple tags only', function() { + it('should generate correct query', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + groupBy: [{type: 'time', params: ['auto']}], + tags: [{key: 'hostname', value: 'server1'}, {key: 'app', value: 'email', condition: "AND"}] + }); + + var queryText = query.render(); + expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' AND "app" = \'email\' AND ' + + '$timeFilter GROUP BY time($interval)'); + }); + }); + + describe('series with tags OR condition', function() { + it('should generate correct query', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + groupBy: [{type: 'time', params: ['auto']}], + tags: [{key: 'hostname', value: 'server1'}, {key: 'hostname', value: 'server2', condition: "OR"}] + }); + + var queryText = query.render(); + expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' OR "hostname" = \'server2\' AND ' + + '$timeFilter GROUP BY time($interval)'); + }); + }); + + describe('series with groupByTag', function() { + it('should generate correct query', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + tags: [], + groupBy: [{type: 'time', interval: 'auto'}, {type: 'tag', params: ['host']}], + }); + + var queryText = query.render(); + expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter ' + + 'GROUP BY time($interval), "host"'); + }); + }); + describe('render series without group by', function() { it('should generate correct query', function() { var query = new InfluxQuery({ diff --git a/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts b/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts index 5e06ef77db4..69b7aa0b6cd 100644 --- a/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts @@ -6,116 +6,6 @@ declare var InfluxQueryBuilder: any; describe('InfluxQueryBuilder', function() { - describe('series with mesurement only', function() { - it('should generate correct query', function() { - var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}] - }); - - var query = builder.build(); - - expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); - }); - }); - - describe('series with math expr and as expr', function() { - it('should generate correct query', function() { - var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - fields: [{name: 'test', func: 'max', mathExpr: '*2', asExpr: 'new_name'}], - groupBy: [{type: 'time', interval: 'auto'}] - }); - - var query = builder.build(); - - expect(query).to.be('SELECT max("test")*2 AS "new_name" FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); - }); - }); - - describe('series with single tag only', function() { - it('should generate correct query', function() { - var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'hostname', value: 'server1'}] - }); - - var query = builder.build(); - - expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE "hostname" = \'server1\' AND $timeFilter' - + ' GROUP BY time($interval)'); - }); - - it('should switch regex operator with tag value is regex', function() { - var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'app', value: '/e.*/'}] - }); - - var query = builder.build(); - expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE "app" =~ /e.*/ AND $timeFilter GROUP BY time($interval)'); - }); - }); - - describe('series with multiple fields', function() { - it('should generate correct query', function() { - var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - tags: [], - groupBy: [{type: 'time', interval: 'auto'}], - fields: [{ name: 'tx_in', func: 'sum' }, { name: 'tx_out', func: 'mean' }] - }); - - var query = builder.build(); - expect(query).to.be('SELECT sum("tx_in") AS "tx_in", mean("tx_out") AS "tx_out" ' + - 'FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); - }); - }); - - describe('series with multiple tags only', function() { - it('should generate correct query', function() { - var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'hostname', value: 'server1'}, {key: 'app', value: 'email', condition: "AND"}] - }); - - var query = builder.build(); - expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE "hostname" = \'server1\' AND "app" = \'email\' AND ' + - '$timeFilter GROUP BY time($interval)'); - }); - }); - - describe('series with tags OR condition', function() { - it('should generate correct query', function() { - var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'hostname', value: 'server1'}, {key: 'hostname', value: 'server2', condition: "OR"}] - }); - - var query = builder.build(); - expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE "hostname" = \'server1\' OR "hostname" = \'server2\' AND ' + - '$timeFilter GROUP BY time($interval)'); - }); - }); - - describe('series with groupByTag', function() { - it('should generate correct query', function() { - var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - tags: [], - groupBy: [{type: 'time', interval: 'auto'}, {type: 'tag', key: 'host'}], - }); - - var query = builder.build(); - expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE $timeFilter ' + - 'GROUP BY time($interval), "host"'); - }); - }); - describe('when building explore queries', function() { it('should only have measurement condition in tag keys query given query with measurement', function() { From 98f7febed1de5d87390b43b1252d95a59426e791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 30 Nov 2015 16:28:56 +0100 Subject: [PATCH 17/17] feat(influxdb): added all functions --- .../datasource/influxdb/influx_query.ts | 3 + .../plugins/datasource/influxdb/query_part.ts | 126 ++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 092eb45c2df..34b86e16930 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -85,6 +85,9 @@ class InfluxQuery { if (partModel.def.category === categories.Aggregations) { return false; } + if (partModel.def.category === categories.Selectors) { + return false; + } return true; }); }); diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 152cb50b5a9..56ea7eecd23 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -5,6 +5,7 @@ import _ = require('lodash'); var index = []; var categories = { Aggregations: [], + Selectors: [], Transformations: [], Math: [], Aliasing: [], @@ -91,6 +92,10 @@ function replaceAggregationAddStrategy(selectParts, partModel) { selectParts[i] = partModel; return; } + if (part.def.category === categories.Selectors) { + selectParts[i] = partModel; + return; + } } selectParts.splice(1, 0, partModel); @@ -161,6 +166,34 @@ QueryPartDef.register({ renderer: fieldRenderer, }); +// Aggregations +QueryPartDef.register({ + type: 'count', + addStrategy: replaceAggregationAddStrategy, + category: categories.Aggregations, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + type: 'distinct', + addStrategy: replaceAggregationAddStrategy, + category: categories.Aggregations, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + type: 'integral', + addStrategy: replaceAggregationAddStrategy, + category: categories.Aggregations, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + QueryPartDef.register({ type: 'mean', addStrategy: replaceAggregationAddStrategy, @@ -170,6 +203,15 @@ QueryPartDef.register({ renderer: functionRenderer, }); +QueryPartDef.register({ + type: 'median', + addStrategy: replaceAggregationAddStrategy, + category: categories.Aggregations, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + QueryPartDef.register({ type: 'sum', addStrategy: replaceAggregationAddStrategy, @@ -179,6 +221,8 @@ QueryPartDef.register({ renderer: functionRenderer, }); +// transformations + QueryPartDef.register({ type: 'derivative', addStrategy: addTransformationStrategy, @@ -188,6 +232,24 @@ QueryPartDef.register({ renderer: functionRenderer, }); +QueryPartDef.register({ + type: 'non_negative_derivative', + addStrategy: addTransformationStrategy, + category: categories.Transformations, + params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h']}], + defaultParams: ['10s'], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + type: 'stddev', + addStrategy: addTransformationStrategy, + category: categories.Transformations, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + QueryPartDef.register({ type: 'time', category: groupByTimeFunctions, @@ -204,6 +266,70 @@ QueryPartDef.register({ renderer: functionRenderer, }); +// Selectors +QueryPartDef.register({ + type: 'bottom', + addStrategy: replaceAggregationAddStrategy, + category: categories.Selectors, + params: [{name: 'count', type: 'int'}], + defaultParams: [3], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + type: 'first', + addStrategy: replaceAggregationAddStrategy, + category: categories.Selectors, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + type: 'last', + addStrategy: replaceAggregationAddStrategy, + category: categories.Selectors, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + type: 'max', + addStrategy: replaceAggregationAddStrategy, + category: categories.Selectors, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + type: 'min', + addStrategy: replaceAggregationAddStrategy, + category: categories.Selectors, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + type: 'percentile', + addStrategy: replaceAggregationAddStrategy, + category: categories.Selectors, + params: [{name: 'nth', type: 'int'}], + defaultParams: [95], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + type: 'top', + addStrategy: replaceAggregationAddStrategy, + category: categories.Selectors, + params: [{name: 'count', type: 'int'}], + defaultParams: [3], + renderer: functionRenderer, +}); + QueryPartDef.register({ type: 'tag', category: groupByTimeFunctions,