Commit ae7f18f9 by Torkel Ödegaard

Made a copy of influxdb datasource named influxdb_08 so the main influxdb data…

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 f5f07bd5
......@@ -26,6 +26,12 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy
reqQueryVals.Add("u", ds.User)
reqQueryVals.Add("p", ds.Password)
req.URL.RawQuery = reqQueryVals.Encode()
} else if ds.Type == m.DS_INFLUXDB_08 {
req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
reqQueryVals.Add("db", ds.Database)
reqQueryVals.Add("u", ds.User)
reqQueryVals.Add("p", ds.Password)
req.URL.RawQuery = reqQueryVals.Encode()
} else {
req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
}
......
......@@ -38,7 +38,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
"default": ds.IsDefault,
}
if ds.Type == m.DS_INFLUXDB {
if ds.Type == m.DS_INFLUXDB_08 {
if ds.Access == m.DS_ACCESS_DIRECT {
dsMap["username"] = ds.User
dsMap["password"] = ds.Password
......@@ -46,6 +46,15 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
}
}
if ds.Type == m.DS_INFLUXDB {
if ds.Access == m.DS_ACCESS_DIRECT {
dsMap["username"] = ds.User
dsMap["password"] = ds.Password
dsMap["database"] = ds.Database
dsMap["url"] = url
}
}
if ds.Type == m.DS_ES {
dsMap["index"] = ds.Database
}
......
......@@ -8,6 +8,7 @@ import (
const (
DS_GRAPHITE = "graphite"
DS_INFLUXDB = "influxdb"
DS_INFLUXDB_08 = "influxdb_08"
DS_ES = "elasticsearch"
DS_OPENTSDB = "opentsdb"
DS_ACCESS_DIRECT = "direct"
......
......@@ -4,6 +4,7 @@ define([
'./templating/templateSrv',
'./graphite/datasource',
'./influxdb/datasource',
'./influxdb_08/datasource',
'./opentsdb/datasource',
'./elasticsearch/datasource',
'./dashboard/all',
......
......@@ -19,9 +19,11 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
this.urls = _.map(datasource.url.split(','), function(url) {
return url.trim();
});
this.username = datasource.username;
this.password = datasource.password;
this.name = datasource.name;
this.database = datasource.database;
this.basicAuth = datasource.basicAuth;
this.grafanaDB = datasource.grafanaDB;
......@@ -55,7 +57,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
var alias = target.alias ? templateSrv.replace(target.alias) : '';
var handleResponse = _.partial(handleInfluxQueryResponse, alias, queryBuilder.groupByField);
var handleResponse = _.partial(handleInfluxQueryResponse, alias);
return this._seriesQuery(query).then(handleResponse);
}, this);
......@@ -98,7 +100,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
query = '/' + query + '/';
}
return this._seriesQuery('list series ' + query).then(function(data) {
return this._seriesQuery('SHOW MEASUREMENTS').then(function(data) {
if (!data || data.length === 0) {
return [];
}
......@@ -145,24 +147,28 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
}
InfluxDatasource.prototype._seriesQuery = function(query) {
return this._influxRequest('GET', '/series', {
return this._influxRequest('GET', '/query', {
q: query,
});
};
InfluxDatasource.prototype._influxRequest = function(method, url, data) {
var _this = this;
var self = this;
var deferred = $q.defer();
retry(deferred, function() {
var currentUrl = _this.urls.shift();
_this.urls.push(currentUrl);
var currentUrl = self.urls.shift();
self.urls.push(currentUrl);
var params = {
u: _this.username,
p: _this.password,
u: self.username,
p: self.password,
};
if (self.database) {
params.db = self.database;
}
if (method === 'GET') {
_.extend(params, data);
data = null;
......@@ -173,12 +179,13 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
url: currentUrl + url,
params: params,
data: data,
precision: "ms",
inspect: { type: 'influxdb' },
};
options.headers = options.headers || {};
if (_this.basicAuth) {
options.headers.Authorization = 'Basic ' + _this.basicAuth;
if (self.basicAuth) {
options.headers.Authorization = 'Basic ' + self.basicAuth;
}
return $http(options).success(function (data) {
......@@ -360,13 +367,8 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
});
};
function handleInfluxQueryResponse(alias, groupByField, seriesList) {
var influxSeries = new InfluxSeries({
seriesList: seriesList,
alias: alias,
groupByField: groupByField
});
function handleInfluxQueryResponse(alias, seriesList) {
var influxSeries = new InfluxSeries({ seriesList: seriesList, alias: alias });
return influxSeries.getTimeSeries();
}
......
......@@ -7,7 +7,6 @@ function (_) {
function InfluxSeries(options) {
this.seriesList = options.seriesList;
this.alias = options.alias;
this.groupByField = options.groupByField;
this.annotation = options.annotation;
}
......@@ -16,51 +15,20 @@ function (_) {
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;
}
});
console.log(self.seriesList);
if (!self.seriesList || !self.seriesList.results || !self.seriesList.results[0]) {
return output;
}
var groups = {};
this.seriesList = self.seriesList.results[0].series;
if (self.groupByField) {
groups = _.groupBy(series.points, function (point) {
return point[groupByCol];
});
}
else {
groups[series.columns[valueCol]] = series.points;
_.each(self.seriesList, function(series) {
var datapoints = [];
for (var i = 0; i < series.values.length; i++) {
datapoints[i] = [series.values[i][1], new Date(series.values[i][0]).getTime()];
}
_.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 });
});
output.push({ target: series.name, datapoints: datapoints });
});
return output;
......
......@@ -11,8 +11,6 @@
</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>
......
......@@ -52,15 +52,7 @@ function () {
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 query;
};
return InfluxQueryBuilder;
......
......@@ -30,7 +30,7 @@ function (angular, _) {
delete target.groupby_field_add;
}
$scope.rawQuery = false;
$scope.rawQuery = true;
$scope.functions = [
'count', 'mean', 'sum', 'min',
......
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();
}
};
});
});
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;
});
<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>
<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 &amp; 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 &gt;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: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
</ul>
</div>
</div>
</div>
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;
});
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);
};
});
});
......@@ -18,7 +18,8 @@ function (angular) {
$scope.types = [
{ name: 'Graphite', type: 'graphite' },
{ name: 'InfluxDB', type: 'influxdb' },
{ name: 'InfluxDB 0.9.x (Experimental support)', type: 'influxdb' },
{ name: 'InfluxDB 0.8.x', type: 'influxdb_08' },
{ name: 'Elasticsearch', type: 'elasticsearch' },
{ name: 'OpenTSDB', type: 'opentsdb' },
];
......
......@@ -19,7 +19,7 @@
</div>
<div class="editor-option">
<label class="small">Type</label>
<select class="input-medium" ng-model="current.type" ng-options="f.type as f.name for f in types" ng-change="typeChanged()"></select>
<select class="input-large" ng-model="current.type" ng-options="f.type as f.name for f in types" ng-change="typeChanged()"></select>
</div>
<editor-opt-bool text="Mark as default" model="current.isDefault" change="render()"></editor-opt-bool>
</div>
......
......@@ -10,6 +10,7 @@ function (angular, _, config) {
var typeMap = {
'graphite': 'GraphiteDatasource',
'influxdb': 'InfluxDatasource',
'influxdb_08': 'InfluxDatasource_08',
'elasticsearch': 'ElasticDatasource',
'opentsdb': 'OpenTSDBDatasource',
'grafana': 'GrafanaDatasource',
......
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');
// });
//
// });
//
// });
});
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();
// });
//
// });
//
// });
//
});
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