-
+
+
diff --git a/public/app/plugins/datasource/graphite/query_ctrl.ts b/public/app/plugins/datasource/graphite/query_ctrl.ts
index 792aa86f6ee..9f661e924bd 100644
--- a/public/app/plugins/datasource/graphite/query_ctrl.ts
+++ b/public/app/plugins/datasource/graphite/query_ctrl.ts
@@ -3,7 +3,7 @@ import './func_editor';
import _ from 'lodash';
import gfunc from './gfunc';
-import {Parser} from './parser';
+import GraphiteQuery from './graphite_query';
import {QueryCtrl} from 'app/plugins/sdk';
import appEvents from 'app/core/app_events';
@@ -12,11 +12,9 @@ const GRAPHITE_TAG_OPERATORS = ['=', '!=', '=~', '!=~'];
export class GraphiteQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html';
- functions: any[];
+ queryModel: GraphiteQuery;
segments: any[];
addTagSegments: any[];
- tags: any[];
- seriesByTagUsed: boolean;
removeTagValue: string;
/** @ngInject **/
@@ -25,119 +23,47 @@ export class GraphiteQueryCtrl extends QueryCtrl {
if (this.target) {
this.target.target = this.target.target || '';
- this.parseTarget();
+ this.queryModel = new GraphiteQuery(this.target, templateSrv);
+ this.buildSegments();
}
this.removeTagValue = '-- remove tag --';
}
- toggleEditorMode() {
- this.target.textEditor = !this.target.textEditor;
- this.parseTarget();
- }
-
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.checkOtherSegments(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.checkOtherSegments(this.segments.length - 1);
- this.checkForSeriesByTag();
+ this.queryModel.parseTarget();
+ this.buildSegments();
}
- addFunctionParameter(func, value, index, shiftBack) {
- if (shiftBack) {
- index = Math.max(index - 1, 0);
- }
- func.params[index] = value;
+ toggleEditorMode() {
+ this.target.textEditor = !this.target.textEditor;
+ this.parseTarget();
}
- 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;
- }
+ buildSegments() {
+ this.segments = _.map(this.queryModel.segments, segment => {
+ return this.uiSegmentSrv.newSegment(segment);
+ });
+ let checkOtherSegmentsIndex = this.queryModel.checkOtherSegmentsIndex || 0;
+ this.checkOtherSegments(checkOtherSegmentsIndex);
- this.segments = _.map(astNode.segments, segment => {
- return this.uiSegmentSrv.newSegment(segment);
- });
+ if (this.queryModel.seriesByTagUsed) {
+ this.fixTagSegments();
}
}
- isShiftParamsBack(func) {
- return func.def.name !== 'seriesByTag';
- }
-
- getSegmentPathUpTo(index) {
- var arr = this.segments.slice(0, index);
-
- return _.reduce(arr, function(result, segment) {
- return result ? (result + "." + segment.value) : segment.value;
- }, "");
+ addSelectMetricSegment() {
+ this.queryModel.addSelectMetricSegment();
+ this.segments.push(this.uiSegmentSrv.newSelectMetric());
}
checkOtherSegments(fromIndex) {
if (fromIndex === 0) {
- this.segments.push(this.uiSegmentSrv.newSelectMetric());
+ this.addSelectMetricSegment();
return;
}
- var path = this.getSegmentPathUpTo(fromIndex + 1);
+ var path = this.queryModel.getSegmentPathUpTo(fromIndex + 1);
if (path === "") {
return Promise.resolve();
}
@@ -145,12 +71,13 @@ export class GraphiteQueryCtrl extends QueryCtrl {
return this.datasource.metricFindQuery(path).then(segments => {
if (segments.length === 0) {
if (path !== '') {
+ this.queryModel.segments = this.queryModel.segments.splice(0, fromIndex);
this.segments = this.segments.splice(0, fromIndex);
- this.segments.push(this.uiSegmentSrv.newSelectMetric());
+ this.addSelectMetricSegment();
}
} else if (segments[0].expandable) {
if (this.segments.length === fromIndex) {
- this.segments.push(this.uiSegmentSrv.newSelectMetric());
+ this.addSelectMetricSegment();
} else {
return this.checkOtherSegments(fromIndex + 1);
}
@@ -166,12 +93,8 @@ export class GraphiteQueryCtrl extends QueryCtrl {
});
}
- wrapFunction(target, func) {
- return func.render(target);
- }
-
getAltSegments(index) {
- var query = index === 0 ? '*' : this.getSegmentPathUpTo(index) + '.*';
+ var query = index === 0 ? '*' : this.queryModel.getSegmentPathUpTo(index) + '.*';
var options = {range: this.panelCtrl.range, requestId: "get-alt-segments"};
return this.datasource.metricFindQuery(query, options).then(segments => {
@@ -200,9 +123,10 @@ export class GraphiteQueryCtrl extends QueryCtrl {
segmentValueChanged(segment, segmentIndex) {
this.error = null;
+ this.queryModel.updateSegmentValue(segment, segmentIndex);
- if (this.functions.length > 0 && this.functions[0].def.fake) {
- this.functions = [];
+ if (this.queryModel.functions.length > 0 && this.queryModel.functions[0].def.fake) {
+ this.queryModel.functions = [];
}
if (segment.expandable) {
@@ -211,81 +135,41 @@ export class GraphiteQueryCtrl extends QueryCtrl {
this.targetChanged();
});
} else {
- this.segments = this.segments.splice(0, segmentIndex + 1);
+ this.spliceSegments(segmentIndex + 1);
}
this.setSegmentFocus(segmentIndex + 1);
this.targetChanged();
}
+ spliceSegments(index) {
+ this.segments = this.segments.splice(0, index);
+ this.queryModel.segments = this.queryModel.segments.splice(0, index);
+ }
+
+ emptySegments() {
+ this.queryModel.segments = [];
+ this.segments = [];
+ }
+
targetTextChanged() {
this.updateModelTarget();
this.refresh();
}
updateModelTarget() {
- // render query
- if (!this.target.textEditor) {
- var metricPath = this.getSegmentPathUpTo(this.segments.length);
- this.target.target = _.reduce(this.functions, this.wrapFunction, metricPath);
- }
-
- this.updateRenderedTarget(this.target);
-
- // loop through other queries and update targetFull as needed
- for (const target of this.panelCtrl.panel.targets || []) {
- if (target.refId !== this.target.refId) {
- this.updateRenderedTarget(target);
- }
- }
- }
-
- updateRenderedTarget(target) {
- // render nested query
- var targetsByRefId = _.keyBy(this.panelCtrl.panel.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;
- }
+ this.queryModel.updateModelTarget(this.panelCtrl.panel.targets);
}
targetChanged() {
- if (this.error) {
+ if (this.queryModel.error) {
return;
}
- var oldTarget = this.target.target;
+ var oldTarget = this.queryModel.target.target;
this.updateModelTarget();
- if (this.target.target !== oldTarget) {
+ if (this.queryModel.target !== oldTarget) {
var lastSegment = this.segments.length > 0 ? this.segments[this.segments.length - 1] : {};
if (lastSegment.value !== 'select metric') {
this.panelCtrl.refresh();
@@ -293,21 +177,14 @@ export class GraphiteQueryCtrl extends QueryCtrl {
}
}
- removeFunction(func) {
- this.functions = _.without(this.functions, func);
- this.targetChanged();
- }
-
addFunction(funcDef) {
var newFunc = gfunc.createFuncInstance(funcDef, { withDefaultParams: true });
newFunc.added = true;
- this.functions.push(newFunc);
-
- this.moveAliasFuncLast();
+ this.queryModel.addFunction(newFunc);
this.smartlyHandleNewAliasByNode(newFunc);
if (this.segments.length === 1 && this.segments[0].fake) {
- this.segments = [];
+ this.emptySegments();
}
if (!newFunc.params.length && newFunc.added) {
@@ -319,17 +196,9 @@ export class GraphiteQueryCtrl extends QueryCtrl {
}
}
- 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);
- }
+ removeFunction(func) {
+ this.queryModel.removeFunction(func);
+ this.targetChanged();
}
smartlyHandleNewAliasByNode(func) {
@@ -338,7 +207,7 @@ export class GraphiteQueryCtrl extends QueryCtrl {
}
for (var i = 0; i < this.segments.length; i++) {
- if (this.segments[i].value.indexOf('*') >= 0) {
+ if (this.segments[i].value.indexOf('*') >= 0) {
func.params[0] = i;
func.added = false;
this.targetChanged();
@@ -347,38 +216,6 @@ export class GraphiteQueryCtrl extends QueryCtrl {
}
}
- //////////////////////////////////
- // Graphite seriesByTag support //
- //////////////////////////////////
-
- checkForSeriesByTag() {
- let seriesByTagFunc = _.find(this.functions, (func) => func.def.name === 'seriesByTag');
- if (seriesByTagFunc) {
- this.seriesByTagUsed = true;
- let tags = this.splitSeriesByTagParams(seriesByTagFunc);
- this.tags = tags;
- this.fixTagSegments();
- }
- }
-
- 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 [];
- }));
- }
-
getTags() {
return this.datasource.getTags().then((values) => {
let altTags = _.map(values, 'text');
@@ -408,45 +245,20 @@ export class GraphiteQueryCtrl extends QueryCtrl {
}
tagChanged(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;
+ this.queryModel.updateTag(tag, tagIndex);
this.targetChanged();
}
- 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;
- }
- }
-
addNewTag(segment) {
let newTagKey = segment.value;
let newTag = {key: newTagKey, operator: '=', value: 'select tag value'};
- let newTagParam = renderTagString(newTag);
- this.getSeriesByTagFunc().params.push(newTagParam);
- this.tags.push(newTag);
+ this.queryModel.addTag(newTag);
this.targetChanged();
this.fixTagSegments();
}
removeTag(index) {
- this.getSeriesByTagFunc().params.splice(index, 1);
- this.tags.splice(index, 1);
+ this.queryModel.removeTag(index);
this.targetChanged();
}
@@ -456,14 +268,10 @@ export class GraphiteQueryCtrl extends QueryCtrl {
}
showDelimiter(index) {
- return index !== this.tags.length - 1;
+ return index !== this.queryModel.tags.length - 1;
}
}
-function renderTagString(tag) {
- return tag.key + tag.operator + tag.value;
-}
-
function mapToDropdownOptions(results) {
return _.map(results, (value) => {
return {text: value, value: value};
diff --git a/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts
index f9eced25642..177c1e2a0d6 100644
--- a/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts
+++ b/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts
@@ -48,7 +48,7 @@ describe('GraphiteQueryCtrl', 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() {
});
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() {
@@ -99,7 +99,7 @@ describe('GraphiteQueryCtrl', 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() {
});
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() {
});
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() {
});
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() {
- expect(ctx.ctrl.seriesByTagUsed).to.be(true);
+ expect(ctx.ctrl.queryModel.seriesByTagUsed).to.be(true);
});
it('should update target', function() {
@@ -247,7 +247,7 @@ describe('GraphiteQueryCtrl', function() {
{key: 'tag1', operator: '=', value: 'value1'},
{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() {
@@ -267,7 +267,7 @@ describe('GraphiteQueryCtrl', function() {
const expected = [
{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() {
@@ -289,7 +289,7 @@ describe('GraphiteQueryCtrl', function() {
{key: 'tag1', operator: '=', value: 'new_value'},
{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() {
@@ -310,7 +310,7 @@ describe('GraphiteQueryCtrl', function() {
const expected = [
{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() {