Commit 732ea8ee by Torkel Ödegaard

Graphite: fixed variable quoting when variable value is nummeric, fixes #2078

parent 50a10706
import _ from 'lodash'; import _ from 'lodash';
import { isVersionGtOrEq } from 'app/core/utils/version'; import { isVersionGtOrEq } from 'app/core/utils/version';
import { InterpolateFunction } from '@grafana/ui';
const index = {}; const index = {};
...@@ -961,24 +962,30 @@ export class FuncInstance { ...@@ -961,24 +962,30 @@ export class FuncInstance {
this.updateText(); this.updateText();
} }
render(metricExp) { render(metricExp: string, replaceVariables: InterpolateFunction): string {
const str = this.def.name + '('; const str = this.def.name + '(';
const parameters = _.map(this.params, (value, index) => { const parameters = _.map(this.params, (value, index) => {
const valueInterpolated = replaceVariables(value);
let paramType; let paramType;
if (index < this.def.params.length) { if (index < this.def.params.length) {
paramType = this.def.params[index].type; paramType = this.def.params[index].type;
} else if (_.get(_.last(this.def.params), 'multiple')) { } else if (_.get(_.last(this.def.params), 'multiple')) {
paramType = _.get(_.last(this.def.params), 'type'); paramType = _.get(_.last(this.def.params), 'type');
} }
// param types that should never be quoted // param types that should never be quoted
if (_.includes(['value_or_series', 'boolean', 'int', 'float', 'node'], paramType)) { if (_.includes(['value_or_series', 'boolean', 'int', 'float', 'node'], paramType)) {
return value; return value;
} }
// param types that might be quoted // param types that might be quoted
if (_.includes(['int_or_interval', 'node_or_tag'], paramType) && _.isFinite(+value)) { // To quote variables correctly we need to interpolate it to check if it contains a numeric or string value
return _.toString(+value); if (_.includes(['int_or_interval', 'node_or_tag'], paramType) && _.isFinite(+valueInterpolated)) {
return _.toString(value);
} }
return "'" + value + "'"; return "'" + value + "'";
}); });
......
...@@ -18,6 +18,7 @@ export default class GraphiteQuery { ...@@ -18,6 +18,7 @@ export default class GraphiteQuery {
constructor(datasource, target, templateSrv?, scopedVars?) { constructor(datasource, target, templateSrv?, scopedVars?) {
this.datasource = datasource; this.datasource = datasource;
this.target = target; this.target = target;
this.templateSrv = templateSrv;
this.parseTarget(); this.parseTarget();
this.removeTagValue = '-- remove tag --'; this.removeTagValue = '-- remove tag --';
...@@ -160,7 +161,10 @@ export default class GraphiteQuery { ...@@ -160,7 +161,10 @@ export default class GraphiteQuery {
} }
updateModelTarget(targets) { updateModelTarget(targets) {
// render query const wrapFunction = (target: string, func: any) => {
return func.render(target, this.templateSrv.replace);
};
if (!this.target.textEditor) { if (!this.target.textEditor) {
const metricPath = this.getSegmentPathUpTo(this.segments.length).replace(/\.select metric$/, ''); const metricPath = this.getSegmentPathUpTo(this.segments.length).replace(/\.select metric$/, '');
this.target.target = _.reduce(this.functions, wrapFunction, metricPath); this.target.target = _.reduce(this.functions, wrapFunction, metricPath);
...@@ -302,10 +306,6 @@ export default class GraphiteQuery { ...@@ -302,10 +306,6 @@ export default class GraphiteQuery {
} }
} }
function wrapFunction(target, func) {
return func.render(target);
}
function renderTagString(tag) { function renderTagString(tag) {
return tag.key + tag.operator + tag.value; return tag.key + tag.operator + tag.value;
} }
...@@ -30,66 +30,92 @@ describe('when creating func instance from func names', () => { ...@@ -30,66 +30,92 @@ describe('when creating func instance from func names', () => {
}); });
}); });
function replaceVariablesDummy(str: string) {
return str;
}
describe('when rendering func instance', () => { describe('when rendering func instance', () => {
it('should handle single metric param', () => { it('should handle single metric param', () => {
const func = gfunc.createFuncInstance('sumSeries'); const func = gfunc.createFuncInstance('sumSeries');
expect(func.render('hello.metric')).toEqual('sumSeries(hello.metric)'); expect(func.render('hello.metric', replaceVariablesDummy)).toEqual('sumSeries(hello.metric)');
}); });
it('should include default params if options enable it', () => { it('should include default params if options enable it', () => {
const func = gfunc.createFuncInstance('scaleToSeconds', { const func = gfunc.createFuncInstance('scaleToSeconds', {
withDefaultParams: true, withDefaultParams: true,
}); });
expect(func.render('hello')).toEqual('scaleToSeconds(hello, 1)'); expect(func.render('hello', replaceVariablesDummy)).toEqual('scaleToSeconds(hello, 1)');
}); });
it('should handle int or interval params with number', () => { it('should handle int or interval params with number', () => {
const func = gfunc.createFuncInstance('movingMedian'); const func = gfunc.createFuncInstance('movingMedian');
func.params[0] = '5'; func.params[0] = '5';
expect(func.render('hello')).toEqual('movingMedian(hello, 5)'); expect(func.render('hello', replaceVariablesDummy)).toEqual('movingMedian(hello, 5)');
}); });
it('should handle int or interval params with interval string', () => { it('should handle int or interval params with interval string', () => {
const func = gfunc.createFuncInstance('movingMedian'); const func = gfunc.createFuncInstance('movingMedian');
func.params[0] = '5min'; func.params[0] = '5min';
expect(func.render('hello')).toEqual("movingMedian(hello, '5min')"); expect(func.render('hello', replaceVariablesDummy)).toEqual("movingMedian(hello, '5min')");
}); });
it('should never quote boolean paramater', () => { it('should never quote boolean paramater', () => {
const func = gfunc.createFuncInstance('sortByName'); const func = gfunc.createFuncInstance('sortByName');
func.params[0] = '$natural'; func.params[0] = '$natural';
expect(func.render('hello')).toEqual('sortByName(hello, $natural)'); expect(func.render('hello', replaceVariablesDummy)).toEqual('sortByName(hello, $natural)');
}); });
it('should never quote int paramater', () => { it('should never quote int paramater', () => {
const func = gfunc.createFuncInstance('maximumAbove'); const func = gfunc.createFuncInstance('maximumAbove');
func.params[0] = '$value'; func.params[0] = '$value';
expect(func.render('hello')).toEqual('maximumAbove(hello, $value)'); expect(func.render('hello', replaceVariablesDummy)).toEqual('maximumAbove(hello, $value)');
}); });
it('should never quote node paramater', () => { it('should never quote node paramater', () => {
const func = gfunc.createFuncInstance('aliasByNode'); const func = gfunc.createFuncInstance('aliasByNode');
func.params[0] = '$node'; func.params[0] = '$node';
expect(func.render('hello')).toEqual('aliasByNode(hello, $node)'); expect(func.render('hello', replaceVariablesDummy)).toEqual('aliasByNode(hello, $node)');
}); });
it('should handle metric param and int param and string param', () => { it('should handle metric param and int param and string param', () => {
const func = gfunc.createFuncInstance('groupByNode'); const func = gfunc.createFuncInstance('groupByNode');
func.params[0] = 5; func.params[0] = 5;
func.params[1] = 'avg'; func.params[1] = 'avg';
expect(func.render('hello.metric')).toEqual("groupByNode(hello.metric, 5, 'avg')"); expect(func.render('hello.metric', replaceVariablesDummy)).toEqual("groupByNode(hello.metric, 5, 'avg')");
}); });
it('should handle function with no metric param', () => { it('should handle function with no metric param', () => {
const func = gfunc.createFuncInstance('randomWalk'); const func = gfunc.createFuncInstance('randomWalk');
func.params[0] = 'test'; func.params[0] = 'test';
expect(func.render(undefined)).toEqual("randomWalk('test')"); expect(func.render(undefined, replaceVariablesDummy)).toEqual("randomWalk('test')");
}); });
it('should handle function multiple series params', () => { it('should handle function multiple series params', () => {
const func = gfunc.createFuncInstance('asPercent'); const func = gfunc.createFuncInstance('asPercent');
func.params[0] = '#B'; func.params[0] = '#B';
expect(func.render('#A')).toEqual('asPercent(#A, #B)'); expect(func.render('#A', replaceVariablesDummy)).toEqual('asPercent(#A, #B)');
});
it('should not quote variables that have numeric value', () => {
const func = gfunc.createFuncInstance('movingAverage');
func.params[0] = '$variable';
const replaceVariables = (str: string) => {
return str.replace('$variable', '60');
};
expect(func.render('metric', replaceVariables)).toBe('movingAverage(metric, $variable)');
});
it('should quote variables that have string value', () => {
const func = gfunc.createFuncInstance('movingAverage');
func.params[0] = '$variable';
const replaceVariables = (str: string) => {
return str.replace('$variable', '10min');
};
expect(func.render('metric', replaceVariables)).toBe("movingAverage(metric, '$variable')");
}); });
}); });
......
import gfunc from '../gfunc'; import gfunc from '../gfunc';
import GraphiteQuery from '../graphite_query'; import GraphiteQuery from '../graphite_query';
import { TemplateSrvStub } from 'test/specs/helpers';
describe('Graphite query model', () => { describe('Graphite query model', () => {
const ctx: any = { const ctx: any = {
...@@ -9,7 +10,7 @@ describe('Graphite query model', () => { ...@@ -9,7 +10,7 @@ describe('Graphite query model', () => {
waitForFuncDefsLoaded: jest.fn().mockReturnValue(Promise.resolve(null)), waitForFuncDefsLoaded: jest.fn().mockReturnValue(Promise.resolve(null)),
createFuncInstance: gfunc.createFuncInstance, createFuncInstance: gfunc.createFuncInstance,
}, },
templateSrv: {}, templateSrv: new TemplateSrvStub(),
targets: [], targets: [],
}; };
......
import { uiSegmentSrv } from 'app/core/services/segment_srv'; import { uiSegmentSrv } from 'app/core/services/segment_srv';
import gfunc from '../gfunc'; import gfunc from '../gfunc';
import { GraphiteQueryCtrl } from '../query_ctrl'; import { GraphiteQueryCtrl } from '../query_ctrl';
import { TemplateSrvStub } from 'test/specs/helpers';
describe('GraphiteQueryCtrl', () => { describe('GraphiteQueryCtrl', () => {
const ctx = { const ctx = {
...@@ -30,7 +31,7 @@ describe('GraphiteQueryCtrl', () => { ...@@ -30,7 +31,7 @@ describe('GraphiteQueryCtrl', () => {
{}, {},
{}, {},
new uiSegmentSrv({ trustAsHtml: html => html }, { highlightVariablesAsHtml: () => {} }), new uiSegmentSrv({ trustAsHtml: html => html }, { highlightVariablesAsHtml: () => {} }),
{}, new TemplateSrvStub(),
{} {}
); );
}); });
...@@ -291,7 +292,7 @@ describe('GraphiteQueryCtrl', () => { ...@@ -291,7 +292,7 @@ describe('GraphiteQueryCtrl', () => {
ctx.ctrl.target.target = "seriesByTag('tag1=value1', 'tag2!=~value2')"; ctx.ctrl.target.target = "seriesByTag('tag1=value1', 'tag2!=~value2')";
ctx.ctrl.datasource.metricFindQuery = () => Promise.resolve([{ expandable: false }]); ctx.ctrl.datasource.metricFindQuery = () => Promise.resolve([{ expandable: false }]);
ctx.ctrl.parseTarget(); ctx.ctrl.parseTarget();
ctx.ctrl.removeTag(0); ctx.ctrl.tagChanged({ key: ctx.ctrl.removeTagValue });
}); });
it('should update tags', () => { it('should update tags', () => {
......
...@@ -172,7 +172,7 @@ export function TemplateSrvStub(this: any) { ...@@ -172,7 +172,7 @@ export function TemplateSrvStub(this: any) {
this.variables = []; this.variables = [];
this.templateSettings = { interpolate: /\[\[([\s\S]+?)\]\]/g }; this.templateSettings = { interpolate: /\[\[([\s\S]+?)\]\]/g };
this.data = {}; this.data = {};
this.replace = function(text: string) { this.replace = (text: string) => {
return _.template(text, this.templateSettings)(this.data); return _.template(text, this.templateSettings)(this.data);
}; };
this.init = () => {}; this.init = () => {};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment