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