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">