Merge branch 'new_func_editor'

This commit is contained in:
Torkel Ödegaard
2014-03-07 15:07:11 +01:00
9 changed files with 395 additions and 170 deletions

View File

@@ -222,24 +222,44 @@ function (angular, _, config, gfunc, Parser) {
$scope.targetChanged();
};
$scope.functionParamsChanged = function(func) {
func.updateText();
$scope.targetChanged();
$scope.addFunction = function(funcDef) {
var newFunc = gfunc.createFuncInstance(funcDef);
newFunc.added = true;
$scope.functions.push(newFunc);
$scope.moveAliasFuncLast();
$scope.smartlyHandleNewAliasByNode(newFunc);
if (!funcDef.params && newFunc.added) {
$scope.targetChanged();
}
};
$scope.addFunction = function(funcDef) {
$scope.functions.push(gfunc.createFuncInstance(funcDef));
$scope.moveAliasFuncLast = function() {
var aliasFunc = _.find($scope.functions, function(func) {
return func.def.name === 'alias';
return func.def.name === 'alias' ||
func.def.name === 'aliasByNode' ||
func.def.name === 'aliasByMetric';
});
if (aliasFunc) {
$scope.functions = _.without($scope.functions, aliasFunc);
$scope.functions.push(aliasFunc);
}
};
$scope.targetChanged();
$scope.smartlyHandleNewAliasByNode = function(func) {
if (func.def.name !== 'aliasByNode') {
return;
}
for(var i = 0; i < $scope.segments.length; i++) {
if ($scope.segments[i].val.indexOf('*') >= 0) {
func.params[0] = i;
func.added = false;
$scope.targetChanged();
return;
}
}
};
$scope.duplicate = function() {

View File

@@ -13,5 +13,6 @@ define([
'./grafanaGraph',
'./bootstrap-tagsinput',
'./bodyClass',
'./addGraphiteFunc'
'./addGraphiteFunc',
'./graphiteFuncEditor'
], function () {});

View File

@@ -0,0 +1,216 @@
define([
'angular',
'underscore',
'jquery',
],
function (angular, _, $) {
'use strict';
angular
.module('kibana.directives')
.directive('graphiteFuncEditor', function($compile) {
var funcSpanTemplate = '<a ng-click="">{{func.def.name}}</a><span>(</span>';
var paramTemplate = '<input type="text" style="display:none"' +
' class="input-mini grafana-function-param-input"></input>';
var funcControlsTemplate =
'<div class="graphite-func-controls">' +
'<span class="pointer icon-arrow-left"></span>' +
'<span class="pointer icon-info-sign"></span>' +
'<span class="pointer icon-remove" ></span>' +
'<span class="pointer icon-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;
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 inputBlur(paramIndex) {
/*jshint validthis:true */
var $input = $(this);
var $link = $input.prev();
if ($input.val() !== '') {
$link.text($input.val());
if (func.updateParam($input.val(), paramIndex)) {
$scope.$apply(function() {
$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('.grafana-target-inner');
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) {
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + func.params[index] + '</a>');
var $input = $(paramTemplate);
$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 (index !== funcDef.params.length - 1) {
$('<span>, </span>').appendTo(elem);
}
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('icon-remove')) {
toggleFuncControls();
$scope.$apply(function() {
$scope.removeFunction($scope.func);
});
return;
}
if ($target.hasClass('icon-arrow-left')) {
$scope.$apply(function() {
_.move($scope.functions, $scope.$index, $scope.$index - 1);
});
return;
}
if ($target.hasClass('icon-arrow-right')) {
$scope.$apply(function() {
_.move($scope.functions, $scope.$index, $scope.$index + 1);
});
return;
}
if ($target.hasClass('icon-info-sign')) {
window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank');
return;
}
});
}
addElementsAndCompile();
ifJustAddedFocusFistParam();
registerFuncControlsToggle();
registerFuncControlsActions();
}
};
});
});

View File

@@ -7,88 +7,99 @@
ng-controller="GraphiteTargetCtrl"
ng-init="init()">
<div class="grafana-target-inner-wrapper">
<div class="grafana-target-inner">
<ul class="grafana-target-controls">
<li ng-show="parserError">
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
<i class="icon-warning-sign"></i>
</a>
</li>
<li>
<a class="pointer" tabindex="1" ng-click="showTextEditor = !showTextEditor">
<i class="icon-pencil"></i>
</a>
</li>
<li class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="icon-cog"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1"
ng-click="duplicate()">
Duplicate
</a>
</li>
</ul>
</li>
<li>
<a class="pointer" tabindex="1" ng-click="removeTarget(target)">
<i class="icon-remove"></i>
</a>
</li>
</ul>
<div class="grafana-target-inner">
<ul class="grafana-target-controls">
<li ng-show="parserError">
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
<i class="icon-warning-sign"></i>
</a>
</li>
<li>
<a class="pointer" tabindex="1" ng-click="showTextEditor = !showTextEditor">
<i class="icon-pencil"></i>
</a>
</li>
<li class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="icon-cog"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1"
ng-click="duplicate()">
Duplicate
</a>
</li>
</ul>
</li>
<li>
<a class="pointer" tabindex="1" ng-click="removeTarget(target)">
<i class="icon-remove"></i>
</a>
</li>
</ul>
<ul class="grafana-target-controls-left">
<li>
<a class="grafana-target-segment"
ng-click="target.hide = !target.hide; get_data();"
role="menuitem">
<i class="icon-eye-open"></i>
</a>
</li>
</ul>
<ul class="grafana-target-controls-left">
<li>
<a class="grafana-target-segment"
ng-click="target.hide = !target.hide; get_data();"
role="menuitem">
<i class="icon-eye-open"></i>
</a>
</li>
</ul>
<input type="text"
class="grafana-target-text-input span10"
ng-model="target.target"
focus-me="showTextEditor"
spellcheck='false'
ng-model-onblur ng-change="targetTextChanged()"
ng-show="showTextEditor" />
<input type="text"
class="grafana-target-text-input span10"
ng-model="target.target"
focus-me="showTextEditor"
spellcheck='false'
ng-model-onblur ng-change="targetTextChanged()"
ng-show="showTextEditor" />
<ul class="grafana-segment-list" role="menu" ng-hide="showTextEditor">
<li class="dropdown" ng-repeat="segment in segments" role="menuitem">
<a tabindex="1"
class="grafana-target-segment dropdown-toggle"
data-toggle="dropdown"
ng-click="getAltSegments($index)"
focus-me="segment.focus"
ng-bind-html-unsafe="segment.html">
</a>
<ul class="dropdown-menu scrollable grafana-segment-dropdown-menu" role="menu">
<li ng-repeat="altSegment in altSegments" role="menuitem">
<a href="javascript:void(0)" tabindex="1" ng-click="setSegment($index, $parent.$index)" ng-bind-html-unsafe="altSegment.html"></a>
</li>
</ul>
</li>
<li ng-repeat="func in functions">
<a class="grafana-target-segment grafana-target-function dropdown-toggle" bs-popover="'app/partials/graphite/funcEditor.html'" data-placement="bottom">
{{func.text}}
</a>
</li>
<li class="dropdown" graphite-add-func>
<ul class="grafana-segment-list" role="menu" ng-hide="showTextEditor">
<li class="dropdown" ng-repeat="segment in segments" role="menuitem">
<a tabindex="1"
class="grafana-target-segment dropdown-toggle"
data-toggle="dropdown"
ng-click="getAltSegments($index)"
focus-me="segment.focus"
ng-bind-html-unsafe="segment.html">
</a>
<ul class="dropdown-menu scrollable grafana-segment-dropdown-menu" role="menu">
<li ng-repeat="altSegment in altSegments" role="menuitem">
<a href="javascript:void(0)" tabindex="1" ng-click="setSegment($index, $parent.$index)" ng-bind-html-unsafe="altSegment.html"></a>
</li>
</ul>
</li>
<li ng-repeat="func in functions">
<span graphite-func-editor class="grafana-target-segment grafana-target-function">
</span>
<!-- <a class="grafana-target-segment grafana-target-function dropdown-toggle"
bs-popover="'app/partials/graphite/funcEditor.html'"
data-placement="bottom">
{{func.def.name}}
</a> -->
<!-- <span class="grafana-target-segment grafana-target-function">
<span>{{func.def.name}}(</span><span ng-repeat="param in func.def.params">
<input type="text"
class="input-mini grafana-function-param-input"
dynamic-width
ng-model="func.params[$index]"></input>
</span><span>)</span>
</span> -->
</li>
<li class="dropdown" graphite-add-func>
</li>
</ul>
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="clearfix"></div>
</div>
</div>
</div>

View File

@@ -1,51 +0,0 @@
<div class="grafana-func-editor">
<div class="grafana-func-editor-header">
<a ng-click="removeFunction(func)">
Remove
</a>
&nbsp;&nbsp;
<a ng-click="helpFunction(func)">
Help
</a>
&nbsp;&nbsp;
<a class="close" ng-click="dismiss();" href="">×</a>
</div>
<div class="editor-row" ng-if="func.def.params.length">
<div class="section">
<div class="editor-option" ng-repeat="param in func.def.params">
<label class="small">{{param.name}}</label>
<div ng-switch on="param.type">
<div ng-switch-when="int">
<input
type="number"
step="any"
focus-me="true"
class="input-mini"
ng-change="functionParamsChanged(func)" ng-model-onblur
ng-model="func.params[$index]" />
</div>
<div ng-switch-when="string">
<input
type="text"
focus-me="true"
class="input-small"
ng-change="functionParamsChanged(func)" ng-model-onblur
ng-model="func.params[$index]" />
</div>
<div ng-switch-when="select">
<select
class="input-mini"
ng-model="func.params[$index]"
ng-change="functionParamsChanged(func)"
focus-me="true"
ng-options="f for f in param.options">
</select>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -89,12 +89,12 @@ function (_) {
params: [
{
name: "node",
type: "select",
type: "int",
options: [1,2,3,4,5,6,7,8,9,10,12]
},
{
name: "function",
type: "select",
type: "string",
options: ['sum', 'avg']
}
],
@@ -104,7 +104,7 @@ function (_) {
addFuncDef({
name: 'aliasByNode',
category: categories.Special,
params: [ { name: "node", type: "select", options: [0,1,2,3,4,5,6,7,8,9,10,12] } ],
params: [ { name: "node", type: "int", options: [0,1,2,3,4,5,6,7,8,9,10,12] } ],
defaultParams: [3]
});
@@ -211,14 +211,14 @@ function (_) {
category: categories.Filter,
params: [ { name: "n", type: "int", } ],
defaultParams: [25]
});
});
addFuncDef({
name: 'currentBelow',
category: categories.Filter,
params: [ { name: "n", type: "int", } ],
defaultParams: [25]
});
});
addFuncDef({
name: "exclude",
@@ -279,7 +279,7 @@ function (_) {
this.updateText();
}
FuncInstance.prototype.render = function (metricExp) {
FuncInstance.prototype.render = function(metricExp) {
var str = this.def.name + '(';
var parameters = _.map(this.params, function(value) {
return _.isString(value) ? "'" + value + "'" : value;
@@ -292,6 +292,21 @@ function (_) {
return str + parameters.join(',') + ')';
};
FuncInstance.prototype.updateParam = function(strValue, index) {
var oldValue = this.params[index];
if (this.def.params[index].type === 'int') {
this.params[index] = parseInt(strValue, 10);
}
else {
this.params[index] = strValue;
}
this.updateText();
return oldValue !== this.params[index];
};
FuncInstance.prototype.updateText = function () {
if (this.params.length === 0) {
this.text = this.def.name + '()';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -221,10 +221,6 @@
border-bottom: 1px solid @grafanaTargetBorder;
}
.grafana-target-inner-wrapper {
width: 100%;
}
.grafana-target-inner {
border-top: 1px solid @grafanaTargetBorder;
border-left: 1px solid @grafanaTargetBorder;
@@ -262,6 +258,10 @@
color: @grafanaTargetColor;
display: inline-block;
.has-open-function & {
padding-top: 25px;
}
.grafana-target-hidden & {
color: @grafanaTargetColorHide;
}
@@ -269,16 +269,32 @@
&:hover, &:focus {
text-decoration: none;
}
&:hover {
&a:hover {
background: @grafanaTargetFuncBackground;
}
}
.grafana-target-function {
background: @grafanaTargetFuncBackground;
&:hover {
background: @grafanaTargetFuncHightlight;
> a {
color: @grafanaTargetColor;
}
> a:hover {
color: @linkColor;
}
&.show-function-controls {
padding-top: 5px;
min-width: 100px;
text-align: center;
}
}
input[type=text].grafana-function-param-input {
background: transparent;
border: none;
margin: 0;
padding: 0;
}
.grafana-target-controls-left {
@@ -348,13 +364,23 @@ select.grafana-target-segment-input {
padding: 0; margin: 0;
}
input[type=text].func-param {
border: none;
background: inherit;
width: 30px;
border-radius: none;
padding: 0;
margin: 0;
.graphite-func-controls {
display: none;
text-align: center;
.icon-arrow-left {
float: left;
position: relative;
top: 2px;
}
.icon-arrow-right {
float: right;
position: relative;
top: 2px;
}
.icon-remove {
margin-left: 10px;
}
}
.grafana-target {
@@ -363,19 +389,6 @@ input[type=text].func-param {
}
}
.grafana-func-editor {
min-width: 140px;
.grafana-func-editor-header {
background: @grafanaTargetFuncHightlight;
text-align: center;
border-bottom: 1px solid @grafanaTargetFuncBackground;
padding: 3px 5px;
white-space: nowrap;
}
.editor-row {
padding: 5px;
}
}
.scrollable {
max-height: 300px;