Skip to content

Commit

Permalink
HLRC ML Add Event To Calendar API (#35704)
Browse files Browse the repository at this point in the history
* HLRC: ML Adding Post event to calendar api

* Fixing tests and serialization

* removing unused import
  • Loading branch information
benwtrent authored Nov 20, 2018
1 parent d707838 commit 7657e6d
Show file tree
Hide file tree
Showing 13 changed files with 525 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,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 @@ -538,6 +539,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 @@ -60,6 +60,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 @@ -1384,6 +1386,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 @@ -46,6 +46,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 @@ -61,6 +62,8 @@
import org.elasticsearch.client.ml.UpdateModelSnapshotRequest;
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 @@ -73,6 +76,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 @@ -83,6 +87,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 @@ -586,6 +591,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

0 comments on commit 7657e6d

Please sign in to comment.