Skip to content

Commit 3a4e051

Browse files
committed
support for loading function definitions from graphite
1 parent 307b419 commit 3a4e051

File tree

14 files changed

+658
-358
lines changed

14 files changed

+658
-358
lines changed

.editorconfig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ charset = utf-8
88
trim_trailing_whitespace = true
99
insert_final_newline = true
1010
max_line_length = 120
11-
insert_final_newline = true
1211

1312
[*.go]
1413
indent_style = tab

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135
"clipboard": "^1.7.1",
136136
"d3": "^4.11.0",
137137
"d3-scale-chromatic": "^1.1.1",
138-
"eventemitter3": "^2.0.2",
138+
"eventemitter3": "^2.0.3",
139139
"file-saver": "^1.3.3",
140140
"jquery": "^3.2.1",
141141
"lodash": "^4.17.4",
@@ -154,6 +154,7 @@
154154
"react-select": "^1.1.0",
155155
"react-sizeme": "^2.3.6",
156156
"remarkable": "^1.7.1",
157+
"rst2html": "github:thoward/rst2html#d6e2f21",
157158
"rxjs": "^5.4.3",
158159
"tether": "^1.4.0",
159160
"tether-drop": "https://github.com/torkelo/drop",

public/app/plugins/datasource/graphite/add_graphite_func.js

Lines changed: 69 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@ define([
22
'angular',
33
'lodash',
44
'jquery',
5-
'./gfunc',
65
],
7-
function (angular, _, $, gfunc) {
6+
function (angular, _, $) {
87
'use strict';
98

10-
gfunc = gfunc.default;
11-
129
angular
1310
.module('grafana.directives')
1411
.directive('graphiteAddFunc', function($compile) {
@@ -23,91 +20,92 @@ function (angular, _, $, gfunc) {
2320
return {
2421
link: function($scope, elem) {
2522
var ctrl = $scope.ctrl;
26-
var graphiteVersion = ctrl.datasource.graphiteVersion;
27-
var categories = gfunc.getCategories(graphiteVersion);
28-
var allFunctions = getAllFunctionNames(categories);
29-
30-
$scope.functionMenu = createFunctionDropDownMenu(categories);
3123

3224
var $input = $(inputTemplate);
3325
var $button = $(buttonTemplate);
26+
3427
$input.appendTo(elem);
3528
$button.appendTo(elem);
3629

37-
$input.attr('data-provide', 'typeahead');
38-
$input.typeahead({
39-
source: allFunctions,
40-
minLength: 1,
41-
items: 10,
42-
updater: function (value) {
43-
var funcDef = gfunc.getFuncDef(value);
44-
if (!funcDef) {
45-
// try find close match
46-
value = value.toLowerCase();
47-
funcDef = _.find(allFunctions, function(funcName) {
48-
return funcName.toLowerCase().indexOf(value) === 0;
30+
ctrl.datasource.getFuncDefs().then(function(funcDefs) {
31+
var allFunctions = _.map(funcDefs, 'name').sort();
32+
33+
$scope.functionMenu = createFunctionDropDownMenu(funcDefs);
34+
35+
$input.attr('data-provide', 'typeahead');
36+
$input.typeahead({
37+
source: allFunctions,
38+
minLength: 1,
39+
items: 10,
40+
updater: function (value) {
41+
var funcDef = ctrl.datasource.getFuncDef(value);
42+
if (!funcDef) {
43+
// try find close match
44+
value = value.toLowerCase();
45+
funcDef = _.find(allFunctions, function(funcName) {
46+
return funcName.toLowerCase().indexOf(value) === 0;
47+
});
48+
49+
if (!funcDef) { return; }
50+
}
51+
52+
$scope.$apply(function() {
53+
ctrl.addFunction(funcDef);
4954
});
5055

51-
if (!funcDef) { return; }
56+
$input.trigger('blur');
57+
return '';
5258
}
53-
54-
$scope.$apply(function() {
55-
ctrl.addFunction(funcDef);
56-
});
57-
58-
$input.trigger('blur');
59-
return '';
60-
}
61-
});
62-
63-
$button.click(function() {
64-
$button.hide();
65-
$input.show();
66-
$input.focus();
67-
});
68-
69-
$input.keyup(function() {
70-
elem.toggleClass('open', $input.val() === '');
59+
});
60+
61+
$button.click(function() {
62+
$button.hide();
63+
$input.show();
64+
$input.focus();
65+
});
66+
67+
$input.keyup(function() {
68+
elem.toggleClass('open', $input.val() === '');
69+
});
70+
71+
$input.blur(function() {
72+
// clicking the function dropdown menu wont
73+
// work if you remove class at once
74+
setTimeout(function() {
75+
$input.val('');
76+
$input.hide();
77+
$button.show();
78+
elem.removeClass('open');
79+
}, 200);
80+
});
81+
82+
$compile(elem.contents())($scope);
7183
});
72-
73-
$input.blur(function() {
74-
// clicking the function dropdown menu wont
75-
// work if you remove class at once
76-
setTimeout(function() {
77-
$input.val('');
78-
$input.hide();
79-
$button.show();
80-
elem.removeClass('open');
81-
}, 200);
82-
});
83-
84-
$compile(elem.contents())($scope);
8584
}
8685
};
8786
});
8887

89-
function getAllFunctionNames(categories) {
90-
return _.reduce(categories, function(list, category) {
91-
_.each(category, function(func) {
92-
list.push(func.name);
93-
});
94-
return list;
95-
}, []);
96-
}
97-
98-
function createFunctionDropDownMenu(categories) {
99-
return _.map(categories, function(list, category) {
100-
var submenu = _.map(list, function(value) {
101-
return {
102-
text: value.name,
103-
click: "ctrl.addFunction('" + value.name + "')",
104-
};
88+
function createFunctionDropDownMenu(funcDefs) {
89+
var categories = {};
90+
91+
_.forEach(funcDefs, function(funcDef) {
92+
if (!funcDef.category) {
93+
return;
94+
}
95+
if (!categories[funcDef.category]) {
96+
categories[funcDef.category] = [];
97+
}
98+
categories[funcDef.category].push({
99+
text: funcDef.name,
100+
click: "ctrl.addFunction('" + funcDef.name + "')",
105101
});
102+
});
106103

104+
return _.sortBy(_.map(categories, function(submenu, category) {
107105
return {
108106
text: category,
109-
submenu: submenu
107+
submenu: _.sortBy(submenu, 'text')
110108
};
111-
});
109+
}), 'text');
112110
}
113111
});

public/app/plugins/datasource/graphite/datasource.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import _ from 'lodash';
22
import * as dateMath from 'app/core/utils/datemath';
33
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
4+
import gfunc from './gfunc';
45

56
/** @ngInject */
67
export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv) {
@@ -12,6 +13,7 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
1213
this.cacheTimeout = instanceSettings.cacheTimeout;
1314
this.withCredentials = instanceSettings.withCredentials;
1415
this.render_method = instanceSettings.render_method || 'POST';
16+
this.funcDefs = null;
1517

1618
this.getQueryOptionsInfo = function() {
1719
return {
@@ -347,6 +349,125 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
347349
});
348350
};
349351

352+
this.createFuncInstance = function(funcDef, options?) {
353+
return gfunc.createFuncInstance(funcDef, options, this.funcDefs);
354+
};
355+
356+
this.getFuncDef = function(name) {
357+
return gfunc.getFuncDef(name, this.funcDefs);
358+
};
359+
360+
this.getFuncDefs = function() {
361+
let self = this;
362+
363+
if (self.funcDefs !== null) {
364+
return Promise.resolve(self.funcDefs);
365+
}
366+
367+
if (!supportsFunctionIndex(self.graphiteVersion)) {
368+
self.funcDefs = gfunc.getFuncDefs(self.graphiteVersion);
369+
return Promise.resolve(self.funcDefs);
370+
}
371+
372+
let httpOptions = {
373+
method: 'GET',
374+
url: '/functions',
375+
};
376+
377+
return self
378+
.doGraphiteRequest(httpOptions)
379+
.then(results => {
380+
if (results.status !== 200 || typeof results.data !== 'object') {
381+
self.funcDefs = gfunc.getFuncDefs(self.graphiteVersion);
382+
return Promise.resolve(self.funcDefs);
383+
}
384+
385+
self.funcDefs = {};
386+
_.forEach(results.data || {}, (funcDef, funcName) => {
387+
// skip graphite graph functions
388+
if (funcDef.group === 'Graph') {
389+
return;
390+
}
391+
392+
var func = {
393+
name: funcDef.name,
394+
description: funcDef.description,
395+
category: funcDef.group,
396+
params: [],
397+
defaultParams: [],
398+
fake: false,
399+
};
400+
401+
// get rid of the first "seriesList" param
402+
if (/^seriesLists?$/.test(_.get(funcDef, 'params[0].type', ''))) {
403+
// handle functions that accept multiple seriesLists
404+
// we leave the param in place but mark it optional, so users can add more series if they wish
405+
if (funcDef.params[0].multiple) {
406+
funcDef.params[0].required = false;
407+
// otherwise chop off the first param, it'll be handled separately
408+
} else {
409+
funcDef.params.shift();
410+
}
411+
// tag function as fake
412+
} else {
413+
func.fake = true;
414+
}
415+
416+
_.forEach(funcDef.params, rawParam => {
417+
var param = {
418+
name: rawParam.name,
419+
type: 'string',
420+
optional: !rawParam.required,
421+
multiple: !!rawParam.multiple,
422+
options: undefined,
423+
};
424+
425+
if (rawParam.default !== undefined) {
426+
func.defaultParams.push(_.toString(rawParam.default));
427+
} else if (rawParam.suggestions) {
428+
func.defaultParams.push(_.toString(rawParam.suggestions[0]));
429+
} else {
430+
func.defaultParams.push('');
431+
}
432+
433+
if (rawParam.type === 'boolean') {
434+
param.type = 'boolean';
435+
param.options = ['true', 'false'];
436+
} else if (rawParam.type === 'integer') {
437+
param.type = 'int';
438+
} else if (rawParam.type === 'float') {
439+
param.type = 'float';
440+
} else if (rawParam.type === 'node') {
441+
param.type = 'node';
442+
param.options = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];
443+
} else if (rawParam.type === 'nodeOrTag') {
444+
param.type = 'node_or_tag';
445+
param.options = ['name', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];
446+
} else if (rawParam.type === 'intOrInterval') {
447+
param.type = 'int_or_interval';
448+
} else if (rawParam.type === 'seriesList') {
449+
param.type = 'value_or_series';
450+
}
451+
452+
if (rawParam.options) {
453+
param.options = _.map(rawParam.options, _.toString);
454+
} else if (rawParam.suggestions) {
455+
param.options = _.map(rawParam.suggestions, _.toString);
456+
}
457+
458+
func.params.push(param);
459+
});
460+
461+
self.funcDefs[funcName] = func;
462+
});
463+
return self.funcDefs;
464+
})
465+
.catch(err => {
466+
self.funcDefs = gfunc.getFuncDefs(self.graphiteVersion);
467+
return self.funcDefs;
468+
});
469+
};
470+
350471
this.testDatasource = function() {
351472
return this.metricFindQuery('*').then(function() {
352473
return { status: 'success', message: 'Data source is working' };
@@ -440,3 +561,7 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
440561
function supportsTags(version: string): boolean {
441562
return isVersionGtOrEq(version, '1.1');
442563
}
564+
565+
function supportsFunctionIndex(version: string): boolean {
566+
return isVersionGtOrEq(version, '1.1');
567+
}

0 commit comments

Comments
 (0)