diff --git a/docs/painless/painless-contexts/painless-watcher-condition-context.asciidoc b/docs/painless/painless-contexts/painless-watcher-condition-context.asciidoc index 713bcf32dae..91ab51561ef 100644 --- a/docs/painless/painless-contexts/painless-watcher-condition-context.asciidoc +++ b/docs/painless/painless-contexts/painless-watcher-condition-context.asciidoc @@ -1,38 +1,136 @@ [[painless-watcher-condition-context]] === Watcher condition context -Use a Painless script as a {xpack-ref}/condition-script.html[watcher condition] -to test if a response is necessary. +Use a Painless script as a {xpack-ref}/condition-script.html[watch condition] +that determines whether to execute a watch or a particular action within a watch. +Condition scripts return a Boolean value to indicate the status of the condition. -*Variables* - -`params` (`Map`, read-only):: - User-defined parameters passed in as part of the query. - -`ctx['watch_id']` (`String`, read-only):: - The id of the watch. - -`ctx['execution_time']` (`ZonedDateTime`, read-only):: - The start time for the watch. - -`ctx['trigger']['scheduled_time']` (`ZonedDateTime`, read-only):: - The scheduled trigger time for the watch. - -`ctx['trigger']['triggered_time']` (`ZonedDateTime`, read-only):: - The actual trigger time for the watch. - -`ctx['metadata']` (`Map`, read-only):: - Any metadata associated with the watch. - -`ctx['payload']` (`Map`, read-only):: - The accessible watch data based upon the - {xpack-ref}/input.html[watch input]. +include::painless-watcher-context-variables.asciidoc[] *Return* `boolean`:: - Expects `true` if the condition is met, and `false` otherwise. + Expects `true` if the condition is met, and `false` if it is not. *API* -The standard <> is available. \ No newline at end of file +The standard <> is available. + +*Example* + +[source,Painless] +---- +POST _watcher/watch/_execute +{ + "watch" : { + "trigger" : { "schedule" : { "interval" : "24h" } }, + "input" : { + "search" : { + "request" : { + "indices" : [ "seats" ], + "body" : { + "query" : { + "term": { "sold": "true"} + }, + "aggs" : { + "theatres" : { + "terms" : { "field" : "play" }, + "aggs" : { + "money" : { + "sum": { "field" : "cost" } + } + } + } + } + } + } + } + }, + "condition" : { + "script" : + """ + return ctx.payload.aggregations.theatres.buckets.stream() <1> + .filter(theatre -> theatre.money.value < 15000 || + theatre.money.value > 50000) <2> + .count() > 0 <3> + """ + }, + "actions" : { + "my_log" : { + "logging" : { + "text" : "The output of the search was : {{ctx.payload.aggregations.theatres.buckets}}" + } + } + } + } +} +---- + +<1> The Java Stream API is used in the condition. This API allows manipulation of +the elements of the list in a pipeline. +<2> The stream filter removes items that do not meet the filter criteria. +<3> If there is at least one item in the list, the condition evaluates to true and the watch is executed. + +The following action condition script controls execution of the my_log action based +on the value of the seats sold for the plays in the data set. The script aggregates +the total sold seats for each play and returns true if there is at least one play +that has sold over $50,000. + +[source,Painless] +---- +POST _watcher/watch/_execute +{ + "watch" : { + "trigger" : { "schedule" : { "interval" : "24h" } }, + "input" : { + "search" : { + "request" : { + "indices" : [ "seats" ], + "body" : { + "query" : { + "term": { "sold": "true"} + }, + "aggs" : { + "theatres" : { + "terms" : { "field" : "play" }, + "aggs" : { + "money" : { + "sum": { "field" : "cost" } + } + } + } + } + } + } + } + }, + "actions" : { + "my_log" : { + "condition": { <1> + "script" : + """ + return ctx.payload.aggregations.theatres.buckets.stream() + .anyMatch(theatre -> theatre.money.value > 50000) <2> + """ + }, + "logging" : { + "text" : "At least one play has grossed over $50,000: {{ctx.payload.aggregations.theatres.buckets}}" + } + } + } + } +} +---- + +This example uses a nearly identical condition as the previous example. The +differences below are subtle and are worth calling out. + +<1> The location of the condition is no longer at the top level, but is within +an individual action. +<2> Instead of a filter, `anyMatch` is used to return a boolean value + +The following example shows scripted watch and action conditions within the +context of a complete watch. This watch also uses a scripted +<>. + +include::painless-watcher-context-example.asciidoc[] diff --git a/docs/painless/painless-contexts/painless-watcher-context-example.asciidoc b/docs/painless/painless-contexts/painless-watcher-context-example.asciidoc new file mode 100644 index 00000000000..2d2e3993e32 --- /dev/null +++ b/docs/painless/painless-contexts/painless-watcher-context-example.asciidoc @@ -0,0 +1,157 @@ +[source,Painless] +---- +POST _watcher/watch/_execute +{ + "watch" : { + "metadata" : { "high_threshold": 50000, "low_threshold": 15000 }, + "trigger" : { "schedule" : { "interval" : "24h" } }, + "input" : { + "search" : { + "request" : { + "indices" : [ "seats" ], + "body" : { + "query" : { + "term": { "sold": "true"} + }, + "aggs" : { + "theatres" : { + "terms" : { "field" : "play" }, + "aggs" : { + "money" : { + "sum": { "field" : "cost" } + } + } + } + } + } + } + } + }, + "condition" : { + "script" : + """ + return ctx.payload.aggregations.theatres.buckets.stream() + .anyMatch(theatre -> theatre.money.value < ctx.metadata.low_threshold || + theatre.money.value > ctx.metadata.high_threshold) + """ + }, + "transform" : { + "script": + """ + return [ + 'money_makers': ctx.payload.aggregations.theatres.buckets.stream() + .filter(t -> { + return t.money.value > ctx.metadata.high_threshold + }) + .map(t -> { + return ['play': t.key, 'total_value': t.money.value ] + }).collect(Collectors.toList()), + 'duds' : ctx.payload.aggregations.theatres.buckets.stream() + .filter(t -> { + return t.money.value < ctx.metadata.low_threshold + }) + .map(t -> { + return ['play': t.key, 'total_value': t.money.value ] + }).collect(Collectors.toList()) + ] + """ + }, + "actions" : { + "log_money_makers" : { + "condition": { + "script" : "return ctx.payload.money_makers.size() > 0" + }, + "transform": { + "script" : + """ + def formatter = NumberFormat.getCurrencyInstance(); + return [ + 'plays_value': ctx.payload.money_makers.stream() + .map(t-> formatter.format(t.total_value) + ' for the play ' + t.play) + .collect(Collectors.joining(", ")) + ] + """ + }, + "logging" : { + "text" : "The following plays contain the highest grossing total income: {{ctx.payload.plays_value}}" + } + }, + "log_duds" : { + "condition": { + "script" : "return ctx.payload.duds.size() > 0" + }, + "transform": { + "script" : + """ + def formatter = NumberFormat.getCurrencyInstance(); + return [ + 'plays_value': ctx.payload.duds.stream() + .map(t-> formatter.format(t.total_value) + ' for the play ' + t.play) + .collect(Collectors.joining(", ")) + ] + """ + }, + "logging" : { + "text" : "The following plays need more advertising due to their low total income: {{ctx.payload.plays_value}}" + } + } + } + } +} +---- + +The following example shows the use of metadata and transforming dates into a readable format. + +[source,Painless] +---- +POST _xpack/watcher/watch/_execute +{ + "watch" : { + "metadata" : { "min_hits": 10000 }, + "trigger" : { "schedule" : { "interval" : "24h" } }, + "input" : { + "search" : { + "request" : { + "indices" : [ "seats" ], + "body" : { + "query" : { + "term": { "sold": "true"} + }, + "aggs" : { + "theatres" : { + "terms" : { "field" : "play" }, + "aggs" : { + "money" : { + "sum": { "field" : "cost" } + } + } + } + } + } + } + } + }, + "condition" : { + "script" : + """ + return ctx.payload.hits.total > ctx.metadata.min_hits + """ + }, + "transform" : { + "script" : + """ + def theDate = ZonedDateTime.ofInstant(ctx.execution_time.toInstant(), ctx.execution_time.getZone()); + return ['human_date': DateTimeFormatter.RFC_1123_DATE_TIME.format(theDate), + 'aggregations': ctx.payload.aggregations] + """ + }, + "actions" : { + "my_log" : { + "logging" : { + "text" : "The watch was successfully executed on {{ctx.payload.human_date}} and contained {{ctx.payload.aggregations.theatres.buckets.size}} buckets" + } + } + } + } +} +---- diff --git a/docs/painless/painless-contexts/painless-watcher-context-variables.asciidoc b/docs/painless/painless-contexts/painless-watcher-context-variables.asciidoc new file mode 100644 index 00000000000..addfd11cab9 --- /dev/null +++ b/docs/painless/painless-contexts/painless-watcher-context-variables.asciidoc @@ -0,0 +1,40 @@ +The following variables are available in all watcher contexts. + +*Variables* + +`params` (`Map`, read-only):: + User-defined parameters passed in as part of the query. + +`ctx['watch_id']` (`String`, read-only):: + The id of the watch. + +`ctx['id']` (`String`, read-only):: + The server generated unique identifer for the run watch. + +`ctx['metadata']` (`Map`, read-only):: + Metadata can be added to the top level of the watch definition. This + is user defined and is typically used to consolidate duplicate values + in a watch. + +`ctx['execution_time']` (`ZonedDateTime`, read-only):: + The time the watch began execution. + +`ctx['trigger']['scheduled_time']` (`ZonedDateTime`, read-only):: + The scheduled trigger time for the watch. This is the time the + watch should be executed. + +`ctx['trigger']['triggered_time']` (`ZonedDateTime`, read-only):: + The actual trigger time for the watch. This is the time the + watch was triggered for execution. + +`ctx['payload']` (`Map`, read-only):: + The accessible watch data based upon the + {xpack-ref}/input.html[watch input]. + +*API* + + +The standard <> is available. + +To run this example, first follow the steps in +<>. diff --git a/docs/painless/painless-contexts/painless-watcher-transform-context.asciidoc b/docs/painless/painless-contexts/painless-watcher-transform-context.asciidoc index 27cb4eb1505..92012720aa6 100644 --- a/docs/painless/painless-contexts/painless-watcher-transform-context.asciidoc +++ b/docs/painless/painless-contexts/painless-watcher-transform-context.asciidoc @@ -1,33 +1,11 @@ [[painless-watcher-transform-context]] === Watcher transform context -Use a Painless script to {xpack-ref}/transform-script.html[transform] watch -data into a new payload for use in a response to a condition. - -*Variables* - -`params` (`Map`, read-only):: - User-defined parameters passed in as part of the query. - -`ctx['watch_id']` (`String`, read-only):: - The id of the watch. - -`ctx['execution_time']` (`ZonedDateTime`, read-only):: - The start time for the watch. - -`ctx['trigger']['scheduled_time']` (`ZonedDateTime`, read-only):: - The scheduled trigger time for the watch. - -`ctx['trigger']['triggered_time']` (`ZonedDateTime`, read-only):: - The actual trigger time for the watch. - -`ctx['metadata']` (`Map`, read-only):: - Any metadata associated with the watch. - -`ctx['payload']` (`Map`, read-only):: - The accessible watch data based upon the - {xpack-ref}/input.html[watch input]. +Use a Painless script as a {xpack-ref}/transform-script.html[watch transform] +to transform a payload into a new payload for further use in the watch. +Transform scripts return an Object value of the new payload. +include::painless-watcher-context-variables.asciidoc[] *Return* @@ -36,4 +14,142 @@ data into a new payload for use in a response to a condition. *API* -The standard <> is available. \ No newline at end of file +The standard <> is available. + +*Example* + +[source,Painless] +---- +POST _watcher/watch/_execute +{ + "watch" : { + "trigger" : { "schedule" : { "interval" : "24h" } }, + "input" : { + "search" : { + "request" : { + "indices" : [ "seats" ], + "body" : { + "query" : { "term": { "sold": "true"} }, + "aggs" : { + "theatres" : { + "terms" : { "field" : "play" }, + "aggs" : { + "money" : { + "sum": { "field" : "cost" } + } + } + } + } + } + } + } + }, + "transform" : { + "script": + """ + return [ + 'money_makers': ctx.payload.aggregations.theatres.buckets.stream() <1> + .filter(t -> { <2> + return t.money.value > 50000 + }) + .map(t -> { <3> + return ['play': t.key, 'total_value': t.money.value ] + }).collect(Collectors.toList()), <4> + 'duds' : ctx.payload.aggregations.theatres.buckets.stream() <5> + .filter(t -> { + return t.money.value < 15000 + }) + .map(t -> { + return ['play': t.key, 'total_value': t.money.value ] + }).collect(Collectors.toList()) + ] + """ + }, + "actions" : { + "my_log" : { + "logging" : { + "text" : "The output of the payload was transformed to {{ctx.payload}}" + } + } + } + } +} +---- + +<1> The Java Stream API is used in the transform. This API allows manipulation of +the elements of the list in a pipeline. +<2> The stream filter removes items that do not meet the filter criteria. +<3> The stream map transforms each element into a new object. +<4> The collector reduces the stream to a `java.util.List`. +<5> This is done again for the second set of values in the transform. + +The following action transform changes each value in the mod_log action into a `String`. +This transform does not change the values in the unmod_log action. + +[source,Painless] +---- +POST _watcher/watch/_execute +{ + "watch" : { + "trigger" : { "schedule" : { "interval" : "24h" } }, + "input" : { + "search" : { + "request" : { + "indices" : [ "seats" ], + "body" : { + "query" : { + "term": { "sold": "true"} + }, + "aggs" : { + "theatres" : { + "terms" : { "field" : "play" }, + "aggs" : { + "money" : { + "sum": { "field" : "cost" } + } + } + } + } + } + } + } + }, + "actions" : { + "mod_log" : { + "transform": { <1> + "script" : + """ + def formatter = NumberFormat.getCurrencyInstance(); + return [ + 'msg': ctx.payload.aggregations.theatres.buckets.stream() + .map(t-> formatter.format(t.money.value) + ' for the play ' + t.key) + .collect(Collectors.joining(", ")) + ] + """ + }, + "logging" : { + "text" : "The output of the payload was transformed to: {{ctx.payload.msg}}" + } + }, + "unmod_log" : { <2> + "logging" : { + "text" : "The output of the payload was not transformed and this value should not exist: {{ctx.payload.msg}}" + } + } + } + } +} +---- + +This example uses the streaming API in a very similar manner. The differences below are +subtle and worth calling out. + +<1> The location of the transform is no longer at the top level, but is within +an individual action. +<2> A second action that does not transform the payload is given for reference. + +The following example shows scripted watch and action transforms within the +context of a complete watch. This watch also uses a scripted +<>. + +include::painless-watcher-context-example.asciidoc[] diff --git a/docs/painless/painless-contexts/painless-weight-context.asciidoc b/docs/painless/painless-contexts/painless-weight-context.asciidoc index 319b7999aa8..a3088db5f3a 100644 --- a/docs/painless/painless-contexts/painless-weight-context.asciidoc +++ b/docs/painless/painless-contexts/painless-weight-context.asciidoc @@ -42,4 +42,4 @@ Queries that contain multiple terms calculate a separate weight for each term. *API* -The standard <> is available. \ No newline at end of file +The standard <> is available.