From e179fd1274331e30bc3ddfb1ab789fb0d38c92bd Mon Sep 17 00:00:00 2001 From: Jim Ferenczi Date: Fri, 30 Nov 2018 18:02:37 +0100 Subject: [PATCH] Add support for rest_total_hits_as_int in watcher (#36035) This change adds the support for rest_total_hits_as_int in the watcher search inputs. Setting this parameter in the request will transform the search response to contain the total hits as a number (instead of an object). Note that this parameter is currently a noop since #35849 is not merged. Closes #36008 --- .../input/search/ExecutableSearchInput.java | 12 +- .../search/WatcherSearchTemplateRequest.java | 41 +++++- .../test/mustache/30_search_input.yml | 126 +++++++++++++++++ .../rest-api-spec/test/painless/10_basic.yml | 128 ++++++++++++++++++ 4 files changed, 300 insertions(+), 7 deletions(-) diff --git a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/search/ExecutableSearchInput.java b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/search/ExecutableSearchInput.java index 5654ccca477..50b33d5247b 100644 --- a/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/search/ExecutableSearchInput.java +++ b/x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/search/ExecutableSearchInput.java @@ -29,6 +29,7 @@ import org.elasticsearch.xpack.watcher.support.XContentFilterKeysUtils; import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateRequest; import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateService; +import java.util.Collections; import java.util.Map; import static org.elasticsearch.xpack.watcher.input.search.SearchInput.TYPE; @@ -37,11 +38,12 @@ import static org.elasticsearch.xpack.watcher.input.search.SearchInput.TYPE; * An input that executes search and returns the search response as the initial payload */ public class ExecutableSearchInput extends ExecutableInput { - public static final SearchType DEFAULT_SEARCH_TYPE = SearchType.QUERY_THEN_FETCH; private static final Logger logger = LogManager.getLogger(ExecutableSearchInput.class); + private static final Params EMPTY_PARAMS = new MapParams(Collections.emptyMap()); + private final Client client; private final WatcherSearchTemplateService searchTemplateService; private final TimeValue timeout; @@ -86,7 +88,13 @@ public class ExecutableSearchInput extends ExecutableInputtrue) or as an object (false). + * Defaults to false. + */ + public void setRestTotalHitsAsInt(boolean value) { + this.restTotalHitsAsInt = restTotalHitsAsInt; + } + public BytesReference getSearchSource() { return searchSource; } @@ -129,6 +143,9 @@ public class WatcherSearchTemplateRequest implements ToXContentObject { if (types != null) { builder.array(TYPES_FIELD.getPreferredName(), types); } + if (restTotalHitsAsInt) { + builder.field(REST_TOTAL_HITS_AS_INT_FIELD.getPreferredName(), restTotalHitsAsInt); + } if (searchSource != null && searchSource.length() > 0) { try (InputStream stream = searchSource.streamInput()) { builder.rawField(BODY_FIELD.getPreferredName(), stream); @@ -167,6 +184,7 @@ public class WatcherSearchTemplateRequest implements ToXContentObject { IndicesOptions indicesOptions = DEFAULT_INDICES_OPTIONS; BytesReference searchSource = null; Script template = null; + boolean totalHitsAsInt = false; XContentParser.Token token; String currentFieldName = null; @@ -263,10 +281,19 @@ public class WatcherSearchTemplateRequest implements ToXContentObject { types.addAll(Arrays.asList(Strings.delimitedListToStringArray(typesStr, ",", " \t"))); } else if (SEARCH_TYPE_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { searchType = SearchType.fromString(parser.text().toLowerCase(Locale.ROOT)); + } else if (REST_TOTAL_HITS_AS_INT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + totalHitsAsInt = parser.booleanValue(); } else { throw new ElasticsearchParseException("could not read search request. unexpected string field [" + currentFieldName + "]"); } + } else if (token == XContentParser.Token.VALUE_BOOLEAN) { + if (REST_TOTAL_HITS_AS_INT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + totalHitsAsInt = parser.booleanValue(); + } else { + throw new ElasticsearchParseException("could not read search request. unexpected boolean field [" + + currentFieldName + "]"); + } } else { throw new ElasticsearchParseException("could not read search request. unexpected token [" + token + "]"); } @@ -276,8 +303,10 @@ public class WatcherSearchTemplateRequest implements ToXContentObject { searchSource = BytesArray.EMPTY; } - return new WatcherSearchTemplateRequest(indices.toArray(new String[0]), types.toArray(new String[0]), searchType, - indicesOptions, searchSource, template); + WatcherSearchTemplateRequest request = new WatcherSearchTemplateRequest(indices.toArray(new String[0]), + types.toArray(new String[0]), searchType, indicesOptions, searchSource, template); + request.setRestTotalHitsAsInt(totalHitsAsInt); + return request; } @Override @@ -291,13 +320,14 @@ public class WatcherSearchTemplateRequest implements ToXContentObject { Objects.equals(searchType, other.searchType) && Objects.equals(indicesOptions, other.indicesOptions) && Objects.equals(searchSource, other.searchSource) && - Objects.equals(template, other.template); + Objects.equals(template, other.template) && + Objects.equals(restTotalHitsAsInt, other.restTotalHitsAsInt); } @Override public int hashCode() { - return Objects.hash(indices, types, searchType, indicesOptions, searchSource, template); + return Objects.hash(indices, types, searchType, indicesOptions, searchSource, template, restTotalHitsAsInt); } private static final ParseField INDICES_FIELD = new ParseField("indices"); @@ -309,6 +339,7 @@ public class WatcherSearchTemplateRequest implements ToXContentObject { private static final ParseField IGNORE_UNAVAILABLE_FIELD = new ParseField("ignore_unavailable"); private static final ParseField ALLOW_NO_INDICES_FIELD = new ParseField("allow_no_indices"); private static final ParseField TEMPLATE_FIELD = new ParseField("template"); + private static final ParseField REST_TOTAL_HITS_AS_INT_FIELD = new ParseField("rest_total_hits_as_int"); public static final IndicesOptions DEFAULT_INDICES_OPTIONS = IndicesOptions.lenientExpandOpen(); } diff --git a/x-pack/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/mustache/30_search_input.yml b/x-pack/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/mustache/30_search_input.yml index 28e2788fedb..1b023dbedec 100644 --- a/x-pack/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/mustache/30_search_input.yml +++ b/x-pack/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/mustache/30_search_input.yml @@ -163,3 +163,129 @@ setup: - match: { "watch_record.result.input.search.request.body.query.bool.must.0.term.value": "val_2" } - match: { "watch_record.result.input.search.request.template.id": "search-template" } - match: { "watch_record.result.input.search.request.template.params.num": 2 } + +--- +"Test search input mustache integration (using request body and rest_total_hits_as_int)": + - skip: + version: " - 6.99.99" + reason: "rest_total_hits_as_int support was added in 7.0" + - do: + xpack.watcher.execute_watch: + body: > + { + "trigger_data" : { + "scheduled_time" : "2015-01-04T00:00:00" + }, + "watch" : { + "trigger" : { "schedule" : { "interval" : "10s" } }, + "actions" : { + "dummy" : { + "logging" : { + "text" : "executed!" + } + } + }, + "input" : { + "search" : { + "request" : { + "indices" : "idx", + "rest_total_hits_as_int": true, + "body" : { + "query" : { + "bool" : { + "filter" : [ + { + "range" : { + "date" : { + "lte" : "{{ctx.trigger.scheduled_time}}", + "gte" : "{{ctx.trigger.scheduled_time}}||-3d" + } + } + } + ] + } + } + } + } + } + } + } + } + - match: { "watch_record.result.input.type": "search" } + - match: { "watch_record.result.input.status": "success" } + - match: { "watch_record.result.input.payload.hits.total": 4 } + # makes sure that the mustache template snippets have been resolved correctly: + - match: { "watch_record.result.input.search.request.body.query.bool.filter.0.range.date.gte": "2015-01-04T00:00:00.000Z||-3d" } + - match: { "watch_record.result.input.search.request.body.query.bool.filter.0.range.date.lte": "2015-01-04T00:00:00.000Z" } + +--- +"Test search input mustache integration (using request template and rest_total_hits_as_int)": + - skip: + version: " - 6.99.99" + reason: "rest_total_hits_as_int support was added in 7.0" + + - do: + put_script: + id: "search-template" + body: { + "script": { + "lang": "mustache", + "source": { + "query" : { + "bool" : { + "must" : [ + { + "term" : { + "value" : "val_{{num}}" + } + } + ] + } + } + } + } + } + - match: { acknowledged: true } + + - do: + xpack.watcher.execute_watch: + body: > + { + "trigger_data" : { + "scheduled_time" : "2015-01-04T00:00:00" + }, + "watch" : { + "trigger" : { "schedule" : { "interval" : "10s" } }, + "actions" : { + "dummy" : { + "logging" : { + "text" : "executed!" + } + } + }, + "input" : { + "search" : { + "request" : { + "rest_total_hits_as_int": true, + "indices" : "idx", + "template" : { + "id": "search-template", + "params": { + "num": 2 + } + } + } + } + } + } + } + - match: { "watch_record.result.input.type": "search" } + - match: { "watch_record.result.input.status": "success" } + - match: { "watch_record.result.input.payload.hits.total": 1 } + - match: { "watch_record.result.input.payload.hits.hits.0._id": "2" } + # makes sure that the mustache template snippets have been resolved correctly: + - match: { "watch_record.result.input.search.request.body.query.bool.must.0.term.value": "val_2" } + - match: { "watch_record.result.input.search.request.template.id": "search-template" } + - match: { "watch_record.result.input.search.request.template.params.num": 2 } + + diff --git a/x-pack/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/painless/10_basic.yml b/x-pack/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/painless/10_basic.yml index 5996273435e..82764d71667 100644 --- a/x-pack/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/painless/10_basic.yml +++ b/x-pack/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/painless/10_basic.yml @@ -119,3 +119,131 @@ - match: { "watch_record.result.actions.0.status" : "simulated" } - match: { "watch_record.result.actions.0.type" : "email" } - match: { "watch_record.result.actions.0.email.message.subject" : "404 recently encountered" } + +--- +"Test execute watch api with rest_total_hits_as_int": + - skip: + version: " - 6.99.99" + reason: "rest_total_hits_as_int support was added in 7.0" + + - do: + cluster.health: + wait_for_status: green + + - do: + xpack.watcher.put_watch: + id: "my_exe_watch" + body: > + { + "trigger" : { + "schedule" : { "cron" : "0 0 0 1 * ? 2099" } + }, + "input" : { + "chain" : { + "inputs" : [ + { + "first" : { + "search" : { + "request" : { + "indices" : [ "logstash*" ], + "rest_total_hits_as_int": true, + "body" : { + "query" : { + "bool": { + "must" : { + "match": { + "response": 404 + } + }, + "filter": { + "range": { + "@timestamp" : { + "from": "{{ctx.trigger.scheduled_time}}||-5m", + "to": "{{ctx.trigger.triggered_time}}" + } + } + } + } + } + } + } + } + } + }, + { + "second" : { + "transform" : { + "script" : { + "source": "return [ 'hits' : [ 'total' : ctx.payload.first.hits.total ]]" + } + } + } + } + ] + } + }, + "condition" : { + "script" : { + "source" : "ctx.payload.hits.total > 1", + "lang" : "painless" + } + }, + "actions" : { + "email_admin" : { + "transform" : { + "script" : { + "source" : "return ['foo': 'bar']", + "lang" : "painless" + } + }, + "email" : { + "to" : "someone@domain.host.com", + "subject" : "404 recently encountered" + } + } + } + } + - match: { _id: "my_exe_watch" } + + - do: + xpack.watcher.get_watch: + id: "my_exe_watch" + + - match: { _id: "my_exe_watch" } + - match: { watch.actions.email_admin.transform.script.source: "return ['foo': 'bar']" } + - match: { watch.input.chain.inputs.1.second.transform.script.source: "return [ 'hits' : [ 'total' : ctx.payload.first.hits.total ]]" } + + - do: + xpack.watcher.execute_watch: + id: "my_exe_watch" + body: > + { + "trigger_data" : { + "scheduled_time" : "2015-05-05T20:58:02.443Z", + "triggered_time" : "2015-05-05T20:58:02.443Z" + }, + "alternative_input" : { + "foo" : "bar" + }, + "ignore_condition" : true, + "action_modes" : { + "_all" : "force_simulate" + }, + "record_execution" : true + } + - match: { "watch_record.watch_id": "my_exe_watch" } + - match: { "watch_record.state": "executed" } + - match: { "watch_record.trigger_event.type": "manual" } + - match: { "watch_record.trigger_event.triggered_time": "2015-05-05T20:58:02.443Z" } + - match: { "watch_record.trigger_event.manual.schedule.scheduled_time": "2015-05-05T20:58:02.443Z" } + - match: { "watch_record.result.input.type": "simple" } + - match: { "watch_record.result.input.status": "success" } + - match: { "watch_record.result.input.payload.foo": "bar" } + - match: { "watch_record.result.condition.type": "always" } + - match: { "watch_record.result.condition.status": "success" } + - match: { "watch_record.result.condition.met": true } + - match: { "watch_record.result.actions.0.id" : "email_admin" } + - match: { "watch_record.result.actions.0.status" : "simulated" } + - match: { "watch_record.result.actions.0.type" : "email" } + - match: { "watch_record.result.actions.0.email.message.subject" : "404 recently encountered" } +