Skip to content

Commit 801adf4

Browse files
committed
add support for missing_bucket on composite aggregation as per elastic/elasticsearch#29465
1 parent f9637a8 commit 801adf4

File tree

3 files changed

+131
-3
lines changed

3 files changed

+131
-3
lines changed

src/Nest/Aggregations/Bucket/Composite/CompositeAggregationSource.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ public interface ICompositeAggregationSource
3737
/// </summary>
3838
[JsonProperty("order")]
3939
SortOrder? Order { get; set; }
40+
41+
/// <summary>
42+
/// By default documents without a value for a given source are ignored. It is possible to include
43+
/// them in the response as null by setting this to true
44+
/// </summary>
45+
[JsonProperty("missing_bucket")]
46+
bool? MissingBucket { get; set; }
4047
}
4148

4249
/// <inheritdoc />
@@ -59,6 +66,9 @@ protected CompositeAggregationSourceBase(string name) =>
5966

6067
/// <inheritdoc />
6168
public SortOrder? Order { get; set; }
69+
70+
/// <inheritdoc cref="ICompositeAggregationSource.MissingBucket" />
71+
public bool? MissingBucket { get; set; }
6272
}
6373

6474
/// <inheritdoc cref="ICompositeAggregationSource"/>
@@ -93,6 +103,7 @@ public abstract class CompositeAggregationSourceDescriptorBase<TDescriptor, TInt
93103
string ICompositeAggregationSource.SourceType => _sourceType;
94104
Field ICompositeAggregationSource.Field { get; set; }
95105
SortOrder? ICompositeAggregationSource.Order { get; set; }
106+
bool? ICompositeAggregationSource.MissingBucket { get; set; }
96107

97108
protected CompositeAggregationSourceDescriptorBase(string name, string sourceType)
98109
{
@@ -108,6 +119,9 @@ protected CompositeAggregationSourceDescriptorBase(string name, string sourceTyp
108119

109120
/// <inheritdoc cref="ICompositeAggregationSource.Order"/>
110121
public TDescriptor Order(SortOrder? order) => Assign(a => a.Order = order);
122+
123+
/// <inheritdoc cref="ICompositeAggregationSource.Order"/>
124+
public TDescriptor MissingBucket(bool? includeMissing = true) => Assign(a => a.MissingBucket = includeMissing);
111125
}
112126

113127
internal class CompositeAggregationSourceConverter : ReserializeJsonConverter<CompositeAggregationSourceBase, ICompositeAggregationSource>

src/Tests/Tests.Domain/Project.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public class Project
5757
.RuleFor(p => p.NumberOfCommits, f => Gimme.Random.Number(1, 1000))
5858
.RuleFor(p => p.NumberOfContributors, f => Gimme.Random.Number(1, 200))
5959
.RuleFor(p => p.Ranges, f => Ranges.Generator.Generate())
60-
.RuleFor(p => p.Branches, f => Gimme.Random.ListItems(new List<string> { "master", "dev", "release", "qa", "test" }, 2))
60+
.RuleFor(p => p.Branches, f => Gimme.Random.ListItems(new List<string> { "master", "dev", "release", "qa", "test" }))
6161
.RuleFor(p => p.SourceOnly, f =>
6262
TestConfiguration.Instance.Random.SourceSerializer ? new SourceOnlyObject() : null
6363
)

src/Tests/Tests/Aggregations/Bucket/Composite/CompositeAggregationUsageTests.cs

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ namespace Tests.Aggregations.Bucket.Composite
3232
*
3333
* Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-composite-aggregation.html[Composite Aggregation].
3434
*/
35-
[SkipVersion("<6.1.0", "Composite Aggregation is only available in Elasticsearch 6.1.0+")]
35+
//[SkipVersion("<6.1.0", "Composite Aggregation is only available in Elasticsearch 6.1.0+")]
3636
public class CompositeAggregationUsageTests : ProjectsOnlyAggregationUsageTestBase
3737
{
3838
public CompositeAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }
@@ -196,9 +196,123 @@ protected override void ExpectResponse(ISearchResponse<Project> response)
196196
}
197197
}
198198

199+
public class CompositeAggregationMissingBucketUsageTests : ProjectsOnlyAggregationUsageTestBase
200+
{
201+
public CompositeAggregationMissingBucketUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }
202+
203+
protected override object AggregationJson => new
204+
{
205+
my_buckets = new
206+
{
207+
composite = new
208+
{
209+
sources = new object[]
210+
{
211+
new
212+
{
213+
branches = new
214+
{
215+
terms = new
216+
{
217+
field = "branches.keyword",
218+
order = "asc",
219+
missing_bucket = true
220+
}
221+
}
222+
},
223+
}
224+
},
225+
aggs = new
226+
{
227+
project_tags = new
228+
{
229+
nested = new { path = "tags" },
230+
aggs = new
231+
{
232+
tags = new { terms = new {field = "tags.name"} }
233+
}
234+
}
235+
}
236+
}
237+
};
238+
239+
protected override Func<AggregationContainerDescriptor<Project>, IAggregationContainer> FluentAggs => a => a
240+
.Composite("my_buckets", date => date
241+
.Sources(s => s
242+
.Terms("branches", t => t
243+
.Field(f => f.Branches.Suffix("keyword"))
244+
.MissingBucket()
245+
.Order(SortOrder.Ascending)
246+
)
247+
)
248+
.Aggregations(childAggs => childAggs
249+
.Nested("project_tags", n => n
250+
.Path(p => p.Tags)
251+
.Aggregations(nestedAggs => nestedAggs
252+
.Terms("tags", avg => avg.Field(p => p.Tags.First().Name))
253+
)
254+
)
255+
)
256+
);
257+
258+
protected override AggregationDictionary InitializerAggs =>
259+
new CompositeAggregation("my_buckets")
260+
{
261+
Sources = new List<ICompositeAggregationSource>
262+
{
263+
new TermsCompositeAggregationSource("branches")
264+
{
265+
Field = Infer.Field<Project>(f => f.Branches.Suffix("keyword")),
266+
MissingBucket = true,
267+
Order = SortOrder.Ascending
268+
}
269+
},
270+
Aggregations = new NestedAggregation("project_tags")
271+
{
272+
Path = Field<Project>(p => p.Tags),
273+
Aggregations = new TermsAggregation("tags")
274+
{
275+
Field = Field<Project>(p => p.Tags.First().Name)
276+
}
277+
}
278+
};
279+
280+
/**==== Handling Responses
281+
* Each Composite aggregation bucket key is an `CompositeKey`, a specialized
282+
* `IReadOnlyDictionary<string, object>` type with methods to convert values to supported types
283+
*/
284+
protected override void ExpectResponse(ISearchResponse<Project> response)
285+
{
286+
response.ShouldBeValid();
287+
288+
var composite = response.Aggregations.Composite("my_buckets");
289+
composite.Should().NotBeNull();
290+
composite.Buckets.Should().NotBeNullOrEmpty();
291+
composite.AfterKey.Should().NotBeNull();
292+
if (TestConfiguration.Instance.InRange(">=6.3.0"))
293+
composite.AfterKey.Should().HaveCount(1).And.ContainKeys("branches");
294+
var i = 0;
295+
foreach (var item in composite.Buckets)
296+
{
297+
var key = item.Key;
298+
key.Should().NotBeNull();
299+
300+
key.TryGetValue("branches", out string branches).Should().BeTrue("expected to find 'branches' in composite bucket");
301+
if (i == 0) branches.Should().BeNull("First key should be null as we expect to have some projects with no branches");
302+
else branches.Should().NotBeNullOrEmpty();
303+
304+
var nested = item.Nested("project_tags");
305+
nested.Should().NotBeNull();
306+
307+
var nestedTerms = nested.Terms("tags");
308+
nestedTerms.Buckets.Count.Should().BeGreaterThan(0);
309+
i++;
310+
}
311+
}
312+
}
199313

200314
//hide
201-
[SkipVersion("<6.3.0", "Date histogram source only supports format starting from Elasticsearch 6.3.0+")]
315+
//[SkipVersion("<6.3.0", "Date histogram source only supports format starting from Elasticsearch 6.3.0+")]
202316
public class DateFormatCompositeAggregationUsageTests : ProjectsOnlyAggregationUsageTestBase
203317
{
204318
public DateFormatCompositeAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }

0 commit comments

Comments
 (0)