Commit 199d0d15 by Alexander Zobnin

graphite: datasource refactor

parent a834812e
import _ from 'lodash';
import gfunc from './gfunc';
import {Parser} from './parser';
export default class GraphiteQuery {
target: any;
functions: any[];
segments: any[];
tags: any[];
error: any;
seriesByTagUsed: boolean;
checkOtherSegmentsIndex: number;
removeTagValue: string;
templateSrv: any;
scopedVars: any;
/** @ngInject */
constructor(target, templateSrv?, scopedVars?) {
this.target = target;
this.parseTarget();
this.removeTagValue = '-- remove tag --';
}
parseTarget() {
this.functions = [];
this.segments = [];
this.error = null;
if (this.target.textEditor) {
return;
}
var parser = new Parser(this.target.target);
var astNode = parser.getAst();
if (astNode === null) {
this.checkOtherSegmentsIndex = 0;
return;
}
if (astNode.type === 'error') {
this.error = astNode.message + " at position: " + astNode.pos;
this.target.textEditor = true;
return;
}
try {
this.parseTargetRecursive(astNode, null, 0);
} catch (err) {
console.log('error parsing target:', err.message);
this.error = err.message;
this.target.textEditor = true;
}
this.checkOtherSegmentsIndex = this.segments.length - 1;
this.checkForSeriesByTag();
}
checkForSeriesByTag() {
let seriesByTagFunc = _.find(this.functions, (func) => func.def.name === 'seriesByTag');
if (seriesByTagFunc) {
this.seriesByTagUsed = true;
seriesByTagFunc.hidden = true;
let tags = this.splitSeriesByTagParams(seriesByTagFunc);
this.tags = tags;
}
}
getSegmentPathUpTo(index) {
var arr = this.segments.slice(0, index);
return _.reduce(arr, function(result, segment) {
return result ? (result + "." + segment.value) : segment.value;
}, "");
}
parseTargetRecursive(astNode, func, index) {
if (astNode === null) {
return null;
}
switch (astNode.type) {
case 'function':
var innerFunc = gfunc.createFuncInstance(astNode.name, { withDefaultParams: false });
_.each(astNode.params, (param, index) => {
this.parseTargetRecursive(param, innerFunc, index);
});
innerFunc.updateText();
this.functions.push(innerFunc);
break;
case 'series-ref':
this.addFunctionParameter(func, astNode.value, index, this.segments.length > 0);
break;
case 'bool':
case 'string':
case 'number':
if ((index-1) >= func.def.params.length) {
throw { message: 'invalid number of parameters to method ' + func.def.name };
}
var shiftBack = this.isShiftParamsBack(func);
this.addFunctionParameter(func, astNode.value, index, shiftBack);
break;
case 'metric':
if (this.segments.length > 0) {
if (astNode.segments.length !== 1) {
throw { message: 'Multiple metric params not supported, use text editor.' };
}
this.addFunctionParameter(func, astNode.segments[0].value, index, true);
break;
}
this.segments = astNode.segments;
}
}
isShiftParamsBack(func) {
return func.def.name !== 'seriesByTag';
}
updateSegmentValue(segment, index) {
this.segments[index].value = segment.value;
}
addSelectMetricSegment() {
this.segments.push({value: "select metric"});
}
addFunction(newFunc) {
this.functions.push(newFunc);
this.moveAliasFuncLast();
}
moveAliasFuncLast() {
var aliasFunc = _.find(this.functions, function(func) {
return func.def.name === 'alias' ||
func.def.name === 'aliasByNode' ||
func.def.name === 'aliasByMetric';
});
if (aliasFunc) {
this.functions = _.without(this.functions, aliasFunc);
this.functions.push(aliasFunc);
}
}
addFunctionParameter(func, value, index, shiftBack) {
if (shiftBack) {
index = Math.max(index - 1, 0);
}
func.params[index] = value;
}
removeFunction(func) {
this.functions = _.without(this.functions, func);
}
updateModelTarget(targets) {
// render query
if (!this.target.textEditor) {
var metricPath = this.getSegmentPathUpTo(this.segments.length);
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
var targetsByRefId = _.keyBy(targets, 'refId');
// no references to self
delete targetsByRefId[target.refId];
var nestedSeriesRefRegex = /\#([A-Z])/g;
var targetWithNestedQueries = target.target;
// 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)) {
var updated = targetWithNestedQueries.replace(nestedSeriesRefRegex, (match, g1) => {
var t = targetsByRefId[g1];
if (!t) {
return match;
}
// no circular references
delete targetsByRefId[g1];
return t.target;
});
if (updated === targetWithNestedQueries) {
break;
}
targetWithNestedQueries = updated;
}
delete target.targetFull;
if (target.target !== targetWithNestedQueries) {
target.targetFull = targetWithNestedQueries;
}
}
splitSeriesByTagParams(func) {
const tagPattern = /([^\!=~]+)([\!=~]+)([^\!=~]+)/;
return _.flatten(_.map(func.params, (param: string) => {
let matches = tagPattern.exec(param);
if (matches) {
let tag = matches.slice(1);
if (tag.length === 3) {
return {
key: tag[0],
operator: tag[1],
value: tag[2]
};
}
}
return [];
}));
}
getSeriesByTagFuncIndex() {
return _.findIndex(this.functions, (func) => func.def.name === 'seriesByTag');
}
getSeriesByTagFunc() {
let seriesByTagFuncIndex = this.getSeriesByTagFuncIndex();
if (seriesByTagFuncIndex >= 0) {
return this.functions[seriesByTagFuncIndex];
} else {
return undefined;
}
}
addTag(tag) {
let newTagParam = renderTagString(tag);
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;
}
let newTagParam = renderTagString(tag);
this.getSeriesByTagFunc().params[tagIndex] = newTagParam;
this.tags[tagIndex] = tag;
}
}
function wrapFunction(target, func) {
return func.render(target);
}
function renderTagString(tag) {
return tag.key + tag.operator + tag.value;
}
...@@ -6,11 +6,11 @@ ...@@ -6,11 +6,11 @@
<div ng-hide="ctrl.target.textEditor"> <div ng-hide="ctrl.target.textEditor">
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form" ng-if="ctrl.seriesByTagUsed"> <div class="gf-form" ng-if="ctrl.queryModel.seriesByTagUsed">
<label class="gf-form-label query-keyword">seriesByTag</label> <label class="gf-form-label query-keyword">seriesByTag</label>
</div> </div>
<div ng-repeat="tag in ctrl.tags" class="gf-form"> <div ng-repeat="tag in ctrl.queryModel.tags" class="gf-form">
<gf-form-dropdown model="tag.key" lookup-text="false" allow-custom="false" label-mode="true" css-class="query-segment-key" <gf-form-dropdown model="tag.key" lookup-text="false" allow-custom="false" label-mode="true" css-class="query-segment-key"
get-options="ctrl.getTags()" get-options="ctrl.getTags()"
on-change="ctrl.tagChanged(tag, $index)"> on-change="ctrl.tagChanged(tag, $index)">
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
</gf-form-dropdown> </gf-form-dropdown>
<label class="gf-form-label query-keyword" ng-if="ctrl.showDelimiter($index)">,</label> <label class="gf-form-label query-keyword" ng-if="ctrl.showDelimiter($index)">,</label>
</div> </div>
<div ng-if="ctrl.seriesByTagUsed" ng-repeat="segment in ctrl.addTagSegments" role="menuitem" class="gf-form"> <div ng-if="ctrl.queryModel.seriesByTagUsed" ng-repeat="segment in ctrl.addTagSegments" role="menuitem" class="gf-form">
<metric-segment segment="segment" <metric-segment segment="segment"
get-options="ctrl.getTagsAsSegments()" get-options="ctrl.getTagsAsSegments()"
on-change="ctrl.addNewTag(segment)"> on-change="ctrl.addNewTag(segment)">
...@@ -36,8 +36,8 @@ ...@@ -36,8 +36,8 @@
<metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment> <metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment>
</div> </div>
<div ng-repeat="func in ctrl.functions" class="gf-form"> <div ng-repeat="func in ctrl.queryModel.functions" class="gf-form">
<span graphite-func-editor class="gf-form-label query-part" ng-hide="ctrl.getSeriesByTagFuncIndex() === $index"></span> <span graphite-func-editor class="gf-form-label query-part" ng-hide="func.hidden"></span>
</div> </div>
<div class="gf-form dropdown"> <div class="gf-form dropdown">
......
...@@ -48,7 +48,7 @@ describe('GraphiteQueryCtrl', function() { ...@@ -48,7 +48,7 @@ describe('GraphiteQueryCtrl', function() {
}); });
it('should parse expression and build function model', function() { it('should parse expression and build function model', function() {
expect(ctx.ctrl.functions.length).to.be(2); expect(ctx.ctrl.queryModel.functions.length).to.be(2);
}); });
}); });
...@@ -61,7 +61,7 @@ describe('GraphiteQueryCtrl', function() { ...@@ -61,7 +61,7 @@ describe('GraphiteQueryCtrl', function() {
}); });
it('should add function with correct node number', function() { it('should add function with correct node number', function() {
expect(ctx.ctrl.functions[0].params[0]).to.be(2); expect(ctx.ctrl.queryModel.functions[0].params[0]).to.be(2);
}); });
it('should update target', function() { it('should update target', function() {
...@@ -99,7 +99,7 @@ describe('GraphiteQueryCtrl', function() { ...@@ -99,7 +99,7 @@ describe('GraphiteQueryCtrl', function() {
}); });
it('should add both series refs as params', function() { it('should add both series refs as params', function() {
expect(ctx.ctrl.functions[0].params.length).to.be(2); expect(ctx.ctrl.queryModel.functions[0].params.length).to.be(2);
}); });
}); });
...@@ -115,7 +115,7 @@ describe('GraphiteQueryCtrl', function() { ...@@ -115,7 +115,7 @@ describe('GraphiteQueryCtrl', function() {
}); });
it('should add function param', function() { it('should add function param', function() {
expect(ctx.ctrl.functions[0].params.length).to.be(1); expect(ctx.ctrl.queryModel.functions[0].params.length).to.be(1);
}); });
}); });
...@@ -131,7 +131,7 @@ describe('GraphiteQueryCtrl', function() { ...@@ -131,7 +131,7 @@ describe('GraphiteQueryCtrl', function() {
}); });
it('should have correct func params', function() { it('should have correct func params', function() {
expect(ctx.ctrl.functions[0].params.length).to.be(1); expect(ctx.ctrl.queryModel.functions[0].params.length).to.be(1);
}); });
}); });
...@@ -219,11 +219,11 @@ describe('GraphiteQueryCtrl', function() { ...@@ -219,11 +219,11 @@ describe('GraphiteQueryCtrl', function() {
}); });
it('should update functions', function() { it('should update functions', function() {
expect(ctx.ctrl.getSeriesByTagFuncIndex()).to.be(0); expect(ctx.ctrl.queryModel.getSeriesByTagFuncIndex()).to.be(0);
}); });
it('should update seriesByTagUsed flag', function() { it('should update seriesByTagUsed flag', function() {
expect(ctx.ctrl.seriesByTagUsed).to.be(true); expect(ctx.ctrl.queryModel.seriesByTagUsed).to.be(true);
}); });
it('should update target', function() { it('should update target', function() {
...@@ -247,7 +247,7 @@ describe('GraphiteQueryCtrl', function() { ...@@ -247,7 +247,7 @@ describe('GraphiteQueryCtrl', function() {
{key: 'tag1', operator: '=', value: 'value1'}, {key: 'tag1', operator: '=', value: 'value1'},
{key: 'tag2', operator: '!=~', value: 'value2'} {key: 'tag2', operator: '!=~', value: 'value2'}
]; ];
expect(ctx.ctrl.tags).to.eql(expected); expect(ctx.ctrl.queryModel.tags).to.eql(expected);
}); });
it('should add plus button', function() { it('should add plus button', function() {
...@@ -267,7 +267,7 @@ describe('GraphiteQueryCtrl', function() { ...@@ -267,7 +267,7 @@ describe('GraphiteQueryCtrl', function() {
const expected = [ const expected = [
{key: 'tag1', operator: '=', value: 'select tag value'} {key: 'tag1', operator: '=', value: 'select tag value'}
]; ];
expect(ctx.ctrl.tags).to.eql(expected); expect(ctx.ctrl.queryModel.tags).to.eql(expected);
}); });
it('should update target', function() { it('should update target', function() {
...@@ -289,7 +289,7 @@ describe('GraphiteQueryCtrl', function() { ...@@ -289,7 +289,7 @@ describe('GraphiteQueryCtrl', function() {
{key: 'tag1', operator: '=', value: 'new_value'}, {key: 'tag1', operator: '=', value: 'new_value'},
{key: 'tag2', operator: '!=~', value: 'value2'} {key: 'tag2', operator: '!=~', value: 'value2'}
]; ];
expect(ctx.ctrl.tags).to.eql(expected); expect(ctx.ctrl.queryModel.tags).to.eql(expected);
}); });
it('should update target', function() { it('should update target', function() {
...@@ -310,7 +310,7 @@ describe('GraphiteQueryCtrl', function() { ...@@ -310,7 +310,7 @@ describe('GraphiteQueryCtrl', function() {
const expected = [ const expected = [
{key: 'tag2', operator: '!=~', value: 'value2'} {key: 'tag2', operator: '!=~', value: 'value2'}
]; ];
expect(ctx.ctrl.tags).to.eql(expected); expect(ctx.ctrl.queryModel.tags).to.eql(expected);
}); });
it('should update target', function() { it('should update target', function() {
......
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