mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
add sql_part component
This commit is contained in:
118
public/app/core/components/sql_part/sql_part.ts
Normal file
118
public/app/core/components/sql_part/sql_part.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export class SqlPartDef {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export class SqlPart {
|
||||
part: any;
|
||||
def: SqlPartDef;
|
||||
params: any[];
|
||||
text: string;
|
||||
|
||||
constructor(part: any, def: any) {
|
||||
this.part = part;
|
||||
this.def = def;
|
||||
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(','), (partVal, idx) => {
|
||||
this.updateParam(partVal.trim(), idx);
|
||||
});
|
||||
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 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(', ') + ')';
|
||||
}
|
||||
|
||||
export function suffixRenderer(part, innerExpr) {
|
||||
return innerExpr + ' ' + part.params[0];
|
||||
}
|
||||
|
||||
export function identityRenderer(part, innerExpr) {
|
||||
return part.params[0];
|
||||
}
|
||||
|
||||
export function quotedIdentityRenderer(part, innerExpr) {
|
||||
return '"' + part.params[0] + '"';
|
||||
}
|
||||
185
public/app/core/components/sql_part/sql_part_editor.ts
Normal file
185
public/app/core/components/sql_part/sql_part_editor.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
var template = `
|
||||
<div class="dropdown cascade-open">
|
||||
<a ng-click="showActionsMenu()" class="query-part-name pointer dropdown-toggle" data-toggle="dropdown">{{part.def.type}}</a>
|
||||
<span>(</span><span class="query-part-parameters"></span><span>)</span>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="action in partActions">
|
||||
<a ng-click="triggerPartAction(action)">{{action.text}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
|
||||
/** @ngInject */
|
||||
export function sqlPartEditorDirective($compile, templateSrv) {
|
||||
var paramTemplate = '<input type="text" class="hide input-mini tight-form-func-param"></input>';
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: template,
|
||||
scope: {
|
||||
part: '=',
|
||||
handleEvent: '&',
|
||||
debounce: '@',
|
||||
},
|
||||
link: function postLink($scope, elem) {
|
||||
var part = $scope.part;
|
||||
var partDef = part.def;
|
||||
var $paramsContainer = elem.find('.query-part-parameters');
|
||||
var debounceLookup = $scope.debounce;
|
||||
|
||||
$scope.partActions = [];
|
||||
|
||||
function clickFuncParam(paramIndex) {
|
||||
/*jshint validthis:true */
|
||||
var $link = $(this);
|
||||
var $input = $link.next();
|
||||
|
||||
$input.val(part.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 inputBlur(paramIndex) {
|
||||
/*jshint validthis:true */
|
||||
var $input = $(this);
|
||||
var $link = $input.prev();
|
||||
var newValue = $input.val();
|
||||
|
||||
if (newValue !== '' || part.def.params[paramIndex].optional) {
|
||||
$link.html(templateSrv.highlightVariablesAsHtml(newValue));
|
||||
|
||||
part.updateParam($input.val(), paramIndex);
|
||||
$scope.$apply(() => {
|
||||
$scope.handleEvent({ $event: { name: 'part-param-changed' } });
|
||||
});
|
||||
}
|
||||
|
||||
$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, param, paramIndex) {
|
||||
if (!param.options && !param.dynamicLookup) {
|
||||
return;
|
||||
}
|
||||
|
||||
var typeaheadSource = function(query, callback) {
|
||||
if (param.options) {
|
||||
var options = param.options;
|
||||
if (param.type === 'int') {
|
||||
options = _.map(options, function(val) {
|
||||
return val.toString();
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
$scope.$apply(function() {
|
||||
$scope.handleEvent({ $event: { name: 'get-param-options' } }).then(function(result) {
|
||||
var dynamicOptions = _.map(result, function(op) {
|
||||
return op.value;
|
||||
});
|
||||
callback(dynamicOptions);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
|
||||
$input.typeahead({
|
||||
source: typeaheadSource,
|
||||
minLength: 0,
|
||||
items: 1000,
|
||||
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() || '';
|
||||
var items = this.source(this.query, $.proxy(this.process, this));
|
||||
return items ? this.process(items) : items;
|
||||
};
|
||||
|
||||
if (debounceLookup) {
|
||||
typeahead.lookup = _.debounce(typeahead.lookup, 500, { leading: true });
|
||||
}
|
||||
}
|
||||
|
||||
$scope.showActionsMenu = function() {
|
||||
$scope.handleEvent({ $event: { name: 'get-part-actions' } }).then(res => {
|
||||
$scope.partActions = res;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.triggerPartAction = function(action) {
|
||||
$scope.handleEvent({ $event: { name: 'action', action: action } });
|
||||
};
|
||||
|
||||
function addElementsAndCompile() {
|
||||
_.each(partDef.params, function(param, index) {
|
||||
if (param.optional && part.params.length <= index) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
$('<span>, </span>').appendTo($paramsContainer);
|
||||
}
|
||||
|
||||
var paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]);
|
||||
var $paramLink = $('<a class="graphite-func-param-link pointer">' + paramValue + '</a>');
|
||||
var $input = $(paramTemplate);
|
||||
|
||||
$paramLink.appendTo($paramsContainer);
|
||||
$input.appendTo($paramsContainer);
|
||||
|
||||
$input.blur(_.partial(inputBlur, index));
|
||||
$input.keyup(inputKeyDown);
|
||||
$input.keypress(_.partial(inputKeyPress, index));
|
||||
$paramLink.click(_.partial(clickFuncParam, index));
|
||||
|
||||
addTypeahead($input, param, index);
|
||||
});
|
||||
}
|
||||
|
||||
function relink() {
|
||||
$paramsContainer.empty();
|
||||
addElementsAndCompile();
|
||||
}
|
||||
|
||||
relink();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('sqlPartEditor', sqlPartEditorDirective);
|
||||
@@ -31,6 +31,7 @@ import { layoutSelector } from './components/layout_selector/layout_selector';
|
||||
import { switchDirective } from './components/switch';
|
||||
import { dashboardSelector } from './components/dashboard_selector';
|
||||
import { queryPartEditorDirective } from './components/query_part/query_part_editor';
|
||||
import { sqlPartEditorDirective } from './components/sql_part/sql_part_editor';
|
||||
import { formDropdownDirective } from './components/form_dropdown/form_dropdown';
|
||||
import 'app/core/controllers/all';
|
||||
import 'app/core/services/all';
|
||||
@@ -74,6 +75,7 @@ export {
|
||||
appEvents,
|
||||
dashboardSelector,
|
||||
queryPartEditorDirective,
|
||||
sqlPartEditorDirective,
|
||||
colors,
|
||||
formDropdownDirective,
|
||||
assignModelProperties,
|
||||
|
||||
Reference in New Issue
Block a user