Merge branch 'master' into valuepanel
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/* 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'],
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/* 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);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
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 {
|
||||
|
||||
+10
-1
@@ -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',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user