From 787519ee4ced81c8408f3050685d4f591622b9b8 Mon Sep 17 00:00:00 2001 From: Adrien Grand <jpountz@gmail.com> Date: Fri, 9 Dec 2016 09:48:48 +0100 Subject: [PATCH] Fix `other_bucket` on the `filters` agg to be enabled if a key is set. (#21994) Closes #21951 --- .../filters/FiltersAggregationBuilder.java | 7 +- .../aggregations/metrics/FiltersTests.java | 50 +++++ docs/build.gradle | 1 - .../bucket/filters-aggregation.asciidoc | 178 ++++++++---------- 4 files changed, 135 insertions(+), 101 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filters/FiltersAggregationBuilder.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filters/FiltersAggregationBuilder.java index 2cd4f508ccb..989dcd45353 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filters/FiltersAggregationBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filters/FiltersAggregationBuilder.java @@ -209,7 +209,7 @@ public class FiltersAggregationBuilder extends AbstractAggregationBuilder<Filter XContentParser.Token token = null; String currentFieldName = null; String otherBucketKey = null; - Boolean otherBucket = false; + Boolean otherBucket = null; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { currentFieldName = parser.currentName(); @@ -260,8 +260,9 @@ public class FiltersAggregationBuilder extends AbstractAggregationBuilder<Filter } } - if (otherBucket && otherBucketKey == null) { - otherBucketKey = "_other_"; + if (otherBucket == null && otherBucketKey != null) { + // automatically enable the other bucket if a key is set, as per the doc + otherBucket = true; } FiltersAggregationBuilder factory; diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/FiltersTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/FiltersTests.java index 89fc38b7cd8..a1fe372687b 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/FiltersTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/FiltersTests.java @@ -19,11 +19,23 @@ package org.elasticsearch.search.aggregations.metrics; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.MatchNoneQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.QueryParseContext; +import org.elasticsearch.indices.query.IndicesQueriesRegistry; import org.elasticsearch.search.aggregations.BaseAggregationTestCase; import org.elasticsearch.search.aggregations.bucket.filters.FiltersAggregator.KeyedFilter; +import org.elasticsearch.search.slice.SliceBuilder; + +import java.io.IOException; + import org.elasticsearch.search.aggregations.bucket.filters.FiltersAggregationBuilder; public class FiltersTests extends BaseAggregationTestCase<FiltersAggregationBuilder> { @@ -73,4 +85,42 @@ public class FiltersTests extends BaseAggregationTestCase<FiltersAggregationBuil assertEquals("aaa", original[1].key()); } + public void testOtherBucket() throws IOException { + XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values())); + builder.startObject(); + builder.startArray("filters").endArray(); + builder.endObject(); + XContentParser parser = XContentHelper.createParser(shuffleXContent(builder).bytes()); + parser.nextToken(); + QueryParseContext context = new QueryParseContext(new IndicesQueriesRegistry(), parser, + ParseFieldMatcher.STRICT); + FiltersAggregationBuilder filters = FiltersAggregationBuilder.parse("agg_name", context); + // The other bucket is disabled by default + assertFalse(filters.otherBucket()); + + builder = XContentFactory.contentBuilder(randomFrom(XContentType.values())); + builder.startObject(); + builder.startArray("filters").endArray(); + builder.field("other_bucket_key", "some_key"); + builder.endObject(); + parser = XContentHelper.createParser(shuffleXContent(builder).bytes()); + parser.nextToken(); + context = new QueryParseContext(new IndicesQueriesRegistry(), parser, ParseFieldMatcher.STRICT); + filters = FiltersAggregationBuilder.parse("agg_name", context); + // but setting a key enables it automatically + assertTrue(filters.otherBucket()); + + builder = XContentFactory.contentBuilder(randomFrom(XContentType.values())); + builder.startObject(); + builder.startArray("filters").endArray(); + builder.field("other_bucket", false); + builder.field("other_bucket_key", "some_key"); + builder.endObject(); + parser = XContentHelper.createParser(shuffleXContent(builder).bytes()); + parser.nextToken(); + context = new QueryParseContext(new IndicesQueriesRegistry(), parser, ParseFieldMatcher.STRICT); + filters = FiltersAggregationBuilder.parse("agg_name", context); + // unless the other bucket is explicitly disabled + assertFalse(filters.otherBucket()); + } } diff --git a/docs/build.gradle b/docs/build.gradle index 891a8400e4c..5ba625cad94 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -28,7 +28,6 @@ buildRestTests.expectedUnconvertedCandidates = [ 'reference/aggregations/bucket/daterange-aggregation.asciidoc', 'reference/aggregations/bucket/diversified-sampler-aggregation.asciidoc', 'reference/aggregations/bucket/filter-aggregation.asciidoc', - 'reference/aggregations/bucket/filters-aggregation.asciidoc', 'reference/aggregations/bucket/geodistance-aggregation.asciidoc', 'reference/aggregations/bucket/geohashgrid-aggregation.asciidoc', 'reference/aggregations/bucket/global-aggregation.asciidoc', diff --git a/docs/reference/aggregations/bucket/filters-aggregation.asciidoc b/docs/reference/aggregations/bucket/filters-aggregation.asciidoc index 7ba9d22d8d9..97b7f2a50e1 100644 --- a/docs/reference/aggregations/bucket/filters-aggregation.asciidoc +++ b/docs/reference/aggregations/bucket/filters-aggregation.asciidoc @@ -9,62 +9,61 @@ Example: [source,js] -------------------------------------------------- +PUT /logs/message/_bulk?refresh +{ "index" : { "_id" : 1 } } +{ "body" : "warning: page could not be rendered" } +{ "index" : { "_id" : 2 } } +{ "body" : "authentication error" } +{ "index" : { "_id" : 3 } } +{ "body" : "warning: connection timed out" } + +GET logs/_search { + "size": 0, "aggs" : { "messages" : { "filters" : { "filters" : { - "errors" : { "term" : { "body" : "error" }}, - "warnings" : { "term" : { "body" : "warning" }} - } - }, - "aggs" : { - "monthly" : { - "histogram" : { - "field" : "timestamp", - "interval" : "1M" - } + "errors" : { "match" : { "body" : "error" }}, + "warnings" : { "match" : { "body" : "warning" }} } } } } } -------------------------------------------------- +// CONSOLE In the above example, we analyze log messages. The aggregation will build two collection (buckets) of log messages - one for all those containing an error, -and another for all those containing a warning. And for each of these buckets -it will break them down by month. +and another for all those containing a warning. Response: [source,js] -------------------------------------------------- -... - "aggs" : { - "messages" : { - "buckets" : { - "errors" : { - "doc_count" : 34, - "monthly" : { - "buckets" : [ - ... // the histogram monthly breakdown - ] - } +{ + "took": 9, + "timed_out": false, + "_shards": ..., + "hits": ..., + "aggregations": { + "messages": { + "buckets": { + "errors": { + "doc_count": 1 }, - "warnings" : { - "doc_count" : 439, - "monthly" : { - "buckets" : [ - ... // the histogram monthly breakdown - ] - } + "warnings": { + "doc_count": 2 } } } } -... +} -------------------------------------------------- +// TESTRESPONSE[s/"took": 9/"took": $body.took/] +// TESTRESPONSE[s/"_shards": \.\.\./"_shards": $body._shards/] +// TESTRESPONSE[s/"hits": \.\.\./"hits": $body.hits/] ==== Anonymous filters @@ -73,58 +72,51 @@ following request: [source,js] -------------------------------------------------- +GET logs/_search { + "size": 0, "aggs" : { "messages" : { "filters" : { "filters" : [ - { "term" : { "body" : "error" }}, - { "term" : { "body" : "warning" }} + { "match" : { "body" : "error" }}, + { "match" : { "body" : "warning" }} ] - }, - "aggs" : { - "monthly" : { - "histogram" : { - "field" : "timestamp", - "interval" : "1M" - } - } } } } } -------------------------------------------------- +// CONSOLE +// TEST[continued] The filtered buckets are returned in the same order as provided in the request. The response for this example would be: [source,js] -------------------------------------------------- -... - "aggs" : { - "messages" : { - "buckets" : [ +{ + "took": 4, + "timed_out": false, + "_shards": ..., + "hits": ..., + "aggregations": { + "messages": { + "buckets": [ { - "doc_count" : 34, - "monthly" : { - "buckets" : [ - ... // the histogram monthly breakdown - ] - } + "doc_count": 1 }, { - "doc_count" : 439, - "monthly" : { - "buckets" : [ - ... // the histogram monthly breakdown - ] - } + "doc_count": 2 } ] } } -... +} -------------------------------------------------- +// TESTRESPONSE[s/"took": 4/"took": $body.took/] +// TESTRESPONSE[s/"_shards": \.\.\./"_shards": $body._shards/] +// TESTRESPONSE[s/"hits": \.\.\./"hits": $body.hits/] ==== `Other` Bucket @@ -142,64 +134,56 @@ The following snippet shows a response where the `other` bucket is requested to [source,js] -------------------------------------------------- +PUT logs/message/4?refresh { + "body": "info: user Bob logged out" +} + +GET logs/_search +{ + "size": 0, "aggs" : { "messages" : { "filters" : { "other_bucket_key": "other_messages", "filters" : { - "errors" : { "term" : { "body" : "error" }}, - "warnings" : { "term" : { "body" : "warning" }} - } - }, - "aggs" : { - "monthly" : { - "histogram" : { - "field" : "timestamp", - "interval" : "1M" - } + "errors" : { "match" : { "body" : "error" }}, + "warnings" : { "match" : { "body" : "warning" }} } } } } } -------------------------------------------------- +// CONSOLE +// TEST[continued] The response would be something like the following: [source,js] -------------------------------------------------- -... - "aggs" : { - "messages" : { - "buckets" : { - "errors" : { - "doc_count" : 34, - "monthly" : { - "buckets" : [ - ... // the histogram monthly breakdown - ] - } - }, - "warnings" : { - "doc_count" : 439, - "monthly" : { - "buckets" : [ - ... // the histogram monthly breakdown - ] - } - }, - "other_messages" : { - "doc_count" : 237, - "monthly" : { - "buckets" : [ - ... // the histogram monthly breakdown - ] - } - } +{ + "took": 3, + "timed_out": false, + "_shards": ..., + "hits": ..., + "aggregations": { + "messages": { + "buckets": { + "errors": { + "doc_count": 1 + }, + "warnings": { + "doc_count": 2 + }, + "other_messages": { + "doc_count": 1 } } } } -... +} -------------------------------------------------- +// TESTRESPONSE[s/"took": 3/"took": $body.took/] +// TESTRESPONSE[s/"_shards": \.\.\./"_shards": $body._shards/] +// TESTRESPONSE[s/"hits": \.\.\./"hits": $body.hits/]