Skip to content

Commit 87d1752

Browse files
committed
HLRC: ML Add Job to Calendar API (#35666)
1 parent 68d1766 commit 87d1752

File tree

9 files changed

+309
-0
lines changed

9 files changed

+309
-0
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.elasticsearch.client.ml.OpenJobRequest;
5252
import org.elasticsearch.client.ml.PostDataRequest;
5353
import org.elasticsearch.client.ml.PreviewDatafeedRequest;
54+
import org.elasticsearch.client.ml.PutCalendarJobRequest;
5455
import org.elasticsearch.client.ml.PutCalendarRequest;
5556
import org.elasticsearch.client.ml.PutDatafeedRequest;
5657
import org.elasticsearch.client.ml.PutFilterRequest;
@@ -485,6 +486,18 @@ static Request getCalendars(GetCalendarsRequest getCalendarsRequest) throws IOEx
485486
return request;
486487
}
487488

489+
static Request putCalendarJob(PutCalendarJobRequest putCalendarJobRequest) {
490+
String endpoint = new EndpointBuilder()
491+
.addPathPartAsIs("_xpack")
492+
.addPathPartAsIs("ml")
493+
.addPathPartAsIs("calendars")
494+
.addPathPart(putCalendarJobRequest.getCalendarId())
495+
.addPathPartAsIs("jobs")
496+
.addPathPart(Strings.collectionToCommaDelimitedString(putCalendarJobRequest.getJobIds()))
497+
.build();
498+
return new Request(HttpPut.METHOD_NAME, endpoint);
499+
}
500+
488501
static Request deleteCalendar(DeleteCalendarRequest deleteCalendarRequest) {
489502
String endpoint = new EndpointBuilder()
490503
.addPathPartAsIs("_xpack")

client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import org.elasticsearch.client.ml.PostDataResponse;
6464
import org.elasticsearch.client.ml.PreviewDatafeedRequest;
6565
import org.elasticsearch.client.ml.PreviewDatafeedResponse;
66+
import org.elasticsearch.client.ml.PutCalendarJobRequest;
6667
import org.elasticsearch.client.ml.PutCalendarRequest;
6768
import org.elasticsearch.client.ml.PutCalendarResponse;
6869
import org.elasticsearch.client.ml.PutDatafeedRequest;
@@ -1217,6 +1218,47 @@ public void putCalendarAsync(PutCalendarRequest request, RequestOptions options,
12171218
Collections.emptySet());
12181219
}
12191220

1221+
/**
1222+
* Adds Machine Learning Job(s) to a calendar
1223+
* <p>
1224+
* For additional info
1225+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-calendar-job.html">
1226+
* ML Put calendar job documentation</a>
1227+
*
1228+
* @param request The request
1229+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
1230+
* @return The {@link PutCalendarResponse} containing the updated calendar
1231+
* @throws IOException when there is a serialization issue sending the request or receiving the response
1232+
*/
1233+
public PutCalendarResponse putCalendarJob(PutCalendarJobRequest request, RequestOptions options) throws IOException {
1234+
return restHighLevelClient.performRequestAndParseEntity(request,
1235+
MLRequestConverters::putCalendarJob,
1236+
options,
1237+
PutCalendarResponse::fromXContent,
1238+
Collections.emptySet());
1239+
}
1240+
1241+
/**
1242+
* Adds Machine Learning Job(s) to a calendar, notifies listener when completed
1243+
* <p>
1244+
* For additional info
1245+
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-calendar-job.html">
1246+
* ML Put calendar job documentation</a>
1247+
*
1248+
* @param request The request
1249+
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
1250+
* @param listener Listener to be notified upon request completion
1251+
*/
1252+
public void putCalendarJobAsync(PutCalendarJobRequest request, RequestOptions options, ActionListener<PutCalendarResponse> listener) {
1253+
restHighLevelClient.performRequestAsyncAndParseEntity(request,
1254+
MLRequestConverters::putCalendarJob,
1255+
options,
1256+
PutCalendarResponse::fromXContent,
1257+
listener,
1258+
Collections.emptySet());
1259+
}
1260+
1261+
12201262
/**
12211263
* Deletes the given Machine Learning Calendar
12221264
* <p>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Licensed to Elasticsearch 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 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+
package org.elasticsearch.client.ml;
20+
21+
import org.elasticsearch.action.ActionRequest;
22+
import org.elasticsearch.action.ActionRequestValidationException;
23+
24+
import java.security.InvalidParameterException;
25+
import java.util.Arrays;
26+
import java.util.List;
27+
import java.util.Objects;
28+
29+
/**
30+
* Request class for adding Machine Learning Jobs to an existing calendar
31+
*/
32+
public class PutCalendarJobRequest extends ActionRequest {
33+
34+
private final List<String> jobIds;
35+
private final String calendarId;
36+
37+
/**
38+
* Create a new request referencing an existing Calendar and which JobIds to add
39+
* to it.
40+
*
41+
* @param calendarId The non-null ID of the calendar
42+
* @param jobIds JobIds to add to the calendar, cannot be empty, or contain null values.
43+
* It can be a list of jobs or groups.
44+
*/
45+
public PutCalendarJobRequest(String calendarId, String... jobIds) {
46+
this.calendarId = Objects.requireNonNull(calendarId, "[calendar_id] must not be null.");
47+
if (jobIds.length == 0) {
48+
throw new InvalidParameterException("jobIds must not be empty.");
49+
}
50+
if (Arrays.stream(jobIds).anyMatch(Objects::isNull)) {
51+
throw new NullPointerException("jobIds must not contain null values.");
52+
}
53+
this.jobIds = Arrays.asList(jobIds);
54+
}
55+
56+
public List<String> getJobIds() {
57+
return jobIds;
58+
}
59+
60+
public String getCalendarId() {
61+
return calendarId;
62+
}
63+
64+
@Override
65+
public ActionRequestValidationException validate() {
66+
return null;
67+
}
68+
69+
@Override
70+
public int hashCode() {
71+
return Objects.hash(jobIds, calendarId);
72+
}
73+
74+
@Override
75+
public boolean equals(Object other) {
76+
if (this == other) {
77+
return true;
78+
}
79+
80+
if (other == null || getClass() != other.getClass()) {
81+
return false;
82+
}
83+
84+
PutCalendarJobRequest that = (PutCalendarJobRequest) other;
85+
return Objects.equals(jobIds, that.jobIds) &&
86+
Objects.equals(calendarId, that.calendarId);
87+
}
88+
}

client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.elasticsearch.client.ml.OpenJobRequest;
4848
import org.elasticsearch.client.ml.PostDataRequest;
4949
import org.elasticsearch.client.ml.PreviewDatafeedRequest;
50+
import org.elasticsearch.client.ml.PutCalendarJobRequest;
5051
import org.elasticsearch.client.ml.PutCalendarRequest;
5152
import org.elasticsearch.client.ml.PutDatafeedRequest;
5253
import org.elasticsearch.client.ml.PutFilterRequest;
@@ -518,6 +519,16 @@ public void testPutCalendar() throws IOException {
518519
}
519520
}
520521

522+
public void testPutCalendarJob() throws IOException {
523+
String calendarId = randomAlphaOfLength(10);
524+
String job1 = randomAlphaOfLength(5);
525+
String job2 = randomAlphaOfLength(5);
526+
PutCalendarJobRequest putCalendarJobRequest = new PutCalendarJobRequest(calendarId, job1, job2);
527+
Request request = MLRequestConverters.putCalendarJob(putCalendarJobRequest);
528+
assertEquals(HttpPut.METHOD_NAME, request.getMethod());
529+
assertEquals("/_xpack/ml/calendars/" + calendarId + "/jobs/" + job1 + "," + job2, request.getEndpoint());
530+
}
531+
521532
public void testGetCalendars() throws IOException {
522533
GetCalendarsRequest getCalendarsRequest = new GetCalendarsRequest();
523534
String expectedEndpoint = "/_xpack/ml/calendars";

client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import org.elasticsearch.client.ml.PostDataResponse;
5959
import org.elasticsearch.client.ml.PreviewDatafeedRequest;
6060
import org.elasticsearch.client.ml.PreviewDatafeedResponse;
61+
import org.elasticsearch.client.ml.PutCalendarJobRequest;
6162
import org.elasticsearch.client.ml.PutCalendarRequest;
6263
import org.elasticsearch.client.ml.PutCalendarResponse;
6364
import org.elasticsearch.client.ml.PutDatafeedRequest;
@@ -826,6 +827,26 @@ public void testPutCalendar() throws IOException {
826827
assertThat(putCalendarResponse.getCalendar(), equalTo(calendar));
827828
}
828829

830+
public void testPutCalendarJob() throws IOException {
831+
Calendar calendar = new Calendar("put-calendar-job-id", Collections.singletonList("put-calendar-job-0"), null);
832+
MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
833+
PutCalendarResponse putCalendarResponse =
834+
machineLearningClient.putCalendar(new PutCalendarRequest(calendar), RequestOptions.DEFAULT);
835+
836+
assertThat(putCalendarResponse.getCalendar().getJobIds(), containsInAnyOrder( "put-calendar-job-0"));
837+
838+
String jobId1 = "put-calendar-job-1";
839+
String jobId2 = "put-calendar-job-2";
840+
841+
PutCalendarJobRequest putCalendarJobRequest = new PutCalendarJobRequest(calendar.getId(), jobId1, jobId2);
842+
843+
putCalendarResponse = execute(putCalendarJobRequest,
844+
machineLearningClient::putCalendarJob,
845+
machineLearningClient::putCalendarJobAsync);
846+
847+
assertThat(putCalendarResponse.getCalendar().getJobIds(), containsInAnyOrder(jobId1, jobId2, "put-calendar-job-0"));
848+
}
849+
829850
public void testGetCalendars() throws Exception {
830851
Calendar calendar1 = CalendarTests.testInstance();
831852
Calendar calendar2 = CalendarTests.testInstance();

client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.elasticsearch.client.ml.PostDataResponse;
7777
import org.elasticsearch.client.ml.PreviewDatafeedRequest;
7878
import org.elasticsearch.client.ml.PreviewDatafeedResponse;
79+
import org.elasticsearch.client.ml.PutCalendarJobRequest;
7980
import org.elasticsearch.client.ml.PutCalendarRequest;
8081
import org.elasticsearch.client.ml.PutCalendarResponse;
8182
import org.elasticsearch.client.ml.PutDatafeedRequest;
@@ -2083,6 +2084,58 @@ public void onFailure(Exception e) {
20832084
assertTrue(latch.await(30L, TimeUnit.SECONDS));
20842085
}
20852086

2087+
public void testPutCalendarJob() throws IOException, InterruptedException {
2088+
RestHighLevelClient client = highLevelClient();
2089+
2090+
Calendar calendar = new Calendar("holidays", Collections.singletonList("job_1"), "A calendar for public holidays");
2091+
PutCalendarRequest putRequest = new PutCalendarRequest(calendar);
2092+
client.machineLearning().putCalendar(putRequest, RequestOptions.DEFAULT);
2093+
{
2094+
// tag::put-calendar-job-request
2095+
PutCalendarJobRequest request = new PutCalendarJobRequest("holidays", // <1>
2096+
"job_2", "job_group_1"); // <2>
2097+
// end::put-calendar-job-request
2098+
2099+
// tag::put-calendar-job-execute
2100+
PutCalendarResponse response = client.machineLearning().putCalendarJob(request, RequestOptions.DEFAULT);
2101+
// end::put-calendar-job-execute
2102+
2103+
// tag::put-calendar-job-response
2104+
Calendar updatedCalendar = response.getCalendar(); // <1>
2105+
// end::put-calendar-job-response
2106+
2107+
assertThat(updatedCalendar.getJobIds(), containsInAnyOrder("job_1", "job_2", "job_group_1"));
2108+
}
2109+
{
2110+
PutCalendarJobRequest request = new PutCalendarJobRequest("holidays", "job_4");
2111+
2112+
// tag::put-calendar-job-execute-listener
2113+
ActionListener<PutCalendarResponse> listener =
2114+
new ActionListener<PutCalendarResponse>() {
2115+
@Override
2116+
public void onResponse(PutCalendarResponse putCalendarsResponse) {
2117+
// <1>
2118+
}
2119+
2120+
@Override
2121+
public void onFailure(Exception e) {
2122+
// <2>
2123+
}
2124+
};
2125+
// end::put-calendar-job-execute-listener
2126+
2127+
// Replace the empty listener by a blocking listener in test
2128+
final CountDownLatch latch = new CountDownLatch(1);
2129+
listener = new LatchedActionListener<>(listener, latch);
2130+
2131+
// tag::put-calendar-job-execute-async
2132+
client.machineLearning().putCalendarJobAsync(request, RequestOptions.DEFAULT, listener); // <1>
2133+
// end::put-calendar-job-execute-async
2134+
2135+
assertTrue(latch.await(30L, TimeUnit.SECONDS));
2136+
}
2137+
}
2138+
20862139
public void testGetCalendar() throws IOException, InterruptedException {
20872140
RestHighLevelClient client = highLevelClient();
20882141

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Licensed to Elasticsearch 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 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+
package org.elasticsearch.client.ml;
21+
22+
import org.elasticsearch.test.ESTestCase;
23+
24+
public class PutCalendarJobRequestTests extends ESTestCase {
25+
26+
public void testWithNullId() {
27+
NullPointerException ex = expectThrows(NullPointerException.class,
28+
() -> new PutCalendarJobRequest(null, "job1"));
29+
assertEquals("[calendar_id] must not be null.", ex.getMessage());
30+
}
31+
32+
public void testSetJobIds() {
33+
String calendarId = randomAlphaOfLength(10);
34+
35+
NullPointerException ex = expectThrows(NullPointerException.class,
36+
() ->new PutCalendarJobRequest(calendarId, "job1", null));
37+
assertEquals("jobIds must not contain null values.", ex.getMessage());
38+
39+
IllegalArgumentException illegalArgumentException =
40+
expectThrows(IllegalArgumentException.class, () -> new PutCalendarJobRequest(calendarId));
41+
assertEquals("jobIds must not be empty.", illegalArgumentException.getMessage());
42+
}
43+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--
2+
:api: put-calendar-job
3+
:request: PutCalendarJobRequest
4+
:response: PutCalendarResponse
5+
--
6+
[id="{upid}-{api}"]
7+
=== Put Calendar Job API
8+
Adds {ml} jobs to an existing {ml} calendar.
9+
The API accepts a +{request}+ and responds
10+
with a +{response}+ object.
11+
12+
[id="{upid}-{api}-request"]
13+
==== Put Calendar Job Request
14+
15+
A +{request}+ is constructed referencing a non-null
16+
calendar ID, and JobIDs to which to add to the calendar
17+
18+
["source","java",subs="attributes,callouts,macros"]
19+
--------------------------------------------------
20+
include-tagged::{doc-tests-file}[{api}-request]
21+
--------------------------------------------------
22+
<1> The ID of the calendar to which to add the jobs
23+
<2> The JobIds to add to the calendar
24+
25+
[id="{upid}-{api}-response"]
26+
==== Put Calendar Response
27+
28+
The returned +{response}+ contains the updated Calendar:
29+
30+
["source","java",subs="attributes,callouts,macros"]
31+
--------------------------------------------------
32+
include-tagged::{doc-tests-file}[{api}-response]
33+
--------------------------------------------------
34+
<1> The updated Calendar
35+
36+
include::../execution.asciidoc[]

docs/java-rest/high-level/supported-apis.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ The Java High Level REST Client supports the following Machine Learning APIs:
263263
* <<{upid}-get-categories>>
264264
* <<{upid}-get-calendars>>
265265
* <<{upid}-put-calendar>>
266+
* <<{upid}-put-calendar-job>>
266267
* <<{upid}-delete-calendar>>
267268
* <<{upid}-put-filter>>
268269
* <<{upid}-get-filters>>
@@ -297,6 +298,7 @@ include::ml/get-influencers.asciidoc[]
297298
include::ml/get-categories.asciidoc[]
298299
include::ml/get-calendars.asciidoc[]
299300
include::ml/put-calendar.asciidoc[]
301+
include::ml/put-calendar-job.asciidoc[]
300302
include::ml/delete-calendar.asciidoc[]
301303
include::ml/put-filter.asciidoc[]
302304
include::ml/get-model-snapshots.asciidoc[]

0 commit comments

Comments
 (0)