From bd42558458bbab775d76951c46b3f6f77830188f Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Fri, 23 Apr 2021 05:55:16 +0100 Subject: [PATCH] Allow formatting of sort values (#5610) --- src/Nest/Search/Search/Sort/SortBase.cs | 13 +++ tests/Tests/Search/Search/SearchApiTests.cs | 93 +++++++++++++++++---- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/src/Nest/Search/Search/Sort/SortBase.cs b/src/Nest/Search/Search/Sort/SortBase.cs index d76c9a1fce1..ed9fc6ab43a 100644 --- a/src/Nest/Search/Search/Sort/SortBase.cs +++ b/src/Nest/Search/Search/Sort/SortBase.cs @@ -12,6 +12,12 @@ namespace Nest [JsonFormatter(typeof(SortFormatter))] public interface ISort { + /// + /// A format to apply to the sort value. + /// + [DataMember(Name ="format")] + string Format { get; set; } + /// /// Specifies how documents which are missing the sort field should /// be treated. @@ -53,6 +59,9 @@ public interface ISort public abstract class SortBase : ISort { + /// + public string Format { get; set; } + /// public object Missing { get; set; } @@ -87,6 +96,7 @@ public abstract class SortDescriptorBase : Descripto /// protected abstract Field SortKey { get; } + string ISort.Format { get; set; } object ISort.Missing { get; set; } SortMode? ISort.Mode { get; set; } NumericType? ISort.NumericType { get; set; } @@ -103,6 +113,9 @@ public abstract class SortDescriptorBase : Descripto /// Sorts by descending sort order /// public virtual TDescriptor Descending() => Assign(SortOrder.Descending, (a, v) => a.Order = v); + + /// + public virtual TDescriptor Format(string format) => Assign(format, (a, v) => a.Format = v); /// public virtual TDescriptor Order(SortOrder? order) => Assign(order, (a, v) => a.Order = v); diff --git a/tests/Tests/Search/Search/SearchApiTests.cs b/tests/Tests/Search/Search/SearchApiTests.cs index 482a8e7b822..52913cb1376 100644 --- a/tests/Tests/Search/Search/SearchApiTests.cs +++ b/tests/Tests/Search/Search/SearchApiTests.cs @@ -111,7 +111,8 @@ protected override void ExpectResponse(ISearchResponse response) var startDates = response.Aggregations.Terms("startDates"); startDates.Should().NotBeNull(); - foreach (var document in response.Documents) document.ShouldAdhereToSourceSerializerWhenSet(); + foreach (var document in response.Documents) + document.ShouldAdhereToSourceSerializerWhenSet(); } } @@ -540,20 +541,20 @@ protected override void ExpectResponse(ListTasksResponse response) { response.ShouldBeValid(); foreach (var node in response.Nodes) - foreach (var task in node.Value.Tasks) - { - task.Value.Headers.Should().NotBeNull(); - if (task.Value.Headers.TryGetValue(RequestData.OpaqueIdHeader, out var opaqueIdValue)) - opaqueIdValue.Should() - .Be(CallIsolatedValue, - $"OpaqueId header {opaqueIdValue} did not match {CallIsolatedValue}"); - // TODO: Determine if this is a valid assertion i.e. should all tasks returned have an OpaqueId header? -// else -// { -// Assert.True(false, -// $"No OpaqueId header for task {task.Key} and OpaqueId value {this.CallIsolatedValue}"); -// } - } + foreach (var task in node.Value.Tasks) + { + task.Value.Headers.Should().NotBeNull(); + if (task.Value.Headers.TryGetValue(RequestData.OpaqueIdHeader, out var opaqueIdValue)) + opaqueIdValue.Should() + .Be(CallIsolatedValue, + $"OpaqueId header {opaqueIdValue} did not match {CallIsolatedValue}"); + // TODO: Determine if this is a valid assertion i.e. should all tasks returned have an OpaqueId header? + // else + // { + // Assert.True(false, + // $"No OpaqueId header for task {task.Key} and OpaqueId value {this.CallIsolatedValue}"); + // } + } } } @@ -610,7 +611,7 @@ public class SearchWithPointInTimeApiTests : ApiTestBase, ISearchRequest, SearchDescriptor, SearchRequest> { public SearchWithPointInTimeApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } - + protected override object ExpectJson => new { size = 1, @@ -624,7 +625,7 @@ public SearchWithPointInTimeApiTests(ReadOnlyCluster cluster, EndpointUsage usag keep_alive = "1m" } }; - + protected override Func, ISearchRequest> Fluent => s => s .Size(1) .Query(q => q.MatchAll()) @@ -675,7 +676,7 @@ public SearchApiRuntimeFieldsTests(ReadOnlyCluster cluster, EndpointUsage usage) }, "search_runtime_field" }, - runtime_mappings = new + runtime_mappings = new { search_runtime_field = new { @@ -740,4 +741,60 @@ protected override void ExpectResponse(ISearchResponse response) } } } + + [SkipVersion("<7.13.0", "Format for sort values added in Elasticsearch 7.13.0")] + public class SearchApiSortFormatTests : SearchApiTests + { + public SearchApiSortFormatTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override object ExpectJson => new + { + size = 5, + query = new + { + match_all = new { } + }, + sort = new[] + { + new + { + startedOn = new + { + format = "strict_date_optional_time_nanos", + mode = "avg", + order = "asc" + } + } + } + }; + + protected override Func, ISearchRequest> Fluent => s => s + .Size(5) + .Query(q => q + .MatchAll() + ) + .Sort(srt => srt.Field(f => f + .Field(fld => fld.StartedOn) + .Order(SortOrder.Ascending) + .Format("strict_date_optional_time_nanos") + .Mode(SortMode.Average))); + + protected override SearchRequest Initializer => new() + { + Size = 5, + Query = new QueryContainer(new MatchAllQuery()), + Sort = new List + { + new FieldSort + { + Field = Infer.Field(f => f.StartedOn), + Format = "strict_date_optional_time_nanos", + Mode = SortMode.Average, + Order = SortOrder.Ascending + } + } + }; + + protected override void ExpectResponse(ISearchResponse response) => response.Hits.Count.Should().BeGreaterThan(0); + } }