feat(influxdb editor): more progress

This commit is contained in:
Torkel Ödegaard 2015-09-30 14:37:27 +02:00
parent 2dc8fcd3be
commit f053b41645
6 changed files with 225 additions and 308 deletions

View File

@ -65,22 +65,20 @@
<div ng-hide="target.rawQuery">
<div class="tight-form" ng-repeat="parts in select">
<div class="tight-form" ng-repeat="parts in selectParts">
<ul class="tight-form-list">
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
<span ng-show="$index === 0">SELECT</span>
</li>
<li ng-repeat="func in parts">
<span influx-query-part-editor class="tight-form-item tight-form-func">
</span>
<li ng-repeat="part in parts">
<influx-query-part-editor part="part" class="tight-form-item tight-form-func"></influx-query-part-editor>
</li>
</ul>
<ul class="tight-form-list pull-right">
<li class="tight-form-item last" ng-show="$index === 0">
<a class="pointer" ng-click="addSelect()"><i class="fa fa-plus"></i></a>
</li>
<li class="tight-form-item last" ng-show="target.fields.length > 1">
<li class="tight-form-item last" ng-show="target.select.length > 1">
<a class="pointer" ng-click="removeSelect($index)"><i class="fa fa-minus"></i></a>
</li>
</ul>
@ -93,8 +91,7 @@
<span ng-show="$index === 0">GROUP BY</span>
</li>
<li ng-if="groupBy.type === 'time'">
<span influx-query-part-editor class="tight-form-item tight-form-func">
</span>
<influx-query-part-editor part="groupByParts" class="tight-form-item tight-form-func"></influx-query-part-editor>
</li>
<!-- <li class="dropdown" ng&#45;if="groupBy.type === 'time'"> -->
<!-- <a class="tight&#45;form&#45;item pointer" data&#45;toggle="dropdown" bs&#45;tooltip="'Insert missing values, important when stacking'" data&#45;placement="right"> -->

View File

@ -0,0 +1,6 @@
<div class="tight-form-func-controls">
<span class="pointer fa fa-question-circle"></span>
<span class="pointer fa fa-remove" ></span>
</div>
<a ng-click="toggleControls()">{{part.def.name}}</a><span>(</span><span class="query-part-parameters"></span><span>)</span>

View File

@ -10,7 +10,7 @@ function (angular, _, InfluxQueryBuilder, queryPart) {
var module = angular.module('grafana.controllers');
module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv, $q, uiSegmentSrv) {
module.controller('InfluxQueryCtrl', function($scope, templateSrv, $q, uiSegmentSrv) {
$scope.init = function() {
if (!$scope.target) { return; }
@ -19,25 +19,14 @@ function (angular, _, InfluxQueryBuilder, queryPart) {
target.tags = target.tags || [];
target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}];
target.fields = target.fields || [{name: 'value'}];
target.select = target.select || [[{type: 'field', params: ['value']}]];
target.select[0] = [
{type: 'field', params: ['value']},
{type: 'mean', params: []},
{type: 'derivate', params: ['10s']},
{type: 'math', params: ['/ 100']},
{type: 'alias', params: ['google']},
];
target.select = target.select || [[
{name: 'field', params: ['value']},
{name: 'mean', params: []},
]];
$scope.select = _.map(target.select, function(parts) {
return _.map(parts, function(part) {
var partModel = queryPart.create(part.type);
partModel.params = part.params;
partModel.updateText();
return partModel;
});
});
$scope.updateSelectParts();
$scope.func = queryPart.create('time', { withDefaultParams: true });
$scope.groupByParts = queryPart.create({name: 'time', params:['$interval']});
$scope.queryBuilder = new InfluxQueryBuilder(target);
@ -89,14 +78,27 @@ function (angular, _, InfluxQueryBuilder, queryPart) {
};
$scope.addSelect = function() {
$scope.target.fields.push({name: "select field", func: 'mean'});
$scope.target.select.push([
{name: 'field', params: ['value']},
{name: 'mean', params: []},
]);
$scope.updateSelectParts();
};
$scope.removeSelect = function(index) {
$scope.target.fields.splice(index, 1);
$scope.target.select.splice(index, 1);
$scope.updateSelectParts();
$scope.get_data();
};
$scope.updateSelectParts = function() {
$scope.selectParts = _.map($scope.target.select, function(parts) {
return _.map(parts, function(part) {
return queryPart.create(part);
});
});
};
$scope.changeFunction = function(func) {
$scope.target.function = func;
$scope.$parent.get_data();

View File

@ -1,168 +0,0 @@
define([
'lodash',
'jquery'
],
function (_, $) {
'use strict';
var index = [];
var categories = {
Combine: [],
Transform: [],
Calculate: [],
Filter: [],
Special: []
};
function addFuncDef(funcDef) {
funcDef.params = funcDef.params || [];
funcDef.defaultParams = funcDef.defaultParams || [];
if (funcDef.category) {
funcDef.category.push(funcDef);
}
index[funcDef.name] = funcDef;
index[funcDef.shortName || funcDef.name] = funcDef;
}
addFuncDef({
name: 'field',
category: categories.Transform,
params: [{type: 'field'}],
defaultParams: ['value'],
});
addFuncDef({
name: 'mean',
category: categories.Transform,
params: [],
defaultParams: [],
});
addFuncDef({
name: 'derivate',
category: categories.Transform,
params: [{ name: "rate", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h'] }],
defaultParams: ['10s'],
});
addFuncDef({
name: 'time',
category: categories.Transform,
params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }],
defaultParams: ['$interval'],
});
addFuncDef({
name: 'math',
category: categories.Transform,
params: [{ name: "expr", type: "string"}],
defaultParams: [' / 100'],
});
addFuncDef({
name: 'alias',
category: categories.Transform,
params: [{ name: "name", type: "string"}],
defaultParams: ['alias'],
});
_.each(categories, function(funcList, catName) {
categories[catName] = _.sortBy(funcList, 'name');
});
function FuncInstance(funcDef, options) {
this.def = funcDef;
this.params = [];
if (options && options.withDefaultParams) {
this.params = funcDef.defaultParams.slice(0);
}
this.updateText();
}
FuncInstance.prototype.render = function(metricExp) {
var str = this.def.name + '(';
var parameters = _.map(this.params, function(value, index) {
var paramType = this.def.params[index].type;
if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') {
return value;
}
else if (paramType === 'int_or_interval' && $.isNumeric(value)) {
return value;
}
return "'" + value + "'";
}, this);
if (metricExp) {
parameters.unshift(metricExp);
}
return str + parameters.join(', ') + ')';
};
FuncInstance.prototype._hasMultipleParamsInString = function(strValue, index) {
if (strValue.indexOf(',') === -1) {
return false;
}
return this.def.params[index + 1] && this.def.params[index + 1].optional;
};
FuncInstance.prototype.updateParam = function(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, 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.updateText();
};
FuncInstance.prototype.updateText = function () {
if (this.params.length === 0) {
this.text = this.def.name + '()';
return;
}
var text = this.def.name + '(';
text += this.params.join(', ');
text += ')';
this.text = text;
};
return {
create: function(funcDef, options) {
if (_.isString(funcDef)) {
if (!index[funcDef]) {
throw { message: 'Method not found ' + name };
}
funcDef = index[funcDef];
}
return new FuncInstance(funcDef, options);
},
getFuncDef: function(name) {
return index[name];
},
getCategories: function() {
return categories;
}
};
});

View File

@ -0,0 +1,165 @@
///<reference path="../../../headers/common.d.ts" />
import _ = require('lodash');
var index = [];
var categories = {
Combine: [],
Transform: [],
Calculate: [],
Filter: [],
Special: []
};
class QueryPartDef {
name: string;
params: any[];
defaultParams: any[];
constructor(options: any) {
this.name = options.name;
this.params = options.params;
this.defaultParams = options.defaultParams;
}
static register(options: any) {
index[options.name] = new QueryPartDef(options);
}
}
QueryPartDef.register({
name: 'field',
category: categories.Transform,
params: [{type: 'field'}],
defaultParams: ['value'],
});
QueryPartDef.register({
name: 'mean',
category: categories.Transform,
params: [],
defaultParams: [],
});
QueryPartDef.register({
name: 'derivate',
category: categories.Transform,
params: [{ name: "rate", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h'] }],
defaultParams: ['10s'],
});
QueryPartDef.register({
name: 'time',
category: categories.Transform,
params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }],
defaultParams: ['$interval'],
});
QueryPartDef.register({
name: 'math',
category: categories.Transform,
params: [{ name: "expr", type: "string"}],
defaultParams: [' / 100'],
});
QueryPartDef.register({
name: 'alias',
category: categories.Transform,
params: [{ name: "name", type: "string"}],
defaultParams: ['alias'],
});
class QueryPart {
part: any;
def: QueryPartDef;
params: any[];
text: string;
constructor(part: any) {
this.part = part;
this.def = index[part.name];
if (!this.def) {
throw {message: 'Could not find query part ' + part.name};
}
this.params = part.params || _.clone(this.def.defaultParams);
}
render(innerExpr: string) {
var str = this.def.name + '(';
var parameters = _.map(this.params, (value, index) => {
var paramType = this.def.params[index].type;
if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') {
return value;
}
else if (paramType === 'int_or_interval' && _.isNumber(value)) {
return value;
}
return "'" + value + "'";
});
if (innerExpr) {
parameters.unshift(innerExpr);
}
return str + parameters.join(', ') + ')';
}
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.name + '()';
return;
}
var text = this.def.name + '(';
text += this.params.join(', ');
text += ')';
this.text = text;
}
}
export = {
create: function(part): any {
return new QueryPart(part);
},
getFuncDef: function(name) {
return index[name];
},
getCategories: function() {
return categories;
}
};

View File

@ -10,33 +10,26 @@ function (angular, _, $) {
.module('grafana.directives')
.directive('influxQueryPartEditor', function($compile, templateSrv) {
var funcSpanTemplate = '<a ng-click="">{{func.def.name}}</a><span>(</span>';
var paramTemplate = '<input type="text" style="display:none"' +
' class="input-mini tight-form-func-param"></input>';
var funcControlsTemplate =
'<div class="tight-form-func-controls">' +
'<span class="pointer fa fa-question-circle"></span>' +
'<span class="pointer fa fa-remove" ></span>' +
'</div>';
return {
restrict: 'A',
restrict: 'E',
templateUrl: 'app/plugins/datasource/influxdb/partials/query_part.html',
scope: {
part: "="
},
link: function postLink($scope, elem) {
var $funcLink = $(funcSpanTemplate);
var $funcControls = $(funcControlsTemplate);
var func = $scope.func;
var funcDef = func.def;
var scheduledRelink = false;
var paramCountAtLink = 0;
var part = $scope.part;
var partDef = part.def;
var $paramsContainer = elem.find('.query-part-parameters');
var $controlsContainer = elem.find('.tight-form-func-controls');
function clickFuncParam(paramIndex) {
/*jshint validthis:true */
var $link = $(this);
var $input = $link.next();
$input.val(func.params[paramIndex]);
$input.val(part.params[paramIndex]);
$input.css('width', ($link.width() + 16) + 'px');
$link.hide();
@ -51,32 +44,16 @@ function (angular, _, $) {
}
}
function scheduledRelinkIfNeeded() {
if (paramCountAtLink === func.params.length) {
return;
}
if (!scheduledRelink) {
scheduledRelink = true;
setTimeout(function() {
relink();
scheduledRelink = false;
}, 200);
}
}
function inputBlur(paramIndex) {
/*jshint validthis:true */
var $input = $(this);
var $link = $input.prev();
var newValue = $input.val();
if (newValue !== '' || func.def.params[paramIndex].optional) {
if (newValue !== '' || part.def.params[paramIndex].optional) {
$link.html(templateSrv.highlightVariablesAsHtml(newValue));
func.updateParam($input.val(), paramIndex);
scheduledRelinkIfNeeded();
part.updateParam($input.val(), paramIndex);
$scope.$apply($scope.targetChanged);
}
@ -99,8 +76,8 @@ function (angular, _, $) {
function addTypeahead($input, paramIndex) {
$input.attr('data-provide', 'typeahead');
var options = funcDef.params[paramIndex].options;
if (funcDef.params[paramIndex].type === 'int') {
var options = partDef.params[paramIndex].options;
if (partDef.params[paramIndex].type === 'int') {
options = _.map(options, function(val) { return val.toString(); });
}
@ -123,114 +100,52 @@ function (angular, _, $) {
};
}
function toggleFuncControls() {
$scope.toggleControls = function() {
var targetDiv = elem.closest('.tight-form');
if (elem.hasClass('show-function-controls')) {
elem.removeClass('show-function-controls');
targetDiv.removeClass('has-open-function');
$funcControls.hide();
$controlsContainer.hide();
return;
}
elem.addClass('show-function-controls');
targetDiv.addClass('has-open-function');
$funcControls.show();
}
$controlsContainer.show();
};
function addElementsAndCompile() {
$funcControls.appendTo(elem);
$funcLink.appendTo(elem);
_.each(funcDef.params, function(param, index) {
if (param.optional && func.params.length <= index) {
_.each(partDef.params, function(param, index) {
if (param.optional && part.params.length <= index) {
return;
}
if (index > 0) {
$('<span>, </span>').appendTo(elem);
$('<span>, </span>').appendTo($paramsContainer);
}
var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]);
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + paramValue + '</a>');
var paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]);
var $paramLink = $('<a class="graphite-func-param-link pointer">' + paramValue + '</a>');
var $input = $(paramTemplate);
paramCountAtLink++;
$paramLink.appendTo(elem);
$input.appendTo(elem);
$paramLink.appendTo($paramsContainer);
$input.appendTo($paramsContainer);
$input.blur(_.partial(inputBlur, index));
$input.keyup(inputKeyDown);
$input.keypress(_.partial(inputKeyPress, index));
$paramLink.click(_.partial(clickFuncParam, index));
if (funcDef.params[index].options) {
if (partDef.params[index].options) {
addTypeahead($input, index);
}
});
$('<span>)</span>').appendTo(elem);
$compile(elem.contents())($scope);
}
function ifJustAddedFocusFistParam() {
if ($scope.func.added) {
$scope.func.added = false;
setTimeout(function() {
elem.find('.graphite-func-param-link').first().click();
}, 10);
}
}
function registerFuncControlsToggle() {
$funcLink.click(toggleFuncControls);
}
function registerFuncControlsActions() {
$funcControls.click(function(e) {
var $target = $(e.target);
if ($target.hasClass('fa-remove')) {
toggleFuncControls();
$scope.$apply(function() {
$scope.removeFunction($scope.func);
});
return;
}
if ($target.hasClass('fa-arrow-left')) {
$scope.$apply(function() {
_.move($scope.functions, $scope.$index, $scope.$index - 1);
$scope.targetChanged();
});
return;
}
if ($target.hasClass('fa-arrow-right')) {
$scope.$apply(function() {
_.move($scope.functions, $scope.$index, $scope.$index + 1);
$scope.targetChanged();
});
return;
}
if ($target.hasClass('fa-question-circle')) {
window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank');
return;
}
});
}
function relink() {
elem.children().remove();
$paramsContainer.empty();
addElementsAndCompile();
ifJustAddedFocusFistParam();
registerFuncControlsToggle();
registerFuncControlsActions();
}
relink();