From 55f725fcd4733037c7240ebf1022e17fb11eab37 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Wed, 3 Oct 2018 15:33:46 -0500 Subject: [PATCH 1/3] HLRC: ML Add preview datafeed api --- .../client/MLRequestConverters.java | 12 ++ .../client/MachineLearningClient.java | 45 +++++++ .../client/ml/PreviewDatafeedRequest.java | 100 ++++++++++++++++ .../client/ml/PreviewDatafeedResponse.java | 113 ++++++++++++++++++ .../client/MLRequestConvertersTests.java | 8 ++ .../client/MachineLearningIT.java | 55 +++++++++ .../MlClientDocumentationIT.java | 60 ++++++++++ .../ml/PreviewDatafeedRequestTests.java | 43 +++++++ .../ml/PreviewDatafeedResponseTests.java | 99 +++++++++++++++ .../high-level/ml/preview-datafeed.asciidoc | 53 ++++++++ .../high-level/supported-apis.asciidoc | 2 + 11 files changed, 590 insertions(+) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedResponse.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/PreviewDatafeedRequestTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/PreviewDatafeedResponseTests.java create mode 100644 docs/java-rest/high-level/ml/preview-datafeed.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 1030464be4f18..d65aa0dbb2c6e 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 @@ -45,6 +45,7 @@ import org.elasticsearch.client.ml.GetRecordsRequest; import org.elasticsearch.client.ml.OpenJobRequest; import org.elasticsearch.client.ml.PostDataRequest; +import org.elasticsearch.client.ml.PreviewDatafeedRequest; import org.elasticsearch.client.ml.PutCalendarRequest; import org.elasticsearch.client.ml.PutDatafeedRequest; import org.elasticsearch.client.ml.PutJobRequest; @@ -259,6 +260,17 @@ static Request stopDatafeed(StopDatafeedRequest stopDatafeedRequest) throws IOEx return request; } + static Request previewDatafeed(PreviewDatafeedRequest previewDatafeedRequest) { + String endpoint = new EndpointBuilder() + .addPathPartAsIs("_xpack") + .addPathPartAsIs("ml") + .addPathPartAsIs("datafeeds") + .addPathPart(previewDatafeedRequest.getDatafeedId()) + .addPathPartAsIs("_preview") + .build(); + return new Request(HttpGet.METHOD_NAME, endpoint); + } + static Request deleteForecast(DeleteForecastRequest deleteForecastRequest) { String endpoint = new EndpointBuilder() .addPathPartAsIs("_xpack") 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 43bc18fad0da9..3b1fd2bfd2d3e 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 @@ -52,6 +52,8 @@ import org.elasticsearch.client.ml.OpenJobResponse; import org.elasticsearch.client.ml.PostDataRequest; import org.elasticsearch.client.ml.PostDataResponse; +import org.elasticsearch.client.ml.PreviewDatafeedRequest; +import org.elasticsearch.client.ml.PreviewDatafeedResponse; import org.elasticsearch.client.ml.PutCalendarRequest; import org.elasticsearch.client.ml.PutCalendarResponse; import org.elasticsearch.client.ml.PutDatafeedRequest; @@ -649,6 +651,49 @@ public void stopDatafeedAsync(StopDatafeedRequest request, RequestOptions option Collections.emptySet()); } + /** + * Previews the given Machine Learning Datafeed + *

+ * For additional info + * see + * ML Preview Datafeed documentation + * + * @param request The request to preview the datafeed + * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return {@link PreviewDatafeedResponse} object containing a {@link org.elasticsearch.common.bytes.BytesReference} of the data in + * JSON format + * @throws IOException when there is a serialization issue sending the request or receiving the response + */ + public PreviewDatafeedResponse previewDatafeed(PreviewDatafeedRequest request, RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, + MLRequestConverters::previewDatafeed, + options, + PreviewDatafeedResponse::fromXContent, + Collections.emptySet()); + } + + /** + * Previews the given Machine Learning Datafeed asynchronously and notifies the listener on completion + *

+ * For additional info + * see + * ML Preview Datafeed documentation + * + * @param request The request to preview the datafeed + * @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 previewDatafeedAsync(PreviewDatafeedRequest request, + RequestOptions options, + ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, + MLRequestConverters::previewDatafeed, + options, + PreviewDatafeedResponse::fromXContent, + listener, + Collections.emptySet()); + } + /** * Updates a Machine Learning {@link org.elasticsearch.client.ml.job.config.Job} *

diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedRequest.java new file mode 100644 index 0000000000000..a21e96b464234 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedRequest.java @@ -0,0 +1,100 @@ +/* + * 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.datafeed.DatafeedConfig; +import org.elasticsearch.common.Strings; +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.Objects; + +/** + * Request to preview a MachineLearning Datafeed + */ +public class PreviewDatafeedRequest extends ActionRequest implements ToXContentObject { + + public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "open_datafeed_request", true, a -> new PreviewDatafeedRequest((String) a[0])); + + static { + PARSER.declareString(ConstructingObjectParser.constructorArg(), DatafeedConfig.ID); + } + + public static PreviewDatafeedRequest fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + private final String datafeedId; + + /** + * Create a new request with the desired datafeedId + * + * @param datafeedId unique datafeedId, must not be null + */ + public PreviewDatafeedRequest(String datafeedId) { + this.datafeedId = Objects.requireNonNull(datafeedId, "[datafeed_id] must not be null"); + } + + public String getDatafeedId() { + return datafeedId; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(DatafeedConfig.ID.getPreferredName(), datafeedId); + builder.endObject(); + return builder; + } + + @Override + public String toString() { + return Strings.toString(this); + } + + @Override + public int hashCode() { + return Objects.hash(datafeedId); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + PreviewDatafeedRequest that = (PreviewDatafeedRequest) other; + return Objects.equals(datafeedId, that.datafeedId); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedResponse.java new file mode 100644 index 0000000000000..dd51d8452d686 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedResponse.java @@ -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.ActionResponse; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Response containing a datafeed preview in JSON format + */ +public class PreviewDatafeedResponse extends ActionResponse implements ToXContentObject { + + private BytesReference preview; + + public static PreviewDatafeedResponse fromXContent(XContentParser parser) throws IOException { + try (XContentBuilder builder = XContentFactory.jsonBuilder()) { + parser.nextToken(); + builder.copyCurrentStructure(parser); + return new PreviewDatafeedResponse(BytesReference.bytes(builder)); + } + } + + public PreviewDatafeedResponse(BytesReference preview) { + this.preview = preview; + } + + public BytesReference getPreview() { + return preview; + } + + /** + * Parses the preview to a list of {@link Map} objects + * @return List of previewed data + * @throws IOException If there is a parsing issue with the {@link BytesReference} + * @throws java.lang.ClassCastException If casting the raw {@link Object} entries to a {@link Map} fails + */ + @SuppressWarnings("unchecked") + public List> getDataList() throws IOException { + try(StreamInput streamInput = preview.streamInput(); + XContentParser parser = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, streamInput)) { + XContentParser.Token token = parser.nextToken(); + if (token == XContentParser.Token.START_ARRAY) { + return parser.listOrderedMap().stream().map(obj -> (Map)obj).collect(Collectors.toList()); + } else { + return Collections.singletonList(parser.mapOrdered()); + } + } + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + try (InputStream stream = preview.streamInput()) { + builder.rawValue(stream, XContentType.JSON); + } + return builder; + } + + @Override + public int hashCode() { + return Objects.hash(preview); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PreviewDatafeedResponse other = (PreviewDatafeedResponse) obj; + return Objects.equals(preview, other.preview); + } + + @Override + public final String toString() { + return Strings.toString(this); + } +} 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 ee53da18cd23f..3cb4579cf13d0 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 @@ -41,6 +41,7 @@ import org.elasticsearch.client.ml.GetRecordsRequest; import org.elasticsearch.client.ml.OpenJobRequest; import org.elasticsearch.client.ml.PostDataRequest; +import org.elasticsearch.client.ml.PreviewDatafeedRequest; import org.elasticsearch.client.ml.PutCalendarRequest; import org.elasticsearch.client.ml.PutDatafeedRequest; import org.elasticsearch.client.ml.PutJobRequest; @@ -293,6 +294,13 @@ public void testStopDatafeed() throws Exception { } } + public void testPreviewDatafeed() { + PreviewDatafeedRequest datafeedRequest = new PreviewDatafeedRequest("datafeed_1"); + Request request = MLRequestConverters.previewDatafeed(datafeedRequest); + assertEquals(HttpGet.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/ml/datafeeds/" + datafeedRequest.getDatafeedId() + "/_preview", request.getEndpoint()); + } + public void testDeleteForecast() { String jobId = randomAlphaOfLength(10); DeleteForecastRequest deleteForecastRequest = new DeleteForecastRequest(jobId); 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 a8050397ad109..a0c6b8febb221 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 @@ -49,6 +49,8 @@ import org.elasticsearch.client.ml.OpenJobResponse; import org.elasticsearch.client.ml.PostDataRequest; import org.elasticsearch.client.ml.PostDataResponse; +import org.elasticsearch.client.ml.PreviewDatafeedRequest; +import org.elasticsearch.client.ml.PreviewDatafeedResponse; import org.elasticsearch.client.ml.PutCalendarRequest; import org.elasticsearch.client.ml.PutCalendarResponse; import org.elasticsearch.client.ml.PutDatafeedRequest; @@ -76,8 +78,11 @@ import org.junit.After; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -564,6 +569,56 @@ public void testStopDatafeed() throws Exception { } } + public void testPreviewDatafeed() throws Exception { + String jobId = "test-preview-datafeed"; + String indexName = "preview_data_1"; + + // Set up the index and docs + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName); + createIndexRequest.mapping("doc", "timestamp", "type=date", "total", "type=long"); + highLevelClient().indices().create(createIndexRequest, RequestOptions.DEFAULT); + BulkRequest bulk = new BulkRequest(); + bulk.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + long now = (System.currentTimeMillis()/1000)*1000; + long thePast = now - 60000; + int i = 0; + List totalTotals = new ArrayList<>(60); + while(thePast < now) { + Integer total = randomInt(1000); + IndexRequest doc = new IndexRequest(); + doc.index(indexName); + doc.type("doc"); + doc.id("id" + i); + doc.source("{\"total\":" + total + ",\"timestamp\":"+ thePast +"}", XContentType.JSON); + bulk.add(doc); + thePast += 1000; + i++; + totalTotals.add(total); + } + highLevelClient().bulk(bulk, RequestOptions.DEFAULT); + + MachineLearningClient machineLearningClient = highLevelClient().machineLearning(); + // create the job and the datafeed + Job job = buildJob(jobId); + putJob(job); + openJob(job); + + String datafeedId = jobId + "-feed"; + DatafeedConfig datafeed = DatafeedConfig.builder(datafeedId, jobId) + .setIndices(indexName) + .setQueryDelay(TimeValue.timeValueSeconds(1)) + .setTypes(Collections.singletonList("doc")) + .setFrequency(TimeValue.timeValueSeconds(1)).build(); + machineLearningClient.putDatafeed(new PutDatafeedRequest(datafeed), RequestOptions.DEFAULT); + + PreviewDatafeedResponse response = execute(new PreviewDatafeedRequest(datafeedId), + machineLearningClient::previewDatafeed, + machineLearningClient::previewDatafeedAsync); + + Integer[] totals = response.getDataList().stream().map(map -> (Integer)map.get("total")).toArray(Integer[]::new); + assertThat(totalTotals, containsInAnyOrder(totals)); + } + public void testDeleteForecast() throws Exception { String jobId = "test-delete-forecast"; 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 a9fbb56f68fa2..9827ed5df48ed 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 @@ -65,6 +65,8 @@ import org.elasticsearch.client.ml.OpenJobResponse; import org.elasticsearch.client.ml.PostDataRequest; import org.elasticsearch.client.ml.PostDataResponse; +import org.elasticsearch.client.ml.PreviewDatafeedRequest; +import org.elasticsearch.client.ml.PreviewDatafeedResponse; import org.elasticsearch.client.ml.PutCalendarRequest; import org.elasticsearch.client.ml.PutCalendarResponse; import org.elasticsearch.client.ml.PutDatafeedRequest; @@ -97,6 +99,7 @@ import org.elasticsearch.client.ml.job.results.OverallBucket; import org.elasticsearch.client.ml.job.stats.JobStats; import org.elasticsearch.client.ml.job.util.PageParams; +import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.QueryBuilders; @@ -708,6 +711,63 @@ public void onFailure(Exception e) { } } + public void testPreviewDatafeed() throws Exception { + RestHighLevelClient client = highLevelClient(); + + Job job = MachineLearningIT.buildJob("preview-datafeed-job"); + client.machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT); + String datafeedId = job.getId() + "-feed"; + String indexName = "preview_data_2"; + CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName); + createIndexRequest.mapping("doc", "timestamp", "type=date", "total", "type=long"); + highLevelClient().indices().create(createIndexRequest, RequestOptions.DEFAULT); + DatafeedConfig datafeed = DatafeedConfig.builder(datafeedId, job.getId()) + .setTypes(Arrays.asList("doc")) + .setIndices(indexName) + .build(); + client.machineLearning().putDatafeed(new PutDatafeedRequest(datafeed), RequestOptions.DEFAULT); + { + //tag::x-pack-ml-preview-datafeed-request + PreviewDatafeedRequest request = new PreviewDatafeedRequest(datafeedId); // <1> + //end::x-pack-ml-preview-datafeed-request + + //tag::x-pack-ml-preview-datafeed-execute + PreviewDatafeedResponse response = client.machineLearning().previewDatafeed(request, RequestOptions.DEFAULT); + BytesReference rawPreview = response.getPreview(); // <1> + List> semiParsedPreview = response.getDataList(); // <2> + //end::x-pack-ml-preview-datafeed-execute + + assertTrue(semiParsedPreview.isEmpty()); + } + { + PreviewDatafeedRequest request = new PreviewDatafeedRequest(datafeedId); + + // tag::x-pack-ml-preview-datafeed-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(PreviewDatafeedResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::x-pack-ml-preview-datafeed-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-preview-datafeed-execute-async + client.machineLearning().previewDatafeedAsync(request, RequestOptions.DEFAULT, listener); // <1> + // end::x-pack-ml-preview-datafeed-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + } + public void testStartDatafeed() throws Exception { RestHighLevelClient client = highLevelClient(); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/PreviewDatafeedRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/PreviewDatafeedRequestTests.java new file mode 100644 index 0000000000000..2359ec6927a16 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/PreviewDatafeedRequestTests.java @@ -0,0 +1,43 @@ +/* + * 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.datafeed.DatafeedConfigTests; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; + +public class PreviewDatafeedRequestTests extends AbstractXContentTestCase { + + @Override + protected PreviewDatafeedRequest createTestInstance() { + return new PreviewDatafeedRequest(DatafeedConfigTests.randomValidDatafeedId()); + } + + @Override + protected PreviewDatafeedRequest doParseInstance(XContentParser parser) throws IOException { + return PreviewDatafeedRequest.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return true; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/PreviewDatafeedResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/PreviewDatafeedResponseTests.java new file mode 100644 index 0000000000000..bb0ec3e5e70fe --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/PreviewDatafeedResponseTests.java @@ -0,0 +1,99 @@ +/* + * 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.datafeed.DatafeedConfig; +import org.elasticsearch.client.ml.datafeed.DatafeedConfigTests; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.stream.Collectors; + +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; +import static org.hamcrest.Matchers.containsInAnyOrder; + +public class PreviewDatafeedResponseTests extends ESTestCase { + + protected PreviewDatafeedResponse createTestInstance() throws IOException { + //This is just to create a random object to stand in the place of random data + DatafeedConfig datafeedConfig = DatafeedConfigTests.createRandom(); + BytesReference bytes = XContentHelper.toXContent(datafeedConfig, XContentType.JSON, false); + return new PreviewDatafeedResponse(bytes); + } + + public void testGetDataList() throws IOException { + String rawData = "[\n" + + " {\n" + + " \"time\": 1454803200000,\n" + + " \"airline\": \"JZA\",\n" + + " \"doc_count\": 5,\n" + + " \"responsetime\": 990.4628295898438\n" + + " },\n" + + " {\n" + + " \"time\": 1454803200000,\n" + + " \"airline\": \"JBU\",\n" + + " \"doc_count\": 23,\n" + + " \"responsetime\": 877.5927124023438\n" + + " },\n" + + " {\n" + + " \"time\": 1454803200000,\n" + + " \"airline\": \"KLM\",\n" + + " \"doc_count\": 42,\n" + + " \"responsetime\": 1355.481201171875\n" + + " }\n" + + "]"; + BytesReference bytes = new BytesArray(rawData); + PreviewDatafeedResponse response = new PreviewDatafeedResponse(bytes); + assertThat(response.getDataList() + .stream() + .map(map -> (String)map.get("airline")) + .collect(Collectors.toList()), containsInAnyOrder("JZA", "JBU", "KLM")); + + rawData = "{\"key\":\"my_value\"}"; + bytes = new BytesArray(rawData); + response = new PreviewDatafeedResponse(bytes); + assertThat(response.getDataList() + .stream() + .map(map -> (String)map.get("key")) + .collect(Collectors.toList()), containsInAnyOrder("my_value")); + + } + + //Because this is raw a BytesReference, the shuffling done via `AbstractXContentTestCase` is unacceptable and causes equality failures + public void testSerializationDeserialization() throws IOException { + for (int runs = 0; runs < 20; runs++) { + XContentType xContentType = XContentType.JSON; + PreviewDatafeedResponse testInstance = createTestInstance(); + BytesReference originalXContent = XContentHelper.toXContent(testInstance, xContentType, false); + XContentParser parser = this.createParser(xContentType.xContent(), originalXContent); + PreviewDatafeedResponse parsed = PreviewDatafeedResponse.fromXContent(parser); + assertEquals(testInstance, parsed); + assertToXContentEquivalent( + XContentHelper.toXContent(testInstance, xContentType, false), + XContentHelper.toXContent(parsed, xContentType, false), + xContentType); + } + } + +} diff --git a/docs/java-rest/high-level/ml/preview-datafeed.asciidoc b/docs/java-rest/high-level/ml/preview-datafeed.asciidoc new file mode 100644 index 0000000000000..89e8ba3b94700 --- /dev/null +++ b/docs/java-rest/high-level/ml/preview-datafeed.asciidoc @@ -0,0 +1,53 @@ +[[java-rest-high-x-pack-ml-preview-datafeed]] +=== Preview Datafeed API + +The Preview Datafeed API provides the ability to preview a {ml} datafeed's data +in the cluster. It accepts a `PreviewDatafeedRequest` object and responds +with a `PreviewDatafeedResponse` object. + +[[java-rest-high-x-pack-ml-preview-datafeed-request]] +==== Preview Datafeed Request + +A `PreviewDatafeedRequest` object is created referencing a non-null `datafeedId`. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-preview-datafeed-request] +-------------------------------------------------- +<1> Constructing a new request referencing an existing `datafeedId` + +[[java-rest-high-x-pack-ml-preview-datafeed-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-preview-datafeed-execute] +-------------------------------------------------- +<1> The raw BytesReference of the data preview +<2> A List of Map that represents the previewed data + +[[java-rest-high-x-pack-ml-preview-datafeed-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-preview-datafeed-execute-async] +-------------------------------------------------- +<1> The `PreviewDatafeedRequest` to execute and the `ActionListener` to use when +the execution completes + +The method does not block and returns immediately. The passed `ActionListener` is used +to notify the caller of completion. A typical `ActionListener` for `PreviewDatafeedResponse` may +look like + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-preview-datafeed-listener] +-------------------------------------------------- +<1> `onResponse` is called back when the action is completed successfully +<2> `onFailure` is called back when some unexpected error occurs diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index bb326cbb9c66d..2ed7409f8c5ed 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -233,6 +233,7 @@ The Java High Level REST Client supports the following Machine Learning APIs: * <> * <> * <> +* <> * <> * <> * <> @@ -257,6 +258,7 @@ include::ml/flush-job.asciidoc[] include::ml/put-datafeed.asciidoc[] include::ml/get-datafeed.asciidoc[] include::ml/delete-datafeed.asciidoc[] +include::ml/preview-datafeed.asciidoc[] include::ml/start-datafeed.asciidoc[] include::ml/stop-datafeed.asciidoc[] include::ml/get-job-stats.asciidoc[] From 1ec31b8816cd7d9ba58c827f7c32c66cc3ac09ed Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Wed, 3 Oct 2018 16:45:28 -0500 Subject: [PATCH 2/3] Changing deprecation handling for parser --- .../org/elasticsearch/client/ml/PreviewDatafeedResponse.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedResponse.java index dd51d8452d686..ca96f153c6050 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PreviewDatafeedResponse.java @@ -22,7 +22,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.common.xcontent.DeprecationHandler; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -71,7 +71,7 @@ public BytesReference getPreview() { public List> getDataList() throws IOException { try(StreamInput streamInput = preview.streamInput(); XContentParser parser = XContentType.JSON.xContent() - .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, streamInput)) { + .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, streamInput)) { XContentParser.Token token = parser.nextToken(); if (token == XContentParser.Token.START_ARRAY) { return parser.listOrderedMap().stream().map(obj -> (Map)obj).collect(Collectors.toList()); From 5f376cf2bf84cbfe3b03c3a0f8caa8c8cc096e70 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Thu, 4 Oct 2018 09:44:27 -0500 Subject: [PATCH 3/3] Removing some duplication in docs, will address other APIs in another PR --- .../MlClientDocumentationIT.java | 19 ++++--- .../high-level/ml/preview-datafeed.asciidoc | 53 ++++++------------- .../high-level/supported-apis.asciidoc | 52 +++++++++--------- 3 files changed, 55 insertions(+), 69 deletions(-) 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 9827ed5df48ed..ed4aa1aa30b05 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 @@ -727,22 +727,25 @@ public void testPreviewDatafeed() throws Exception { .build(); client.machineLearning().putDatafeed(new PutDatafeedRequest(datafeed), RequestOptions.DEFAULT); { - //tag::x-pack-ml-preview-datafeed-request + //tag::preview-datafeed-request PreviewDatafeedRequest request = new PreviewDatafeedRequest(datafeedId); // <1> - //end::x-pack-ml-preview-datafeed-request + //end::preview-datafeed-request - //tag::x-pack-ml-preview-datafeed-execute + //tag::preview-datafeed-execute PreviewDatafeedResponse response = client.machineLearning().previewDatafeed(request, RequestOptions.DEFAULT); + //end::preview-datafeed-execute + + //tag::preview-datafeed-response BytesReference rawPreview = response.getPreview(); // <1> List> semiParsedPreview = response.getDataList(); // <2> - //end::x-pack-ml-preview-datafeed-execute + //end::preview-datafeed-response assertTrue(semiParsedPreview.isEmpty()); } { PreviewDatafeedRequest request = new PreviewDatafeedRequest(datafeedId); - // tag::x-pack-ml-preview-datafeed-listener + // tag::preview-datafeed-execute-listener ActionListener listener = new ActionListener() { @Override public void onResponse(PreviewDatafeedResponse response) { @@ -754,15 +757,15 @@ public void onFailure(Exception e) { // <2> } }; - // end::x-pack-ml-preview-datafeed-listener + // end::preview-datafeed-execute-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-preview-datafeed-execute-async + // tag::preview-datafeed-execute-async client.machineLearning().previewDatafeedAsync(request, RequestOptions.DEFAULT, listener); // <1> - // end::x-pack-ml-preview-datafeed-execute-async + // end::preview-datafeed-execute-async assertTrue(latch.await(30L, TimeUnit.SECONDS)); } diff --git a/docs/java-rest/high-level/ml/preview-datafeed.asciidoc b/docs/java-rest/high-level/ml/preview-datafeed.asciidoc index 89e8ba3b94700..5b812af8344d6 100644 --- a/docs/java-rest/high-level/ml/preview-datafeed.asciidoc +++ b/docs/java-rest/high-level/ml/preview-datafeed.asciidoc @@ -1,53 +1,34 @@ -[[java-rest-high-x-pack-ml-preview-datafeed]] +-- +:api: preview-datafeed +:request: PreviewDatafeedRequest +:response: PreviewDatafeedResponse +-- +[id="{upid}-{api}"] === Preview Datafeed API The Preview Datafeed API provides the ability to preview a {ml} datafeed's data -in the cluster. It accepts a `PreviewDatafeedRequest` object and responds -with a `PreviewDatafeedResponse` object. +in the cluster. It accepts a +{request}+ object and responds +with a +{response}+ object. -[[java-rest-high-x-pack-ml-preview-datafeed-request]] +[id="{upid}-{api}-request"] ==== Preview Datafeed Request -A `PreviewDatafeedRequest` object is created referencing a non-null `datafeedId`. +A +{request}+ object is created referencing a non-null `datafeedId`. ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-preview-datafeed-request] +include-tagged::{doc-tests-file}[{api}-request] -------------------------------------------------- <1> Constructing a new request referencing an existing `datafeedId` -[[java-rest-high-x-pack-ml-preview-datafeed-execution]] -==== Execution - -The request can be executed through the `MachineLearningClient` contained -in the `RestHighLevelClient` object, accessed via the `machineLearningClient()` method. +[id="{upid}-{api}-response"] +==== Preview Datafeed Response ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-preview-datafeed-execute] +include-tagged::{doc-tests-file}[{api}-response] -------------------------------------------------- -<1> The raw BytesReference of the data preview -<2> A List of Map that represents the previewed data - -[[java-rest-high-x-pack-ml-preview-datafeed-execution-async]] -==== Asynchronous Execution - -The request can also be executed asynchronously: +<1> The raw +BytesReference+ of the data preview +<2> A +List>+ that represents the previewed data -["source","java",subs="attributes,callouts,macros"] --------------------------------------------------- -include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-preview-datafeed-execute-async] --------------------------------------------------- -<1> The `PreviewDatafeedRequest` to execute and the `ActionListener` to use when -the execution completes - -The method does not block and returns immediately. The passed `ActionListener` is used -to notify the caller of completion. A typical `ActionListener` for `PreviewDatafeedResponse` may -look like - -["source","java",subs="attributes,callouts,macros"] --------------------------------------------------- -include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-preview-datafeed-listener] --------------------------------------------------- -<1> `onResponse` is called back when the action is completed successfully -<2> `onFailure` is called back when some unexpected error occurs +include::../execution.asciidoc[] diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 2ed7409f8c5ed..39e2efd44630e 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -219,34 +219,36 @@ include::licensing/get-license.asciidoc[] include::licensing/delete-license.asciidoc[] == Machine Learning APIs +:upid: {mainid}-x-pack-ml +:doc-tests-file: {doc-tests}/MlClientDocumentationIT.java The Java High Level REST Client supports the following Machine Learning APIs: -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> +* <<{upid}-put-job>> +* <<{upid}-get-job>> +* <<{upid}-delete-job>> +* <<{upid}-open-job>> +* <<{upid}-close-job>> +* <<{upid}-flush-job>> +* <<{upid}-update-job>> +* <<{upid}-get-job-stats>> +* <<{upid}-put-datafeed>> +* <<{upid}-get-datafeed>> +* <<{upid}-delete-datafeed>> +* <<{upid}-preview-datafeed>> +* <<{upid}-start-datafeed>> +* <<{upid}-stop-datafeed>> +* <<{upid}-forecast-job>> +* <<{upid}-delete-forecast>> +* <<{upid}-get-buckets>> +* <<{upid}-get-overall-buckets>> +* <<{upid}-get-records>> +* <<{upid}-post-data>> +* <<{upid}-get-influencers>> +* <<{upid}-get-categories>> +* <<{upid}-get-calendars>> +* <<{upid}-put-calendar>> +* <<{upid}-delete-calendar>> include::ml/put-job.asciidoc[] include::ml/get-job.asciidoc[]