diff --git a/docs/reference/ml/anomaly-detection/apis/delete-forecast.asciidoc b/docs/reference/ml/anomaly-detection/apis/delete-forecast.asciidoc
index 3eadef83728..9785e58a1ad 100644
--- a/docs/reference/ml/anomaly-detection/apis/delete-forecast.asciidoc
+++ b/docs/reference/ml/anomaly-detection/apis/delete-forecast.asciidoc
@@ -6,7 +6,7 @@
Delete forecast
++++
-Deletes forecasts from a {ml} job.
+Deletes forecasts from a {ml} job.
[[ml-delete-forecast-request]]
== {api-request-title}
@@ -27,12 +27,12 @@ Deletes forecasts from a {ml} job.
[[ml-delete-forecast-desc]]
== {api-description-title}
-By default, forecasts are retained for 14 days. You can specify a different
+By default, forecasts are retained for 14 days. You can specify a different
retention period with the `expires_in` parameter in the
<>. The delete forecast API enables you to delete
one or more forecasts before they expire.
-NOTE: When you delete a job, its associated forecasts are deleted.
+NOTE: When you delete a job, its associated forecasts are deleted.
For more information, see
{ml-docs}/ml-overview.html#ml-forecasting[Forecasting the future].
@@ -42,26 +42,26 @@ For more information, see
``::
(Optional, string) A comma-separated list of forecast identifiers. If you do not
-specify this optional parameter or if you specify `_all`, the API deletes all
-forecasts from the job.
-
+specify this optional parameter or if you specify `_all` or `*` the API deletes all
+forecasts from the job.
+
``::
(Required, string)
include::{es-repo-dir}/ml/ml-shared.asciidoc[tag=job-id-anomaly-detection]
-
+
[[ml-delete-forecast-query-parms]]
== {api-query-parms-title}
`allow_no_forecasts`::
(Optional, boolean) Specifies whether an error occurs when there are no
- forecasts. In particular, if this parameter is set to `false` and there are no
+ forecasts. In particular, if this parameter is set to `false` and there are no
forecasts associated with the job, attempts to delete all forecasts return an
error. The default value is `true`.
`timeout`::
- (Optional, <>) Specifies the period of time to wait
- for the completion of the delete operation. When this period of time elapses,
+ (Optional, <>) Specifies the period of time to wait
+ for the completion of the delete operation. When this period of time elapses,
the API fails and returns an error. The default value is `30s`.
[[ml-delete-forecast-example]]
diff --git a/docs/reference/ml/anomaly-detection/apis/get-calendar-event.asciidoc b/docs/reference/ml/anomaly-detection/apis/get-calendar-event.asciidoc
index 5c0d2f5aed5..3f3516f9e2c 100644
--- a/docs/reference/ml/anomaly-detection/apis/get-calendar-event.asciidoc
+++ b/docs/reference/ml/anomaly-detection/apis/get-calendar-event.asciidoc
@@ -25,8 +25,10 @@ Retrieves information about the scheduled events in calendars.
[[ml-get-calendar-event-desc]]
== {api-description-title}
-You can get scheduled event information for a single calendar or for all
-calendars by using `_all`.
+You can get scheduled event information for multiple calendars in a single
+API request by using a comma-separated list of ids or a wildcard expression.
+You can get scheduled event information for all calendars by using `_all`,
+by specifying `*` as the ``, or by omitting the ``.
For more information, see
{ml-docs}/ml-calendars.html[Calendars and scheduled events].
diff --git a/docs/reference/ml/anomaly-detection/apis/get-calendar.asciidoc b/docs/reference/ml/anomaly-detection/apis/get-calendar.asciidoc
index aaa5787fac0..7b4e5bbe12b 100644
--- a/docs/reference/ml/anomaly-detection/apis/get-calendar.asciidoc
+++ b/docs/reference/ml/anomaly-detection/apis/get-calendar.asciidoc
@@ -25,10 +25,12 @@ Retrieves configuration information for calendars.
[[ml-get-calendar-desc]]
== {api-description-title}
-You can get information for a single calendar or for all calendars by using
-`_all`.
+You can get information for multiple calendars in a single API request by using a
+comma-separated list of ids or a wildcard expression. You can get
+information for all calendars by using `_all`, by specifying `*` as the
+``, or by omitting the ``.
-For more information, see
+For more information, see
{ml-docs}/ml-calendars.html[Calendars and scheduled events].
[[ml-get-calendar-path-parms]]
diff --git a/docs/reference/ml/anomaly-detection/apis/get-snapshot.asciidoc b/docs/reference/ml/anomaly-detection/apis/get-snapshot.asciidoc
index 7ece93cc4b6..5fcc2784c94 100644
--- a/docs/reference/ml/anomaly-detection/apis/get-snapshot.asciidoc
+++ b/docs/reference/ml/anomaly-detection/apis/get-snapshot.asciidoc
@@ -34,8 +34,10 @@ include::{es-repo-dir}/ml/ml-shared.asciidoc[tag=job-id-anomaly-detection]
include::{es-repo-dir}/ml/ml-shared.asciidoc[tag=snapshot-id]
+
--
-If you do not specify this optional parameter, the API returns information about
-all model snapshots.
+You can multiple snapshots for a single job in a single API request
+by using a comma-separated list of `` or a wildcard expression.
+You can get all snapshots for all calendars by using `_all`,
+by specifying `*` as the ``, or by omitting the ``.
--
[[ml-get-snapshot-request-body]]
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/GetCalendarEventsAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/GetCalendarEventsAction.java
index 095f90e4a50..9c1d17bad02 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/GetCalendarEventsAction.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/GetCalendarEventsAction.java
@@ -128,7 +128,7 @@ public class GetCalendarEventsAction extends ActionType data = new ArrayList<>();
+ while (timestamp < now) {
+ data.add(createJsonRecord(createRecord(timestamp, 10.0)));
+ data.add(createJsonRecord(createRecord(timestamp, 30.0)));
+ timestamp += bucketSpan.seconds();
+ }
+
+ postData(job.getId(), data.stream().collect(Collectors.joining()));
+ flushJob(job.getId(), false);
+ String forecastIdDefaultDurationDefaultExpiry = forecast(job.getId(), null, null);
+ String forecastIdDuration1HourNoExpiry = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
+ String forecastId2Duration1HourNoExpiry = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
+ String forecastId2Duration1HourNoExpiry2 = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
+ waitForecastToFinish(job.getId(), forecastIdDefaultDurationDefaultExpiry);
+ waitForecastToFinish(job.getId(), forecastIdDuration1HourNoExpiry);
+ waitForecastToFinish(job.getId(), forecastId2Duration1HourNoExpiry);
+ waitForecastToFinish(job.getId(), forecastId2Duration1HourNoExpiry2);
+ closeJob(job.getId());
+
+ assertNotNull(getForecastStats(job.getId(), forecastIdDefaultDurationDefaultExpiry));
+ assertNotNull(getForecastStats(job.getId(), forecastIdDuration1HourNoExpiry));
+ assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry));
+ assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry2));
+
+ {
+ DeleteForecastAction.Request request = new DeleteForecastAction.Request(job.getId(),
+ forecastIdDefaultDurationDefaultExpiry.substring(0, forecastIdDefaultDurationDefaultExpiry.length() - 2) + "*"
+ + ","
+ + forecastIdDuration1HourNoExpiry);
+ AcknowledgedResponse response = client().execute(DeleteForecastAction.INSTANCE, request).actionGet();
+ assertTrue(response.isAcknowledged());
+
+ assertNull(getForecastStats(job.getId(), forecastIdDefaultDurationDefaultExpiry));
+ assertNull(getForecastStats(job.getId(), forecastIdDuration1HourNoExpiry));
+ assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry));
+ assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry2));
+ }
+
+ {
+ DeleteForecastAction.Request request = new DeleteForecastAction.Request(job.getId(), "*");
+ AcknowledgedResponse response = client().execute(DeleteForecastAction.INSTANCE, request).actionGet();
+ assertTrue(response.isAcknowledged());
+
+ assertNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry));
+ assertNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry2));
+ }
+
+ }
+
public void testDelete() throws Exception {
Detector.Builder detector = new Detector.Builder("mean", "value");
@@ -317,6 +385,8 @@ public class ForecastIT extends MlNativeAutodetectIntegTestCase {
flushJob(job.getId(), false);
String forecastIdDefaultDurationDefaultExpiry = forecast(job.getId(), null, null);
String forecastIdDuration1HourNoExpiry = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
+ String forecastId2Duration1HourNoExpiry = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
+ String forecastId2Duration1HourNoExpiry2 = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
waitForecastToFinish(job.getId(), forecastIdDefaultDurationDefaultExpiry);
waitForecastToFinish(job.getId(), forecastIdDuration1HourNoExpiry);
closeJob(job.getId());
@@ -333,13 +403,11 @@ public class ForecastIT extends MlNativeAutodetectIntegTestCase {
forecastIdDefaultDurationDefaultExpiry + "," + forecastIdDuration1HourNoExpiry);
AcknowledgedResponse response = client().execute(DeleteForecastAction.INSTANCE, request).actionGet();
assertTrue(response.isAcknowledged());
- }
- {
- ForecastRequestStats forecastStats = getForecastStats(job.getId(), forecastIdDefaultDurationDefaultExpiry);
- assertNull(forecastStats);
- ForecastRequestStats otherStats = getForecastStats(job.getId(), forecastIdDuration1HourNoExpiry);
- assertNull(otherStats);
+ assertNull(getForecastStats(job.getId(), forecastIdDefaultDurationDefaultExpiry));
+ assertNull(getForecastStats(job.getId(), forecastIdDuration1HourNoExpiry));
+ assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry));
+ assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry2));
}
{
@@ -354,6 +422,9 @@ public class ForecastIT extends MlNativeAutodetectIntegTestCase {
DeleteForecastAction.Request request = new DeleteForecastAction.Request(job.getId(), Metadata.ALL);
AcknowledgedResponse response = client().execute(DeleteForecastAction.INSTANCE, request).actionGet();
assertTrue(response.isAcknowledged());
+
+ assertNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry));
+ assertNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry2));
}
{
diff --git a/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/JobResultsProviderIT.java b/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/JobResultsProviderIT.java
index 8f46663daa6..0d46a66e187 100644
--- a/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/JobResultsProviderIT.java
+++ b/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/xpack/ml/integration/JobResultsProviderIT.java
@@ -16,6 +16,7 @@ import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
+import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.OriginSettingClient;
import org.elasticsearch.cluster.metadata.AliasMetadata;
@@ -37,6 +38,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.ClientHelper;
+import org.elasticsearch.xpack.core.action.util.PageParams;
import org.elasticsearch.xpack.core.action.util.QueryPage;
import org.elasticsearch.xpack.core.ml.MlMetaIndex;
import org.elasticsearch.xpack.core.ml.MlMetadata;
@@ -72,6 +74,7 @@ import org.elasticsearch.xpack.ml.utils.persistence.ResultsPersisterService;
import org.junit.Before;
import java.io.IOException;
+import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
@@ -255,21 +258,69 @@ public class JobResultsProviderIT extends MlSingleNodeTestCase {
calendars.add(new Calendar("cat foo calendar", Arrays.asList("cat", "foo"), null));
indexCalendars(calendars);
- List queryResult = getCalendars("ted");
+ List queryResult = getCalendars(CalendarQueryBuilder.builder().jobId("ted"));
assertThat(queryResult, is(empty()));
- queryResult = getCalendars("foo");
+ queryResult = getCalendars(CalendarQueryBuilder.builder().jobId("foo"));
assertThat(queryResult, hasSize(3));
Long matchedCount = queryResult.stream().filter(
c -> c.getId().equals("foo calendar") || c.getId().equals("foo bar calendar") || c.getId().equals("cat foo calendar"))
.count();
assertEquals(Long.valueOf(3), matchedCount);
- queryResult = getCalendars("bar");
+ queryResult = getCalendars(CalendarQueryBuilder.builder().jobId("bar"));
assertThat(queryResult, hasSize(1));
assertEquals("foo bar calendar", queryResult.get(0).getId());
}
+ public void testGetCalandarById() throws Exception {
+ List calendars = new ArrayList<>();
+ calendars.add(new Calendar("empty calendar", Collections.emptyList(), null));
+ calendars.add(new Calendar("foo calendar", Collections.singletonList("foo"), null));
+ calendars.add(new Calendar("foo bar calendar", Arrays.asList("foo", "bar"), null));
+ calendars.add(new Calendar("cat calendar", Collections.singletonList("cat"), null));
+ calendars.add(new Calendar("cat foo calendar", Arrays.asList("cat", "foo"), null));
+ indexCalendars(calendars);
+
+ List queryResult = getCalendars(CalendarQueryBuilder.builder()
+ .calendarIdTokens(new String[]{"foo*"})
+ .sort(true));
+ assertThat(queryResult, hasSize(2));
+ assertThat(queryResult.get(0).getId(), equalTo("foo bar calendar"));
+ assertThat(queryResult.get(1).getId(), equalTo("foo calendar"));
+
+ queryResult = getCalendars(CalendarQueryBuilder.builder()
+ .calendarIdTokens(new String[]{"foo calendar", "cat calendar"})
+ .sort(true));
+ assertThat(queryResult, hasSize(2));
+ assertThat(queryResult.get(0).getId(), equalTo("cat calendar"));
+ assertThat(queryResult.get(1).getId(), equalTo("foo calendar"));
+ }
+
+ public void testGetCalendarByIdAndPaging() throws Exception {
+ List calendars = new ArrayList<>();
+ calendars.add(new Calendar("empty calendar", Collections.emptyList(), null));
+ calendars.add(new Calendar("foo calendar", Collections.singletonList("foo"), null));
+ calendars.add(new Calendar("foo bar calendar", Arrays.asList("foo", "bar"), null));
+ calendars.add(new Calendar("cat calendar", Collections.singletonList("cat"), null));
+ calendars.add(new Calendar("cat foo calendar", Arrays.asList("cat", "foo"), null));
+ indexCalendars(calendars);
+
+ List queryResult = getCalendars(CalendarQueryBuilder.builder()
+ .calendarIdTokens(new String[]{"foo*"})
+ .pageParams(new PageParams(0, 1))
+ .sort(true));
+ assertThat(queryResult, hasSize(1));
+ assertThat(queryResult.get(0).getId(), equalTo("foo bar calendar"));
+
+ queryResult = getCalendars(CalendarQueryBuilder.builder()
+ .calendarIdTokens(new String[]{"foo calendar", "cat calendar"})
+ .sort(true)
+ .pageParams(new PageParams(1, 1)));
+ assertThat(queryResult, hasSize(1));
+ assertThat(queryResult.get(0).getId(), equalTo("foo calendar"));
+ }
+
public void testUpdateCalendar() throws Exception {
MlMetadata.Builder mlBuilder = new MlMetadata.Builder();
mlBuilder.putJob(createJob("foo").build(), false);
@@ -319,7 +370,7 @@ public class JobResultsProviderIT extends MlSingleNodeTestCase {
throw exceptionHolder.get();
}
- List updatedCalendars = getCalendars(null);
+ List updatedCalendars = getCalendars(CalendarQueryBuilder.builder());
assertEquals(5, updatedCalendars.size());
for (Calendar cal: updatedCalendars) {
assertThat("bar", is(not(in(cal.getJobIds()))));
@@ -341,7 +392,7 @@ public class JobResultsProviderIT extends MlSingleNodeTestCase {
throw exceptionHolder.get();
}
- updatedCalendars = getCalendars(null);
+ updatedCalendars = getCalendars(CalendarQueryBuilder.builder());
assertEquals(5, updatedCalendars.size());
for (Calendar cal: updatedCalendars) {
assertThat("bar", is(not(in(cal.getJobIds()))));
@@ -384,16 +435,11 @@ public class JobResultsProviderIT extends MlSingleNodeTestCase {
return aliasMetadataList.stream().map(AliasMetadata::alias).collect(Collectors.toSet());
}
- private List getCalendars(String jobId) throws Exception {
+ private List getCalendars(CalendarQueryBuilder query) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference exceptionHolder = new AtomicReference<>();
AtomicReference> result = new AtomicReference<>();
- CalendarQueryBuilder query = new CalendarQueryBuilder();
-
- if (jobId != null) {
- query.jobId(jobId);
- }
jobProvider.calendars(query, ActionListener.wrap(
r -> {
result.set(r);
@@ -455,7 +501,7 @@ public class JobResultsProviderIT extends MlSingleNodeTestCase {
return calendarHolder.get();
}
- public void testScheduledEvents() throws Exception {
+ public void testScheduledEventsForJobs() throws Exception {
Job.Builder jobA = createJob("job_a");
Job.Builder jobB = createJob("job_b");
Job.Builder jobC = createJob("job_c");
@@ -504,6 +550,59 @@ public class JobResultsProviderIT extends MlSingleNodeTestCase {
assertEquals(events.get(3), returnedEvents.get(1));
}
+ public void testScheduledEvents() throws Exception {
+ createJob("job_a");
+ createJob("job_b");
+ createJob("job_c");
+
+ String calendarAId = "maintenance_a";
+ List calendars = new ArrayList<>();
+ calendars.add(new Calendar(calendarAId, Collections.singletonList("job_a"), null));
+
+ ZonedDateTime now = ZonedDateTime.now();
+ List events = new ArrayList<>();
+ events.add(buildScheduledEvent("downtime", now.plusDays(1), now.plusDays(2), calendarAId));
+ events.add(buildScheduledEvent("downtime_AA", now.plusDays(8), now.plusDays(9), calendarAId));
+ events.add(buildScheduledEvent("downtime_AAA", now.plusDays(15), now.plusDays(16), calendarAId));
+
+ String calendarABId = "maintenance_a_and_b";
+ calendars.add(new Calendar(calendarABId, Arrays.asList("job_a", "job_b"), null));
+
+ events.add(buildScheduledEvent("downtime_AB", now.plusDays(12), now.plusDays(13), calendarABId));
+
+ indexCalendars(calendars);
+ indexScheduledEvents(events);
+
+ List returnedEvents = getScheduledEvents(new ScheduledEventsQueryBuilder());
+ assertEquals(4, returnedEvents.size());
+ assertEquals(events.get(0), returnedEvents.get(0));
+ assertEquals(events.get(1), returnedEvents.get(1));
+ assertEquals(events.get(3), returnedEvents.get(2));
+ assertEquals(events.get(2), returnedEvents.get(3));
+
+ returnedEvents = getScheduledEvents(ScheduledEventsQueryBuilder.builder().calendarIds(new String[]{"maintenance_a"}));
+ assertEquals(3, returnedEvents.size());
+ assertEquals(events.get(0), returnedEvents.get(0));
+ assertEquals(events.get(1), returnedEvents.get(1));
+ assertEquals(events.get(2), returnedEvents.get(2));
+
+ returnedEvents = getScheduledEvents(ScheduledEventsQueryBuilder.builder()
+ .calendarIds(new String[]{"maintenance_a", "maintenance_a_and_b"}));
+ assertEquals(4, returnedEvents.size());
+ assertEquals(events.get(0), returnedEvents.get(0));
+ assertEquals(events.get(1), returnedEvents.get(1));
+ assertEquals(events.get(3), returnedEvents.get(2));
+ assertEquals(events.get(2), returnedEvents.get(3));
+
+ returnedEvents = getScheduledEvents(ScheduledEventsQueryBuilder.builder()
+ .calendarIds(new String[]{"maintenance_a*"}));
+ assertEquals(4, returnedEvents.size());
+ assertEquals(events.get(0), returnedEvents.get(0));
+ assertEquals(events.get(1), returnedEvents.get(1));
+ assertEquals(events.get(3), returnedEvents.get(2));
+ assertEquals(events.get(2), returnedEvents.get(3));
+ }
+
public void testScheduledEventsForJob_withGroup() throws Exception {
String groupA = "group-a";
String groupB = "group-b";
@@ -545,6 +644,49 @@ public class JobResultsProviderIT extends MlSingleNodeTestCase {
.build();
}
+ public void testGetSnapshots() {
+ String jobId = "test_get_snapshots";
+ Job.Builder job = createJob(jobId);
+ indexModelSnapshot(new ModelSnapshot.Builder(jobId).setSnapshotId("snap_2")
+ .setTimestamp(Date.from(Instant.ofEpochMilli(10)))
+ .build());
+ indexModelSnapshot(new ModelSnapshot.Builder(jobId).setSnapshotId("snap_1")
+ .setTimestamp(Date.from(Instant.ofEpochMilli(11)))
+ .build());
+ indexModelSnapshot(new ModelSnapshot.Builder(jobId).setSnapshotId("other_snap")
+ .setTimestamp(Date.from(Instant.ofEpochMilli(12)))
+ .build());
+
+ client().admin().indices().prepareRefresh(AnomalyDetectorsIndex.jobStateIndexPattern(),
+ AnomalyDetectorsIndex.jobResultsAliasedName(jobId)).get();
+
+ PlainActionFuture> future = new PlainActionFuture<>();
+ jobProvider.modelSnapshots(jobId, 0, 4, "9", "15", "", false, "snap_2,snap_1", future::onResponse, future::onFailure);
+ List snapshots = future.actionGet().results();
+ assertThat(snapshots.get(0).getSnapshotId(), equalTo("snap_2"));
+ assertThat(snapshots.get(1).getSnapshotId(), equalTo("snap_1"));
+
+ future = new PlainActionFuture<>();
+ jobProvider.modelSnapshots(jobId, 0, 4, "9", "15", "", false, "snap_*", future::onResponse, future::onFailure);
+ snapshots = future.actionGet().results();
+ assertThat(snapshots.get(0).getSnapshotId(), equalTo("snap_2"));
+ assertThat(snapshots.get(1).getSnapshotId(), equalTo("snap_1"));
+
+ future = new PlainActionFuture<>();
+ jobProvider.modelSnapshots(jobId, 0, 4, "9", "15", "", false, "snap_*,other_snap", future::onResponse, future::onFailure);
+ snapshots = future.actionGet().results();
+ assertThat(snapshots.get(0).getSnapshotId(), equalTo("snap_2"));
+ assertThat(snapshots.get(1).getSnapshotId(), equalTo("snap_1"));
+ assertThat(snapshots.get(2).getSnapshotId(), equalTo("other_snap"));
+
+ future = new PlainActionFuture<>();
+ jobProvider.modelSnapshots(jobId, 0, 4, "9", "15", "", false, "*", future::onResponse, future::onFailure);
+ snapshots = future.actionGet().results();
+ assertThat(snapshots.get(0).getSnapshotId(), equalTo("snap_2"));
+ assertThat(snapshots.get(1).getSnapshotId(), equalTo("snap_1"));
+ assertThat(snapshots.get(2).getSnapshotId(), equalTo("other_snap"));
+ }
+
public void testGetAutodetectParams() throws Exception {
String jobId = "test_get_autodetect_params";
Job.Builder job = createJob(jobId, Arrays.asList("fruit", "tea"));
@@ -664,6 +806,27 @@ public class JobResultsProviderIT extends MlSingleNodeTestCase {
return searchResultHolder.get().results();
}
+ private List getScheduledEvents(ScheduledEventsQueryBuilder query) throws Exception {
+ AtomicReference errorHolder = new AtomicReference<>();
+ AtomicReference> searchResultHolder = new AtomicReference<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ jobProvider.scheduledEvents(query, ActionListener.wrap(
+ params -> {
+ searchResultHolder.set(params);
+ latch.countDown();
+ }, e -> {
+ errorHolder.set(e);
+ latch.countDown();
+ }));
+
+ latch.await();
+ if (errorHolder.get() != null) {
+ throw errorHolder.get();
+ }
+
+ return searchResultHolder.get().results();
+ }
+
private Job.Builder createJob(String jobId) {
return createJob(jobId, Collections.emptyList());
}
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteForecastAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteForecastAction.java
index 57d082e64bf..ea866934f92 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteForecastAction.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteForecastAction.java
@@ -54,11 +54,11 @@ import org.elasticsearch.xpack.core.ml.job.results.ForecastRequestStats;
import org.elasticsearch.xpack.core.ml.job.results.ForecastRequestStats.ForecastRequestStatus;
import org.elasticsearch.xpack.core.ml.job.results.Result;
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
+import org.elasticsearch.xpack.ml.utils.QueryBuilderHelper;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
@@ -96,22 +96,21 @@ public class TransportDeleteForecastAction extends HandledTransportAction listener) {
final String jobId = request.getJobId();
- String forecastsExpression = request.getForecastId();
- final String[] forecastIds = Strings.tokenizeToStringArray(forecastsExpression, ",");
+ final String forecastsExpression = request.getForecastId();
+ final String[] forecastIds = Strings.splitStringByCommaToArray(forecastsExpression);
+
ActionListener forecastStatsHandler = ActionListener.wrap(
searchResponse -> deleteForecasts(searchResponse, request, listener),
e -> listener.onFailure(new ElasticsearchException("An error occurred while searching forecasts to delete", e)));
SearchSourceBuilder source = new SearchSourceBuilder();
- BoolQueryBuilder builder = QueryBuilders.boolQuery();
- BoolQueryBuilder innerBool = QueryBuilders.boolQuery().must(
- QueryBuilders.termQuery(Result.RESULT_TYPE.getPreferredName(), ForecastRequestStats.RESULT_TYPE_VALUE));
- if (Strings.isAllOrWildcard(forecastIds) == false) {
- innerBool.must(QueryBuilders.termsQuery(Forecast.FORECAST_ID.getPreferredName(), new HashSet<>(Arrays.asList(forecastIds))));
- }
-
- source.query(builder.filter(innerBool));
+ BoolQueryBuilder builder = QueryBuilders.boolQuery()
+ .filter(QueryBuilders.termQuery(Result.RESULT_TYPE.getPreferredName(), ForecastRequestStats.RESULT_TYPE_VALUE));
+ QueryBuilderHelper
+ .buildTokenFilterQuery(Forecast.FORECAST_ID.getPreferredName(), forecastIds)
+ .ifPresent(builder::filter);
+ source.query(builder);
SearchRequest searchRequest = new SearchRequest(AnomalyDetectorsIndex.jobResultsAliasedName(jobId));
searchRequest.source(source);
@@ -143,7 +142,7 @@ public class TransportDeleteForecastAction extends HandledTransportAction new ParameterizedMessage("[{}] {}", request.getJobId(), msg));
// We don't care about the bulk response, just that it succeeded
listener.onResponse(new AcknowledgedResponse(true));
}
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetCalendarEventsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetCalendarEventsAction.java
index b88c80d93bc..516ed012c1a 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetCalendarEventsAction.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetCalendarEventsAction.java
@@ -17,6 +17,7 @@ import org.elasticsearch.xpack.core.ml.action.GetCalendarEventsAction;
import org.elasticsearch.xpack.core.ml.calendars.ScheduledEvent;
import org.elasticsearch.xpack.core.ml.job.config.Job;
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
+import org.elasticsearch.xpack.ml.job.persistence.CalendarQueryBuilder;
import org.elasticsearch.xpack.ml.job.persistence.JobConfigProvider;
import org.elasticsearch.xpack.ml.job.persistence.JobResultsProvider;
import org.elasticsearch.xpack.ml.job.persistence.ScheduledEventsQueryBuilder;
@@ -41,17 +42,15 @@ public class TransportGetCalendarEventsAction extends HandledTransportAction listener) {
+ final String[] calendarId = Strings.splitStringByCommaToArray(request.getCalendarId());
ActionListener calendarExistsListener = ActionListener.wrap(
r -> {
ScheduledEventsQueryBuilder query = new ScheduledEventsQueryBuilder()
- .start(request.getStart())
- .end(request.getEnd())
- .from(request.getPageParams().getFrom())
- .size(request.getPageParams().getSize());
-
- if (Strings.isAllOrWildcard(request.getCalendarId()) == false) {
- query.calendarIds(Collections.singletonList(request.getCalendarId()));
- }
+ .start(request.getStart())
+ .end(request.getEnd())
+ .from(request.getPageParams().getFrom())
+ .size(request.getPageParams().getSize())
+ .calendarIds(calendarId);
ActionListener> eventsListener = ActionListener.wrap(
events -> {
@@ -63,8 +62,8 @@ public class TransportGetCalendarEventsAction extends HandledTransportAction {
- Job job = jobBuiler.build();
+ jobBuilder -> {
+ Job job = jobBuilder.build();
jobResultsProvider.scheduledEventsForJob(request.getJobId(), job.getGroups(), query, eventsListener);
},
@@ -74,7 +73,10 @@ public class TransportGetCalendarEventsAction extends HandledTransportAction {
if (groupExists) {
jobResultsProvider.scheduledEventsForJob(
- null, Collections.singletonList(request.getJobId()), query, eventsListener);
+ null,
+ Collections.singletonList(request.getJobId()),
+ query,
+ eventsListener);
} else {
listener.onFailure(ExceptionsHelper.missingJobException(request.getJobId()));
}
@@ -89,16 +91,16 @@ public class TransportGetCalendarEventsAction extends HandledTransportAction listener) {
+ private void checkCalendarExists(String[] calendarId, ActionListener listener) {
if (Strings.isAllOrWildcard(calendarId)) {
listener.onResponse(true);
return;
}
- jobResultsProvider.calendar(calendarId, ActionListener.wrap(
+ jobResultsProvider.calendars(CalendarQueryBuilder.builder().calendarIdTokens(calendarId), ActionListener.wrap(
c -> listener.onResponse(true),
listener::onFailure
));
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetCalendarsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetCalendarsAction.java
index df346958a5d..194aadb68fb 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetCalendarsAction.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetCalendarsAction.java
@@ -14,12 +14,9 @@ import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.ml.action.GetCalendarsAction;
import org.elasticsearch.xpack.core.action.util.PageParams;
-import org.elasticsearch.xpack.core.action.util.QueryPage;
-import org.elasticsearch.xpack.core.ml.calendars.Calendar;
import org.elasticsearch.xpack.ml.job.persistence.CalendarQueryBuilder;
import org.elasticsearch.xpack.ml.job.persistence.JobResultsProvider;
-import java.util.Collections;
public class TransportGetCalendarsAction extends HandledTransportAction {
@@ -34,35 +31,18 @@ public class TransportGetCalendarsAction extends HandledTransportAction listener) {
- final String calendarId = request.getCalendarId();
- if (request.getCalendarId() != null && Strings.isAllOrWildcard(request.getCalendarId()) == false) {
- getCalendar(calendarId, listener);
- } else {
- PageParams pageParams = request.getPageParams();
- if (pageParams == null) {
- pageParams = PageParams.defaultParams();
- }
- getCalendars(pageParams, listener);
+ final String[] calendarIds = Strings.splitStringByCommaToArray(request.getCalendarId());
+ PageParams pageParams = request.getPageParams();
+ if (pageParams == null) {
+ pageParams = PageParams.defaultParams();
}
+ getCalendars(calendarIds, pageParams, listener);
}
- private void getCalendar(String calendarId, ActionListener listener) {
-
- jobResultsProvider.calendar(calendarId, ActionListener.wrap(
- calendar -> {
- QueryPage page = new QueryPage<>(Collections.singletonList(calendar), 1, Calendar.RESULTS_FIELD);
- listener.onResponse(new GetCalendarsAction.Response(page));
- },
- listener::onFailure
- ));
- }
-
- private void getCalendars(PageParams pageParams, ActionListener listener) {
- CalendarQueryBuilder query = new CalendarQueryBuilder().pageParams(pageParams).sort(true);
+ private void getCalendars(String[] idTokens, PageParams pageParams, ActionListener listener) {
+ CalendarQueryBuilder query = new CalendarQueryBuilder().pageParams(pageParams).calendarIdTokens(idTokens).sort(true);
jobResultsProvider.calendars(query, ActionListener.wrap(
- calendars -> {
- listener.onResponse(new GetCalendarsAction.Response(calendars));
- },
+ calendars -> listener.onResponse(new GetCalendarsAction.Response(calendars)),
listener::onFailure
));
}
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetModelSnapshotsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetModelSnapshotsAction.java
index 43a42501bd0..b600982159a 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetModelSnapshotsAction.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportGetModelSnapshotsAction.java
@@ -7,6 +7,7 @@ package org.elasticsearch.xpack.ml.action;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
@@ -40,10 +41,17 @@ public class TransportGetModelSnapshotsAction extends HandledTransportAction listener) {
- logger.debug("Get model snapshots for job {} snapshot ID {}. from = {}, size = {}"
- + " start = '{}', end='{}', sort={} descending={}",
- request.getJobId(), request.getSnapshotId(), request.getPageParams().getFrom(), request.getPageParams().getSize(),
- request.getStart(), request.getEnd(), request.getSort(), request.getDescOrder());
+ logger.debug(
+ () -> new ParameterizedMessage(
+ "Get model snapshots for job {} snapshot ID {}. from = {}, size = {} start = '{}', end='{}', sort={} descending={}",
+ request.getJobId(),
+ request.getSnapshotId(),
+ request.getPageParams().getFrom(),
+ request.getPageParams().getSize(),
+ request.getStart(),
+ request.getEnd(),
+ request.getSort(),
+ request.getDescOrder()));
jobManager.jobExists(request.getJobId(), ActionListener.wrap(
ok -> {
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/CalendarQueryBuilder.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/CalendarQueryBuilder.java
index a338400bb55..58796ac244a 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/CalendarQueryBuilder.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/CalendarQueryBuilder.java
@@ -5,13 +5,16 @@
*/
package org.elasticsearch.xpack.ml.job.persistence;
+import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.cluster.metadata.Metadata;
+import org.elasticsearch.common.Strings;
import org.elasticsearch.index.query.BoolQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermsQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.xpack.core.action.util.PageParams;
import org.elasticsearch.xpack.core.ml.calendars.Calendar;
+import org.elasticsearch.xpack.ml.utils.QueryBuilderHelper;
import java.util.ArrayList;
import java.util.Collections;
@@ -23,6 +26,11 @@ public class CalendarQueryBuilder {
private String jobId;
private List jobGroups = Collections.emptyList();
private boolean sort = false;
+ private String[] idTokens = new String[0];
+
+ public static CalendarQueryBuilder builder() {
+ return new CalendarQueryBuilder();
+ }
/**
* Page the query result
@@ -49,6 +57,19 @@ public class CalendarQueryBuilder {
return this;
}
+ public CalendarQueryBuilder calendarIdTokens(String[] idTokens) {
+ this.idTokens = idTokens;
+ return this;
+ }
+
+ public boolean isForAllCalendars() {
+ return Strings.isAllOrWildcard(idTokens);
+ }
+
+ public Exception buildNotFoundException() {
+ return new ResourceNotFoundException("No calendar with id [" + Strings.arrayToCommaDelimitedString(idTokens) + "]");
+ }
+
/**
* Sort results by calendar_id
* @param sort Sort if true
@@ -60,7 +81,8 @@ public class CalendarQueryBuilder {
}
public SearchSourceBuilder build() {
- QueryBuilder qb;
+ BoolQueryBuilder qb = QueryBuilders.boolQuery()
+ .filter(QueryBuilders.termQuery(Calendar.TYPE.getPreferredName(), Calendar.CALENDAR_TYPE));
List jobIdAndGroups = new ArrayList<>(jobGroups);
if (jobId != null) {
jobIdAndGroups.add(jobId);
@@ -68,12 +90,11 @@ public class CalendarQueryBuilder {
if (jobIdAndGroups.isEmpty() == false) {
jobIdAndGroups.add(Metadata.ALL);
- qb = new BoolQueryBuilder()
- .filter(new TermsQueryBuilder(Calendar.TYPE.getPreferredName(), Calendar.CALENDAR_TYPE))
- .filter(new TermsQueryBuilder(Calendar.JOB_IDS.getPreferredName(), jobIdAndGroups));
- } else {
- qb = new TermsQueryBuilder(Calendar.TYPE.getPreferredName(), Calendar.CALENDAR_TYPE);
+ qb.filter(new TermsQueryBuilder(Calendar.JOB_IDS.getPreferredName(), jobIdAndGroups));
}
+ QueryBuilderHelper
+ .buildTokenFilterQuery(Calendar.ID.getPreferredName(), idTokens)
+ .ifPresent(qb::filter);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().query(qb);
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/JobResultsProvider.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/JobResultsProvider.java
index f96ece95043..6ab99923016 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/JobResultsProvider.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/JobResultsProvider.java
@@ -1037,12 +1037,12 @@ public class JobResultsProvider {
String snapshotId,
Consumer> handler,
Consumer errorHandler) {
- ResultsFilterBuilder fb = new ResultsFilterBuilder();
- if (snapshotId != null && !snapshotId.isEmpty()) {
- fb.term(ModelSnapshotField.SNAPSHOT_ID.getPreferredName(), snapshotId);
- }
+ String[] snapshotIds = Strings.splitStringByCommaToArray(snapshotId);
+ QueryBuilder qb = new ResultsFilterBuilder()
+ .resourceTokenFilters(ModelSnapshotField.SNAPSHOT_ID.getPreferredName(), snapshotIds)
+ .timeRange(Result.TIMESTAMP.getPreferredName(), startEpochMs, endEpochMs)
+ .build();
- QueryBuilder qb = fb.timeRange(Result.TIMESTAMP.getPreferredName(), startEpochMs, endEpochMs).build();
modelSnapshots(jobId, from, size, sortField, sortDescending, qb, handler, errorHandler);
}
@@ -1298,7 +1298,7 @@ public class JobResultsProvider {
handler.onResponse(new QueryPage<>(Collections.emptyList(), 0, ScheduledEvent.RESULTS_FIELD));
return;
}
- List calendarIds = calendars.results().stream().map(Calendar::getId).collect(Collectors.toList());
+ String[] calendarIds = calendars.results().stream().map(Calendar::getId).toArray(String[]::new);
queryBuilder.calendarIds(calendarIds);
scheduledEvents(queryBuilder, handler);
},
@@ -1487,6 +1487,10 @@ public class JobResultsProvider {
List calendars = new ArrayList<>();
SearchHit[] hits = response.getHits().getHits();
try {
+ if (queryBuilder.isForAllCalendars() == false && hits.length == 0) {
+ listener.onFailure(queryBuilder.buildNotFoundException());
+ return;
+ }
for (SearchHit hit : hits) {
calendars.add(MlParserUtils.parse(hit, Calendar.LENIENT_PARSER).build());
}
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ResultsFilterBuilder.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ResultsFilterBuilder.java
index 745793d9722..287aa9d22a3 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ResultsFilterBuilder.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ResultsFilterBuilder.java
@@ -12,6 +12,7 @@ import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.xpack.core.ml.job.results.Result;
+import org.elasticsearch.xpack.ml.utils.QueryBuilderHelper;
import java.util.ArrayList;
import java.util.List;
@@ -88,6 +89,11 @@ public class ResultsFilterBuilder {
return this;
}
+ public ResultsFilterBuilder resourceTokenFilters(String fieldName, String[] tokens) {
+ QueryBuilderHelper.buildTokenFilterQuery(fieldName, tokens).ifPresent(this::addQuery);
+ return this;
+ }
+
public ResultsFilterBuilder resultType(String resultType) {
return term(Result.RESULT_TYPE.getPreferredName(), resultType);
}
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ScheduledEventsQueryBuilder.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ScheduledEventsQueryBuilder.java
index 618e680b0a6..c57be5d8959 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ScheduledEventsQueryBuilder.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ScheduledEventsQueryBuilder.java
@@ -6,16 +6,13 @@
package org.elasticsearch.xpack.ml.job.persistence;
import org.elasticsearch.index.query.BoolQueryBuilder;
-import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
-import org.elasticsearch.index.query.TermsQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.xpack.core.ml.calendars.Calendar;
import org.elasticsearch.xpack.core.ml.calendars.ScheduledEvent;
+import org.elasticsearch.xpack.ml.utils.QueryBuilderHelper;
-import java.util.ArrayList;
-import java.util.List;
/**
* Query builder for {@link ScheduledEvent}s
@@ -27,11 +24,15 @@ public class ScheduledEventsQueryBuilder {
private Integer from = 0;
private Integer size = DEFAULT_SIZE;
- private List calendarIds;
+ private String[] calendarIds;
private String start;
private String end;
- public ScheduledEventsQueryBuilder calendarIds(List calendarIds) {
+ public static ScheduledEventsQueryBuilder builder() {
+ return new ScheduledEventsQueryBuilder();
+ }
+
+ public ScheduledEventsQueryBuilder calendarIds(String[] calendarIds) {
this.calendarIds = calendarIds;
return this;
}
@@ -72,46 +73,31 @@ public class ScheduledEventsQueryBuilder {
}
public SearchSourceBuilder build() {
- List queries = new ArrayList<>();
-
+ BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
+ .filter(QueryBuilders.termQuery(ScheduledEvent.TYPE.getPreferredName(), ScheduledEvent.SCHEDULED_EVENT_TYPE));
if (start != null) {
RangeQueryBuilder startQuery = QueryBuilders.rangeQuery(ScheduledEvent.END_TIME.getPreferredName());
startQuery.gt(start);
- queries.add(startQuery);
+ boolQueryBuilder.filter(startQuery);
}
if (end != null) {
RangeQueryBuilder endQuery = QueryBuilders.rangeQuery(ScheduledEvent.START_TIME.getPreferredName());
endQuery.lt(end);
- queries.add(endQuery);
+ boolQueryBuilder.filter(endQuery);
}
- if (calendarIds != null && calendarIds.isEmpty() == false) {
- queries.add(new TermsQueryBuilder(Calendar.ID.getPreferredName(), calendarIds));
- }
+ QueryBuilderHelper.buildTokenFilterQuery(Calendar.ID.getPreferredName(), calendarIds).ifPresent(boolQueryBuilder::filter);
- QueryBuilder typeQuery = new TermsQueryBuilder(ScheduledEvent.TYPE.getPreferredName(), ScheduledEvent.SCHEDULED_EVENT_TYPE);
-
- SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
- searchSourceBuilder.sort(ScheduledEvent.START_TIME.getPreferredName());
- searchSourceBuilder.sort(ScheduledEvent.DESCRIPTION.getPreferredName());
+ SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource()
+ .sort(ScheduledEvent.START_TIME.getPreferredName())
+ .sort(ScheduledEvent.DESCRIPTION.getPreferredName())
+ .query(boolQueryBuilder);
if (from != null) {
searchSourceBuilder.from(from);
}
if (size != null) {
searchSourceBuilder.size(size);
}
-
- if (queries.isEmpty()) {
- searchSourceBuilder.query(typeQuery);
- } else {
- BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
- boolQueryBuilder.filter(typeQuery);
- for (QueryBuilder query : queries) {
- boolQueryBuilder.filter(query);
- }
- searchSourceBuilder.query(boolQueryBuilder);
- }
-
return searchSourceBuilder;
}
}
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/QueryBuilderHelper.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/QueryBuilderHelper.java
new file mode 100644
index 00000000000..811fb742ad9
--- /dev/null
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/QueryBuilderHelper.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.ml.utils;
+
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.regex.Regex;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.TermsQueryBuilder;
+import org.elasticsearch.index.query.WildcardQueryBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+public final class QueryBuilderHelper {
+
+ private QueryBuilderHelper() { }
+
+ /**
+ * Helper function for adding OR type queries for a given identity field.
+ *
+ * The filter consists of should clauses (i.e. "or" boolean queries).
+ *
+ * - When a token is a wildcard token, a wildcard query is added
+ * - When a token is NOT a wildcard, a term query is added
+ *
+ * @param identityField The field to query for the tokens
+ * @param tokens A non-null collection of tokens. Can include wildcards
+ * @return An optional boolean query builder filled with "should" queries for the supplied tokens and identify field
+ */
+ public static Optional buildTokenFilterQuery(String identityField, String[] tokens) {
+ if (Strings.isAllOrWildcard(tokens)) {
+ return Optional.empty();
+ }
+
+ BoolQueryBuilder shouldQueries = new BoolQueryBuilder();
+ List terms = new ArrayList<>();
+ for (String token : tokens) {
+ if (Regex.isSimpleMatchPattern(token)) {
+ shouldQueries.should(new WildcardQueryBuilder(identityField, token));
+ } else {
+ terms.add(token);
+ }
+ }
+
+ if (terms.isEmpty() == false) {
+ shouldQueries.should(new TermsQueryBuilder(identityField, terms));
+ }
+
+ if (shouldQueries.should().isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.of(shouldQueries);
+ }
+
+}
diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/ml/calendar_crud.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/ml/calendar_crud.yml
index b4b5c9ad9da..ba85f9d093f 100644
--- a/x-pack/plugin/src/test/resources/rest-api-spec/test/ml/calendar_crud.yml
+++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/ml/calendar_crud.yml
@@ -1,5 +1,9 @@
---
"Test calendar CRUD":
+ - do:
+ ml.get_calendars:
+ calendar_id: _all
+ - match: { count: 0 }
- do:
ml.put_job: