Skip to content

HLRC ML Add Event To Calendar API #35704

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.elasticsearch.client.ml.GetOverallBucketsRequest;
import org.elasticsearch.client.ml.GetRecordsRequest;
import org.elasticsearch.client.ml.OpenJobRequest;
import org.elasticsearch.client.ml.PostCalendarEventRequest;
import org.elasticsearch.client.ml.PostDataRequest;
import org.elasticsearch.client.ml.PreviewDatafeedRequest;
import org.elasticsearch.client.ml.PutCalendarJobRequest;
Expand Down Expand Up @@ -509,6 +510,21 @@ static Request deleteCalendar(DeleteCalendarRequest deleteCalendarRequest) {
return request;
}

static Request postCalendarEvents(PostCalendarEventRequest postCalendarEventRequest) throws IOException {
String endpoint = new EndpointBuilder()
.addPathPartAsIs("_xpack")
.addPathPartAsIs("ml")
.addPathPartAsIs("calendars")
.addPathPart(postCalendarEventRequest.getCalendarId())
.addPathPartAsIs("events")
.build();
Request request = new Request(HttpPost.METHOD_NAME, endpoint);
request.setEntity(createEntity(postCalendarEventRequest,
REQUEST_BODY_CONTENT_TYPE,
PostCalendarEventRequest.EXCLUDE_CALENDAR_ID_PARAMS));
return request;
}

static Request putFilter(PutFilterRequest putFilterRequest) throws IOException {
String endpoint = new EndpointBuilder()
.addPathPartAsIs("_xpack")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
import org.elasticsearch.client.ml.GetRecordsResponse;
import org.elasticsearch.client.ml.OpenJobRequest;
import org.elasticsearch.client.ml.OpenJobResponse;
import org.elasticsearch.client.ml.PostCalendarEventRequest;
import org.elasticsearch.client.ml.PostCalendarEventResponse;
import org.elasticsearch.client.ml.PostDataRequest;
import org.elasticsearch.client.ml.PostDataResponse;
import org.elasticsearch.client.ml.PreviewDatafeedRequest;
Expand Down Expand Up @@ -1299,6 +1301,47 @@ public void deleteCalendarAsync(DeleteCalendarRequest request, RequestOptions op
Collections.emptySet());
}

/**
* Creates new events for a a machine learning calendar
* <p>
* For additional info
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-post-calendar-event.html">
* Add Events to Calendar API</a>
*
* @param request The request
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return The {@link PostCalendarEventRequest} containing the scheduled events
* @throws IOException when there is a serialization issue sending the request or receiving the response
*/
public PostCalendarEventResponse postCalendarEvent(PostCalendarEventRequest request, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(request,
MLRequestConverters::postCalendarEvents,
options,
PostCalendarEventResponse::fromXContent,
Collections.emptySet());
}

/**
* Creates new events for a a machine learning calendar asynchronously, notifies the listener on completion
* <p>
* For additional info
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-post-calendar-event.html">
* Add Events to Calendar API</a>
*
* @param request The request
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @param listener Listener to be notified upon request completion
*/
public void postCalendarEventAsync(PostCalendarEventRequest request, RequestOptions options,
ActionListener<PostCalendarEventResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(request,
MLRequestConverters::postCalendarEvents,
options,
PostCalendarEventResponse::fromXContent,
listener,
Collections.emptySet());
}

/**
* Creates a new Machine Learning Filter
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,12 @@ static Request deleteScript(DeleteStoredScriptRequest deleteStoredScriptRequest)
}

static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException {
BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef();
return createEntity(toXContent, xContentType, ToXContent.EMPTY_PARAMS);
}

static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType, ToXContent.Params toXContentParams)
throws IOException {
BytesRef source = XContentHelper.toXContent(toXContent, xContentType, toXContentParams, false).toBytesRef();
return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.ml;

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.client.ml.calendars.Calendar;
import org.elasticsearch.client.ml.calendars.ScheduledEvent;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
* Request to add a ScheduledEvent to a Machine Learning calendar
*/
public class PostCalendarEventRequest extends ActionRequest implements ToXContentObject {

private final String calendarId;
private final List<ScheduledEvent> scheduledEvents;

public static final String INCLUDE_CALENDAR_ID_KEY = "include_calendar_id";
public static final ParseField EVENTS = new ParseField("events");

@SuppressWarnings("unchecked")
public static final ConstructingObjectParser<PostCalendarEventRequest, Void> PARSER =
new ConstructingObjectParser<>("post_calendar_event_request",
a -> new PostCalendarEventRequest((String)a[0], (List<ScheduledEvent>)a[1]));

static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), Calendar.ID);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> ScheduledEvent.PARSER.apply(p, null), EVENTS);
}
public static final MapParams EXCLUDE_CALENDAR_ID_PARAMS =
new MapParams(Collections.singletonMap(INCLUDE_CALENDAR_ID_KEY, Boolean.toString(false)));

/**
* Create a new PostCalendarEventRequest with an existing non-null calendarId and a list of Scheduled events
*
* @param calendarId The ID of the calendar, must be non-null
* @param scheduledEvents The non-null, non-empty, list of {@link ScheduledEvent} objects to add to the calendar
*/
public PostCalendarEventRequest(String calendarId, List<ScheduledEvent> scheduledEvents) {
this.calendarId = Objects.requireNonNull(calendarId, "[calendar_id] must not be null.");
this.scheduledEvents = Objects.requireNonNull(scheduledEvents, "[events] must not be null.");
if (scheduledEvents.isEmpty()) {
throw new IllegalArgumentException("At least 1 event is required");
}
}

public String getCalendarId() {
return calendarId;
}

public List<ScheduledEvent> getScheduledEvents() {
return scheduledEvents;
}

@Override
public ActionRequestValidationException validate() {
return null;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (params.paramAsBoolean(INCLUDE_CALENDAR_ID_KEY, true)) {
builder.field(Calendar.ID.getPreferredName(), calendarId);
}
builder.field(EVENTS.getPreferredName(), scheduledEvents);
builder.endObject();
return builder;
}

@Override
public int hashCode() {
return Objects.hash(calendarId, scheduledEvents);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PostCalendarEventRequest other = (PostCalendarEventRequest) obj;
return Objects.equals(calendarId, other.calendarId) && Objects.equals(scheduledEvents, other.scheduledEvents);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.ml;

import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.client.ml.calendars.ScheduledEvent;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;

import java.io.IOException;
import java.util.List;
import java.util.Objects;

/**
* Response to adding ScheduledEvent(s) to a Machine Learning calendar
*/
public class PostCalendarEventResponse extends ActionResponse implements ToXContentObject {

private final List<ScheduledEvent> scheduledEvents;
public static final ParseField EVENTS = new ParseField("events");

@SuppressWarnings("unchecked")
public static final ConstructingObjectParser<PostCalendarEventResponse, Void> PARSER =
new ConstructingObjectParser<>("post_calendar_event_response",
true,
a -> new PostCalendarEventResponse((List<ScheduledEvent>)a[0]));

static {
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(),
(p, c) -> ScheduledEvent.PARSER.apply(p, null), EVENTS);
}

public static PostCalendarEventResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}

/**
* Create a new PostCalendarEventResponse containing the scheduled Events
*
* @param scheduledEvents The list of {@link ScheduledEvent} objects
*/
public PostCalendarEventResponse(List<ScheduledEvent> scheduledEvents) {
this.scheduledEvents = scheduledEvents;
}

public List<ScheduledEvent> getScheduledEvents() {
return scheduledEvents;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(EVENTS.getPreferredName(), scheduledEvents);
builder.endObject();
return builder;
}

@Override
public int hashCode(){
return Objects.hash(scheduledEvents);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PostCalendarEventResponse other = (PostCalendarEventResponse) obj;
return Objects.equals(scheduledEvents, other.scheduledEvents);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.elasticsearch.client.ml.GetOverallBucketsRequest;
import org.elasticsearch.client.ml.GetRecordsRequest;
import org.elasticsearch.client.ml.OpenJobRequest;
import org.elasticsearch.client.ml.PostCalendarEventRequest;
import org.elasticsearch.client.ml.PostDataRequest;
import org.elasticsearch.client.ml.PreviewDatafeedRequest;
import org.elasticsearch.client.ml.PutCalendarJobRequest;
Expand All @@ -59,6 +60,8 @@
import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.client.ml.calendars.Calendar;
import org.elasticsearch.client.ml.calendars.CalendarTests;
import org.elasticsearch.client.ml.calendars.ScheduledEvent;
import org.elasticsearch.client.ml.calendars.ScheduledEventTests;
import org.elasticsearch.client.ml.datafeed.DatafeedConfig;
import org.elasticsearch.client.ml.datafeed.DatafeedConfigTests;
import org.elasticsearch.client.ml.job.config.AnalysisConfig;
Expand All @@ -71,6 +74,7 @@
import org.elasticsearch.client.ml.job.util.PageParams;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
Expand All @@ -81,6 +85,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.hamcrest.Matchers.equalTo;
Expand Down Expand Up @@ -558,6 +563,22 @@ public void testDeleteCalendar() {
assertEquals("/_xpack/ml/calendars/" + deleteCalendarRequest.getCalendarId(), request.getEndpoint());
}

public void testPostCalendarEvent() throws Exception {
String calendarId = randomAlphaOfLength(10);
List<ScheduledEvent> events = Arrays.asList(ScheduledEventTests.testInstance(),
ScheduledEventTests.testInstance(),
ScheduledEventTests.testInstance());
PostCalendarEventRequest postCalendarEventRequest = new PostCalendarEventRequest(calendarId, events);

Request request = MLRequestConverters.postCalendarEvents(postCalendarEventRequest);
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
assertEquals("/_xpack/ml/calendars/" + calendarId + "/events", request.getEndpoint());

XContentBuilder builder = JsonXContent.contentBuilder();
builder = postCalendarEventRequest.toXContent(builder, PostCalendarEventRequest.EXCLUDE_CALENDAR_ID_PARAMS);
assertEquals(Strings.toString(builder), requestEntityToString(request));
}

public void testPutFilter() throws IOException {
MlFilter filter = MlFilterTests.createRandomBuilder("foo").build();
PutFilterRequest putFilterRequest = new PutFilterRequest(filter);
Expand Down
Loading