Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 202bf4d

Browse files
stevejgordongithub-actions[bot]
authored andcommittedMar 9, 2023
Support bucket sort pipeline aggregation (#7320)
1 parent 9703593 commit 202bf4d

File tree

6 files changed

+602
-4
lines changed

6 files changed

+602
-4
lines changed
 

‎src/Elastic.Clients.Elasticsearch/Types/Aggregations/AggregateDictionaryConverter.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,6 @@ public static void ReadAggregate(ref Utf8JsonReader reader, JsonSerializerOption
399399
throw new Exception("The aggregate in response is not yet supported.");
400400
case "bucket_selector":
401401
throw new Exception("The aggregate in response is not yet supported.");
402-
case "bucket_sort":
403-
throw new Exception("The aggregate in response is not yet supported.");
404402

405403
case "cumulative_cardinality":
406404
{

‎src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/Aggregation.g.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ public override Aggregation Read(ref Utf8JsonReader reader, Type typeToConvert,
6464
return AggregationSerializationHelper.ReadContainer<Elastic.Clients.Elasticsearch.Aggregations.BoxplotAggregation?>("boxplot", ref reader, options);
6565
}
6666

67+
if (propertyName == "bucket_sort")
68+
{
69+
return AggregationSerializationHelper.ReadContainer<Elastic.Clients.Elasticsearch.Aggregations.BucketSortAggregation?>("bucket_sort", ref reader, options);
70+
}
71+
6772
if (propertyName == "cardinality")
6873
{
6974
return AggregationSerializationHelper.ReadContainer<Elastic.Clients.Elasticsearch.Aggregations.CardinalityAggregation?>("cardinality", ref reader, options);
@@ -316,6 +321,11 @@ public AggregationDescriptor<TDocument> Boxplot(string name, Action<BoxplotAggre
316321
return SetContainer(name, Aggregation.CreateWithAction("boxplot", configure));
317322
}
318323

324+
public AggregationDescriptor<TDocument> BucketSort(string name, Action<BucketSortAggregationDescriptor<TDocument>> configure)
325+
{
326+
return SetContainer(name, Aggregation.CreateWithAction("bucket_sort", configure));
327+
}
328+
319329
public AggregationDescriptor<TDocument> Cardinality(string name, Action<CardinalityAggregationDescriptor<TDocument>> configure)
320330
{
321331
return SetContainer(name, Aggregation.CreateWithAction("cardinality", configure));
@@ -564,6 +574,16 @@ public AggregationDescriptor Boxplot<TDocument>(string name, Action<BoxplotAggre
564574
return SetContainer(name, Aggregation.CreateWithAction("boxplot", configure));
565575
}
566576

577+
public AggregationDescriptor BucketSort(string name, Action<BucketSortAggregationDescriptor> configure)
578+
{
579+
return SetContainer(name, Aggregation.CreateWithAction("bucket_sort", configure));
580+
}
581+
582+
public AggregationDescriptor BucketSort<TDocument>(string name, Action<BucketSortAggregationDescriptor<TDocument>> configure)
583+
{
584+
return SetContainer(name, Aggregation.CreateWithAction("bucket_sort", configure));
585+
}
586+
567587
public AggregationDescriptor Cardinality(string name, Action<CardinalityAggregationDescriptor> configure)
568588
{
569589
return SetContainer(name, Aggregation.CreateWithAction("cardinality", configure));
Lines changed: 464 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,464 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
//
5+
// ███╗ ██╗ ██████╗ ████████╗██╗ ██████╗███████╗
6+
// ████╗ ██║██╔═══██╗╚══██╔══╝██║██╔════╝██╔════╝
7+
// ██╔██╗ ██║██║ ██║ ██║ ██║██║ █████╗
8+
// ██║╚██╗██║██║ ██║ ██║ ██║██║ ██╔══╝
9+
// ██║ ╚████║╚██████╔╝ ██║ ██║╚██████╗███████╗
10+
// ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝
11+
// ------------------------------------------------
12+
//
13+
// This file is automatically generated.
14+
// Please do not edit these files manually.
15+
//
16+
// ------------------------------------------------
17+
18+
using Elastic.Clients.Elasticsearch.Fluent;
19+
using Elastic.Clients.Elasticsearch.Serialization;
20+
using System;
21+
using System.Collections.Generic;
22+
using System.Linq.Expressions;
23+
using System.Text.Json;
24+
using System.Text.Json.Serialization;
25+
26+
#nullable restore
27+
namespace Elastic.Clients.Elasticsearch.Aggregations;
28+
internal sealed class BucketSortAggregationConverter : JsonConverter<BucketSortAggregation>
29+
{
30+
public override BucketSortAggregation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
31+
{
32+
if (reader.TokenType != JsonTokenType.StartObject)
33+
throw new JsonException("Unexpected JSON detected.");
34+
reader.Read();
35+
var aggName = reader.GetString();
36+
if (aggName != "bucket_sort")
37+
throw new JsonException("Unexpected JSON detected.");
38+
var agg = new BucketSortAggregation(aggName);
39+
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
40+
{
41+
if (reader.TokenType == JsonTokenType.PropertyName)
42+
{
43+
if (reader.ValueTextEquals("from"))
44+
{
45+
reader.Read();
46+
var value = JsonSerializer.Deserialize<int?>(ref reader, options);
47+
if (value is not null)
48+
{
49+
agg.From = value;
50+
}
51+
52+
continue;
53+
}
54+
55+
if (reader.ValueTextEquals("gap_policy"))
56+
{
57+
reader.Read();
58+
var value = JsonSerializer.Deserialize<Elastic.Clients.Elasticsearch.Aggregations.GapPolicy?>(ref reader, options);
59+
if (value is not null)
60+
{
61+
agg.GapPolicy = value;
62+
}
63+
64+
continue;
65+
}
66+
67+
if (reader.ValueTextEquals("size"))
68+
{
69+
reader.Read();
70+
var value = JsonSerializer.Deserialize<int?>(ref reader, options);
71+
if (value is not null)
72+
{
73+
agg.Size = value;
74+
}
75+
76+
continue;
77+
}
78+
79+
if (reader.ValueTextEquals("sort"))
80+
{
81+
reader.Read();
82+
var value = SingleOrManySerializationHelper.Deserialize<Elastic.Clients.Elasticsearch.SortOptions>(ref reader, options);
83+
if (value is not null)
84+
{
85+
agg.Sort = value;
86+
}
87+
88+
continue;
89+
}
90+
}
91+
}
92+
93+
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
94+
{
95+
if (reader.TokenType == JsonTokenType.PropertyName)
96+
{
97+
if (reader.ValueTextEquals("meta"))
98+
{
99+
var value = JsonSerializer.Deserialize<Dictionary<string, object>>(ref reader, options);
100+
if (value is not null)
101+
{
102+
agg.Meta = value;
103+
}
104+
105+
continue;
106+
}
107+
}
108+
}
109+
110+
return agg;
111+
}
112+
113+
public override void Write(Utf8JsonWriter writer, BucketSortAggregation value, JsonSerializerOptions options)
114+
{
115+
writer.WriteStartObject();
116+
writer.WritePropertyName("bucket_sort");
117+
writer.WriteStartObject();
118+
if (value.From.HasValue)
119+
{
120+
writer.WritePropertyName("from");
121+
writer.WriteNumberValue(value.From.Value);
122+
}
123+
124+
if (value.GapPolicy is not null)
125+
{
126+
writer.WritePropertyName("gap_policy");
127+
JsonSerializer.Serialize(writer, value.GapPolicy, options);
128+
}
129+
130+
if (value.Size.HasValue)
131+
{
132+
writer.WritePropertyName("size");
133+
writer.WriteNumberValue(value.Size.Value);
134+
}
135+
136+
if (value.Sort is not null)
137+
{
138+
writer.WritePropertyName("sort");
139+
SingleOrManySerializationHelper.Serialize<Elastic.Clients.Elasticsearch.SortOptions>(value.Sort, writer, options);
140+
}
141+
142+
writer.WriteEndObject();
143+
if (value.Meta is not null)
144+
{
145+
writer.WritePropertyName("meta");
146+
JsonSerializer.Serialize(writer, value.Meta, options);
147+
}
148+
149+
writer.WriteEndObject();
150+
}
151+
}
152+
153+
[JsonConverter(typeof(BucketSortAggregationConverter))]
154+
public sealed partial class BucketSortAggregation : SearchAggregation
155+
{
156+
public BucketSortAggregation(string name) => Name = name;
157+
internal BucketSortAggregation()
158+
{
159+
}
160+
161+
public int? From { get; set; }
162+
163+
public Elastic.Clients.Elasticsearch.Aggregations.GapPolicy? GapPolicy { get; set; }
164+
165+
public IDictionary<string, object>? Meta { get; set; }
166+
167+
public override string? Name { get; internal set; }
168+
169+
public int? Size { get; set; }
170+
171+
public ICollection<Elastic.Clients.Elasticsearch.SortOptions>? Sort { get; set; }
172+
}
173+
174+
public sealed partial class BucketSortAggregationDescriptor<TDocument> : SerializableDescriptor<BucketSortAggregationDescriptor<TDocument>>
175+
{
176+
internal BucketSortAggregationDescriptor(Action<BucketSortAggregationDescriptor<TDocument>> configure) => configure.Invoke(this);
177+
public BucketSortAggregationDescriptor() : base()
178+
{
179+
}
180+
181+
private ICollection<Elastic.Clients.Elasticsearch.SortOptions>? SortValue { get; set; }
182+
183+
private SortOptionsDescriptor<TDocument> SortDescriptor { get; set; }
184+
185+
private Action<SortOptionsDescriptor<TDocument>> SortDescriptorAction { get; set; }
186+
187+
private Action<SortOptionsDescriptor<TDocument>>[] SortDescriptorActions { get; set; }
188+
189+
private int? FromValue { get; set; }
190+
191+
private Elastic.Clients.Elasticsearch.Aggregations.GapPolicy? GapPolicyValue { get; set; }
192+
193+
private IDictionary<string, object>? MetaValue { get; set; }
194+
195+
private int? SizeValue { get; set; }
196+
197+
public BucketSortAggregationDescriptor<TDocument> Sort(ICollection<Elastic.Clients.Elasticsearch.SortOptions>? sort)
198+
{
199+
SortDescriptor = null;
200+
SortDescriptorAction = null;
201+
SortDescriptorActions = null;
202+
SortValue = sort;
203+
return Self;
204+
}
205+
206+
public BucketSortAggregationDescriptor<TDocument> Sort(SortOptionsDescriptor<TDocument> descriptor)
207+
{
208+
SortValue = null;
209+
SortDescriptorAction = null;
210+
SortDescriptorActions = null;
211+
SortDescriptor = descriptor;
212+
return Self;
213+
}
214+
215+
public BucketSortAggregationDescriptor<TDocument> Sort(Action<SortOptionsDescriptor<TDocument>> configure)
216+
{
217+
SortValue = null;
218+
SortDescriptor = null;
219+
SortDescriptorActions = null;
220+
SortDescriptorAction = configure;
221+
return Self;
222+
}
223+
224+
public BucketSortAggregationDescriptor<TDocument> Sort(params Action<SortOptionsDescriptor<TDocument>>[] configure)
225+
{
226+
SortValue = null;
227+
SortDescriptor = null;
228+
SortDescriptorAction = null;
229+
SortDescriptorActions = configure;
230+
return Self;
231+
}
232+
233+
public BucketSortAggregationDescriptor<TDocument> From(int? from)
234+
{
235+
FromValue = from;
236+
return Self;
237+
}
238+
239+
public BucketSortAggregationDescriptor<TDocument> GapPolicy(Elastic.Clients.Elasticsearch.Aggregations.GapPolicy? gapPolicy)
240+
{
241+
GapPolicyValue = gapPolicy;
242+
return Self;
243+
}
244+
245+
public BucketSortAggregationDescriptor<TDocument> Meta(Func<FluentDictionary<string, object>, FluentDictionary<string, object>> selector)
246+
{
247+
MetaValue = selector?.Invoke(new FluentDictionary<string, object>());
248+
return Self;
249+
}
250+
251+
public BucketSortAggregationDescriptor<TDocument> Size(int? size)
252+
{
253+
SizeValue = size;
254+
return Self;
255+
}
256+
257+
protected override void Serialize(Utf8JsonWriter writer, JsonSerializerOptions options, IElasticsearchClientSettings settings)
258+
{
259+
writer.WriteStartObject();
260+
writer.WritePropertyName("bucket_sort");
261+
writer.WriteStartObject();
262+
if (SortDescriptor is not null)
263+
{
264+
writer.WritePropertyName("sort");
265+
JsonSerializer.Serialize(writer, SortDescriptor, options);
266+
}
267+
else if (SortDescriptorAction is not null)
268+
{
269+
writer.WritePropertyName("sort");
270+
JsonSerializer.Serialize(writer, new SortOptionsDescriptor<TDocument>(SortDescriptorAction), options);
271+
}
272+
else if (SortDescriptorActions is not null)
273+
{
274+
writer.WritePropertyName("sort");
275+
if (SortDescriptorActions.Length > 1)
276+
writer.WriteStartArray();
277+
foreach (var action in SortDescriptorActions)
278+
{
279+
JsonSerializer.Serialize(writer, new SortOptionsDescriptor<TDocument>(action), options);
280+
}
281+
282+
if (SortDescriptorActions.Length > 1)
283+
writer.WriteEndArray();
284+
}
285+
else if (SortValue is not null)
286+
{
287+
writer.WritePropertyName("sort");
288+
SingleOrManySerializationHelper.Serialize<Elastic.Clients.Elasticsearch.SortOptions>(SortValue, writer, options);
289+
}
290+
291+
if (FromValue.HasValue)
292+
{
293+
writer.WritePropertyName("from");
294+
writer.WriteNumberValue(FromValue.Value);
295+
}
296+
297+
if (GapPolicyValue is not null)
298+
{
299+
writer.WritePropertyName("gap_policy");
300+
JsonSerializer.Serialize(writer, GapPolicyValue, options);
301+
}
302+
303+
if (SizeValue.HasValue)
304+
{
305+
writer.WritePropertyName("size");
306+
writer.WriteNumberValue(SizeValue.Value);
307+
}
308+
309+
writer.WriteEndObject();
310+
if (MetaValue is not null)
311+
{
312+
writer.WritePropertyName("meta");
313+
JsonSerializer.Serialize(writer, MetaValue, options);
314+
}
315+
316+
writer.WriteEndObject();
317+
}
318+
}
319+
320+
public sealed partial class BucketSortAggregationDescriptor : SerializableDescriptor<BucketSortAggregationDescriptor>
321+
{
322+
internal BucketSortAggregationDescriptor(Action<BucketSortAggregationDescriptor> configure) => configure.Invoke(this);
323+
public BucketSortAggregationDescriptor() : base()
324+
{
325+
}
326+
327+
private ICollection<Elastic.Clients.Elasticsearch.SortOptions>? SortValue { get; set; }
328+
329+
private SortOptionsDescriptor SortDescriptor { get; set; }
330+
331+
private Action<SortOptionsDescriptor> SortDescriptorAction { get; set; }
332+
333+
private Action<SortOptionsDescriptor>[] SortDescriptorActions { get; set; }
334+
335+
private int? FromValue { get; set; }
336+
337+
private Elastic.Clients.Elasticsearch.Aggregations.GapPolicy? GapPolicyValue { get; set; }
338+
339+
private IDictionary<string, object>? MetaValue { get; set; }
340+
341+
private int? SizeValue { get; set; }
342+
343+
public BucketSortAggregationDescriptor Sort(ICollection<Elastic.Clients.Elasticsearch.SortOptions>? sort)
344+
{
345+
SortDescriptor = null;
346+
SortDescriptorAction = null;
347+
SortDescriptorActions = null;
348+
SortValue = sort;
349+
return Self;
350+
}
351+
352+
public BucketSortAggregationDescriptor Sort(SortOptionsDescriptor descriptor)
353+
{
354+
SortValue = null;
355+
SortDescriptorAction = null;
356+
SortDescriptorActions = null;
357+
SortDescriptor = descriptor;
358+
return Self;
359+
}
360+
361+
public BucketSortAggregationDescriptor Sort(Action<SortOptionsDescriptor> configure)
362+
{
363+
SortValue = null;
364+
SortDescriptor = null;
365+
SortDescriptorActions = null;
366+
SortDescriptorAction = configure;
367+
return Self;
368+
}
369+
370+
public BucketSortAggregationDescriptor Sort(params Action<SortOptionsDescriptor>[] configure)
371+
{
372+
SortValue = null;
373+
SortDescriptor = null;
374+
SortDescriptorAction = null;
375+
SortDescriptorActions = configure;
376+
return Self;
377+
}
378+
379+
public BucketSortAggregationDescriptor From(int? from)
380+
{
381+
FromValue = from;
382+
return Self;
383+
}
384+
385+
public BucketSortAggregationDescriptor GapPolicy(Elastic.Clients.Elasticsearch.Aggregations.GapPolicy? gapPolicy)
386+
{
387+
GapPolicyValue = gapPolicy;
388+
return Self;
389+
}
390+
391+
public BucketSortAggregationDescriptor Meta(Func<FluentDictionary<string, object>, FluentDictionary<string, object>> selector)
392+
{
393+
MetaValue = selector?.Invoke(new FluentDictionary<string, object>());
394+
return Self;
395+
}
396+
397+
public BucketSortAggregationDescriptor Size(int? size)
398+
{
399+
SizeValue = size;
400+
return Self;
401+
}
402+
403+
protected override void Serialize(Utf8JsonWriter writer, JsonSerializerOptions options, IElasticsearchClientSettings settings)
404+
{
405+
writer.WriteStartObject();
406+
writer.WritePropertyName("bucket_sort");
407+
writer.WriteStartObject();
408+
if (SortDescriptor is not null)
409+
{
410+
writer.WritePropertyName("sort");
411+
JsonSerializer.Serialize(writer, SortDescriptor, options);
412+
}
413+
else if (SortDescriptorAction is not null)
414+
{
415+
writer.WritePropertyName("sort");
416+
JsonSerializer.Serialize(writer, new SortOptionsDescriptor(SortDescriptorAction), options);
417+
}
418+
else if (SortDescriptorActions is not null)
419+
{
420+
writer.WritePropertyName("sort");
421+
if (SortDescriptorActions.Length > 1)
422+
writer.WriteStartArray();
423+
foreach (var action in SortDescriptorActions)
424+
{
425+
JsonSerializer.Serialize(writer, new SortOptionsDescriptor(action), options);
426+
}
427+
428+
if (SortDescriptorActions.Length > 1)
429+
writer.WriteEndArray();
430+
}
431+
else if (SortValue is not null)
432+
{
433+
writer.WritePropertyName("sort");
434+
SingleOrManySerializationHelper.Serialize<Elastic.Clients.Elasticsearch.SortOptions>(SortValue, writer, options);
435+
}
436+
437+
if (FromValue.HasValue)
438+
{
439+
writer.WritePropertyName("from");
440+
writer.WriteNumberValue(FromValue.Value);
441+
}
442+
443+
if (GapPolicyValue is not null)
444+
{
445+
writer.WritePropertyName("gap_policy");
446+
JsonSerializer.Serialize(writer, GapPolicyValue, options);
447+
}
448+
449+
if (SizeValue.HasValue)
450+
{
451+
writer.WritePropertyName("size");
452+
writer.WriteNumberValue(SizeValue.Value);
453+
}
454+
455+
writer.WriteEndObject();
456+
if (MetaValue is not null)
457+
{
458+
writer.WritePropertyName("meta");
459+
JsonSerializer.Serialize(writer, MetaValue, options);
460+
}
461+
462+
writer.WriteEndObject();
463+
}
464+
}

‎src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TopHitsAggregation.g.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ public override void Write(Utf8JsonWriter writer, TopHitsAggregation value, Json
316316
if (value.Sort is not null)
317317
{
318318
writer.WritePropertyName("sort");
319-
JsonSerializer.Serialize(writer, value.Sort, options);
319+
SingleOrManySerializationHelper.Serialize<Elastic.Clients.Elasticsearch.SortOptions>(value.Sort, writer, options);
320320
}
321321

322322
if (value.StoredFields is not null)

‎src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TopMetricsAggregation.g.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public override void Write(Utf8JsonWriter writer, TopMetricsAggregation value, J
172172
if (value.Sort is not null)
173173
{
174174
writer.WritePropertyName("sort");
175-
JsonSerializer.Serialize(writer, value.Sort, options);
175+
SingleOrManySerializationHelper.Serialize<Elastic.Clients.Elasticsearch.SortOptions>(value.Sort, writer, options);
176176
}
177177

178178
writer.WriteEndObject();
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Elastic.Clients.Elasticsearch.Aggregations;
6+
using System.Collections.Generic;
7+
using System;
8+
using Tests.Core.ManagedElasticsearch.Clusters;
9+
using Tests.Domain;
10+
using Tests.Framework.EndpointTests.TestState;
11+
using Tests.Core.Extensions;
12+
13+
namespace Tests.Aggregations.Pipeline;
14+
15+
public class BucketSortAggregationUsageTests : AggregationUsageTestBase<ReadOnlyCluster>
16+
{
17+
public BucketSortAggregationUsageTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }
18+
19+
protected override object AggregationJson => new
20+
{
21+
projects_started_per_month = new
22+
{
23+
date_histogram = new
24+
{
25+
field = "startedOn",
26+
calendar_interval = "month",
27+
},
28+
aggregations = new
29+
{
30+
commits = new
31+
{
32+
sum = new
33+
{
34+
field = "numberOfCommits"
35+
}
36+
},
37+
commits_bucket_sort = new
38+
{
39+
bucket_sort = new
40+
{
41+
sort = new { commits = new { order = "desc" } },
42+
from = 0,
43+
size = 3,
44+
gap_policy = "insert_zeros"
45+
}
46+
}
47+
}
48+
}
49+
};
50+
51+
#pragma warning disable 618, 612
52+
protected override Action<AggregationDescriptor<Project>> FluentAggs => a => a
53+
.DateHistogram("projects_started_per_month", dh => dh
54+
.Field(p => p.StartedOn)
55+
.CalendarInterval(CalendarInterval.Month)
56+
.Aggregations(aa => aa
57+
.Sum("commits", sm => sm
58+
.Field(p => p.NumberOfCommits)
59+
)
60+
.BucketSort("commits_bucket_sort", bs => bs
61+
.Sort(s => s
62+
.Field("commits", f => f.Order(SortOrder.Desc))
63+
)
64+
.From(0)
65+
.Size(3)
66+
.GapPolicy(GapPolicy.InsertZeros)
67+
)
68+
)
69+
);
70+
71+
protected override AggregationDictionary InitializerAggs =>
72+
new DateHistogramAggregation("projects_started_per_month")
73+
{
74+
Field = "startedOn",
75+
CalendarInterval = CalendarInterval.Month,
76+
Aggregations =
77+
new SumAggregation("commits", "numberOfCommits") &&
78+
new BucketSortAggregation("commits_bucket_sort")
79+
{
80+
Sort = new List<SortOptions>
81+
{
82+
SortOptions.Field("commits", new FieldSort { Order = SortOrder.Desc })
83+
},
84+
From = 0,
85+
Size = 3,
86+
GapPolicy = GapPolicy.InsertZeros
87+
}
88+
};
89+
#pragma warning restore 618, 612
90+
91+
protected override void ExpectResponse(SearchResponse<Project> response)
92+
{
93+
response.ShouldBeValid();
94+
95+
var projectsPerMonth = response.Aggregations.GetDateHistogram("projects_started_per_month");
96+
projectsPerMonth.Should().NotBeNull();
97+
projectsPerMonth.Buckets.Should().NotBeNull();
98+
projectsPerMonth.Buckets.Count.Should().Be(3);
99+
100+
double previousCommits = -1;
101+
102+
// sum of commits should descend over buckets
103+
foreach (var item in projectsPerMonth.Buckets)
104+
{
105+
var value = item.GetSum("commits").Value;
106+
if (value == null)
107+
continue;
108+
109+
var numberOfCommits = value.Value;
110+
if (Math.Abs(previousCommits - (-1)) > double.Epsilon)
111+
numberOfCommits.Should().BeLessOrEqualTo(previousCommits);
112+
113+
previousCommits = numberOfCommits;
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)
Please sign in to comment.