Skip to content

Commit

Permalink
Allow formatting of sort values (#5610)
Browse files Browse the repository at this point in the history
  • Loading branch information
stevejgordon authored Apr 23, 2021
1 parent e2b2eda commit bd42558
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 18 deletions.
13 changes: 13 additions & 0 deletions src/Nest/Search/Search/Sort/SortBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ namespace Nest
[JsonFormatter(typeof(SortFormatter))]
public interface ISort
{
/// <summary>
/// A format to apply to the sort value.
/// </summary>
[DataMember(Name ="format")]
string Format { get; set; }

/// <summary>
/// Specifies how documents which are missing the sort field should
/// be treated.
Expand Down Expand Up @@ -53,6 +59,9 @@ public interface ISort

public abstract class SortBase : ISort
{
/// <inheritdoc />
public string Format { get; set; }

/// <inheritdoc />
public object Missing { get; set; }

Expand Down Expand Up @@ -87,6 +96,7 @@ public abstract class SortDescriptorBase<TDescriptor, TInterface, T> : Descripto
/// </summary>
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; }
Expand All @@ -103,6 +113,9 @@ public abstract class SortDescriptorBase<TDescriptor, TInterface, T> : Descripto
/// Sorts by descending sort order
/// </summary>
public virtual TDescriptor Descending() => Assign(SortOrder.Descending, (a, v) => a.Order = v);

/// <inheritdoc cref="ISort.Format" />
public virtual TDescriptor Format(string format) => Assign(format, (a, v) => a.Format = v);

/// <inheritdoc cref="ISort.Order" />
public virtual TDescriptor Order(SortOrder? order) => Assign(order, (a, v) => a.Order = v);
Expand Down
93 changes: 75 additions & 18 deletions tests/Tests/Search/Search/SearchApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ protected override void ExpectResponse(ISearchResponse<Project> 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();
}
}

Expand Down Expand Up @@ -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}");
// }
}
}
}

Expand Down Expand Up @@ -610,7 +611,7 @@ public class SearchWithPointInTimeApiTests
: ApiTestBase<ReadOnlyCluster, ISearchResponse<Project>, ISearchRequest, SearchDescriptor<Project>, SearchRequest<Project>>
{
public SearchWithPointInTimeApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { }

protected override object ExpectJson => new
{
size = 1,
Expand All @@ -624,7 +625,7 @@ public SearchWithPointInTimeApiTests(ReadOnlyCluster cluster, EndpointUsage usag
keep_alive = "1m"
}
};

protected override Func<SearchDescriptor<Project>, ISearchRequest> Fluent => s => s
.Size(1)
.Query(q => q.MatchAll())
Expand Down Expand Up @@ -675,7 +676,7 @@ public SearchApiRuntimeFieldsTests(ReadOnlyCluster cluster, EndpointUsage usage)
},
"search_runtime_field"
},
runtime_mappings = new
runtime_mappings = new
{
search_runtime_field = new
{
Expand Down Expand Up @@ -740,4 +741,60 @@ protected override void ExpectResponse(ISearchResponse<Project> 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<SearchDescriptor<Project>, 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<Project> Initializer => new()
{
Size = 5,
Query = new QueryContainer(new MatchAllQuery()),
Sort = new List<ISort>
{
new FieldSort
{
Field = Infer.Field<Project>(f => f.StartedOn),
Format = "strict_date_optional_time_nanos",
Mode = SortMode.Average,
Order = SortOrder.Ascending
}
}
};

protected override void ExpectResponse(ISearchResponse<Project> response) => response.Hits.Count.Should().BeGreaterThan(0);
}
}

0 comments on commit bd42558

Please sign in to comment.