Commit d8b86a89 by Bruce Sherrod

Merge remote-tracking branch 'remotes/torkelo/master'

parents 6d981afc ed9e336c
# 1.5.2 (2013-03-24)
### New Features and improvements
- Support for second optional params for functions like aliasByNode (Issue #167). Read the wiki on the [Function Editor](https://github.com/torkelo/grafana/wiki/Graphite-Function-Editor) for more info.
- More functions added to InfluxDB query editor (Issue #218)
- Filters can now be used inside other filters (templated segments) (Issue #128)
- More graphite functions added
### Fixes
- Float arguments now work for functions like scale (Issue #223)
- Fix for graphite function editor, the graph & target was not updated after adding a function and leaving default params as is #191
The zip files now contains a sub folder with project name and version prefix. (Issue #209)
# 1.5.1 (2013-03-10)
### Fixes
- maxDataPoints must be an integer #184 (thanks @frejsoya for fixing this)
For people who are find Grafana slow for large time spans or high resolution metrics. This is most likely due to graphite returning a large number of datapoints. The maxDataPoints parameter solves this issue. For maxDataPoints to work you need to run the latest graphite-web (some builds of 0.9.12 does not include this feature).
Read this for more info:
[Performance for large time spans](https://github.com/torkelo/grafana/wiki/Performance-for-large-time-spans)
# 1.5.0 (2013-03-09) # 1.5.0 (2013-03-09)
###New Features and improvements ### New Features and improvements
- New function editor [video demo](http://youtu.be/I90WHRwE1ZM) (Issue #178) - New function editor [video demo](http://youtu.be/I90WHRwE1ZM) (Issue #178)
- Links to function documentation from function editor (Issue #3) - Links to function documentation from function editor (Issue #3)
- Reorder functions (Issue #130) - Reorder functions (Issue #130)
...@@ -18,7 +40,7 @@ ...@@ -18,7 +40,7 @@
- Fix to annotations with graphite source & null values (Issue #138) - Fix to annotations with graphite source & null values (Issue #138)
# 1.4.0 (2013-02-21) # 1.4.0 (2013-02-21)
###New Features ### New Features
- #44 Annotations! Required a lot of work to get right. Read wiki article for more info. Supported annotations data sources are graphite metrics and graphite events. Support for more will be added in the future! - #44 Annotations! Required a lot of work to get right. Read wiki article for more info. Supported annotations data sources are graphite metrics and graphite events. Support for more will be added in the future!
- #35 Support for multiple graphite servers! (Read wiki article for more) - #35 Support for multiple graphite servers! (Read wiki article for more)
- #116 Back to dashboard link in top menu to easily exist full screen / edit mode. - #116 Back to dashboard link in top menu to easily exist full screen / edit mode.
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"company": "Coding Instinct AB" "company": "Coding Instinct AB"
}, },
"name": "grafana", "name": "grafana",
"version": "1.5.1", "version": "1.5.2",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "http://github.com/torkelo/grafana.git" "url": "http://github.com/torkelo/grafana.git"
......
...@@ -15,6 +15,7 @@ function (angular) { ...@@ -15,6 +15,7 @@ function (angular) {
$scope.target.function = 'mean'; $scope.target.function = 'mean';
} }
$scope.functions = ['count', 'mean', 'sum', 'min', 'max', 'mode', 'distinct', 'median', 'derivative', 'stddev', 'first', 'last'];
$scope.oldSeries = $scope.target.series; $scope.oldSeries = $scope.target.series;
$scope.$on('typeahead-updated', function(){ $scope.$on('typeahead-updated', function(){
$timeout($scope.get_data); $timeout($scope.get_data);
......
...@@ -327,8 +327,8 @@ function (angular, $, kbn, moment, _) { ...@@ -327,8 +327,8 @@ function (angular, $, kbn, moment, _) {
url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : ''; url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : '';
url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : ''; url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : '';
url += scope.panel.legend ? '' : '&hideLegend=true'; url += scope.panel.legend ? '' : '&hideLegend=true';
url += scope.panel.grid.min ? '&yMin=' + scope.panel.grid.min : ''; url += scope.panel.grid.min !== null ? '&yMin=' + scope.panel.grid.min : '';
url += scope.panel.grid.max ? '&yMax=' + scope.panel.grid.max : ''; url += scope.panel.grid.max !== null ? '&yMax=' + scope.panel.grid.max : '';
url += scope.panel['x-axis'] ? '' : '&hideAxes=true'; url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true'; url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
......
...@@ -29,6 +29,8 @@ function (angular, _, $) { ...@@ -29,6 +29,8 @@ function (angular, _, $) {
var $funcControls = $(funcControlsTemplate); var $funcControls = $(funcControlsTemplate);
var func = $scope.func; var func = $scope.func;
var funcDef = func.def; var funcDef = func.def;
var scheduledRelink = false;
var paramCountAtLink = 0;
function clickFuncParam(paramIndex) { function clickFuncParam(paramIndex) {
/*jshint validthis:true */ /*jshint validthis:true */
...@@ -51,16 +53,32 @@ function (angular, _, $) { ...@@ -51,16 +53,32 @@ function (angular, _, $) {
} }
} }
function scheduledRelinkIfNeeded() {
if (paramCountAtLink === func.params.length) {
return;
}
if (!scheduledRelink) {
scheduledRelink = true;
setTimeout(function() {
relink();
scheduledRelink = false;
}, 200);
}
}
function inputBlur(paramIndex) { function inputBlur(paramIndex) {
/*jshint validthis:true */ /*jshint validthis:true */
var $input = $(this); var $input = $(this);
var $link = $input.prev(); var $link = $input.prev();
if ($input.val() !== '') { if ($input.val() !== '' || func.def.params[paramIndex].optional) {
$link.text($input.val()); $link.text($input.val());
func.updateParam($input.val(), paramIndex); func.updateParam($input.val(), paramIndex);
scheduledRelinkIfNeeded();
$scope.$apply($scope.targetChanged); $scope.$apply($scope.targetChanged);
} }
...@@ -129,9 +147,19 @@ function (angular, _, $) { ...@@ -129,9 +147,19 @@ function (angular, _, $) {
$funcLink.appendTo(elem); $funcLink.appendTo(elem);
_.each(funcDef.params, function(param, index) { _.each(funcDef.params, function(param, index) {
if (param.optional && !func.params[index]) {
return;
}
if (index > 0) {
$('<span>, </span>').appendTo(elem);
}
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + func.params[index] + '</a>'); var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + func.params[index] + '</a>');
var $input = $(paramTemplate); var $input = $(paramTemplate);
paramCountAtLink++;
$paramLink.appendTo(elem); $paramLink.appendTo(elem);
$input.appendTo(elem); $input.appendTo(elem);
...@@ -140,10 +168,6 @@ function (angular, _, $) { ...@@ -140,10 +168,6 @@ function (angular, _, $) {
$input.keypress(_.partial(inputKeyPress, index)); $input.keypress(_.partial(inputKeyPress, index));
$paramLink.click(_.partial(clickFuncParam, index)); $paramLink.click(_.partial(clickFuncParam, index));
if (index !== funcDef.params.length - 1) {
$('<span>, </span>').appendTo(elem);
}
if (funcDef.params[index].options) { if (funcDef.params[index].options) {
addTypeahead($input, index); addTypeahead($input, index);
} }
...@@ -200,11 +224,17 @@ function (angular, _, $) { ...@@ -200,11 +224,17 @@ function (angular, _, $) {
}); });
} }
function relink() {
elem.children().remove();
addElementsAndCompile(); addElementsAndCompile();
ifJustAddedFocusFistParam(); ifJustAddedFocusFistParam();
registerFuncControlsToggle(); registerFuncControlsToggle();
registerFuncControlsActions(); registerFuncControlsActions();
} }
relink();
}
}; };
}); });
......
...@@ -74,7 +74,11 @@ ...@@ -74,7 +74,11 @@
function function
</li> </li>
<li> <li>
<select class="input-medium grafana-target-segment-input" ng-change="get_data()" ng-model="target.function" ng-options="f for f in ['mean', 'sum', 'min', 'max', 'median', 'derivative', 'stddev']" ></select> <select class="input-medium grafana-target-segment-input"
ng-change="get_data()"
ng-model="target.function"
ng-options="f for f in functions" ></select>
</li>
</li> </li>
<li class="grafana-target-segment"> <li class="grafana-target-segment">
group by time group by time
......
...@@ -13,10 +13,8 @@ function (angular, _, config) { ...@@ -13,10 +13,8 @@ function (angular, _, config) {
module.service('datasourceSrv', function($q, filterSrv, $http, GraphiteDatasource, InfluxDatasource) { module.service('datasourceSrv', function($q, filterSrv, $http, GraphiteDatasource, InfluxDatasource) {
this.init = function() { this.init = function() {
var defaultDatasource = _.findWhere(_.values(config.datasources), { default: true } ); var defaultDatasource = _.findWhere(_.values(config.datasources), { default: true } );
this.default = this.datasourceFactory(defaultDatasource); this.default = this.datasourceFactory(defaultDatasource);
}; };
this.datasourceFactory = function(ds) { this.datasourceFactory = function(ds) {
......
...@@ -32,12 +32,12 @@ define([ ...@@ -32,12 +32,12 @@ define([
}; };
if (self.list.length) { if (self.list.length) {
this.updateTemplateData(true); this._updateTemplateData(true);
} }
}; };
this.updateTemplateData = function(initial) { this._updateTemplateData = function(initial) {
self.filterTemplateData = {}; self._filterTemplateData = {};
_.each(self.list, function(filter) { _.each(self.list, function(filter) {
if (initial) { if (initial) {
...@@ -46,18 +46,17 @@ define([ ...@@ -46,18 +46,17 @@ define([
filter.current = { text: urlValue, value: urlValue }; filter.current = { text: urlValue, value: urlValue };
} }
} }
if (!filter.current || !filter.current.value) { if (!filter.current || !filter.current.value) {
return; return;
} }
self.filterTemplateData[filter.name] = filter.current.value; self._filterTemplateData[filter.name] = filter.current.value;
}); });
}; };
this.filterOptionSelected = function(filter, option) { this.filterOptionSelected = function(filter, option) {
filter.current = option; filter.current = option;
this.updateTemplateData(); this._updateTemplateData();
dashboard.refresh(); dashboard.refresh();
}; };
...@@ -70,7 +69,7 @@ define([ ...@@ -70,7 +69,7 @@ define([
return target; return target;
} }
return _.template(target, self.filterTemplateData, self.templateSettings); return _.template(target, self._filterTemplateData, self.templateSettings);
}; };
this.remove = function(filter) { this.remove = function(filter) {
......
...@@ -132,7 +132,10 @@ function (_) { ...@@ -132,7 +132,10 @@ function (_) {
addFuncDef({ addFuncDef({
name: 'aliasByNode', name: 'aliasByNode',
category: categories.Special, 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] defaultParams: [3]
}); });
...@@ -340,9 +343,29 @@ function (_) { ...@@ -340,9 +343,29 @@ function (_) {
return str + parameters.join(',') + ')'; 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) { FuncInstance.prototype.updateParam = function(strValue, index) {
if (this.def.params[index].type === 'int') { // handle optional parameters
this.params[index] = parseInt(strValue, 10); // 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] = parseFloat(strValue, 10);
} }
else { else {
this.params[index] = strValue; this.params[index] = strValue;
...@@ -359,6 +382,10 @@ function (_) { ...@@ -359,6 +382,10 @@ function (_) {
var text = this.def.name + '('; var text = this.def.name + '(';
_.each(this.def.params, function(param, index) { _.each(this.def.params, function(param, index) {
if (param.optional && this.params[index] === undefined) {
return;
}
text += this.params[index] + ', '; text += this.params[index] + ', ';
}, this); }, this);
text = text.substring(0, text.length - 2); text = text.substring(0, text.length - 2);
......
...@@ -14,8 +14,7 @@ module.exports = function(config) { ...@@ -14,8 +14,7 @@ module.exports = function(config) {
], ],
// list of files to exclude // list of files to exclude
exclude: [ exclude: [],
],
reporters: ['progress'], reporters: ['progress'],
port: 9876, port: 9876,
......
define([],
function() {
return {
create: function() {
return {
refresh: function() {},
current: {
title: "",
tags: [],
style: "dark",
timezone: 'browser',
editable: true,
failover: false,
panel_hints: true,
rows: [],
pulldowns: [ { type: 'templating' }, { type: 'annotations' } ],
nav: [ { type: 'timepicker' } ],
services: {},
loader: {
save_gist: false,
save_elasticsearch: true,
save_local: true,
save_default: true,
save_temp: true,
save_temp_ttl_enable: true,
save_temp_ttl: '30d',
load_gist: false,
load_elasticsearch: true,
load_elasticsearch_size: 20,
load_local: false,
hide: false
},
refresh: false
}
}
}
}
});
define([
'angular',
'angularMocks',
'panels/graphite/module'
], function(angular) {
/* describe('controller', function() {
var scope, metricCtrl;
beforeEach(function() {
angular.mock.inject(function($rootScope, $controller) {
scope = $rootScope.$new();
metricCtrl = $controller('kibana.panels.graphite.graphite', {
$scope: scope
});
});
});
it('should work', function() {
metricCtrl.toggleYAxis({alias:'myAlias'});
scope.panel.aliasYAxis['myAlias'].should.be(2);
});
});*/
});
define([
'mocks/dashboard-mock',
'underscore',
'services/filterSrv'
], function(dashboardMock, _) {
describe('filterSrv', function() {
var _filterSrv;
beforeEach(module('kibana.services'));
beforeEach(module(function($provide){
$provide.value('dashboard', dashboardMock.create());
}));
beforeEach(inject(function(filterSrv) {
_filterSrv = filterSrv;
}));
describe('init', function() {
beforeEach(function() {
_filterSrv.add({ name: 'test', current: { value: 'oogle' } });
_filterSrv.init();
});
it('should initialize template data', function() {
var target = _filterSrv.applyFilterToTarget('this.[[test]].filters');
expect(target).to.be('this.oogle.filters');
});
});
describe('filterOptionSelected', function() {
beforeEach(function() {
_filterSrv.add({ name: 'test' });
_filterSrv.filterOptionSelected(_filterSrv.list[0], { value: 'muuuu' });
});
it('should set current value and update template data', function() {
var target = _filterSrv.applyFilterToTarget('this.[[test]].filters');
expect(target).to.be('this.muuuu.filters');
});
});
describe('timeRange', function() {
it('should return unparsed when parse is false', function() {
_filterSrv.setTime({from: 'now', to: 'now-1h' });
var time = _filterSrv.timeRange(false);
expect(time.from).to.be('now');
expect(time.to).to.be('now-1h');
});
it('should return parsed when parse is true', function() {
_filterSrv.setTime({from: 'now', to: 'now-1h' });
var time = _filterSrv.timeRange(true);
expect(_.isDate(time.from)).to.be(true);
expect(_.isDate(time.to)).to.be(true);
});
});
});
});
...@@ -57,12 +57,51 @@ define([ ...@@ -57,12 +57,51 @@ define([
}); });
describe('when requesting function categories', function() { describe('when requesting function categories', function() {
it('should return function categories', function() { it('should return function categories', function() {
var catIndex = gfunc.getCategories(); var catIndex = gfunc.getCategories();
expect(catIndex.Special.length).to.be.greaterThan(8); 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)');
});
it('should parse numbers as float', function() {
var func = gfunc.createFuncInstance('scale');
func.updateParam('0.001', 0);
expect(func.params[0]).to.be(0.001);
});
});
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)');
});
}); });
}); });
define([
'mocks/dashboard-mock',
'underscore',
'services/filterSrv'
], function(dashboardMock, _) {
describe('graphiteTargetCtrl', function() {
var _filterSrv;
beforeEach(module('kibana.services'));
beforeEach(module(function($provide){
$provide.value('filterSrv',{});
}));
beforeEach(inject(function($controller, $rootScope) {
_targetCtrl = $controller({
$scope: $rootScope.$new()
});
}));
describe('init', function() {
beforeEach(function() {
_filterSrv.add({ name: 'test', current: { value: 'oogle' } });
_filterSrv.init();
});
});
});
});
...@@ -88,6 +88,12 @@ define([ ...@@ -88,6 +88,12 @@ define([
expect(tokens[4].pos).to.be(20); expect(tokens[4].pos).to.be(20);
}); });
it('should handle float parameters', function() {
var lexer = new Lexer("alias(metric, 0.002)");
var tokens = lexer.tokenize();
expect(tokens[4].type).to.be('number');
expect(tokens[4].value).to.be('0.002');
});
}); });
......
...@@ -139,6 +139,13 @@ define([ ...@@ -139,6 +139,13 @@ define([
expect(rootNode.type).to.be('function'); expect(rootNode.type).to.be('function');
}); });
it('handle float function arguments', function() {
var parser = new Parser('scale(test, 0.002)');
var rootNode = parser.getAst();
expect(rootNode.type).to.be('function');
expect(rootNode.params[1].type).to.be('number');
expect(rootNode.params[1].value).to.be(0.002);
});
}); });
......
...@@ -3,6 +3,7 @@ require.config({ ...@@ -3,6 +3,7 @@ require.config({
paths: { paths: {
specs: '../test/specs', specs: '../test/specs',
mocks: '../test/mocks',
config: '../config.sample', config: '../config.sample',
kbn: 'components/kbn', kbn: 'components/kbn',
...@@ -102,10 +103,20 @@ require.config({ ...@@ -102,10 +103,20 @@ require.config({
}); });
require([ require([
'angular',
'angularMocks',
], function(angular) {
angular.module('kibana', []);
angular.module('kibana.services', []);
require([
'specs/lexer-specs', 'specs/lexer-specs',
'specs/parser-specs', 'specs/parser-specs',
'specs/gfunc-specs', 'specs/gfunc-specs',
'specs/ctrl-specs', 'specs/filterSrv-specs',
], function () { ], function () {
window.__karma__.start(); window.__karma__.start();
});
}); });
...@@ -9,9 +9,11 @@ module.exports = function(config) { ...@@ -9,9 +9,11 @@ module.exports = function(config) {
expand: true, expand: true,
cwd: '<%= destDir %>', cwd: '<%= destDir %>',
src: ['**/*'], src: ['**/*'],
dest: '<%= pkg.name %>/',
}, },
{ {
expand: true, expand: true,
dest: '<%= pkg.name %>/',
src: ['LICENSE.md', 'README.md', 'NOTICE.md'], src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
} }
] ]
...@@ -25,10 +27,12 @@ module.exports = function(config) { ...@@ -25,10 +27,12 @@ module.exports = function(config) {
expand: true, expand: true,
cwd: '<%= destDir %>', cwd: '<%= destDir %>',
src: ['**/*'], src: ['**/*'],
dest: '<%= pkg.name %>/',
}, },
{ {
expand: true, expand: true,
src: ['LICENSE.md', 'README.md', 'NOTICE.md'], src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
dest: '<%= pkg.name %>/',
} }
] ]
}, },
...@@ -41,10 +45,12 @@ module.exports = function(config) { ...@@ -41,10 +45,12 @@ module.exports = function(config) {
expand: true, expand: true,
cwd: '<%= destDir %>', cwd: '<%= destDir %>',
src: ['**/*'], src: ['**/*'],
dest: '<%= pkg.name %>-<%= pkg.version %>/',
}, },
{ {
expand: true, expand: true,
src: ['LICENSE.md', 'README.md', 'NOTICE.md'], src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
dest: '<%= pkg.name %>-<%= pkg.version %>/',
} }
] ]
}, },
...@@ -57,10 +63,12 @@ module.exports = function(config) { ...@@ -57,10 +63,12 @@ module.exports = function(config) {
expand: true, expand: true,
cwd: '<%= destDir %>', cwd: '<%= destDir %>',
src: ['**/*'], src: ['**/*'],
dest: '<%= pkg.name %>-<%= pkg.version %>/',
}, },
{ {
expand: true, expand: true,
src: ['LICENSE.md', 'README.md', 'NOTICE.md'], src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
dest: '<%= pkg.name %>-<%= pkg.version %>/',
} }
] ]
} }
......
...@@ -3,6 +3,11 @@ module.exports = function(config) { ...@@ -3,6 +3,11 @@ module.exports = function(config) {
dev: { dev: {
configFile: 'src/test/karma.conf.js', configFile: 'src/test/karma.conf.js',
singleRun: false, singleRun: false,
browsers: ['PhantomJS']
},
debug: {
configFile: 'src/test/karma.conf.js',
singleRun: true,
browsers: ['Chrome'] browsers: ['Chrome']
}, },
test: { test: {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment