mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
poc: influxdb editor v3 test
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<div class="tight-form-container-no-item-borders">
|
||||
<div class="">
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list pull-right">
|
||||
<li ng-show="parserError" class="tight-form-item">
|
||||
@@ -48,18 +48,7 @@
|
||||
<li>
|
||||
<metric-segment segment="measurementSegment" get-options="getMeasurements()" on-change="measurementChanged()"></metric-segment>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div style="padding: 10px" ng-if="target.rawQuery">
|
||||
<textarea ng-model="target.query" rows="8" spellcheck="false" style="width: 100%; box-sizing: border-box;" ng-blur="get_data()"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-hide="target.rawQuery">
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
|
||||
<li class="tight-form-item query-keyword" style="padding-left: 15px; padding-right: 15px;">
|
||||
WHERE
|
||||
</li>
|
||||
<li ng-repeat="segment in tagSegments">
|
||||
@@ -67,27 +56,26 @@
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div style="padding: 10px" ng-if="target.rawQuery">
|
||||
<textarea ng-model="target.query" rows="8" spellcheck="false" style="width: 100%; box-sizing: border-box;" ng-blur="get_data()"></textarea>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-hide="target.rawQuery">
|
||||
|
||||
<div class="tight-form" ng-repeat="field in target.fields">
|
||||
<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>
|
||||
<metric-segment-model property="field.func" get-options="getFunctions()" on-change="get_data()" css-class="tight-form-item-xlarge"></metric-segment>
|
||||
<metric-segment-model property="field.name" get-options="getFields()" on-change="get_data()"></metric-segment>
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment-model property="field.name" get-options="getFields()" on-change="get_data()" css-class="tight-form-item-large"></metric-segment>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="tight-form-clear-input text-center" style="width: 70px;" ng-model="field.mathExpr" spellcheck='false' placeholder="math expr" ng-blur="get_data()">
|
||||
</li>
|
||||
<li class="tight-form-item query-keyword">
|
||||
AS
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="tight-form-clear-input" style="width: 180px;" ng-model="field.asExpr" spellcheck='false' placeholder="as expr" ng-blur="get_data()">
|
||||
<li ng-repeat="func in field.parts">
|
||||
<span influx-query-part-editor class="tight-form-item tight-form-func">
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -108,30 +96,29 @@
|
||||
<span ng-show="$index === 0">GROUP BY</span>
|
||||
</li>
|
||||
<li ng-if="groupBy.type === 'time'">
|
||||
<span class="tight-form-item">time</span>
|
||||
<metric-segment-model property="groupBy.interval" get-options="getGroupByTimeIntervals()" on-change="get_data()">
|
||||
</metric-segment>
|
||||
</li>
|
||||
<li class="dropdown" ng-if="groupBy.type === 'time'">
|
||||
<a class="tight-form-item pointer" data-toggle="dropdown" bs-tooltip="'Insert missing values, important when stacking'" data-placement="right">
|
||||
<span ng-show="target.fill">
|
||||
fill ({{target.fill}})
|
||||
<span influx-query-part-editor class="tight-form-item tight-form-func">
|
||||
</span>
|
||||
<span ng-show="!target.fill">
|
||||
no fill
|
||||
</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a ng-click="setFill('')">no fill</a></li>
|
||||
<li><a ng-click="setFill('0')">fill (0)</a></li>
|
||||
<li><a ng-click="setFill('null')">fill (null)</a></li>
|
||||
<li><a ng-click="setFill('none')">fill (none)</a></li>
|
||||
<li><a ng-click="setFill('previous')">fill (previous)</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li ng-if="groupBy.type === 'tag'">
|
||||
<metric-segment-model property="groupBy.key" get-options="getTagOptions()" on-change="get_data()"></metric-segment>
|
||||
</li>
|
||||
<!-- <li class="dropdown" ng-if="groupBy.type === 'time'"> -->
|
||||
<!-- <a class="tight-form-item pointer" data-toggle="dropdown" bs-tooltip="'Insert missing values, important when stacking'" data-placement="right"> -->
|
||||
<!-- <span ng-show="target.fill"> -->
|
||||
<!-- fill ({{target.fill}}) -->
|
||||
<!-- </span> -->
|
||||
<!-- <span ng-show="!target.fill"> -->
|
||||
<!-- no fill -->
|
||||
<!-- </span> -->
|
||||
<!-- </a> -->
|
||||
<!-- <ul class="dropdown-menu"> -->
|
||||
<!-- <li><a ng-click="setFill('')">no fill</a></li> -->
|
||||
<!-- <li><a ng-click="setFill('0')">fill (0)</a></li> -->
|
||||
<!-- <li><a ng-click="setFill('null')">fill (null)</a></li> -->
|
||||
<!-- <li><a ng-click="setFill('none')">fill (none)</a></li> -->
|
||||
<!-- <li><a ng-click="setFill('previous')">fill (previous)</a></li> -->
|
||||
<!-- </ul> -->
|
||||
<!-- </li> -->
|
||||
<!-- <li ng-if="groupBy.type === 'tag'"> -->
|
||||
<!-- <metric-segment-model property="groupBy.key" get-options="getTagOptions()" on-change="get_data()"></metric-segment> -->
|
||||
<!-- </li> -->
|
||||
</ul>
|
||||
|
||||
<ul class="tight-form-list pull-right">
|
||||
@@ -146,6 +133,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
|
||||
|
||||
@@ -2,8 +2,10 @@ define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'./query_builder',
|
||||
'./query_part',
|
||||
'./query_part_editor',
|
||||
],
|
||||
function (angular, _, InfluxQueryBuilder) {
|
||||
function (angular, _, InfluxQueryBuilder, queryPart) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
@@ -17,6 +19,14 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
target.tags = target.tags || [];
|
||||
target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}];
|
||||
target.fields = target.fields || [{name: 'value', func: target.function || 'mean'}];
|
||||
target.fields[0].parts = [
|
||||
queryPart.create('mean', { withDefaultParams: true }),
|
||||
queryPart.create('derivate', { withDefaultParams: true }),
|
||||
queryPart.create('math', { withDefaultParams: true }),
|
||||
queryPart.create('alias', { withDefaultParams: true }),
|
||||
];
|
||||
|
||||
$scope.func = queryPart.create('time', { withDefaultParams: true });
|
||||
|
||||
$scope.queryBuilder = new InfluxQueryBuilder(target);
|
||||
|
||||
|
||||
161
public/app/plugins/datasource/influxdb/query_part.js
Normal file
161
public/app/plugins/datasource/influxdb/query_part.js
Normal file
@@ -0,0 +1,161 @@
|
||||
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: '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;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
244
public/app/plugins/datasource/influxdb/query_part_editor.js
Normal file
244
public/app/plugins/datasource/influxdb/query_part_editor.js
Normal file
@@ -0,0 +1,244 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'jquery',
|
||||
],
|
||||
function (angular, _, $) {
|
||||
'use strict';
|
||||
|
||||
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-arrow-left"></span>' +
|
||||
'<span class="pointer fa fa-question-circle"></span>' +
|
||||
'<span class="pointer fa fa-remove" ></span>' +
|
||||
'<span class="pointer fa fa-arrow-right"></span>' +
|
||||
'</div>';
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
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;
|
||||
|
||||
function clickFuncParam(paramIndex) {
|
||||
/*jshint validthis:true */
|
||||
|
||||
var $link = $(this);
|
||||
var $input = $link.next();
|
||||
|
||||
$input.val(func.params[paramIndex]);
|
||||
$input.css('width', ($link.width() + 16) + 'px');
|
||||
|
||||
$link.hide();
|
||||
$input.show();
|
||||
$input.focus();
|
||||
$input.select();
|
||||
|
||||
var typeahead = $input.data('typeahead');
|
||||
if (typeahead) {
|
||||
$input.val('');
|
||||
typeahead.lookup();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
$link.html(templateSrv.highlightVariablesAsHtml(newValue));
|
||||
|
||||
func.updateParam($input.val(), paramIndex);
|
||||
scheduledRelinkIfNeeded();
|
||||
|
||||
$scope.$apply($scope.targetChanged);
|
||||
}
|
||||
|
||||
$input.hide();
|
||||
$link.show();
|
||||
}
|
||||
|
||||
function inputKeyPress(paramIndex, e) {
|
||||
/*jshint validthis:true */
|
||||
if(e.which === 13) {
|
||||
inputBlur.call(this, paramIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function inputKeyDown() {
|
||||
/*jshint validthis:true */
|
||||
this.style.width = (3 + this.value.length) * 8 + 'px';
|
||||
}
|
||||
|
||||
function addTypeahead($input, paramIndex) {
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
|
||||
var options = funcDef.params[paramIndex].options;
|
||||
if (funcDef.params[paramIndex].type === 'int') {
|
||||
options = _.map(options, function(val) { return val.toString(); });
|
||||
}
|
||||
|
||||
$input.typeahead({
|
||||
source: options,
|
||||
minLength: 0,
|
||||
items: 20,
|
||||
updater: function (value) {
|
||||
setTimeout(function() {
|
||||
inputBlur.call($input[0], paramIndex);
|
||||
}, 0);
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
var typeahead = $input.data('typeahead');
|
||||
typeahead.lookup = function () {
|
||||
this.query = this.$element.val() || '';
|
||||
return this.process(this.source);
|
||||
};
|
||||
}
|
||||
|
||||
function toggleFuncControls() {
|
||||
var targetDiv = elem.closest('.tight-form');
|
||||
|
||||
if (elem.hasClass('show-function-controls')) {
|
||||
elem.removeClass('show-function-controls');
|
||||
targetDiv.removeClass('has-open-function');
|
||||
$funcControls.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
elem.addClass('show-function-controls');
|
||||
targetDiv.addClass('has-open-function');
|
||||
|
||||
$funcControls.show();
|
||||
}
|
||||
|
||||
function addElementsAndCompile() {
|
||||
$funcControls.appendTo(elem);
|
||||
$funcLink.appendTo(elem);
|
||||
|
||||
_.each(funcDef.params, function(param, index) {
|
||||
if (param.optional && func.params.length <= index) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
$('<span>, </span>').appendTo(elem);
|
||||
}
|
||||
|
||||
var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]);
|
||||
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + paramValue + '</a>');
|
||||
var $input = $(paramTemplate);
|
||||
|
||||
paramCountAtLink++;
|
||||
|
||||
$paramLink.appendTo(elem);
|
||||
$input.appendTo(elem);
|
||||
|
||||
$input.blur(_.partial(inputBlur, index));
|
||||
$input.keyup(inputKeyDown);
|
||||
$input.keypress(_.partial(inputKeyPress, index));
|
||||
$paramLink.click(_.partial(clickFuncParam, index));
|
||||
|
||||
if (funcDef.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();
|
||||
|
||||
addElementsAndCompile();
|
||||
ifJustAddedFocusFistParam();
|
||||
registerFuncControlsToggle();
|
||||
registerFuncControlsActions();
|
||||
}
|
||||
|
||||
relink();
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user