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 1030464be4f..d65aa0dbb2c 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.GetOverallBucketsRequest; 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 @@ final class MLRequestConverters { 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 43bc18fad0d..3b1fd2bfd2d 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.OpenJobRequest; 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 final class MachineLearningClient { 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 00000000000..a21e96b4642 --- /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 00000000000..ca96f153c60 --- /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.DeprecationHandler; +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, 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()); + } 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 ee53da18cd2..3cb4579cf13 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.GetOverallBucketsRequest; 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 class MLRequestConvertersTests extends ESTestCase { } } + 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 a8050397ad1..a0c6b8febb2 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.OpenJobRequest; 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.elasticsearch.rest.RestStatus; 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 class MachineLearningIT extends ESRestHighLevelClientTestCase { } } + 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 a9fbb56f68f..ed4aa1aa30b 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.OpenJobRequest; 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.Influencer; 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,66 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase { } } + 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::preview-datafeed-request + PreviewDatafeedRequest request = new PreviewDatafeedRequest(datafeedId); // <1> + //end::preview-datafeed-request + + //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::preview-datafeed-response + + assertTrue(semiParsedPreview.isEmpty()); + } + { + PreviewDatafeedRequest request = new PreviewDatafeedRequest(datafeedId); + + // tag::preview-datafeed-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(PreviewDatafeedResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // 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::preview-datafeed-execute-async + client.machineLearning().previewDatafeedAsync(request, RequestOptions.DEFAULT, listener); // <1> + // end::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 00000000000..2359ec6927a --- /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 00000000000..bb0ec3e5e70 --- /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 00000000000..5b812af8344 --- /dev/null +++ b/docs/java-rest/high-level/ml/preview-datafeed.asciidoc @@ -0,0 +1,34 @@ +-- +: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 +{request}+ object and responds +with a +{response}+ object. + +[id="{upid}-{api}-request"] +==== Preview Datafeed Request + +A +{request}+ object is created referencing a non-null `datafeedId`. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-request] +-------------------------------------------------- +<1> Constructing a new request referencing an existing `datafeedId` + +[id="{upid}-{api}-response"] +==== Preview Datafeed Response + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-response] +-------------------------------------------------- +<1> The raw +BytesReference+ of the data preview +<2> A +List>+ that represents the previewed data + +include::../execution.asciidoc[] diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 3bc5905a46f..055b3b7303b 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -219,33 +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[] @@ -257,6 +260,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[]