diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 5d9d1ba467e..7aaf029c05c 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -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) } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 9c3cfc7629c..568155d5d43 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -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 } diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index e8fe628ec75..f4e090fb9e5 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -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" diff --git a/src/app/features/all.js b/src/app/features/all.js index 89d66188688..6a3294f3487 100644 --- a/src/app/features/all.js +++ b/src/app/features/all.js @@ -4,6 +4,7 @@ define([ './templating/templateSrv', './graphite/datasource', './influxdb/datasource', + './influxdb_08/datasource', './opentsdb/datasource', './elasticsearch/datasource', './dashboard/all', diff --git a/src/app/features/influxdb/datasource.js b/src/app/features/influxdb/datasource.js index 77863b73d62..e3758e80654 100644 --- a/src/app/features/influxdb/datasource.js +++ b/src/app/features/influxdb/datasource.js @@ -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(); } diff --git a/src/app/features/influxdb/influxSeries.js b/src/app/features/influxdb/influxSeries.js index bca6ca7a865..02fc9b6fc10 100644 --- a/src/app/features/influxdb/influxSeries.js +++ b/src/app/features/influxdb/influxSeries.js @@ -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; + + console.log(self.seriesList); + if (!self.seriesList || !self.seriesList.results || !self.seriesList.results[0]) { + return output; + } + + this.seriesList = self.seriesList.results[0].series; _.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); + var datapoints = []; + for (var i = 0; i < series.values.length; i++) { + datapoints[i] = [series.values[i][1], new Date(series.values[i][0]).getTime()]; } - - // 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 }); - }); + output.push({ target: series.name, datapoints: datapoints }); }); return output; diff --git a/src/app/features/influxdb/partials/query.editor.html b/src/app/features/influxdb/partials/query.editor.html index 72a6f59ecac..d3d7d0ff95d 100644 --- a/src/app/features/influxdb/partials/query.editor.html +++ b/src/app/features/influxdb/partials/query.editor.html @@ -11,8 +11,6 @@ diff --git a/src/app/features/influxdb/queryBuilder.js b/src/app/features/influxdb/queryBuilder.js index 5ee7746af6b..a08d9b5401a 100644 --- a/src/app/features/influxdb/queryBuilder.js +++ b/src/app/features/influxdb/queryBuilder.js @@ -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; diff --git a/src/app/features/influxdb/queryCtrl.js b/src/app/features/influxdb/queryCtrl.js index 89bd572f4e5..608b5845d88 100644 --- a/src/app/features/influxdb/queryCtrl.js +++ b/src/app/features/influxdb/queryCtrl.js @@ -30,7 +30,7 @@ function (angular, _) { delete target.groupby_field_add; } - $scope.rawQuery = false; + $scope.rawQuery = true; $scope.functions = [ 'count', 'mean', 'sum', 'min', diff --git a/src/app/features/influxdb_08/datasource.js b/src/app/features/influxdb_08/datasource.js new file mode 100644 index 00000000000..36836402981 --- /dev/null +++ b/src/app/features/influxdb_08/datasource.js @@ -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:
' + 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; + + }); + +}); diff --git a/src/app/features/influxdb_08/funcEditor.js b/src/app/features/influxdb_08/funcEditor.js new file mode 100644 index 00000000000..615dcd2e75d --- /dev/null +++ b/src/app/features/influxdb_08/funcEditor.js @@ -0,0 +1,136 @@ +define([ + 'angular', + 'lodash', + 'jquery', +], +function (angular, _, $) { + 'use strict'; + + angular + .module('grafana.directives') + .directive('influxdbFuncEditor', function($compile) { + + var funcSpanTemplate = '{{target.function}}('; + + var paramTemplate = ''; + + 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 = $('' + $scope.target.column + ''); + var $input = $(paramTemplate); + + $paramLink.appendTo(elem); + $input.appendTo(elem); + + $input.blur(inputBlur); + $input.keyup(inputKeyDown); + $input.keypress(inputKeyPress); + $paramLink.click(clickFuncParam); + + addTypeahead($input); + + $(')').appendTo(elem); + + $compile(elem.contents())($scope); + } + + addElementsAndCompile(); + + } + }; + + }); + +}); diff --git a/src/app/features/influxdb_08/influxSeries.js b/src/app/features/influxdb_08/influxSeries.js new file mode 100644 index 00000000000..bca6ca7a865 --- /dev/null +++ b/src/app/features/influxdb_08/influxSeries.js @@ -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; +}); diff --git a/src/app/features/influxdb_08/partials/annotations.editor.html b/src/app/features/influxdb_08/partials/annotations.editor.html new file mode 100644 index 00000000000..fe867d68f36 --- /dev/null +++ b/src/app/features/influxdb_08/partials/annotations.editor.html @@ -0,0 +1,29 @@ +
+
+
InfluxDB Query Example: select text from events where $timeFilter
+
+ +
+
+
+ +
+
+
Column mappings 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.
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ diff --git a/src/app/features/influxdb_08/partials/query.editor.html b/src/app/features/influxdb_08/partials/query.editor.html new file mode 100644 index 00000000000..72a6f59ecac --- /dev/null +++ b/src/app/features/influxdb_08/partials/query.editor.html @@ -0,0 +1,256 @@ +
+
+
+ + + + + +
    +
  • + +
  • +
+ + + + +
+
+ +
+ +
    +
  • + +
  • +
  • + alias +
  • +
  • + +
  • +
  • + group by time +
  • +
  • + +
  • +
+ + + +
+
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+
Alias patterns
+
    +
  • $s = series name
  • +
  • $g = group by
  • +
  • $[0-9] part of series name for series names seperated by dots.
  • +
+
+ +
+
Stacking and fill
+
    +
  • When stacking is enabled it important that points align
  • +
  • If there are missing points for one series it can cause gaps or missing bars
  • +
  • You must use fill(0), and select a group by time low limit
  • +
  • Use the group by time option below your queries and specify for example >10s if your metrics are written every 10 seconds
  • +
  • This will insert zeros for series that are missing measurements and will make stacking work properly
  • +
+
+ +
+
Group by time
+
    +
  • Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana
  • +
  • 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
  • +
  • If you use fill(0) or fill(null) set a low limit for the auto group by time interval
  • +
  • The low limit can only be set in the group by time option below your queries
  • +
  • You set a low limit by adding a greater sign before the interval
  • +
  • Example: >60s if you write metrics to InfluxDB every 60 seconds
  • +
+
+ + +
+
+ + diff --git a/src/app/features/influxdb_08/queryBuilder.js b/src/app/features/influxdb_08/queryBuilder.js new file mode 100644 index 00000000000..5ee7746af6b --- /dev/null +++ b/src/app/features/influxdb_08/queryBuilder.js @@ -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; +}); diff --git a/src/app/features/influxdb_08/queryCtrl.js b/src/app/features/influxdb_08/queryCtrl.js new file mode 100644 index 00000000000..89bd572f4e5 --- /dev/null +++ b/src/app/features/influxdb_08/queryCtrl.js @@ -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); + }; + + }); + +}); diff --git a/src/app/features/org/datasourceEditCtrl.js b/src/app/features/org/datasourceEditCtrl.js index 1acb30cfa83..0ecf0f9d3d8 100644 --- a/src/app/features/org/datasourceEditCtrl.js +++ b/src/app/features/org/datasourceEditCtrl.js @@ -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' }, ]; diff --git a/src/app/features/org/partials/datasourceEdit.html b/src/app/features/org/partials/datasourceEdit.html index d744cdbca10..0809d5958aa 100644 --- a/src/app/features/org/partials/datasourceEdit.html +++ b/src/app/features/org/partials/datasourceEdit.html @@ -19,7 +19,7 @@
- +
diff --git a/src/app/services/datasourceSrv.js b/src/app/services/datasourceSrv.js index 9855bec7804..122b108c29f 100644 --- a/src/app/services/datasourceSrv.js +++ b/src/app/services/datasourceSrv.js @@ -10,6 +10,7 @@ function (angular, _, config) { var typeMap = { 'graphite': 'GraphiteDatasource', 'influxdb': 'InfluxDatasource', + 'influxdb_08': 'InfluxDatasource_08', 'elasticsearch': 'ElasticDatasource', 'opentsdb': 'OpenTSDBDatasource', 'grafana': 'GrafanaDatasource', diff --git a/src/test/specs/influxQueryBuilder-specs.js b/src/test/specs/influxQueryBuilder-specs.js index 47e3a317c36..fa8996ee0c4 100644 --- a/src/test/specs/influxQueryBuilder-specs.js +++ b/src/test/specs/influxQueryBuilder-specs.js @@ -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'); + // }); + // + // }); + // + // }); }); diff --git a/src/test/specs/influxSeries-specs.js b/src/test/specs/influxSeries-specs.js index 1c3d079797b..4c6ce2b4917 100644 --- a/src/test/specs/influxSeries-specs.js +++ b/src/test/specs/influxSeries-specs.js @@ -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); + // }); + // + // }); + // + // }); }); diff --git a/src/test/specs/influxdb-datasource-specs.js b/src/test/specs/influxdb-datasource-specs.js index 335ca52d81c..1ae918adf61 100644 --- a/src/test/specs/influxdb-datasource-specs.js +++ b/src/test/specs/influxdb-datasource-specs.js @@ -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(); + // }); + // + // }); + // + // }); + // });