diff --git a/src/app/directives/graphiteFuncEditor.js b/src/app/directives/graphiteFuncEditor.js index 10516f1ca7c..7767d097c04 100644 --- a/src/app/directives/graphiteFuncEditor.js +++ b/src/app/directives/graphiteFuncEditor.js @@ -29,6 +29,8 @@ function (angular, _, $) { var $funcControls = $(funcControlsTemplate); var func = $scope.func; var funcDef = func.def; + var scheduledRelink = false; + var paramCountAtLink = 0; function clickFuncParam(paramIndex) { /*jshint validthis:true */ @@ -51,17 +53,33 @@ 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(); - if ($input.val() !== '') { + if ($input.val() !== '' || func.def.params[paramIndex].optional) { $link.text($input.val()); - + func.updateParam($input.val(), paramIndex); - $scope.$apply($scope.targetChanged); + scheduledRelinkIfNeeded(); + + $scope.$apply($scope.targetChanged); } $input.hide(); @@ -129,9 +147,19 @@ function (angular, _, $) { $funcLink.appendTo(elem); _.each(funcDef.params, function(param, index) { + if (param.optional && !func.params[index]) { + return; + } + + if (index > 0) { + $(', ').appendTo(elem); + } + var $paramLink = $('' + func.params[index] + ''); var $input = $(paramTemplate); + paramCountAtLink++; + $paramLink.appendTo(elem); $input.appendTo(elem); @@ -140,10 +168,6 @@ function (angular, _, $) { $input.keypress(_.partial(inputKeyPress, index)); $paramLink.click(_.partial(clickFuncParam, index)); - if (index !== funcDef.params.length - 1) { - $(', ').appendTo(elem); - } - if (funcDef.params[index].options) { addTypeahead($input, index); } @@ -200,10 +224,16 @@ function (angular, _, $) { }); } - addElementsAndCompile(); - ifJustAddedFocusFistParam(); - registerFuncControlsToggle(); - registerFuncControlsActions(); + function relink() { + elem.children().remove(); + + addElementsAndCompile(); + ifJustAddedFocusFistParam(); + registerFuncControlsToggle(); + registerFuncControlsActions(); + } + + relink(); } }; diff --git a/src/app/services/graphite/gfunc.js b/src/app/services/graphite/gfunc.js index 9902e7c15d6..dadfb736e93 100644 --- a/src/app/services/graphite/gfunc.js +++ b/src/app/services/graphite/gfunc.js @@ -132,7 +132,10 @@ function (_) { addFuncDef({ name: 'aliasByNode', category: categories.Special, - params: [ { name: "node", type: "int", 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] }, + { name: "node", type: "int", options: [0,-1,-2,-3,-4,-5,-6,-7], optional: true }, + ], defaultParams: [3] }); @@ -340,13 +343,33 @@ function (_) { return str + parameters.join(',') + ')'; }; - FuncInstance.prototype.updateParam = function(strValue, index) { - if (this.def.params[index].type === 'int') { + 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 if (this.def.params[index].type === 'int') { this.params[index] = parseInt(strValue, 10); } else { this.params[index] = strValue; - } + } this.updateText(); }; @@ -359,6 +382,10 @@ function (_) { var text = this.def.name + '('; _.each(this.def.params, function(param, index) { + if (param.optional && this.params[index] === undefined) { + return; + } + text += this.params[index] + ', '; }, this); text = text.substring(0, text.length - 2); diff --git a/src/test/specs/gfunc-specs.js b/src/test/specs/gfunc-specs.js index b66c05fe08c..942f098fdea 100644 --- a/src/test/specs/gfunc-specs.js +++ b/src/test/specs/gfunc-specs.js @@ -57,12 +57,47 @@ define([ }); describe('when requesting function categories', function() { - it('should return function categories', function() { var catIndex = gfunc.getCategories(); expect(catIndex.Special.length).to.be.greaterThan(8); }); + }); + + describe('when updating func param', function() { + it('should update param value and update text representation', function() { + var func = gfunc.createFuncInstance('summarize'); + func.updateParam('1h', 0); + expect(func.params[0]).to.be('1h'); + expect(func.text).to.be('summarize(1h, sum)'); + }); + }); + describe('when updating func param with optional second parameter', function() { + it('should update value and text', function() { + var func = gfunc.createFuncInstance('aliasByNode'); + func.updateParam('1', 0); + expect(func.params[0]).to.be(1); + }); + + it('should slit text and put value in second param', function() { + var func = gfunc.createFuncInstance('aliasByNode'); + func.updateParam('4,-5', 0); + expect(func.params[0]).to.be(4); + expect(func.params[1]).to.be(-5); + expect(func.text).to.be('aliasByNode(4, -5)'); + }); + + it('should remove second param when empty string is set', function() { + var func = gfunc.createFuncInstance('aliasByNode'); + func.updateParam('4,-5', 0); + func.updateParam('', 1); + expect(func.params[0]).to.be(4); + expect(func.params[1]).to.be(undefined); + expect(func.text).to.be('aliasByNode(4)'); + }); + }); + }); +