Skip to content

Commit 378583b

Browse files
committed
Top hits: add support for options that were missing
Highlighting, explain, script fields, fielddata fields, and include versions Closes #1168
1 parent 69819fe commit 378583b

File tree

5 files changed

+141
-10
lines changed

5 files changed

+141
-10
lines changed

src/Nest/DSL/Aggregations/TopHitsAggregationDescriptor.cs

+84
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System;
44
using System.Collections.Generic;
55
using System.Linq;
6+
using System.Linq.Expressions;
67
using System.Text;
78

89
namespace Nest
@@ -23,6 +24,22 @@ public interface ITopHitsAggregator : IMetricAggregator
2324

2425
[JsonProperty("_source")]
2526
ISourceFilter Source { get; set; }
27+
28+
[JsonProperty("highlight")]
29+
IHighlightRequest Highlight { get; set; }
30+
31+
[JsonProperty("explain")]
32+
bool? Explain { get; set; }
33+
34+
[JsonProperty("script_fields")]
35+
[JsonConverter(typeof(DictionaryKeysAreNotPropertyNamesJsonConverter))]
36+
IDictionary<string, IScriptFilter> ScriptFields { get; set; }
37+
38+
[JsonProperty("fielddata_fields")]
39+
IEnumerable<PropertyPathMarker> FieldDataFields { get; set; }
40+
41+
[JsonProperty("version")]
42+
bool? Version { get; set; }
2643
}
2744

2845
public class TopHitsAggregator : MetricAggregator, ITopHitsAggregator
@@ -31,6 +48,11 @@ public class TopHitsAggregator : MetricAggregator, ITopHitsAggregator
3148
public int? Size { get; set; }
3249
public IList<KeyValuePair<PropertyPathMarker, ISort>> Sort { get; set; }
3350
public ISourceFilter Source { get; set; }
51+
public IHighlightRequest Highlight { get; set; }
52+
public bool? Explain { get; set; }
53+
public IDictionary<string, IScriptFilter> ScriptFields { get; set; }
54+
public IEnumerable<PropertyPathMarker> FieldDataFields { get; set; }
55+
public bool? Version { get; set; }
3456
}
3557

3658
public class TopHitsAggregationDescriptor<T>
@@ -47,6 +69,16 @@ public class TopHitsAggregationDescriptor<T>
4769

4870
ISourceFilter ITopHitsAggregator.Source { get; set; }
4971

72+
IHighlightRequest ITopHitsAggregator.Highlight { get; set; }
73+
74+
bool? ITopHitsAggregator.Explain { get; set; }
75+
76+
IDictionary<string, IScriptFilter> ITopHitsAggregator.ScriptFields { get; set; }
77+
78+
IEnumerable<PropertyPathMarker> ITopHitsAggregator.FieldDataFields { get; set; }
79+
80+
bool? ITopHitsAggregator.Version { get; set; }
81+
5082
public TopHitsAggregationDescriptor<T> From(int from)
5183
{
5284
this.Self.From = from;
@@ -87,5 +119,57 @@ public TopHitsAggregationDescriptor<T> Source(Func<SearchSourceDescriptor<T>, Se
87119
this.Self.Source = sourceSelector(new SearchSourceDescriptor<T>());
88120
return this;
89121
}
122+
123+
public TopHitsAggregationDescriptor<T> Highlight(Func<HighlightDescriptor<T>, HighlightDescriptor<T>> highlightDescriptor)
124+
{
125+
highlightDescriptor.ThrowIfNull("highlightDescriptor");
126+
this.Self.Highlight = highlightDescriptor(new HighlightDescriptor<T>());
127+
return this;
128+
}
129+
130+
public TopHitsAggregationDescriptor<T> Explain(bool explain = true)
131+
{
132+
this.Self.Explain = explain;
133+
return this;
134+
}
135+
136+
public TopHitsAggregationDescriptor<T> ScriptFields(
137+
Func<FluentDictionary<string, Func<ScriptFilterDescriptor, ScriptFilterDescriptor>>,
138+
FluentDictionary<string, Func<ScriptFilterDescriptor, ScriptFilterDescriptor>>> scriptFields)
139+
{
140+
scriptFields.ThrowIfNull("scriptFields");
141+
var scriptFieldDescriptors = scriptFields(new FluentDictionary<string, Func<ScriptFilterDescriptor, ScriptFilterDescriptor>>());
142+
if (scriptFieldDescriptors == null || scriptFieldDescriptors.All(d => d.Value == null))
143+
{
144+
Self.ScriptFields = null;
145+
return this;
146+
}
147+
Self.ScriptFields = new FluentDictionary<string, IScriptFilter>();
148+
foreach (var d in scriptFieldDescriptors)
149+
{
150+
if (d.Value == null)
151+
continue;
152+
Self.ScriptFields.Add(d.Key, d.Value(new ScriptFilterDescriptor()));
153+
}
154+
return this;
155+
}
156+
157+
public TopHitsAggregationDescriptor<T> FieldDataFields(params PropertyPathMarker[] fields)
158+
{
159+
this.Self.FieldDataFields = fields;
160+
return this;
161+
}
162+
163+
public TopHitsAggregationDescriptor<T> FieldDataFields(params Expression<Func<T, object>>[] objectPaths)
164+
{
165+
this.Self.FieldDataFields = objectPaths.Select(e => (PropertyPathMarker)e);
166+
return this;
167+
}
168+
169+
public TopHitsAggregationDescriptor<T> Version (bool version = true)
170+
{
171+
this.Self.Version = version;
172+
return this;
173+
}
90174
}
91175
}

src/Nest/Domain/Aggregations/AggregationsHelper.cs

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34

45
namespace Nest
@@ -92,9 +93,15 @@ public PercentilesMetric PercentilesRank(string key)
9293
return this.TryGet<PercentilesMetric>(key);
9394
}
9495

96+
[Obsolete("Scheduled to be removed in 2.0. Use TopHits() instead.")]
9597
public TopHitsMetric TopHitsMetric(string key)
96-
{
97-
return this.TryGet<TopHitsMetric>(key);
98+
{
99+
return this.TopHits(key);
100+
}
101+
102+
public TopHitsMetric TopHits(string key)
103+
{
104+
return this.TryGet<TopHitsMetric>(key);
98105
}
99106

100107
public FiltersBucket Filters(string key)

src/Nest/Domain/Aggregations/TopHitsMetric.cs

+13-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public class TopHitsMetric : IMetricAggregation
1111
{
1212
private IEnumerable<JObject> _hits;
1313

14+
internal JsonSerializer _defaultSerializer;
15+
1416
public TopHitsMetric()
1517
{
1618
}
@@ -20,22 +22,28 @@ internal TopHitsMetric(IEnumerable<JObject> hits)
2022
_hits = hits;
2123
}
2224

25+
internal TopHitsMetric(IEnumerable<JObject> hits, JsonSerializer serializer)
26+
{
27+
_hits = hits;
28+
_defaultSerializer = serializer;
29+
}
30+
2331
public long Total { get; set; }
2432
public double? MaxScore { get; set; }
2533

2634
public IEnumerable<Hit<T>> Hits<T>(JsonSerializer serializer = null) where T : class
2735
{
28-
if (serializer != null)
29-
return _hits.Select(h => h.ToObject<Hit<T>>(serializer));
36+
var s = serializer ?? _defaultSerializer;
37+
38+
if (s != null)
39+
return _hits.Select(h => h.ToObject<Hit<T>>(s));
40+
3041
return _hits.Select(h => h.ToObject<Hit<T>>());
3142
}
3243

3344
public IEnumerable<T> Documents<T>(JsonSerializer serializer = null) where T : class
3445
{
3546
return this.Hits<T>(serializer).Select(h => h.Source);
3647
}
37-
38-
3948
}
40-
4149
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private IAggregation GetHitsAggregation(JsonReader reader, JsonSerializer serial
7979
var maxScore = o["max_score"].ToObject<double?>();
8080
var hits = o["hits"].Children().OfType<JObject>().Select(s=>s);
8181
reader.Read();
82-
return new TopHitsMetric(hits) { Total = total, MaxScore = maxScore };
82+
return new TopHitsMetric(hits, serializer) { Total = total, MaxScore = maxScore };
8383
}
8484

8585
private IAggregation GetGeoBoundsMetricAggregation(JsonReader reader, JsonSerializer serializer)

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

+33-1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ public void TopHits()
173173
{
174174
var results = this.Client.Search<ElasticsearchProject>(s => s
175175
.Size(0)
176+
.Query(q => q
177+
.Match(m => m
178+
.OnField(p => p.Name)
179+
.Query("elasticsearch")
180+
)
181+
)
176182
.Aggregations(a => a
177183
.Terms("top-countries", t => t
178184
.Field(p => p.Country)
@@ -187,6 +193,26 @@ public void TopHits()
187193
.Include(p => p.Name)
188194
)
189195
.Size(1)
196+
.Explain(true)
197+
.Version(true)
198+
.Highlight(h => h
199+
.PreTags("<em>")
200+
.PostTags("</em>")
201+
.OnFields(hf => hf
202+
.OnField(p => p.Name)
203+
.PreTags("<em>")
204+
.PostTags("</em>")
205+
)
206+
)
207+
.ScriptFields(sf => sf
208+
.Add("locscriptfield", sff => sff
209+
.Script("doc['loc'].value * multiplier")
210+
.Params(sp => sp
211+
.Add("multiplier", 2)
212+
)
213+
)
214+
)
215+
.FieldDataFields(p => p.Name, p => p.Country)
190216
)
191217
)
192218
)
@@ -198,11 +224,17 @@ public void TopHits()
198224
var topCountries = results.Aggs.Terms("top-countries").Items;
199225
foreach(var topCountry in topCountries)
200226
{
201-
var topHits = topCountry.TopHitsMetric("top-country-hits");
227+
var topHits = topCountry.TopHits("top-country-hits");
202228
topHits.Should().NotBeNull();
203229
topHits.Total.Should().BeGreaterThan(0);
204230
var hits = topHits.Hits<ElasticsearchProject>();
205231
hits.Should().NotBeEmpty().And.NotContain(h=> h.Id.IsNullOrEmpty() || h.Index.IsNullOrEmpty());
232+
hits.All(h => h.Explanation != null).Should().BeTrue();
233+
hits.All(h => !h.Version.IsNullOrEmpty()).Should().BeTrue();
234+
hits.All(h => h.Highlights.Count() > 0).Should().BeTrue();
235+
hits.All(h => h.Fields.FieldValues<int[]>("locscriptfield").HasAny()).Should().BeTrue();
236+
hits.All(h => h.Fields.FieldValues<string[]>("name").HasAny()).Should().BeTrue();
237+
hits.All(h => h.Fields.FieldValues<string[]>("country").HasAny()).Should().BeTrue();
206238
topHits.Documents<ElasticsearchProject>().Should().NotBeEmpty();
207239
}
208240
}

0 commit comments

Comments
 (0)