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

This commit is contained in:
Bruce Sherrod
2014-03-24 12:35:12 -07:00
20 changed files with 327 additions and 69 deletions

View File

@@ -1,5 +1,27 @@
# 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)
###New Features and improvements
### New Features and improvements
- New function editor [video demo](http://youtu.be/I90WHRwE1ZM) (Issue #178)
- Links to function documentation from function editor (Issue #3)
- Reorder functions (Issue #130)
@@ -18,7 +40,7 @@
- Fix to annotations with graphite source & null values (Issue #138)
# 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!
- #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.

View File

@@ -4,7 +4,7 @@
"company": "Coding Instinct AB"
},
"name": "grafana",
"version": "1.5.1",
"version": "1.5.2",
"repository": {
"type": "git",
"url": "http://github.com/torkelo/grafana.git"

View File

@@ -15,6 +15,7 @@ function (angular) {
$scope.target.function = 'mean';
}
$scope.functions = ['count', 'mean', 'sum', 'min', 'max', 'mode', 'distinct', 'median', 'derivative', 'stddev', 'first', 'last'];
$scope.oldSeries = $scope.target.series;
$scope.$on('typeahead-updated', function(){
$timeout($scope.get_data);

View File

@@ -318,7 +318,7 @@ function (angular, $, kbn, moment, _) {
}
});
function render_panel_as_graphite_png(url) {
function render_panel_as_graphite_png(url) {
url += '&width=' + elem.width();
url += '&height=' + elem.css('height').replace('px', '');
url += '&bgcolor=1f1f1f'; // @grayDarker & @kibanaPanelBackground
@@ -327,8 +327,8 @@ function (angular, $, kbn, moment, _) {
url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : '';
url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : '';
url += scope.panel.legend ? '' : '&hideLegend=true';
url += scope.panel.grid.min ? '&yMin=' + scope.panel.grid.min : '';
url += scope.panel.grid.max ? '&yMax=' + scope.panel.grid.max : '';
url += scope.panel.grid.min !== null ? '&yMin=' + scope.panel.grid.min : '';
url += scope.panel.grid.max !== null ? '&yMax=' + scope.panel.grid.max : '';
url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';

View File

@@ -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) {
$('<span>, </span>').appendTo(elem);
}
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + func.params[index] + '</a>');
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) {
$('<span>, </span>').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();
}
};

View File

@@ -74,7 +74,11 @@
function
</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 class="grafana-target-segment">
group by time

View File

@@ -13,10 +13,8 @@ function (angular, _, config) {
module.service('datasourceSrv', function($q, filterSrv, $http, GraphiteDatasource, InfluxDatasource) {
this.init = function() {
var defaultDatasource = _.findWhere(_.values(config.datasources), { default: true } );
this.default = this.datasourceFactory(defaultDatasource);
};
this.datasourceFactory = function(ds) {

View File

@@ -32,12 +32,12 @@ define([
};
if (self.list.length) {
this.updateTemplateData(true);
this._updateTemplateData(true);
}
};
this.updateTemplateData = function(initial) {
self.filterTemplateData = {};
this._updateTemplateData = function(initial) {
self._filterTemplateData = {};
_.each(self.list, function(filter) {
if (initial) {
@@ -46,18 +46,17 @@ define([
filter.current = { text: urlValue, value: urlValue };
}
}
if (!filter.current || !filter.current.value) {
return;
}
self.filterTemplateData[filter.name] = filter.current.value;
self._filterTemplateData[filter.name] = filter.current.value;
});
};
this.filterOptionSelected = function(filter, option) {
filter.current = option;
this.updateTemplateData();
this._updateTemplateData();
dashboard.refresh();
};
@@ -70,7 +69,7 @@ define([
return target;
}
return _.template(target, self.filterTemplateData, self.templateSettings);
return _.template(target, self._filterTemplateData, self.templateSettings);
};
this.remove = function(filter) {

View File

@@ -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') {
this.params[index] = parseInt(strValue, 10);
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] = parseFloat(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);

View File

@@ -14,8 +14,7 @@ module.exports = function(config) {
],
// list of files to exclude
exclude: [
],
exclude: [],
reporters: ['progress'],
port: 9876,

View File

@@ -0,0 +1,40 @@
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
}
}
}
}
});

View File

@@ -1,26 +0,0 @@
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);
});
});*/
});

View File

@@ -0,0 +1,60 @@
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);
});
});
});
});

View File

@@ -57,12 +57,51 @@ 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)');
});
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)');
});
});
});

View File

@@ -0,0 +1,28 @@
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();
});
});
});
});

View File

@@ -88,6 +88,12 @@ define([
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');
});
});

View File

@@ -139,6 +139,13 @@ define([
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);
});
});

View File

@@ -3,6 +3,7 @@ require.config({
paths: {
specs: '../test/specs',
mocks: '../test/mocks',
config: '../config.sample',
kbn: 'components/kbn',
@@ -102,10 +103,20 @@ require.config({
});
require([
'specs/lexer-specs',
'specs/parser-specs',
'specs/gfunc-specs',
'specs/ctrl-specs',
], function () {
window.__karma__.start();
});
'angular',
'angularMocks',
], function(angular) {
angular.module('kibana', []);
angular.module('kibana.services', []);
require([
'specs/lexer-specs',
'specs/parser-specs',
'specs/gfunc-specs',
'specs/filterSrv-specs',
], function () {
window.__karma__.start();
});
});

View File

@@ -9,9 +9,11 @@ module.exports = function(config) {
expand: true,
cwd: '<%= destDir %>',
src: ['**/*'],
dest: '<%= pkg.name %>/',
},
{
expand: true,
dest: '<%= pkg.name %>/',
src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
}
]
@@ -25,10 +27,12 @@ module.exports = function(config) {
expand: true,
cwd: '<%= destDir %>',
src: ['**/*'],
dest: '<%= pkg.name %>/',
},
{
expand: true,
src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
dest: '<%= pkg.name %>/',
}
]
},
@@ -41,10 +45,12 @@ module.exports = function(config) {
expand: true,
cwd: '<%= destDir %>',
src: ['**/*'],
dest: '<%= pkg.name %>-<%= pkg.version %>/',
},
{
expand: true,
src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
dest: '<%= pkg.name %>-<%= pkg.version %>/',
}
]
},
@@ -57,10 +63,12 @@ module.exports = function(config) {
expand: true,
cwd: '<%= destDir %>',
src: ['**/*'],
dest: '<%= pkg.name %>-<%= pkg.version %>/',
},
{
expand: true,
src: ['LICENSE.md', 'README.md', 'NOTICE.md'],
dest: '<%= pkg.name %>-<%= pkg.version %>/',
}
]
}

View File

@@ -3,6 +3,11 @@ module.exports = function(config) {
dev: {
configFile: 'src/test/karma.conf.js',
singleRun: false,
browsers: ['PhantomJS']
},
debug: {
configFile: 'src/test/karma.conf.js',
singleRun: true,
browsers: ['Chrome']
},
test: {
@@ -11,4 +16,4 @@ module.exports = function(config) {
browsers: ['PhantomJS']
}
};
};
};