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

312 lines
7.9 KiB
TypeScript
Raw Normal View History

2017-12-20 05:33:33 -06:00
import _ from 'lodash';
import { Parser } from './parser';
2017-10-13 04:05:38 -05:00
export default class GraphiteQuery {
datasource: any;
2017-10-13 04:05:38 -05:00
target: any;
functions: any[];
segments: any[];
tags: any[];
error: any;
seriesByTagUsed: boolean;
checkOtherSegmentsIndex: number;
removeTagValue: string;
templateSrv: any;
scopedVars: any;
/** @ngInject */
constructor(datasource, target, templateSrv?, scopedVars?) {
this.datasource = datasource;
2017-10-13 04:05:38 -05:00
this.target = target;
this.parseTarget();
2017-12-20 05:33:33 -06:00
this.removeTagValue = '-- remove tag --';
2017-10-13 04:05:38 -05:00
}
parseTarget() {
this.functions = [];
this.segments = [];
2017-11-16 07:16:37 -06:00
this.tags = [];
2017-10-13 04:05:38 -05:00
this.error = null;
if (this.target.textEditor) {
return;
}
2018-08-29 07:26:50 -05:00
const parser = new Parser(this.target.target);
const astNode = parser.getAst();
2017-10-13 04:05:38 -05:00
if (astNode === null) {
this.checkOtherSegmentsIndex = 0;
return;
}
2017-12-20 05:33:33 -06:00
if (astNode.type === 'error') {
this.error = astNode.message + ' at position: ' + astNode.pos;
2017-10-13 04:05:38 -05:00
this.target.textEditor = true;
return;
}
try {
2017-12-10 09:45:41 -06:00
this.parseTargetRecursive(astNode, null);
2017-10-13 04:05:38 -05:00
} catch (err) {
2017-12-20 05:33:33 -06:00
console.log('error parsing target:', err.message);
2017-10-13 04:05:38 -05:00
this.error = err.message;
this.target.textEditor = true;
}
this.checkOtherSegmentsIndex = this.segments.length - 1;
this.checkForSeriesByTag();
}
checkForSeriesByTag() {
const seriesByTagFunc = _.find(this.functions, func => func.def.name === 'seriesByTag');
2017-10-13 04:05:38 -05:00
if (seriesByTagFunc) {
this.seriesByTagUsed = true;
seriesByTagFunc.hidden = true;
const tags = this.splitSeriesByTagParams(seriesByTagFunc);
2017-10-13 04:05:38 -05:00
this.tags = tags;
}
}
getSegmentPathUpTo(index) {
2018-08-29 07:26:50 -05:00
const arr = this.segments.slice(0, index);
2017-10-13 04:05:38 -05:00
return _.reduce(
arr,
(result, segment) => {
2017-12-20 05:33:33 -06:00
return result ? result + '.' + segment.value : segment.value;
},
2017-12-20 05:33:33 -06:00
''
);
2017-10-13 04:05:38 -05:00
}
2017-12-10 09:45:41 -06:00
parseTargetRecursive(astNode, func) {
2017-10-13 04:05:38 -05:00
if (astNode === null) {
return null;
}
switch (astNode.type) {
2017-12-20 05:33:33 -06:00
case 'function':
2018-08-29 07:26:50 -05:00
const innerFunc = this.datasource.createFuncInstance(astNode.name, {
2017-12-20 05:33:33 -06:00
withDefaultParams: false,
});
2017-12-10 09:45:41 -06:00
_.each(astNode.params, param => {
this.parseTargetRecursive(param, innerFunc);
2017-10-13 04:05:38 -05:00
});
innerFunc.updateText();
this.functions.push(innerFunc);
break;
2017-12-20 05:33:33 -06:00
case 'series-ref':
if (this.segments.length > 0 || this.getSeriesByTagFuncIndex() >= 0) {
2017-12-10 09:45:41 -06:00
this.addFunctionParameter(func, astNode.value);
2017-12-09 15:27:05 -06:00
} else {
this.segments.push(astNode);
}
2017-10-13 04:05:38 -05:00
break;
2017-12-20 05:33:33 -06:00
case 'bool':
case 'string':
case 'number':
2017-12-10 09:45:41 -06:00
this.addFunctionParameter(func, astNode.value);
2017-12-09 15:27:05 -06:00
break;
2017-12-20 05:33:33 -06:00
case 'metric':
2017-10-13 04:05:38 -05:00
if (this.segments.length > 0) {
this.addFunctionParameter(func, _.join(_.map(astNode.segments, 'value'), '.'));
2017-12-09 15:27:05 -06:00
} else {
this.segments = astNode.segments;
2017-10-13 04:05:38 -05:00
}
break;
}
}
updateSegmentValue(segment, index) {
this.segments[index].value = segment.value;
}
addSelectMetricSegment() {
2017-12-20 05:33:33 -06:00
this.segments.push({ value: 'select metric' });
2017-10-13 04:05:38 -05:00
}
addFunction(newFunc) {
this.functions.push(newFunc);
this.moveAliasFuncLast();
}
moveAliasFuncLast() {
const aliasFunc = _.find(this.functions, func => {
return func.def.name.startsWith('alias');
2017-10-13 04:05:38 -05:00
});
if (aliasFunc) {
this.functions = _.without(this.functions, aliasFunc);
this.functions.push(aliasFunc);
}
}
2017-12-10 09:45:41 -06:00
addFunctionParameter(func, value) {
if (func.params.length >= func.def.params.length && !_.get(_.last(func.def.params), 'multiple', false)) {
2017-12-20 05:33:33 -06:00
throw { message: 'too many parameters for function ' + func.def.name };
2017-12-09 15:27:05 -06:00
}
2017-12-10 09:45:41 -06:00
func.params.push(value);
2017-10-13 04:05:38 -05:00
}
removeFunction(func) {
this.functions = _.without(this.functions, func);
}
moveFunction(func, offset) {
const index = this.functions.indexOf(func);
_.move(this.functions, index, index + offset);
}
2017-10-13 04:05:38 -05:00
updateModelTarget(targets) {
// render query
if (!this.target.textEditor) {
2018-08-29 07:26:50 -05:00
const metricPath = this.getSegmentPathUpTo(this.segments.length).replace(/\.select metric$/, '');
2017-10-13 04:05:38 -05:00
this.target.target = _.reduce(this.functions, wrapFunction, metricPath);
}
this.updateRenderedTarget(this.target, targets);
// loop through other queries and update targetFull as needed
for (const target of targets || []) {
if (target.refId !== this.target.refId) {
this.updateRenderedTarget(target, targets);
}
}
}
updateRenderedTarget(target, targets) {
// render nested query
2018-08-29 07:26:50 -05:00
const targetsByRefId = _.keyBy(targets, 'refId');
2017-10-13 04:05:38 -05:00
// no references to self
delete targetsByRefId[target.refId];
2018-08-29 07:26:50 -05:00
const nestedSeriesRefRegex = /\#([A-Z])/g;
let targetWithNestedQueries = target.target;
2017-10-13 04:05:38 -05:00
// Use ref count to track circular references
function countTargetRefs(targetsByRefId, refId) {
let refCount = 0;
_.each(targetsByRefId, (t, id) => {
if (id !== refId) {
const match = nestedSeriesRefRegex.exec(t.target);
const count = match && match.length ? match.length - 1 : 0;
refCount += count;
}
});
targetsByRefId[refId].refCount = refCount;
}
_.each(targetsByRefId, (t, id) => {
countTargetRefs(targetsByRefId, id);
});
2017-10-13 04:05:38 -05:00
// Keep interpolating until there are no query references
// The reason for the loop is that the referenced query might contain another reference to another query
while (targetWithNestedQueries.match(nestedSeriesRefRegex)) {
2018-08-29 07:26:50 -05:00
const updated = targetWithNestedQueries.replace(nestedSeriesRefRegex, (match, g1) => {
const t = targetsByRefId[g1];
if (!t) {
return match;
2017-10-13 04:05:38 -05:00
}
// no circular references
if (t.refCount === 0) {
delete targetsByRefId[g1];
}
t.refCount--;
return t.target;
});
2017-10-13 04:05:38 -05:00
if (updated === targetWithNestedQueries) {
break;
}
targetWithNestedQueries = updated;
}
delete target.targetFull;
if (target.target !== targetWithNestedQueries) {
target.targetFull = targetWithNestedQueries;
}
}
splitSeriesByTagParams(func) {
2017-12-22 14:08:59 -06:00
const tagPattern = /([^\!=~]+)(\!?=~?)(.*)/;
return _.flatten(
_.map(func.params, (param: string) => {
const matches = tagPattern.exec(param);
if (matches) {
const tag = matches.slice(1);
if (tag.length === 3) {
return {
key: tag[0],
operator: tag[1],
2017-12-20 05:33:33 -06:00
value: tag[2],
};
}
2017-10-13 04:05:38 -05:00
}
return [];
})
);
2017-10-13 04:05:38 -05:00
}
getSeriesByTagFuncIndex() {
2017-12-20 05:33:33 -06:00
return _.findIndex(this.functions, func => func.def.name === 'seriesByTag');
2017-10-13 04:05:38 -05:00
}
getSeriesByTagFunc() {
const seriesByTagFuncIndex = this.getSeriesByTagFuncIndex();
2017-10-13 04:05:38 -05:00
if (seriesByTagFuncIndex >= 0) {
return this.functions[seriesByTagFuncIndex];
} else {
return undefined;
}
}
addTag(tag) {
const newTagParam = renderTagString(tag);
2017-10-13 04:05:38 -05:00
this.getSeriesByTagFunc().params.push(newTagParam);
this.tags.push(tag);
}
removeTag(index) {
this.getSeriesByTagFunc().params.splice(index, 1);
this.tags.splice(index, 1);
}
updateTag(tag, tagIndex) {
this.error = null;
if (tag.key === this.removeTagValue) {
this.removeTag(tagIndex);
return;
}
const newTagParam = renderTagString(tag);
2017-10-13 04:05:38 -05:00
this.getSeriesByTagFunc().params[tagIndex] = newTagParam;
this.tags[tagIndex] = tag;
}
renderTagExpressions(excludeIndex = -1) {
return _.compact(
_.map(this.tags, (tagExpr, index) => {
// Don't render tag that we want to lookup
if (index !== excludeIndex) {
return tagExpr.key + tagExpr.operator + tagExpr.value;
}
})
);
}
2017-10-13 04:05:38 -05:00
}
function wrapFunction(target, func) {
return func.render(target);
}
function renderTagString(tag) {
return tag.key + tag.operator + tag.value;
}