Skip to content

Commit f7712ff

Browse files
Mpdreamzrusscam
authored andcommitted
add support for missing_bucket on Composite Aggregation (#3420)
per elastic/elasticsearch#29465
1 parent d48dc4e commit f7712ff

File tree

3 files changed

+140
-6
lines changed

3 files changed

+140
-6
lines changed

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

+14
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 />
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.MissingBucket"/>
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

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

+125-5
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public CompositeAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) :
155155
};
156156

157157
/**==== Handling Responses
158-
* Each Composite aggregation bucket key is an `CompositeKey`, a specialized
158+
* Each Composite aggregation bucket key is a `CompositeKey` type, a specialized
159159
* `IReadOnlyDictionary<string, object>` type with methods to convert values to supported types
160160
*/
161161
protected override void ExpectResponse(ISearchResponse<Project> response)
@@ -196,6 +196,130 @@ protected override void ExpectResponse(ISearchResponse<Project> response)
196196
}
197197
}
198198

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

200324
//hide
201325
[SkipVersion("<6.3.0", "Date histogram source only supports format starting from Elasticsearch 6.3.0+")]
@@ -286,10 +410,6 @@ public DateFormatCompositeAggregationUsageTests(ReadOnlyCluster i, EndpointUsage
286410
}
287411
};
288412

289-
/**==== Handling Responses
290-
* Each Composite aggregation bucket key is an `CompositeKey`, a specialized
291-
* `IReadOnlyDictionary<string, object>` type with methods to convert values to supported types
292-
*/
293413
protected override void ExpectResponse(ISearchResponse<Project> response)
294414
{
295415
response.ShouldBeValid();

0 commit comments

Comments
 (0)