From 9543992d8e1c15215055942a15b670d7b0213d75 Mon Sep 17 00:00:00 2001 From: David Kyle Date: Tue, 18 Sep 2018 11:51:11 +0100 Subject: [PATCH] HLRC: Get ML calendars (#33760) --- .../client/MLRequestConverters.java | 17 ++- .../client/MachineLearningClient.java | 40 +++++++ .../client/ml/GetCalendarsRequest.java | 104 ++++++++++++++++++ .../client/ml/GetCalendarsResponse.java | 86 +++++++++++++++ .../client/ml/job/results/AnomalyRecord.java | 16 +-- .../client/ml/job/results/Bucket.java | 15 +-- .../ml/job/results/BucketInfluencer.java | 16 +-- .../client/ml/job/results/Influencer.java | 16 +-- .../client/ml/job/results/OverallBucket.java | 16 +-- .../client/ml/job/results/Result.java | 1 - .../client/MLRequestConvertersTests.java | 25 ++++- .../client/MachineLearningIT.java | 27 ++++- .../MlClientDocumentationIT.java | 65 +++++++++++ .../client/ml/GetCalendarsRequestTests.java | 46 ++++++++ .../client/ml/GetCalendarsResponseTests.java | 52 +++++++++ .../high-level/ml/get-calendars.asciidoc | 83 ++++++++++++++ .../high-level/ml/put-calendar.asciidoc | 2 +- .../high-level/supported-apis.asciidoc | 2 + 18 files changed, 563 insertions(+), 66 deletions(-) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsResponse.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsRequestTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsResponseTests.java create mode 100644 docs/java-rest/high-level/ml/get-calendars.asciidoc diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java index 1a681822eca..bc2ff7b17d5 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java @@ -34,6 +34,7 @@ import org.elasticsearch.client.ml.DeleteJobRequest; import org.elasticsearch.client.ml.FlushJobRequest; import org.elasticsearch.client.ml.ForecastJobRequest; import org.elasticsearch.client.ml.GetBucketsRequest; +import org.elasticsearch.client.ml.GetCalendarsRequest; import org.elasticsearch.client.ml.GetCategoriesRequest; import org.elasticsearch.client.ml.GetDatafeedRequest; import org.elasticsearch.client.ml.GetInfluencersRequest; @@ -229,7 +230,7 @@ final class MLRequestConverters { return request; } - static Request deleteForecast(DeleteForecastRequest deleteForecastRequest) throws IOException { + static Request deleteForecast(DeleteForecastRequest deleteForecastRequest) { String endpoint = new EndpointBuilder() .addPathPartAsIs("_xpack") .addPathPartAsIs("ml") @@ -305,7 +306,7 @@ final class MLRequestConverters { return request; } - static Request postData(PostDataRequest postDataRequest) throws IOException { + static Request postData(PostDataRequest postDataRequest) { String endpoint = new EndpointBuilder() .addPathPartAsIs("_xpack") .addPathPartAsIs("ml") @@ -359,4 +360,16 @@ final class MLRequestConverters { request.setEntity(createEntity(putCalendarRequest, REQUEST_BODY_CONTENT_TYPE)); return request; } + + static Request getCalendars(GetCalendarsRequest getCalendarsRequest) throws IOException { + String endpoint = new EndpointBuilder() + .addPathPartAsIs("_xpack") + .addPathPartAsIs("ml") + .addPathPartAsIs("calendars") + .addPathPart(getCalendarsRequest.getCalendarId()) + .build(); + Request request = new Request(HttpGet.METHOD_NAME, endpoint); + request.setEntity(createEntity(getCalendarsRequest, REQUEST_BODY_CONTENT_TYPE)); + return request; + } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java index caaf1326dbd..5edb5115d85 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java @@ -31,6 +31,8 @@ import org.elasticsearch.client.ml.ForecastJobRequest; import org.elasticsearch.client.ml.ForecastJobResponse; import org.elasticsearch.client.ml.GetBucketsRequest; import org.elasticsearch.client.ml.GetBucketsResponse; +import org.elasticsearch.client.ml.GetCalendarsRequest; +import org.elasticsearch.client.ml.GetCalendarsResponse; import org.elasticsearch.client.ml.GetCategoriesRequest; import org.elasticsearch.client.ml.GetCategoriesResponse; import org.elasticsearch.client.ml.GetDatafeedRequest; @@ -792,6 +794,44 @@ public final class MachineLearningClient { Collections.emptySet()); } + /** + * Gets a single or multiple calendars. + *

+ * For additional info + * see ML GET calendars documentation + * + * @param request The calendars request + * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return {@link GetCalendarsResponse} response object containing the {@link org.elasticsearch.client.ml.calendars.Calendar} + * objects and the number of calendars found + */ + public GetCalendarsResponse getCalendars(GetCalendarsRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, + MLRequestConverters::getCalendars, + options, + GetCalendarsResponse::fromXContent, + Collections.emptySet()); + } + + /** + * Gets a single or multiple calendars, notifies listener once the requested records are retrieved. + *

+ * For additional info + * see ML GET calendars documentation + * + * @param request The calendars 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 getCalendarsAsync(GetCalendarsRequest request, RequestOptions options, ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, + MLRequestConverters::getCalendars, + options, + GetCalendarsResponse::fromXContent, + listener, + Collections.emptySet()); + } + /** * Gets the influencers for a Machine Learning Job. *

diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsRequest.java new file mode 100644 index 00000000000..322efc19927 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsRequest.java @@ -0,0 +1,104 @@ +/* + * 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.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import org.elasticsearch.client.ml.job.util.PageParams; + +import java.io.IOException; +import java.util.Objects; + +public class GetCalendarsRequest extends ActionRequest implements ToXContentObject { + + public static final ObjectParser PARSER = + new ObjectParser<>("get_calendars_request", GetCalendarsRequest::new); + + static { + PARSER.declareString(GetCalendarsRequest::setCalendarId, Calendar.ID); + PARSER.declareObject(GetCalendarsRequest::setPageParams, PageParams.PARSER, PageParams.PAGE); + } + + private String calendarId; + private PageParams pageParams; + + public GetCalendarsRequest() { + } + + public GetCalendarsRequest(String calendarId) { + this.calendarId = calendarId; + } + + public String getCalendarId() { + return calendarId; + } + + public void setCalendarId(String calendarId) { + this.calendarId = calendarId; + } + + public PageParams getPageParams() { + return pageParams; + } + + public void setPageParams(PageParams pageParams) { + this.pageParams = pageParams; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (calendarId != null) { + builder.field(Calendar.ID.getPreferredName(), calendarId); + } + if (pageParams != null) { + builder.field(PageParams.PAGE.getPreferredName(), pageParams); + } + builder.endObject(); + return builder; + } + + @Override + public int hashCode() { + return Objects.hash(calendarId, pageParams); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GetCalendarsRequest other = (GetCalendarsRequest) obj; + return Objects.equals(calendarId, other.calendarId) && Objects.equals(pageParams, other.pageParams); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsResponse.java new file mode 100644 index 00000000000..e07b90f34e2 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsResponse.java @@ -0,0 +1,86 @@ +/* + * 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.client.ml.calendars.Calendar; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; + +public class GetCalendarsResponse extends AbstractResultResponse { + + public static final ParseField RESULTS_FIELD = new ParseField("calendars"); + + @SuppressWarnings("unchecked") + public static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("calendars_response", true, + a -> new GetCalendarsResponse((List) a[0], (long) a[1])); + + static { + PARSER.declareObjectArray(constructorArg(), Calendar.PARSER, RESULTS_FIELD); + PARSER.declareLong(constructorArg(), AbstractResultResponse.COUNT); + } + + public static GetCalendarsResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + GetCalendarsResponse(List calendars, long count) { + super(RESULTS_FIELD, calendars, count); + } + + /** + * The collection of {@link Calendar} objects found in the query + */ + public List calendars() { + return results; + } + + @Override + public int hashCode() { + return Objects.hash(results, count); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + GetCalendarsResponse other = (GetCalendarsResponse) obj; + return Objects.equals(results, other.results) && count == other.count; + } + + @Override + public final String toString() { + return Strings.toString(this); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/AnomalyRecord.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/AnomalyRecord.java index db4483fef4b..c10610a872f 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/AnomalyRecord.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/AnomalyRecord.java @@ -19,16 +19,14 @@ package org.elasticsearch.client.ml.job.results; import org.elasticsearch.client.ml.job.config.Job; +import org.elasticsearch.client.ml.job.util.TimeUtil; import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ObjectParser.ValueType; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentParser.Token; import java.io.IOException; -import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.Date; import java.util.List; @@ -90,15 +88,9 @@ public class AnomalyRecord implements ToXContentObject { static { PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID); - PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> { - if (p.currentToken() == Token.VALUE_NUMBER) { - return new Date(p.longValue()); - } else if (p.currentToken() == Token.VALUE_STRING) { - return new Date(DateFormatters.toZonedDateTime(DateTimeFormatter.ISO_INSTANT.parse(p.text())).toInstant().toEpochMilli()); - } - throw new IllegalArgumentException("unexpected token [" + p.currentToken() + "] for [" - + Result.TIMESTAMP.getPreferredName() + "]"); - }, Result.TIMESTAMP, ValueType.VALUE); + PARSER.declareField(ConstructingObjectParser.constructorArg(), + (p) -> TimeUtil.parseTimeField(p, Result.TIMESTAMP.getPreferredName()), + Result.TIMESTAMP, ValueType.VALUE); PARSER.declareLong(ConstructingObjectParser.constructorArg(), BUCKET_SPAN); PARSER.declareString((anomalyRecord, s) -> {}, Result.RESULT_TYPE); PARSER.declareDouble(AnomalyRecord::setProbability, PROBABILITY); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Bucket.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Bucket.java index 2dfed4c3834..9f549f16bbc 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Bucket.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Bucket.java @@ -19,16 +19,14 @@ package org.elasticsearch.client.ml.job.results; import org.elasticsearch.client.ml.job.config.Job; +import org.elasticsearch.client.ml.job.util.TimeUtil; import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ObjectParser.ValueType; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentParser.Token; import java.io.IOException; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -63,15 +61,8 @@ public class Bucket implements ToXContentObject { static { PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID); - PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> { - if (p.currentToken() == Token.VALUE_NUMBER) { - return new Date(p.longValue()); - } else if (p.currentToken() == Token.VALUE_STRING) { - return new Date(DateFormatters.toZonedDateTime(DateTimeFormatter.ISO_INSTANT.parse(p.text())).toInstant().toEpochMilli()); - } - throw new IllegalArgumentException("unexpected token [" + p.currentToken() + "] for [" - + Result.TIMESTAMP.getPreferredName() + "]"); - }, Result.TIMESTAMP, ValueType.VALUE); + PARSER.declareField(ConstructingObjectParser.constructorArg(), + (p) -> TimeUtil.parseTimeField(p, Result.TIMESTAMP.getPreferredName()), Result.TIMESTAMP, ValueType.VALUE); PARSER.declareLong(ConstructingObjectParser.constructorArg(), BUCKET_SPAN); PARSER.declareDouble(Bucket::setAnomalyScore, ANOMALY_SCORE); PARSER.declareDouble(Bucket::setInitialAnomalyScore, INITIAL_ANOMALY_SCORE); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/BucketInfluencer.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/BucketInfluencer.java index 6fc2a9b8b2d..ade5a5a2f50 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/BucketInfluencer.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/BucketInfluencer.java @@ -19,16 +19,14 @@ package org.elasticsearch.client.ml.job.results; import org.elasticsearch.client.ml.job.config.Job; +import org.elasticsearch.client.ml.job.util.TimeUtil; import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ObjectParser.ValueType; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentParser.Token; import java.io.IOException; -import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.Objects; @@ -56,15 +54,9 @@ public class BucketInfluencer implements ToXContentObject { static { PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID); - PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> { - if (p.currentToken() == Token.VALUE_NUMBER) { - return new Date(p.longValue()); - } else if (p.currentToken() == Token.VALUE_STRING) { - return new Date(DateFormatters.toZonedDateTime(DateTimeFormatter.ISO_INSTANT.parse(p.text())).toInstant().toEpochMilli()); - } - throw new IllegalArgumentException("unexpected token [" + p.currentToken() + "] for [" - + Result.TIMESTAMP.getPreferredName() + "]"); - }, Result.TIMESTAMP, ValueType.VALUE); + PARSER.declareField(ConstructingObjectParser.constructorArg(), + (p) -> TimeUtil.parseTimeField(p, Result.TIMESTAMP.getPreferredName()), + Result.TIMESTAMP, ValueType.VALUE); PARSER.declareLong(ConstructingObjectParser.constructorArg(), BUCKET_SPAN); PARSER.declareString((bucketInfluencer, s) -> {}, Result.RESULT_TYPE); PARSER.declareString(BucketInfluencer::setInfluencerFieldName, INFLUENCER_FIELD_NAME); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Influencer.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Influencer.java index 28ceb243bf6..4892b7f9346 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Influencer.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Influencer.java @@ -19,16 +19,14 @@ package org.elasticsearch.client.ml.job.results; import org.elasticsearch.client.ml.job.config.Job; +import org.elasticsearch.client.ml.job.util.TimeUtil; import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ObjectParser.ValueType; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentParser.Token; import java.io.IOException; -import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.Objects; @@ -61,15 +59,9 @@ public class Influencer implements ToXContentObject { PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID); PARSER.declareString(ConstructingObjectParser.constructorArg(), INFLUENCER_FIELD_NAME); PARSER.declareString(ConstructingObjectParser.constructorArg(), INFLUENCER_FIELD_VALUE); - PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> { - if (p.currentToken() == Token.VALUE_NUMBER) { - return new Date(p.longValue()); - } else if (p.currentToken() == Token.VALUE_STRING) { - return new Date(DateFormatters.toZonedDateTime(DateTimeFormatter.ISO_INSTANT.parse(p.text())).toInstant().toEpochMilli()); - } - throw new IllegalArgumentException("unexpected token [" + p.currentToken() + "] for [" - + Result.TIMESTAMP.getPreferredName() + "]"); - }, Result.TIMESTAMP, ValueType.VALUE); + PARSER.declareField(ConstructingObjectParser.constructorArg(), + (p) -> TimeUtil.parseTimeField(p, Result.TIMESTAMP.getPreferredName()), + Result.TIMESTAMP, ValueType.VALUE); PARSER.declareLong(ConstructingObjectParser.constructorArg(), BUCKET_SPAN); PARSER.declareString((influencer, s) -> {}, Result.RESULT_TYPE); PARSER.declareDouble(Influencer::setProbability, PROBABILITY); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/OverallBucket.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/OverallBucket.java index eaf050f8be9..722c2361b67 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/OverallBucket.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/OverallBucket.java @@ -19,16 +19,14 @@ package org.elasticsearch.client.ml.job.results; import org.elasticsearch.client.ml.job.config.Job; +import org.elasticsearch.client.ml.job.util.TimeUtil; import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.time.DateFormatters; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; -import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.Date; import java.util.List; @@ -56,15 +54,9 @@ public class OverallBucket implements ToXContentObject { a -> new OverallBucket((Date) a[0], (long) a[1], (double) a[2], (boolean) a[3])); static { - PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> { - if (p.currentToken() == XContentParser.Token.VALUE_NUMBER) { - return new Date(p.longValue()); - } else if (p.currentToken() == XContentParser.Token.VALUE_STRING) { - return new Date(DateFormatters.toZonedDateTime(DateTimeFormatter.ISO_INSTANT.parse(p.text())).toInstant().toEpochMilli()); - } - throw new IllegalArgumentException("unexpected token [" + p.currentToken() + "] for [" - + Result.TIMESTAMP.getPreferredName() + "]"); - }, Result.TIMESTAMP, ObjectParser.ValueType.VALUE); + PARSER.declareField(ConstructingObjectParser.constructorArg(), + (p) -> TimeUtil.parseTimeField(p, Result.TIMESTAMP.getPreferredName()), + Result.TIMESTAMP, ObjectParser.ValueType.VALUE); PARSER.declareLong(ConstructingObjectParser.constructorArg(), BUCKET_SPAN); PARSER.declareDouble(ConstructingObjectParser.constructorArg(), OVERALL_SCORE); PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), Result.IS_INTERIM); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Result.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Result.java index a7f8933a0a1..f98aef55f5b 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Result.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Result.java @@ -28,7 +28,6 @@ public final class Result { /** * Serialisation fields */ - public static final ParseField TYPE = new ParseField("result"); public static final ParseField RESULT_TYPE = new ParseField("result_type"); public static final ParseField TIMESTAMP = new ParseField("timestamp"); public static final ParseField IS_INTERIM = new ParseField("is_interim"); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java index 61122901b86..fdd4200ee81 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java @@ -30,6 +30,7 @@ import org.elasticsearch.client.ml.DeleteJobRequest; import org.elasticsearch.client.ml.FlushJobRequest; import org.elasticsearch.client.ml.ForecastJobRequest; import org.elasticsearch.client.ml.GetBucketsRequest; +import org.elasticsearch.client.ml.GetCalendarsRequest; import org.elasticsearch.client.ml.GetCategoriesRequest; import org.elasticsearch.client.ml.GetDatafeedRequest; import org.elasticsearch.client.ml.GetInfluencersRequest; @@ -259,7 +260,7 @@ public class MLRequestConvertersTests extends ESTestCase { assertEquals(Boolean.toString(true), request.getParameters().get("force")); } - public void testDeleteForecast() throws Exception { + public void testDeleteForecast() { String jobId = randomAlphaOfLength(10); DeleteForecastRequest deleteForecastRequest = new DeleteForecastRequest(jobId); @@ -415,6 +416,28 @@ public class MLRequestConvertersTests extends ESTestCase { } } + public void testGetCalendars() throws IOException { + GetCalendarsRequest getCalendarsRequest = new GetCalendarsRequest(); + String expectedEndpoint = "/_xpack/ml/calendars"; + + if (randomBoolean()) { + String calendarId = randomAlphaOfLength(10); + getCalendarsRequest.setCalendarId(calendarId); + expectedEndpoint += "/" + calendarId; + } + if (randomBoolean()) { + getCalendarsRequest.setPageParams(new PageParams(10, 20)); + } + + Request request = MLRequestConverters.getCalendars(getCalendarsRequest); + assertEquals(HttpGet.METHOD_NAME, request.getMethod()); + assertEquals(expectedEndpoint, request.getEndpoint()); + try (XContentParser parser = createParser(JsonXContent.jsonXContent, request.getEntity().getContent())) { + GetCalendarsRequest parsedRequest = GetCalendarsRequest.PARSER.apply(parser, null); + assertThat(parsedRequest, equalTo(getCalendarsRequest)); + } + } + private static Job createValidJob(String jobId) { AnalysisConfig.Builder analysisConfig = AnalysisConfig.builder(Collections.singletonList( Detector.builder().setFunction("count").build())); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java index 5349378e335..e90d541b9c7 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java @@ -32,6 +32,8 @@ import org.elasticsearch.client.ml.FlushJobRequest; import org.elasticsearch.client.ml.FlushJobResponse; import org.elasticsearch.client.ml.ForecastJobRequest; import org.elasticsearch.client.ml.ForecastJobResponse; +import org.elasticsearch.client.ml.GetCalendarsRequest; +import org.elasticsearch.client.ml.GetCalendarsResponse; import org.elasticsearch.client.ml.GetDatafeedRequest; import org.elasticsearch.client.ml.GetDatafeedResponse; import org.elasticsearch.client.ml.GetJobRequest; @@ -483,7 +485,6 @@ public class MachineLearningIT extends ESRestHighLevelClientTestCase { } public void testPutCalendar() throws IOException { - Calendar calendar = CalendarTests.testInstance(); MachineLearningClient machineLearningClient = highLevelClient().machineLearning(); PutCalendarResponse putCalendarResponse = execute(new PutCalendarRequest(calendar), machineLearningClient::putCalendar, @@ -492,6 +493,30 @@ public class MachineLearningIT extends ESRestHighLevelClientTestCase { assertThat(putCalendarResponse.getCalendar(), equalTo(calendar)); } + public void testGetCalendars() throws Exception { + Calendar calendar1 = CalendarTests.testInstance(); + Calendar calendar2 = CalendarTests.testInstance(); + + MachineLearningClient machineLearningClient = highLevelClient().machineLearning(); + machineLearningClient.putCalendar(new PutCalendarRequest(calendar1), RequestOptions.DEFAULT); + machineLearningClient.putCalendar(new PutCalendarRequest(calendar2), RequestOptions.DEFAULT); + + GetCalendarsRequest getCalendarsRequest = new GetCalendarsRequest(); + getCalendarsRequest.setCalendarId("_all"); + GetCalendarsResponse getCalendarsResponse = execute(getCalendarsRequest, machineLearningClient::getCalendars, + machineLearningClient::getCalendarsAsync); + assertEquals(2, getCalendarsResponse.count()); + assertEquals(2, getCalendarsResponse.calendars().size()); + assertThat(getCalendarsResponse.calendars().stream().map(Calendar::getId).collect(Collectors.toList()), + hasItems(calendar1.getId(), calendar1.getId())); + + getCalendarsRequest.setCalendarId(calendar1.getId()); + getCalendarsResponse = execute(getCalendarsRequest, machineLearningClient::getCalendars, + machineLearningClient::getCalendarsAsync); + assertEquals(1, getCalendarsResponse.count()); + assertEquals(calendar1, getCalendarsResponse.calendars().get(0)); + } + public static String randomValidJobId() { CodepointSetGenerator generator = new CodepointSetGenerator("abcdefghijklmnopqrstuvwxyz0123456789".toCharArray()); return generator.ofCodePointsLength(random(), 10, 10); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java index f0f7ffd939f..ddaf9d8db6c 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java @@ -43,6 +43,8 @@ import org.elasticsearch.client.ml.ForecastJobRequest; import org.elasticsearch.client.ml.ForecastJobResponse; import org.elasticsearch.client.ml.GetBucketsRequest; import org.elasticsearch.client.ml.GetBucketsResponse; +import org.elasticsearch.client.ml.GetCalendarsRequest; +import org.elasticsearch.client.ml.GetCalendarsResponse; import org.elasticsearch.client.ml.GetCategoriesRequest; import org.elasticsearch.client.ml.GetCategoriesResponse; import org.elasticsearch.client.ml.GetDatafeedRequest; @@ -880,6 +882,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase { PostDataRequest postDataRequest = new PostDataRequest(job.getId(), builder); client.machineLearning().postData(postDataRequest, RequestOptions.DEFAULT); client.machineLearning().flushJob(new FlushJobRequest(job.getId()), RequestOptions.DEFAULT); + ForecastJobResponse forecastJobResponse = client.machineLearning(). forecastJob(new ForecastJobRequest(job.getId()), RequestOptions.DEFAULT); String forecastId = forecastJobResponse.getForecastId(); @@ -1526,4 +1529,66 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase { assertTrue(latch.await(30L, TimeUnit.SECONDS)); } + + public void testGetCalendar() throws IOException, InterruptedException { + RestHighLevelClient client = highLevelClient(); + + Calendar calendar = new Calendar("holidays", Collections.singletonList("job_1"), "A calendar for public holidays"); + PutCalendarRequest putRequest = new PutCalendarRequest(calendar); + client.machineLearning().putCalendar(putRequest, RequestOptions.DEFAULT); + { + //tag::x-pack-ml-get-calendars-request + GetCalendarsRequest request = new GetCalendarsRequest(); // <1> + //end::x-pack-ml-get-calendars-request + + //tag::x-pack-ml-get-calendars-id + request.setCalendarId("holidays"); // <1> + //end::x-pack-ml-get-calendars-id + + //tag::x-pack-ml-get-calendars-page + request.setPageParams(new PageParams(10, 20)); // <1> + //end::x-pack-ml-get-calendars-page + + // reset page params + request.setPageParams(null); + + //tag::x-pack-ml-get-calendars-execution + GetCalendarsResponse response = client.machineLearning().getCalendars(request, RequestOptions.DEFAULT); + //end::x-pack-ml-get-calendars-execution + + // tag::x-pack-ml-get-calendars-response + long count = response.count(); // <1> + List calendars = response.calendars(); // <2> + // end::x-pack-ml-get-calendars-response + assertEquals(1, calendars.size()); + } + { + GetCalendarsRequest request = new GetCalendarsRequest("holidays"); + + // tag::x-pack-ml-get-calendars-listener + ActionListener listener = + new ActionListener() { + @Override + public void onResponse(GetCalendarsResponse getCalendarsResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::x-pack-ml-get-calendars-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::x-pack-ml-get-calendars-execute-async + client.machineLearning().getCalendarsAsync(request, RequestOptions.DEFAULT, listener); // <1> + // end::x-pack-ml-get-calendars-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsRequestTests.java new file mode 100644 index 00000000000..b7ca44fd5fa --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsRequestTests.java @@ -0,0 +1,46 @@ +/* + * 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.client.ml.job.util.PageParams; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +public class GetCalendarsRequestTests extends AbstractXContentTestCase { + + @Override + protected GetCalendarsRequest createTestInstance() { + GetCalendarsRequest request = new GetCalendarsRequest(); + request.setCalendarId(randomAlphaOfLength(9)); + if (randomBoolean()) { + request.setPageParams(new PageParams(1, 2)); + } + return request; + } + + @Override + protected GetCalendarsRequest doParseInstance(XContentParser parser) { + return GetCalendarsRequest.PARSER.apply(parser, null); + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsResponseTests.java new file mode 100644 index 00000000000..fd28e410cdc --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsResponseTests.java @@ -0,0 +1,52 @@ +/* + * 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.client.ml.calendars.Calendar; +import org.elasticsearch.client.ml.calendars.CalendarTests; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class GetCalendarsResponseTests extends AbstractXContentTestCase { + + @Override + protected GetCalendarsResponse createTestInstance() { + List calendars = new ArrayList<>(); + int count = randomIntBetween(0, 3); + for (int i=0; i Constructing a new request for all calendars + + +==== Optional Arguments +The following arguments are optional: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-id] +-------------------------------------------------- +<1> Construct a request for the single calendar `holidays` + + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-page] +-------------------------------------------------- +<1> The page parameters `from` and `size`. `from` specifies the number of calendars to skip. +`size` specifies the maximum number of calendars to get. Defaults to `0` and `100` respectively. + +[[java-rest-high-x-pack-ml-get-calendars-execution]] +==== Execution +The request can be executed through the `MachineLearningClient` contained +in the `RestHighLevelClient` object, accessed via the `machineLearningClient()` method. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-execution] +-------------------------------------------------- + +[[java-rest-high-x-pack-ml-get-calendars-execution-async]] +==== Asynchronous Execution + +The request can also be executed asynchronously: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-execute-async] +-------------------------------------------------- +<1> The `GetCalendarsRequest` to execute and the `ActionListener` to use when +the execution completes + +The asynchronous method does not block and returns immediately. Once it is +completed the `ActionListener` is called back with the `onResponse` method +if the execution is successful or the `onFailure` method if the execution +failed. + +A typical listener for `GetCalendarsResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-listener] +-------------------------------------------------- +<1> `onResponse` is called back when the action is completed successfully +<2> `onFailure` is called back when some unexpected error occurs + +[[java-rest-high-x-pack-ml-get-calendars-response]] +==== Get calendars Response + +The returned `GetCalendarsResponse` contains the requested calendars: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-response] +-------------------------------------------------- +<1> The count of calendars that were matched +<2> The calendars retrieved \ No newline at end of file diff --git a/docs/java-rest/high-level/ml/put-calendar.asciidoc b/docs/java-rest/high-level/ml/put-calendar.asciidoc index e6814c76fad..5d163f37eb4 100644 --- a/docs/java-rest/high-level/ml/put-calendar.asciidoc +++ b/docs/java-rest/high-level/ml/put-calendar.asciidoc @@ -4,7 +4,7 @@ Creates a new {ml} calendar. The API accepts a `PutCalendarRequest` and responds with a `PutCalendarResponse` object. -[[java-rest-high-x-pack-ml-get-calendars-request]] +[[java-rest-high-x-pack-ml-put-calendar-request]] ==== Put Calendar Request A `PutCalendarRequest` is constructed with a Calendar object diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 78a9f0bc7c2..2c907dd2053 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -231,6 +231,7 @@ The Java High Level REST Client supports the following Machine Learning APIs: * <> * <> * <> +* <> * <> include::ml/put-job.asciidoc[] @@ -252,6 +253,7 @@ include::ml/get-records.asciidoc[] include::ml/post-data.asciidoc[] include::ml/get-influencers.asciidoc[] include::ml/get-categories.asciidoc[] +include::ml/get-calendars.asciidoc[] include::ml/put-calendar.asciidoc[] == Migration APIs