Skip to content

Commit 2cc649c

Browse files
committed
Merge pull request #940 from elasticsearch/feature/aggregations-top-hits
Feature/aggregations top hits
2 parents 100f87d + bc2f99d commit 2cc649c

File tree

9 files changed

+230
-13
lines changed

9 files changed

+230
-13
lines changed

src/Nest/DSL/Aggregations/AggregationDescriptor.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ public interface IAggregationContainer
8484
[JsonProperty("percentile_ranks")]
8585
IPercentileRanksAggregaor PercentileRanks { get; set; }
8686

87+
[JsonProperty("top_hits")]
88+
ITopHitsAggregator TopHits { get; set; }
89+
8790
[JsonProperty("aggs")]
8891
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
8992
IDictionary<string, IAggregationContainer> Aggregations { get; set; }
@@ -111,14 +114,14 @@ public class AggregationContainer : IAggregationContainer
111114
private ISignificantTermsAggregator _significantTerms;
112115
private IPercentileRanksAggregaor _percentileRanks;
113116

117+
private ITopHitsAggregator _topHits;
114118
public IAverageAggregator Average { get; set; }
115119
public IValueCountAggregator ValueCount { get; set; }
116120
public IMaxAggregator Max { get; set; }
117121
public IMinAggregator Min { get; set; }
118122
public IStatsAggregator Stats { get; set; }
119123
public ISumAggregator Sum { get; set; }
120124
public IExtendedStatsAggregator ExtendedStats { get; set; }
121-
122125
public IDateHistogramAggregator DateHistogram
123126
{
124127
get { return _dateHistogram; }
@@ -227,6 +230,12 @@ public IPercentileRanksAggregaor PercentileRanks
227230
set { _percentileRanks = value; }
228231
}
229232

233+
public ITopHitsAggregator TopHits
234+
{
235+
get { return _topHits; }
236+
set { _topHits = value; }
237+
}
238+
230239
private void LiftAggregations(IBucketAggregator bucket)
231240
{
232241
if (bucket == null) return;
@@ -290,7 +299,9 @@ public class AggregationDescriptor<T> : IAggregationContainer
290299
IPercentileRanksAggregaor IAggregationContainer.PercentileRanks { get;set; }
291300

292301
ITermsAggregator IAggregationContainer.Terms { get; set; }
293-
302+
303+
ITopHitsAggregator IAggregationContainer.TopHits { get; set; }
304+
294305
public AggregationDescriptor<T> Average(string name, Func<AverageAggregationDescriptor<T>, AverageAggregationDescriptor<T>> selector)
295306
{
296307
return _SetInnerAggregation(name, selector, (a, d) => a.Average = d);
@@ -429,6 +440,12 @@ public AggregationDescriptor<T> ValueCount(string name,
429440
return _SetInnerAggregation(name, selector, (a, d) => a.ValueCount = d);
430441
}
431442

443+
public AggregationDescriptor<T> TopHits(string name,
444+
Func<TopHitsAggregationDescriptor<T>, TopHitsAggregationDescriptor<T>> selector)
445+
{
446+
return _SetInnerAggregation(name, selector, (a, d) => a.TopHits = d);
447+
}
448+
432449
private AggregationDescriptor<T> _SetInnerAggregation<TAggregation>(
433450
string key,
434451
Func<TAggregation, TAggregation> selector
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using Nest.Resolvers.Converters;
2+
using Newtonsoft.Json;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
8+
namespace Nest
9+
{
10+
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
11+
[JsonConverter(typeof(ReadAsTypeConverter<TopHitsAggregator>))]
12+
public interface ITopHitsAggregator : IMetricAggregator
13+
{
14+
[JsonProperty("from")]
15+
int? From { get; set; }
16+
17+
[JsonProperty("size")]
18+
int? Size { get; set; }
19+
20+
[JsonProperty("sort")]
21+
[JsonConverter(typeof(SortCollectionConverter))]
22+
IList<KeyValuePair<PropertyPathMarker, ISort>> Sort { get; set; }
23+
24+
[JsonProperty("_source")]
25+
ISourceFilter Source { get; set; }
26+
}
27+
28+
public class TopHitsAggregator : MetricAggregator, ITopHitsAggregator
29+
{
30+
public int? From { get; set; }
31+
public int? Size { get; set; }
32+
public IList<KeyValuePair<PropertyPathMarker, ISort>> Sort { get; set; }
33+
public ISourceFilter Source { get; set; }
34+
}
35+
36+
public class TopHitsAggregationDescriptor<T>
37+
: MetricAggregationBaseDescriptor<TopHitsAggregationDescriptor<T>, T>, ITopHitsAggregator
38+
where T : class
39+
{
40+
ITopHitsAggregator Self { get { return this; } }
41+
42+
int? ITopHitsAggregator.From { get; set; }
43+
44+
int? ITopHitsAggregator.Size { get; set; }
45+
46+
IList<KeyValuePair<PropertyPathMarker, ISort>> ITopHitsAggregator.Sort { get; set; }
47+
48+
ISourceFilter ITopHitsAggregator.Source { get; set; }
49+
50+
public TopHitsAggregationDescriptor<T> From(int from)
51+
{
52+
this.Self.From = from;
53+
return this;
54+
}
55+
56+
public TopHitsAggregationDescriptor<T> Size(int size)
57+
{
58+
this.Self.Size = size;
59+
return this;
60+
}
61+
62+
public TopHitsAggregationDescriptor<T> Sort(Func<SortFieldDescriptor<T>, IFieldSort> sortSelector)
63+
{
64+
sortSelector.ThrowIfNull("sortSelector");
65+
66+
if (Self.Sort == null)
67+
Self.Sort = new List<KeyValuePair<PropertyPathMarker, ISort>>();
68+
69+
var descriptor = sortSelector(new SortFieldDescriptor<T>());
70+
this.Self.Sort.Add(new KeyValuePair<PropertyPathMarker, ISort>(descriptor.Field, descriptor));
71+
72+
return this;
73+
}
74+
75+
public TopHitsAggregationDescriptor<T> Source(bool include = true)
76+
{
77+
if (!include)
78+
this.Self.Source = new SourceFilter { Exclude = new PropertyPathMarker[] { "*" } };
79+
else
80+
this.Self.Source = null;
81+
82+
return this;
83+
}
84+
85+
public TopHitsAggregationDescriptor<T> Source(Func<SearchSourceDescriptor<T>, SearchSourceDescriptor<T>> sourceSelector)
86+
{
87+
this.Self.Source = sourceSelector(new SearchSourceDescriptor<T>());
88+
return this;
89+
}
90+
}
91+
}

src/Nest/Domain/Aggregations/AggregationsHelper.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ public PercentilesMetric PercentilesRank(string key)
8181
return this.TryGet<PercentilesMetric>(key);
8282
}
8383

84+
public TopHitsMetric TopHitsMetric(string key)
85+
{
86+
return this.TryGet<TopHitsMetric>(key);
87+
}
88+
8489
public SingleBucket Global(string key)
8590
{
8691
return this.TryGet<SingleBucket>(key);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using Newtonsoft.Json;
6+
using Newtonsoft.Json.Linq;
7+
8+
namespace Nest
9+
{
10+
public class TopHitsMetric : IMetricAggregation
11+
{
12+
private IEnumerable<JObject> _hits;
13+
14+
public TopHitsMetric()
15+
{
16+
}
17+
18+
internal TopHitsMetric(IEnumerable<JObject> hits)
19+
{
20+
_hits = hits;
21+
}
22+
23+
public long Total { get; set; }
24+
public double? MaxScore { get; set; }
25+
26+
public IEnumerable<Hit<T>> Hits<T>(JsonSerializer serializer = null) where T : class
27+
{
28+
if (serializer != null)
29+
return _hits.Select(h => h.ToObject<Hit<T>>(serializer));
30+
return _hits.Select(h => h.ToObject<Hit<T>>());
31+
}
32+
33+
public IEnumerable<T> Documents<T>(JsonSerializer serializer = null) where T : class
34+
{
35+
return this.Hits<T>(serializer).Select(h => h.Source);
36+
}
37+
38+
39+
}
40+
41+
}

src/Nest/Domain/Hit/Hit.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ public class Hit<T> : IHit<T>
3535
public T Source { get; internal set; }
3636
[JsonProperty(PropertyName = "_index")]
3737
public string Index { get; internal set; }
38+
39+
//TODO in NEST 2.0 make the property itself double?
3840
[JsonProperty(PropertyName = "_score")]
39-
public double Score { get; internal set; }
41+
internal double? _score { get; set; }
42+
public double Score { get { return _score.GetValueOrDefault(0); } }
43+
4044
[JsonProperty(PropertyName = "_type")]
4145
public string Type { get; internal set; }
4246
[JsonProperty(PropertyName = "_version")]

src/Nest/Nest.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
<Compile Include="Domain\Aggregations\AggregationsHelper.cs" />
107107
<Compile Include="Domain\Aggregations\Bucket.cs" />
108108
<Compile Include="Domain\Aggregations\BucketAggregationBase.cs" />
109+
<Compile Include="Domain\Aggregations\TopHitsMetric.cs" />
109110
<Compile Include="Domain\Aggregations\GeoBoundsMetric.cs" />
110111
<Compile Include="Domain\Aggregations\HistogramItem.cs" />
111112
<Compile Include="Domain\Aggregations\ExtendedStatsMetric.cs" />
@@ -211,6 +212,7 @@
211212
<Compile Include="Domain\Stats\PluginStats.cs" />
212213
<Compile Include="Domain\Stats\SegmentsStats.cs" />
213214
<Compile Include="DSL\Aggregations\PercentileRanksAggregationDescriptor.cs" />
215+
<Compile Include="DSL\Aggregations\TopHitsAggregationDescriptor.cs" />
214216
<Compile Include="DSL\Aggregations\GeoBoundsAggregationDescriptor.cs" />
215217
<Compile Include="DSL\Aggregations\ReverseNestedAggregationDescriptor.cs" />
216218
<Compile Include="DSL\CatIndicesDescriptor.cs" />

src/Nest/Resolvers/Converters/Aggregations/AggregationConverter.cs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,27 @@ private IAggregation ReadAggregation(JsonReader reader, JsonSerializer serialize
5959
return GetSingleBucketAggregation(reader, serializer);
6060
case "bounds":
6161
return GetGeoBoundsMetricAggregation(reader, serializer);
62+
case "hits":
63+
return GetHitsAggregation(reader, serializer);
6264
default:
6365
return null;
6466

6567
}
6668
}
6769

70+
private IAggregation GetHitsAggregation(JsonReader reader, JsonSerializer serializer)
71+
{
72+
reader.Read();
73+
var o = JObject.Load(reader);
74+
if (o == null)
75+
return null;
76+
77+
var total = o["total"].ToObject<long>();
78+
var maxScore = o["max_score"].ToObject<double?>();
79+
var hits = o["hits"].Children().OfType<JObject>().Select(s=>s);
80+
return new TopHitsMetric(hits) { Total = total, MaxScore = maxScore };
81+
}
82+
6883
private IAggregation GetGeoBoundsMetricAggregation(JsonReader reader, JsonSerializer serializer)
6984
{
7085
reader.Read();
@@ -370,15 +385,15 @@ public IAggregation GetRangeAggregation(JsonReader reader, JsonSerializer serial
370385
break;
371386
}
372387
}
373-
var bucket = new RangeItem
374-
{
375-
Key = key,
376-
From = fromDouble,
377-
To = toDouble,
378-
DocCount = docCount.GetValueOrDefault(),
379-
FromAsString = fromAsString,
380-
ToAsString = toAsString
381-
};
388+
var bucket = new RangeItem
389+
{
390+
Key = key,
391+
From = fromDouble,
392+
To = toDouble,
393+
DocCount = docCount.GetValueOrDefault(),
394+
FromAsString = fromAsString,
395+
ToAsString = toAsString
396+
};
382397

383398
bucket.Aggregations = this.GetNestedAggregations(reader, serializer);
384399
return bucket;

src/Nest/Resolvers/Converters/ConcreteTypeConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ internal static Type GetConcreteTypeUsingSelector<T>(
175175
hitDynamic.Fields = sel;
176176
hitDynamic.Source = d._source;
177177
hitDynamic.Index = d._index;
178-
hitDynamic.Score = (d._score is double) ? d._score : default(double);
178+
hitDynamic._score = (d._score is double) ? d._score : default(double);
179179
hitDynamic.Type = d._type;
180180
hitDynamic.Version = d._version;
181181
hitDynamic.Id = d._id;

src/Tests/Nest.Tests.Integration/Aggregations/MetricAggregationTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Linq;
2+
using Elasticsearch.Net;
23
using FluentAssertions;
34
using Nest.Tests.MockData.Domain;
45
using NUnit.Framework;
@@ -162,5 +163,46 @@ public void GeoBounds()
162163
geoBoundsMetric.Bounds.BottomRight.Lat.Should().NotBe(0);
163164
geoBoundsMetric.Bounds.BottomRight.Lon.Should().NotBe(0);
164165
}
166+
167+
[Test]
168+
public void TopHits()
169+
{
170+
var results = this.Client.Search<ElasticsearchProject>(s => s
171+
.Size(0)
172+
.Aggregations(a => a
173+
.Terms("top-countries", t => t
174+
.Field(p => p.Country)
175+
.Size(3)
176+
.Aggregations(aa => aa
177+
.TopHits("top-country-hits", th => th
178+
.Sort(sort => sort
179+
.OnField(p => p.StartedOn)
180+
.Order(SortOrder.Descending)
181+
)
182+
.Source(src => src
183+
.Include(p => p.Name)
184+
)
185+
.Size(1)
186+
)
187+
)
188+
)
189+
)
190+
);
191+
192+
results.IsValid.Should().BeTrue();
193+
194+
var topCountries = results.Aggs.Terms("top-countries").Items;
195+
foreach(var topCountry in topCountries)
196+
{
197+
var topHits = topCountry.TopHitsMetric("top-country-hits");
198+
topHits.Should().NotBeNull();
199+
topHits.Total.Should().BeGreaterThan(0);
200+
var hits = topHits.Hits<ElasticsearchProject>();
201+
hits.Should().NotBeEmpty().And.NotContain(h=> h.Id.IsNullOrEmpty() || h.Index.IsNullOrEmpty());
202+
topHits.Documents<ElasticsearchProject>().Should().NotBeEmpty();
203+
204+
}
205+
206+
}
165207
}
166208
}

0 commit comments

Comments
 (0)