Browse Source

Add support for metrics aggregations to mvt end point (#78614)

It adds support for several aggregations.
Ignacio Vera 4 years ago
parent
commit
920b3b52c2

+ 7 - 0
docs/reference/search/search-vector-tile-api.asciidoc

@@ -260,10 +260,17 @@ If `false`, the response does not include the total number of hits matching the
 aggregation types:
 +
 * <<search-aggregations-metrics-avg-aggregation,`avg`>>
+* <<search-aggregations-metrics-boxplot-aggregation,`boxplot`>>
 * <<search-aggregations-metrics-cardinality-aggregation,`cardinality`>>
+* <<search-aggregations-metrics-extendedstats-aggregation,`extended stats`>>
 * <<search-aggregations-metrics-max-aggregation,`max`>>
+* <<search-aggregations-metrics-median-absolute-deviation-aggregation,`median absolute deviation`>>
 * <<search-aggregations-metrics-min-aggregation,`min`>>
+* <<search-aggregations-metrics-percentile-aggregation,`percentile`>>
+* <<search-aggregations-metrics-percentile-rank-aggregation,`percentile-rank`>>
+* <<search-aggregations-metrics-stats-aggregation,`stats`>>
 * <<search-aggregations-metrics-sum-aggregation,`sum`>>
+* <<search-aggregations-metrics-valuecount-aggregation,`value count`>>
 +
 The aggregation names can't start with `_mvt_`. The `_mvt_` prefix is reserved
 for internal aggregations.

+ 33 - 4
x-pack/plugin/vector-tile/src/javaRestTest/java/org/elasticsearch/xpack/vectortile/VectorTileRestIT.java

@@ -652,7 +652,7 @@ public class VectorTileRestIT extends ESRestTestCase {
         assertLayer(tile, META_LAYER, 4096, 1, 13);
     }
 
-    public void testMinAgg() throws Exception {
+    public void testSingleValueAgg() throws Exception {
         final Request mvtRequest = new Request(getHttpMethod(), INDEX_POLYGON + "/_mvt/location/" + z + "/" + x + "/" + y);
         mvtRequest.setJsonEntity(
             "{\n"
@@ -676,6 +676,35 @@ public class VectorTileRestIT extends ESRestTestCase {
         assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.minVal.max", 1.0);
     }
 
+    public void testMultiValueAgg() throws Exception {
+        final Request mvtRequest = new Request(getHttpMethod(), INDEX_POLYGON + "/_mvt/location/" + z + "/" + x + "/" + y);
+        mvtRequest.setJsonEntity(
+            "{\n"
+                + "  \"aggs\": {\n"
+                + "    \"percentilesAgg\": {\n"
+                + "      \"percentiles\": {\n"
+                + "         \"field\": \"value1\",\n"
+                + "         \"percents\": [95, 99, 99.9]\n"
+                + "        }\n"
+                + "    }\n"
+                + "  }\n"
+                + "}"
+        );
+        final VectorTile.Tile tile = execute(mvtRequest);
+        assertThat(tile.getLayersCount(), Matchers.equalTo(3));
+        assertLayer(tile, HITS_LAYER, 4096, 1, 2);
+        assertLayer(tile, AGGS_LAYER, 4096, 256 * 256, 4);
+        assertLayer(tile, META_LAYER, 4096, 1, 28);
+        // check pipeline aggregation values
+        final VectorTile.Tile.Layer metaLayer = getLayer(tile, META_LAYER);
+        assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.95.0.min", 1.0);
+        assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.95.0.max", 1.0);
+        assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.99.0.min", 1.0);
+        assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.99.0.max", 1.0);
+        assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.99.9.min", 1.0);
+        assertDoubleTag(metaLayer, metaLayer.getFeatures(0), "aggregations.percentilesAgg.99.9.max", 1.0);
+    }
+
     public void testOverlappingMultipolygon() throws Exception {
         // Overlapping multipolygon are accepted by Elasticsearch but is invalid for JTS. This
         // causes and error in the mvt library that gets logged using slf4j
@@ -720,7 +749,7 @@ public class VectorTileRestIT extends ESRestTestCase {
                 return;
             }
         }
-        fail("Could not find tag [" + tag + " ]");
+        fail("Could not find tag [" + tag + "]");
     }
 
     private void assertDoubleTag(VectorTile.Tile.Layer layer, VectorTile.Tile.Feature feature, String tag, double value) {
@@ -732,7 +761,7 @@ public class VectorTileRestIT extends ESRestTestCase {
                 return;
             }
         }
-        fail("Could not find tag [" + tag + " ]");
+        fail("Could not find tag [" + tag + "]");
     }
 
     private void assertStringTag(VectorTile.Tile.Layer layer, VectorTile.Tile.Feature feature, String tag, String value) {
@@ -744,7 +773,7 @@ public class VectorTileRestIT extends ESRestTestCase {
                 return;
             }
         }
-        fail("Could not find tag [" + tag + " ]");
+        fail("Could not find tag [" + tag + "]");
     }
 
     private VectorTile.Tile execute(Request mvtRequest) throws IOException {

+ 27 - 22
x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/rest/RestVectorTileAction.java

@@ -33,9 +33,7 @@ import org.elasticsearch.rest.action.RestResponseListener;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.SearchHits;
 import org.elasticsearch.search.aggregations.Aggregation;
-import org.elasticsearch.search.aggregations.AggregationBuilder;
 import org.elasticsearch.search.aggregations.Aggregations;
-import org.elasticsearch.search.aggregations.AggregatorFactories;
 import org.elasticsearch.search.aggregations.bucket.geogrid.GeoGridAggregationBuilder;
 import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileGridAggregationBuilder;
 import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils;
@@ -46,13 +44,14 @@ import org.elasticsearch.search.aggregations.metrics.GeoCentroidAggregationBuild
 import org.elasticsearch.search.aggregations.metrics.InternalGeoBounds;
 import org.elasticsearch.search.aggregations.metrics.InternalGeoCentroid;
 import org.elasticsearch.search.aggregations.pipeline.StatsBucketPipelineAggregationBuilder;
+import org.elasticsearch.search.aggregations.support.ValuesSourceAggregationBuilder.MetricsAggregationBuilder;
 import org.elasticsearch.search.fetch.subphase.FieldAndFormat;
 import org.elasticsearch.search.profile.SearchProfileResults;
 import org.elasticsearch.search.sort.SortBuilder;
 
 import java.io.IOException;
-import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import static org.elasticsearch.rest.RestRequest.Method.GET;
@@ -209,27 +208,33 @@ public class RestVectorTileAction extends BaseRestHandler {
             searchRequestBuilder.addAggregation(tileAggBuilder);
             searchRequestBuilder.addAggregation(new StatsBucketPipelineAggregationBuilder(COUNT_TAG, GRID_FIELD + "." + COUNT_TAG));
             if (request.getGridType() == VectorTileRequest.GRID_TYPE.CENTROID) {
-                GeoCentroidAggregationBuilder centroidAggregationBuilder = new GeoCentroidAggregationBuilder(CENTROID_AGG_NAME);
-                centroidAggregationBuilder.field(request.getField());
-                tileAggBuilder.subAggregation(centroidAggregationBuilder);
+                tileAggBuilder.subAggregation(new GeoCentroidAggregationBuilder(CENTROID_AGG_NAME).field(request.getField()));
             }
-            final AggregatorFactories.Builder otherAggBuilder = request.getAggBuilder();
-            if (otherAggBuilder != null) {
-                final Collection<AggregationBuilder> aggregations = otherAggBuilder.getAggregatorFactories();
-                for (AggregationBuilder aggregation : aggregations) {
-                    if (aggregation.getName().startsWith(INTERNAL_AGG_PREFIX)) {
-                        throw new IllegalArgumentException(
-                            "Invalid aggregation name ["
-                                + aggregation.getName()
-                                + "]. Aggregation names cannot start with prefix '"
-                                + INTERNAL_AGG_PREFIX
-                                + "'"
-                        );
+            final List<MetricsAggregationBuilder<?, ?>> aggregations = request.getAggBuilder();
+            for (MetricsAggregationBuilder<?, ?> aggregation : aggregations) {
+                if (aggregation.getName().startsWith(INTERNAL_AGG_PREFIX)) {
+                    throw new IllegalArgumentException(
+                        "Invalid aggregation name ["
+                            + aggregation.getName()
+                            + "]. Aggregation names cannot start with prefix '"
+                            + INTERNAL_AGG_PREFIX
+                            + "'"
+                    );
+                }
+                tileAggBuilder.subAggregation(aggregation);
+                final Set<String> metricNames = aggregation.metricNames();
+                for (String metric : metricNames) {
+                    final String bucketPath;
+                    // handle the case where the metric contains a dot
+                    if (metric.contains(".")) {
+                        bucketPath = GRID_FIELD + ">" + aggregation.getName() + "[" + metric + "]";
+                    } else {
+                        bucketPath = GRID_FIELD + ">" + aggregation.getName() + "." + metric;
                     }
-                    tileAggBuilder.subAggregation(aggregation);
-                    // we add the metric (.value) to the path in order to support aggregation names with '.'
-                    final String bucketPath = GRID_FIELD + ">" + aggregation.getName() + ".value";
-                    searchRequestBuilder.addAggregation(new StatsBucketPipelineAggregationBuilder(aggregation.getName(), bucketPath));
+                    // we only add the metric name to multi-value metric aggregations
+                    final String aggName = metricNames.size() == 1 ? aggregation.getName() : aggregation.getName() + "." + metric;
+                    searchRequestBuilder.addAggregation(new StatsBucketPipelineAggregationBuilder(aggName, bucketPath));
+
                 }
             }
         }

+ 13 - 21
x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/rest/VectorTileRequest.java

@@ -22,11 +22,7 @@ import org.elasticsearch.search.aggregations.AggregationBuilder;
 import org.elasticsearch.search.aggregations.AggregatorFactories;
 import org.elasticsearch.search.aggregations.PipelineAggregationBuilder;
 import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils;
-import org.elasticsearch.search.aggregations.metrics.AvgAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.CardinalityAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.MaxAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.MinAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
+import org.elasticsearch.search.aggregations.support.ValuesSourceAggregationBuilder.MetricsAggregationBuilder;
 import org.elasticsearch.search.builder.SearchSourceBuilder;
 import org.elasticsearch.search.fetch.subphase.FieldAndFormat;
 import org.elasticsearch.search.sort.ScriptSortBuilder;
@@ -85,7 +81,7 @@ class VectorTileRequest {
         public static final List<FieldAndFormat> FETCH = emptyList();
         public static final Map<String, Object> RUNTIME_MAPPINGS = emptyMap();
         public static final QueryBuilder QUERY = null;
-        public static final AggregatorFactories.Builder AGGS = null;
+        public static final List<MetricsAggregationBuilder<?, ?>> AGGS = emptyList();
         public static final int GRID_PRECISION = 8;
         public static final GRID_TYPE GRID_TYPE = VectorTileRequest.GRID_TYPE.GRID;
         public static final int EXTENT = 4096;
@@ -211,7 +207,7 @@ class VectorTileRequest {
     private GRID_TYPE gridType = Defaults.GRID_TYPE;
     private int size = Defaults.SIZE;
     private int extent = Defaults.EXTENT;
-    private AggregatorFactories.Builder aggBuilder = Defaults.AGGS;
+    private List<MetricsAggregationBuilder<?, ?>> aggs = Defaults.AGGS;
     private List<FieldAndFormat> fields = Defaults.FETCH;
     private List<SortBuilder<?>> sortBuilders;
     private boolean exact_bounds = Defaults.EXACT_BOUNDS;
@@ -328,23 +324,19 @@ class VectorTileRequest {
         this.size = size;
     }
 
-    public AggregatorFactories.Builder getAggBuilder() {
-        return aggBuilder;
+    public List<MetricsAggregationBuilder<?, ?>> getAggBuilder() {
+        return aggs;
     }
 
     private void setAggBuilder(AggregatorFactories.Builder aggBuilder) {
+        List<MetricsAggregationBuilder<?, ?>> aggs = new ArrayList<>(aggBuilder.count());
         for (AggregationBuilder aggregation : aggBuilder.getAggregatorFactories()) {
-            final String type = aggregation.getType();
-            switch (type) {
-                case MinAggregationBuilder.NAME:
-                case MaxAggregationBuilder.NAME:
-                case AvgAggregationBuilder.NAME:
-                case SumAggregationBuilder.NAME:
-                case CardinalityAggregationBuilder.NAME:
-                    break;
-                default:
-                    // top term and percentile should be supported
-                    throw new IllegalArgumentException("Unsupported aggregation of type [" + type + "]");
+            if (aggregation instanceof MetricsAggregationBuilder<?, ?>) {
+                aggs.add((MetricsAggregationBuilder<?, ?>) aggregation);
+            } else {
+                throw new IllegalArgumentException(
+                    "Unsupported aggregation of type [" + aggregation.getType() + "]." + "Only metric aggregations are supported."
+                );
             }
         }
         for (PipelineAggregationBuilder aggregation : aggBuilder.getPipelineAggregatorFactories()) {
@@ -352,7 +344,7 @@ class VectorTileRequest {
             final String type = aggregation.getType();
             throw new IllegalArgumentException("Unsupported pipeline aggregation of type [" + type + "]");
         }
-        this.aggBuilder = aggBuilder;
+        this.aggs = aggs;
     }
 
     public List<SortBuilder<?>> getSortBuilders() {

+ 2 - 2
x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/rest/VectorTileRequestTests.java

@@ -150,8 +150,8 @@ public class VectorTileRequestTests extends ESTestCase {
             aggregationBuilder.toXContent(builder, ToXContent.EMPTY_PARAMS);
             builder.endObject();
         }, (vectorTileRequest) -> {
-            assertThat(vectorTileRequest.getAggBuilder().getAggregatorFactories(), Matchers.iterableWithSize(1));
-            assertThat(vectorTileRequest.getAggBuilder().getAggregatorFactories().contains(aggregationBuilder), Matchers.equalTo(true));
+            assertThat(vectorTileRequest.getAggBuilder(), Matchers.iterableWithSize(1));
+            assertThat(vectorTileRequest.getAggBuilder().contains(aggregationBuilder), Matchers.equalTo(true));
         });
     }
 

+ 106 - 0
x-pack/plugin/vector-tile/src/yamlRestTest/resources/rest-api-spec/test/20_aggregations.yml

@@ -93,3 +93,109 @@ setup:
             test.cardinality_value:
               cardinality:
                 field: value
+
+---
+"value count agg":
+  - do:
+      search_mvt:
+        index: locations
+        field: location
+        x: 0
+        y: 0
+        zoom: 0
+        body:
+          aggs:
+            test.value_count_value:
+              value_count:
+                field: value
+
+---
+"median absolute deviation agg":
+  - do:
+      search_mvt:
+        index: locations
+        field: location
+        x: 0
+        y: 0
+        zoom: 0
+        body:
+          aggs:
+            test.median_absolute_deviation_value:
+              median_absolute_deviation:
+                field: value
+
+---
+"stats agg":
+  - do:
+      search_mvt:
+        index: locations
+        field: location
+        x: 0
+        y: 0
+        zoom: 0
+        body:
+          aggs:
+            test.stats_values:
+              stats:
+                field: value
+
+---
+"extended stats agg":
+  - do:
+      search_mvt:
+        index: locations
+        field: location
+        x: 0
+        y: 0
+        zoom: 0
+        body:
+          aggs:
+            test.extended_stats_values:
+              extended_stats:
+                field: value
+
+---
+"percentile agg":
+  - do:
+      search_mvt:
+        index: locations
+        field: location
+        x: 0
+        y: 0
+        zoom: 0
+        body:
+          aggs:
+            test.percentiles_values:
+              percentiles:
+                field: value
+
+---
+"percentile rank agg":
+  - do:
+      search_mvt:
+        index: locations
+        field: location
+        x: 0
+        y: 0
+        zoom: 0
+        body:
+          aggs:
+            test.percentile_ranks_values:
+              percentile_ranks:
+                field: value
+                values: [5, 10]
+
+---
+"boxplot deviation agg":
+  - do:
+      search_mvt:
+        index: locations
+        field: location
+        x: 0
+        y: 0
+        zoom: 0
+        body:
+          aggs:
+            test.boxplot_values:
+              boxplot:
+                field: value