فهرست منبع

Merge branch 'master' of github.com:grafana/grafana

Nick Christus 10 سال پیش
والد
کامیت
3d178c8eb6
26فایلهای تغییر یافته به همراه268 افزوده شده و 99 حذف شده
  1. 1 0
      .gitignore
  2. 3 1
      CHANGELOG.md
  3. 1 1
      README.md
  4. 1 6
      docs/sources/datasources/cloudwatch.md
  5. 5 18
      public/app/core/table_model.ts
  6. 6 1
      public/app/features/org/datasourceEditCtrl.js
  7. 18 2
      public/app/panels/table/controller.ts
  8. 1 1
      public/app/panels/table/specs/renderer_specs.ts
  9. 6 7
      public/app/panels/table/specs/transformers_specs.ts
  10. 39 1
      public/app/panels/table/transformers.ts
  11. 16 22
      public/app/plugins/datasource/cloudwatch/datasource.js
  12. 3 2
      public/app/plugins/datasource/cloudwatch/query_ctrl.js
  13. 1 1
      public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts
  14. 9 3
      public/app/plugins/datasource/elasticsearch/datasource.js
  15. 12 1
      public/app/plugins/datasource/elasticsearch/partials/config.html
  16. 0 1
      public/app/plugins/datasource/elasticsearch/partials/query.editor.html
  17. 15 5
      public/app/plugins/datasource/elasticsearch/query_builder.js
  18. 1 11
      public/app/plugins/datasource/elasticsearch/query_ctrl.js
  19. 33 8
      public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts
  20. 19 5
      public/app/plugins/datasource/influxdb/datasource.js
  21. 2 0
      public/app/plugins/datasource/influxdb/influx_query.ts
  22. 41 1
      public/app/plugins/datasource/influxdb/influx_series.js
  23. 6 0
      public/app/plugins/datasource/influxdb/partials/query.editor.html
  24. 5 0
      public/app/plugins/datasource/influxdb/query_ctrl.js
  25. 23 0
      public/app/plugins/datasource/influxdb/specs/influx_series_specs.ts
  26. 1 1
      public/test/core/table_model_specs.ts

+ 1 - 0
.gitignore

@@ -1,4 +1,5 @@
 node_modules
+npm-debug.log
 coverage/
 .aws-config.json
 awsconfig

+ 3 - 1
CHANGELOG.md

@@ -8,7 +8,6 @@
 * **Elasticsearch**: Support for dynamic daily indices for annotations, closes [#3061](https://github.com/grafana/grafana/issues/3061)
 * **Graph Panel**: Option to hide series with all zeroes from legend and tooltip, closes [#1381](https://github.com/grafana/grafana/issues/1381), [#3336](https://github.com/grafana/grafana/issues/3336)
 
-
 ### Bug Fixes
 * **cloudwatch**: fix for handling of period for long time ranges, fixes [#3086](https://github.com/grafana/grafana/issues/3086)
 * **dashboard**: fix for collapse row by clicking on row title, fixes [#3065](https://github.com/grafana/grafana/issues/3065)
@@ -16,6 +15,9 @@
 * **graph**: layout fix for color picker when right side legend was enabled, fixes [#3093](https://github.com/grafana/grafana/issues/3093)
 * **elasticsearch**: disabling elastic query (via eye) caused error, fixes [#3300](https://github.com/grafana/grafana/issues/3300)
 
+### Breaking changes
+* **elasticsearch**: Manual json edited queries are not supported any more (They very barely worked in 2.5)
+
 # 2.5 (2015-10-28)
 
 **New Feature: Mix data sources**

+ 1 - 1
README.md

@@ -90,7 +90,7 @@ Replace X.Y.Z by actual version number.
 cd $GOPATH/src/github.com/grafana/grafana
 go run build.go setup            (only needed once to install godep)
 godep restore                    (will pull down all golang lib dependencies in your current GOPATH)
-godep go run build.go build
+go run build.go build
 ```
 
 ### Building frontend assets

+ 1 - 6
docs/sources/datasources/cloudwatch.md

@@ -63,15 +63,10 @@ Name | Description
 `namespaces()` | Returns a list of namespaces CloudWatch support.
 `metrics(namespace)` | Returns a list of metrics in the namespace.
 `dimension_keys(namespace)` | Returns a list of dimension keys in the namespace.
-`dimension_values(region, namespace, metric)` | Returns a list of dimension values matching the specified `region`, `namespace` and `metric`.
+`dimension_values(region, namespace, metric, dimension_key)` | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`.
 
 For details about the metrics CloudWatch provides, please refer to the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html).
 
-If you want to filter dimension values by other dimension key/value pair, you can specify optional parameter like this.
-```sql
-dimension_values(region, namespace, metric, dim_key1=dim_val1,dim_key2=dim_val2,...)
-```
-
 ![](/img/v2/cloudwatch_templating.png)
 
 ## Cost

+ 5 - 18
public/app/panels/table/table_model.ts → public/app/core/table_model.ts

@@ -1,12 +1,13 @@
-import {transformers} from './transformers';
 
-export class TableModel {
+class TableModel {
   columns: any[];
   rows: any[];
+  type: string;
 
   constructor() {
     this.columns = [];
     this.rows = [];
+    this.type = 'table';
   }
 
   sort(options) {
@@ -33,20 +34,6 @@ export class TableModel {
       this.columns[options.col].desc = true;
     }
   }
-
-  static transform(data, panel) {
-    var model = new TableModel();
-
-    if (!data || data.length === 0) {
-      return model;
-    }
-
-    var transformer = transformers[panel.transform];
-    if (!transformer) {
-      throw {message: 'Transformer ' + panel.transformer + ' not found'};
-    }
-
-    transformer.transform(data, panel, model);
-    return model;
-  }
 }
+
+export = TableModel;

+ 6 - 1
public/app/features/org/datasourceEditCtrl.js

@@ -13,7 +13,7 @@ function (angular, _, config) {
 
     $scope.httpConfigPartialSrc = 'app/features/org/partials/datasourceHttpConfig.html';
 
-    var defaults = {name: '', type: 'graphite', url: '', access: 'proxy' };
+    var defaults = {name: '', type: 'graphite', url: '', access: 'proxy', jsonData: {}};
 
     $scope.indexPatternTypes = [
       {name: 'No pattern',  value: undefined},
@@ -24,6 +24,11 @@ function (angular, _, config) {
       {name: 'Yearly',      value: 'Yearly',  example: '[logstash-]YYYY'},
     ];
 
+    $scope.esVersions = [
+      {name: '1.x', value: 1},
+      {name: '2.x', value: 2},
+    ];
+
     $scope.init = function() {
       $scope.isNew = true;
       $scope.datasources = [];

+ 18 - 2
public/app/panels/table/controller.ts

@@ -5,7 +5,7 @@ import _ = require('lodash');
 import moment = require('moment');
 import PanelMeta = require('app/features/panel/panel_meta');
 
-import {TableModel} from './table_model';
+import {transformDataToTable} from './transformers';
 
 export class TablePanelCtrl {
 
@@ -104,7 +104,23 @@ export class TablePanelCtrl {
     };
 
     $scope.render = function() {
-      $scope.table = TableModel.transform($scope.dataRaw, $scope.panel);
+      // automatically correct transform mode
+      // based on data
+      if ($scope.dataRaw && $scope.dataRaw.length) {
+        if ($scope.dataRaw[0].type === 'table') {
+          $scope.panel.transform = 'table';
+        } else {
+          if ($scope.dataRaw[0].type === 'docs') {
+            $scope.panel.transform = 'json';
+          } else {
+            if ($scope.panel.transform === 'table' || $scope.panel.transform === 'json') {
+              $scope.panel.transform = 'timeseries_to_rows';
+            }
+          }
+        }
+      }
+
+      $scope.table = transformDataToTable($scope.dataRaw, $scope.panel);
       $scope.table.sort($scope.panel.sort);
       panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw);
     };

+ 1 - 1
public/app/panels/table/specs/renderer_specs.ts

@@ -1,6 +1,6 @@
 import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
 
-import {TableModel} from '../table_model';
+import TableModel = require('app/core/table_model');
 import {TableRenderer} from '../renderer';
 
 describe('when rendering table', () => {

+ 6 - 7
public/app/panels/table/specs/transformers_specs.ts

@@ -1,7 +1,6 @@
 import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
 
-import {TableModel} from '../table_model';
-import {transformers} from '../transformers';
+import {transformers, transformDataToTable} from '../transformers';
 
 describe('when transforming time series table', () => {
   var table;
@@ -26,7 +25,7 @@ describe('when transforming time series table', () => {
       };
 
       beforeEach(() => {
-        table = TableModel.transform(timeSeries, panel);
+        table = transformDataToTable(timeSeries, panel);
       });
 
       it('should return 3 rows', () => {
@@ -51,7 +50,7 @@ describe('when transforming time series table', () => {
       };
 
       beforeEach(() => {
-        table = TableModel.transform(timeSeries, panel);
+        table = transformDataToTable(timeSeries, panel);
       });
 
       it ('should return 3 columns', () => {
@@ -80,7 +79,7 @@ describe('when transforming time series table', () => {
       };
 
       beforeEach(() => {
-        table = TableModel.transform(timeSeries, panel);
+        table = transformDataToTable(timeSeries, panel);
       });
 
       it('should return 2 rows', () => {
@@ -133,7 +132,7 @@ describe('when transforming time series table', () => {
 
       describe('transform', function() {
         beforeEach(() => {
-          table = TableModel.transform(rawData, panel);
+          table = transformDataToTable(rawData, panel);
         });
 
         it ('should return 2 columns', () => {
@@ -164,7 +163,7 @@ describe('when transforming time series table', () => {
       ];
 
       beforeEach(() => {
-        table = TableModel.transform(rawData, panel);
+        table = transformDataToTable(rawData, panel);
       });
 
       it ('should return 4 columns', () => {

+ 39 - 1
public/app/panels/table/transformers.ts

@@ -4,6 +4,7 @@ import moment = require('moment');
 import _ = require('lodash');
 import flatten = require('app/core/utils/flatten');
 import TimeSeries = require('app/core/time_series');
+import TableModel = require('app/core/table_model');
 
 var transformers = {};
 
@@ -136,6 +137,27 @@ transformers['annotations'] = {
   }
 };
 
+transformers['table'] = {
+  description: 'Table',
+  getColumns: function(data) {
+    if (!data || data.length === 0) {
+      return [];
+    }
+  },
+  transform: function(data, panel, model) {
+    if (!data || data.length === 0) {
+      return;
+    }
+
+    if (data[0].type !== 'table') {
+      throw {message: 'Query result is not in table format, try using another transform.'};
+    }
+
+    model.columns = data[0].columns;
+    model.rows = data[0].rows;
+  }
+};
+
 transformers['json'] = {
   description: 'JSON Data',
   getColumns: function(data) {
@@ -197,4 +219,20 @@ transformers['json'] = {
   }
 };
 
-export {transformers}
+function transformDataToTable(data, panel) {
+  var model = new TableModel();
+
+  if (!data || data.length === 0) {
+    return model;
+  }
+
+  var transformer = transformers[panel.transform];
+  if (!transformer) {
+    throw {message: 'Transformer ' + panel.transformer + ' not found'};
+  }
+
+  transformer.transform(data, panel, model);
+  return model;
+}
+
+export {transformers, transformDataToTable}

+ 16 - 22
public/app/plugins/datasource/cloudwatch/datasource.js

@@ -113,23 +113,28 @@ function (angular, _) {
       });
     };
 
-    CloudWatchDatasource.prototype.getDimensionValues = function(region, namespace, metricName, dimensions) {
+    CloudWatchDatasource.prototype.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
       var request = {
         region: templateSrv.replace(region),
         action: 'ListMetrics',
         parameters: {
           namespace: templateSrv.replace(namespace),
           metricName: templateSrv.replace(metricName),
-          dimensions: convertDimensionFormat(dimensions, {}),
+          dimensions: convertDimensionFormat(filterDimensions, {}),
         }
       };
 
       return this.awsRequest(request).then(function(result) {
-        return _.chain(result.Metrics).map(function(metric) {
-          return _.pluck(metric.Dimensions, 'Value');
-        }).flatten().uniq().sortBy(function(name) {
-          return name;
-        }).map(function(value) {
+        return _.chain(result.Metrics)
+        .pluck('Dimensions')
+        .flatten()
+        .filter(function(dimension) {
+          return dimension.Name === dimensionKey;
+        })
+        .pluck('Value')
+        .uniq()
+        .sortBy()
+        .map(function(value) {
           return {value: value, text: value};
         }).value();
       });
@@ -174,25 +179,14 @@ function (angular, _) {
         return this.getDimensionKeys(dimensionKeysQuery[1]);
       }
 
-      var dimensionValuesQuery = query.match(/^dimension_values\(([^,]+?),\s?([^,]+?),\s?([^,]+?)(,\s?([^)]*))?\)/);
+      var dimensionValuesQuery = query.match(/^dimension_values\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/);
       if (dimensionValuesQuery) {
         region = templateSrv.replace(dimensionValuesQuery[1]);
         namespace = templateSrv.replace(dimensionValuesQuery[2]);
         metricName = templateSrv.replace(dimensionValuesQuery[3]);
-        var dimensionPart = templateSrv.replace(dimensionValuesQuery[5]);
-
-        var dimensions = {};
-        if (!_.isEmpty(dimensionPart)) {
-          _.each(dimensionPart.split(','), function(v) {
-            var t = v.split('=');
-            if (t.length !== 2) {
-              throw new Error('Invalid query format');
-            }
-            dimensions[t[0]] = t[1];
-          });
-        }
+        var dimensionKey = templateSrv.replace(dimensionValuesQuery[4]);
 
-        return this.getDimensionValues(region, namespace, metricName, dimensions);
+        return this.getDimensionValues(region, namespace, metricName, dimensionKey, {});
       }
 
       var ebsVolumeIdsQuery = query.match(/^ebs_volume_ids\(([^,]+?),\s?([^,]+?)\)/);
@@ -222,7 +216,7 @@ function (angular, _) {
       var metricName = 'EstimatedCharges';
       var dimensions = {};
 
-      return this.getDimensionValues(region, namespace, metricName, dimensions).then(function () {
+      return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
         return { status: 'success', message: 'Data source is working', title: 'Success' };
       });
     };

+ 3 - 2
public/app/plugins/datasource/cloudwatch/query_ctrl.js

@@ -76,7 +76,7 @@ function (angular, _) {
       }
     };
 
-    $scope.getDimSegments = function(segment) {
+    $scope.getDimSegments = function(segment, $index) {
       if (segment.type === 'operator') { return $q.when([]); }
 
       var target = $scope.target;
@@ -85,7 +85,8 @@ function (angular, _) {
       if (segment.type === 'key' || segment.type === 'plus-button') {
         query = $scope.datasource.getDimensionKeys($scope.target.namespace);
       } else if (segment.type === 'value')  {
-        query = $scope.datasource.getDimensionValues(target.region, target.namespace, target.metricName, {});
+        var dimensionKey = $scope.dimSegments[$index-2].value;
+        query = $scope.datasource.getDimensionValues(target.region, target.namespace, target.metricName, dimensionKey, {});
       }
 
       return query.then($scope.transformToSegments(true)).then(function(results) {

+ 1 - 1
public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts

@@ -165,7 +165,7 @@ describe('CloudWatchDatasource', function() {
     });
   });
 
-  describeMetricFindQuery('dimension_values(us-east-1,AWS/EC2,CPUUtilization)', scenario => {
+  describeMetricFindQuery('dimension_values(us-east-1,AWS/EC2,CPUUtilization,InstanceId)', scenario => {
     scenario.setup(() => {
       scenario.requestResponse = {
         Metrics: [

+ 9 - 3
public/app/plugins/datasource/elasticsearch/datasource.js

@@ -23,9 +23,11 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
       this.name = datasource.name;
       this.index = datasource.index;
       this.timeField = datasource.jsonData.timeField;
+      this.esVersion = datasource.jsonData.esVersion;
       this.indexPattern = new IndexPattern(datasource.index, datasource.jsonData.interval);
       this.queryBuilder = new ElasticQueryBuilder({
-        timeField: this.timeField
+        timeField: this.timeField,
+        esVersion: this.esVersion,
       });
     }
 
@@ -94,7 +96,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
 
       var payload = angular.toJson(header) + '\n' + angular.toJson(data) + '\n';
 
-      return this._post('/_msearch', payload).then(function(res) {
+      return this._post('_msearch', payload).then(function(res) {
         var list = [];
         var hits = res.responses[0].hits.hits;
 
@@ -183,12 +185,16 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
         sentTargets.push(target);
       }
 
+      if (sentTargets.length === 0) {
+        return $q.when([]);
+      }
+
       payload = payload.replace(/\$interval/g, options.interval);
       payload = payload.replace(/\$timeFrom/g, options.range.from.valueOf());
       payload = payload.replace(/\$timeTo/g, options.range.to.valueOf());
       payload = templateSrv.replace(payload, options.scopedVars);
 
-      return this._post('/_msearch', payload).then(function(res) {
+      return this._post('_msearch', payload).then(function(res) {
         return new ElasticResponse(sentTargets, res).getTimeSeries();
       });
     };

+ 12 - 1
public/app/plugins/datasource/elasticsearch/partials/config.html

@@ -20,7 +20,7 @@
 	</ul>
 	<div class="clearfix"></div>
 </div>
-<div class="tight-form last">
+<div class="tight-form">
 	<ul class="tight-form-list">
 		<li class="tight-form-item" style="width: 144px">
 			Time field name
@@ -31,3 +31,14 @@
 	</ul>
 	<div class="clearfix"></div>
 </div>
+<div class="tight-form last">
+	<ul class="tight-form-list">
+		<li class="tight-form-item" style="width: 144px">
+			Version
+		</li>
+		<li>
+			<select class="input-medium tight-form-input" ng-model="current.jsonData.esVersion" ng-options="f.value as f.name for f in esVersions"></select>
+		</li>
+	</ul>
+	<div class="clearfix"></div>
+</div>

+ 0 - 1
public/app/plugins/datasource/elasticsearch/partials/query.editor.html

@@ -14,7 +14,6 @@
 					<i class="fa fa-bars"></i>
 				</a>
 				<ul class="dropdown-menu pull-right" role="menu">
-					<li role="menuitem"><a tabindex="1" ng-click="toggleQueryMode()">Switch editor mode</a></li>
 					<li role="menuitem"><a tabindex="1" ng-click="duplicateDataQuery(target)">Duplicate</a></li>
 					<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index-1)">Move up</a></li>
 					<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a></li>

+ 15 - 5
public/app/plugins/datasource/elasticsearch/query_builder.js

@@ -1,16 +1,21 @@
 define([
-  "angular"
 ],
-function (angular) {
+function () {
   'use strict';
 
   function ElasticQueryBuilder(options) {
     this.timeField = options.timeField;
+    this.esVersion = options.esVersion;
   }
 
   ElasticQueryBuilder.prototype.getRangeFilter = function() {
     var filter = {};
     filter[this.timeField] = {"gte": "$timeFrom", "lte": "$timeTo"};
+
+    if (this.esVersion >= 2) {
+      filter[this.timeField]["format"] = "epoch_millis";
+    }
+
     return filter;
   };
 
@@ -82,9 +87,11 @@ function (angular) {
   };
 
   ElasticQueryBuilder.prototype.build = function(target) {
-    if (target.rawQuery) {
-      return angular.fromJson(target.rawQuery);
-    }
+    // make sure query has defaults;
+    target.metrics = target.metrics || [{ type: 'count', id: '1' }];
+    target.dsType = 'elasticsearch';
+    target.bucketAggs = target.bucketAggs || [{type: 'date_histogram', id: '2', settings: {interval: 'auto'}}];
+    target.timeField =  this.timeField;
 
     var i, nestedAggs, metric;
     var query = {
@@ -129,6 +136,9 @@ function (angular) {
             "min_doc_count": 0,
             "extended_bounds": { "min": "$timeFrom", "max": "$timeTo" }
           };
+          if (this.esVersion >= 2) {
+            esAgg["date_histogram"]["format"] = "epoch_millis";
+          }
           break;
         }
         case 'filters': {

+ 1 - 11
public/app/plugins/datasource/elasticsearch/query_ctrl.js

@@ -12,9 +12,7 @@ function (angular) {
       var target = $scope.target;
       if (!target) { return; }
 
-      target.metrics = target.metrics || [{ type: 'count', id: '1' }];
-      target.bucketAggs = target.bucketAggs || [{type: 'date_histogram', id: '2', settings: {interval: 'auto'}}];
-      target.timeField =  $scope.datasource.timeField;
+      $scope.queryUpdated();
     };
 
     $scope.getFields = function(type) {
@@ -39,14 +37,6 @@ function (angular) {
       return [];
     };
 
-    $scope.toggleQueryMode = function () {
-      if ($scope.target.rawQuery) {
-        delete $scope.target.rawQuery;
-      } else {
-        $scope.target.rawQuery = angular.toJson($scope.datasource.queryBuilder.build($scope.target), true);
-      }
-    };
-
     $scope.init();
 
   });

+ 33 - 8
public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts

@@ -22,14 +22,6 @@ describe('ElasticQueryBuilder', function() {
     expect(query.aggs["1"].date_histogram.extended_bounds.min).to.be("$timeFrom");
   });
 
-  it('with raw query', function() {
-    var query = builder.build({
-      rawQuery: '{"query": "$lucene_query"}',
-    });
-
-    expect(query.query).to.be("$lucene_query");
-  });
-
   it('with multiple bucket aggs', function() {
     var query = builder.build({
       metrics: [{type: 'count', id: '1'}],
@@ -44,6 +36,39 @@ describe('ElasticQueryBuilder', function() {
     expect(query.aggs["2"].aggs["3"].date_histogram.field).to.be("@timestamp");
   });
 
+  it('with es1.x and es2.x date histogram queries check time format', function() {
+    var builder_2x = new ElasticQueryBuilder({
+      timeField: '@timestamp',
+      esVersion: 2
+    });
+
+    var query_params = {
+      metrics: [],
+      bucketAggs: [
+        {type: 'date_histogram', field: '@timestamp', id: '1'}
+      ],
+    };
+
+    // format should not be specified in 1.x queries
+    expect("format" in builder.build(query_params)["aggs"]["1"]["date_histogram"]).to.be(false);
+
+    // 2.x query should specify format to be "epoch_millis"
+    expect(builder_2x.build(query_params)["aggs"]["1"]["date_histogram"]["format"]).to.be("epoch_millis");
+  });
+
+  it('with es1.x and es2.x range filter check time format', function() {
+    var builder_2x = new ElasticQueryBuilder({
+      timeField: '@timestamp',
+      esVersion: 2
+    });
+
+    // format should not be specified in 1.x queries
+    expect("format" in builder.getRangeFilter()["@timestamp"]).to.be(false);
+
+    // 2.x query should specify format to be "epoch_millis"
+    expect(builder_2x.getRangeFilter()["@timestamp"]["format"]).to.be("epoch_millis");
+  });
+
   it('with select field', function() {
     var query = builder.build({
       metrics: [{type: 'avg', field: '@value', id: '1'}],

+ 19 - 5
public/app/plugins/datasource/influxdb/datasource.js

@@ -53,6 +53,7 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) {
 
       // replace templated variables
       allQueries = templateSrv.replace(allQueries, options.scopedVars);
+
       return this._seriesQuery(allQueries).then(function(data) {
         if (!data || !data.results) {
           return [];
@@ -63,13 +64,26 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) {
           var result = data.results[i];
           if (!result || !result.series) { continue; }
 
-          var alias = (queryTargets[i] || {}).alias;
+          var target = queryTargets[i];
+          var alias = target.alias;
           if (alias) {
-            alias = templateSrv.replace(alias, options.scopedVars);
+            alias = templateSrv.replace(target.alias, options.scopedVars);
           }
-          var targetSeries = new InfluxSeries({ series: data.results[i].series, alias: alias }).getTimeSeries();
-          for (y = 0; y < targetSeries.length; y++) {
-            seriesList.push(targetSeries[y]);
+
+          var influxSeries = new InfluxSeries({ series: data.results[i].series, alias: alias });
+
+          switch(target.resultFormat) {
+            case 'table': {
+              seriesList.push(influxSeries.getTable());
+              break;
+            }
+            default: {
+              var timeSeries = influxSeries.getTimeSeries();
+              for (y = 0; y < timeSeries.length; y++) {
+                seriesList.push(timeSeries[y]);
+              }
+              break;
+            }
           }
         }
 

+ 2 - 0
public/app/plugins/datasource/influxdb/influx_query.ts

@@ -12,6 +12,8 @@ class InfluxQuery {
   constructor(target) {
     this.target = target;
 
+    target.dsType = 'influxdb';
+    target.resultFormat = target.resultFormat || 'time_series';
     target.tags = target.tags || [];
     target.groupBy = target.groupBy || [
       {type: 'time', params: ['$interval']},

+ 41 - 1
public/app/plugins/datasource/influxdb/influx_series.js

@@ -1,7 +1,8 @@
 define([
   'lodash',
+  'app/core/table_model',
 ],
-function (_) {
+function (_, TableModel) {
   'use strict';
 
   function InfluxSeries(options) {
@@ -108,5 +109,44 @@ function (_) {
     return list;
   };
 
+  p.getTable = function() {
+    var table = new TableModel();
+    var self = this;
+    var i, j;
+
+    if (self.series.length === 0) {
+      return table;
+    }
+
+    _.each(self.series, function(series, seriesIndex) {
+
+      if (seriesIndex === 0) {
+        table.columns.push({text: 'Time', type: 'time'});
+        _.each(_.keys(series.tags), function(key) {
+          table.columns.push({text: key});
+        });
+        for (j = 1; j < series.columns.length; j++) {
+          table.columns.push({text: series.columns[j]});
+        }
+      }
+
+      if (series.values) {
+        for (i = 0; i < series.values.length; i++) {
+          var values = series.values[i];
+          if (series.tags) {
+            for (var key in series.tags) {
+              if (series.tags.hasOwnProperty(key)) {
+                values.splice(1, 0, series.tags[key]);
+              }
+            }
+          }
+          table.rows.push(values);
+        }
+      }
+    });
+
+    return table;
+  };
+
   return InfluxSeries;
 });

+ 6 - 0
public/app/plugins/datasource/influxdb/partials/query.editor.html

@@ -103,6 +103,12 @@
 			<li>
 				<input type="text" class="tight-form-clear-input input-xlarge" ng-model="target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="get_data()">
 			</li>
+			<li class="tight-form-item">
+				Format as
+			</li>
+			<li>
+				<select class="input-small tight-form-input" style="width: 104px" ng-model="target.resultFormat" ng-options="f.value as f.text for f in resultFormats" ng-change="get_data()"></select>
+			</li>
 		</ul>
 		<div class="clearfix"></div>
 	</div>

+ 5 - 0
public/app/plugins/datasource/influxdb/query_ctrl.js

@@ -20,6 +20,11 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
       $scope.queryModel = new InfluxQuery($scope.target);
       $scope.queryBuilder = new InfluxQueryBuilder($scope.target);
       $scope.groupBySegment = uiSegmentSrv.newPlusButton();
+      $scope.resultFormats = [
+         {text: 'Time series', value: 'time_series'},
+         {text: 'Table', value: 'table'},
+         {text: 'JSON field', value: 'json_field'},
+      ];
 
       if (!$scope.target.measurement) {
         $scope.measurementSegment = uiSegmentSrv.newSelectMeasurement();

+ 23 - 0
public/app/plugins/datasource/influxdb/specs/influx_series_specs.ts

@@ -186,5 +186,28 @@ describe('when generating timeseries from influxdb response', function() {
     });
   });
 
+  describe('given table response', function() {
+    var options = {
+      alias: '',
+      series: [
+        {
+          name: 'app.prod.server1.count',
+          tags:  {},
+          columns: ['time', 'datacenter', 'value'],
+          values: [[1431946625000, 'America', 10], [1431946626000, 'EU', 12]]
+        }
+      ]
+    };
+
+    it('should return table', function() {
+      var series = new InfluxSeries(options);
+      var table = series.getTable();
+
+      expect(table.type).to.be('table');
+      expect(table.columns.length).to.be(3);
+      expect(table.rows[0]).to.eql([1431946625000, 'America', 10]);;
+    });
+  });
+
 });
 

+ 1 - 1
public/app/panels/table/specs/table_model_specs.ts → public/test/core/table_model_specs.ts

@@ -1,6 +1,6 @@
 import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
 
-import {TableModel} from '../table_model';
+import TableModel = require('app/core/table_model');
 
 describe('when sorting table desc', () => {
   var table;