Commit ed8dd03f by Torkel Ödegaard

Merge branch 'master' into valuepanel

parents 272cf64a e5bb7f7c
/* global _ */
/*
* Complex scripted dashboard
* This script generates a dashboard object that Grafana can load. It also takes a number of user
* supplied URL parameters (int ARGS variable)
*
* Return a dashboard object, or a function
*
* For async scripts, return a function, this function must take a single callback function as argument,
* call this callback function with the dashboard object (look at scripted_async.js for an example)
*/
'use strict';
// accessable variables in this scope
var window, document, ARGS, $, jQuery, moment, kbn, services, _;
// default datasource
var datasource = services.datasourceSrv.default;
// get datasource used for saving dashboards
var dashboardDB = services.datasourceSrv.getGrafanaDB();
var targets = [];
function getTargets(path) {
return datasource.metricFindQuery(path + '.*').then(function(result) {
if (!result) {
return null;
}
if (targets.length === 10) {
return null;
}
var promises = _.map(result, function(metric) {
if (metric.expandable) {
return getTargets(path + "." + metric.text);
}
else {
targets.push(path + '.' + metric.text);
}
return null;
});
return services.$q.when(promises);
});
}
function createDashboard(target, index) {
// Intialize a skeleton with nothing but a rows array and service object
var dashboard = { rows : [] };
dashboard.title = 'Scripted dash ' + index;
dashboard.time = {
from: "now-6h",
to: "now"
};
dashboard.rows.push({
title: 'Chart',
height: '300px',
panels: [
{
title: 'Events',
type: 'graph',
span: 12,
targets: [ {target: target} ]
}
]
});
}
return function(callback) {
getTargets('apps').then(function(results) {
console.log('targets: ', targets);
_.each(targets, function(target, index) {
var dashboard = createDashboard(target);
});
});
};
......@@ -11,10 +11,15 @@
**Misc**
- [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory
- [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts
- [Issue #991](https://github.com/grafana/grafana/issues/991). ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example)
**OpenTSDB**
- [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc
**Fixes**
- [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other)
- [Issue #505](https://github.com/grafana/grafana/issues/505). Graph: fix for second y axis tick unit labels wrapping on the next line
- [Issue #987](https://github.com/grafana/grafana/issues/987). Dashboard: Collapsed rows became invisible when hide controls was enabled
=======
# 1.8.1 (2014-09-30)
......
......@@ -3,6 +3,7 @@
*/
require.config({
baseUrl: 'app',
urlArgs: 'bust=' + (new Date().getTime()),
paths: {
config: ['../config', '../config.sample'],
......
/* global _ */
/*
* Complex scripted dashboard
* This script generates a dashboard object that Grafana can load. It also takes a number of user
* supplied URL parameters (int ARGS variable)
*
* Return a dashboard object, or a function
*
* For async scripts, return a function, this function must take a single callback function as argument,
* call this callback function with the dashboard object (look at scripted_async.js for an example)
*/
'use strict';
// accessable variables in this scope
var window, document, ARGS, $, jQuery, moment, kbn, services, _;
// default datasource
var datasource = services.datasourceSrv.default;
// get datasource used for saving dashboards
var dashboardDB = services.datasourceSrv.getGrafanaDB();
var targets = [];
function getTargets(path) {
return datasource.metricFindQuery(path + '.*').then(function(result) {
if (!result) {
return null;
}
if (targets.length === 10) {
return null;
}
var promises = _.map(result, function(metric) {
if (metric.expandable) {
return getTargets(path + "." + metric.text);
}
else {
targets.push(path + '.' + metric.text);
}
return null;
});
return services.$q.all(promises);
});
}
function createDashboard(target, index) {
// Intialize a skeleton with nothing but a rows array and service object
var dashboard = { rows : [] };
dashboard.title = 'Scripted dash ' + index;
dashboard.time = {
from: "now-6h",
to: "now"
};
dashboard.rows.push({
title: 'Chart',
height: '300px',
panels: [
{
title: 'Events',
type: 'graph',
span: 12,
targets: [ {target: target} ]
}
]
});
return dashboard;
}
function saveDashboard(dashboard) {
var model = services.dashboardSrv.create(dashboard);
dashboardDB.saveDashboard(model);
}
return function(callback) {
getTargets('apps').then(function() {
console.log('targets: ', targets);
_.each(targets, function(target, index) {
var dashboard = createDashboard(target, index);
saveDashboard(dashboard);
if (index === targets.length - 1) {
callback(dashboard);
}
});
});
};
......@@ -144,8 +144,9 @@ function ($) {
hoverInfo = seriesHoverInfo[i];
value = series.formatValue(hoverInfo.value);
group = '<i class="icon-minus" style="color:' + series.color +';"></i> ' + series.label;
seriesHtml = group + ': <span class="graph-tooltip-value">' + value + '</span><br>' + seriesHtml;
seriesHtml += '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
seriesHtml += '<i class="icon-minus" style="color:' + series.color +';"></i> ' + series.label + ':</div>';
seriesHtml += '<div class="graph-tooltip-value">' + value + '</div></div>';
plot.highlight(i, hoverInfo.hoverIndex);
}
......@@ -154,7 +155,8 @@ function ($) {
// single series tooltip
else if (item) {
series = seriesList[item.seriesIndex];
group = '<i class="icon-minus" style="color:' + item.series.color +';"></i> ' + series.label;
group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
group += '<i class="icon-minus" style="color:' + item.series.color +';"></i> ' + series.label + ':</div>';
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
value = item.datapoint[1] - item.datapoint[2];
......@@ -165,7 +167,7 @@ function ($) {
value = series.formatValue(value);
timestamp = dashboard.formatDate(item.datapoint[0]);
group += ': <span class="graph-tooltip-value">' + value + '</span><br>';
group += '<div class="graph-tooltip-value">' + value + '</div>';
self.showTooltip(timestamp, group, pos);
}
......
......@@ -202,6 +202,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
$scope.panelMeta.loading = false;
$scope.panelMeta.error = err.message || "Timeseries data request error";
$scope.inspector.error = err;
$scope.legend = [];
$scope.render([]);
});
};
......
......@@ -89,6 +89,32 @@
ng-model="target.isCounter"
ng-change="targetBlur()">
</li>
<li class="grafana-target-segment" ng-hide="!target.isCounter">
Counter Max:
</li>
<li ng-hide="!target.isCounter">
<input type="text"
class="grafana-target-segment-input input-medium"
ng-disabled="!target.shouldComputeRate"
ng-model="target.counterMax"
spellcheck='false'
placeholder="Counter max value"
ng-blur="targetBlur()"
/>
</li>
<li class="grafana-target-segment" ng-hide="!target.isCounter">
Counter Reset Value:
</li>
<li ng-hide="!target.isCounter">
<input type="text"
class="grafana-target-segment-input input-medium"
ng-disabled="!target.shouldComputeRate"
ng-model="target.counterResetValue"
spellcheck='false'
placeholder="Counter reset value"
ng-blur="targetBlur()"
/>
</li>
<li class="grafana-target-segment">
Alias:
</li>
......
......@@ -16,21 +16,28 @@ function (angular, $, config, _, kbn, moment) {
.when('/dashboard/script/:jsFile', {
templateUrl: 'app/partials/dashboard.html',
controller : 'DashFromScriptProvider',
reloadOnSearch: false,
});
});
module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, alertSrv, $q) {
module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, $q, dashboardSrv, datasourceSrv, $timeout) {
var execute_script = function(result) {
var services = {
dashboardSrv: dashboardSrv,
datasourceSrv: datasourceSrv,
$q: $q,
};
/*jshint -W054 */
var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', result.data);
var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $);
var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', 'services', result.data);
var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $, services);
// Handle async dashboard scripts
if (_.isFunction(script_result)) {
var deferred = $q.defer();
script_result(function(dashboard) {
$rootScope.$apply(function() {
$timeout(function() {
deferred.resolve({ data: dashboard });
});
});
......@@ -47,7 +54,7 @@ function (angular, $, config, _, kbn, moment) {
.then(execute_script)
.then(null,function(err) {
console.log('Script dashboard error '+ err);
alertSrv.set('Error', "Could not load <i>scripts/"+file+"</i>. Please make sure it exists and returns a valid dashboard", 'error');
$scope.appEvent('alert-error', ["Script Error", "Please make sure it exists and returns a valid dashboard"]);
return false;
});
};
......
......@@ -18,7 +18,7 @@ function () {
var query = 'select ';
var seriesName = target.series;
if(!seriesName.match('^/.*/')) {
if(!seriesName.match('^/.*/') && !seriesName.match(/^merge\(.*\)/)) {
seriesName = '"' + seriesName+ '"';
}
......
......@@ -44,7 +44,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
// replace grafana variables
query = query.replace('$timeFilter', timeFilter);
query = query.replace('$interval', (target.interval || options.interval));
query = query.replace(/\$interval/g, (target.interval || options.interval));
// replace templated variables
query = templateSrv.replace(query);
......
define([
'angular',
'lodash',
'kbn'
'kbn',
'moment'
],
function (angular, _, kbn) {
'use strict';
var module = angular.module('grafana.services');
module.factory('OpenTSDBDatasource', function($q, $http) {
module.factory('OpenTSDBDatasource', function($q, $http, templateSrv) {
function OpenTSDBDatasource(datasource) {
this.type = 'opentsdb';
......@@ -123,12 +124,12 @@ function (angular, _, kbn) {
}
var query = {
metric: target.metric,
metric: templateSrv.replace(target.metric),
aggregator: "avg"
};
if (target.aggregator) {
query.aggregator = target.aggregator;
query.aggregator = templateSrv.replace(target.aggregator);
}
if (target.shouldComputeRate) {
......@@ -136,6 +137,14 @@ function (angular, _, kbn) {
query.rateOptions = {
counter: !!target.isCounter
};
if (target.counterMax && target.counterMax.length) {
query.rateOptions.counterMax = parseInt(target.counterMax);
}
if (target.counterResetValue && target.counterResetValue.length) {
query.rateOptions.resetValue = parseInt(target.counterResetValue);
}
}
if (target.shouldDownsample) {
......@@ -143,6 +152,11 @@ function (angular, _, kbn) {
}
query.tags = angular.copy(target.tags);
if(query.tags){
for(var key in query.tags){
query.tags[key] = templateSrv.replace(query.tags[key]);
}
}
return query;
}
......
......@@ -16,7 +16,7 @@
.hide-controls {
padding: 0;
.row-control-inner {
.row-tab {
display: none;
}
.submenu-controls {
......
......@@ -178,9 +178,18 @@
top: -3px;
}
.graph-tooltip-list-item {
display: table-row;
}
.graph-tooltip-series-name {
display: table-cell;
}
.graph-tooltip-value {
display: table-cell;
font-weight: bold;
float: right;
padding-left: 10px;
text-align: right;
}
}
......@@ -19,7 +19,7 @@ function (angular, _, kbn) {
this.url = datasource.url;
}
CustomDatasource.prototype.query = function(filterSrv, options) {
CustomDatasource.prototype.query = function(options) {
// get from & to in seconds
var from = kbn.parseDate(options.range.from).getTime() / 1000;
var to = kbn.parseDate(options.range.to).getTime() / 1000;
......
......@@ -36,6 +36,20 @@ define([
var data = ctx.scope.render.getCall(0).args[0];
expect(data.length).to.be(2);
});
describe('get_data failure following success', function() {
beforeEach(function() {
ctx.datasource.query = sinon.stub().returns(ctx.$q.reject('Datasource Error'));
ctx.scope.get_data();
ctx.scope.$digest();
});
it('should clear the legend data', function() {
expect(ctx.scope.legend).to.eql([]);
});
});
});
});
......
......@@ -44,6 +44,35 @@ define([
});
describe('merge function detection', function() {
it('should not quote wrap regex merged series', function() {
var builder = new InfluxQueryBuilder({
series: 'merge(/^google.test/)',
column: 'value',
function: 'mean'
});
var query = builder.build();
expect(query).to.be('select mean(value) from merge(/^google.test/) where $timeFilter ' +
'group by time($interval) order asc');
});
it('should quote wrap series names that start with "merge"', function() {
var builder = new InfluxQueryBuilder({
series: 'merge.google.test',
column: 'value',
function: 'mean'
});
var query = builder.build();
expect(query).to.be('select mean(value) from "merge.google.test" where $timeFilter ' +
'group by time($interval) order asc');
});
});
});
});
......@@ -10,6 +10,7 @@ module.exports = function(config) {
'app/filters/**/*.js',
'app/panels/**/*.js',
'app/routes/**/*.js',
'plugins/**/*.js',
'app/app.js',
'vendor/angular/**/*.js',
],
......
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