Просмотр исходного кода

feat(elasticsearch): work on supporting filters aggregate, #2785

Torkel Ödegaard 10 лет назад
Родитель
Сommit
e694a74c9d

+ 17 - 0
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;

+ 32 - 18
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++) {

+ 4 - 1
public/app/plugins/datasource/elasticsearch/partials/bucketAgg.html

@@ -7,9 +7,12 @@
 		<li>
 			<metric-segment-model property="agg.type" options="bucketAggTypes" on-change="onTypeChanged()" custom="false" css-class="tight-form-item-large"></metric-segment-model>
 		</li>
-		<li>
+		<li ng-if="agg.type !== 'filters'">
 			<metric-segment-model property="agg.field" get-options="getFields()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment>
 		</li>
+		<li ng-if="agg.type === 'filters'">
+			<input type="text" class="tight-form-input tight-form-item-xxlarge" ng-model="agg.query" spellcheck='false' placeholder="Lucence query" ng-blur="onChange()">
+		</li>
 		<li class="tight-form-item last" ng-if="settingsLinkText">
 			<a ng-click="toggleOptions()">{{settingsLinkText}}</a>
 		</li>

+ 32 - 1
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;

+ 1 - 0
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' },
     ],
 

+ 4 - 0
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;

+ 16 - 0
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");
+    });
+
   });
 
 });

+ 51 - 0
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');
+      });
+    });
+
   });
 });