Commit 3d178c8e by Nick Christus

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

parents d86b2c4f db9c2880
node_modules node_modules
npm-debug.log
coverage/ coverage/
.aws-config.json .aws-config.json
awsconfig awsconfig
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
* **Elasticsearch**: Support for dynamic daily indices for annotations, closes [#3061](https://github.com/grafana/grafana/issues/3061) * **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) * **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 ### Bug Fixes
* **cloudwatch**: fix for handling of period for long time ranges, fixes [#3086](https://github.com/grafana/grafana/issues/3086) * **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) * **dashboard**: fix for collapse row by clicking on row title, fixes [#3065](https://github.com/grafana/grafana/issues/3065)
...@@ -16,6 +15,9 @@ ...@@ -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) * **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) * **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) # 2.5 (2015-10-28)
**New Feature: Mix data sources** **New Feature: Mix data sources**
......
...@@ -90,7 +90,7 @@ Replace X.Y.Z by actual version number. ...@@ -90,7 +90,7 @@ Replace X.Y.Z by actual version number.
cd $GOPATH/src/github.com/grafana/grafana cd $GOPATH/src/github.com/grafana/grafana
go run build.go setup (only needed once to install godep) 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 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 ### Building frontend assets
......
...@@ -63,15 +63,10 @@ Name | Description ...@@ -63,15 +63,10 @@ Name | Description
`namespaces()` | Returns a list of namespaces CloudWatch support. `namespaces()` | Returns a list of namespaces CloudWatch support.
`metrics(namespace)` | Returns a list of metrics in the namespace. `metrics(namespace)` | Returns a list of metrics in the namespace.
`dimension_keys(namespace)` | Returns a list of dimension keys 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). 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) ![](/img/v2/cloudwatch_templating.png)
## Cost ## Cost
......
import {transformers} from './transformers';
export class TableModel { class TableModel {
columns: any[]; columns: any[];
rows: any[]; rows: any[];
type: string;
constructor() { constructor() {
this.columns = []; this.columns = [];
this.rows = []; this.rows = [];
this.type = 'table';
} }
sort(options) { sort(options) {
...@@ -33,20 +34,6 @@ export class TableModel { ...@@ -33,20 +34,6 @@ export class TableModel {
this.columns[options.col].desc = true; 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;
...@@ -13,7 +13,7 @@ function (angular, _, config) { ...@@ -13,7 +13,7 @@ function (angular, _, config) {
$scope.httpConfigPartialSrc = 'app/features/org/partials/datasourceHttpConfig.html'; $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 = [ $scope.indexPatternTypes = [
{name: 'No pattern', value: undefined}, {name: 'No pattern', value: undefined},
...@@ -24,6 +24,11 @@ function (angular, _, config) { ...@@ -24,6 +24,11 @@ function (angular, _, config) {
{name: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY'}, {name: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY'},
]; ];
$scope.esVersions = [
{name: '1.x', value: 1},
{name: '2.x', value: 2},
];
$scope.init = function() { $scope.init = function() {
$scope.isNew = true; $scope.isNew = true;
$scope.datasources = []; $scope.datasources = [];
......
...@@ -5,7 +5,7 @@ import _ = require('lodash'); ...@@ -5,7 +5,7 @@ import _ = require('lodash');
import moment = require('moment'); import moment = require('moment');
import PanelMeta = require('app/features/panel/panel_meta'); import PanelMeta = require('app/features/panel/panel_meta');
import {TableModel} from './table_model'; import {transformDataToTable} from './transformers';
export class TablePanelCtrl { export class TablePanelCtrl {
...@@ -104,7 +104,23 @@ export class TablePanelCtrl { ...@@ -104,7 +104,23 @@ export class TablePanelCtrl {
}; };
$scope.render = function() { $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); $scope.table.sort($scope.panel.sort);
panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw); panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw);
}; };
......
import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; 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'; import {TableRenderer} from '../renderer';
describe('when rendering table', () => { describe('when rendering table', () => {
......
import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
import {TableModel} from '../table_model'; import {transformers, transformDataToTable} from '../transformers';
import {transformers} from '../transformers';
describe('when transforming time series table', () => { describe('when transforming time series table', () => {
var table; var table;
...@@ -26,7 +25,7 @@ describe('when transforming time series table', () => { ...@@ -26,7 +25,7 @@ describe('when transforming time series table', () => {
}; };
beforeEach(() => { beforeEach(() => {
table = TableModel.transform(timeSeries, panel); table = transformDataToTable(timeSeries, panel);
}); });
it('should return 3 rows', () => { it('should return 3 rows', () => {
...@@ -51,7 +50,7 @@ describe('when transforming time series table', () => { ...@@ -51,7 +50,7 @@ describe('when transforming time series table', () => {
}; };
beforeEach(() => { beforeEach(() => {
table = TableModel.transform(timeSeries, panel); table = transformDataToTable(timeSeries, panel);
}); });
it ('should return 3 columns', () => { it ('should return 3 columns', () => {
...@@ -80,7 +79,7 @@ describe('when transforming time series table', () => { ...@@ -80,7 +79,7 @@ describe('when transforming time series table', () => {
}; };
beforeEach(() => { beforeEach(() => {
table = TableModel.transform(timeSeries, panel); table = transformDataToTable(timeSeries, panel);
}); });
it('should return 2 rows', () => { it('should return 2 rows', () => {
...@@ -133,7 +132,7 @@ describe('when transforming time series table', () => { ...@@ -133,7 +132,7 @@ describe('when transforming time series table', () => {
describe('transform', function() { describe('transform', function() {
beforeEach(() => { beforeEach(() => {
table = TableModel.transform(rawData, panel); table = transformDataToTable(rawData, panel);
}); });
it ('should return 2 columns', () => { it ('should return 2 columns', () => {
...@@ -164,7 +163,7 @@ describe('when transforming time series table', () => { ...@@ -164,7 +163,7 @@ describe('when transforming time series table', () => {
]; ];
beforeEach(() => { beforeEach(() => {
table = TableModel.transform(rawData, panel); table = transformDataToTable(rawData, panel);
}); });
it ('should return 4 columns', () => { it ('should return 4 columns', () => {
......
...@@ -4,6 +4,7 @@ import moment = require('moment'); ...@@ -4,6 +4,7 @@ import moment = require('moment');
import _ = require('lodash'); import _ = require('lodash');
import flatten = require('app/core/utils/flatten'); import flatten = require('app/core/utils/flatten');
import TimeSeries = require('app/core/time_series'); import TimeSeries = require('app/core/time_series');
import TableModel = require('app/core/table_model');
var transformers = {}; var transformers = {};
...@@ -136,6 +137,27 @@ transformers['annotations'] = { ...@@ -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'] = { transformers['json'] = {
description: 'JSON Data', description: 'JSON Data',
getColumns: function(data) { getColumns: function(data) {
...@@ -197,4 +219,20 @@ transformers['json'] = { ...@@ -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}
...@@ -113,23 +113,28 @@ function (angular, _) { ...@@ -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 = { var request = {
region: templateSrv.replace(region), region: templateSrv.replace(region),
action: 'ListMetrics', action: 'ListMetrics',
parameters: { parameters: {
namespace: templateSrv.replace(namespace), namespace: templateSrv.replace(namespace),
metricName: templateSrv.replace(metricName), metricName: templateSrv.replace(metricName),
dimensions: convertDimensionFormat(dimensions, {}), dimensions: convertDimensionFormat(filterDimensions, {}),
} }
}; };
return this.awsRequest(request).then(function(result) { return this.awsRequest(request).then(function(result) {
return _.chain(result.Metrics).map(function(metric) { return _.chain(result.Metrics)
return _.pluck(metric.Dimensions, 'Value'); .pluck('Dimensions')
}).flatten().uniq().sortBy(function(name) { .flatten()
return name; .filter(function(dimension) {
}).map(function(value) { return dimension.Name === dimensionKey;
})
.pluck('Value')
.uniq()
.sortBy()
.map(function(value) {
return {value: value, text: value}; return {value: value, text: value};
}).value(); }).value();
}); });
...@@ -174,25 +179,14 @@ function (angular, _) { ...@@ -174,25 +179,14 @@ function (angular, _) {
return this.getDimensionKeys(dimensionKeysQuery[1]); 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) { if (dimensionValuesQuery) {
region = templateSrv.replace(dimensionValuesQuery[1]); region = templateSrv.replace(dimensionValuesQuery[1]);
namespace = templateSrv.replace(dimensionValuesQuery[2]); namespace = templateSrv.replace(dimensionValuesQuery[2]);
metricName = templateSrv.replace(dimensionValuesQuery[3]); metricName = templateSrv.replace(dimensionValuesQuery[3]);
var dimensionPart = templateSrv.replace(dimensionValuesQuery[5]); var dimensionKey = templateSrv.replace(dimensionValuesQuery[4]);
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];
});
}
return this.getDimensionValues(region, namespace, metricName, dimensions); return this.getDimensionValues(region, namespace, metricName, dimensionKey, {});
} }
var ebsVolumeIdsQuery = query.match(/^ebs_volume_ids\(([^,]+?),\s?([^,]+?)\)/); var ebsVolumeIdsQuery = query.match(/^ebs_volume_ids\(([^,]+?),\s?([^,]+?)\)/);
...@@ -222,7 +216,7 @@ function (angular, _) { ...@@ -222,7 +216,7 @@ function (angular, _) {
var metricName = 'EstimatedCharges'; var metricName = 'EstimatedCharges';
var dimensions = {}; 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' }; return { status: 'success', message: 'Data source is working', title: 'Success' };
}); });
}; };
......
...@@ -76,7 +76,7 @@ function (angular, _) { ...@@ -76,7 +76,7 @@ function (angular, _) {
} }
}; };
$scope.getDimSegments = function(segment) { $scope.getDimSegments = function(segment, $index) {
if (segment.type === 'operator') { return $q.when([]); } if (segment.type === 'operator') { return $q.when([]); }
var target = $scope.target; var target = $scope.target;
...@@ -85,7 +85,8 @@ function (angular, _) { ...@@ -85,7 +85,8 @@ function (angular, _) {
if (segment.type === 'key' || segment.type === 'plus-button') { if (segment.type === 'key' || segment.type === 'plus-button') {
query = $scope.datasource.getDimensionKeys($scope.target.namespace); query = $scope.datasource.getDimensionKeys($scope.target.namespace);
} else if (segment.type === 'value') { } 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) { return query.then($scope.transformToSegments(true)).then(function(results) {
......
...@@ -165,7 +165,7 @@ describe('CloudWatchDatasource', function() { ...@@ -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.setup(() => {
scenario.requestResponse = { scenario.requestResponse = {
Metrics: [ Metrics: [
......
...@@ -23,9 +23,11 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes ...@@ -23,9 +23,11 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
this.name = datasource.name; this.name = datasource.name;
this.index = datasource.index; this.index = datasource.index;
this.timeField = datasource.jsonData.timeField; this.timeField = datasource.jsonData.timeField;
this.esVersion = datasource.jsonData.esVersion;
this.indexPattern = new IndexPattern(datasource.index, datasource.jsonData.interval); this.indexPattern = new IndexPattern(datasource.index, datasource.jsonData.interval);
this.queryBuilder = new ElasticQueryBuilder({ this.queryBuilder = new ElasticQueryBuilder({
timeField: this.timeField timeField: this.timeField,
esVersion: this.esVersion,
}); });
} }
...@@ -94,7 +96,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes ...@@ -94,7 +96,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
var payload = angular.toJson(header) + '\n' + angular.toJson(data) + '\n'; 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 list = [];
var hits = res.responses[0].hits.hits; var hits = res.responses[0].hits.hits;
...@@ -183,12 +185,16 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes ...@@ -183,12 +185,16 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
sentTargets.push(target); sentTargets.push(target);
} }
if (sentTargets.length === 0) {
return $q.when([]);
}
payload = payload.replace(/\$interval/g, options.interval); payload = payload.replace(/\$interval/g, options.interval);
payload = payload.replace(/\$timeFrom/g, options.range.from.valueOf()); payload = payload.replace(/\$timeFrom/g, options.range.from.valueOf());
payload = payload.replace(/\$timeTo/g, options.range.to.valueOf()); payload = payload.replace(/\$timeTo/g, options.range.to.valueOf());
payload = templateSrv.replace(payload, options.scopedVars); 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(); return new ElasticResponse(sentTargets, res).getTimeSeries();
}); });
}; };
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
<div class="tight-form last"> <div class="tight-form">
<ul class="tight-form-list"> <ul class="tight-form-list">
<li class="tight-form-item" style="width: 144px"> <li class="tight-form-item" style="width: 144px">
Time field name Time field name
...@@ -31,3 +31,14 @@ ...@@ -31,3 +31,14 @@
</ul> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
</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>
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
<i class="fa fa-bars"></i> <i class="fa fa-bars"></i>
</a> </a>
<ul class="dropdown-menu pull-right" role="menu"> <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="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 up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a></li> <li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a></li>
......
define([ define([
"angular"
], ],
function (angular) { function () {
'use strict'; 'use strict';
function ElasticQueryBuilder(options) { function ElasticQueryBuilder(options) {
this.timeField = options.timeField; this.timeField = options.timeField;
this.esVersion = options.esVersion;
} }
ElasticQueryBuilder.prototype.getRangeFilter = function() { ElasticQueryBuilder.prototype.getRangeFilter = function() {
var filter = {}; var filter = {};
filter[this.timeField] = {"gte": "$timeFrom", "lte": "$timeTo"}; filter[this.timeField] = {"gte": "$timeFrom", "lte": "$timeTo"};
if (this.esVersion >= 2) {
filter[this.timeField]["format"] = "epoch_millis";
}
return filter; return filter;
}; };
...@@ -82,9 +87,11 @@ function (angular) { ...@@ -82,9 +87,11 @@ function (angular) {
}; };
ElasticQueryBuilder.prototype.build = function(target) { ElasticQueryBuilder.prototype.build = function(target) {
if (target.rawQuery) { // make sure query has defaults;
return angular.fromJson(target.rawQuery); 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 i, nestedAggs, metric;
var query = { var query = {
...@@ -129,6 +136,9 @@ function (angular) { ...@@ -129,6 +136,9 @@ function (angular) {
"min_doc_count": 0, "min_doc_count": 0,
"extended_bounds": { "min": "$timeFrom", "max": "$timeTo" } "extended_bounds": { "min": "$timeFrom", "max": "$timeTo" }
}; };
if (this.esVersion >= 2) {
esAgg["date_histogram"]["format"] = "epoch_millis";
}
break; break;
} }
case 'filters': { case 'filters': {
......
...@@ -12,9 +12,7 @@ function (angular) { ...@@ -12,9 +12,7 @@ function (angular) {
var target = $scope.target; var target = $scope.target;
if (!target) { return; } if (!target) { return; }
target.metrics = target.metrics || [{ type: 'count', id: '1' }]; $scope.queryUpdated();
target.bucketAggs = target.bucketAggs || [{type: 'date_histogram', id: '2', settings: {interval: 'auto'}}];
target.timeField = $scope.datasource.timeField;
}; };
$scope.getFields = function(type) { $scope.getFields = function(type) {
...@@ -39,14 +37,6 @@ function (angular) { ...@@ -39,14 +37,6 @@ function (angular) {
return []; 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(); $scope.init();
}); });
......
...@@ -22,14 +22,6 @@ describe('ElasticQueryBuilder', function() { ...@@ -22,14 +22,6 @@ describe('ElasticQueryBuilder', function() {
expect(query.aggs["1"].date_histogram.extended_bounds.min).to.be("$timeFrom"); 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() { it('with multiple bucket aggs', function() {
var query = builder.build({ var query = builder.build({
metrics: [{type: 'count', id: '1'}], metrics: [{type: 'count', id: '1'}],
...@@ -44,6 +36,39 @@ describe('ElasticQueryBuilder', function() { ...@@ -44,6 +36,39 @@ describe('ElasticQueryBuilder', function() {
expect(query.aggs["2"].aggs["3"].date_histogram.field).to.be("@timestamp"); 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() { it('with select field', function() {
var query = builder.build({ var query = builder.build({
metrics: [{type: 'avg', field: '@value', id: '1'}], metrics: [{type: 'avg', field: '@value', id: '1'}],
......
...@@ -53,6 +53,7 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) { ...@@ -53,6 +53,7 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) {
// replace templated variables // replace templated variables
allQueries = templateSrv.replace(allQueries, options.scopedVars); allQueries = templateSrv.replace(allQueries, options.scopedVars);
return this._seriesQuery(allQueries).then(function(data) { return this._seriesQuery(allQueries).then(function(data) {
if (!data || !data.results) { if (!data || !data.results) {
return []; return [];
...@@ -63,13 +64,26 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) { ...@@ -63,13 +64,26 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) {
var result = data.results[i]; var result = data.results[i];
if (!result || !result.series) { continue; } if (!result || !result.series) { continue; }
var alias = (queryTargets[i] || {}).alias; var target = queryTargets[i];
var alias = target.alias;
if (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++) { var influxSeries = new InfluxSeries({ series: data.results[i].series, alias: alias });
seriesList.push(targetSeries[y]);
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;
}
} }
} }
......
...@@ -12,6 +12,8 @@ class InfluxQuery { ...@@ -12,6 +12,8 @@ class InfluxQuery {
constructor(target) { constructor(target) {
this.target = target; this.target = target;
target.dsType = 'influxdb';
target.resultFormat = target.resultFormat || 'time_series';
target.tags = target.tags || []; target.tags = target.tags || [];
target.groupBy = target.groupBy || [ target.groupBy = target.groupBy || [
{type: 'time', params: ['$interval']}, {type: 'time', params: ['$interval']},
......
define([ define([
'lodash', 'lodash',
'app/core/table_model',
], ],
function (_) { function (_, TableModel) {
'use strict'; 'use strict';
function InfluxSeries(options) { function InfluxSeries(options) {
...@@ -108,5 +109,44 @@ function (_) { ...@@ -108,5 +109,44 @@ function (_) {
return list; 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; return InfluxSeries;
}); });
...@@ -103,6 +103,12 @@ ...@@ -103,6 +103,12 @@
<li> <li>
<input type="text" class="tight-form-clear-input input-xlarge" ng-model="target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="get_data()"> <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>
<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> </ul>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
......
...@@ -20,6 +20,11 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { ...@@ -20,6 +20,11 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.queryModel = new InfluxQuery($scope.target); $scope.queryModel = new InfluxQuery($scope.target);
$scope.queryBuilder = new InfluxQueryBuilder($scope.target); $scope.queryBuilder = new InfluxQueryBuilder($scope.target);
$scope.groupBySegment = uiSegmentSrv.newPlusButton(); $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) { if (!$scope.target.measurement) {
$scope.measurementSegment = uiSegmentSrv.newSelectMeasurement(); $scope.measurementSegment = uiSegmentSrv.newSelectMeasurement();
......
...@@ -186,5 +186,28 @@ describe('when generating timeseries from influxdb response', function() { ...@@ -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]);;
});
});
}); });
import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; 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', () => { describe('when sorting table desc', () => {
var table; var table;
......
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