Merge branch 'master' of github.com:grafana/grafana
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
coverage/
|
||||
.aws-config.json
|
||||
awsconfig
|
||||
|
||||
+3
-1
@@ -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**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,...)
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Cost
|
||||
|
||||
@@ -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;
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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,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', () => {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 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?([^,]+?)\)/);
|
||||
@@ -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' };
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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': {
|
||||
|
||||
@@ -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();
|
||||
|
||||
});
|
||||
|
||||
@@ -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'}],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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']},
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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;
|
||||
Reference in New Issue
Block a user