From e694a74c9d59d95b929efefbb346f683ee669031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 21 Sep 2015 19:23:18 +0200 Subject: [PATCH] feat(elasticsearch): work on supporting filters aggregate, #2785 --- .../datasource/elasticsearch/bucketAgg.js | 17 +++++++ .../elasticsearch/elasticResponse.js | 50 +++++++++++------- .../elasticsearch/partials/bucketAgg.html | 5 +- .../datasource/elasticsearch/queryBuilder.js | 33 +++++++++++- .../datasource/elasticsearch/queryDef.js | 1 + public/less/tightform.less | 4 ++ .../specs/elasticsearch-querybuilder-specs.js | 16 ++++++ .../specs/elasticsearch-response-specs.js | 51 +++++++++++++++++++ 8 files changed, 157 insertions(+), 20 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/bucketAgg.js b/public/app/plugins/datasource/elasticsearch/bucketAgg.js index a8e56aa07f3..d96e9ad8090 100644 --- a/public/app/plugins/datasource/elasticsearch/bucketAgg.js +++ b/public/app/plugins/datasource/elasticsearch/bucketAgg.js @@ -35,6 +35,20 @@ function (angular, _, queryDef) { $scope.agg.settings = {}; $scope.showOptions = false; + switch($scope.agg.type) { + case 'date_histogram': + case 'terms': { + delete $scope.agg.query; + $scope.agg.type = 'select field'; + break; + } + case 'filters': { + delete $scope.agg.field; + $scope.agg.query = '*'; + break; + } + } + $scope.validateModel(); $scope.onChange(); }; @@ -65,6 +79,9 @@ function (angular, _, queryDef) { break; } + case 'filters': { + break; + } case 'date_histogram': { settings.interval = settings.interval || 'auto'; $scope.agg.field = $scope.target.timeField; diff --git a/public/app/plugins/datasource/elasticsearch/elasticResponse.js b/public/app/plugins/datasource/elasticsearch/elasticResponse.js index 878b1ce9468..5914e024c44 100644 --- a/public/app/plugins/datasource/elasticsearch/elasticResponse.js +++ b/public/app/plugins/datasource/elasticsearch/elasticResponse.js @@ -10,23 +10,8 @@ function (_, queryDef) { this.response = response; } - // This is quite complex - // neeed to recurise down the nested buckets to build series - ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, level, props) { - var value, metric, i, y, bucket, aggDef, esAgg, newSeries; - - aggDef = target.bucketAggs[level]; - esAgg = aggs[aggDef.id]; - - if (level < target.bucketAggs.length - 1) { - for (i = 0; i < esAgg.buckets.length; i++) { - bucket = esAgg.buckets[i]; - props = _.clone(props); - props[aggDef.field] = bucket.key; - this.processBuckets(bucket, target, seriesList, level+1, props); - } - return; - } + ElasticResponse.prototype.processMetrics = function(esAgg, target, seriesList, props) { + var metric, y, i, newSeries, bucket, value; for (y = 0; y < target.metrics.length; y++) { metric = target.metrics[y]; @@ -101,6 +86,35 @@ function (_, queryDef) { } }; + // This is quite complex + // neeed to recurise down the nested buckets to build series + ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, props) { + var bucket, aggDef, esAgg, aggId; + + for (aggId in aggs) { + aggDef = _.findWhere(target.bucketAggs, {id: aggId}); + esAgg = aggs[aggId]; + if (!aggDef) { + continue; + } + + if (aggDef.type === 'date_histogram') { + this.processMetrics(esAgg, target, seriesList, props); + } else { + for (var nameIndex in esAgg.buckets) { + bucket = esAgg.buckets[nameIndex]; + props = _.clone(props); + if (bucket.key) { + props[aggDef.field] = bucket.key; + } else { + props["filter"] = nameIndex; + } + this.processBuckets(bucket, target, seriesList, props); + } + } + } + }; + ElasticResponse.prototype._getMetricName = function(metric) { var metricDef = _.findWhere(queryDef.metricAggTypes, {value: metric}); if (!metricDef) { @@ -172,7 +186,7 @@ function (_, queryDef) { var target = this.targets[i]; var tmpSeriesList = []; - this.processBuckets(aggregations, target, tmpSeriesList, 0, {}); + this.processBuckets(aggregations, target, tmpSeriesList, {}); this.nameSeries(tmpSeriesList, target); for (var y = 0; y < tmpSeriesList.length; y++) { diff --git a/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html b/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html index e87872fd7bf..2d7349f295a 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html +++ b/public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html @@ -7,9 +7,12 @@
  • -
  • +
  • +
  • + +
  • {{settingsLinkText}}
  • diff --git a/public/app/plugins/datasource/elasticsearch/queryBuilder.js b/public/app/plugins/datasource/elasticsearch/queryBuilder.js index 961f6cad96e..f1d1998e51d 100644 --- a/public/app/plugins/datasource/elasticsearch/queryBuilder.js +++ b/public/app/plugins/datasource/elasticsearch/queryBuilder.js @@ -54,12 +54,34 @@ function (angular) { } }; + ElasticQueryBuilder.prototype.getFiltersAgg = function(target) { + var filterObj = {}; + + for (var i = 0; i < target.bucketAggs.length; i++) { + var aggDef = target.bucketAggs[i]; + if (aggDef.type !== 'filters') { + continue; + } + + filterObj[aggDef.query] = { + query: { + query_string: { + query: aggDef.query, + analyze_wildcard: true + } + } + }; + } + + return filterObj; + }; + ElasticQueryBuilder.prototype.build = function(target) { if (target.rawQuery) { return angular.fromJson(target.rawQuery); } - var i, nestedAggs, metric; + var i, nestedAggs, metric, filtersHandled; var query = { "size": 0, "query": { @@ -95,6 +117,15 @@ function (angular) { }; break; } + case 'filters': { + // skip filters if we already processed them + if (filtersHandled) { + continue; + } + esAgg["filters"] = {filters: this.getFiltersAgg(target)}; + filtersHandled = true; + break; + } case 'terms': { this.buildTermsAgg(aggDef, esAgg, target); break; diff --git a/public/app/plugins/datasource/elasticsearch/queryDef.js b/public/app/plugins/datasource/elasticsearch/queryDef.js index 8770ea25fba..9c6089810ff 100644 --- a/public/app/plugins/datasource/elasticsearch/queryDef.js +++ b/public/app/plugins/datasource/elasticsearch/queryDef.js @@ -18,6 +18,7 @@ function (_) { bucketAggTypes: [ {text: "Terms", value: 'terms' }, + {text: "Filters", value: 'filters' }, {text: "Date Histogram", value: 'date_histogram' }, ], diff --git a/public/less/tightform.less b/public/less/tightform.less index 39c788803a6..494497653ed 100644 --- a/public/less/tightform.less +++ b/public/less/tightform.less @@ -214,6 +214,10 @@ select.tight-form-input { .tight-form-item-xlarge { width: 150px; } .tight-form-item-xxlarge { width: 200px; } +.tight-form-input.tight-form-item-xxlarge { + width: 215px; +} + .tight-form-inner-box { margin: 20px 0 20px 148px; display: inline-block; diff --git a/public/test/specs/elasticsearch-querybuilder-specs.js b/public/test/specs/elasticsearch-querybuilder-specs.js index 0e1b354f60a..f80b29f4021 100644 --- a/public/test/specs/elasticsearch-querybuilder-specs.js +++ b/public/test/specs/elasticsearch-querybuilder-specs.js @@ -95,6 +95,22 @@ define([ expect(firstLevel.aggs["1"].percentiles.percents).to.eql([1,2,3,4]); }); + it('with filters aggs', function() { + var query = builder.build({ + metrics: [{type: 'count', id: '1'}], + timeField: '@timestamp', + bucketAggs: [ + {type: 'filters', query: '@metric:cpu', id: '2'}, + {type: 'filters', query: '@metric:logins.count', id: '3' }, + {type: 'date_histogram', field: '@timestamp', id: '4'} + ], + }); + + expect(query.aggs["2"].filters.filters["@metric:cpu"].query.query_string.query).to.be("@metric:cpu"); + expect(query.aggs["2"].filters.filters["@metric:logins.count"].query.query_string.query).to.be("@metric:logins.count"); + expect(query.aggs["2"].aggs["4"].date_histogram.field).to.be("@timestamp"); + }); + }); }); diff --git a/public/test/specs/elasticsearch-response-specs.js b/public/test/specs/elasticsearch-response-specs.js index 41073d65f16..4001327aa5e 100644 --- a/public/test/specs/elasticsearch-response-specs.js +++ b/public/test/specs/elasticsearch-response-specs.js @@ -351,5 +351,56 @@ define([ }); }); + describe('with two filters agg', function() { + var result; + + beforeEach(function() { + targets = [{ + refId: 'A', + metrics: [{type: 'count', id: '1'}], + bucketAggs: [ + {type: 'filters', query: '@metric:cpu', id: '2'}, + {type: 'filters', query: '@metric:logins.count', id: '5'}, + {type: 'date_histogram', field: '@timestamp', id: '3'} + ], + }]; + response = { + responses: [{ + aggregations: { + "2": { + buckets: { + "@metric:cpu": { + "3": { + buckets: [ + {doc_count: 1, key: 1000}, + {doc_count: 3, key: 2000} + ] + }, + }, + "@metric:logins.count": { + "3": { + buckets: [ + {doc_count: 2, key: 1000}, + {doc_count: 8, key: 2000} + ] + }, + }, + } + } + } + }] + }; + + result = new ElasticResponse(targets, response).getTimeSeries(); + }); + + it('should return 2 series', function() { + expect(result.data.length).to.be(2); + expect(result.data[0].datapoints.length).to.be(2); + expect(result.data[0].target).to.be('@metric:cpu'); + expect(result.data[1].target).to.be('@metric:logins.count'); + }); + }); + }); });