diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html
index 494eaff5520..8ea612b0924 100644
--- a/public/app/plugins/datasource/influxdb/partials/query.editor.html
+++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html
@@ -91,7 +91,7 @@
time($interval)
-
+
diff --git a/public/app/plugins/datasource/influxdb/queryBuilder.js b/public/app/plugins/datasource/influxdb/queryBuilder.js
index 9317cea4574..461408307d3 100644
--- a/public/app/plugins/datasource/influxdb/queryBuilder.js
+++ b/public/app/plugins/datasource/influxdb/queryBuilder.js
@@ -1,5 +1,5 @@
define([
- 'lodash'
+ 'lodash'
],
function (_) {
'use strict';
@@ -32,12 +32,18 @@ function (_) {
}
query += aggregationFunc + '(value)';
- query += ' FROM ' + measurement + ' WHERE $timeFilter';
- query += _.map(target.tags, function(tag) {
- return ' AND ' + tag.key + '=' + "'" + tag.value + "'";
- }).join('');
+ query += ' FROM ' + measurement + ' WHERE ';
+ var conditions = _.map(target.tags, function(tag) {
+ return tag.key + '=' + "'" + tag.value + "' ";
+ });
+ conditions.push('$timeFilter');
+
+ query += conditions.join('AND ');
query += ' GROUP BY time($interval)';
+ if (target.groupByTags && target.groupByTags.length > 0) {
+ query += ', ' + target.groupByTags.join();
+ }
if (target.fill) {
query += ' fill(' + target.fill + ')';
diff --git a/public/app/plugins/datasource/influxdb/queryCtrl.js b/public/app/plugins/datasource/influxdb/queryCtrl.js
index 5aeb68f00e7..e68d10a2067 100644
--- a/public/app/plugins/datasource/influxdb/queryCtrl.js
+++ b/public/app/plugins/datasource/influxdb/queryCtrl.js
@@ -35,14 +35,12 @@ function (angular, _) {
if (tag.condition) {
$scope.tagSegments.push(MetricSegment.newCondition(tag.condition));
}
- $scope.tagSegments.push(new MetricSegment({value: tag.key, type: 'key' }));
- $scope.tagSegments.push(new MetricSegment({fake: true, value: "="}));
- $scope.tagSegments.push(new MetricSegment({value: tag.value, type: 'value'}));
+ $scope.tagSegments.push(new MetricSegment({value: tag.key, type: 'key', cssClass: 'query-segment-key' }));
+ $scope.tagSegments.push(new MetricSegment({fake: true, value: "=", cssClass: 'query-segment-operator'}));
+ $scope.tagSegments.push(new MetricSegment({value: tag.value, type: 'value', cssClass: 'query-segment-value'}));
});
- if ($scope.tagSegments.length % 3 === 0) {
- $scope.tagSegments.push(MetricSegment.newPlusButton());
- }
+ $scope.fixTagSegments();
$scope.groupBySegments = [];
_.each(target.groupByTags, function(tag) {
@@ -50,12 +48,37 @@ function (angular, _) {
});
$scope.groupBySegments.push(MetricSegment.newPlusButton());
+
+ $scope.removeTagFilterSegment = new MetricSegment({fake: true, value: 'remove tag filter'});
+ $scope.removeGroupBySegment = new MetricSegment({fake: true, value: 'remove group by'});
+ };
+
+ $scope.fixTagSegments = function() {
+ var count = $scope.tagSegments.length;
+ var lastSegment = $scope.tagSegments[Math.max(count-1, 0)];
+
+ if (!lastSegment || lastSegment.type !== 'plus-button') {
+ $scope.tagSegments.push(MetricSegment.newPlusButton());
+ }
};
$scope.groupByTagUpdated = function(segment, index) {
+ if (segment.value === $scope.removeGroupBySegment.value) {
+ $scope.target.groupByTags.splice(index, 1);
+ $scope.groupBySegments.splice(index, 1);
+ $scope.$parent.get_data();
+ return;
+ }
+
if (index === $scope.groupBySegments.length-1) {
$scope.groupBySegments.push(MetricSegment.newPlusButton());
}
+
+ segment.type = 'group-by-key';
+ segment.fake = false;
+
+ $scope.target.groupByTags[index] = segment.value;
+ $scope.$parent.get_data();
};
$scope.changeFunction = function(func) {
@@ -65,7 +88,6 @@ function (angular, _) {
$scope.measurementChanged = function() {
$scope.target.measurement = $scope.measurementSegment.value;
- console.log('measurement updated', $scope.target.measurement);
$scope.$parent.get_data();
};
@@ -126,8 +148,23 @@ function (angular, _) {
.then($scope.transformToSegments)
.then($scope.addTemplateVariableSegments)
.then(function(results) {
- if (queryType === 'TAG_KEYS' && segment.type !== 'plus-button') {
- results.push(new MetricSegment({fake: true, value: 'remove tag filter'}));
+ if (queryType === 'TAG_KEYS' && segment.type === 'key') {
+ results.push(angular.copy($scope.removeTagFilterSegment));
+ }
+ return results;
+ })
+ .then(null, $scope.handleQueryError);
+ };
+
+ $scope.getGroupByTagSegments = function(segment) {
+ var query = 'SHOW TAG KEYS FROM "' + $scope.target.measurement + '"';
+
+ return $scope.datasource.metricFindQuery(query, 'TAG_KEYS')
+ .then($scope.transformToSegments)
+ .then($scope.addTemplateVariableSegments)
+ .then(function(results) {
+ if (segment.type !== 'plus-button') {
+ results.push(angular.copy($scope.removeGroupBySegment));
}
return results;
})
@@ -137,13 +174,15 @@ function (angular, _) {
$scope.tagSegmentUpdated = function(segment, index) {
$scope.tagSegments[index] = segment;
- if (segment.value === 'remove tag filter') {
+ if (segment.value === $scope.removeTagFilterSegment.value) {
$scope.tagSegments.splice(index, 3);
if ($scope.tagSegments.length === 0) {
$scope.tagSegments.push(MetricSegment.newPlusButton());
- } else {
- $scope.tagSegments.splice(index-1, 1);
- $scope.tagSegments.push(MetricSegment.newPlusButton());
+ } else if ($scope.tagSegments.length > 2) {
+ $scope.tagSegments.splice(Math.max(index-1, 0), 1);
+ if ($scope.tagSegments[$scope.tagSegments.length-1].type !== 'plus-button') {
+ $scope.tagSegments.push(MetricSegment.newPlusButton());
+ }
}
}
else {
@@ -151,9 +190,10 @@ function (angular, _) {
if (index > 2) {
$scope.tagSegments.splice(index, 0, MetricSegment.newCondition('AND'));
}
- $scope.tagSegments.push(new MetricSegment({fake: true, value: '=', type: 'operator'}));
- $scope.tagSegments.push(new MetricSegment({fake: true, value: 'select tag value', type: 'value' }));
+ $scope.tagSegments.push(MetricSegment.newFake('=', 'operator', 'query-segment-operator'));
+ $scope.tagSegments.push(MetricSegment.newFake('select tag value', 'value', 'query-segment-value'));
segment.type = 'key';
+ segment.cssClass = 'query-segment-key';
}
if ((index+1) === $scope.tagSegments.length) {
@@ -165,10 +205,13 @@ function (angular, _) {
};
$scope.rebuildTargetTagConditions = function() {
- var tags = [{}];
+ var tags = [];
var tagIndex = 0;
_.each($scope.tagSegments, function(segment2) {
if (segment2.type === 'key') {
+ if (tags.length === 0) {
+ tags.push({});
+ }
tags[tagIndex].key = segment2.value;
}
else if (segment2.type === 'value') {
@@ -211,6 +254,10 @@ function (angular, _) {
return new MetricSegment({value: 'select measurement', fake: true});
};
+ MetricSegment.newFake = function(text, type, cssClass) {
+ return new MetricSegment({value: text, fake: true, type: type, cssClass: cssClass});
+ };
+
MetricSegment.newCondition = function(condition) {
return new MetricSegment({value: condition, type: 'condition', cssClass: 'query-keyword' });
};
diff --git a/public/css/less/grafana.less b/public/css/less/grafana.less
index 9be157f80d0..681702315c0 100644
--- a/public/css/less/grafana.less
+++ b/public/css/less/grafana.less
@@ -343,4 +343,16 @@
color: @blue;
}
+.query-segment-key {
+ border-right: none;
+ padding-right: 1px;
+}
+
+.query-segment-operator {
+ padding-right: 1px;
+ border-right: none;
+ color: @orange;
+}
+
+
diff --git a/public/test/specs/influx09-querybuilder-specs.js b/public/test/specs/influx09-querybuilder-specs.js
index bec2035760f..1b126b0bdf9 100644
--- a/public/test/specs/influx09-querybuilder-specs.js
+++ b/public/test/specs/influx09-querybuilder-specs.js
@@ -18,7 +18,7 @@ define([
});
- describe('series with tags only', function() {
+ describe('series with single tag only', function() {
var builder = new InfluxQueryBuilder({
measurement: 'cpu',
tags: [{key: 'hostname', value: 'server1'}]
@@ -27,12 +27,41 @@ define([
var query = builder.build();
it('should generate correct query', function() {
- expect(query).to.be('SELECT mean(value) FROM "cpu" WHERE $timeFilter ' +
- 'AND hostname=\'server1\' GROUP BY time($interval) ORDER BY asc');
+ expect(query).to.be('SELECT mean(value) FROM "cpu" WHERE hostname=\'server1\' AND $timeFilter'
+ + ' GROUP BY time($interval) ORDER BY asc');
});
});
+ describe('series with multiple tags only', function() {
+ var builder = new InfluxQueryBuilder({
+ measurement: 'cpu',
+ tags: [{key: 'hostname', value: 'server1'}, {key: 'app', value: 'email', condition: "AND"}]
+ });
+
+ var query = builder.build();
+
+ it('should generate correct query', function() {
+ expect(query).to.be('SELECT mean(value) FROM "cpu" WHERE hostname=\'server1\' AND app=\'email\' AND ' +
+ '$timeFilter GROUP BY time($interval) ORDER BY asc');
+ });
+ });
+
+ describe('series with groupByTag', function() {
+ var builder = new InfluxQueryBuilder({
+ measurement: 'cpu',
+ tags: [],
+ groupByTags: ["host"]
+ });
+
+ var query = builder.build();
+
+ it('should generate correct query', function() {
+ expect(query).to.be('SELECT mean(value) FROM "cpu" WHERE $timeFilter ' +
+ 'GROUP BY time($interval), host ORDER BY asc');
+ });
+ });
+
});
});
diff --git a/public/test/specs/influxdbQueryCtrl-specs.js b/public/test/specs/influxdbQueryCtrl-specs.js
index 0e177a6aebe..2b106952160 100644
--- a/public/test/specs/influxdbQueryCtrl-specs.js
+++ b/public/test/specs/influxdbQueryCtrl-specs.js
@@ -101,13 +101,31 @@ define([
});
});
- describe('when deleting is changed', function() {
+ describe('when deleting first tag filter after value is selected', function() {
+ beforeEach(function() {
+ ctx.scope.init();
+ ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0);
+ ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2);
+ ctx.scope.tagSegmentUpdated(ctx.scope.removeTagFilterSegment, 0);
+ });
+
+ it('should remove tags', function() {
+ expect(ctx.scope.target.tags.length).to.be(0);
+ });
+
+ it('should remove all segment after 2 and replace with plus button', function() {
+ expect(ctx.scope.tagSegments.length).to.be(1);
+ expect(ctx.scope.tagSegments[0].type).to.be('plus-button');
+ });
+ });
+
+ describe('when deleting second tag value before second tag value is complete', function() {
beforeEach(function() {
ctx.scope.init();
ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0);
ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2);
ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3);
- ctx.scope.tagSegmentUpdated({value: 'remove tag filter', type: 'key'}, 4);
+ ctx.scope.tagSegmentUpdated(ctx.scope.removeTagFilterSegment, 4);
});
it('should remove all segment after 2 and replace with plus button', function() {
@@ -116,5 +134,69 @@ define([
});
});
+ describe('when deleting second tag value before second tag value is complete', function() {
+ beforeEach(function() {
+ ctx.scope.init();
+ ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0);
+ ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2);
+ ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3);
+ ctx.scope.tagSegmentUpdated(ctx.scope.removeTagFilterSegment, 4);
+ });
+
+ it('should remove all segment after 2 and replace with plus button', function() {
+ expect(ctx.scope.tagSegments.length).to.be(4);
+ expect(ctx.scope.tagSegments[3].type).to.be('plus-button');
+ });
+ });
+
+ describe('when deleting second tag value after second tag filter is complete', function() {
+ beforeEach(function() {
+ ctx.scope.init();
+ ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0);
+ ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2);
+ ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3);
+ ctx.scope.tagSegmentUpdated({value: 'value', type: 'value'}, 6);
+ ctx.scope.tagSegmentUpdated(ctx.scope.removeTagFilterSegment, 4);
+ });
+
+ it('should remove all segment after 2 and replace with plus button', function() {
+ expect(ctx.scope.tagSegments.length).to.be(4);
+ expect(ctx.scope.tagSegments[3].type).to.be('plus-button');
+ });
+ });
+
+ describe('when adding group by', function() {
+ beforeEach(function() {
+ ctx.scope.init();
+ ctx.scope.groupByTagUpdated({value: 'host', type: 'plus-button' }, 0);
+ });
+
+ it('should add group by', function() {
+ expect(ctx.scope.target.groupByTags.length).to.be(1);
+ expect(ctx.scope.target.groupByTags[0]).to.be('host');
+ });
+
+ it('should add another plus button segment', function() {
+ expect(ctx.scope.groupBySegments[1].type).to.be('plus-button');
+ });
+ });
+
+ describe('when removing group by', function() {
+ beforeEach(function() {
+ ctx.scope.init();
+ ctx.scope.groupByTagUpdated({value: 'host', type: 'plus-button' }, 0);
+ ctx.scope.groupByTagUpdated(ctx.scope.removeGroupBySegment, 0);
+ });
+
+ it('should add group by', function() {
+ expect(ctx.scope.target.groupByTags.length).to.be(0);
+ });
+
+ it('should remove segment', function() {
+ expect(ctx.scope.groupBySegments.length).to.be(1);
+ expect(ctx.scope.groupBySegments[0].type).to.be('plus-button');
+ });
+ });
+
});
});