From 2f3b542d57a6a1e50ba67e4c69b9dece0cac180b Mon Sep 17 00:00:00 2001
From: Ed Savage <32410745+edsavage@users.noreply.github.com>
Date: Tue, 11 Sep 2018 12:48:14 +0100
Subject: [PATCH] HLRC: Add ML get categories API (#33465)
HLRC: Adding the ML 'get categories' API
---
.../client/MLRequestConverters.java | 15 ++
.../client/MachineLearningClient.java | 41 +++++
.../client/ml/GetCategoriesRequest.java | 128 ++++++++++++++++
.../client/ml/GetCategoriesResponse.java | 79 ++++++++++
.../client/MLRequestConvertersTests.java | 16 ++
.../client/MachineLearningGetResultsIT.java | 141 ++++++++++++++++++
.../MlClientDocumentationIT.java | 75 +++++++++-
.../client/ml/GetCategoriesRequestTests.java | 51 +++++++
.../client/ml/GetCategoriesResponseTests.java | 53 +++++++
.../job/results/CategoryDefinitionTests.java | 2 +-
.../high-level/ml/get-categories.asciidoc | 83 +++++++++++
.../high-level/supported-apis.asciidoc | 2 +
12 files changed, 684 insertions(+), 2 deletions(-)
create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCategoriesRequest.java
create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCategoriesResponse.java
create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCategoriesRequestTests.java
create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCategoriesResponseTests.java
create mode 100644 docs/java-rest/high-level/ml/get-categories.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 ecbe7f2d3a5..d158c1a06a2 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
@@ -32,6 +32,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.GetCategoriesRequest;
import org.elasticsearch.client.ml.GetInfluencersRequest;
import org.elasticsearch.client.ml.GetJobRequest;
import org.elasticsearch.client.ml.GetJobStatsRequest;
@@ -194,6 +195,20 @@ final class MLRequestConverters {
return request;
}
+ static Request getCategories(GetCategoriesRequest getCategoriesRequest) throws IOException {
+ String endpoint = new EndpointBuilder()
+ .addPathPartAsIs("_xpack")
+ .addPathPartAsIs("ml")
+ .addPathPartAsIs("anomaly_detectors")
+ .addPathPart(getCategoriesRequest.getJobId())
+ .addPathPartAsIs("results")
+ .addPathPartAsIs("categories")
+ .build();
+ Request request = new Request(HttpGet.METHOD_NAME, endpoint);
+ request.setEntity(createEntity(getCategoriesRequest, REQUEST_BODY_CONTENT_TYPE));
+ return request;
+ }
+
static Request getOverallBuckets(GetOverallBucketsRequest getOverallBucketsRequest) throws IOException {
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 85c5771f345..b5f7550b913 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
@@ -32,6 +32,8 @@ import org.elasticsearch.client.ml.FlushJobRequest;
import org.elasticsearch.client.ml.FlushJobResponse;
import org.elasticsearch.client.ml.GetBucketsRequest;
import org.elasticsearch.client.ml.GetBucketsResponse;
+import org.elasticsearch.client.ml.GetCategoriesRequest;
+import org.elasticsearch.client.ml.GetCategoriesResponse;
import org.elasticsearch.client.ml.GetInfluencersRequest;
import org.elasticsearch.client.ml.GetInfluencersResponse;
import org.elasticsearch.client.ml.GetJobRequest;
@@ -474,6 +476,45 @@ public final class MachineLearningClient {
Collections.emptySet());
}
+ /**
+ * Gets the categories for a Machine Learning Job.
+ *
+ * For additional info
+ * see
+ * ML GET categories documentation
+ *
+ * @param request The request
+ * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @throws IOException when there is a serialization issue sending the request or receiving the response
+ */
+ public GetCategoriesResponse getCategories(GetCategoriesRequest request, RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request,
+ MLRequestConverters::getCategories,
+ options,
+ GetCategoriesResponse::fromXContent,
+ Collections.emptySet());
+ }
+
+ /**
+ * Gets the categories for a Machine Learning Job, notifies listener once the requested buckets are retrieved.
+ *
+ * For additional info
+ * see
+ * ML GET categories documentation
+ *
+ * @param request The request
+ * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @param listener Listener to be notified upon request completion
+ */
+ public void getCategoriesAsync(GetCategoriesRequest request, RequestOptions options, ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(request,
+ MLRequestConverters::getCategories,
+ options,
+ GetCategoriesResponse::fromXContent,
+ listener,
+ Collections.emptySet());
+ }
+
/**
* Gets overall buckets for a set of Machine Learning Jobs.
*
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCategoriesRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCategoriesRequest.java
new file mode 100644
index 00000000000..4fc68793f00
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCategoriesRequest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.job.config.Job;
+import org.elasticsearch.client.ml.job.util.PageParams;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * A request to retrieve categories of a given job
+ */
+public class GetCategoriesRequest extends ActionRequest implements ToXContentObject {
+
+
+ public static final ParseField CATEGORY_ID = new ParseField("category_id");
+
+ public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(
+ "get_categories_request", a -> new GetCategoriesRequest((String) a[0]));
+
+
+ static {
+ PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID);
+ PARSER.declareLong(GetCategoriesRequest::setCategoryId, CATEGORY_ID);
+ PARSER.declareObject(GetCategoriesRequest::setPageParams, PageParams.PARSER, PageParams.PAGE);
+ }
+
+ private final String jobId;
+ private Long categoryId;
+ private PageParams pageParams;
+
+ /**
+ * Constructs a request to retrieve category information from a given job
+ * @param jobId id of the job from which to retrieve results
+ */
+ public GetCategoriesRequest(String jobId) {
+ this.jobId = Objects.requireNonNull(jobId);
+ }
+
+ public String getJobId() {
+ return jobId;
+ }
+
+ public PageParams getPageParams() {
+ return pageParams;
+ }
+
+ public Long getCategoryId() {
+ return categoryId;
+ }
+
+ /**
+ * Sets the category id
+ * @param categoryId the category id
+ */
+ public void setCategoryId(Long categoryId) {
+ this.categoryId = categoryId;
+ }
+
+ /**
+ * Sets the paging parameters
+ * @param pageParams the paging parameters
+ */
+ 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();
+ builder.field(Job.ID.getPreferredName(), jobId);
+ if (categoryId != null) {
+ builder.field(CATEGORY_ID.getPreferredName(), categoryId);
+ }
+ if (pageParams != null) {
+ builder.field(PageParams.PAGE.getPreferredName(), pageParams);
+ }
+ builder.endObject();
+ return builder;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ GetCategoriesRequest request = (GetCategoriesRequest) obj;
+ return Objects.equals(jobId, request.jobId)
+ && Objects.equals(categoryId, request.categoryId)
+ && Objects.equals(pageParams, request.pageParams);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(jobId, categoryId, pageParams);
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCategoriesResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCategoriesResponse.java
new file mode 100644
index 00000000000..3d3abe00bfb
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCategoriesResponse.java
@@ -0,0 +1,79 @@
+/*
+ * 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.results.CategoryDefinition;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A response containing the requested categories
+ */
+public class GetCategoriesResponse extends AbstractResultResponse {
+
+ public static final ParseField CATEGORIES = new ParseField("categories");
+
+ @SuppressWarnings("unchecked")
+ public static final ConstructingObjectParser PARSER =
+ new ConstructingObjectParser<>("get_categories_response", true,
+ a -> new GetCategoriesResponse((List) a[0], (long) a[1]));
+
+ static {
+ PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(), CategoryDefinition.PARSER, CATEGORIES);
+ PARSER.declareLong(ConstructingObjectParser.constructorArg(), COUNT);
+ }
+
+ public static GetCategoriesResponse fromXContent(XContentParser parser) throws IOException {
+ return PARSER.parse(parser, null);
+ }
+
+ GetCategoriesResponse(List categories, long count) {
+ super(CATEGORIES, categories, count);
+ }
+
+ /**
+ * The retrieved categories
+ * @return the retrieved categories
+ */
+ public List categories() {
+ return results;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(count, results);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ GetCategoriesResponse other = (GetCategoriesResponse) obj;
+ return count == other.count && Objects.equals(results, other.results);
+ }
+}
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 26e6251af48..7cc5f119c39 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
@@ -28,6 +28,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.GetCategoriesRequest;
import org.elasticsearch.client.ml.GetInfluencersRequest;
import org.elasticsearch.client.ml.GetJobRequest;
import org.elasticsearch.client.ml.GetJobStatsRequest;
@@ -220,6 +221,21 @@ public class MLRequestConvertersTests extends ESTestCase {
}
}
+ public void testGetCategories() throws IOException {
+ String jobId = randomAlphaOfLength(10);
+ GetCategoriesRequest getCategoriesRequest = new GetCategoriesRequest(jobId);
+ getCategoriesRequest.setPageParams(new PageParams(100, 300));
+
+
+ Request request = MLRequestConverters.getCategories(getCategoriesRequest);
+ assertEquals(HttpGet.METHOD_NAME, request.getMethod());
+ assertEquals("/_xpack/ml/anomaly_detectors/" + jobId + "/results/categories", request.getEndpoint());
+ try (XContentParser parser = createParser(JsonXContent.jsonXContent, request.getEntity().getContent())) {
+ GetCategoriesRequest parsedRequest = GetCategoriesRequest.PARSER.apply(parser, null);
+ assertThat(parsedRequest, equalTo(getCategoriesRequest));
+ }
+ }
+
public void testGetOverallBuckets() throws IOException {
String jobId = randomAlphaOfLength(10);
GetOverallBucketsRequest getOverallBucketsRequest = new GetOverallBucketsRequest(jobId);
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningGetResultsIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningGetResultsIT.java
index 40d8596d1ba..ddaec641573 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningGetResultsIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningGetResultsIT.java
@@ -23,6 +23,8 @@ import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.ml.GetBucketsRequest;
import org.elasticsearch.client.ml.GetBucketsResponse;
+import org.elasticsearch.client.ml.GetCategoriesRequest;
+import org.elasticsearch.client.ml.GetCategoriesResponse;
import org.elasticsearch.client.ml.GetInfluencersRequest;
import org.elasticsearch.client.ml.GetInfluencersResponse;
import org.elasticsearch.client.ml.GetOverallBucketsRequest;
@@ -126,11 +128,150 @@ public class MachineLearningGetResultsIT extends ESRestHighLevelClientTestCase {
bulkRequest.add(indexRequest);
}
+ private void addCategoryIndexRequest(long categoryId, String categoryName, BulkRequest bulkRequest) {
+ IndexRequest indexRequest = new IndexRequest(RESULTS_INDEX, DOC);
+ indexRequest.source("{\"job_id\":\"" + JOB_ID + "\", \"category_id\": " + categoryId + ", \"terms\": \"" +
+ categoryName + "\", \"regex\": \".*?" + categoryName + ".*\", \"max_matching_length\": 3, \"examples\": [\"" +
+ categoryName + "\"]}", XContentType.JSON);
+ bulkRequest.add(indexRequest);
+ }
+
+ private void addCategoriesIndexRequests(BulkRequest bulkRequest) {
+
+ List categories = Arrays.asList("AAL", "JZA", "JBU");
+
+ for (int i = 0; i < categories.size(); i++) {
+ addCategoryIndexRequest(i+1, categories.get(i), bulkRequest);
+ }
+ }
+
@After
public void deleteJob() throws IOException {
new MlRestTestStateCleaner(logger, client()).clearMlMetadata();
}
+ public void testGetCategories() throws IOException {
+
+ // index some category results
+ BulkRequest bulkRequest = new BulkRequest();
+ bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
+
+ addCategoriesIndexRequests(bulkRequest);
+
+ highLevelClient().bulk(bulkRequest, RequestOptions.DEFAULT);
+
+ MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
+
+ {
+ GetCategoriesRequest request = new GetCategoriesRequest(JOB_ID);
+ request.setPageParams(new PageParams(0, 10000));
+
+ GetCategoriesResponse response = execute(request, machineLearningClient::getCategories,
+ machineLearningClient::getCategoriesAsync);
+
+ assertThat(response.count(), equalTo(3L));
+ assertThat(response.categories().size(), equalTo(3));
+ assertThat(response.categories().get(0).getCategoryId(), equalTo(1L));
+ assertThat(response.categories().get(0).getGrokPattern(), equalTo(".*?AAL.*"));
+ assertThat(response.categories().get(0).getRegex(), equalTo(".*?AAL.*"));
+ assertThat(response.categories().get(0).getTerms(), equalTo("AAL"));
+
+ assertThat(response.categories().get(1).getCategoryId(), equalTo(2L));
+ assertThat(response.categories().get(1).getGrokPattern(), equalTo(".*?JZA.*"));
+ assertThat(response.categories().get(1).getRegex(), equalTo(".*?JZA.*"));
+ assertThat(response.categories().get(1).getTerms(), equalTo("JZA"));
+
+ assertThat(response.categories().get(2).getCategoryId(), equalTo(3L));
+ assertThat(response.categories().get(2).getGrokPattern(), equalTo(".*?JBU.*"));
+ assertThat(response.categories().get(2).getRegex(), equalTo(".*?JBU.*"));
+ assertThat(response.categories().get(2).getTerms(), equalTo("JBU"));
+ }
+ {
+ GetCategoriesRequest request = new GetCategoriesRequest(JOB_ID);
+ request.setPageParams(new PageParams(0, 1));
+
+ GetCategoriesResponse response = execute(request, machineLearningClient::getCategories,
+ machineLearningClient::getCategoriesAsync);
+
+ assertThat(response.count(), equalTo(3L));
+ assertThat(response.categories().size(), equalTo(1));
+ assertThat(response.categories().get(0).getCategoryId(), equalTo(1L));
+ assertThat(response.categories().get(0).getGrokPattern(), equalTo(".*?AAL.*"));
+ assertThat(response.categories().get(0).getRegex(), equalTo(".*?AAL.*"));
+ assertThat(response.categories().get(0).getTerms(), equalTo("AAL"));
+ }
+ {
+ GetCategoriesRequest request = new GetCategoriesRequest(JOB_ID);
+ request.setPageParams(new PageParams(1, 2));
+
+ GetCategoriesResponse response = execute(request, machineLearningClient::getCategories,
+ machineLearningClient::getCategoriesAsync);
+
+ assertThat(response.count(), equalTo(3L));
+ assertThat(response.categories().size(), equalTo(2));
+ assertThat(response.categories().get(0).getCategoryId(), equalTo(2L));
+ assertThat(response.categories().get(0).getGrokPattern(), equalTo(".*?JZA.*"));
+ assertThat(response.categories().get(0).getRegex(), equalTo(".*?JZA.*"));
+ assertThat(response.categories().get(0).getTerms(), equalTo("JZA"));
+
+ assertThat(response.categories().get(1).getCategoryId(), equalTo(3L));
+ assertThat(response.categories().get(1).getGrokPattern(), equalTo(".*?JBU.*"));
+ assertThat(response.categories().get(1).getRegex(), equalTo(".*?JBU.*"));
+ assertThat(response.categories().get(1).getTerms(), equalTo("JBU"));
+ }
+ {
+ GetCategoriesRequest request = new GetCategoriesRequest(JOB_ID);
+ request.setCategoryId(0L); // request a non-existent category
+
+ GetCategoriesResponse response = execute(request, machineLearningClient::getCategories,
+ machineLearningClient::getCategoriesAsync);
+
+ assertThat(response.count(), equalTo(0L));
+ assertThat(response.categories().size(), equalTo(0));
+ }
+ {
+ GetCategoriesRequest request = new GetCategoriesRequest(JOB_ID);
+ request.setCategoryId(1L);
+
+ GetCategoriesResponse response = execute(request, machineLearningClient::getCategories,
+ machineLearningClient::getCategoriesAsync);
+
+ assertThat(response.count(), equalTo(1L));
+ assertThat(response.categories().size(), equalTo(1));
+ assertThat(response.categories().get(0).getCategoryId(), equalTo(1L));
+ assertThat(response.categories().get(0).getGrokPattern(), equalTo(".*?AAL.*"));
+ assertThat(response.categories().get(0).getRegex(), equalTo(".*?AAL.*"));
+ assertThat(response.categories().get(0).getTerms(), equalTo("AAL"));
+ }
+ {
+ GetCategoriesRequest request = new GetCategoriesRequest(JOB_ID);
+ request.setCategoryId(2L);
+
+ GetCategoriesResponse response = execute(request, machineLearningClient::getCategories,
+ machineLearningClient::getCategoriesAsync);
+
+ assertThat(response.count(), equalTo(1L));
+ assertThat(response.categories().get(0).getCategoryId(), equalTo(2L));
+ assertThat(response.categories().get(0).getGrokPattern(), equalTo(".*?JZA.*"));
+ assertThat(response.categories().get(0).getRegex(), equalTo(".*?JZA.*"));
+ assertThat(response.categories().get(0).getTerms(), equalTo("JZA"));
+
+ }
+ {
+ GetCategoriesRequest request = new GetCategoriesRequest(JOB_ID);
+ request.setCategoryId(3L);
+
+ GetCategoriesResponse response = execute(request, machineLearningClient::getCategories,
+ machineLearningClient::getCategoriesAsync);
+
+ assertThat(response.count(), equalTo(1L));
+ assertThat(response.categories().get(0).getCategoryId(), equalTo(3L));
+ assertThat(response.categories().get(0).getGrokPattern(), equalTo(".*?JBU.*"));
+ assertThat(response.categories().get(0).getRegex(), equalTo(".*?JBU.*"));
+ assertThat(response.categories().get(0).getTerms(), equalTo("JBU"));
+ }
+ }
+
public void testGetBuckets() throws IOException {
MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
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 9abef54d0d2..845729eccbd 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
@@ -39,6 +39,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.GetCategoriesRequest;
+import org.elasticsearch.client.ml.GetCategoriesResponse;
import org.elasticsearch.client.ml.GetInfluencersRequest;
import org.elasticsearch.client.ml.GetInfluencersResponse;
import org.elasticsearch.client.ml.GetJobRequest;
@@ -69,6 +71,7 @@ import org.elasticsearch.client.ml.job.config.Operator;
import org.elasticsearch.client.ml.job.config.RuleCondition;
import org.elasticsearch.client.ml.job.results.AnomalyRecord;
import org.elasticsearch.client.ml.job.results.Bucket;
+import org.elasticsearch.client.ml.job.results.CategoryDefinition;
import org.elasticsearch.client.ml.job.results.Influencer;
import org.elasticsearch.client.ml.job.results.OverallBucket;
import org.elasticsearch.client.ml.job.stats.JobStats;
@@ -473,7 +476,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
-
+
public void testGetBuckets() throws IOException, InterruptedException {
RestHighLevelClient client = highLevelClient();
@@ -1111,4 +1114,74 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
+
+ public void testGetCategories() throws IOException, InterruptedException {
+ RestHighLevelClient client = highLevelClient();
+
+ String jobId = "test-get-categories";
+ Job job = MachineLearningIT.buildJob(jobId);
+ client.machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT);
+
+ // Let us index a category
+ IndexRequest indexRequest = new IndexRequest(".ml-anomalies-shared", "doc");
+ indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
+ indexRequest.source("{\"job_id\": \"test-get-categories\", \"category_id\": 1, \"terms\": \"AAL\"," +
+ " \"regex\": \".*?AAL.*\", \"max_matching_length\": 3, \"examples\": [\"AAL\"]}", XContentType.JSON);
+ client.index(indexRequest, RequestOptions.DEFAULT);
+
+ {
+ // tag::x-pack-ml-get-categories-request
+ GetCategoriesRequest request = new GetCategoriesRequest(jobId); // <1>
+ // end::x-pack-ml-get-categories-request
+
+ // tag::x-pack-ml-get-categories-category-id
+ request.setCategoryId(1L); // <1>
+ // end::x-pack-ml-get-categories-category-id
+
+ // tag::x-pack-ml-get-categories-page
+ request.setPageParams(new PageParams(100, 200)); // <1>
+ // end::x-pack-ml-get-categories-page
+
+ // Set page params back to null so the response contains the category we indexed
+ request.setPageParams(null);
+
+ // tag::x-pack-ml-get-categories-execute
+ GetCategoriesResponse response = client.machineLearning().getCategories(request, RequestOptions.DEFAULT);
+ // end::x-pack-ml-get-categories-execute
+
+ // tag::x-pack-ml-get-categories-response
+ long count = response.count(); // <1>
+ List categories = response.categories(); // <2>
+ // end::x-pack-ml-get-categories-response
+ assertEquals(1, categories.size());
+ }
+ {
+ GetCategoriesRequest request = new GetCategoriesRequest(jobId);
+
+ // tag::x-pack-ml-get-categories-listener
+ ActionListener listener =
+ new ActionListener() {
+ @Override
+ public void onResponse(GetCategoriesResponse getcategoriesResponse) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::x-pack-ml-get-categories-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-categories-execute-async
+ client.machineLearning().getCategoriesAsync(request, RequestOptions.DEFAULT, listener); // <1>
+ // end::x-pack-ml-get-categories-execute-async
+
+ assertTrue(latch.await(30L, TimeUnit.SECONDS));
+ }
+ }
}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCategoriesRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCategoriesRequestTests.java
new file mode 100644
index 00000000000..7d9fe2b238f
--- /dev/null
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCategoriesRequestTests.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import java.io.IOException;
+
+public class GetCategoriesRequestTests extends AbstractXContentTestCase {
+
+ @Override
+ protected GetCategoriesRequest createTestInstance() {
+ GetCategoriesRequest request = new GetCategoriesRequest(randomAlphaOfLengthBetween(1, 20));
+ if (randomBoolean()) {
+ request.setCategoryId(randomNonNegativeLong());
+ } else {
+ int from = randomInt(10000);
+ int size = randomInt(10000);
+ request.setPageParams(new PageParams(from, size));
+ }
+ return request;
+ }
+
+ @Override
+ protected GetCategoriesRequest doParseInstance(XContentParser parser) throws IOException {
+ return GetCategoriesRequest.PARSER.apply(parser, null);
+ }
+
+ @Override
+ protected boolean supportsUnknownFields() {
+ return false;
+ }
+}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCategoriesResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCategoriesResponseTests.java
new file mode 100644
index 00000000000..e8718ba20e9
--- /dev/null
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCategoriesResponseTests.java
@@ -0,0 +1,53 @@
+/*
+ * 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.results.CategoryDefinition;
+import org.elasticsearch.client.ml.job.results.CategoryDefinitionTests;
+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 GetCategoriesResponseTests extends AbstractXContentTestCase {
+
+ @Override
+ protected GetCategoriesResponse createTestInstance() {
+ String jobId = randomAlphaOfLength(20);
+ int listSize = randomInt(10);
+ List categories = new ArrayList<>(listSize);
+ for (int j = 0; j < listSize; j++) {
+ CategoryDefinition category = CategoryDefinitionTests.createTestInstance(jobId);
+ categories.add(category);
+ }
+ return new GetCategoriesResponse(categories, listSize);
+ }
+
+ @Override
+ protected GetCategoriesResponse doParseInstance(XContentParser parser) throws IOException {
+ return GetCategoriesResponse.fromXContent(parser);
+ }
+
+ @Override
+ protected boolean supportsUnknownFields() {
+ return true;
+ }
+}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/results/CategoryDefinitionTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/results/CategoryDefinitionTests.java
index 27e15a1600d..63f26158386 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/results/CategoryDefinitionTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/ml/job/results/CategoryDefinitionTests.java
@@ -25,7 +25,7 @@ import java.util.Arrays;
public class CategoryDefinitionTests extends AbstractXContentTestCase {
- public CategoryDefinition createTestInstance(String jobId) {
+ public static CategoryDefinition createTestInstance(String jobId) {
CategoryDefinition categoryDefinition = new CategoryDefinition(jobId);
categoryDefinition.setCategoryId(randomLong());
categoryDefinition.setTerms(randomAlphaOfLength(10));
diff --git a/docs/java-rest/high-level/ml/get-categories.asciidoc b/docs/java-rest/high-level/ml/get-categories.asciidoc
new file mode 100644
index 00000000000..0e86a2b7f33
--- /dev/null
+++ b/docs/java-rest/high-level/ml/get-categories.asciidoc
@@ -0,0 +1,83 @@
+[[java-rest-high-x-pack-ml-get-categories]]
+=== Get Categories API
+
+The Get Categories API retrieves one or more category results.
+It accepts a `GetCategoriesRequest` object and responds
+with a `GetCategoriesResponse` object.
+
+[[java-rest-high-x-pack-ml-get-categories-request]]
+==== Get Categories Request
+
+A `GetCategoriesRequest` object gets created with an existing non-null `jobId`.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-categories-request]
+--------------------------------------------------
+<1> Constructing a new request referencing an existing `jobId`
+
+==== Optional Arguments
+The following arguments are optional:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-categories-category-id]
+--------------------------------------------------
+<1> The id of the category to get. Otherwise it will return all categories.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-categories-page]
+--------------------------------------------------
+<1> The page parameters `from` and `size`. `from` specifies the number of categories to skip.
+`size` specifies the maximum number of categories to get. Defaults to `0` and `100` respectively.
+
+[[java-rest-high-x-pack-ml-get-categories-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-categories-execute]
+--------------------------------------------------
+
+
+[[java-rest-high-x-pack-ml-get-categories-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-categories-execute-async]
+--------------------------------------------------
+<1> The `GetCategoriesRequest` 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 `GetCategoriesResponse` looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-categories-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-snapshot-ml-get-categories-response]]
+==== Get Categories Response
+
+The returned `GetCategoriesResponse` contains the requested categories:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-categories-response]
+--------------------------------------------------
+<1> The count of categories that were matched
+<2> The categories retrieved
\ No newline at end of file
diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc
index 8d92653ce57..87639a2ea3f 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/docs/java-rest/high-level/supported-apis.asciidoc
@@ -226,6 +226,7 @@ The Java High Level REST Client supports the following Machine Learning APIs:
* <>
* <>
* <>
+* <>
include::ml/put-job.asciidoc[]
include::ml/get-job.asciidoc[]
@@ -241,6 +242,7 @@ include::ml/get-overall-buckets.asciidoc[]
include::ml/get-records.asciidoc[]
include::ml/post-data.asciidoc[]
include::ml/get-influencers.asciidoc[]
+include::ml/get-categories.asciidoc[]
== Migration APIs