Commit 6885cea0 by Torkel Ödegaard

Improvement and polish to the OpenTSDB query editor (Issue #492)

parents 87a8b1b3 30b62e17
...@@ -14,6 +14,9 @@ ...@@ -14,6 +14,9 @@
- Improvement to InfluxDB query editor and function/value column selection (Issue #473) - Improvement to InfluxDB query editor and function/value column selection (Issue #473)
- Initial support for filtering (templated queries) for InfluxDB (PR #375) - thx @mavimo - Initial support for filtering (templated queries) for InfluxDB (PR #375) - thx @mavimo
- Row editing and adding new panel is now a lot quicker and easier with the new row menu (Issue #475) - Row editing and adding new panel is now a lot quicker and easier with the new row menu (Issue #475)
- New datasource! Initial support for OpenTSDB (PR #211) - thx @mpage
- Improvement and polish to the OpenTSDB query editor (Issue #492)
#### Changes #### Changes
- Graphite panel is now renamed graph (Existing dashboards will still work) - Graphite panel is now renamed graph (Existing dashboards will still work)
......
...@@ -11,4 +11,5 @@ define([ ...@@ -11,4 +11,5 @@ define([
'./influxTargetCtrl', './influxTargetCtrl',
'./playlistCtrl', './playlistCtrl',
'./inspectCtrl', './inspectCtrl',
'./opentsdbTargetCtrl',
], function () {}); ], function () {});
define([
'angular',
'underscore',
'kbn'
],
function (angular, _, kbn) {
'use strict';
var module = angular.module('kibana.controllers');
module.controller('OpenTSDBTargetCtrl', function($scope, $timeout) {
$scope.init = function() {
$scope.target.errors = validateTarget($scope.target);
$scope.aggregators = ['avg', 'sum', 'min', 'max', 'dev', 'zimsum', 'mimmin', 'mimmax'];
if (!$scope.target.aggregator) {
$scope.target.aggregator = 'sum';
}
if (!$scope.target.downsampleAggregator) {
$scope.target.downsampleAggregator = 'sum';
}
$scope.$on('typeahead-updated', function() {
$timeout($scope.targetBlur);
});
};
$scope.targetBlur = function() {
$scope.target.errors = validateTarget($scope.target);
// this does not work so good
if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) {
$scope.oldTarget = angular.copy($scope.target);
$scope.get_data();
}
};
$scope.duplicate = function() {
var clone = angular.copy($scope.target);
$scope.panel.targets.push(clone);
};
$scope.suggestMetrics = function(query, callback) {
$scope.datasource
.performSuggestQuery(query, 'metrics')
.then(callback);
};
$scope.suggestTagKeys = function(query, callback) {
$scope.datasource
.performSuggestQuery(query, 'tagk')
.then(callback);
};
$scope.suggestTagValues = function(query, callback) {
$scope.datasource
.performSuggestQuery(query, 'tagv')
.then(callback);
};
$scope.addTag = function() {
if (!$scope.addTagMode) {
$scope.addTagMode = true;
return;
}
if (!$scope.target.tags) {
$scope.target.tags = {};
}
$scope.target.errors = validateTarget($scope.target);
if (!$scope.target.errors.tags) {
$scope.target.tags[$scope.target.currentTagKey] = $scope.target.currentTagValue;
$scope.target.currentTagKey = '';
$scope.target.currentTagValue = '';
$scope.targetBlur();
}
$scope.addTagMode = false;
};
$scope.removeTag = function(key) {
delete $scope.target.tags[key];
$scope.targetBlur();
};
function validateTarget(target) {
var errs = {};
if (!target.metric) {
errs.metric = "You must supply a metric name.";
}
if (target.shouldDownsample) {
try {
if (target.downsampleInterval) {
kbn.describe_interval(target.downsampleInterval);
} else {
errs.downsampleInterval = "You must supply a downsample interval (e.g. '1m' or '1h').";
}
} catch(err) {
errs.downsampleInterval = err.message;
}
}
if (target.tags && _.has(target.tags, target.currentTagKey)) {
errs.tags = "Duplicate tag key '" + target.currentTagKey + "'.";
}
return errs;
}
});
});
...@@ -4,13 +4,14 @@ define([ ...@@ -4,13 +4,14 @@ define([
'config', 'config',
'./graphite/graphiteDatasource', './graphite/graphiteDatasource',
'./influxdb/influxdbDatasource', './influxdb/influxdbDatasource',
'./opentsdb/opentsdbDatasource',
], ],
function (angular, _, config) { function (angular, _, config) {
'use strict'; 'use strict';
var module = angular.module('kibana.services'); var module = angular.module('kibana.services');
module.service('datasourceSrv', function($q, $http, GraphiteDatasource, InfluxDatasource) { module.service('datasourceSrv', function($q, filterSrv, $http, GraphiteDatasource, InfluxDatasource, OpenTSDBDatasource) {
this.init = function() { this.init = function() {
var defaultDatasource = _.findWhere(_.values(config.datasources), { default: true }); var defaultDatasource = _.findWhere(_.values(config.datasources), { default: true });
...@@ -23,6 +24,8 @@ function (angular, _, config) { ...@@ -23,6 +24,8 @@ function (angular, _, config) {
return new GraphiteDatasource(ds); return new GraphiteDatasource(ds);
case 'influxdb': case 'influxdb':
return new InfluxDatasource(ds); return new InfluxDatasource(ds);
case 'opentsdb':
return new OpenTSDBDatasource(ds);
} }
}; };
......
define([
'angular',
'underscore',
'kbn'
],
function (angular, _, kbn) {
'use strict';
var module = angular.module('kibana.services');
module.factory('OpenTSDBDatasource', function($q, $http) {
function OpenTSDBDatasource(datasource) {
this.type = 'opentsdb';
this.editorSrc = 'app/partials/opentsdb/editor.html';
this.url = datasource.url;
this.name = datasource.name;
}
// Called once per panel (graph)
OpenTSDBDatasource.prototype.query = function(filterSrv, options) {
var start = convertToTSDBTime(options.range.from);
var end = convertToTSDBTime(options.range.to);
var queries = _.compact(_.map(options.targets, convertTargetToQuery));
// No valid targets, return the empty result to save a round trip.
if (_.isEmpty(queries)) {
var d = $q.defer();
d.resolve({ data: [] });
return d.promise;
}
var groupByTags = {};
_.each(queries, function(query) {
_.each(query.tags, function(val, key) {
if (val === "*") {
groupByTags[key] = true;
}
});
});
return this.performTimeSeriesQuery(queries, start, end)
.then(function(response) {
var result = _.map(response.data, function(metricData) {
return transformMetricData(metricData, groupByTags);
});
return { data: result };
});
};
OpenTSDBDatasource.prototype.performTimeSeriesQuery = function(queries, start, end) {
var reqBody = {
start: start,
queries: queries
};
// Relative queries (e.g. last hour) don't include an end time
if (end) {
reqBody.end = end;
}
var options = {
method: 'POST',
url: this.url + '/api/query',
data: reqBody
};
return $http(options);
};
OpenTSDBDatasource.prototype.performSuggestQuery = function(query, type) {
var options = {
method: 'GET',
url: this.url + '/api/suggest',
params: {
type: type,
q: query
}
};
return $http(options).then(function(result) {
return result.data;
});
};
function transformMetricData(md, groupByTags) {
var dps = [];
// TSDB returns datapoints has a hash of ts => value.
// Can't use _.pairs(invert()) because it stringifies keys/values
_.each(md.dps, function (v, k) {
dps.push([v, k]);
});
var target = md.metric;
if (!_.isEmpty(md.tags)) {
var tagData = [];
_.each(_.pairs(md.tags), function(tag) {
if (_.has(groupByTags, tag[0])) {
tagData.push(tag[0] + "=" + tag[1]);
}
});
if (!_.isEmpty(tagData)) {
target = target + "{" + tagData.join(", ") + "}";
}
}
return { target: target, datapoints: dps };
}
function convertTargetToQuery(target) {
if (!target.metric) {
return null;
}
var query = {
metric: target.metric,
aggregator: "avg"
};
if (target.aggregator) {
query.aggregator = target.aggregator;
}
if (target.shouldComputeRate) {
query.rate = true;
query.rateOptions = {
counter: !!target.isCounter
};
}
if (target.shouldDownsample) {
query.downsample = target.downsampleInterval + "-" + target.downsampleAggregator;
}
query.tags = angular.copy(target.tags);
return query;
}
function convertToTSDBTime(date) {
if (date === 'now') {
return null;
}
date = kbn.parseDate(date);
return date.getTime();
}
return OpenTSDBDatasource;
});
});
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -371,6 +371,10 @@ input[type=text].grafana-target-segment-input { ...@@ -371,6 +371,10 @@ input[type=text].grafana-target-segment-input {
padding: 8px 4px; padding: 8px 4px;
} }
input[type=checkbox].grafana-target-option-checkbox {
margin: 0;
}
select.grafana-target-segment-input { select.grafana-target-segment-input {
border: none; border: none;
border-right: 1px solid @grafanaTargetSegmentBorder; border-right: 1px solid @grafanaTargetSegmentBorder;
......
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