Skip to content

Commit 4dec45f

Browse files
authored
Add NEST support for EQL search status API (#5663)
* Add initial request and response * Generate code for EQL get status * Add test, update naming and headers * Update models and assertions
1 parent f96a6f9 commit 4dec45f

12 files changed

+313
-11
lines changed

src/Elasticsearch.Net/Api/RequestParameters/RequestParameters.Eql.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ public TimeSpan WaitForCompletionTimeout
6969
}
7070
}
7171

72-
///<summary>Request options for GetStatus <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
73-
public class GetStatusRequestParameters : RequestParameters<GetStatusRequestParameters>
72+
///<summary>Request options for SearchStatus <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
73+
public class EqlSearchStatusRequestParameters : RequestParameters<EqlSearchStatusRequestParameters>
7474
{
7575
public override HttpMethod DefaultHttpMethod => HttpMethod.GET;
7676
public override bool SupportsBody => false;

src/Elasticsearch.Net/ElasticLowLevelClient.Eql.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ public Task<TResponse> GetAsync<TResponse>(string id, GetRequestParameters reque
8686
///<summary>GET on /_eql/search/status/{id} <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
8787
///<param name = "id">The async search ID</param>
8888
///<param name = "requestParameters">Request specific configuration such as querystring parameters &amp; request specific connection settings.</param>
89-
public TResponse GetStatus<TResponse>(string id, GetStatusRequestParameters requestParameters = null)
89+
public TResponse SearchStatus<TResponse>(string id, EqlSearchStatusRequestParameters requestParameters = null)
9090
where TResponse : class, IElasticsearchResponse, new() => DoRequest<TResponse>(GET, Url($"_eql/search/status/{id:id}"), null, RequestParams(requestParameters));
9191
///<summary>GET on /_eql/search/status/{id} <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
9292
///<param name = "id">The async search ID</param>
9393
///<param name = "requestParameters">Request specific configuration such as querystring parameters &amp; request specific connection settings.</param>
9494
[MapsApi("eql.get_status", "id")]
95-
public Task<TResponse> GetStatusAsync<TResponse>(string id, GetStatusRequestParameters requestParameters = null, CancellationToken ctx = default)
95+
public Task<TResponse> SearchStatusAsync<TResponse>(string id, EqlSearchStatusRequestParameters requestParameters = null, CancellationToken ctx = default)
9696
where TResponse : class, IElasticsearchResponse, new() => DoRequestAsync<TResponse>(GET, Url($"_eql/search/status/{id:id}"), ctx, null, RequestParams(requestParameters));
9797
///<summary>POST on /{index}/_eql/search <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
9898
///<param name = "index">A comma-separated list of index names to search; use the special string `_all` or Indices.All to perform the operation on all indices</param>

src/Nest/Descriptors.Eql.cs

+21
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@
4848
// ReSharper disable RedundantNameQualifier
4949
namespace Nest
5050
{
51+
///<summary>Descriptor for SearchStatus <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
52+
public partial class EqlSearchStatusDescriptor : RequestDescriptorBase<EqlSearchStatusDescriptor, EqlSearchStatusRequestParameters, IEqlSearchStatusRequest>, IEqlSearchStatusRequest
53+
{
54+
internal override ApiUrls ApiUrls => ApiUrlsLookups.EqlSearchStatus;
55+
///<summary>/_eql/search/status/{id}</summary>
56+
///<param name = "id">this parameter is required</param>
57+
public EqlSearchStatusDescriptor(Id id): base(r => r.Required("id", id))
58+
{
59+
}
60+
61+
///<summary>Used for serialization purposes, making sure we have a parameterless constructor</summary>
62+
[SerializationConstructor]
63+
protected EqlSearchStatusDescriptor(): base()
64+
{
65+
}
66+
67+
// values part of the url path
68+
Id IEqlSearchStatusRequest.Id => Self.RouteValues.Get<Id>("id");
69+
// Request parameters
70+
}
71+
5172
///<summary>Descriptor for Search <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
5273
public partial class EqlSearchDescriptor<TInferDocument> : RequestDescriptorBase<EqlSearchDescriptor<TInferDocument>, EqlSearchRequestParameters, IEqlSearchRequest<TInferDocument>>, IEqlSearchRequest<TInferDocument>
5374
{

src/Nest/ElasticClient.Eql.cs

+24
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,30 @@ internal EqlNamespace(ElasticClient client): base(client)
5454
{
5555
}
5656

57+
/// <summary>
58+
/// <c>GET</c> request to the <c>eql.get_status</c> API, read more about this API online:
59+
/// <para></para>
60+
/// <a href = "https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</a>
61+
/// </summary>
62+
public EqlSearchStatusResponse SearchStatus(Id id, Func<EqlSearchStatusDescriptor, IEqlSearchStatusRequest> selector = null) => SearchStatus(selector.InvokeOrDefault(new EqlSearchStatusDescriptor(id: id)));
63+
/// <summary>
64+
/// <c>GET</c> request to the <c>eql.get_status</c> API, read more about this API online:
65+
/// <para></para>
66+
/// <a href = "https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</a>
67+
/// </summary>
68+
public Task<EqlSearchStatusResponse> SearchStatusAsync(Id id, Func<EqlSearchStatusDescriptor, IEqlSearchStatusRequest> selector = null, CancellationToken ct = default) => SearchStatusAsync(selector.InvokeOrDefault(new EqlSearchStatusDescriptor(id: id)), ct);
69+
/// <summary>
70+
/// <c>GET</c> request to the <c>eql.get_status</c> API, read more about this API online:
71+
/// <para></para>
72+
/// <a href = "https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</a>
73+
/// </summary>
74+
public EqlSearchStatusResponse SearchStatus(IEqlSearchStatusRequest request) => DoRequest<IEqlSearchStatusRequest, EqlSearchStatusResponse>(request, request.RequestParameters);
75+
/// <summary>
76+
/// <c>GET</c> request to the <c>eql.get_status</c> API, read more about this API online:
77+
/// <para></para>
78+
/// <a href = "https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</a>
79+
/// </summary>
80+
public Task<EqlSearchStatusResponse> SearchStatusAsync(IEqlSearchStatusRequest request, CancellationToken ct = default) => DoRequestAsync<IEqlSearchStatusRequest, EqlSearchStatusResponse>(request, request.RequestParameters, ct);
5781
/// <summary>
5882
/// <c>POST</c> request to the <c>eql.search</c> API, read more about this API online:
5983
/// <para></para>

src/Nest/Requests.Eql.cs

+33
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,39 @@
4949
// ReSharper disable RedundantNameQualifier
5050
namespace Nest
5151
{
52+
[InterfaceDataContract]
53+
public partial interface IEqlSearchStatusRequest : IRequest<EqlSearchStatusRequestParameters>
54+
{
55+
[IgnoreDataMember]
56+
Id Id
57+
{
58+
get;
59+
}
60+
}
61+
62+
///<summary>Request for SearchStatus <para>https://www.elastic.co/guide/en/elasticsearch/reference/current/eql-search-api.html</para></summary>
63+
public partial class EqlSearchStatusRequest : PlainRequestBase<EqlSearchStatusRequestParameters>, IEqlSearchStatusRequest
64+
{
65+
protected IEqlSearchStatusRequest Self => this;
66+
internal override ApiUrls ApiUrls => ApiUrlsLookups.EqlSearchStatus;
67+
///<summary>/_eql/search/status/{id}</summary>
68+
///<param name = "id">this parameter is required</param>
69+
public EqlSearchStatusRequest(Id id): base(r => r.Required("id", id))
70+
{
71+
}
72+
73+
///<summary>Used for serialization purposes, making sure we have a parameterless constructor</summary>
74+
[SerializationConstructor]
75+
protected EqlSearchStatusRequest(): base()
76+
{
77+
}
78+
79+
// values part of the url path
80+
[IgnoreDataMember]
81+
Id IEqlSearchStatusRequest.Id => Self.RouteValues.Get<Id>("id");
82+
// Request parameters
83+
}
84+
5285
[InterfaceDataContract]
5386
public partial interface IEqlSearchRequest : IRequest<EqlSearchRequestParameters>
5487
{

src/Nest/XPack/Eql/Search/EqlSearchResponse.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -59,31 +59,31 @@ public class EqlSearchResponse<TDocument> : ResponseBase where TDocument : class
5959
/// Identifier for the search.
6060
/// </summary>
6161
[DataMember(Name = "id")]
62-
public Id Id { get; internal set; }
62+
public string Id { get; internal set; }
6363

6464
/// <summary>
6565
/// If true, the response does not contain complete search results.
6666
/// </summary>
6767
[DataMember(Name = "is_partial")]
68-
public bool? IsPartial { get; internal set; }
68+
public bool IsPartial { get; internal set; }
6969

7070
/// <summary>
7171
/// If true, the search request is still executing.
7272
/// </summary>
7373
[DataMember(Name = "is_running")]
74-
public bool? IsRunning { get; internal set; }
74+
public bool IsRunning { get; internal set; }
7575

7676
/// <summary>
7777
/// Milliseconds it took Elasticsearch to execute the request.
7878
/// </summary>
7979
[DataMember(Name = "took")]
80-
public int Took { get; internal set; }
80+
public long Took { get; internal set; }
8181

8282
/// <summary>
8383
/// If true, the request timed out before completion.
8484
/// </summary>
8585
[DataMember(Name = "timed_out")]
86-
public bool? TimedOut { get; internal set; }
86+
public bool TimedOut { get; internal set; }
8787

8888
/// <summary>
8989
/// The total number of hits.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
namespace Nest
21+
{
22+
[MapsApi("eql.get_status.json")]
23+
[ReadAs(typeof(EqlSearchStatusRequest))]
24+
public partial interface IEqlSearchStatusRequest { }
25+
26+
public partial class EqlSearchStatusRequest { }
27+
28+
public partial class EqlSearchStatusDescriptor { }
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
using System.Runtime.Serialization;
21+
22+
namespace Nest
23+
{
24+
/// <summary>
25+
/// A response to an EQL get status request.
26+
/// </summary>
27+
public class EqlSearchStatusResponse : ResponseBase
28+
{
29+
/// <summary>
30+
/// For a completed search shows the http status code of the completed search.
31+
/// </summary>
32+
[DataMember(Name = "completion_status")]
33+
public int CompletionStatus { get; internal set; }
34+
35+
/// <summary>
36+
/// For a running search shows a timestamp when the eql search started, in milliseconds since the Unix epoch.
37+
/// </summary>
38+
[DataMember(Name = "expiration_time_in_millis")]
39+
public long ExpirationTimeInMillis { get; internal set; }
40+
41+
/// <summary>
42+
/// Identifier for the search.
43+
/// </summary>
44+
[DataMember(Name = "id")]
45+
public string Id { get; internal set; }
46+
47+
/// <summary>
48+
/// If true, the response does not contain complete search results.
49+
/// </summary>
50+
[DataMember(Name = "is_partial")]
51+
public bool IsPartial { get; internal set; }
52+
53+
/// <summary>
54+
/// If true, the search request is still executing. If false, the search is completed.
55+
/// </summary>
56+
[DataMember(Name = "is_running")]
57+
public bool IsRunning { get; internal set; }
58+
59+
/// <summary>
60+
/// For a running search shows a timestamp when the eql search started, in milliseconds since the Unix epoch.
61+
/// </summary>
62+
[DataMember(Name = "start_time_in_millis")]
63+
public long StartTimeInMillis { get; internal set; }
64+
}
65+
}

src/Nest/_Generated/ApiUrlsLookup.generated.cs

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ internal static class ApiUrlsLookups
106106
internal static ApiUrls EnrichGetPolicy = new ApiUrls(new[]{"_enrich/policy/{name}", "_enrich/policy/"});
107107
internal static ApiUrls EnrichPutPolicy = new ApiUrls(new[]{"_enrich/policy/{name}"});
108108
internal static ApiUrls EnrichStats = new ApiUrls(new[]{"_enrich/_stats"});
109+
internal static ApiUrls EqlSearchStatus = new ApiUrls(new[]{"_eql/search/status/{id}"});
109110
internal static ApiUrls EqlSearch = new ApiUrls(new[]{"{index}/_eql/search"});
110111
internal static ApiUrls NoNamespaceDocumentExists = new ApiUrls(new[]{"{index}/_doc/{id}"});
111112
internal static ApiUrls NoNamespaceSourceExists = new ApiUrls(new[]{"{index}/_source/{id}"});

tests/Tests.Configuration/tests.default.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
# tracked by git).
66

77
# mode either u (unit test), i (integration test) or m (mixed mode)
8-
mode: u
8+
mode: i
99

1010
# the elasticsearch version that should be started
1111
# Can be a snapshot version of sonatype or "latest" to get the latest snapshot of sonatype
12-
elasticsearch_version: latest
12+
elasticsearch_version: 7.13.0-SNAPSHOT
1313
# cluster filter allows you to only run the integration tests of a particular cluster (cluster suffix not needed)
1414
# cluster_filter:
1515
# whether we want to forcefully reseed on the node, if you are starting the tests with a node already running
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
using System.Threading.Tasks;
21+
using Elastic.Elasticsearch.Xunit.XunitPlumbing;
22+
using FluentAssertions;
23+
using Nest;
24+
using Tests.Core.Extensions;
25+
using Tests.Core.ManagedElasticsearch.Clusters;
26+
using Tests.Domain;
27+
using Tests.Framework.EndpointTests;
28+
using Tests.Framework.EndpointTests.TestState;
29+
30+
namespace Tests.XPack.Eql
31+
{
32+
[SkipVersion("<7.11.0", "GA in 7.11.0")]
33+
public class EqlSearchApiCoordinatedTests : CoordinatedIntegrationTestBase<TimeSeriesCluster>
34+
{
35+
private const string SubmitStep = nameof(SubmitStep);
36+
private const string StatusStep = nameof(StatusStep);
37+
38+
public EqlSearchApiCoordinatedTests(TimeSeriesCluster cluster, EndpointUsage usage) : base(new CoordinatedUsage(cluster, usage, testOnlyOne: true)
39+
{
40+
{SubmitStep, u =>
41+
u.Calls<EqlSearchDescriptor<Log>, EqlSearchRequest<Log>, IEqlSearchRequest, EqlSearchResponse<Log>>(
42+
v => new EqlSearchRequest<Log>
43+
{
44+
Query = "any where true",
45+
KeepOnCompletion = true,
46+
TimestampField = Infer.Field<Log>(f => f.Timestamp),
47+
WaitForCompletionTimeout = "1nanos"
48+
},
49+
(v, d) => d
50+
.Query("any where true")
51+
.KeepOnCompletion()
52+
.TimestampField(Infer.Field<Log>(f => f.Timestamp))
53+
.WaitForCompletionTimeout("1nanos"),
54+
(v, c, f) => c.Eql.Search(f),
55+
(v, c, f) => c.Eql.SearchAsync(f),
56+
(v, c, r) => c.Eql.Search<Log>(r),
57+
(v, c, r) => c.Eql.SearchAsync<Log>(r),
58+
onResponse: (r, values) => values.ExtendedValue("id", r.Id)
59+
)
60+
},
61+
{StatusStep, u =>
62+
u.Calls<EqlSearchStatusDescriptor, EqlSearchStatusRequest, IEqlSearchStatusRequest, EqlSearchStatusResponse>(
63+
v => new EqlSearchStatusRequest(v),
64+
(v, d) => d,
65+
(v, c, f) => c.Eql.SearchStatus(v, f),
66+
(v, c, f) => c.Eql.SearchStatusAsync(v, f),
67+
(v, c, r) => c.Eql.SearchStatus(r),
68+
(v, c, r) => c.Eql.SearchStatusAsync(r),
69+
uniqueValueSelector: values => values.ExtendedValue<string>("id")
70+
)
71+
}
72+
}) { }
73+
74+
[I] public async Task EqlSearchResponse() => await Assert<EqlSearchResponse<Log>>(SubmitStep, r =>
75+
{
76+
r.ShouldBeValid();
77+
r.Id.Should().NotBeNullOrEmpty();
78+
r.IsPartial.Should().BeTrue();
79+
r.IsRunning.Should().BeTrue();
80+
r.TimedOut.Should().BeFalse();
81+
});
82+
83+
[I] public async Task EqlSearchStatusResponse() => await Assert<EqlSearchStatusResponse>(StatusStep, r =>
84+
{
85+
r.ShouldBeValid();
86+
r.Id.Should().NotBeNullOrEmpty();
87+
r.IsPartial.Should().BeTrue();
88+
r.IsRunning.Should().BeTrue();
89+
r.ExpirationTimeInMillis.Should().BeGreaterThan(0);
90+
r.StartTimeInMillis.Should().BeGreaterThan(0);
91+
});
92+
}
93+
}

0 commit comments

Comments
 (0)