From 3a4e05133e8799a0219f683bc4ca0d9dcb84d2b9 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Fri, 8 Dec 2017 18:48:39 -0500 Subject: [PATCH] support for loading function definitions from graphite --- .editorconfig | 1 - package.json | 3 +- .../datasource/graphite/add_graphite_func.js | 134 +++--- .../plugins/datasource/graphite/datasource.ts | 125 ++++++ .../datasource/graphite/func_editor.js | 81 +++- .../app/plugins/datasource/graphite/gfunc.ts | 421 ++++++++---------- .../datasource/graphite/graphite_query.ts | 11 +- .../graphite/partials/query.editor.html | 2 +- .../plugins/datasource/graphite/query_ctrl.ts | 8 +- .../datasource/graphite/specs/gfunc.jest.ts | 11 +- .../graphite/specs/query_ctrl_specs.ts | 21 +- public/sass/components/_query_editor.scss | 1 + public/sass/components/_query_part.scss | 8 + yarn.lock | 183 +++++++- 14 files changed, 655 insertions(+), 355 deletions(-) diff --git a/.editorconfig b/.editorconfig index 84bbaf8a420..cbde126ca6c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,6 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true max_line_length = 120 -insert_final_newline = true [*.go] indent_style = tab diff --git a/package.json b/package.json index 01241637d46..13b8b9e3955 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "clipboard": "^1.7.1", "d3": "^4.11.0", "d3-scale-chromatic": "^1.1.1", - "eventemitter3": "^2.0.2", + "eventemitter3": "^2.0.3", "file-saver": "^1.3.3", "jquery": "^3.2.1", "lodash": "^4.17.4", @@ -154,6 +154,7 @@ "react-select": "^1.1.0", "react-sizeme": "^2.3.6", "remarkable": "^1.7.1", + "rst2html": "github:thoward/rst2html#d6e2f21", "rxjs": "^5.4.3", "tether": "^1.4.0", "tether-drop": "https://github.com/torkelo/drop", diff --git a/public/app/plugins/datasource/graphite/add_graphite_func.js b/public/app/plugins/datasource/graphite/add_graphite_func.js index b4df928adca..10d9b9ced71 100644 --- a/public/app/plugins/datasource/graphite/add_graphite_func.js +++ b/public/app/plugins/datasource/graphite/add_graphite_func.js @@ -2,13 +2,10 @@ define([ 'angular', 'lodash', 'jquery', - './gfunc', ], -function (angular, _, $, gfunc) { +function (angular, _, $) { 'use strict'; - gfunc = gfunc.default; - angular .module('grafana.directives') .directive('graphiteAddFunc', function($compile) { @@ -23,91 +20,92 @@ function (angular, _, $, gfunc) { return { link: function($scope, elem) { var ctrl = $scope.ctrl; - var graphiteVersion = ctrl.datasource.graphiteVersion; - var categories = gfunc.getCategories(graphiteVersion); - var allFunctions = getAllFunctionNames(categories); - - $scope.functionMenu = createFunctionDropDownMenu(categories); var $input = $(inputTemplate); var $button = $(buttonTemplate); + $input.appendTo(elem); $button.appendTo(elem); - $input.attr('data-provide', 'typeahead'); - $input.typeahead({ - source: allFunctions, - minLength: 1, - items: 10, - updater: function (value) { - var funcDef = gfunc.getFuncDef(value); - if (!funcDef) { - // try find close match - value = value.toLowerCase(); - funcDef = _.find(allFunctions, function(funcName) { - return funcName.toLowerCase().indexOf(value) === 0; + ctrl.datasource.getFuncDefs().then(function(funcDefs) { + var allFunctions = _.map(funcDefs, 'name').sort(); + + $scope.functionMenu = createFunctionDropDownMenu(funcDefs); + + $input.attr('data-provide', 'typeahead'); + $input.typeahead({ + source: allFunctions, + minLength: 1, + items: 10, + updater: function (value) { + var funcDef = ctrl.datasource.getFuncDef(value); + if (!funcDef) { + // try find close match + value = value.toLowerCase(); + funcDef = _.find(allFunctions, function(funcName) { + return funcName.toLowerCase().indexOf(value) === 0; + }); + + if (!funcDef) { return; } + } + + $scope.$apply(function() { + ctrl.addFunction(funcDef); }); - if (!funcDef) { return; } + $input.trigger('blur'); + return ''; } + }); - $scope.$apply(function() { - ctrl.addFunction(funcDef); - }); + $button.click(function() { + $button.hide(); + $input.show(); + $input.focus(); + }); - $input.trigger('blur'); - return ''; - } + $input.keyup(function() { + elem.toggleClass('open', $input.val() === ''); + }); + + $input.blur(function() { + // clicking the function dropdown menu wont + // work if you remove class at once + setTimeout(function() { + $input.val(''); + $input.hide(); + $button.show(); + elem.removeClass('open'); + }, 200); + }); + + $compile(elem.contents())($scope); }); - - $button.click(function() { - $button.hide(); - $input.show(); - $input.focus(); - }); - - $input.keyup(function() { - elem.toggleClass('open', $input.val() === ''); - }); - - $input.blur(function() { - // clicking the function dropdown menu wont - // work if you remove class at once - setTimeout(function() { - $input.val(''); - $input.hide(); - $button.show(); - elem.removeClass('open'); - }, 200); - }); - - $compile(elem.contents())($scope); } }; }); - function getAllFunctionNames(categories) { - return _.reduce(categories, function(list, category) { - _.each(category, function(func) { - list.push(func.name); - }); - return list; - }, []); - } + function createFunctionDropDownMenu(funcDefs) { + var categories = {}; - function createFunctionDropDownMenu(categories) { - return _.map(categories, function(list, category) { - var submenu = _.map(list, function(value) { - return { - text: value.name, - click: "ctrl.addFunction('" + value.name + "')", - }; + _.forEach(funcDefs, function(funcDef) { + if (!funcDef.category) { + return; + } + if (!categories[funcDef.category]) { + categories[funcDef.category] = []; + } + categories[funcDef.category].push({ + text: funcDef.name, + click: "ctrl.addFunction('" + funcDef.name + "')", }); + }); + return _.sortBy(_.map(categories, function(submenu, category) { return { text: category, - submenu: submenu + submenu: _.sortBy(submenu, 'text') }; - }); + }), 'text'); } }); diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 38133f801d8..8733a167da4 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -1,6 +1,7 @@ import _ from 'lodash'; import * as dateMath from 'app/core/utils/datemath'; import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version'; +import gfunc from './gfunc'; /** @ngInject */ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv) { @@ -12,6 +13,7 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv this.cacheTimeout = instanceSettings.cacheTimeout; this.withCredentials = instanceSettings.withCredentials; this.render_method = instanceSettings.render_method || 'POST'; + this.funcDefs = null; this.getQueryOptionsInfo = function() { return { @@ -347,6 +349,125 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv }); }; + this.createFuncInstance = function(funcDef, options?) { + return gfunc.createFuncInstance(funcDef, options, this.funcDefs); + }; + + this.getFuncDef = function(name) { + return gfunc.getFuncDef(name, this.funcDefs); + }; + + this.getFuncDefs = function() { + let self = this; + + if (self.funcDefs !== null) { + return Promise.resolve(self.funcDefs); + } + + if (!supportsFunctionIndex(self.graphiteVersion)) { + self.funcDefs = gfunc.getFuncDefs(self.graphiteVersion); + return Promise.resolve(self.funcDefs); + } + + let httpOptions = { + method: 'GET', + url: '/functions', + }; + + return self + .doGraphiteRequest(httpOptions) + .then(results => { + if (results.status !== 200 || typeof results.data !== 'object') { + self.funcDefs = gfunc.getFuncDefs(self.graphiteVersion); + return Promise.resolve(self.funcDefs); + } + + self.funcDefs = {}; + _.forEach(results.data || {}, (funcDef, funcName) => { + // skip graphite graph functions + if (funcDef.group === 'Graph') { + return; + } + + var func = { + name: funcDef.name, + description: funcDef.description, + category: funcDef.group, + params: [], + defaultParams: [], + fake: false, + }; + + // get rid of the first "seriesList" param + if (/^seriesLists?$/.test(_.get(funcDef, 'params[0].type', ''))) { + // handle functions that accept multiple seriesLists + // we leave the param in place but mark it optional, so users can add more series if they wish + if (funcDef.params[0].multiple) { + funcDef.params[0].required = false; + // otherwise chop off the first param, it'll be handled separately + } else { + funcDef.params.shift(); + } + // tag function as fake + } else { + func.fake = true; + } + + _.forEach(funcDef.params, rawParam => { + var param = { + name: rawParam.name, + type: 'string', + optional: !rawParam.required, + multiple: !!rawParam.multiple, + options: undefined, + }; + + if (rawParam.default !== undefined) { + func.defaultParams.push(_.toString(rawParam.default)); + } else if (rawParam.suggestions) { + func.defaultParams.push(_.toString(rawParam.suggestions[0])); + } else { + func.defaultParams.push(''); + } + + if (rawParam.type === 'boolean') { + param.type = 'boolean'; + param.options = ['true', 'false']; + } else if (rawParam.type === 'integer') { + param.type = 'int'; + } else if (rawParam.type === 'float') { + param.type = 'float'; + } else if (rawParam.type === 'node') { + param.type = 'node'; + param.options = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']; + } else if (rawParam.type === 'nodeOrTag') { + param.type = 'node_or_tag'; + param.options = ['name', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']; + } else if (rawParam.type === 'intOrInterval') { + param.type = 'int_or_interval'; + } else if (rawParam.type === 'seriesList') { + param.type = 'value_or_series'; + } + + if (rawParam.options) { + param.options = _.map(rawParam.options, _.toString); + } else if (rawParam.suggestions) { + param.options = _.map(rawParam.suggestions, _.toString); + } + + func.params.push(param); + }); + + self.funcDefs[funcName] = func; + }); + return self.funcDefs; + }) + .catch(err => { + self.funcDefs = gfunc.getFuncDefs(self.graphiteVersion); + return self.funcDefs; + }); + }; + this.testDatasource = function() { return this.metricFindQuery('*').then(function() { return { status: 'success', message: 'Data source is working' }; @@ -440,3 +561,7 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv function supportsTags(version: string): boolean { return isVersionGtOrEq(version, '1.1'); } + +function supportsFunctionIndex(version: string): boolean { + return isVersionGtOrEq(version, '1.1'); +} diff --git a/public/app/plugins/datasource/graphite/func_editor.js b/public/app/plugins/datasource/graphite/func_editor.js index 534b0886fff..08c11d6c5b7 100644 --- a/public/app/plugins/datasource/graphite/func_editor.js +++ b/public/app/plugins/datasource/graphite/func_editor.js @@ -2,8 +2,9 @@ define([ 'angular', 'lodash', 'jquery', + 'rst2html', ], -function (angular, _, $) { +function (angular, _, $, rst2html) { 'use strict'; angular @@ -12,7 +13,7 @@ function (angular, _, $) { var funcSpanTemplate = '{{func.def.name}}('; var paramTemplate = ''; + ' class="input-small tight-form-func-param">'; var funcControlsTemplate = '
' + @@ -29,7 +30,6 @@ function (angular, _, $) { var $funcControls = $(funcControlsTemplate); var ctrl = $scope.ctrl; var func = $scope.func; - var funcDef = func.def; var scheduledRelink = false; var paramCountAtLink = 0; @@ -37,11 +37,12 @@ function (angular, _, $) { /*jshint validthis:true */ var $link = $(this); + var $comma = $link.prev('.comma'); var $input = $link.next(); $input.val(func.params[paramIndex]); - $input.css('width', ($link.width() + 16) + 'px'); + $comma.removeClass('last'); $link.hide(); $input.show(); $input.focus(); @@ -68,22 +69,42 @@ function (angular, _, $) { } } + function paramDef(index) { + if (index < func.def.params.length) { + return func.def.params[index]; + } + if (_.last(func.def.params).multiple) { + return _.assign({}, _.last(func.def.params), {optional: true}); + } + return {}; + } + function inputBlur(paramIndex) { /*jshint validthis:true */ var $input = $(this); + if ($input.data('typeahead') && $input.data('typeahead').shown) { + return; + } + var $link = $input.prev(); + var $comma = $link.prev('.comma'); var newValue = $input.val(); - if (newValue !== '' || func.def.params[paramIndex].optional) { + if (newValue !== '' || paramDef(paramIndex).optional) { $link.html(templateSrv.highlightVariablesAsHtml(newValue)); - func.updateParam($input.val(), paramIndex); + func.updateParam(newValue, paramIndex); scheduledRelinkIfNeeded(); $scope.$apply(function() { ctrl.targetChanged(); }); + if ($link.hasClass('last') && newValue === '') { + $comma.addClass('last'); + } else { + $link.removeClass('last'); + } $input.hide(); $link.show(); } @@ -104,8 +125,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 = paramDef(paramIndex).options; + if (paramDef(paramIndex).type === 'int') { options = _.map(options, function(val) { return val.toString(); }); } @@ -148,18 +169,34 @@ function (angular, _, $) { $funcControls.appendTo(elem); $funcLink.appendTo(elem); - _.each(funcDef.params, function(param, index) { - if (param.optional && func.params.length <= index) { - return; - } + var defParams = _.clone(func.def.params); + var lastParam = _.last(func.def.params); - if (index > 0) { - $(', ').appendTo(elem); + while (func.params.length >= defParams.length && lastParam && lastParam.multiple) { + defParams.push(_.assign({}, lastParam, {optional: true})); + } + + _.each(defParams, function(param, index) { + if (param.optional && func.params.length < index) { + return false; } var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]); - var $paramLink = $('' + paramValue + ''); + + var last = (index >= func.params.length - 1) && param.optional && !paramValue; + if (last && param.multiple) { + paramValue = '+'; + } + + if (index > 0) { + $(', ').appendTo(elem); + } + + var $paramLink = $( + '' + + (paramValue || ' ') + ''); var $input = $(paramTemplate); + $input.attr('placeholder', param.name); paramCountAtLink++; @@ -171,10 +208,9 @@ function (angular, _, $) { $input.keypress(_.partial(inputKeyPress, index)); $paramLink.click(_.partial(clickFuncParam, index)); - if (funcDef.params[index].options) { + if (param.options) { addTypeahead($input, index); } - }); $(')').appendTo(elem); @@ -182,7 +218,7 @@ function (angular, _, $) { $compile(elem.contents())($scope); } - function ifJustAddedFocusFistParam() { + function ifJustAddedFocusFirstParam() { if ($scope.func.added) { $scope.func.added = false; setTimeout(function() { @@ -223,7 +259,12 @@ function (angular, _, $) { } if ($target.hasClass('fa-question-circle')) { - window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank'); + if (func.def.description) { + alert(rst2html(func.def.description)); + } else { + window.open( + "http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + func.def.name,'_blank'); + } return; } }); @@ -233,7 +274,7 @@ function (angular, _, $) { elem.children().remove(); addElementsAndCompile(); - ifJustAddedFocusFistParam(); + ifJustAddedFocusFirstParam(); registerFuncControlsToggle(); registerFuncControlsActions(); } diff --git a/public/app/plugins/datasource/graphite/gfunc.ts b/public/app/plugins/datasource/graphite/gfunc.ts index 282de306518..fdfa67be1f9 100644 --- a/public/app/plugins/datasource/graphite/gfunc.ts +++ b/public/app/plugins/datasource/graphite/gfunc.ts @@ -1,71 +1,56 @@ import _ from 'lodash'; -import $ from 'jquery'; import { isVersionGtOrEq } from 'app/core/utils/version'; -var index = []; -var categories = { - Combine: [], - Transform: [], - Calculate: [], - Filter: [], - Special: [], -}; +var index = {}; 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; + if (funcDef.shortName) { + index[funcDef.shortName] = funcDef; + } } -var optionalSeriesRefArgs = [ - { name: 'other', type: 'value_or_series', optional: true }, - { name: 'other', type: 'value_or_series', optional: true }, - { name: 'other', type: 'value_or_series', optional: true }, - { name: 'other', type: 'value_or_series', optional: true }, - { name: 'other', type: 'value_or_series', optional: true }, -]; +var optionalSeriesRefArgs = [{ name: 'other', type: 'value_or_series', optional: true, multiple: true }]; addFuncDef({ name: 'scaleToSeconds', - category: categories.Transform, + category: 'Transform', params: [{ name: 'seconds', type: 'int' }], defaultParams: [1], }); addFuncDef({ name: 'perSecond', - category: categories.Transform, + category: 'Transform', params: [{ name: 'max value', type: 'int', optional: true }], defaultParams: [], }); addFuncDef({ name: 'holtWintersForecast', - category: categories.Calculate, + category: 'Calculate', }); addFuncDef({ name: 'holtWintersConfidenceBands', - category: categories.Calculate, + category: 'Calculate', params: [{ name: 'delta', type: 'int' }], defaultParams: [3], }); addFuncDef({ name: 'holtWintersAberration', - category: categories.Calculate, + category: 'Calculate', params: [{ name: 'delta', type: 'int' }], defaultParams: [3], }); addFuncDef({ name: 'nPercentile', - category: categories.Calculate, + category: 'Calculate', params: [{ name: 'Nth percentile', type: 'int' }], defaultParams: [95], }); @@ -74,48 +59,48 @@ addFuncDef({ name: 'diffSeries', params: optionalSeriesRefArgs, defaultParams: ['#A'], - category: categories.Calculate, + category: 'Calculate', }); addFuncDef({ name: 'stddevSeries', params: optionalSeriesRefArgs, defaultParams: [''], - category: categories.Calculate, + category: 'Calculate', }); addFuncDef({ name: 'divideSeries', params: optionalSeriesRefArgs, defaultParams: ['#A'], - category: categories.Calculate, + category: 'Calculate', }); addFuncDef({ name: 'multiplySeries', params: optionalSeriesRefArgs, defaultParams: ['#A'], - category: categories.Calculate, + category: 'Calculate', }); addFuncDef({ name: 'asPercent', params: optionalSeriesRefArgs, defaultParams: ['#A'], - category: categories.Calculate, + category: 'Calculate', }); addFuncDef({ name: 'group', params: optionalSeriesRefArgs, defaultParams: ['#A', '#B'], - category: categories.Combine, + category: 'Combine', }); addFuncDef({ name: 'sumSeries', shortName: 'sum', - category: categories.Combine, + category: 'Combine', params: optionalSeriesRefArgs, defaultParams: [''], }); @@ -123,78 +108,73 @@ addFuncDef({ addFuncDef({ name: 'averageSeries', shortName: 'avg', - category: categories.Combine, + category: 'Combine', params: optionalSeriesRefArgs, defaultParams: [''], }); addFuncDef({ name: 'rangeOfSeries', - category: categories.Combine, + category: 'Combine', }); addFuncDef({ name: 'percentileOfSeries', - category: categories.Combine, + category: 'Combine', params: [{ name: 'n', type: 'int' }, { name: 'interpolate', type: 'boolean', options: ['true', 'false'] }], defaultParams: [95, 'false'], }); addFuncDef({ name: 'sumSeriesWithWildcards', - category: categories.Combine, - params: [ - { name: 'node', type: 'int' }, - { name: 'node', type: 'int', optional: true }, - { name: 'node', type: 'int', optional: true }, - { name: 'node', type: 'int', optional: true }, - ], + category: 'Combine', + params: [{ name: 'node', type: 'int', multiple: true }], defaultParams: [3], }); addFuncDef({ name: 'maxSeries', shortName: 'max', - category: categories.Combine, + category: 'Combine', }); addFuncDef({ name: 'minSeries', shortName: 'min', - category: categories.Combine, + category: 'Combine', }); addFuncDef({ name: 'averageSeriesWithWildcards', - category: categories.Combine, - params: [{ name: 'node', type: 'int' }, { name: 'node', type: 'int', optional: true }], + category: 'Combine', + params: [{ name: 'node', type: 'int', multiple: true }], defaultParams: [3], }); addFuncDef({ name: 'alias', - category: categories.Special, + category: 'Special', params: [{ name: 'alias', type: 'string' }], defaultParams: ['alias'], }); addFuncDef({ name: 'aliasSub', - category: categories.Special, + category: 'Special', params: [{ name: 'search', type: 'string' }, { name: 'replace', type: 'string' }], defaultParams: ['', '\\1'], }); addFuncDef({ name: 'stacked', - category: categories.Special, + category: 'Special', params: [{ name: 'stack', type: 'string' }], defaultParams: ['stacked'], }); addFuncDef({ name: 'consolidateBy', - category: categories.Special, + category: 'Special', params: [ { name: 'function', @@ -207,14 +187,14 @@ addFuncDef({ addFuncDef({ name: 'cumulative', - category: categories.Special, + category: 'Special', params: [], defaultParams: [], }); addFuncDef({ name: 'groupByNode', - category: categories.Special, + category: 'Special', params: [ { name: 'node', @@ -232,30 +212,13 @@ addFuncDef({ addFuncDef({ name: 'aliasByNode', - category: categories.Special, + category: 'Special', params: [ { name: 'node', type: 'int', options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12], - }, - { - name: 'node', - type: 'int', - options: [0, -1, -2, -3, -4, -5, -6, -7], - optional: true, - }, - { - name: 'node', - type: 'int', - options: [0, -1, -2, -3, -4, -5, -6, -7], - optional: true, - }, - { - name: 'node', - type: 'int', - options: [0, -1, -2, -3, -4, -5, -6, -7], - optional: true, + multiple: true, }, ], defaultParams: [3], @@ -263,7 +226,7 @@ addFuncDef({ addFuncDef({ name: 'substr', - category: categories.Special, + category: 'Special', params: [ { name: 'start', @@ -281,7 +244,7 @@ addFuncDef({ addFuncDef({ name: 'sortByName', - category: categories.Special, + category: 'Special', params: [ { name: 'natural', @@ -295,104 +258,104 @@ addFuncDef({ addFuncDef({ name: 'sortByMaxima', - category: categories.Special, + category: 'Special', }); addFuncDef({ name: 'sortByMinima', - category: categories.Special, + category: 'Special', }); addFuncDef({ name: 'sortByTotal', - category: categories.Special, + category: 'Special', }); addFuncDef({ name: 'aliasByMetric', - category: categories.Special, + category: 'Special', }); addFuncDef({ name: 'randomWalk', fake: true, - category: categories.Special, + category: 'Special', params: [{ name: 'name', type: 'string' }], defaultParams: ['randomWalk'], }); addFuncDef({ name: 'countSeries', - category: categories.Special, + category: 'Special', }); addFuncDef({ name: 'constantLine', - category: categories.Special, + category: 'Special', params: [{ name: 'value', type: 'int' }], defaultParams: [10], }); addFuncDef({ name: 'cactiStyle', - category: categories.Special, + category: 'Special', }); addFuncDef({ name: 'keepLastValue', - category: categories.Special, + category: 'Special', params: [{ name: 'n', type: 'int' }], defaultParams: [100], }); addFuncDef({ name: 'changed', - category: categories.Special, + category: 'Special', params: [], defaultParams: [], }); addFuncDef({ name: 'scale', - category: categories.Transform, + category: 'Transform', params: [{ name: 'factor', type: 'int' }], defaultParams: [1], }); addFuncDef({ name: 'offset', - category: categories.Transform, + category: 'Transform', params: [{ name: 'amount', type: 'int' }], defaultParams: [10], }); addFuncDef({ name: 'transformNull', - category: categories.Transform, + category: 'Transform', params: [{ name: 'amount', type: 'int' }], defaultParams: [0], }); addFuncDef({ name: 'integral', - category: categories.Transform, + category: 'Transform', }); addFuncDef({ name: 'derivative', - category: categories.Transform, + category: 'Transform', }); addFuncDef({ name: 'nonNegativeDerivative', - category: categories.Transform, + category: 'Transform', params: [{ name: 'max value or 0', type: 'int', optional: true }], defaultParams: [''], }); addFuncDef({ name: 'timeShift', - category: categories.Transform, + category: 'Transform', params: [ { name: 'amount', @@ -405,7 +368,7 @@ addFuncDef({ addFuncDef({ name: 'timeStack', - category: categories.Transform, + category: 'Transform', params: [ { name: 'timeShiftUnit', @@ -420,7 +383,7 @@ addFuncDef({ addFuncDef({ name: 'summarize', - category: categories.Transform, + category: 'Transform', params: [ { name: 'interval', type: 'string' }, { @@ -440,7 +403,7 @@ addFuncDef({ addFuncDef({ name: 'smartSummarize', - category: categories.Transform, + category: 'Transform', params: [ { name: 'interval', type: 'string' }, { @@ -454,124 +417,124 @@ addFuncDef({ addFuncDef({ name: 'absolute', - category: categories.Transform, + category: 'Transform', }); addFuncDef({ name: 'hitcount', - category: categories.Transform, + category: 'Transform', params: [{ name: 'interval', type: 'string' }], defaultParams: ['10s'], }); addFuncDef({ name: 'log', - category: categories.Transform, + category: 'Transform', params: [{ name: 'base', type: 'int' }], defaultParams: ['10'], }); addFuncDef({ name: 'averageAbove', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [25], }); addFuncDef({ name: 'averageBelow', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [25], }); addFuncDef({ name: 'currentAbove', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [25], }); addFuncDef({ name: 'currentBelow', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [25], }); addFuncDef({ name: 'maximumAbove', - category: categories.Filter, + category: 'Filter', params: [{ name: 'value', type: 'int' }], defaultParams: [0], }); addFuncDef({ name: 'maximumBelow', - category: categories.Filter, + category: 'Filter', params: [{ name: 'value', type: 'int' }], defaultParams: [0], }); addFuncDef({ name: 'minimumAbove', - category: categories.Filter, + category: 'Filter', params: [{ name: 'value', type: 'int' }], defaultParams: [0], }); addFuncDef({ name: 'minimumBelow', - category: categories.Filter, + category: 'Filter', params: [{ name: 'value', type: 'int' }], defaultParams: [0], }); addFuncDef({ name: 'limit', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [5], }); addFuncDef({ name: 'mostDeviant', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [10], }); addFuncDef({ name: 'exclude', - category: categories.Filter, + category: 'Filter', params: [{ name: 'exclude', type: 'string' }], defaultParams: ['exclude'], }); addFuncDef({ name: 'highestCurrent', - category: categories.Filter, + category: 'Filter', params: [{ name: 'count', type: 'int' }], defaultParams: [5], }); addFuncDef({ name: 'highestMax', - category: categories.Filter, + category: 'Filter', params: [{ name: 'count', type: 'int' }], defaultParams: [5], }); addFuncDef({ name: 'lowestCurrent', - category: categories.Filter, + category: 'Filter', params: [{ name: 'count', type: 'int' }], defaultParams: [5], }); addFuncDef({ name: 'movingAverage', - category: categories.Filter, + category: 'Filter', params: [ { name: 'windowSize', @@ -584,7 +547,7 @@ addFuncDef({ addFuncDef({ name: 'movingMedian', - category: categories.Filter, + category: 'Filter', params: [ { name: 'windowSize', @@ -597,56 +560,56 @@ addFuncDef({ addFuncDef({ name: 'stdev', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }, { name: 'tolerance', type: 'int' }], defaultParams: [5, 0.1], }); addFuncDef({ name: 'highestAverage', - category: categories.Filter, + category: 'Filter', params: [{ name: 'count', type: 'int' }], defaultParams: [5], }); addFuncDef({ name: 'lowestAverage', - category: categories.Filter, + category: 'Filter', params: [{ name: 'count', type: 'int' }], defaultParams: [5], }); addFuncDef({ name: 'removeAbovePercentile', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [5], }); addFuncDef({ name: 'removeAboveValue', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [5], }); addFuncDef({ name: 'removeBelowPercentile', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [5], }); addFuncDef({ name: 'removeBelowValue', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [5], }); addFuncDef({ name: 'useSeriesAbove', - category: categories.Filter, + category: 'Filter', params: [{ name: 'value', type: 'int' }, { name: 'search', type: 'string' }, { name: 'replace', type: 'string' }], defaultParams: [0, 'search', 'replace'], }); @@ -657,7 +620,7 @@ addFuncDef({ addFuncDef({ name: 'aggregateLine', - category: categories.Combine, + category: 'Combine', params: [ { name: 'func', @@ -671,7 +634,7 @@ addFuncDef({ addFuncDef({ name: 'averageOutsidePercentile', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [95], version: '1.0', @@ -679,7 +642,7 @@ addFuncDef({ addFuncDef({ name: 'delay', - category: categories.Transform, + category: 'Transform', params: [{ name: 'steps', type: 'int' }], defaultParams: [1], version: '1.0', @@ -687,7 +650,7 @@ addFuncDef({ addFuncDef({ name: 'exponentialMovingAverage', - category: categories.Calculate, + category: 'Calculate', params: [ { name: 'windowSize', @@ -701,7 +664,7 @@ addFuncDef({ addFuncDef({ name: 'fallbackSeries', - category: categories.Special, + category: 'Special', params: [{ name: 'fallback', type: 'string' }], defaultParams: ['constantLine(0)'], version: '1.0', @@ -709,7 +672,7 @@ addFuncDef({ addFuncDef({ name: 'grep', - category: categories.Filter, + category: 'Filter', params: [{ name: 'grep', type: 'string' }], defaultParams: ['grep'], version: '1.0', @@ -717,7 +680,7 @@ addFuncDef({ addFuncDef({ name: 'groupByNodes', - category: categories.Special, + category: 'Special', params: [ { name: 'function', @@ -728,24 +691,7 @@ addFuncDef({ name: 'node', type: 'int', options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12], - }, - { - name: 'node', - type: 'int', - options: [0, -1, -2, -3, -4, -5, -6, -7], - optional: true, - }, - { - name: 'node', - type: 'int', - options: [0, -1, -2, -3, -4, -5, -6, -7], - optional: true, - }, - { - name: 'node', - type: 'int', - options: [0, -1, -2, -3, -4, -5, -6, -7], - optional: true, + multiple: true, }, ], defaultParams: ['sum', 3], @@ -754,7 +700,7 @@ addFuncDef({ addFuncDef({ name: 'integralByInterval', - category: categories.Transform, + category: 'Transform', params: [ { name: 'intervalUnit', @@ -768,7 +714,7 @@ addFuncDef({ addFuncDef({ name: 'interpolate', - category: categories.Transform, + category: 'Transform', params: [{ name: 'limit', type: 'int', optional: true }], defaultParams: [], version: '1.0', @@ -776,19 +722,19 @@ addFuncDef({ addFuncDef({ name: 'invert', - category: categories.Transform, + category: 'Transform', version: '1.0', }); addFuncDef({ name: 'isNonNull', - category: categories.Combine, + category: 'Combine', version: '1.0', }); addFuncDef({ name: 'linearRegression', - category: categories.Calculate, + category: 'Calculate', params: [ { name: 'startSourceAt', @@ -812,13 +758,13 @@ addFuncDef({ shortName: 'map', params: [{ name: 'node', type: 'int' }], defaultParams: [3], - category: categories.Combine, + category: 'Combine', version: '1.0', }); addFuncDef({ name: 'movingMin', - category: categories.Calculate, + category: 'Calculate', params: [ { name: 'windowSize', @@ -832,7 +778,7 @@ addFuncDef({ addFuncDef({ name: 'movingMax', - category: categories.Calculate, + category: 'Calculate', params: [ { name: 'windowSize', @@ -846,7 +792,7 @@ addFuncDef({ addFuncDef({ name: 'movingSum', - category: categories.Calculate, + category: 'Calculate', params: [ { name: 'windowSize', @@ -860,30 +806,13 @@ addFuncDef({ addFuncDef({ name: 'multiplySeriesWithWildcards', - category: categories.Calculate, + category: 'Calculate', params: [ { name: 'position', type: 'int', options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12], - }, - { - name: 'position', - type: 'int', - options: [0, -1, -2, -3, -4, -5, -6, -7], - optional: true, - }, - { - name: 'position', - type: 'int', - options: [0, -1, -2, -3, -4, -5, -6, -7], - optional: true, - }, - { - name: 'position', - type: 'int', - options: [0, -1, -2, -3, -4, -5, -6, -7], - optional: true, + multiple: true, }, ], defaultParams: [2], @@ -892,13 +821,13 @@ addFuncDef({ addFuncDef({ name: 'offsetToZero', - category: categories.Transform, + category: 'Transform', version: '1.0', }); addFuncDef({ name: 'pow', - category: categories.Transform, + category: 'Transform', params: [{ name: 'factor', type: 'int' }], defaultParams: [10], version: '1.0', @@ -906,7 +835,7 @@ addFuncDef({ addFuncDef({ name: 'powSeries', - category: categories.Transform, + category: 'Transform', params: optionalSeriesRefArgs, defaultParams: [''], version: '1.0', @@ -926,17 +855,16 @@ addFuncDef({ type: 'int', options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], }, - { name: 'reduceMatchers', type: 'string' }, - { name: 'reduceMatchers', type: 'string' }, + { name: 'reduceMatchers', type: 'string', multiple: true }, ], - defaultParams: ['asPercent', 2, 'used_bytes', 'total_bytes'], - category: categories.Combine, + defaultParams: ['asPercent', 2, 'used_bytes'], + category: 'Combine', version: '1.0', }); addFuncDef({ name: 'removeBetweenPercentile', - category: categories.Filter, + category: 'Filter', params: [{ name: 'n', type: 'int' }], defaultParams: [95], version: '1.0', @@ -944,19 +872,19 @@ addFuncDef({ addFuncDef({ name: 'removeEmptySeries', - category: categories.Filter, + category: 'Filter', version: '1.0', }); addFuncDef({ name: 'squareRoot', - category: categories.Transform, + category: 'Transform', version: '1.0', }); addFuncDef({ name: 'timeSlice', - category: categories.Transform, + category: 'Transform', params: [ { name: 'startSliceAt', @@ -976,7 +904,7 @@ addFuncDef({ addFuncDef({ name: 'weightedAverage', - category: categories.Filter, + category: 'Filter', params: [ { name: 'other', type: 'value_or_series', optional: true }, { @@ -991,29 +919,21 @@ addFuncDef({ addFuncDef({ name: 'seriesByTag', - category: categories.Special, - params: [ - { name: 'tagExpression', type: 'string' }, - { name: 'tagExpression', type: 'string', optional: true }, - { name: 'tagExpression', type: 'string', optional: true }, - { name: 'tagExpression', type: 'string', optional: true }, - ], + category: 'Special', + params: [{ name: 'tagExpression', type: 'string', multiple: true }], version: '1.1', }); addFuncDef({ name: 'groupByTags', - category: categories.Special, + category: 'Special', params: [ { name: 'function', type: 'string', options: ['sum', 'avg', 'maxSeries'], }, - { name: 'tag', type: 'string' }, - { name: 'tag', type: 'string', optional: true }, - { name: 'tag', type: 'string', optional: true }, - { name: 'tag', type: 'string', optional: true }, + { name: 'tag', type: 'string', multiple: true }, ], defaultParams: ['sum', 'tag'], version: '1.1', @@ -1021,23 +941,14 @@ addFuncDef({ addFuncDef({ name: 'aliasByTags', - category: categories.Special, - params: [ - { name: 'tag', type: 'string' }, - { name: 'tag', type: 'string', optional: true }, - { name: 'tag', type: 'string', optional: true }, - { name: 'tag', type: 'string', optional: true }, - ], + category: 'Special', + params: [{ name: 'tag', type: 'string', multiple: true }], defaultParams: ['tag'], version: '1.1', }); -_.each(categories, function(funcList, catName) { - categories[catName] = _.sortBy(funcList, 'name'); -}); - -function isVersionRelatedFunction(func, graphiteVersion) { - return isVersionGtOrEq(graphiteVersion, func.version) || !func.version; +function isVersionRelatedFunction(obj, graphiteVersion) { + return !obj.version || isVersionGtOrEq(graphiteVersion, obj.version); } export class FuncInstance { @@ -1059,20 +970,34 @@ export class FuncInstance { render(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)) { + var paramType; + if (index < this.def.params.length) { + paramType = this.def.params[index].type; + } else if (_.get(_.last(this.def.params), 'multiple')) { + paramType = _.get(_.last(this.def.params), 'type'); + } + if (paramType === 'value_or_series') { return value; } - + if (paramType === 'boolean' && _.includes(['true', 'false'], value)) { + return value; + } + if (_.includes(['int', 'float', 'int_or_interval', 'node_or_tag', 'node'], paramType) && _.isFinite(+value)) { + return _.toString(+value); + } return "'" + value + "'"; }.bind(this) ); + // don't send any blank parameters to graphite + while (parameters[parameters.length - 1] === '') { + parameters.pop(); + } + if (metricExp) { parameters.unshift(metricExp); } @@ -1085,7 +1010,15 @@ export class FuncInstance { return false; } - return this.def.params[index + 1] && this.def.params[index + 1].optional; + if (this.def.params[index + 1] && this.def.params[index + 1].optional) { + return true; + } + + if (index + 1 >= this.def.params.length && _.get(_.last(this.def.params), 'multiple')) { + return true; + } + + return false; } updateParam(strValue, index) { @@ -1101,7 +1034,7 @@ export class FuncInstance { return; } - if (strValue === '' && this.def.params[index].optional) { + if (strValue === '' && (index >= this.def.params.length || this.def.params[index].optional)) { this.params.splice(index, 1); } else { this.params[index] = strValue; @@ -1123,32 +1056,36 @@ export class FuncInstance { } } -export default { - createFuncInstance: function(funcDef, options?) { - if (_.isString(funcDef)) { - if (!index[funcDef]) { - throw { message: 'Method not found ' + name }; - } - funcDef = index[funcDef]; - } - return new FuncInstance(funcDef, options); - }, +function createFuncInstance(funcDef, options?, idx?) { + if (_.isString(funcDef)) { + funcDef = getFuncDef(funcDef, idx); + } + return new FuncInstance(funcDef, options); +} - getFuncDef: function(name) { - return index[name]; - }, +function getFuncDef(name, idx?) { + if (!(idx || index)[name]) { + throw { message: 'Method not found ' + name }; + } + return (idx || index)[name]; +} - getCategories: function(graphiteVersion) { - var filteredCategories: any = {}; - _.each(categories, function(functions, category) { - var filteredFuncs = _.filter(functions, function(func) { - return isVersionRelatedFunction(func, graphiteVersion); +function getFuncDefs(graphiteVersion, idx?) { + var funcs = {}; + _.forEach(idx || index, function(funcDef) { + if (isVersionRelatedFunction(funcDef, graphiteVersion)) { + funcs[funcDef.name] = _.assign({}, funcDef, { + params: _.filter(funcDef.params, function(param) { + return isVersionRelatedFunction(param, graphiteVersion); + }), }); - if (filteredFuncs.length) { - filteredCategories[category] = filteredFuncs; - } - }); + } + }); + return funcs; +} - return filteredCategories; - }, +export default { + createFuncInstance: createFuncInstance, + getFuncDef: getFuncDef, + getFuncDefs: getFuncDefs, }; diff --git a/public/app/plugins/datasource/graphite/graphite_query.ts b/public/app/plugins/datasource/graphite/graphite_query.ts index 9f9d4d12701..9f651e72744 100644 --- a/public/app/plugins/datasource/graphite/graphite_query.ts +++ b/public/app/plugins/datasource/graphite/graphite_query.ts @@ -1,8 +1,8 @@ import _ from 'lodash'; -import gfunc from './gfunc'; import { Parser } from './parser'; export default class GraphiteQuery { + datasource: any; target: any; functions: any[]; segments: any[]; @@ -15,7 +15,8 @@ export default class GraphiteQuery { scopedVars: any; /** @ngInject */ - constructor(target, templateSrv?, scopedVars?) { + constructor(datasource, target, templateSrv?, scopedVars?) { + this.datasource = datasource; this.target = target; this.parseTarget(); @@ -86,7 +87,7 @@ export default class GraphiteQuery { switch (astNode.type) { case 'function': - var innerFunc = gfunc.createFuncInstance(astNode.name, { + var innerFunc = this.datasource.createFuncInstance(astNode.name, { withDefaultParams: false, }); _.each(astNode.params, param => { @@ -133,7 +134,7 @@ export default class GraphiteQuery { moveAliasFuncLast() { var aliasFunc = _.find(this.functions, function(func) { - return func.def.name === 'alias' || func.def.name === 'aliasByNode' || func.def.name === 'aliasByMetric'; + return func.def.name.startsWith('alias'); }); if (aliasFunc) { @@ -143,7 +144,7 @@ export default class GraphiteQuery { } addFunctionParameter(func, value) { - if (func.params.length >= func.def.params.length) { + if (func.params.length >= func.def.params.length && !_.get(_.last(func.def.params), 'multiple', false)) { throw { message: 'too many parameters for function ' + func.def.name }; } func.params.push(value); diff --git a/public/app/plugins/datasource/graphite/partials/query.editor.html b/public/app/plugins/datasource/graphite/partials/query.editor.html index b79164e1781..c1889cb92fa 100755 --- a/public/app/plugins/datasource/graphite/partials/query.editor.html +++ b/public/app/plugins/datasource/graphite/partials/query.editor.html @@ -18,7 +18,7 @@ + min-input-width="30">