Commit adfa341d authored by Torkel Ödegaard's avatar Torkel Ödegaard
Browse files

Merge branch 'new_func_editor'

parents 1c72e0c4 0b03ca15
......@@ -222,24 +222,44 @@ function (angular, _, config, gfunc, Parser) {
$scope.targetChanged();
};
$scope.functionParamsChanged = function(func) {
func.updateText();
$scope.targetChanged();
};
$scope.addFunction = function(funcDef) {
$scope.functions.push(gfunc.createFuncInstance(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.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() {
......
......@@ -13,5 +13,6 @@ define([
'./grafanaGraph',
'./bootstrap-tagsinput',
'./bodyClass',
'./addGraphiteFunc'
'./addGraphiteFunc',
'./graphiteFuncEditor'
], function () {});
\ No newline at end of file
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();
}
};
});
});
\ No newline at end of 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>
<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>
\ No newline at end of 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 + '()';
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -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,18 +269,34 @@
&:hover, &:focus {
text-decoration: none;
}
&:hover {
&a:hover {