grafana/public/app/plugins/datasource/influxdb/query_part.ts

433 lines
10 KiB
TypeScript

///<reference path="../../../headers/common.d.ts" />
import _ = require('lodash');
var index = [];
var categories = {
Aggregations: [],
Selectors: [],
Transformations: [],
Math: [],
Aliasing: [],
Fields: [],
};
var groupByTimeFunctions = [];
class QueryPartDef {
type: string;
params: any[];
defaultParams: any[];
renderer: any;
category: any;
addStrategy: any;
constructor(options: any) {
this.type = options.type;
this.params = options.params;
this.defaultParams = options.defaultParams;
this.renderer = options.renderer;
this.category = options.category;
this.addStrategy = options.addStrategy;
}
static register(options: any) {
index[options.type] = new QueryPartDef(options);
options.category.push(index[options.type]);
}
}
function functionRenderer(part, innerExpr) {
var str = part.def.type + '(';
var parameters = _.map(part.params, (value, index) => {
var paramType = part.def.params[index];
if (paramType.type === 'time') {
if (value === 'auto') {
value = '$interval';
}
}
if (paramType.quote === 'single') {
return "'" + value + "'";
} else if (paramType.quote === 'double') {
return '"' + value + '"';
}
return value;
});
if (innerExpr) {
parameters.unshift(innerExpr);
}
return str + parameters.join(', ') + ')';
}
function aliasRenderer(part, innerExpr) {
return innerExpr + ' AS ' + '"' + part.params[0] + '"';
}
function suffixRenderer(part, innerExpr) {
return innerExpr + ' ' + part.params[0];
}
function identityRenderer(part, innerExpr) {
return part.params[0];
}
function quotedIdentityRenderer(part, innerExpr) {
return '"' + part.params[0] + '"';
}
function fieldRenderer(part, innerExpr) {
if (part.params[0] === '*') {
return '*';
}
return '"' + part.params[0] + '"';
}
function replaceAggregationAddStrategy(selectParts, partModel) {
// look for existing aggregation
for (var i = 0; i < selectParts.length; i++) {
var part = selectParts[i];
if (part.def.category === categories.Aggregations) {
selectParts[i] = partModel;
return;
}
if (part.def.category === categories.Selectors) {
selectParts[i] = partModel;
return;
}
}
selectParts.splice(1, 0, partModel);
}
function addTransformationStrategy(selectParts, partModel) {
var i;
// look for index to add transformation
for (i = 0; i < selectParts.length; i++) {
var part = selectParts[i];
if (part.def.category === categories.Math || part.def.category === categories.Aliasing) {
break;
}
}
selectParts.splice(i, 0, partModel);
}
function addMathStrategy(selectParts, partModel) {
var partCount = selectParts.length;
if (partCount > 0) {
// if last is math, replace it
if (selectParts[partCount-1].def.type === 'math') {
selectParts[partCount-1] = partModel;
return;
}
// if next to last is math, replace it
if (selectParts[partCount-2].def.type === 'math') {
selectParts[partCount-2] = partModel;
return;
}
// if last is alias add it before
else if (selectParts[partCount-1].def.type === 'alias') {
selectParts.splice(partCount-1, 0, partModel);
return;
}
}
selectParts.push(partModel);
}
function addAliasStrategy(selectParts, partModel) {
var partCount = selectParts.length;
if (partCount > 0) {
// if last is alias, replace it
if (selectParts[partCount-1].def.type === 'alias') {
selectParts[partCount-1] = partModel;
return;
}
}
selectParts.push(partModel);
}
function addFieldStrategy(selectParts, partModel, query) {
// copy all parts
var parts = _.map(selectParts, function(part: any) {
return new QueryPart({type: part.def.type, params: _.clone(part.params)});
});
query.selectModels.push(parts);
}
QueryPartDef.register({
type: 'field',
addStrategy: addFieldStrategy,
category: categories.Fields,
params: [{type: 'field', dynamicLookup: true}],
defaultParams: ['value'],
renderer: fieldRenderer,
});
// Aggregations
QueryPartDef.register({
type: 'count',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'distinct',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'integral',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'mean',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'median',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'sum',
addStrategy: replaceAggregationAddStrategy,
category: categories.Aggregations,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
// transformations
QueryPartDef.register({
type: 'derivative',
addStrategy: addTransformationStrategy,
category: categories.Transformations,
params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']}],
defaultParams: ['10s'],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'non_negative_derivative',
addStrategy: addTransformationStrategy,
category: categories.Transformations,
params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']}],
defaultParams: ['10s'],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'stddev',
addStrategy: addTransformationStrategy,
category: categories.Transformations,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'time',
category: groupByTimeFunctions,
params: [{ name: "interval", type: "time", options: ['auto', '1s', '10s', '1m', '5m', '10m', '15m', '1h'] }],
defaultParams: ['auto'],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'fill',
category: groupByTimeFunctions,
params: [{ name: "fill", type: "string", options: ['none', 'null', '0', 'previous'] }],
defaultParams: ['null'],
renderer: functionRenderer,
});
// Selectors
QueryPartDef.register({
type: 'bottom',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
params: [{name: 'count', type: 'int'}],
defaultParams: [3],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'first',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'last',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'max',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'min',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
params: [],
defaultParams: [],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'percentile',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
params: [{name: 'nth', type: 'int'}],
defaultParams: [95],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'top',
addStrategy: replaceAggregationAddStrategy,
category: categories.Selectors,
params: [{name: 'count', type: 'int'}],
defaultParams: [3],
renderer: functionRenderer,
});
QueryPartDef.register({
type: 'tag',
category: groupByTimeFunctions,
params: [{name: 'tag', type: 'string', dynamicLookup: true}],
defaultParams: ['tag'],
renderer: fieldRenderer,
});
QueryPartDef.register({
type: 'math',
addStrategy: addMathStrategy,
category: categories.Math,
params: [{ name: "expr", type: "string"}],
defaultParams: [' / 100'],
renderer: suffixRenderer,
});
QueryPartDef.register({
type: 'alias',
addStrategy: addAliasStrategy,
category: categories.Aliasing,
params: [{ name: "name", type: "string", quote: 'double'}],
defaultParams: ['alias'],
renderMode: 'suffix',
renderer: aliasRenderer,
});
class QueryPart {
part: any;
def: QueryPartDef;
params: any[];
text: string;
constructor(part: any) {
this.part = part;
this.def = index[part.type];
if (!this.def) {
throw {message: 'Could not find query part ' + part.type};
}
part.params = part.params || _.clone(this.def.defaultParams);
this.params = part.params;
this.updateText();
}
render(innerExpr: string) {
return this.def.renderer(this, innerExpr);
}
hasMultipleParamsInString (strValue, index) {
if (strValue.indexOf(',') === -1) {
return false;
}
return this.def.params[index + 1] && this.def.params[index + 1].optional;
}
updateParam (strValue, index) {
// handle optional parameters
// if string contains ',' and next param is optional, split and update both
if (this.hasMultipleParamsInString(strValue, index)) {
_.each(strValue.split(','), function(partVal: string, idx) {
this.updateParam(partVal.trim(), idx);
}, this);
return;
}
if (strValue === '' && this.def.params[index].optional) {
this.params.splice(index, 1);
}
else {
this.params[index] = strValue;
}
this.part.params = this.params;
this.updateText();
}
updateText() {
if (this.params.length === 0) {
this.text = this.def.type + '()';
return;
}
var text = this.def.type + '(';
text += this.params.join(', ');
text += ')';
this.text = text;
}
}
export = {
create: function(part): any {
return new QueryPart(part);
},
getCategories: function() {
return categories;
}
};