The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/plugins/datasource/graphite/query_ctrl.ts

334 lines
8.8 KiB

///<reference path="../../../headers/common.d.ts" />
import './add_graphite_func';
import './func_editor';
import angular from 'angular';
import _ from 'lodash';
import moment from 'moment';
import gfunc from './gfunc';
import {Parser} from './parser';
import {QueryCtrl} from 'app/plugins/sdk';
import appEvents from 'app/core/app_events';
export class GraphiteQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html';
functions: any[];
segments: any[];
/** @ngInject **/
constructor($scope, $injector, private uiSegmentSrv, private templateSrv) {
super($scope, $injector);
if (this.target) {
this.target.target = this.target.target || '';
this.parseTarget();
}
}
toggleEditorMode() {
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);
}
addFunctionParameter(func, value, index, shiftBack) {
if (shiftBack) {
index = Math.max(index - 1, 0);
}
func.params[index] = 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 };
}
this.addFunctionParameter(func, astNode.value, index, true);
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 = _.map(astNode.segments, segment => {
return this.uiSegmentSrv.newSegment(segment);
});
}
}
getSegmentPathUpTo(index) {
var arr = this.segments.slice(0, index);
return _.reduce(arr, function(result, segment) {
return result ? (result + "." + segment.value) : segment.value;
}, "");
}
checkOtherSegments(fromIndex) {
if (fromIndex === 0) {
this.segments.push(this.uiSegmentSrv.newSelectMetric());
return;
}
var path = this.getSegmentPathUpTo(fromIndex + 1);
if (path === "") {
return Promise.resolve();
}
return this.datasource.metricFindQuery(path).then(segments => {
if (segments.length === 0) {
if (path !== '') {
this.segments = this.segments.splice(0, fromIndex);
this.segments.push(this.uiSegmentSrv.newSelectMetric());
}
} else if (segments[0].expandable) {
if (this.segments.length === fromIndex) {
this.segments.push(this.uiSegmentSrv.newSelectMetric());
} else {
return this.checkOtherSegments(fromIndex + 1);
}
}
}).catch(err => {
appEvents.emit('alert-error', ['Error', err]);
});
}
setSegmentFocus(segmentIndex) {
_.each(this.segments, (segment, index) => {
segment.focus = segmentIndex === index;
});
}
wrapFunction(target, func) {
return func.render(target);
}
getAltSegments(index) {
var query = index === 0 ? '*' : this.getSegmentPathUpTo(index) + '.*';
return this.datasource.metricFindQuery(query).then(segments => {
var altSegments = _.map(segments, segment => {
return this.uiSegmentSrv.newSegment({value: segment.text, expandable: segment.expandable});
});
if (altSegments.length === 0) { return altSegments; }
// add template variables
_.each(this.templateSrv.variables, variable => {
altSegments.unshift(this.uiSegmentSrv.newSegment({
type: 'template',
value: '$' + variable.name,
expandable: true,
}));
});
// add wildcard option
altSegments.unshift(this.uiSegmentSrv.newSegment('*'));
return altSegments;
}).catch(err => {
appEvents.emit('alert-error', ['Error', err]);
return [];
});
}
segmentValueChanged(segment, segmentIndex) {
this.error = null;
if (this.functions.length > 0 && this.functions[0].def.fake) {
this.functions = [];
}
if (segment.expandable) {
return this.checkOtherSegments(segmentIndex + 1).then(() => {
this.setSegmentFocus(segmentIndex + 1);
this.targetChanged();
});
} else {
this.segments = this.segments.splice(0, segmentIndex + 1);
}
this.setSegmentFocus(segmentIndex + 1);
this.targetChanged();
}
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;
}
}
targetChanged() {
if (this.error) {
return;
}
var oldTarget = this.target.target;
this.updateModelTarget();
if (this.target.target !== oldTarget) {
var lastSegment = this.segments.length > 0 ? this.segments[this.segments.length - 1] : {};
if (lastSegment.value !== 'select metric') {
this.panelCtrl.refresh();
}
}
}
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.smartlyHandleNewAliasByNode(newFunc);
if (this.segments.length === 1 && this.segments[0].fake) {
this.segments = [];
}
if (!newFunc.params.length && newFunc.added) {
this.targetChanged();
}
}
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);
}
}
smartlyHandleNewAliasByNode(func) {
if (func.def.name !== 'aliasByNode') {
return;
}
for (var i = 0; i < this.segments.length; i++) {
if (this.segments[i].value.indexOf('*') >= 0) {
func.params[0] = i;
func.added = false;
this.targetChanged();
return;
}
}
}
}