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/48] 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/48] 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/48] 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/48] 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/48] 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 c5b39a5100fda8efb2671236493d64158163ad1f Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 12 Nov 2015 09:56:46 -0800 Subject: [PATCH 06/48] Added currency units --- public/app/core/utils/kbn.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index 1d60ba48fd6..a9e8d219ce6 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -341,6 +341,8 @@ function($, _) { // Currencies kbn.valueFormats.currencyUSD = kbn.formatBuilders.currency('$'); kbn.valueFormats.currencyGBP = kbn.formatBuilders.currency('£'); + kbn.valueFormats.currencyEUR = kbn.formatBuilders.currency('€'); + kbn.valueFormats.currencyJPY = kbn.formatBuilders.currency('¥'); // Data kbn.valueFormats.bits = kbn.formatBuilders.binarySIPrefix('b'); @@ -508,6 +510,8 @@ function($, _) { submenu: [ {text: 'Dollars ($)', value: 'currencyUSD'}, {text: 'Pounds (£)', value: 'currencyGBP'}, + {text: 'Euro (€)', value: 'currencyEUR'}, + {text: 'Yen (¥)', value: 'currencyJPY'}, ] }, { From 3c54d14460f7356188718714a872337773f212ba Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 12 Nov 2015 20:26:59 -0800 Subject: [PATCH 07/48] Added UI for time units, minute scalability --- public/app/core/utils/kbn.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index a9e8d219ce6..70487fe46e4 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -489,6 +489,26 @@ function($, _) { } }; + kbn.valueFormats.m = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + + if (Math.abs(size) < 60) { + return kbn.toFixed(size, decimals) + " min"; + } + else if (Math.abs(size) < 1440) { + return kbn.toFixedScaled(size / 60, decimals, scaledDecimals, 2, " hour"); + } + else if (Math.abs(size) < 10080) { + return kbn.toFixedScaled(size / 1440, decimals, scaledDecimals, 3, " day"); + } + else if (Math.abs(size) < 604800) { + return kbn.toFixedScaled(size / 86400, decimals, scaledDecimals, 4, " week"); + } + else { + return kbn.toFixedScaled(size / 5.25948e5, decimals, scaledDecimals, 5, " year"); + } + }; + ///// FORMAT MENU ///// kbn.getUnitFormats = function() { @@ -522,6 +542,9 @@ function($, _) { {text: 'microseconds (µs)', value: 'µs' }, {text: 'milliseconds (ms)', value: 'ms' }, {text: 'seconds (s)', value: 's' }, + {text: 'minutes (m)', value: 'm' }, + {text: 'hours (h)', value: 'h' }, + {text: 'days (d)', value: 'd' }, ] }, { From 7f9c8a19357017c427fae258642108c746ed01f6 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 12 Nov 2015 23:56:24 -0800 Subject: [PATCH 08/48] Added hours, days units and tests for all --- public/app/core/utils/kbn.js | 35 +++++++++++++++++++++++++++-- public/test/core/utils/kbn_specs.js | 21 +++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index 70487fe46e4..123e8e16be1 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -432,7 +432,7 @@ function($, _) { kbn.valueFormats.s = function(size, decimals, scaledDecimals) { if (size === null) { return ""; } - if (Math.abs(size) < 600) { + if (Math.abs(size) < 60) { return kbn.toFixed(size, decimals) + " s"; } // Less than 1 hour, devide in minutes @@ -502,13 +502,44 @@ function($, _) { return kbn.toFixedScaled(size / 1440, decimals, scaledDecimals, 3, " day"); } else if (Math.abs(size) < 604800) { - return kbn.toFixedScaled(size / 86400, decimals, scaledDecimals, 4, " week"); + return kbn.toFixedScaled(size / 10080, decimals, scaledDecimals, 4, " week"); } else { return kbn.toFixedScaled(size / 5.25948e5, decimals, scaledDecimals, 5, " year"); } }; + kbn.valueFormats.h = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + + if (Math.abs(size) < 24) { + return kbn.toFixed(size, decimals) + " hour"; + } + else if (Math.abs(size) < 168) { + return kbn.toFixedScaled(size / 24, decimals, scaledDecimals, 2, " day"); + } + else if (Math.abs(size) < 8760) { + return kbn.toFixedScaled(size / 168, decimals, scaledDecimals, 3, " week"); + } + else { + return kbn.toFixedScaled(size / 8760, decimals, scaledDecimals, 4, " year"); + } + }; + + kbn.valueFormats.d = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + + if (Math.abs(size) < 7) { + return kbn.toFixed(size, decimals) + " day"; + } + else if (Math.abs(size) < 365) { + return kbn.toFixedScaled(size / 7, decimals, scaledDecimals, 2, " week"); + } + else { + return kbn.toFixedScaled(size / 365, decimals, scaledDecimals, 3, " year"); + } + }; + ///// FORMAT MENU ///// kbn.getUnitFormats = function() { diff --git a/public/test/core/utils/kbn_specs.js b/public/test/core/utils/kbn_specs.js index 2253f4d8ecd..23c752fe471 100644 --- a/public/test/core/utils/kbn_specs.js +++ b/public/test/core/utils/kbn_specs.js @@ -68,6 +68,27 @@ define([ describeValueFormat('wps', 789000000, 1000000, -1, '789M wps'); describeValueFormat('iops', 11000000000, 1000000000, -1, '11B iops'); + describeValueFormat('s', 24, 1, 0, '24 s'); + describeValueFormat('s', 246, 1, 0, '4.1 min'); + describeValueFormat('s', 24567, 100, 0, '6.82 hour'); + describeValueFormat('s', 24567890, 10000, 0, '40.62 week'); + describeValueFormat('s', 24567890000, 1000000, 0, '778.53 year'); + + describeValueFormat('m', 24, 1, 0, '24 min'); + describeValueFormat('m', 246, 10, 0, '4.1 hour'); + describeValueFormat('m', 6545, 10, 0, '4.55 day'); + describeValueFormat('m', 24567, 100, 0, '2.44 week'); + describeValueFormat('m', 24567892, 10000, 0, '46.7 year'); + + describeValueFormat('h', 21, 1, 0, '21 hour'); + describeValueFormat('h', 145, 1, 0, '6.04 day'); + describeValueFormat('h', 1234, 100, 0, '7.3 week'); + describeValueFormat('h', 9458, 1000, 0, '1.08 year'); + + describeValueFormat('d', 3, 1, 0, '3 day'); + describeValueFormat('d', 245, 100, 0, '35 week'); + describeValueFormat('d', 2456, 10, 0, '6.73 year'); + describe('kbn.toFixed and negative decimals', function() { it('should treat as zero decimals', function() { var str = kbn.toFixed(186.123, -2); From 1c35d4b26b1bd027053bb29cf35364feb06d2d21 Mon Sep 17 00:00:00 2001 From: toni-moreno Date: Mon, 16 Nov 2015 21:55:23 +0100 Subject: [PATCH 09/48] removed autoupdate on variabe refresh, don't needed if working interactive and fix #2722 --- public/app/features/templating/templateValuesSrv.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js index 81e40284667..6029bdf899c 100644 --- a/public/app/features/templating/templateValuesSrv.js +++ b/public/app/features/templating/templateValuesSrv.js @@ -45,17 +45,6 @@ function (angular, _, kbn) { }; this.setVariableFromUrl = function(variable, urlValue) { - if (variable.refresh) { - var self = this; - //refresh the list of options before setting the value - return this.updateOptions(variable).then(function() { - var option = _.findWhere(variable.options, { text: urlValue }); - option = option || { text: urlValue, value: urlValue }; - - self.updateAutoInterval(variable); - return self.setVariableValue(variable, option); - }); - } var option = _.findWhere(variable.options, { text: urlValue }); option = option || { text: urlValue, value: urlValue }; From 5559ad123883d75c4a32a2922b19f9b0b14e6b65 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sat, 21 Nov 2015 05:03:00 -0800 Subject: [PATCH 10/48] Return correct updated+created timestamps to frontend --- pkg/api/dashboard.go | 2 ++ pkg/api/dtos/models.go | 1 + 2 files changed, 3 insertions(+) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 8d7d3f6adfd..6490a118861 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -57,6 +57,8 @@ func GetDashboard(c *middleware.Context) { CanStar: c.IsSignedIn, CanSave: c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR, CanEdit: canEditDashboard(c.OrgRole), + Created: dash.Created, + Updated: dash.Updated, }, } diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index 9a4400adbdf..7af4c84f56d 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -40,6 +40,7 @@ type DashboardMeta struct { Slug string `json:"slug"` Expires time.Time `json:"expires"` Created time.Time `json:"created"` + Updated time.Time `json:"updated"` } type DashboardFullWithMeta struct { From a1afd2328daa121a9e1790f7a5eb79f684d41fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 23 Nov 2015 14:18:21 +0100 Subject: [PATCH 11/48] fix(elasticsearch): made interval template variable appear in group by time interval dropdown, fixes #3241 --- public/app/core/services/segment_srv.js | 19 ++++++++++++++++ .../datasource/elasticsearch/bucket_agg.js | 5 ++++- .../elasticsearch/partials/bucketAgg.html | 2 +- .../datasource/elasticsearch/query_ctrl.js | 22 +++---------------- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/public/app/core/services/segment_srv.js b/public/app/core/services/segment_srv.js index cb11512e12f..836437a6dc5 100644 --- a/public/app/core/services/segment_srv.js +++ b/public/app/core/services/segment_srv.js @@ -7,6 +7,7 @@ function (angular, _, coreModule) { 'use strict'; coreModule.service('uiSegmentSrv', function($sce, templateSrv) { + var self = this; function MetricSegment(options) { if (options === '*' || options.value === '*') { @@ -74,6 +75,24 @@ function (angular, _, coreModule) { }); }; + this.transformToSegments = function(addTemplateVars, variableTypeFilter) { + return function(results) { + var segments = _.map(results, function(segment) { + return self.newSegment({ value: segment.text, expandable: segment.expandable }); + }); + + if (addTemplateVars) { + _.each(templateSrv.variables, function(variable) { + if (variableTypeFilter === void 0 || variableTypeFilter === variable.type) { + segments.unshift(self.newSegment({ type: 'template', value: '$' + variable.name, expandable: true })); + } + }); + } + + return segments; + }; + }; + this.newSelectMetric = function() { return new MetricSegment({value: 'select metric', fake: true}); }; diff --git a/public/app/plugins/datasource/elasticsearch/bucket_agg.js b/public/app/plugins/datasource/elasticsearch/bucket_agg.js index 00bba14af05..014761da7f2 100644 --- a/public/app/plugins/datasource/elasticsearch/bucket_agg.js +++ b/public/app/plugins/datasource/elasticsearch/bucket_agg.js @@ -15,7 +15,6 @@ function (angular, _, queryDef) { $scope.bucketAggTypes = queryDef.bucketAggTypes; $scope.orderOptions = queryDef.orderOptions; $scope.sizeOptions = queryDef.sizeOptions; - $scope.intervalOptions = queryDef.intervalOptions; $rootScope.onAppEvent('elastic-query-updated', function() { $scope.validateModel(); @@ -128,6 +127,10 @@ function (angular, _, queryDef) { } }; + $scope.getIntervalOptions = function() { + return $q.when(uiSegmentSrv.transformToSegments(true, 'interval')(queryDef.intervalOptions)); + }; + $scope.addBucketAgg = function() { // if last is date histogram add it before var lastBucket = bucketAggs[bucketAggs.length - 1]; diff --git a/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html b/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html index ddec7d85de5..f6ff3f6cd93 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html +++ b/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html @@ -41,7 +41,7 @@ Interval
      • - +
      diff --git a/public/app/plugins/datasource/elasticsearch/query_ctrl.js b/public/app/plugins/datasource/elasticsearch/query_ctrl.js index 3965286ac12..08eb734ec33 100644 --- a/public/app/plugins/datasource/elasticsearch/query_ctrl.js +++ b/public/app/plugins/datasource/elasticsearch/query_ctrl.js @@ -1,13 +1,12 @@ define([ 'angular', - 'lodash', ], -function (angular, _) { +function (angular) { 'use strict'; var module = angular.module('grafana.controllers'); - module.controller('ElasticQueryCtrl', function($scope, $timeout, uiSegmentSrv, templateSrv) { + module.controller('ElasticQueryCtrl', function($scope, $timeout, uiSegmentSrv) { $scope.init = function() { var target = $scope.target; @@ -21,7 +20,7 @@ function (angular, _) { $scope.getFields = function(type) { var jsonStr = angular.toJson({find: 'fields', type: type}); return $scope.datasource.metricFindQuery(jsonStr) - .then($scope.transformToSegments(false)) + .then(uiSegmentSrv.transformToSegments(false)) .then(null, $scope.handleQueryError); }; @@ -35,21 +34,6 @@ function (angular, _) { $scope.appEvent('elastic-query-updated'); }; - $scope.transformToSegments = function(addTemplateVars) { - return function(results) { - var segments = _.map(results, function(segment) { - return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable }); - }); - - if (addTemplateVars) { - _.each(templateSrv.variables, function(variable) { - segments.unshift(uiSegmentSrv.newSegment({ type: 'template', value: '$' + variable.name, expandable: true })); - }); - } - return segments; - }; - }; - $scope.handleQueryError = function(err) { $scope.parserError = err.message || 'Failed to issue metric query'; return []; From cf1e167430d2a68db155f7b01d1f86e2d39ded2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 23 Nov 2015 16:10:32 +0100 Subject: [PATCH 12/48] feat(table panel): table panel can now show nested object data, closes #3263 --- public/app/panels/table/editor.html | 2 +- .../panels/table/specs/transformers_specs.ts | 47 ++++++++++++++----- public/app/panels/table/transformers.ts | 19 +++++--- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/public/app/panels/table/editor.html b/public/app/panels/table/editor.html index 27bf9002d56..b3632c96d19 100644 --- a/public/app/panels/table/editor.html +++ b/public/app/panels/table/editor.html @@ -158,7 +158,7 @@
      diff --git a/public/app/panels/table/specs/transformers_specs.ts b/public/app/panels/table/specs/transformers_specs.ts index 6e22e176e66..bb42b997d33 100644 --- a/public/app/panels/table/specs/transformers_specs.ts +++ b/public/app/panels/table/specs/transformers_specs.ts @@ -1,6 +1,7 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; import {TableModel} from '../table_model'; +import {transformers} from '../transformers'; describe('when transforming time series table', () => { var table; @@ -100,7 +101,11 @@ describe('when transforming time series table', () => { describe('JSON Data', () => { var panel = { transform: 'json', - columns: [{text: 'Timestamp', value: 'timestamp'}, {text: 'Message', value: 'message'}] + columns: [ + {text: 'Timestamp', value: 'timestamp'}, + {text: 'Message', value: 'message'}, + {text: 'nested.level2', value: 'nested.level2'}, + ] }; var rawData = [ { @@ -108,26 +113,42 @@ describe('when transforming time series table', () => { datapoints: [ { timestamp: 'time', - message: 'message' + message: 'message', + nested: { + level2: 'level2-value' + } } ] } ]; - beforeEach(() => { - table = TableModel.transform(rawData, panel); + describe('getColumns', function() { + it('should return nested properties', function() { + var columns = transformers['json'].getColumns(rawData); + expect(columns[0].text).to.be('timestamp'); + expect(columns[1].text).to.be('message'); + expect(columns[2].text).to.be('nested.level2'); + }); }); - it ('should return 2 columns', () => { - expect(table.columns.length).to.be(2); - expect(table.columns[0].text).to.be('Timestamp'); - expect(table.columns[1].text).to.be('Message'); - }); + describe('transform', function() { + beforeEach(() => { + table = TableModel.transform(rawData, panel); + }); - it ('should return 2 rows', () => { - expect(table.rows.length).to.be(1); - expect(table.rows[0][0]).to.be('time'); - expect(table.rows[0][1]).to.be('message'); + it ('should return 2 columns', () => { + expect(table.columns.length).to.be(3); + expect(table.columns[0].text).to.be('Timestamp'); + expect(table.columns[1].text).to.be('Message'); + expect(table.columns[2].text).to.be('nested.level2'); + }); + + it ('should return 2 rows', () => { + expect(table.rows.length).to.be(1); + expect(table.rows[0][0]).to.be('time'); + expect(table.rows[0][1]).to.be('message'); + expect(table.rows[0][2]).to.be('level2-value'); + }); }); }); diff --git a/public/app/panels/table/transformers.ts b/public/app/panels/table/transformers.ts index 1a5482693e7..a4d0d4395c5 100644 --- a/public/app/panels/table/transformers.ts +++ b/public/app/panels/table/transformers.ts @@ -2,6 +2,7 @@ import moment = require('moment'); import _ = require('lodash'); +import flatten = require('app/core/utils/flatten'); import TimeSeries = require('app/core/time_series'); var transformers = {}; @@ -149,9 +150,12 @@ transformers['json'] = { continue; } - for (var y = 0; y < series.datapoints.length; y++) { + // only look at 100 docs + var maxDocs = Math.min(series.datapoints.length, 100); + for (var y = 0; y < maxDocs; y++) { var doc = series.datapoints[y]; - for (var propName in doc) { + var flattened = flatten(doc, null); + for (var propName in flattened) { names[propName] = true; } } @@ -177,13 +181,16 @@ transformers['json'] = { for (y = 0; y < series.datapoints.length; y++) { var dp = series.datapoints[y]; var values = []; - for (z = 0; z < panel.columns.length; z++) { - values.push(dp[panel.columns[z].value]); - } - if (values.length === 0) { + if (_.isObject(dp) && panel.columns.length > 0) { + var flattened = flatten(dp, null); + for (z = 0; z < panel.columns.length; z++) { + values.push(flattened[panel.columns[z].value]); + } + } else { values.push(JSON.stringify(dp)); } + model.rows.push(values); } } From 24b9bc1e553a16abf675c273c69ef94715440d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 23 Nov 2015 18:20:12 +0100 Subject: [PATCH 13/48] fix(missing files): added missing files, oops --- public/app/core/utils/flatten.ts | 39 +++++++++++++++++++++++++ public/test/core/utils/flatten_specs.ts | 24 +++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 public/app/core/utils/flatten.ts create mode 100644 public/test/core/utils/flatten_specs.ts diff --git a/public/app/core/utils/flatten.ts b/public/app/core/utils/flatten.ts new file mode 100644 index 00000000000..fb4c47d1e3d --- /dev/null +++ b/public/app/core/utils/flatten.ts @@ -0,0 +1,39 @@ +// Copyright (c) 2014, Hugh Kennedy +// Based on code from https://github.com/hughsk/flat/blob/master/index.js +// +function flatten(target, opts): any { + opts = opts || {}; + + var delimiter = opts.delimiter || '.'; + var maxDepth = opts.maxDepth || 3; + var currentDepth = 1; + var output = {}; + + function step(object, prev) { + Object.keys(object).forEach(function(key) { + var value = object[key]; + var isarray = opts.safe && Array.isArray(value); + var type = Object.prototype.toString.call(value); + var isobject = type === "[object Object]"; + + var newKey = prev ? prev + delimiter + key : key; + + if (!opts.maxDepth) { + maxDepth = currentDepth + 1; + } + + if (!isarray && isobject && Object.keys(value).length && currentDepth < maxDepth) { + ++currentDepth; + return step(value, newKey); + } + + output[newKey] = value; + }); + } + + step(target, null); + + return output; +} + +export = flatten; diff --git a/public/test/core/utils/flatten_specs.ts b/public/test/core/utils/flatten_specs.ts new file mode 100644 index 00000000000..01815df8607 --- /dev/null +++ b/public/test/core/utils/flatten_specs.ts @@ -0,0 +1,24 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common' + +import flatten = require('app/core/utils/flatten') + +describe("flatten", () => { + + it('should return flatten object', () => { + var flattened = flatten({ + level1: 'level1-value', + deeper: { + level2: 'level2-value', + deeper: { + level3: 'level3-value' + } + } + }, null); + + expect(flattened['level1']).to.be('level1-value'); + expect(flattened['deeper.level2']).to.be('level2-value'); + expect(flattened['deeper.deeper.level3']).to.be('level3-value'); + }); + +}); + From ca5d0496ee70037bdd051ef82ab8f80300c1d0b4 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 23 Nov 2015 19:19:26 -0800 Subject: [PATCH 14/48] Validated HTTP responses --- docs/sources/reference/http_api.md | 66 +++++++++++++++--------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/docs/sources/reference/http_api.md b/docs/sources/reference/http_api.md index 5c917a7096f..15e7a06f3b8 100644 --- a/docs/sources/reference/http_api.md +++ b/docs/sources/reference/http_api.md @@ -142,10 +142,10 @@ Will return the dashboard given the dashboard slug. Slug is the url friendly ver "rows": [ { } - ] + ], "schemaVersion": 6, "version": 0 - }, + } } ### Delete dashboard @@ -787,7 +787,7 @@ Update Organisation, fields *Adress 1*, *Adress 2*, *City* are not implemented y "id": 2, "name": "User", "login": "user", - "email": "user@mygraf.com" + "email": "user@mygraf.com", "isAdmin": false } ] @@ -1046,7 +1046,7 @@ Deletes the starring of the given Dashboard for the actual user. "timezone":"browser", "title":"Home", "version":5 - } + }, "expires": 3600 } @@ -1091,34 +1091,33 @@ Keys: "canStar":false, "slug":"", "expires":"2200-13-32T25:23:23+02:00", - "created":"2200-13-32T28:24:23+02:00"}, - - { - "dashboard": { - "editable":false, - "hideControls":true, - "nav":[ - { - "enable":false, - "type":"timepicker" - } - ], - "rows": [ + "created":"2200-13-32T28:24:23+02:00" + }, + "dashboard": { + "editable":false, + "hideControls":true, + "nav":[ { - + "enable":false, + "type":"timepicker" } - ], - "style":"dark", - "tags":[], - "templating":{ - "list":[ - ] - }, - "time":{ - }, - "timezone":"browser", - "title":"Home", - "version":5 + ], + "rows": [ + { + + } + ], + "style":"dark", + "tags":[], + "templating":{ + "list":[ + ] + }, + "time":{ + }, + "timezone":"browser", + "title":"Home", + "version":5 } } @@ -1181,11 +1180,10 @@ Keys: "pluginType":"datasource", "serviceName":"Grafana", "type":"grafanasearch" + } } - } - } - - defaultDatasource: "Grafana" + }, + "defaultDatasource": "Grafana" } ## Login From 9c0141e84ea31386bb9be929421584107b1d1757 Mon Sep 17 00:00:00 2001 From: Joakim Lahtinen Date: Tue, 24 Nov 2015 07:29:52 +0100 Subject: [PATCH 15/48] Fixed some broken HTML. Simplified some control flow. --- public/app/core/settings.js | 3 +-- public/app/core/store.js | 2 +- public/app/features/dashboard/dashboardSrv.js | 11 +++++------ .../features/dashboard/partials/graphiteImport.html | 2 +- public/app/features/dashboard/unsavedChangesSrv.js | 6 +----- public/app/features/dashlinks/module.js | 2 +- public/app/features/org/partials/orgUsers.html | 2 +- public/app/features/panel/panel_menu.js | 3 +-- public/app/panels/text/lib/showdown.js | 6 +++--- .../app/plugins/datasource/influxdb/query_builder.js | 3 +-- .../app/plugins/datasource/prometheus/datasource.js | 6 ++---- 11 files changed, 18 insertions(+), 28 deletions(-) diff --git a/public/app/core/settings.js b/public/app/core/settings.js index 2fa484a9cba..59eaf8ea8b9 100644 --- a/public/app/core/settings.js +++ b/public/app/core/settings.js @@ -22,7 +22,6 @@ function (_) { appSubUrl: "" }; - var settings = _.extend({}, defaults, options); - return settings; + return _.extend({}, defaults, options); }; }); diff --git a/public/app/core/store.js b/public/app/core/store.js index 84e72b96314..504b0e5aff5 100644 --- a/public/app/core/store.js +++ b/public/app/core/store.js @@ -12,7 +12,7 @@ define([], function() { if (def !== void 0 && !this.exists(key)) { return def; } - return window.localStorage[key] === 'true' ? true : false; + return window.localStorage[key] === 'true'; }, exists: function(key) { return window.localStorage[key] !== void 0; diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index ce7cbeb11d2..698abcc51fd 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -26,7 +26,7 @@ function (angular, $, _, moment) { this.tags = data.tags || []; this.style = data.style || "dark"; this.timezone = data.timezone || 'browser'; - this.editable = data.editable === false ? false : true; + this.editable = data.editable !== false; this.hideControls = data.hideControls || false; this.sharedCrosshair = data.sharedCrosshair || false; this.rows = data.rows || []; @@ -48,10 +48,10 @@ function (angular, $, _, moment) { p._initMeta = function(meta) { meta = meta || {}; - meta.canShare = meta.canShare === false ? false : true; - meta.canSave = meta.canSave === false ? false : true; - meta.canStar = meta.canStar === false ? false : true; - meta.canEdit = meta.canEdit === false ? false : true; + meta.canShare = meta.canShare !== false; + meta.canSave = meta.canSave !== false; + meta.canStar = meta.canStar !== false; + meta.canEdit = meta.canEdit !== false; if (!this.editable) { meta.canEdit = false; @@ -151,7 +151,6 @@ function (angular, $, _, moment) { result.panel = panel; result.row = row; result.index = index; - return; } }); }); diff --git a/public/app/features/dashboard/partials/graphiteImport.html b/public/app/features/dashboard/partials/graphiteImport.html index 343b5d52e41..9c351346fe6 100644 --- a/public/app/features/dashboard/partials/graphiteImport.html +++ b/public/app/features/dashboard/partials/graphiteImport.html @@ -25,7 +25,7 @@ diff --git a/public/app/features/dashboard/unsavedChangesSrv.js b/public/app/features/dashboard/unsavedChangesSrv.js index 758c06b1975..bbb38d745c7 100644 --- a/public/app/features/dashboard/unsavedChangesSrv.js +++ b/public/app/features/dashboard/unsavedChangesSrv.js @@ -122,11 +122,7 @@ function(angular, _) { var currentJson = angular.toJson(current); var originalJson = angular.toJson(original); - if (currentJson !== originalJson) { - return true; - } - - return false; + return currentJson !== originalJson; }; p.open_modal = function() { diff --git a/public/app/features/dashlinks/module.js b/public/app/features/dashlinks/module.js index 6b5677e5d2d..b7029e6cf2d 100644 --- a/public/app/features/dashlinks/module.js +++ b/public/app/features/dashlinks/module.js @@ -52,7 +52,7 @@ function (angular, _) { if (link.asDropdown) { template += ' - -
        -
      • - -
      • -
      • - +
    - -
    • 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 18/48] 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 19/48] 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 20/48] 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 0ff5ff5dbe8ebc492385f6b49ce355d29daa29f1 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Wed, 25 Nov 2015 04:05:40 -0800 Subject: [PATCH 21/48] Enabled refresh interval for absolute time range --- public/app/features/dashboard/timeSrv.js | 7 +------ public/test/specs/time_srv_specs.js | 11 +++++++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/public/app/features/dashboard/timeSrv.js b/public/app/features/dashboard/timeSrv.js index e0e0ed47dab..1aff02bea90 100644 --- a/public/app/features/dashboard/timeSrv.js +++ b/public/app/features/dashboard/timeSrv.js @@ -93,12 +93,7 @@ define([ this.setTime = function(time) { _.extend(this.time, time); - // disable refresh if we have an absolute time - if (moment.isMoment(time.to)) { - this.old_refresh = this.dashboard.refresh || this.old_refresh; - this.setAutoRefresh(false); - } - else if (this.old_refresh && this.old_refresh !== this.dashboard.refresh) { + if (this.old_refresh && this.old_refresh !== this.dashboard.refresh) { this.setAutoRefresh(this.old_refresh); this.old_refresh = null; } diff --git a/public/test/specs/time_srv_specs.js b/public/test/specs/time_srv_specs.js index 4f065af6cf8..8c110483b47 100644 --- a/public/test/specs/time_srv_specs.js +++ b/public/test/specs/time_srv_specs.js @@ -78,17 +78,24 @@ define([ }); describe('setTime', function() { - it('should return disable refresh for absolute times', function() { + it('should return disable refresh if refresh is disabled for any range', function() { _dashboard.refresh = false; ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' }); expect(_dashboard.refresh).to.be(false); }); + it('should restore refresh for absolute time range', function() { + _dashboard.refresh = '30s'; + + ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' }); + expect(_dashboard.refresh).to.be('30s'); + }); + it('should restore refresh after relative time range is set', function() { _dashboard.refresh = '10s'; ctx.service.setTime({from: moment([2011,1,1]), to: moment([2015,1,1])}); - expect(_dashboard.refresh).to.be(false); + expect(_dashboard.refresh).to.be('10s'); ctx.service.setTime({from: '2011-01-01', to: 'now' }); expect(_dashboard.refresh).to.be('10s'); }); From 1f57cf08a7e63539b6d8d930205919f3ea156650 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Wed, 25 Nov 2015 04:38:54 -0800 Subject: [PATCH 22/48] Added an option to hide zero values --- public/app/core/time_series.ts | 6 ++++++ public/app/panels/graph/axisEditor.html | 3 +++ public/app/panels/graph/graph.tooltip.js | 5 +++++ public/app/panels/graph/legend.js | 4 ++++ 4 files changed, 18 insertions(+) diff --git a/public/app/core/time_series.ts b/public/app/core/time_series.ts index 0fb2b83b7e0..429e3882e8b 100644 --- a/public/app/core/time_series.ts +++ b/public/app/core/time_series.ts @@ -28,6 +28,7 @@ class TimeSeries { stats: any; legend: boolean; allIsNull: boolean; + allIsZero: boolean; decimals: number; scaledDecimals: number; @@ -96,6 +97,7 @@ class TimeSeries { this.stats.avg = null; this.stats.current = null; this.allIsNull = true; + this.allIsZero = true; var ignoreNulls = fillStyle === 'connected'; var nullAsZero = fillStyle === 'null as zero'; @@ -130,6 +132,10 @@ class TimeSeries { } } + if (currentValue != 0) { + this.allIsZero = false; + } + result.push([currentTime, currentValue]); } diff --git a/public/app/panels/graph/axisEditor.html b/public/app/panels/graph/axisEditor.html index 41ea8dd3696..e2bc4d36b1d 100644 --- a/public/app/panels/graph/axisEditor.html +++ b/public/app/panels/graph/axisEditor.html @@ -167,6 +167,9 @@
  • +
  • + +
  • diff --git a/public/app/panels/graph/graph.tooltip.js b/public/app/panels/graph/graph.tooltip.js index 8a747b61e0c..c56d7ce8c30 100644 --- a/public/app/panels/graph/graph.tooltip.js +++ b/public/app/panels/graph/graph.tooltip.js @@ -52,6 +52,11 @@ function ($) { continue; } + if (!series.data.length || (scope.panel.legend.hideZero && series.allIsZero)) { + results.push({ hidden: true }); + continue; + } + hoverIndex = this.findHoverIndexFromData(pos.x, series); results.time = series.data[hoverIndex][0]; diff --git a/public/app/panels/graph/legend.js b/public/app/panels/graph/legend.js index 065e74fed30..8604dff4f08 100644 --- a/public/app/panels/graph/legend.js +++ b/public/app/panels/graph/legend.js @@ -137,6 +137,10 @@ function (angular, _, $) { if (!series.legend) { continue; } + // ignore zero series + if (panel.legend.hideZero && series.allIsZero) { + continue; + } var html = '
    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 f972863f496e3752ca14e942172d90608c65983b Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Thu, 26 Nov 2015 13:21:38 +0900 Subject: [PATCH 24/48] add credential setting to handleDescribeInstances --- pkg/api/cloudwatch/cloudwatch.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 6311d62f43e..739d9aad4c9 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -126,8 +126,16 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { } func handleDescribeInstances(req *cwRequest, c *middleware.Context) { + creds := credentials.NewChainCredentials( + []credentials.Provider{ + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, + &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + }) + cfg := &aws.Config{ - Region: aws.String(req.Region), + Region: aws.String(req.Region), + Credentials: creds, } svc := ec2.New(session.New(cfg), cfg) From 2aabb387b154074966fb90a565d132964a1b0344 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Thu, 26 Nov 2015 15:59:56 +0900 Subject: [PATCH 25/48] fix EC2RoleProvider parameter --- pkg/api/cloudwatch/cloudwatch.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 739d9aad4c9..f4cde0ebd6a 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" + "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/ec2" @@ -40,11 +41,12 @@ func init() { } func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { + sess := session.New() creds := credentials.NewChainCredentials( []credentials.Provider{ &credentials.EnvProvider{}, &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, - &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, }) cfg := &aws.Config{ @@ -87,11 +89,12 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { } func handleListMetrics(req *cwRequest, c *middleware.Context) { + sess := session.New() creds := credentials.NewChainCredentials( []credentials.Provider{ &credentials.EnvProvider{}, &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, - &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, }) cfg := &aws.Config{ @@ -126,11 +129,12 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { } func handleDescribeInstances(req *cwRequest, c *middleware.Context) { + sess := session.New() creds := credentials.NewChainCredentials( []credentials.Provider{ &credentials.EnvProvider{}, &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, - &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, }) cfg := &aws.Config{ 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 26/48] 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 27/48] 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 712a420217f48d13908ed2b571b419031be7c3f1 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 29 Nov 2015 03:12:48 -0800 Subject: [PATCH 28/48] Fixed refresh setting for absolute time --- public/app/features/dashboard/timeSrv.js | 9 +++++++-- public/app/features/dashboard/timepicker/timepicker.ts | 2 +- public/test/specs/time_srv_specs.js | 9 ++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/timeSrv.js b/public/app/features/dashboard/timeSrv.js index 1aff02bea90..691bfd07904 100644 --- a/public/app/features/dashboard/timeSrv.js +++ b/public/app/features/dashboard/timeSrv.js @@ -90,10 +90,15 @@ define([ timer.cancel(this.refresh_timer); }; - this.setTime = function(time) { + this.setTime = function(time, enableRefresh) { _.extend(this.time, time); - if (this.old_refresh && this.old_refresh !== this.dashboard.refresh) { + // disable refresh if zoom in or zoom out + if (!enableRefresh && moment.isMoment(time.to)) { + this.old_refresh = this.dashboard.refresh || this.old_refresh; + this.setAutoRefresh(false); + } + else if (this.old_refresh && this.old_refresh !== this.dashboard.refresh) { this.setAutoRefresh(this.old_refresh); this.old_refresh = null; } diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/timepicker/timepicker.ts index c6d2680ce02..0f99210f3fa 100644 --- a/public/app/features/dashboard/timepicker/timepicker.ts +++ b/public/app/features/dashboard/timepicker/timepicker.ts @@ -115,7 +115,7 @@ export class TimePickerCtrl { this.timeSrv.setAutoRefresh(this.refresh.value); } - this.timeSrv.setTime(this.timeRaw); + this.timeSrv.setTime(this.timeRaw, true); this.$rootScope.appEvent('hide-dash-editor'); } diff --git a/public/test/specs/time_srv_specs.js b/public/test/specs/time_srv_specs.js index 8c110483b47..d645df3fd1e 100644 --- a/public/test/specs/time_srv_specs.js +++ b/public/test/specs/time_srv_specs.js @@ -92,10 +92,17 @@ define([ expect(_dashboard.refresh).to.be('30s'); }); + it('should restore refresh for absolute time range', function() { + _dashboard.refresh = '30s'; + + ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' }); + expect(_dashboard.refresh).to.be('30s'); + }); + it('should restore refresh after relative time range is set', function() { _dashboard.refresh = '10s'; ctx.service.setTime({from: moment([2011,1,1]), to: moment([2015,1,1])}); - expect(_dashboard.refresh).to.be('10s'); + expect(_dashboard.refresh).to.be(false); ctx.service.setTime({from: '2011-01-01', to: 'now' }); expect(_dashboard.refresh).to.be('10s'); }); From 8cdaa044e1eda21afa34f47caa18503d6a590e0d Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 29 Nov 2015 03:14:26 -0800 Subject: [PATCH 29/48] Removed repeating test --- public/test/specs/time_srv_specs.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/public/test/specs/time_srv_specs.js b/public/test/specs/time_srv_specs.js index d645df3fd1e..9943aae6cc3 100644 --- a/public/test/specs/time_srv_specs.js +++ b/public/test/specs/time_srv_specs.js @@ -92,13 +92,6 @@ define([ expect(_dashboard.refresh).to.be('30s'); }); - it('should restore refresh for absolute time range', function() { - _dashboard.refresh = '30s'; - - ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' }); - expect(_dashboard.refresh).to.be('30s'); - }); - it('should restore refresh after relative time range is set', function() { _dashboard.refresh = '10s'; ctx.service.setTime({from: moment([2011,1,1]), to: moment([2015,1,1])}); From 0dbd7d0e17c215007a4f08fb6783a8db3bda0368 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 29 Nov 2015 03:28:07 -0800 Subject: [PATCH 30/48] Fixed #3357 --- public/app/features/admin/partials/orgs.html | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/admin/partials/orgs.html b/public/app/features/admin/partials/orgs.html index fe87073eb6c..573c0799c68 100644 --- a/public/app/features/admin/partials/orgs.html +++ b/public/app/features/admin/partials/orgs.html @@ -17,6 +17,7 @@ + {{org.id}} {{org.name}} From 85ec70e92b2101f735a1045832e93f4e3dd19e74 Mon Sep 17 00:00:00 2001 From: Mat Schaffer Date: Mon, 30 Nov 2015 15:55:07 +0900 Subject: [PATCH 31/48] Use of `` seems to confuse angular so changing to DIMENSION_NAME --- .../plugins/datasource/cloudwatch/partials/query.editor.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/cloudwatch/partials/query.editor.html b/public/app/plugins/datasource/cloudwatch/partials/query.editor.html index d05ffd8547f..0984bc521e2 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/query.editor.html +++ b/public/app/plugins/datasource/cloudwatch/partials/query.editor.html @@ -73,7 +73,7 @@
    -
    +
    • Columns @@ -27,7 +27,8 @@ {{column.text}}
    • -
    • +
    diff --git a/public/app/panels/table/editor.ts b/public/app/panels/table/editor.ts index 2908023d330..e41ff422800 100644 --- a/public/app/panels/table/editor.ts +++ b/public/app/panels/table/editor.ts @@ -1,6 +1,6 @@ - /// + import angular = require('angular'); import $ = require('jquery'); import _ = require('lodash'); @@ -9,93 +9,103 @@ import moment = require('moment'); import {transformers} from './transformers'; -export function tablePanelEditor() { +export class TablePanelEditorCtrl { + + /** @ngInject */ + constructor($scope, $q, uiSegmentSrv) { + $scope.transformers = transformers; + $scope.unitFormats = kbn.getUnitFormats(); + $scope.colorModes = [ + {text: 'Disabled', value: null}, + {text: 'Cell', value: 'cell'}, + {text: 'Value', value: 'value'}, + {text: 'Row', value: 'row'}, + ]; + $scope.columnTypes = [ + {text: 'Number', value: 'number'}, + {text: 'String', value: 'string'}, + {text: 'Date', value: 'date'}, + ]; + $scope.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%']; + $scope.dateFormats = [ + {text: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss'}, + {text: 'MM/DD/YY h:mm:ss a', value: 'MM/DD/YY h:mm:ss a'}, + {text: 'MMMM D, YYYY LT', value: 'MMMM D, YYYY LT'}, + ]; + + $scope.addColumnSegment = uiSegmentSrv.newPlusButton(); + + $scope.getColumnOptions = function() { + if (!$scope.dataRaw) { + return $q.when([]); + } + var columns = transformers[$scope.panel.transform].getColumns($scope.dataRaw); + var segments = _.map(columns, (c: any) => uiSegmentSrv.newSegment({value: c.text})); + return $q.when(segments); + }; + + $scope.addColumn = function() { + $scope.panel.columns.push({text: $scope.addColumnSegment.value, value: $scope.addColumnSegment.value}); + $scope.render(); + + var plusButton = uiSegmentSrv.newPlusButton(); + $scope.addColumnSegment.html = plusButton.html; + }; + + $scope.transformChanged = function() { + $scope.panel.columns = []; + $scope.render(); + }; + + $scope.removeColumn = function(column) { + $scope.panel.columns = _.without($scope.panel.columns, column); + $scope.render(); + }; + + $scope.setUnitFormat = function(column, subItem) { + column.unit = subItem.value; + $scope.render(); + }; + + $scope.addColumnStyle = function() { + var columnStyleDefaults = { + unit: 'short', + type: 'number', + decimals: 2, + colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + colorMode: null, + pattern: '/.*/', + dateFormat: 'YYYY-MM-DD HH:mm:ss', + thresholds: [], + }; + + $scope.panel.styles.push(angular.copy(columnStyleDefaults)); + }; + + $scope.removeColumnStyle = function(style) { + $scope.panel.styles = _.without($scope.panel.styles, style); + }; + + $scope.getColumnNames = function() { + if (!$scope.table) { + return []; + } + return _.map($scope.table.columns, function(col: any) { + return col.text; + }); + }; + + } +} + + +export function tablePanelEditor($q, uiSegmentSrv) { 'use strict'; return { restrict: 'E', scope: true, templateUrl: 'app/panels/table/editor.html', - link: function(scope, elem) { - scope.transformers = transformers; - scope.unitFormats = kbn.getUnitFormats(); - scope.colorModes = [ - {text: 'Disabled', value: null}, - {text: 'Cell', value: 'cell'}, - {text: 'Value', value: 'value'}, - {text: 'Row', value: 'row'}, - ]; - scope.columnTypes = [ - {text: 'Number', value: 'number'}, - {text: 'String', value: 'string'}, - {text: 'Date', value: 'date'}, - ]; - scope.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%']; - scope.dateFormats = [ - {text: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss'}, - {text: 'MM/DD/YY h:mm:ss a', value: 'MM/DD/YY h:mm:ss a'}, - {text: 'MMMM D, YYYY LT', value: 'MMMM D, YYYY LT'}, - ]; - - scope.updateColumnsMenu = function(data) { - scope.columnsMenu = transformers[scope.panel.transform].getColumns(data); - scope.showColumnOptions = true; - }; - - scope.$on('render', function(event, table, rawData) { - scope.updateColumnsMenu(rawData); - }); - - scope.addColumn = function(menuItem) { - scope.panel.columns.push({text: menuItem.text, value: menuItem.value}); - scope.render(); - }; - - scope.transformChanged = function() { - scope.panel.columns = []; - scope.updateColumnsMenu(); - scope.render(); - }; - - scope.removeColumn = function(column) { - scope.panel.columns = _.without(scope.panel.columns, column); - scope.render(); - }; - - scope.setUnitFormat = function(column, subItem) { - column.unit = subItem.value; - scope.render(); - }; - - scope.addColumnStyle = function() { - var columnStyleDefaults = { - unit: 'short', - type: 'number', - decimals: 2, - colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], - colorMode: null, - pattern: '/.*/', - dateFormat: 'YYYY-MM-DD HH:mm:ss', - thresholds: [], - }; - - scope.panel.styles.push(angular.copy(columnStyleDefaults)); - }; - - scope.removeColumnStyle = function(style) { - scope.panel.styles = _.without(scope.panel.styles, style); - }; - - scope.getColumnNames = function() { - if (!scope.table) { - return []; - } - return _.map(scope.table.columns, function(col: any) { - return col.text; - }); - }; - - scope.updateColumnsMenu(scope.dataRaw); - } + controller: TablePanelEditorCtrl, }; } From 82d8e3c2b6d5ffa467e258cf98c6b25b1df548f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 1 Dec 2015 13:34:42 +0100 Subject: [PATCH 45/48] feat(graph panel): refactoring of hide zero option, #3336 --- CHANGELOG.md | 2 ++ public/app/panels/graph/axisEditor.html | 41 ++++++++++++------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72211224636..81b315d6323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Enhancements * **CloudWatch**: Support for multiple AWS Credentials, closes [#3053](https://github.com/grafana/grafana/issues/3053), [#3080](https://github.com/grafana/grafana/issues/3080) * **Elasticsearch**: Support for dynamic daily indices for annotations, closes [#3061](https://github.com/grafana/grafana/issues/3061) +* **Graph Panel**: Option to hide series with all zeroes from legend and tooltip, closes [#1381](https://github.com/grafana/grafana/issues/1381), [#3336](https://github.com/grafana/grafana/issues/3336) + ### Bug Fixes * **cloudwatch**: fix for handling of period for long time ranges, fixes [#3086](https://github.com/grafana/grafana/issues/3086) diff --git a/public/app/panels/graph/axisEditor.html b/public/app/panels/graph/axisEditor.html index e2bc4d36b1d..175c0c78fa7 100644 --- a/public/app/panels/graph/axisEditor.html +++ b/public/app/panels/graph/axisEditor.html @@ -150,9 +150,9 @@
    -
    +
      -
    • +
    • Legend
    • @@ -164,21 +164,13 @@
    • -
    • - -
    • -
    • - -
    -
    -
      -
    • - Legend values +
    • + Values
    • @@ -195,13 +187,8 @@
    • -
    -
    -
    -
    -
      -
    • - Decimals +
    • + Decimals
    - - +
    +
      +
    • + Hide +
    • +
    • + +
    • +
    • + +
    • +
    +
    +
    From 75b83af08ff4056ee0cdd6e5ef231be1d22c6a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 1 Dec 2015 14:04:44 +0100 Subject: [PATCH 46/48] refactoring(ui): minor ui improvement to graph axis tab --- public/app/panels/graph/axisEditor.html | 118 ++++++++++++++---------- 1 file changed, 68 insertions(+), 50 deletions(-) diff --git a/public/app/panels/graph/axisEditor.html b/public/app/panels/graph/axisEditor.html index 175c0c78fa7..2c22dee8359 100644 --- a/public/app/panels/graph/axisEditor.html +++ b/public/app/panels/graph/axisEditor.html @@ -6,7 +6,7 @@
  • Left Y
  • -
  • +
  • Unit
  • -
  • -    Grid Max -
  • -
  • - -
  • -
  • - Min -
  • -
  • - -
  • Scale type
  • @@ -46,12 +30,36 @@
    -
    +
    +
      +
    • + +
    • +
    • + Y-Max +
    • +
    • + +
    • +
    • + Y-Min +
    • +
    • + +
    • +
    +
    +
    +
    • Right Y
    • -
    • +
    • Unit
    • -
    • -    Grid Max -
    • -
    • - -
    • -
    • - Min -
    • -
    • - -
    • Scale type
    • @@ -91,6 +83,31 @@
    +
    +
      +
    • + +
    • +
    • + Y-Max +
    • +
    • + +
    • +
    • + Y-Min +
    • +
    • + +
    • +
    +
    +
    +
    @@ -168,6 +185,21 @@
    +
      +
    • + Hide series +
    • +
    • + +
    • +
    • + +
    • +
    +
    +
    + +
    • Values @@ -184,7 +216,7 @@
    • -
    • +
    • @@ -197,19 +229,5 @@
    -
    -
      -
    • - Hide -
    • -
    • - -
    • -
    • - -
    • -
    -
    -
    From 2436cda7ac21da46daa099165ec21f3e6447c568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 1 Dec 2015 15:28:34 +0100 Subject: [PATCH 47/48] fix(graph panel): minor spelling change --- public/app/panels/graph/axisEditor.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/panels/graph/axisEditor.html b/public/app/panels/graph/axisEditor.html index 2c22dee8359..d8a537dda46 100644 --- a/public/app/panels/graph/axisEditor.html +++ b/public/app/panels/graph/axisEditor.html @@ -190,10 +190,10 @@ Hide series
  • - +
  • - +
  • From 8eb3e48bc7c65f3682d71cc1923b3d4170f3edb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Dec 2015 18:22:47 +0100 Subject: [PATCH 48/48] fix(build): fixed build issues with concat not including require_config --- package.json | 2 +- public/views/index.html | 8 ++++---- tasks/build_task.js | 6 +++--- tasks/options/concat.js | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 4e3890be19f..75136e48f1a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "grunt-contrib-connect": "~0.5.0", "grunt-contrib-copy": "~0.5.0", "grunt-contrib-cssmin": "~0.6.1", - "grunt-contrib-htmlmin": "~0.1.3", + "grunt-contrib-htmlmin": "~0.6.0", "grunt-contrib-jshint": "~0.10.0", "grunt-contrib-less": "~0.7.0", "grunt-contrib-requirejs": "~0.4.4", diff --git a/public/views/index.html b/public/views/index.html index 5b4e2f5bd92..b9a1bc3c825 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -9,15 +9,15 @@ Grafana [[if .User.LightTheme]] - + [[else]] - + [[end]] - + - + diff --git a/tasks/build_task.js b/tasks/build_task.js index 7773299a06d..54562dfbde1 100644 --- a/tasks/build_task.js +++ b/tasks/build_task.js @@ -13,7 +13,7 @@ module.exports = function(grunt) { 'karma:test', 'phantomjs', 'css', - 'htmlmin:build', + // 'htmlmin:build', 'ngtemplates', 'cssmin:build', 'ngAnnotate:build', @@ -34,8 +34,8 @@ module.exports = function(grunt) { for(var key in summary){ if(summary.hasOwnProperty(key)){ - var orig = key.replace(root, root+'/[[.AppSubUrl]]'); - var revved = summary[key].replace(root, root+'/[[.AppSubUrl]]'); + var orig = key.replace(root, root+'/[[.AppSubUrl]]/public'); + var revved = summary[key].replace(root, root+'/[[.AppSubUrl]]/public'); fixed[orig] = revved; } } diff --git a/tasks/options/concat.js b/tasks/options/concat.js index c15aa8a2d6e..4e6927b306a 100644 --- a/tasks/options/concat.js +++ b/tasks/options/concat.js @@ -27,7 +27,7 @@ module.exports = function(config) { js: { src: [ '<%= tempDir %>/vendor/requirejs/require.js', - '<%= tempDir %>/app/components/require.config.js', + '<%= tempDir %>/app/require_config.js', '<%= tempDir %>/app/app.js', ], dest: '<%= genDir %>/app/app.js'