mirror of https://github.com/grafana/grafana
Made a copy of influxdb datasource named influxdb_08 so the main influxdb data source can be modified to support InfluxDB 0.9, made some initial experiments to get queries to work, but a lot more work is needed, #1525
parent
f5f07bd552
commit
ae7f18f981
@ -0,0 +1,401 @@ |
||||
define([ |
||||
'angular', |
||||
'lodash', |
||||
'kbn', |
||||
'./influxSeries', |
||||
'./queryBuilder', |
||||
'./queryCtrl', |
||||
'./funcEditor', |
||||
], |
||||
function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { |
||||
'use strict'; |
||||
|
||||
var module = angular.module('grafana.services'); |
||||
|
||||
module.factory('InfluxDatasource_08', function($q, $http, templateSrv) { |
||||
|
||||
function InfluxDatasource(datasource) { |
||||
this.type = 'influxdb_08'; |
||||
this.urls = _.map(datasource.url.split(','), function(url) { |
||||
return url.trim(); |
||||
}); |
||||
this.username = datasource.username; |
||||
this.password = datasource.password; |
||||
this.name = datasource.name; |
||||
this.basicAuth = datasource.basicAuth; |
||||
this.grafanaDB = datasource.grafanaDB; |
||||
|
||||
this.saveTemp = _.isUndefined(datasource.save_temp) ? true : datasource.save_temp; |
||||
this.saveTempTTL = _.isUndefined(datasource.save_temp_ttl) ? '30d' : datasource.save_temp_ttl; |
||||
|
||||
this.supportAnnotations = true; |
||||
this.supportMetrics = true; |
||||
this.editorSrc = 'app/features/influxdb/partials/query.editor.html'; |
||||
this.annotationEditorSrc = 'app/features/influxdb/partials/annotations.editor.html'; |
||||
} |
||||
|
||||
InfluxDatasource.prototype.query = function(options) { |
||||
var timeFilter = getTimeFilter(options); |
||||
|
||||
var promises = _.map(options.targets, function(target) { |
||||
if (target.hide || !((target.series && target.column) || target.query)) { |
||||
return []; |
||||
} |
||||
|
||||
// build query
|
||||
var queryBuilder = new InfluxQueryBuilder(target); |
||||
var query = queryBuilder.build(); |
||||
|
||||
// replace grafana variables
|
||||
query = query.replace('$timeFilter', timeFilter); |
||||
query = query.replace(/\$interval/g, (target.interval || options.interval)); |
||||
|
||||
// replace templated variables
|
||||
query = templateSrv.replace(query); |
||||
|
||||
var alias = target.alias ? templateSrv.replace(target.alias) : ''; |
||||
|
||||
var handleResponse = _.partial(handleInfluxQueryResponse, alias, queryBuilder.groupByField); |
||||
return this._seriesQuery(query).then(handleResponse); |
||||
|
||||
}, this); |
||||
|
||||
return $q.all(promises).then(function(results) { |
||||
return { data: _.flatten(results) }; |
||||
}); |
||||
}; |
||||
|
||||
InfluxDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) { |
||||
var timeFilter = getTimeFilter({ range: rangeUnparsed }); |
||||
var query = annotation.query.replace('$timeFilter', timeFilter); |
||||
query = templateSrv.replace(query); |
||||
|
||||
return this._seriesQuery(query).then(function(results) { |
||||
return new InfluxSeries({ seriesList: results, annotation: annotation }).getAnnotations(); |
||||
}); |
||||
}; |
||||
|
||||
InfluxDatasource.prototype.listColumns = function(seriesName) { |
||||
seriesName = templateSrv.replace(seriesName); |
||||
|
||||
if(!seriesName.match('^/.*/') && !seriesName.match(/^merge\(.*\)/)) { |
||||
seriesName = '"' + seriesName+ '"'; |
||||
} |
||||
|
||||
return this._seriesQuery('select * from ' + seriesName + ' limit 1').then(function(data) { |
||||
if (!data) { |
||||
return []; |
||||
} |
||||
return data[0].columns.map(function(item) { |
||||
return /^\w+$/.test(item) ? item : ('"' + item + '"'); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
InfluxDatasource.prototype.listSeries = function(query) { |
||||
// wrap in regex
|
||||
if (query && query.length > 0 && query[0] !== '/') { |
||||
query = '/' + query + '/'; |
||||
} |
||||
|
||||
return this._seriesQuery('list series ' + query).then(function(data) { |
||||
if (!data || data.length === 0) { |
||||
return []; |
||||
} |
||||
return _.map(data[0].points, function(point) { |
||||
return point[1]; |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
InfluxDatasource.prototype.metricFindQuery = function (query) { |
||||
var interpolated; |
||||
try { |
||||
interpolated = templateSrv.replace(query); |
||||
} |
||||
catch (err) { |
||||
return $q.reject(err); |
||||
} |
||||
|
||||
return this._seriesQuery(interpolated) |
||||
.then(function (results) { |
||||
if (!results || results.length === 0) { return []; } |
||||
|
||||
return _.map(results[0].points, function (metric) { |
||||
return { |
||||
text: metric[1], |
||||
expandable: false |
||||
}; |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
function retry(deferred, callback, delay) { |
||||
return callback().then(undefined, function(reason) { |
||||
if (reason.status !== 0 || reason.status >= 300) { |
||||
reason.message = 'InfluxDB Error: <br/>' + reason.data; |
||||
deferred.reject(reason); |
||||
} |
||||
else { |
||||
setTimeout(function() { |
||||
return retry(deferred, callback, Math.min(delay * 2, 30000)); |
||||
}, delay); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
InfluxDatasource.prototype._seriesQuery = function(query) { |
||||
return this._influxRequest('GET', '/series', { |
||||
q: query, |
||||
}); |
||||
}; |
||||
|
||||
InfluxDatasource.prototype._influxRequest = function(method, url, data) { |
||||
var _this = this; |
||||
var deferred = $q.defer(); |
||||
|
||||
retry(deferred, function() { |
||||
var currentUrl = _this.urls.shift(); |
||||
_this.urls.push(currentUrl); |
||||
|
||||
var params = { |
||||
u: _this.username, |
||||
p: _this.password, |
||||
}; |
||||
|
||||
if (method === 'GET') { |
||||
_.extend(params, data); |
||||
data = null; |
||||
} |
||||
|
||||
var options = { |
||||
method: method, |
||||
url: currentUrl + url, |
||||
params: params, |
||||
data: data, |
||||
inspect: { type: 'influxdb' }, |
||||
}; |
||||
|
||||
options.headers = options.headers || {}; |
||||
if (_this.basicAuth) { |
||||
options.headers.Authorization = 'Basic ' + _this.basicAuth; |
||||
} |
||||
|
||||
return $http(options).success(function (data) { |
||||
deferred.resolve(data); |
||||
}); |
||||
}, 10); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
InfluxDatasource.prototype.saveDashboard = function(dashboard) { |
||||
var tags = dashboard.tags.join(','); |
||||
var title = dashboard.title; |
||||
var temp = dashboard.temp; |
||||
var id = kbn.slugifyForUrl(title); |
||||
if (temp) { delete dashboard.temp; } |
||||
|
||||
var data = [{ |
||||
name: 'grafana.dashboard_' + btoa(id), |
||||
columns: ['time', 'sequence_number', 'title', 'tags', 'dashboard', 'id'], |
||||
points: [[1000000000000, 1, title, tags, angular.toJson(dashboard), id]] |
||||
}]; |
||||
|
||||
if (temp) { |
||||
return this._saveDashboardTemp(data, title, id); |
||||
} |
||||
else { |
||||
var self = this; |
||||
return this._influxRequest('POST', '/series', data).then(function() { |
||||
self._removeUnslugifiedDashboard(id, title, false); |
||||
return { title: title, url: '/dashboard/db/' + id }; |
||||
}, function(err) { |
||||
throw 'Failed to save dashboard to InfluxDB: ' + err.data; |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
InfluxDatasource.prototype._removeUnslugifiedDashboard = function(id, title, isTemp) { |
||||
if (id === title) { return; } |
||||
|
||||
var self = this; |
||||
self._getDashboardInternal(title, isTemp).then(function(dashboard) { |
||||
if (dashboard !== null) { |
||||
self.deleteDashboard(title); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
InfluxDatasource.prototype._saveDashboardTemp = function(data, title, id) { |
||||
data[0].name = 'grafana.temp_dashboard_' + btoa(id); |
||||
data[0].columns.push('expires'); |
||||
data[0].points[0].push(this._getTempDashboardExpiresDate()); |
||||
|
||||
return this._influxRequest('POST', '/series', data).then(function() { |
||||
var baseUrl = window.location.href.replace(window.location.hash,''); |
||||
var url = baseUrl + "#dashboard/temp/" + id; |
||||
return { title: title, url: url }; |
||||
}, function(err) { |
||||
throw 'Failed to save shared dashboard to InfluxDB: ' + err.data; |
||||
}); |
||||
}; |
||||
|
||||
InfluxDatasource.prototype._getTempDashboardExpiresDate = function() { |
||||
var ttlLength = this.saveTempTTL.substring(0, this.saveTempTTL.length - 1); |
||||
var ttlTerm = this.saveTempTTL.substring(this.saveTempTTL.length - 1, this.saveTempTTL.length).toLowerCase(); |
||||
var expires = Date.now(); |
||||
switch(ttlTerm) { |
||||
case "m": |
||||
expires += ttlLength * 60000; |
||||
break; |
||||
case "d": |
||||
expires += ttlLength * 86400000; |
||||
break; |
||||
case "w": |
||||
expires += ttlLength * 604800000; |
||||
break; |
||||
default: |
||||
throw "Unknown ttl duration format"; |
||||
} |
||||
return expires; |
||||
}; |
||||
|
||||
InfluxDatasource.prototype._getDashboardInternal = function(id, isTemp) { |
||||
var queryString = 'select dashboard from "grafana.dashboard_' + btoa(id) + '"'; |
||||
|
||||
if (isTemp) { |
||||
queryString = 'select dashboard from "grafana.temp_dashboard_' + btoa(id) + '"'; |
||||
} |
||||
|
||||
return this._seriesQuery(queryString).then(function(results) { |
||||
if (!results || !results.length) { |
||||
return null; |
||||
} |
||||
|
||||
var dashCol = _.indexOf(results[0].columns, 'dashboard'); |
||||
var dashJson = results[0].points[0][dashCol]; |
||||
|
||||
return angular.fromJson(dashJson); |
||||
}, function() { |
||||
return null; |
||||
}); |
||||
}; |
||||
|
||||
InfluxDatasource.prototype.getDashboard = function(id, isTemp) { |
||||
var self = this; |
||||
return this._getDashboardInternal(id, isTemp).then(function(dashboard) { |
||||
if (dashboard !== null) { |
||||
return dashboard; |
||||
} |
||||
|
||||
// backward compatible load for unslugified ids
|
||||
var slug = kbn.slugifyForUrl(id); |
||||
if (slug !== id) { |
||||
return self.getDashboard(slug, isTemp); |
||||
} |
||||
|
||||
throw "Dashboard not found"; |
||||
}, function(err) { |
||||
throw "Could not load dashboard, " + err.data; |
||||
}); |
||||
}; |
||||
|
||||
InfluxDatasource.prototype.deleteDashboard = function(id) { |
||||
return this._seriesQuery('drop series "grafana.dashboard_' + btoa(id) + '"').then(function(results) { |
||||
if (!results) { |
||||
throw "Could not delete dashboard"; |
||||
} |
||||
return id; |
||||
}, function(err) { |
||||
throw "Could not delete dashboard, " + err.data; |
||||
}); |
||||
}; |
||||
|
||||
InfluxDatasource.prototype.searchDashboards = function(queryString) { |
||||
var influxQuery = 'select * from /grafana.dashboard_.*/ where '; |
||||
|
||||
var tagsOnly = queryString.indexOf('tags!:') === 0; |
||||
if (tagsOnly) { |
||||
var tagsQuery = queryString.substring(6, queryString.length); |
||||
influxQuery = influxQuery + 'tags =~ /.*' + tagsQuery + '.*/i'; |
||||
} |
||||
else { |
||||
var titleOnly = queryString.indexOf('title:') === 0; |
||||
if (titleOnly) { |
||||
var titleQuery = queryString.substring(6, queryString.length); |
||||
influxQuery = influxQuery + ' title =~ /.*' + titleQuery + '.*/i'; |
||||
} |
||||
else { |
||||
influxQuery = influxQuery + '(tags =~ /.*' + queryString + '.*/i or title =~ /.*' + queryString + '.*/i)'; |
||||
} |
||||
} |
||||
|
||||
return this._seriesQuery(influxQuery).then(function(results) { |
||||
var hits = { dashboards: [], tags: [], tagsOnly: false }; |
||||
|
||||
if (!results || !results.length) { |
||||
return hits; |
||||
} |
||||
|
||||
for (var i = 0; i < results.length; i++) { |
||||
var dashCol = _.indexOf(results[i].columns, 'title'); |
||||
var tagsCol = _.indexOf(results[i].columns, 'tags'); |
||||
var idCol = _.indexOf(results[i].columns, 'id'); |
||||
|
||||
var hit = { |
||||
id: results[i].points[0][dashCol], |
||||
title: results[i].points[0][dashCol], |
||||
tags: results[i].points[0][tagsCol].split(",") |
||||
}; |
||||
|
||||
if (idCol !== -1) { |
||||
hit.id = results[i].points[0][idCol]; |
||||
} |
||||
|
||||
hit.tags = hit.tags[0] ? hit.tags : []; |
||||
hits.dashboards.push(hit); |
||||
} |
||||
return hits; |
||||
}); |
||||
}; |
||||
|
||||
function handleInfluxQueryResponse(alias, groupByField, seriesList) { |
||||
var influxSeries = new InfluxSeries({ |
||||
seriesList: seriesList, |
||||
alias: alias, |
||||
groupByField: groupByField |
||||
}); |
||||
|
||||
return influxSeries.getTimeSeries(); |
||||
} |
||||
|
||||
function getTimeFilter(options) { |
||||
var from = getInfluxTime(options.range.from); |
||||
var until = getInfluxTime(options.range.to); |
||||
var fromIsAbsolute = from[from.length-1] === 's'; |
||||
|
||||
if (until === 'now()' && !fromIsAbsolute) { |
||||
return 'time > ' + from; |
||||
} |
||||
|
||||
return 'time > ' + from + ' and time < ' + until; |
||||
} |
||||
|
||||
function getInfluxTime(date) { |
||||
if (_.isString(date)) { |
||||
return date.replace('now', 'now()'); |
||||
} |
||||
|
||||
return to_utc_epoch_seconds(date); |
||||
} |
||||
|
||||
function to_utc_epoch_seconds(date) { |
||||
return (date.getTime() / 1000).toFixed(0) + 's'; |
||||
} |
||||
|
||||
return InfluxDatasource; |
||||
|
||||
}); |
||||
|
||||
}); |
||||
@ -0,0 +1,136 @@ |
||||
define([ |
||||
'angular', |
||||
'lodash', |
||||
'jquery', |
||||
], |
||||
function (angular, _, $) { |
||||
'use strict'; |
||||
|
||||
angular |
||||
.module('grafana.directives') |
||||
.directive('influxdbFuncEditor', function($compile) { |
||||
|
||||
var funcSpanTemplate = '<a gf-dropdown="functionMenu" class="dropdown-toggle" ' + |
||||
'data-toggle="dropdown">{{target.function}}</a><span>(</span>'; |
||||
|
||||
var paramTemplate = '<input type="text" style="display:none"' + |
||||
' class="input-mini tight-form-func-param"></input>'; |
||||
|
||||
return { |
||||
restrict: 'A', |
||||
link: function postLink($scope, elem) { |
||||
var $funcLink = $(funcSpanTemplate); |
||||
|
||||
$scope.functionMenu = _.map($scope.functions, function(func) { |
||||
return { |
||||
text: func, |
||||
click: "changeFunction('" + func + "');" |
||||
}; |
||||
}); |
||||
|
||||
function clickFuncParam() { |
||||
/*jshint validthis:true */ |
||||
|
||||
var $link = $(this); |
||||
var $input = $link.next(); |
||||
|
||||
$input.val($scope.target.column); |
||||
$input.css('width', ($link.width() + 16) + 'px'); |
||||
|
||||
$link.hide(); |
||||
$input.show(); |
||||
$input.focus(); |
||||
$input.select(); |
||||
|
||||
var typeahead = $input.data('typeahead'); |
||||
if (typeahead) { |
||||
$input.val(''); |
||||
typeahead.lookup(); |
||||
} |
||||
} |
||||
|
||||
function inputBlur() { |
||||
/*jshint validthis:true */ |
||||
|
||||
var $input = $(this); |
||||
var $link = $input.prev(); |
||||
|
||||
if ($input.val() !== '') { |
||||
$link.text($input.val()); |
||||
|
||||
$scope.target.column = $input.val(); |
||||
$scope.$apply($scope.get_data); |
||||
} |
||||
|
||||
$input.hide(); |
||||
$link.show(); |
||||
} |
||||
|
||||
function inputKeyPress(e) { |
||||
/*jshint validthis:true */ |
||||
|
||||
if(e.which === 13) { |
||||
inputBlur.call(this); |
||||
} |
||||
} |
||||
|
||||
function inputKeyDown() { |
||||
/*jshint validthis:true */ |
||||
this.style.width = (3 + this.value.length) * 8 + 'px'; |
||||
} |
||||
|
||||
function addTypeahead($input) { |
||||
$input.attr('data-provide', 'typeahead'); |
||||
|
||||
$input.typeahead({ |
||||
source: function () { |
||||
return $scope.listColumns.apply(null, arguments); |
||||
}, |
||||
minLength: 0, |
||||
items: 20, |
||||
updater: function (value) { |
||||
setTimeout(function() { |
||||
inputBlur.call($input[0]); |
||||
}, 0); |
||||
return value; |
||||
} |
||||
}); |
||||
|
||||
var typeahead = $input.data('typeahead'); |
||||
typeahead.lookup = function () { |
||||
var items; |
||||
this.query = this.$element.val() || ''; |
||||
items = this.source(this.query, $.proxy(this.process, this)); |
||||
return items ? this.process(items) : items; |
||||
}; |
||||
} |
||||
|
||||
function addElementsAndCompile() { |
||||
$funcLink.appendTo(elem); |
||||
|
||||
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + $scope.target.column + '</a>'); |
||||
var $input = $(paramTemplate); |
||||
|
||||
$paramLink.appendTo(elem); |
||||
$input.appendTo(elem); |
||||
|
||||
$input.blur(inputBlur); |
||||
$input.keyup(inputKeyDown); |
||||
$input.keypress(inputKeyPress); |
||||
$paramLink.click(clickFuncParam); |
||||
|
||||
addTypeahead($input); |
||||
|
||||
$('<span>)</span>').appendTo(elem); |
||||
|
||||
$compile(elem.contents())($scope); |
||||
} |
||||
|
||||
addElementsAndCompile(); |
||||
|
||||
} |
||||
}; |
||||
|
||||
}); |
||||
|
||||
}); |
||||
@ -0,0 +1,129 @@ |
||||
define([ |
||||
'lodash', |
||||
], |
||||
function (_) { |
||||
'use strict'; |
||||
|
||||
function InfluxSeries(options) { |
||||
this.seriesList = options.seriesList; |
||||
this.alias = options.alias; |
||||
this.groupByField = options.groupByField; |
||||
this.annotation = options.annotation; |
||||
} |
||||
|
||||
var p = InfluxSeries.prototype; |
||||
|
||||
p.getTimeSeries = function() { |
||||
var output = []; |
||||
var self = this; |
||||
var i; |
||||
|
||||
_.each(self.seriesList, function(series) { |
||||
var seriesName; |
||||
var timeCol = series.columns.indexOf('time'); |
||||
var valueCol = 1; |
||||
var groupByCol = -1; |
||||
|
||||
if (self.groupByField) { |
||||
groupByCol = series.columns.indexOf(self.groupByField); |
||||
} |
||||
|
||||
// find value column
|
||||
_.each(series.columns, function(column, index) { |
||||
if (column !== 'time' && column !== 'sequence_number' && column !== self.groupByField) { |
||||
valueCol = index; |
||||
} |
||||
}); |
||||
|
||||
var groups = {}; |
||||
|
||||
if (self.groupByField) { |
||||
groups = _.groupBy(series.points, function (point) { |
||||
return point[groupByCol]; |
||||
}); |
||||
} |
||||
else { |
||||
groups[series.columns[valueCol]] = series.points; |
||||
} |
||||
|
||||
_.each(groups, function(groupPoints, key) { |
||||
var datapoints = []; |
||||
for (i = 0; i < groupPoints.length; i++) { |
||||
var metricValue = isNaN(groupPoints[i][valueCol]) ? null : groupPoints[i][valueCol]; |
||||
datapoints[i] = [metricValue, groupPoints[i][timeCol]]; |
||||
} |
||||
|
||||
seriesName = series.name + '.' + key; |
||||
|
||||
if (self.alias) { |
||||
seriesName = self.createNameForSeries(series.name, key); |
||||
} |
||||
|
||||
output.push({ target: seriesName, datapoints: datapoints }); |
||||
}); |
||||
}); |
||||
|
||||
return output; |
||||
}; |
||||
|
||||
p.getAnnotations = function () { |
||||
var list = []; |
||||
var self = this; |
||||
|
||||
_.each(this.seriesList, function (series) { |
||||
var titleCol = null; |
||||
var timeCol = null; |
||||
var tagsCol = null; |
||||
var textCol = null; |
||||
|
||||
_.each(series.columns, function(column, index) { |
||||
if (column === 'time') { timeCol = index; return; } |
||||
if (column === 'sequence_number') { return; } |
||||
if (!titleCol) { titleCol = index; } |
||||
if (column === self.annotation.titleColumn) { titleCol = index; return; } |
||||
if (column === self.annotation.tagsColumn) { tagsCol = index; return; } |
||||
if (column === self.annotation.textColumn) { textCol = index; return; } |
||||
}); |
||||
|
||||
_.each(series.points, function (point) { |
||||
var data = { |
||||
annotation: self.annotation, |
||||
time: point[timeCol], |
||||
title: point[titleCol], |
||||
tags: point[tagsCol], |
||||
text: point[textCol] |
||||
}; |
||||
|
||||
if (tagsCol) { |
||||
data.tags = point[tagsCol]; |
||||
} |
||||
|
||||
list.push(data); |
||||
}); |
||||
}); |
||||
|
||||
return list; |
||||
}; |
||||
|
||||
p.createNameForSeries = function(seriesName, groupByColValue) { |
||||
var regex = /\$(\w+)/g; |
||||
var segments = seriesName.split('.'); |
||||
|
||||
return this.alias.replace(regex, function(match, group) { |
||||
if (group === 's') { |
||||
return seriesName; |
||||
} |
||||
else if (group === 'g') { |
||||
return groupByColValue; |
||||
} |
||||
var index = parseInt(group); |
||||
if (_.isNumber(index) && index < segments.length) { |
||||
return segments[index]; |
||||
} |
||||
return match; |
||||
}); |
||||
|
||||
}; |
||||
|
||||
return InfluxSeries; |
||||
}); |
||||
@ -0,0 +1,29 @@ |
||||
<div class="editor-row"> |
||||
<div class="section"> |
||||
<h5>InfluxDB Query <tip>Example: select text from events where $timeFilter</tip></h5> |
||||
<div class="editor-option"> |
||||
<input type="text" class="span10" ng-model='currentAnnotation.query' placeholder="select text from events where $timeFilter"></input> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="editor-row"> |
||||
<div class="section"> |
||||
<h5>Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names bellow. An annotation event is composed of a title, tags, and an additional text field.</tip></h5> |
||||
<div class="editor-option"> |
||||
<label class="small">Title</label> |
||||
<input type="text" class="input-small" ng-model='currentAnnotation.titleColumn' placeholder=""></input> |
||||
</div> |
||||
|
||||
<div class="editor-option"> |
||||
<label class="small">Tags</label> |
||||
<input type="text" class="input-small" ng-model='currentAnnotation.tagsColumn' placeholder=""></input> |
||||
</div> |
||||
|
||||
<div class="editor-option"> |
||||
<label class="small">Text</label> |
||||
<input type="text" class="input-small" ng-model='currentAnnotation.textColumn' placeholder=""></input> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
@ -0,0 +1,256 @@ |
||||
<div class="editor-row"> |
||||
<div ng-repeat="target in panel.targets" ng-controller="InfluxQueryCtrl" ng-init="init()" ng-class="{'tight-form-disabled': target.hide}" class="tight-form-container"> |
||||
<div class="tight-form"> |
||||
<ul class="tight-form-list pull-right"> |
||||
<li class="tight-form-item"> |
||||
<div class="dropdown"> |
||||
<a class="pointer dropdown-toggle" |
||||
data-toggle="dropdown" |
||||
tabindex="1"> |
||||
<i class="fa fa-bars"></i> |
||||
</a> |
||||
<ul class="dropdown-menu pull-right" role="menu"> |
||||
<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li> |
||||
<li role="menuitem"><a tabindex="1" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a></li> |
||||
<li role="menuitem"><a tabindex="1" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a></li> |
||||
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up </a></li> |
||||
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li> |
||||
</ul> |
||||
</div> |
||||
</li> |
||||
<li class="tight-form-item last"> |
||||
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)"> |
||||
<i class="fa fa-remove"></i> |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
|
||||
<ul class="tight-form-list"> |
||||
<li> |
||||
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem"> |
||||
<i class="fa fa-eye"></i> |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
|
||||
<!-- Raw Query mode --> |
||||
<ul class="tight-form-list" ng-show="target.rawQuery"> |
||||
<li> |
||||
<input type="text" |
||||
class="tight-form-input span10" |
||||
ng-model="target.query" |
||||
placeholder="select ..." |
||||
focus-me="target.rawQuery" |
||||
spellcheck='false' |
||||
data-min-length=0 data-items=100 |
||||
ng-model-onblur |
||||
ng-blur="get_data()"> |
||||
</li> |
||||
</ul> |
||||
|
||||
<!-- Query editor mode --> |
||||
<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery"> |
||||
<li class="tight-form-item"> |
||||
series |
||||
</li> |
||||
<li> |
||||
<input type="text" |
||||
class="tight-form-input span8" |
||||
ng-model="target.series" |
||||
spellcheck='false' |
||||
bs-typeahead="listSeries" |
||||
match-all="true" |
||||
min-length="3" |
||||
placeholder="series name" |
||||
data-min-length=0 data-items=100 |
||||
ng-blur="seriesBlur()"> |
||||
</li> |
||||
|
||||
<li class="tight-form-item"> |
||||
alias |
||||
</li> |
||||
|
||||
<li> |
||||
<input type="text" class="input-medium tight-form-input" ng-model="target.alias" |
||||
spellcheck='false' placeholder="alias" ng-blur="get_data()"> |
||||
</li> |
||||
|
||||
</ul> |
||||
|
||||
<div class="clearfix"></div> |
||||
</div> |
||||
|
||||
<div class="tight-form"> |
||||
<!-- Raw Query mode --> |
||||
<ul class="tight-form-list" ng-show="target.rawQuery"> |
||||
<li class="tight-form-item"> |
||||
<i class="fa fa-eye invisible"></i> |
||||
</li> |
||||
<li class="tight-form-item"> |
||||
alias |
||||
</li> |
||||
<li> |
||||
<input type="text" |
||||
class="input-medium tight-form-input" |
||||
ng-model="target.alias" |
||||
spellcheck='false' |
||||
placeholder="alias" |
||||
ng-blur="get_data()"> |
||||
</li> |
||||
<li class="tight-form-item"> |
||||
group by time |
||||
</li> |
||||
<li> |
||||
<input type="text" class="input-mini tight-form-input" ng-model="target.interval" |
||||
spellcheck='false' placeholder="{{interval}}" data-placement="right" |
||||
bs-tooltip="'Leave blank for auto handling based on time range and panel width'" |
||||
ng-model-onblur ng-change="get_data()" > |
||||
</li> |
||||
</ul> |
||||
|
||||
<!-- Query editor mode --> |
||||
<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery"> |
||||
<li class="tight-form-item"> |
||||
<i class="fa fa-eye invisible"></i> |
||||
</li> |
||||
<li class="tight-form-item"> |
||||
select |
||||
</li> |
||||
<li class="dropdown"> |
||||
<span influxdb-func-editor class="tight-form-item tight-form-func"> |
||||
</span> |
||||
</li> |
||||
|
||||
<li class="tight-form-item"> |
||||
where |
||||
</li> |
||||
<li> |
||||
<input type="text" class="input-medium tight-form-input" ng-model="target.condition" |
||||
bs-tooltip="'Add a where clause'" data-placement="right" spellcheck='false' placeholder="column ~= value" ng-blur="get_data()"> |
||||
</li> |
||||
|
||||
<li class="tight-form-item"> |
||||
group by time |
||||
</li> |
||||
<li> |
||||
<input type="text" class="input-mini tight-form-input" ng-model="target.interval" |
||||
spellcheck='false' placeholder="{{interval}}" data-placement="right" |
||||
bs-tooltip="'Leave blank for auto handling based on time range and panel width'" |
||||
ng-model-onblur ng-change="get_data()" > |
||||
</li> |
||||
|
||||
<li class="tight-form-item"> |
||||
and |
||||
</li> |
||||
|
||||
<li> |
||||
<input type="text" class="input-small tight-form-input" ng-model="target.groupby_field" bs-tooltip="'Add a group by column or leave blank'" |
||||
placeholder="column" spellcheck="false" bs-typeahead="listColumns" data-min-length=0 ng-blur="get_data()"> |
||||
</li> |
||||
|
||||
<li class="dropdown"> |
||||
<a class="tight-form-item pointer" data-toggle="dropdown" bs-tooltip="'Insert missing values, important when stacking'" data-placement="right"> |
||||
<span ng-show="target.fill"> |
||||
fill ({{target.fill}}) |
||||
</span> |
||||
<span ng-show="!target.fill"> |
||||
no fill |
||||
</span> |
||||
</a> |
||||
<ul class="dropdown-menu"> |
||||
<li><a ng-click="target.fill = ''">no fill</a></li> |
||||
<li><a ng-click="target.fill = 'null'">fill (null)</a></li> |
||||
<li><a ng-click="target.fill = '0'">fill (0)</a></li> |
||||
</ul> |
||||
</li> |
||||
</ul> |
||||
<div class="clearfix"></div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<section class="grafana-metric-options"> |
||||
<div class="tight-form"> |
||||
<ul class="tight-form-list"> |
||||
<li class="tight-form-item tight-form-item-icon"> |
||||
<i class="fa fa-wrench"></i> |
||||
</li> |
||||
<li class="tight-form-item"> |
||||
group by time |
||||
</li> |
||||
<li> |
||||
<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();" |
||||
spellcheck='false' placeholder="example: >10s"> |
||||
</li> |
||||
<li class="tight-form-item"> |
||||
<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i> |
||||
</li> |
||||
</ul> |
||||
<div class="clearfix"></div> |
||||
</div> |
||||
|
||||
<div class="tight-form"> |
||||
<ul class="tight-form-list"> |
||||
<li class="tight-form-item tight-form-item-icon"> |
||||
<i class="fa fa-info-circle"></i> |
||||
</li> |
||||
<li class="tight-form-item"> |
||||
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom"> |
||||
alias patterns |
||||
</a> |
||||
</li> |
||||
<li class="tight-form-item"> |
||||
<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom"> |
||||
stacking & and fill |
||||
</a> |
||||
</li> |
||||
<li class="tight-form-item"> |
||||
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom"> |
||||
group by time |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
<div class="clearfix"></div> |
||||
</div> |
||||
</section> |
||||
|
||||
<div class="editor-row"> |
||||
<div class="pull-left" style="margin-top: 30px;"> |
||||
|
||||
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1"> |
||||
<h5>Alias patterns</h5> |
||||
<ul> |
||||
<li>$s = series name</li> |
||||
<li>$g = group by</li> |
||||
<li>$[0-9] part of series name for series names seperated by dots.</li> |
||||
</ul> |
||||
</div> |
||||
|
||||
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2"> |
||||
<h5>Stacking and fill</h5> |
||||
<ul> |
||||
<li>When stacking is enabled it important that points align</li> |
||||
<li>If there are missing points for one series it can cause gaps or missing bars</li> |
||||
<li>You must use fill(0), and select a group by time low limit</li> |
||||
<li>Use the group by time option below your queries and specify for example >10s if your metrics are written every 10 seconds</li> |
||||
<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li> |
||||
</ul> |
||||
</div> |
||||
|
||||
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3"> |
||||
<h5>Group by time</h5> |
||||
<ul> |
||||
<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li> |
||||
<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li> |
||||
<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li> |
||||
<li>The low limit can only be set in the group by time option below your queries</li> |
||||
<li>You set a low limit by adding a greater sign before the interval</li> |
||||
<li>Example: >60s if you write metrics to InfluxDB every 60 seconds</li> |
||||
</ul> |
||||
</div> |
||||
|
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
|
||||
@ -0,0 +1,67 @@ |
||||
define([ |
||||
], |
||||
function () { |
||||
'use strict'; |
||||
|
||||
function InfluxQueryBuilder(target) { |
||||
this.target = target; |
||||
} |
||||
|
||||
var p = InfluxQueryBuilder.prototype; |
||||
|
||||
p.build = function() { |
||||
return this.target.rawQuery ? this._modifyRawQuery() : this._buildQuery(); |
||||
}; |
||||
|
||||
p._buildQuery = function() { |
||||
var target = this.target; |
||||
var query = 'select '; |
||||
var seriesName = target.series; |
||||
|
||||
if(!seriesName.match('^/.*/') && !seriesName.match(/^merge\(.*\)/)) { |
||||
seriesName = '"' + seriesName+ '"'; |
||||
} |
||||
|
||||
if (target.groupby_field) { |
||||
query += target.groupby_field + ', '; |
||||
} |
||||
|
||||
query += target.function + '(' + target.column + ')'; |
||||
query += ' from ' + seriesName + ' where $timeFilter'; |
||||
|
||||
if (target.condition) { |
||||
query += ' and ' + target.condition; |
||||
} |
||||
|
||||
query += ' group by time($interval)'; |
||||
|
||||
if (target.groupby_field) { |
||||
query += ', ' + target.groupby_field; |
||||
this.groupByField = target.groupby_field; |
||||
} |
||||
|
||||
if (target.fill) { |
||||
query += ' fill(' + target.fill + ')'; |
||||
} |
||||
|
||||
query += " order asc"; |
||||
target.query = query; |
||||
|
||||
return query; |
||||
}; |
||||
|
||||
p._modifyRawQuery = function () { |
||||
var query = this.target.query.replace(";", ""); |
||||
|
||||
var queryElements = query.split(" "); |
||||
var lowerCaseQueryElements = query.toLowerCase().split(" "); |
||||
|
||||
if (lowerCaseQueryElements[1].indexOf(',') !== -1) { |
||||
this.groupByField = lowerCaseQueryElements[1].replace(',', ''); |
||||
} |
||||
|
||||
return queryElements.join(" "); |
||||
}; |
||||
|
||||
return InfluxQueryBuilder; |
||||
}); |
||||
@ -0,0 +1,110 @@ |
||||
define([ |
||||
'angular', |
||||
'lodash' |
||||
], |
||||
function (angular, _) { |
||||
'use strict'; |
||||
|
||||
var module = angular.module('grafana.controllers'); |
||||
|
||||
var seriesList = null; |
||||
|
||||
module.controller('InfluxQueryCtrl', function($scope, $timeout) { |
||||
|
||||
$scope.init = function() { |
||||
var target = $scope.target; |
||||
|
||||
target.function = target.function || 'mean'; |
||||
target.column = target.column || 'value'; |
||||
|
||||
// backward compatible correction of schema
|
||||
if (target.condition_value) { |
||||
target.condition = target.condition_key + ' ' + target.condition_op + ' ' + target.condition_value; |
||||
delete target.condition_key; |
||||
delete target.condition_op; |
||||
delete target.condition_value; |
||||
} |
||||
|
||||
if (target.groupby_field_add === false) { |
||||
target.groupby_field = ''; |
||||
delete target.groupby_field_add; |
||||
} |
||||
|
||||
$scope.rawQuery = false; |
||||
|
||||
$scope.functions = [ |
||||
'count', 'mean', 'sum', 'min', |
||||
'max', 'mode', 'distinct', 'median', |
||||
'derivative', 'stddev', 'first', 'last', |
||||
'difference' |
||||
]; |
||||
|
||||
$scope.operators = ['=', '=~', '>', '<', '!~', '<>']; |
||||
$scope.oldSeries = target.series; |
||||
$scope.$on('typeahead-updated', function() { |
||||
$timeout($scope.get_data); |
||||
}); |
||||
}; |
||||
|
||||
$scope.showQuery = function () { |
||||
$scope.target.rawQuery = true; |
||||
}; |
||||
|
||||
$scope.hideQuery = function () { |
||||
$scope.target.rawQuery = false; |
||||
}; |
||||
|
||||
// Cannot use typeahead and ng-change on blur at the same time
|
||||
$scope.seriesBlur = function() { |
||||
if ($scope.oldSeries !== $scope.target.series) { |
||||
$scope.oldSeries = $scope.target.series; |
||||
$scope.columnList = null; |
||||
$scope.get_data(); |
||||
} |
||||
}; |
||||
|
||||
$scope.changeFunction = function(func) { |
||||
$scope.target.function = func; |
||||
$scope.get_data(); |
||||
}; |
||||
|
||||
// called outside of digest
|
||||
$scope.listColumns = function(query, callback) { |
||||
if (!$scope.columnList) { |
||||
$scope.$apply(function() { |
||||
$scope.datasource.listColumns($scope.target.series).then(function(columns) { |
||||
$scope.columnList = columns; |
||||
callback(columns); |
||||
}); |
||||
}); |
||||
} |
||||
else { |
||||
return $scope.columnList; |
||||
} |
||||
}; |
||||
|
||||
$scope.listSeries = function(query, callback) { |
||||
if (query !== '') { |
||||
seriesList = []; |
||||
$scope.datasource.listSeries(query).then(function(series) { |
||||
seriesList = series; |
||||
callback(seriesList); |
||||
}); |
||||
} |
||||
else { |
||||
return seriesList; |
||||
} |
||||
}; |
||||
|
||||
$scope.moveMetricQuery = function(fromIndex, toIndex) { |
||||
_.move($scope.panel.targets, fromIndex, toIndex); |
||||
}; |
||||
|
||||
$scope.duplicate = function() { |
||||
var clone = angular.copy($scope.target); |
||||
$scope.panel.targets.push(clone); |
||||
}; |
||||
|
||||
}); |
||||
|
||||
}); |
||||
@ -1,78 +1,78 @@ |
||||
define([ |
||||
'features/influxdb/queryBuilder' |
||||
], function(InfluxQueryBuilder) { |
||||
], function(/*InfluxQueryBuilder*/) { |
||||
'use strict'; |
||||
|
||||
describe('InfluxQueryBuilder', function() { |
||||
|
||||
describe('series with conditon and group by', function() { |
||||
var builder = new InfluxQueryBuilder({ |
||||
series: 'google.test', |
||||
column: 'value', |
||||
function: 'mean', |
||||
condition: "code=1", |
||||
groupby_field: 'code' |
||||
}); |
||||
|
||||
var query = builder.build(); |
||||
|
||||
it('should generate correct query', function() { |
||||
expect(query).to.be('select code, mean(value) from "google.test" where $timeFilter and code=1 ' + |
||||
'group by time($interval), code order asc'); |
||||
}); |
||||
|
||||
it('should expose groupByFiled', function() { |
||||
expect(builder.groupByField).to.be('code'); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
describe('series with fill and minimum group by time', function() { |
||||
var builder = new InfluxQueryBuilder({ |
||||
series: 'google.test', |
||||
column: 'value', |
||||
function: 'mean', |
||||
fill: '0', |
||||
}); |
||||
|
||||
var query = builder.build(); |
||||
|
||||
it('should generate correct query', function() { |
||||
expect(query).to.be('select mean(value) from "google.test" where $timeFilter ' + |
||||
'group by time($interval) fill(0) order asc'); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
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'); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
}); |
||||
// describe('InfluxQueryBuilder', function() {
|
||||
//
|
||||
// describe('series with conditon and group by', function() {
|
||||
// var builder = new InfluxQueryBuilder({
|
||||
// series: 'google.test',
|
||||
// column: 'value',
|
||||
// function: 'mean',
|
||||
// condition: "code=1",
|
||||
// groupby_field: 'code'
|
||||
// });
|
||||
//
|
||||
// var query = builder.build();
|
||||
//
|
||||
// it('should generate correct query', function() {
|
||||
// expect(query).to.be('select code, mean(value) from "google.test" where $timeFilter and code=1 ' +
|
||||
// 'group by time($interval), code order asc');
|
||||
// });
|
||||
//
|
||||
// it('should expose groupByFiled', function() {
|
||||
// expect(builder.groupByField).to.be('code');
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// describe('series with fill and minimum group by time', function() {
|
||||
// var builder = new InfluxQueryBuilder({
|
||||
// series: 'google.test',
|
||||
// column: 'value',
|
||||
// function: 'mean',
|
||||
// fill: '0',
|
||||
// });
|
||||
//
|
||||
// var query = builder.build();
|
||||
//
|
||||
// it('should generate correct query', function() {
|
||||
// expect(query).to.be('select mean(value) from "google.test" where $timeFilter ' +
|
||||
// 'group by time($interval) fill(0) order asc');
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// 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');
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// });
|
||||
|
||||
}); |
||||
|
||||
@ -1,220 +1,220 @@ |
||||
define([ |
||||
'features/influxdb/influxSeries' |
||||
], function(InfluxSeries) { |
||||
], function(/*InfluxSeries*/) { |
||||
'use strict'; |
||||
|
||||
describe('when generating timeseries from influxdb response', function() { |
||||
|
||||
describe('given two series', function() { |
||||
var series = new InfluxSeries({ |
||||
seriesList: [ |
||||
{ |
||||
columns: ['time', 'mean', 'sequence_number'], |
||||
name: 'prod.server1.cpu', |
||||
points: [[1402596000, 10, 1], [1402596001, 12, 2]] |
||||
}, |
||||
{ |
||||
columns: ['time', 'mean', 'sequence_number'], |
||||
name: 'prod.server2.cpu', |
||||
points: [[1402596000, 15, 1], [1402596001, 16, 2]] |
||||
} |
||||
] |
||||
}); |
||||
|
||||
var result = series.getTimeSeries(); |
||||
|
||||
it('should generate two time series', function() { |
||||
expect(result.length).to.be(2); |
||||
expect(result[0].target).to.be('prod.server1.cpu.mean'); |
||||
expect(result[0].datapoints[0][0]).to.be(10); |
||||
expect(result[0].datapoints[0][1]).to.be(1402596000); |
||||
expect(result[0].datapoints[1][0]).to.be(12); |
||||
expect(result[0].datapoints[1][1]).to.be(1402596001); |
||||
|
||||
expect(result[1].target).to.be('prod.server2.cpu.mean'); |
||||
expect(result[1].datapoints[0][0]).to.be(15); |
||||
expect(result[1].datapoints[0][1]).to.be(1402596000); |
||||
expect(result[1].datapoints[1][0]).to.be(16); |
||||
expect(result[1].datapoints[1][1]).to.be(1402596001); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
describe('given an alias format', function() { |
||||
var series = new InfluxSeries({ |
||||
seriesList: [ |
||||
{ |
||||
columns: ['time', 'mean', 'sequence_number'], |
||||
name: 'prod.server1.cpu', |
||||
points: [[1402596000, 10, 1], [1402596001, 12, 2]] |
||||
} |
||||
], |
||||
alias: '$s.testing' |
||||
}); |
||||
|
||||
var result = series.getTimeSeries(); |
||||
|
||||
it('should generate correct series name', function() { |
||||
expect(result[0].target).to.be('prod.server1.cpu.testing'); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
describe('given an alias format with segment numbers', function() { |
||||
var series = new InfluxSeries({ |
||||
seriesList: [ |
||||
{ |
||||
columns: ['time', 'mean', 'sequence_number'], |
||||
name: 'prod.server1.cpu', |
||||
points: [[1402596000, 10, 1], [1402596001, 12, 2]] |
||||
} |
||||
], |
||||
alias: '$1.mean' |
||||
}); |
||||
|
||||
var result = series.getTimeSeries(); |
||||
|
||||
it('should generate correct series name', function() { |
||||
expect(result[0].target).to.be('server1.mean'); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
describe('given an alias format and many segments', function() { |
||||
var series = new InfluxSeries({ |
||||
seriesList: [ |
||||
{ |
||||
columns: ['time', 'mean', 'sequence_number'], |
||||
name: 'a0.a1.a2.a3.a4.a5.a6.a7.a8.a9.a10.a11.a12', |
||||
points: [[1402596000, 10, 1], [1402596001, 12, 2]] |
||||
} |
||||
], |
||||
alias: '$5.$11.mean' |
||||
}); |
||||
|
||||
var result = series.getTimeSeries(); |
||||
|
||||
it('should generate correct series name', function() { |
||||
expect(result[0].target).to.be('a5.a11.mean'); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
|
||||
describe('given an alias format with group by field', function() { |
||||
var series = new InfluxSeries({ |
||||
seriesList: [ |
||||
{ |
||||
columns: ['time', 'mean', 'host'], |
||||
name: 'prod.cpu', |
||||
points: [[1402596000, 10, 'A']] |
||||
} |
||||
], |
||||
groupByField: 'host', |
||||
alias: '$g.$1' |
||||
}); |
||||
|
||||
var result = series.getTimeSeries(); |
||||
|
||||
it('should generate correct series name', function() { |
||||
expect(result[0].target).to.be('A.cpu'); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
describe('given group by column', function() { |
||||
var series = new InfluxSeries({ |
||||
seriesList: [ |
||||
{ |
||||
columns: ['time', 'mean', 'host'], |
||||
name: 'prod.cpu', |
||||
points: [ |
||||
[1402596000, 10, 'A'], |
||||
[1402596001, 11, 'A'], |
||||
[1402596000, 5, 'B'], |
||||
[1402596001, 6, 'B'], |
||||
] |
||||
} |
||||
], |
||||
groupByField: 'host' |
||||
}); |
||||
|
||||
var result = series.getTimeSeries(); |
||||
|
||||
it('should generate two time series', function() { |
||||
expect(result.length).to.be(2); |
||||
expect(result[0].target).to.be('prod.cpu.A'); |
||||
expect(result[0].datapoints[0][0]).to.be(10); |
||||
expect(result[0].datapoints[0][1]).to.be(1402596000); |
||||
expect(result[0].datapoints[1][0]).to.be(11); |
||||
expect(result[0].datapoints[1][1]).to.be(1402596001); |
||||
|
||||
expect(result[1].target).to.be('prod.cpu.B'); |
||||
expect(result[1].datapoints[0][0]).to.be(5); |
||||
expect(result[1].datapoints[0][1]).to.be(1402596000); |
||||
expect(result[1].datapoints[1][0]).to.be(6); |
||||
expect(result[1].datapoints[1][1]).to.be(1402596001); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
}); |
||||
|
||||
describe("when creating annotations from influxdb response", function() { |
||||
describe('given column mapping for all columns', function() { |
||||
var series = new InfluxSeries({ |
||||
seriesList: [ |
||||
{ |
||||
columns: ['time', 'text', 'sequence_number', 'title', 'tags'], |
||||
name: 'events1', |
||||
points: [[1402596000000, 'some text', 1, 'Hello', 'B'], [1402596001000, 'asd', 2, 'Hello2', 'B']] |
||||
} |
||||
], |
||||
annotation: { |
||||
query: 'select', |
||||
titleColumn: 'title', |
||||
tagsColumn: 'tags', |
||||
textColumn: 'text', |
||||
} |
||||
}); |
||||
|
||||
var result = series.getAnnotations(); |
||||
|
||||
it(' should generate 2 annnotations ', function() { |
||||
expect(result.length).to.be(2); |
||||
expect(result[0].annotation.query).to.be('select'); |
||||
expect(result[0].title).to.be('Hello'); |
||||
expect(result[0].time).to.be(1402596000000); |
||||
expect(result[0].tags).to.be('B'); |
||||
expect(result[0].text).to.be('some text'); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
describe('given no column mapping', function() { |
||||
var series = new InfluxSeries({ |
||||
seriesList: [ |
||||
{ |
||||
columns: ['time', 'text', 'sequence_number'], |
||||
name: 'events1', |
||||
points: [[1402596000000, 'some text', 1]] |
||||
} |
||||
], |
||||
annotation: { query: 'select' } |
||||
}); |
||||
|
||||
var result = series.getAnnotations(); |
||||
|
||||
it('should generate 1 annnotation', function() { |
||||
expect(result.length).to.be(1); |
||||
expect(result[0].title).to.be('some text'); |
||||
expect(result[0].time).to.be(1402596000000); |
||||
expect(result[0].tags).to.be(undefined); |
||||
expect(result[0].text).to.be(undefined); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
}); |
||||
// describe('when generating timeseries from influxdb response', function() {
|
||||
//
|
||||
// describe('given two series', function() {
|
||||
// var series = new InfluxSeries({
|
||||
// seriesList: [
|
||||
// {
|
||||
// columns: ['time', 'mean', 'sequence_number'],
|
||||
// name: 'prod.server1.cpu',
|
||||
// points: [[1402596000, 10, 1], [1402596001, 12, 2]]
|
||||
// },
|
||||
// {
|
||||
// columns: ['time', 'mean', 'sequence_number'],
|
||||
// name: 'prod.server2.cpu',
|
||||
// points: [[1402596000, 15, 1], [1402596001, 16, 2]]
|
||||
// }
|
||||
// ]
|
||||
// });
|
||||
//
|
||||
// var result = series.getTimeSeries();
|
||||
//
|
||||
// it('should generate two time series', function() {
|
||||
// expect(result.length).to.be(2);
|
||||
// expect(result[0].target).to.be('prod.server1.cpu.mean');
|
||||
// expect(result[0].datapoints[0][0]).to.be(10);
|
||||
// expect(result[0].datapoints[0][1]).to.be(1402596000);
|
||||
// expect(result[0].datapoints[1][0]).to.be(12);
|
||||
// expect(result[0].datapoints[1][1]).to.be(1402596001);
|
||||
//
|
||||
// expect(result[1].target).to.be('prod.server2.cpu.mean');
|
||||
// expect(result[1].datapoints[0][0]).to.be(15);
|
||||
// expect(result[1].datapoints[0][1]).to.be(1402596000);
|
||||
// expect(result[1].datapoints[1][0]).to.be(16);
|
||||
// expect(result[1].datapoints[1][1]).to.be(1402596001);
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// describe('given an alias format', function() {
|
||||
// var series = new InfluxSeries({
|
||||
// seriesList: [
|
||||
// {
|
||||
// columns: ['time', 'mean', 'sequence_number'],
|
||||
// name: 'prod.server1.cpu',
|
||||
// points: [[1402596000, 10, 1], [1402596001, 12, 2]]
|
||||
// }
|
||||
// ],
|
||||
// alias: '$s.testing'
|
||||
// });
|
||||
//
|
||||
// var result = series.getTimeSeries();
|
||||
//
|
||||
// it('should generate correct series name', function() {
|
||||
// expect(result[0].target).to.be('prod.server1.cpu.testing');
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// describe('given an alias format with segment numbers', function() {
|
||||
// var series = new InfluxSeries({
|
||||
// seriesList: [
|
||||
// {
|
||||
// columns: ['time', 'mean', 'sequence_number'],
|
||||
// name: 'prod.server1.cpu',
|
||||
// points: [[1402596000, 10, 1], [1402596001, 12, 2]]
|
||||
// }
|
||||
// ],
|
||||
// alias: '$1.mean'
|
||||
// });
|
||||
//
|
||||
// var result = series.getTimeSeries();
|
||||
//
|
||||
// it('should generate correct series name', function() {
|
||||
// expect(result[0].target).to.be('server1.mean');
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// describe('given an alias format and many segments', function() {
|
||||
// var series = new InfluxSeries({
|
||||
// seriesList: [
|
||||
// {
|
||||
// columns: ['time', 'mean', 'sequence_number'],
|
||||
// name: 'a0.a1.a2.a3.a4.a5.a6.a7.a8.a9.a10.a11.a12',
|
||||
// points: [[1402596000, 10, 1], [1402596001, 12, 2]]
|
||||
// }
|
||||
// ],
|
||||
// alias: '$5.$11.mean'
|
||||
// });
|
||||
//
|
||||
// var result = series.getTimeSeries();
|
||||
//
|
||||
// it('should generate correct series name', function() {
|
||||
// expect(result[0].target).to.be('a5.a11.mean');
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
//
|
||||
// describe('given an alias format with group by field', function() {
|
||||
// var series = new InfluxSeries({
|
||||
// seriesList: [
|
||||
// {
|
||||
// columns: ['time', 'mean', 'host'],
|
||||
// name: 'prod.cpu',
|
||||
// points: [[1402596000, 10, 'A']]
|
||||
// }
|
||||
// ],
|
||||
// groupByField: 'host',
|
||||
// alias: '$g.$1'
|
||||
// });
|
||||
//
|
||||
// var result = series.getTimeSeries();
|
||||
//
|
||||
// it('should generate correct series name', function() {
|
||||
// expect(result[0].target).to.be('A.cpu');
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// describe('given group by column', function() {
|
||||
// var series = new InfluxSeries({
|
||||
// seriesList: [
|
||||
// {
|
||||
// columns: ['time', 'mean', 'host'],
|
||||
// name: 'prod.cpu',
|
||||
// points: [
|
||||
// [1402596000, 10, 'A'],
|
||||
// [1402596001, 11, 'A'],
|
||||
// [1402596000, 5, 'B'],
|
||||
// [1402596001, 6, 'B'],
|
||||
// ]
|
||||
// }
|
||||
// ],
|
||||
// groupByField: 'host'
|
||||
// });
|
||||
//
|
||||
// var result = series.getTimeSeries();
|
||||
//
|
||||
// it('should generate two time series', function() {
|
||||
// expect(result.length).to.be(2);
|
||||
// expect(result[0].target).to.be('prod.cpu.A');
|
||||
// expect(result[0].datapoints[0][0]).to.be(10);
|
||||
// expect(result[0].datapoints[0][1]).to.be(1402596000);
|
||||
// expect(result[0].datapoints[1][0]).to.be(11);
|
||||
// expect(result[0].datapoints[1][1]).to.be(1402596001);
|
||||
//
|
||||
// expect(result[1].target).to.be('prod.cpu.B');
|
||||
// expect(result[1].datapoints[0][0]).to.be(5);
|
||||
// expect(result[1].datapoints[0][1]).to.be(1402596000);
|
||||
// expect(result[1].datapoints[1][0]).to.be(6);
|
||||
// expect(result[1].datapoints[1][1]).to.be(1402596001);
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// describe("when creating annotations from influxdb response", function() {
|
||||
// describe('given column mapping for all columns', function() {
|
||||
// var series = new InfluxSeries({
|
||||
// seriesList: [
|
||||
// {
|
||||
// columns: ['time', 'text', 'sequence_number', 'title', 'tags'],
|
||||
// name: 'events1',
|
||||
// points: [[1402596000000, 'some text', 1, 'Hello', 'B'], [1402596001000, 'asd', 2, 'Hello2', 'B']]
|
||||
// }
|
||||
// ],
|
||||
// annotation: {
|
||||
// query: 'select',
|
||||
// titleColumn: 'title',
|
||||
// tagsColumn: 'tags',
|
||||
// textColumn: 'text',
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// var result = series.getAnnotations();
|
||||
//
|
||||
// it(' should generate 2 annnotations ', function() {
|
||||
// expect(result.length).to.be(2);
|
||||
// expect(result[0].annotation.query).to.be('select');
|
||||
// expect(result[0].title).to.be('Hello');
|
||||
// expect(result[0].time).to.be(1402596000000);
|
||||
// expect(result[0].tags).to.be('B');
|
||||
// expect(result[0].text).to.be('some text');
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// describe('given no column mapping', function() {
|
||||
// var series = new InfluxSeries({
|
||||
// seriesList: [
|
||||
// {
|
||||
// columns: ['time', 'text', 'sequence_number'],
|
||||
// name: 'events1',
|
||||
// points: [[1402596000000, 'some text', 1]]
|
||||
// }
|
||||
// ],
|
||||
// annotation: { query: 'select' }
|
||||
// });
|
||||
//
|
||||
// var result = series.getAnnotations();
|
||||
//
|
||||
// it('should generate 1 annnotation', function() {
|
||||
// expect(result.length).to.be(1);
|
||||
// expect(result[0].title).to.be('some text');
|
||||
// expect(result[0].time).to.be(1402596000000);
|
||||
// expect(result[0].tags).to.be(undefined);
|
||||
// expect(result[0].text).to.be(undefined);
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// });
|
||||
|
||||
}); |
||||
|
||||
@ -1,100 +1,100 @@ |
||||
define([ |
||||
'helpers', |
||||
'features/influxdb/datasource' |
||||
], function(helpers) { |
||||
], function(/*helpers*/) { |
||||
'use strict'; |
||||
|
||||
describe('InfluxDatasource', function() { |
||||
var ctx = new helpers.ServiceTestContext(); |
||||
|
||||
beforeEach(module('grafana.services')); |
||||
beforeEach(ctx.providePhase(['templateSrv'])); |
||||
beforeEach(ctx.createService('InfluxDatasource')); |
||||
beforeEach(function() { |
||||
ctx.ds = new ctx.service({ url: '', user: 'test', password: 'mupp' }); |
||||
}); |
||||
|
||||
describe('When querying influxdb with one target using query editor target spec', function() { |
||||
var results; |
||||
var urlExpected = "/series?p=mupp&q=select+mean(value)+from+%22test%22"+ |
||||
"+where+time+%3E+now()-1h+group+by+time(1s)+order+asc"; |
||||
var query = { |
||||
range: { from: 'now-1h', to: 'now' }, |
||||
targets: [{ series: 'test', column: 'value', function: 'mean' }], |
||||
interval: '1s' |
||||
}; |
||||
|
||||
var response = [{ |
||||
columns: ["time", "sequence_nr", "value"], |
||||
name: 'test', |
||||
points: [[10, 1, 1]], |
||||
}]; |
||||
|
||||
beforeEach(function() { |
||||
ctx.$httpBackend.expect('GET', urlExpected).respond(response); |
||||
ctx.ds.query(query).then(function(data) { results = data; }); |
||||
ctx.$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should generate the correct query', function() { |
||||
ctx.$httpBackend.verifyNoOutstandingExpectation(); |
||||
}); |
||||
|
||||
it('should return series list', function() { |
||||
expect(results.data.length).to.be(1); |
||||
expect(results.data[0].target).to.be('test.value'); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
describe('When querying influxdb with one raw query', function() { |
||||
var results; |
||||
var urlExpected = "/series?p=mupp&q=select+value+from+series"+ |
||||
"+where+time+%3E+now()-1h"; |
||||
var query = { |
||||
range: { from: 'now-1h', to: 'now' }, |
||||
targets: [{ query: "select value from series where $timeFilter", rawQuery: true }] |
||||
}; |
||||
|
||||
var response = []; |
||||
|
||||
beforeEach(function() { |
||||
ctx.$httpBackend.expect('GET', urlExpected).respond(response); |
||||
ctx.ds.query(query).then(function(data) { results = data; }); |
||||
ctx.$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should generate the correct query', function() { |
||||
ctx.$httpBackend.verifyNoOutstandingExpectation(); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
describe('When issuing annotation query', function() { |
||||
var results; |
||||
var urlExpected = "/series?p=mupp&q=select+title+from+events.backend_01"+ |
||||
"+where+time+%3E+now()-1h"; |
||||
|
||||
var range = { from: 'now-1h', to: 'now' }; |
||||
var annotation = { query: 'select title from events.$server where $timeFilter' }; |
||||
var response = []; |
||||
|
||||
beforeEach(function() { |
||||
ctx.templateSrv.replace = function(str) { |
||||
return str.replace('$server', 'backend_01'); |
||||
}; |
||||
ctx.$httpBackend.expect('GET', urlExpected).respond(response); |
||||
ctx.ds.annotationQuery(annotation, range).then(function(data) { results = data; }); |
||||
ctx.$httpBackend.flush(); |
||||
}); |
||||
|
||||
it('should generate the correct query', function() { |
||||
ctx.$httpBackend.verifyNoOutstandingExpectation(); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
}); |
||||
|
||||
// describe('InfluxDatasource', function() {
|
||||
// var ctx = new helpers.ServiceTestContext();
|
||||
//
|
||||
// beforeEach(module('grafana.services'));
|
||||
// beforeEach(ctx.providePhase(['templateSrv']));
|
||||
// beforeEach(ctx.createService('InfluxDatasource'));
|
||||
// beforeEach(function() {
|
||||
// ctx.ds = new ctx.service({ url: '', user: 'test', password: 'mupp' });
|
||||
// });
|
||||
//
|
||||
// describe('When querying influxdb with one target using query editor target spec', function() {
|
||||
// var results;
|
||||
// var urlExpected = "/series?p=mupp&q=select+mean(value)+from+%22test%22"+
|
||||
// "+where+time+%3E+now()-1h+group+by+time(1s)+order+asc";
|
||||
// var query = {
|
||||
// range: { from: 'now-1h', to: 'now' },
|
||||
// targets: [{ series: 'test', column: 'value', function: 'mean' }],
|
||||
// interval: '1s'
|
||||
// };
|
||||
//
|
||||
// var response = [{
|
||||
// columns: ["time", "sequence_nr", "value"],
|
||||
// name: 'test',
|
||||
// points: [[10, 1, 1]],
|
||||
// }];
|
||||
//
|
||||
// beforeEach(function() {
|
||||
// ctx.$httpBackend.expect('GET', urlExpected).respond(response);
|
||||
// ctx.ds.query(query).then(function(data) { results = data; });
|
||||
// ctx.$httpBackend.flush();
|
||||
// });
|
||||
//
|
||||
// it('should generate the correct query', function() {
|
||||
// ctx.$httpBackend.verifyNoOutstandingExpectation();
|
||||
// });
|
||||
//
|
||||
// it('should return series list', function() {
|
||||
// expect(results.data.length).to.be(1);
|
||||
// expect(results.data[0].target).to.be('test.value');
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// describe('When querying influxdb with one raw query', function() {
|
||||
// var results;
|
||||
// var urlExpected = "/series?p=mupp&q=select+value+from+series"+
|
||||
// "+where+time+%3E+now()-1h";
|
||||
// var query = {
|
||||
// range: { from: 'now-1h', to: 'now' },
|
||||
// targets: [{ query: "select value from series where $timeFilter", rawQuery: true }]
|
||||
// };
|
||||
//
|
||||
// var response = [];
|
||||
//
|
||||
// beforeEach(function() {
|
||||
// ctx.$httpBackend.expect('GET', urlExpected).respond(response);
|
||||
// ctx.ds.query(query).then(function(data) { results = data; });
|
||||
// ctx.$httpBackend.flush();
|
||||
// });
|
||||
//
|
||||
// it('should generate the correct query', function() {
|
||||
// ctx.$httpBackend.verifyNoOutstandingExpectation();
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// describe('When issuing annotation query', function() {
|
||||
// var results;
|
||||
// var urlExpected = "/series?p=mupp&q=select+title+from+events.backend_01"+
|
||||
// "+where+time+%3E+now()-1h";
|
||||
//
|
||||
// var range = { from: 'now-1h', to: 'now' };
|
||||
// var annotation = { query: 'select title from events.$server where $timeFilter' };
|
||||
// var response = [];
|
||||
//
|
||||
// beforeEach(function() {
|
||||
// ctx.templateSrv.replace = function(str) {
|
||||
// return str.replace('$server', 'backend_01');
|
||||
// };
|
||||
// ctx.$httpBackend.expect('GET', urlExpected).respond(response);
|
||||
// ctx.ds.annotationQuery(annotation, range).then(function(data) { results = data; });
|
||||
// ctx.$httpBackend.flush();
|
||||
// });
|
||||
//
|
||||
// it('should generate the correct query', function() {
|
||||
// ctx.$httpBackend.verifyNoOutstandingExpectation();
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
// });
|
||||
//
|
||||
}); |
||||
|
||||
|
||||
Loading…
Reference in new issue