From a749bf91cf5fb0e53aabbd3978246883e4b95b67 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Wed, 3 Mar 2021 11:29:20 +0000 Subject: [PATCH 1/2] Add multi terms aggregation (#5372) * Implement multi terms aggregation * Add skip version --- docs/aggregations.asciidoc | 4 + .../multi-terms-aggregation-usage.asciidoc | 131 ++++++++++++++++++ src/Nest/Aggregations/AggregateDictionary.cs | 38 +++++ src/Nest/Aggregations/AggregateFormatter.cs | 30 +++- src/Nest/Aggregations/AggregationContainer.cs | 12 ++ .../Bucket/MultiTerms/MultiTermsAggregate.cs | 12 ++ .../MultiTerms/MultiTermsAggregation.cs | 111 +++++++++++++++ .../Bucket/MultiTerms/MultiTermsBucket.cs | 18 +++ .../Aggregations/Bucket/MultiTerms/Term.cs | 35 +++++ .../Aggregations/Bucket/Terms/TermsInclude.cs | 2 +- .../Visitor/AggregationVisitor.cs | 5 + .../Aggregations/Visitor/AggregationWalker.cs | 5 + .../MultiTermsAggregationUsageTests.cs | 120 ++++++++++++++++ 13 files changed, 519 insertions(+), 4 deletions(-) create mode 100644 docs/aggregations/bucket/multi-terms/multi-terms-aggregation-usage.asciidoc create mode 100644 src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregate.cs create mode 100644 src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregation.cs create mode 100644 src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsBucket.cs create mode 100644 src/Nest/Aggregations/Bucket/MultiTerms/Term.cs create mode 100644 tests/Tests/Aggregations/Bucket/MultiTerms/MultiTermsAggregationUsageTests.cs diff --git a/docs/aggregations.asciidoc b/docs/aggregations.asciidoc index dd4f10f5f9a..32313dd4ac9 100644 --- a/docs/aggregations.asciidoc +++ b/docs/aggregations.asciidoc @@ -168,6 +168,8 @@ In addition to the buckets themselves, the bucket aggregations also compute and * <> +* <> + * <> * <> @@ -231,6 +233,8 @@ include::aggregations/bucket/ip-range/ip-range-aggregation-usage.asciidoc[] include::aggregations/bucket/missing/missing-aggregation-usage.asciidoc[] +include::aggregations/bucket/multi-terms/multi-terms-aggregation-usage.asciidoc[] + include::aggregations/bucket/nested/nested-aggregation-usage.asciidoc[] include::aggregations/bucket/parent/parent-aggregation-usage.asciidoc[] diff --git a/docs/aggregations/bucket/multi-terms/multi-terms-aggregation-usage.asciidoc b/docs/aggregations/bucket/multi-terms/multi-terms-aggregation-usage.asciidoc new file mode 100644 index 00000000000..d64c8f43b3d --- /dev/null +++ b/docs/aggregations/bucket/multi-terms/multi-terms-aggregation-usage.asciidoc @@ -0,0 +1,131 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/7.x + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +//// +IMPORTANT NOTE +============== +This file has been generated from https://github.com/elastic/elasticsearch-net/tree/7.x/src/Tests/Tests/Aggregations/Bucket/MultiTerms/MultiTermsAggregationUsageTests.cs. +If you wish to submit a PR for any spelling mistakes, typos or grammatical errors for this file, +please modify the original csharp file found at the link and submit the PR with that change. Thanks! +//// + +[[multi-terms-aggregation-usage]] +=== Multi Terms Aggregation Usage + +A multi-bucket value source based aggregation where buckets are dynamically built - one per unique set of values. + +See the Elasticsearch documentation on {ref_current}//search-aggregations-bucket-multi-terms-aggregation.html[multi terms aggregation] for more detail. + +==== Fluent DSL example + +[source,csharp] +---- +a => a +.MultiTerms("states", st => st + .CollectMode(TermsAggregationCollectMode.BreadthFirst) + .Terms(t => t.Field(f => f.Name), t => t.Field(f => f.NumberOfCommits).Missing(0)) + .MinimumDocumentCount(1) + .Size(5) + .ShardSize(100) + .ShardMinimumDocumentCount(1) + .ShowTermDocCountError(true) + .Order(o => o + .KeyAscending() + .CountDescending() + ) + .Meta(m => m + .Add("foo", "bar") + ) +) +---- + +==== Object Initializer syntax example + +[source,csharp] +---- +new MultiTermsAggregation("states") +{ + CollectMode = TermsAggregationCollectMode.BreadthFirst, + Terms = new List + { + new() {Field = Field(f => f.Name) }, + new() {Field = Field(f => f.NumberOfCommits), Missing = 0 } + }, + MinimumDocumentCount = 1, + Size = 5, + ShardSize = 100, + ShardMinimumDocumentCount = 1, + ShowTermDocCountError = true, + Order = new List + { + TermsOrder.KeyAscending, + TermsOrder.CountDescending + }, + Meta = new Dictionary + { + { "foo", "bar" } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "states": { + "meta": { + "foo": "bar" + }, + "multi_terms": { + "collect_mode": "breadth_first", + "terms": [ + { + "field": "name" + }, + { + "field": "numberOfCommits", + "missing": 0 + } + ], + "min_doc_count": 1, + "shard_min_doc_count": 1, + "size": 5, + "shard_size": 100, + "show_term_doc_count_error": true, + "order": [ + { + "_key": "asc" + }, + { + "_count": "desc" + } + ] + } + } +} +---- + +==== Handling Responses + +[source,csharp] +---- +response.ShouldBeValid(); +var states = response.Aggregations.MultiTerms("states"); +states.Should().NotBeNull(); +states.DocCountErrorUpperBound.Should().HaveValue(); +states.SumOtherDocCount.Should().HaveValue(); +states.Buckets.Should().NotBeNull(); +states.Buckets.Count.Should().BeGreaterThan(0); +foreach (var item in states.Buckets) +{ + item.Key.Should().NotBeNullOrEmpty(); + item.DocCount.Should().BeGreaterOrEqualTo(1); + item.KeyAsString.Should().NotBeNullOrEmpty(); +} +states.Meta.Should().NotBeNull().And.HaveCount(1); +states.Meta["foo"].Should().Be("bar"); +---- + diff --git a/src/Nest/Aggregations/AggregateDictionary.cs b/src/Nest/Aggregations/AggregateDictionary.cs index 21ffa0ef62e..62e6d0ce216 100644 --- a/src/Nest/Aggregations/AggregateDictionary.cs +++ b/src/Nest/Aggregations/AggregateDictionary.cs @@ -221,6 +221,22 @@ public MultiBucketAggregate> RareTerms(string key) public MultiBucketAggregate DateHistogram(string key) => GetMultiBucketAggregate(key); + public MultiTermsAggregate MultiTerms(string key) => MultiTerms(key); + + public MultiTermsAggregate MultiTerms(string key) + { + var bucket = TryGet(key); + return bucket == null + ? null + : new MultiTermsAggregate + { + DocCountErrorUpperBound = bucket.DocCountErrorUpperBound, + SumOtherDocCount = bucket.SumOtherDocCount, + Buckets = GetMultiTermsBuckets(bucket.Items).ToList(), + Meta = bucket.Meta + }; + } + public AutoDateHistogramAggregate AutoDateHistogram(string key) { var bucket = TryGet(key); @@ -324,6 +340,28 @@ private IEnumerable> GetRareTermsBuckets(IEnumerable }; } + private static IEnumerable> GetMultiTermsBuckets(IEnumerable items) + { + var buckets = items.Cast>(); + + foreach (var bucket in buckets) + { + var aggregates = new MultiTermsBucket(bucket.BackingDictionary) + { + DocCount = bucket.DocCount, + DocCountErrorUpperBound = bucket.DocCountErrorUpperBound, + KeyAsString = bucket.KeyAsString + }; + + if (bucket.Key is List allKeys) + aggregates.Key = allKeys.Select(GetKeyFromBucketKey).ToList(); + else + aggregates.Key = new[] { GetKeyFromBucketKey(bucket.Key) }; + + yield return aggregates; + } + } + private static TKey GetKeyFromBucketKey(object key) => typeof(TKey).IsEnum ? (TKey)Enum.Parse(typeof(TKey), key.ToString(), true) diff --git a/src/Nest/Aggregations/AggregateFormatter.cs b/src/Nest/Aggregations/AggregateFormatter.cs index 27eb7930d2e..6ac3817821b 100644 --- a/src/Nest/Aggregations/AggregateFormatter.cs +++ b/src/Nest/Aggregations/AggregateFormatter.cs @@ -935,9 +935,7 @@ private IBucket GetDateHistogramBucket(ref JsonReader reader, IJsonFormatterReso return dateHistogram; } - - - + private IBucket GetKeyedBucket(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { var token = reader.GetCurrentJsonToken(); @@ -947,6 +945,32 @@ private IBucket GetKeyedBucket(ref JsonReader reader, IJsonFormatterResolver for object key; if (token == JsonToken.String) key = reader.ReadString(); + else if (token == JsonToken.BeginArray) // we're in a multi-terms bucket + { + var keys = new List(); + var count = 0; + + while(reader.ReadIsInArray(ref count)) + { + object keyItem; + + var keyToken = reader.GetCurrentJsonToken(); + + if (keyToken == JsonToken.String) + keyItem = reader.ReadString(); + else + { + var numberKey = reader.ReadNumberSegment(); + if (numberKey.IsLong()) + keyItem = NumberConverter.ReadInt64(numberKey.Array, numberKey.Offset, out _); + else + keyItem = NumberConverter.ReadDouble(numberKey.Array, numberKey.Offset, out _); + } + + keys.Add(keyItem); + } + key = keys; + } else { var numberSegment = reader.ReadNumberSegment(); diff --git a/src/Nest/Aggregations/AggregationContainer.cs b/src/Nest/Aggregations/AggregationContainer.cs index 58716deba86..625e725a60d 100644 --- a/src/Nest/Aggregations/AggregationContainer.cs +++ b/src/Nest/Aggregations/AggregationContainer.cs @@ -294,6 +294,9 @@ public interface IAggregationContainer [DataMember(Name = "top_metrics")] ITopMetricsAggregation TopMetrics { get; set; } + [DataMember(Name = "multi_terms")] + IMultiTermsAggregation MultiTerms { get; set; } + void Accept(IAggregationVisitor visitor); } @@ -442,6 +445,8 @@ public class AggregationContainer : IAggregationContainer public ITopMetricsAggregation TopMetrics { get; set; } + public IMultiTermsAggregation MultiTerms { get; set; } + public void Accept(IAggregationVisitor visitor) { if (visitor.Scope == AggregationVisitorScope.Unknown) visitor.Scope = AggregationVisitorScope.Aggregation; @@ -555,6 +560,8 @@ public class AggregationContainerDescriptor : DescriptorBase, IMissingAggregation> selector ) => _SetInnerAggregation(name, selector, (a, d) => a.Missing = d); + public AggregationContainerDescriptor MultiTerms(string name, + Func, IMultiTermsAggregation> selector + ) => + _SetInnerAggregation(name, selector, (a, d) => a.MultiTerms = d); + public AggregationContainerDescriptor Nested(string name, Func, INestedAggregation> selector ) => diff --git a/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregate.cs b/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregate.cs new file mode 100644 index 00000000000..72e6eaba07f --- /dev/null +++ b/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregate.cs @@ -0,0 +1,12 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +namespace Nest +{ + public class MultiTermsAggregate : MultiBucketAggregate> + { + public long? DocCountErrorUpperBound { get; set; } + public long? SumOtherDocCount { get; set; } + } +} diff --git a/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregation.cs b/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregation.cs new file mode 100644 index 00000000000..33728d5dc9a --- /dev/null +++ b/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregation.cs @@ -0,0 +1,111 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using Elasticsearch.Net.Utf8Json; + +namespace Nest +{ + [InterfaceDataContract] + [ReadAs(typeof(MultiTermsAggregation))] + public interface IMultiTermsAggregation : IBucketAggregation + { + [DataMember(Name ="collect_mode")] + TermsAggregationCollectMode? CollectMode { get; set; } + + [DataMember(Name ="min_doc_count")] + int? MinimumDocumentCount { get; set; } + + [DataMember(Name ="order")] + IList Order { get; set; } + + [DataMember(Name = "script")] + IScript Script { get; set; } + + [DataMember(Name = "shard_min_doc_count")] + int? ShardMinimumDocumentCount { get; set; } + + [DataMember(Name ="shard_size")] + int? ShardSize { get; set; } + + [DataMember(Name ="show_term_doc_count_error")] + bool? ShowTermDocCountError { get; set; } + + [DataMember(Name ="size")] + int? Size { get; set; } + + [DataMember(Name = "terms")] + IEnumerable Terms { get; set; } + } + + public class MultiTermsAggregation : BucketAggregationBase, IMultiTermsAggregation + { + internal MultiTermsAggregation() { } + + public MultiTermsAggregation(string name) : base(name) { } + + public TermsAggregationCollectMode? CollectMode { get; set; } + public int? MinimumDocumentCount { get; set; } + public IList Order { get; set; } + public IScript Script { get; set; } + public int? ShardMinimumDocumentCount { get; set; } + public int? ShardSize { get; set; } + public bool? ShowTermDocCountError { get; set; } + public int? Size { get; set; } + public IEnumerable Terms { get; set; } + + internal override void WrapInContainer(AggregationContainer c) => c.MultiTerms = this; + } + + public class MultiTermsAggregationDescriptor + : BucketAggregationDescriptorBase, IMultiTermsAggregation, T>, IMultiTermsAggregation + where T : class + { + TermsAggregationCollectMode? IMultiTermsAggregation.CollectMode { get; set; } + int? IMultiTermsAggregation.MinimumDocumentCount { get; set; } + IList IMultiTermsAggregation.Order { get; set; } + IScript IMultiTermsAggregation.Script { get; set; } + int? IMultiTermsAggregation.ShardMinimumDocumentCount { get; set; } + int? IMultiTermsAggregation.ShardSize { get; set; } + bool? IMultiTermsAggregation.ShowTermDocCountError { get; set; } + int? IMultiTermsAggregation.Size { get; set; } + IEnumerable IMultiTermsAggregation.Terms { get; set; } + + public MultiTermsAggregationDescriptor CollectMode(TermsAggregationCollectMode? collectMode) => + Assign(collectMode, (a, v) => a.CollectMode = v); + + public MultiTermsAggregationDescriptor MinimumDocumentCount(int? minimumDocumentCount) => + Assign(minimumDocumentCount, (a, v) => a.MinimumDocumentCount = v); + + public MultiTermsAggregationDescriptor Order(Func, IPromise>> selector) => + Assign(selector, (a, v) => a.Order = v?.Invoke(new TermsOrderDescriptor())?.Value); + + public MultiTermsAggregationDescriptor Script(string script) => Assign((InlineScript)script, (a, v) => a.Script = v); + + public MultiTermsAggregationDescriptor Script(Func scriptSelector) => + Assign(scriptSelector, (a, v) => a.Script = v?.Invoke(new ScriptDescriptor())); + + public MultiTermsAggregationDescriptor ShardMinimumDocumentCount(int? shardMinimumDocumentCount) => + Assign(shardMinimumDocumentCount, (a, v) => a.ShardMinimumDocumentCount = v); + + public MultiTermsAggregationDescriptor ShardSize(int? shardSize) => Assign(shardSize, (a, v) => a.ShardSize = v); + + public MultiTermsAggregationDescriptor ShowTermDocCountError(bool? showTermDocCountError = true) => + Assign(showTermDocCountError, (a, v) => a.ShowTermDocCountError = v); + + public MultiTermsAggregationDescriptor Size(int? size) => Assign(size, (a, v) => a.Size = v); + + public MultiTermsAggregationDescriptor Terms(params ITerm[] ranges) => + Assign(ranges.ToListOrNullIfEmpty(), (a, v) => a.Terms = v); + + public MultiTermsAggregationDescriptor Terms(params Func, ITerm>[] ranges) => + Assign(ranges?.Select(r => r(new TermDescriptor())).ToListOrNullIfEmpty(), (a, v) => a.Terms = v); + + public MultiTermsAggregationDescriptor Terms(IEnumerable, ITerm>> ranges) => + Assign(ranges?.Select(r => r(new TermDescriptor())).ToListOrNullIfEmpty(), (a, v) => a.Terms = v); + } +} diff --git a/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsBucket.cs b/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsBucket.cs new file mode 100644 index 00000000000..e261647a41b --- /dev/null +++ b/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsBucket.cs @@ -0,0 +1,18 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Collections.Generic; + +namespace Nest +{ + public class MultiTermsBucket : BucketBase + { + public MultiTermsBucket(IReadOnlyDictionary dict) : base(dict) { } + + public long? DocCount { get; set; } + public long? DocCountErrorUpperBound { get; set; } + public IEnumerable Key { get; set; } + public string KeyAsString { get; set; } + } +} diff --git a/src/Nest/Aggregations/Bucket/MultiTerms/Term.cs b/src/Nest/Aggregations/Bucket/MultiTerms/Term.cs new file mode 100644 index 00000000000..4d22e826077 --- /dev/null +++ b/src/Nest/Aggregations/Bucket/MultiTerms/Term.cs @@ -0,0 +1,35 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Linq.Expressions; +using System.Runtime.Serialization; + +namespace Nest +{ + [ReadAs(typeof(Term))] + public interface ITerm + { + Field Field { get; set; } + object Missing { get; set; } + } + + public class Term : ITerm + { + [DataMember(Name = "field")] + public Field Field { get; set; } + [DataMember(Name = "missing")] + public object Missing { get; set; } + } + + public class TermDescriptor : DescriptorBase, ITerm>, ITerm where T : class + { + Field ITerm.Field { get; set; } + object ITerm.Missing { get; set; } + + public TermDescriptor Field(Field field) => Assign(field, (a, v) => a.Field = v); + public TermDescriptor Field(Expression> field) => Assign(field, (a, v) => a.Field = v); + public TermDescriptor Missing(object missing) => Assign(missing, (a, v) => a.Missing = v); + } +} diff --git a/src/Nest/Aggregations/Bucket/Terms/TermsInclude.cs b/src/Nest/Aggregations/Bucket/Terms/TermsInclude.cs index 0e0b995c753..1552d0647c4 100644 --- a/src/Nest/Aggregations/Bucket/Terms/TermsInclude.cs +++ b/src/Nest/Aggregations/Bucket/Terms/TermsInclude.cs @@ -41,7 +41,7 @@ public TermsInclude(long partition, long numberOfPartitions) } /// - /// The total number of paritions we are interested in + /// The total number of partitions we are interested in /// [DataMember(Name ="num_partitions")] public long? NumberOfPartitions { get; set; } diff --git a/src/Nest/Aggregations/Visitor/AggregationVisitor.cs b/src/Nest/Aggregations/Visitor/AggregationVisitor.cs index 344cbdf769f..bd833fda9da 100644 --- a/src/Nest/Aggregations/Visitor/AggregationVisitor.cs +++ b/src/Nest/Aggregations/Visitor/AggregationVisitor.cs @@ -159,6 +159,8 @@ public interface IAggregationVisitor void Visit(ITopMetricsAggregation aggregation); void Visit(ITTestAggregation aggregation); + + void Visit(IMultiTermsAggregation aggregation); } public class AggregationVisitor : IAggregationVisitor @@ -299,8 +301,11 @@ public virtual void Visit(ITopMetricsAggregation aggregation) { } public virtual void Visit(ITTestAggregation aggregation) { } + public virtual void Visit(IMultiTermsAggregation aggregationContainer) { } + public virtual void Visit(IAggregation aggregation) { } public virtual void Visit(IAggregationContainer aggregationContainer) { } + } } diff --git a/src/Nest/Aggregations/Visitor/AggregationWalker.cs b/src/Nest/Aggregations/Visitor/AggregationWalker.cs index 85c087760d1..f51c2a79b9e 100644 --- a/src/Nest/Aggregations/Visitor/AggregationWalker.cs +++ b/src/Nest/Aggregations/Visitor/AggregationWalker.cs @@ -119,6 +119,11 @@ public void Walk(IAggregationContainer aggregation, IAggregationVisitor visitor) }); AcceptAggregation(aggregation.MovingAverage, visitor, (v, d) => v.Visit(d)); AcceptAggregation(aggregation.MovingPercentiles, visitor, (v, d) => v.Visit(d)); + AcceptAggregation(aggregation.MultiTerms, visitor, (v, d) => + { + v.Visit(d); + Accept(v, d.Aggregations); + }); AcceptAggregation(aggregation.Nested, visitor, (v, d) => { v.Visit(d); diff --git a/tests/Tests/Aggregations/Bucket/MultiTerms/MultiTermsAggregationUsageTests.cs b/tests/Tests/Aggregations/Bucket/MultiTerms/MultiTermsAggregationUsageTests.cs new file mode 100644 index 00000000000..6b5203cb961 --- /dev/null +++ b/tests/Tests/Aggregations/Bucket/MultiTerms/MultiTermsAggregationUsageTests.cs @@ -0,0 +1,120 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Collections.Generic; +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using FluentAssertions; +using Nest; +using Tests.Core.Extensions; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Domain; +using Tests.Framework.EndpointTests.TestState; +using static Nest.Infer; + +namespace Tests.Aggregations.Bucket.MultiTerms +{ + /** + * A multi-bucket value source based aggregation where buckets are dynamically built - one per unique set of values. + * + * See the Elasticsearch documentation on {ref_current}//search-aggregations-bucket-multi-terms-aggregation.html[multi terms aggregation] for more detail. + */ + [SkipVersion("<7.12.0", "Multi terms aggregation added in 7.12.0")] + public class MultiTermsAggregationUsageTests : AggregationUsageTestBase + { + public MultiTermsAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } + + protected override object AggregationJson => new + { + states = new + { + meta = new + { + foo = "bar" + }, + multi_terms = new + { + collect_mode = "breadth_first", + terms = new object[] + { + new { field = "name" }, + new { field = "numberOfCommits", missing = 0 } + }, + min_doc_count = 1, + shard_min_doc_count = 1, + size = 5, + shard_size = 100, + show_term_doc_count_error = true, + order = new object[] + { + new { _key = "asc" }, + new { _count = "desc" } + } + } + } + }; + + protected override Func, IAggregationContainer> FluentAggs => a => a + .MultiTerms("states", st => st + .CollectMode(TermsAggregationCollectMode.BreadthFirst) + .Terms(t => t.Field(f => f.Name), t => t.Field(f => f.NumberOfCommits).Missing(0)) + .MinimumDocumentCount(1) + .Size(5) + .ShardSize(100) + .ShardMinimumDocumentCount(1) + .ShowTermDocCountError(true) + .Order(o => o + .KeyAscending() + .CountDescending() + ) + .Meta(m => m + .Add("foo", "bar") + ) + ); + + protected override AggregationDictionary InitializerAggs => + new MultiTermsAggregation("states") + { + CollectMode = TermsAggregationCollectMode.BreadthFirst, + Terms = new List + { + new() {Field = Field(f => f.Name) }, + new() {Field = Field(f => f.NumberOfCommits), Missing = 0 } + }, + MinimumDocumentCount = 1, + Size = 5, + ShardSize = 100, + ShardMinimumDocumentCount = 1, + ShowTermDocCountError = true, + Order = new List + { + TermsOrder.KeyAscending, + TermsOrder.CountDescending + }, + Meta = new Dictionary + { + { "foo", "bar" } + } + }; + + protected override void ExpectResponse(ISearchResponse response) + { + response.ShouldBeValid(); + var states = response.Aggregations.MultiTerms("states"); + states.Should().NotBeNull(); + states.DocCountErrorUpperBound.Should().HaveValue(); + states.SumOtherDocCount.Should().HaveValue(); + states.Buckets.Should().NotBeNull(); + states.Buckets.Count.Should().BeGreaterThan(0); + foreach (var item in states.Buckets) + { + item.Key.Should().NotBeNullOrEmpty(); + item.DocCount.Should().BeGreaterOrEqualTo(1); + item.KeyAsString.Should().NotBeNullOrEmpty(); + } + states.Meta.Should().NotBeNull().And.HaveCount(1); + states.Meta["foo"].Should().Be("bar"); + } + } +} From 3ed624e12bdf94797ac11c0f55a0e9dd88a99eac Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Thu, 25 Mar 2021 07:34:35 +0000 Subject: [PATCH 2/2] Fixup namespaces --- .../Aggregations/Bucket/MultiTerms/MultiTermsAggregation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregation.cs b/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregation.cs index 33728d5dc9a..f57bdb3934d 100644 --- a/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregation.cs +++ b/src/Nest/Aggregations/Bucket/MultiTerms/MultiTermsAggregation.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; -using Elasticsearch.Net.Utf8Json; +using Nest.Utf8Json; namespace Nest {