Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ResponseOps] adds mustache lambdas and array.asJSON #150572

Merged
merged 3 commits into from
Apr 24, 2023

Conversation

pmuellr
Copy link
Member

@pmuellr pmuellr commented Feb 8, 2023

Cloud CI deployment: https://kibana-pr-150572.kb.us-west2.gcp.elastic-cloud.com:9243/

Summary

Extends mustache support in actions. Based on some work in an on-week project; see #146487 for more details.

Also see #84217 as a issue collecting requirements for mustache.

Release note

Adds Mustache lambdas for alerting actions to format dates with {{#FormatDate}}, evaluate math expressions with {{#EvalMath}}, and provide easier JSON formatting with {{#ParseHjson}} and a new asJSON property added to arrays.

lambda FormatDate to format date strings

This lambda uses the moment npm package to parse and generate date strings.

The lambda expects a parseable date string as it's body, and optional time zone and date format, separated by semi-colons:

{{#FormatDate}} {{timeStamp}} ; America/New_York ; dddd MMM Do YYYY HH:mm:ss.SSS {{/FormatDate}}

Given the variable timeStamp with a value of 2022-11-29T15:52:44Z, this would render as

Tuesday Nov 29th 2022 10:52:44.000

lambda ParseHjson to parse Hjson

Hjson is a version of JSON that is not so strict as JSON and has many amenities. This lambda treats the enclosed text as Hjson and parses it, then generates the serialized JSON of that. Basically, it makes it easier to write JSON in a template. One of the little goodies is that it accepts trailing comma's in arrays, which is one of the existing problems we've dealt with. It's not possible to use mustache section looping to generate array elements, because you have to generate a comma for all but the last, but there's no way to do that. Now you can generate one, and the proper JSON will be rendered.

Given the variables

{ 
  context: {
    a: { b: 1 },
    c: { d: 2 }
  }
}

and template (this looks quite a bit like yaml, but is actually Hjson)

{{#ParseHjson}} {
  a:   {{context.a}}
  a_b: {{context.a.b}}
  c:   {{context.c}}
  c_d: {{context.c.d}}

  list: [
    1 2
    3
    4,5,6,
  ]
} {{/ParseHjson}}

The following object will be rendered:

{
  a: { b: 1 },
  a_b: 1,
  c: { d: 2 },
  c_d: 2,
  list: ["1 2", 3, 4, 5, 6]
}

lambda EvalMath to evaluate TinyMath expressions

This lambda expects a TinyMath expression, in a string. Example:

{{#EvalMath}} 1 + context.a.b {{/EvalMath}}

Given the variable context with value { a: { b: 1 }}, this would render as: 2.

arrays have an asJSON property

Just as we extended "objects" in JSON to render as their JSON representation, we've extended arrays with an asJSON property which does the same, renders their JSON represention.

Given the variable context with value { a: [ 1, 2, 3 ] }, the template {context.a} would render as 1,2,3, and the template {context.a.asJSON} would render as [1,2,3].

For historical reference, we specifically excluded arrays when we added the auto-JSON-ification of objects, as mustache already rendered arrays as above, and we were afraid customers might have been making use of it. It can be a nice format, if it works for you. But often doesn't!

Checklist

Delete any items that are not applicable to this PR.

@pmuellr pmuellr force-pushed the actions/mustache-lambdas branch from ff7a84f to 9d5f46e Compare February 13, 2023 14:54
@pmuellr pmuellr force-pushed the actions/mustache-lambdas branch from 8b8ead2 to 64efc10 Compare March 3, 2023 16:57
@pmuellr
Copy link
Member Author

pmuellr commented Mar 6, 2023

Played a bit more with this, this weekend. I'm not happy about the date formatting:

  • the "directive" trick is too tricky, I don't think we should be creating new "extensions" for mustache, which is basically what that is.
  • the current story only allows one date format per action parameter, which may be too limiting; the case I'm thinking about is if someone wants a "short date" in a list/table, but a longer date in the body of the text.

So it seems like we should come up with a <FormatDate> lambda that can take all the parameters - date, time zone, and format string.

Edit: pmuellr 2023-04-11 - I did just that; FormatDate now takes all the params, separated by semi-colons.

@pmuellr pmuellr force-pushed the actions/mustache-lambdas branch 2 times, most recently from b62e794 to 2857bb5 Compare April 11, 2023 05:24
@pmuellr pmuellr added the ci:cloud-deploy Create or update a Cloud deployment label Apr 11, 2023
@pmuellr pmuellr force-pushed the actions/mustache-lambdas branch from 2857bb5 to a4f6bcc Compare April 11, 2023 17:45
@pmuellr
Copy link
Member Author

pmuellr commented Apr 12, 2023

Playing with this a bit. Built an es query rule against the event log, to alert on active alerts (meta-alert, as it were). Then built another rule that I could easily make active.

Here's the mustache template I used for the email:

Elasticsearch query alert '{{alertName}}' is active:

- Value: {{context.value}}
- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}
- Timestamp (original): {{context.date}}
- Timestamp (formatted): {{#FormatDate}}{{{context.date}}};America/New_York;ddd MMM D YYYY hh:mm:ss a{{/FormatDate}}
- Links: 
  - [Discover]({{context.link}})
  - [Rule]({{rule.url}})

affected:

| rule | instance | duration |
|---|---|---|
{{#context.hits}}
{{#_source}}|{{rule.name}}|{{kibana.alerting.instance_id}}|{{#EvalMath}}round({{{event.duration}}}/1000/1000/1000, 1){{/EvalMath}} sec|{{/_source}}
{{/context.hits}}

and here's the resulting formatted email:


image


Some notes.

Showing the "classic" way dates are displayed, with a formatted version below that.

The table at the bottom shows the alert duration. This value is in nanosecond units, so we divide by a billion to get the value in seconds units, rounding to one decimal place.

I had to triple escape both {{{context.date}}} and {{{event.duration}}}. If I didn't, these weren't being treated as valid dates/numbers, respectively. I could see how some ISO date chars might get auto-magically escaped, so you'd need to escape the date. The duration it turns out is a string, so I "unstringed" it with the triple-escapes. Ideally, we could have done this:

{{#EvalMath}}round(event.duration/1000/1000/1000, 1){{/EvalMath}}

No braces at all! Which will work fine, for values which are numbers. But in this case, the value is a string.

@guskovaue
Copy link
Contributor

We can use this Wiki as a source of timezone identifiers for docs: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones.

@pmuellr pmuellr force-pushed the actions/mustache-lambdas branch from a4f6bcc to 45e3bf0 Compare April 17, 2023 13:05

export type Escape = 'markdown' | 'slack' | 'json' | 'none';
type Variables = Record<string, unknown>;
export type Variables = Record<string, unknown>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I know circular dependencies ( import mustache_lambdas in mustache_renderer and mustache_renderer in mustache_lambdas. ) can potentially lead to unintended consequences.
So maybe it would be safer to just copy this type to mustache_lambdas

function getLambdas() {
return {
EvalMath: () =>
function (this: Variables, text: string, render: RenderFn) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about use vars instead of this? Or do you like this more on some reason?

Copy link
Member Author

@pmuellr pmuellr Apr 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an option. The way these lambdas are set up by mustache, is that they call the function with this set to the "variables" available to the lambda. We HAVE to define it this way.

Worth a comment though, I think, because it's obviously a little confusing, especially using this as a "function parameter" like this (it's a special typescript thing). see: https://www.typescriptlang.org/docs/handbook/2/functions.html#declaring-this-in-a-function

@pmuellr pmuellr force-pushed the actions/mustache-lambdas branch from d1fae57 to aaf0080 Compare April 20, 2023 13:42
@pmuellr pmuellr changed the title adds mustache lambdas and array.asJSON [ResponseOps] adds mustache lambdas and array.asJSON Apr 20, 2023
@pmuellr pmuellr added Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) Feature:Actions release_note:enhancement v8.8.0 labels Apr 20, 2023
@pmuellr pmuellr marked this pull request as ready for review April 20, 2023 14:03
@pmuellr pmuellr requested a review from a team as a code owner April 20, 2023 14:03
@elasticmachine
Copy link
Contributor

Pinging @elastic/response-ops (Team:ResponseOps)

Copy link
Contributor

@ymao1 ymao1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super cool @pmuellr!

@ymao1
Copy link
Contributor

ymao1 commented Apr 20, 2023

I used your example action message in a server log logging at info level and left it running for a bit and saw some errors show up but as part of the server log message:

[2023-04-20T14:42:09.382-04:00][INFO ][plugins.actions.server-log] Server log: Elasticsearch query alert 'test' is active:;;- Value: 451;- Conditions Met: Number of matching documents is greater than 0 over 5m;- Timestamp (original): 2023-04-20T18:42:06.434Z;- Timestamp (formatted): Thu Apr 20 2023 02:42:06 pm;- Links: ;  - [Discover](https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/28a46450-dfaa-11ed-8716-dbd9df1487a0);  - [Rule](https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/28a46450-dfaa-11ed-8716-dbd9df1487a0);;affected:;;| rule | instance | duration |;|---|---|---|;|data||0.1 sec|;|data|4dc87ed5-fabe-4e96-9c93-378b8721e51e|0 sec|;|data|e9f4a025-30cc-4640-94f4-66526f742bdb|0 sec|;
[2023-04-20T14:43:12.417-04:00][INFO ][plugins.actions.server-log] Server log: error rendering mustache template "Elasticsearch query alert '{{alertName}}' is active:;;- Value: {{context.value}};- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}};- Timestamp (original): {{context.date}};- Timestamp (formatted): {{#FormatDate}}{{{context.date}}};America/New_York;ddd MMM D YYYY hh:mm:ss a{{/FormatDate}};- Links: ;  - [Discover]({{context.link}});  - [Rule]({{rule.url}});;affected:;;| rule | instance | duration |;|---|---|---|;{{#context.hits}};{{#_source}}|{{rule.name}}|{{kibana.alerting.instance_id}}|{{#EvalMath}}round({{{event.duration}}}/1000/1000/1000, 1){{/EvalMath}} sec|{{/_source}};{{/context.hits}}": error evaluating tinymath expression "round(/1000/1000/1000, 1)": Failed to parse expression. Expected "*", "+", "-", "/", "<", "=", ">", end of input, or whitespace but "(" found.
[2023-04-20T14:44:15.426-04:00][INFO ][plugins.actions.server-log] Server log: error rendering mustache template "Elasticsearch query alert '{{alertName}}' is active:;;- Value: {{context.value}};- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}};- Timestamp (original): {{context.date}};- Timestamp (formatted): {{#FormatDate}}{{{context.date}}};America/New_York;ddd MMM D YYYY hh:mm:ss a{{/FormatDate}};- Links: ;  - [Discover]({{context.link}});  - [Rule]({{rule.url}});;affected:;;| rule | instance | duration |;|---|---|---|;{{#context.hits}};{{#_source}}|{{rule.name}}|{{kibana.alerting.instance_id}}|{{#EvalMath}}round({{{event.duration}}}/1000/1000/1000, 1){{/EvalMath}} sec|{{/_source}};{{/context.hits}}": error evaluating tinymath expression "round(/1000/1000/1000, 1)": Failed to parse expression. Expected "*", "+", "-", "/", "<", "=", ">", end of input, or whitespace but "(" found.
[2023-04-20T14:45:18.436-04:00][INFO ][plugins.actions.server-log] Server log: Elasticsearch query alert 'test' is active:;;- Value: 434;- Conditions Met: Number of matching documents is greater than 0 over 5m;- Timestamp (original): 2023-04-20T18:45:15.490Z;- Timestamp (formatted): Thu Apr 20 2023 02:45:15 pm;- Links: ;  - [Discover](https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/28a46450-dfaa-11ed-8716-dbd9df1487a0);  - [Rule](https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/28a46450-dfaa-11ed-8716-dbd9df1487a0);;affected:;;| rule | instance | duration |;|---|---|---|;|data||0.1 sec|;|data|9c938787-ccde-480b-bdaf-b0a98002534c|0 sec|;|data|5b76e53f-e96e-4ee6-bb5a-7a09af4422fb|0 sec|;
[2023-04-20T14:46:21.454-04:00][INFO ][plugins.actions.server-log] Server log: error rendering mustache template "Elasticsearch query alert '{{alertName}}' is active:;;- Value: {{context.value}};- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}};- Timestamp (original): {{context.date}};- Timestamp (formatted): {{#FormatDate}}{{{context.date}}};America/New_York;ddd MMM D YYYY hh:mm:ss a{{/FormatDate}};- Links: ;  - [Discover]({{context.link}});  - [Rule]({{rule.url}});;affected:;;| rule | instance | duration |;|---|---|---|;{{#context.hits}};{{#_source}}|{{rule.name}}|{{kibana.alerting.instance_id}}|{{#EvalMath}}round({{{event.duration}}}/1000/1000/1000, 1){{/EvalMath}} sec|{{/_source}};{{/context.hits}}": error evaluating tinymath expression "round(/1000/1000/1000, 1)": Failed to parse expression. Expected "*", "+", "-", "/", "<", "=", ">", end of input, or whitespace but "(" found.
[2023-04-20T14:46:39.445-04:00][INFO ][plugins.fleet] Fleet Usage: {"agents_enabled":true,"agents":{"total_enrolled":0,"healthy":0,"unhealthy":0,"offline":0,"inactive":0,"unenrolled":0,"total_all_statuses":0,"updating":0},"fleet_server":{"total_all_statuses":0,"total_enrolled":0,"healthy":0,"unhealthy":0,"offline":0,"updating":0,"num_host_urls":0}}
[2023-04-20T14:47:24.448-04:00][INFO ][plugins.actions.server-log] Server log: Elasticsearch query alert 'test' is active:;;- Value: 449;- Conditions Met: Number of matching documents is greater than 0 over 5m;- Timestamp (original): 2023-04-20T18:47:21.476Z;- Timestamp (formatted): Thu Apr 20 2023 02:47:21 pm;- Links: ;  - [Discover](https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/28a46450-dfaa-11ed-8716-dbd9df1487a0);  - [Rule](https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/28a46450-dfaa-11ed-8716-dbd9df1487a0);;affected:;;| rule | instance | duration |;|---|---|---|;|data||0.1 sec|;|data|c1994ae5-0902-4921-9ec8-a5f462715292|0 sec|;|data|1a8de2c7-5f76-4b1d-8f67-06309330bd0e|0 sec|;
[2023-04-20T14:48:27.475-04:00][INFO ][plugins.actions.server-log] Server log: Elasticsearch query alert 'test' is active:;;- Value: 449;- Conditions Met: Number of matching documents is greater than 0 over 5m;- Timestamp (original): 2023-04-20T18:48:24.510Z;- Timestamp (formatted): Thu Apr 20 2023 02:48:24 pm;- Links: ;  - [Discover](https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/28a46450-dfaa-11ed-8716-dbd9df1487a0);  - [Rule](https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/28a46450-dfaa-11ed-8716-dbd9df1487a0);;affected:;;| rule | instance | duration |;|---|---|---|;|data||0.1 sec|;|data|b9ffcb0e-62bb-4903-8f69-8b55ef211a6f|0 sec|;|data|b8e6d6eb-b11d-4d4f-80fa-28bf7a352362|0 sec|;
[2023-04-20T14:49:30.488-04:00][INFO ][plugins.actions.server-log] Server log: Elasticsearch query alert 'test' is active:;;- Value: 449;- Conditions Met: Number of matching documents is greater than 0 over 5m;- Timestamp (original): 2023-04-20T18:49:27.526Z;- Timestamp (formatted): Thu Apr 20 2023 02:49:27 pm;- Links: ;  - [Discover](https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/28a46450-dfaa-11ed-8716-dbd9df1487a0);  - [Rule](https://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/28a46450-dfaa-11ed-8716-dbd9df1487a0);;affected:;;| rule | instance | duration |;|---|---|---|;|data|3ff34eec-4ce8-4a5e-8004-bbb8f12017d6|12 sec|;|data|6212e32c-d883-4615-b073-cc13ceb968f5|0 sec|;|data|1312aa5b-1096-4667-b337-23c610df8f05|0 sec|;

So you can see some of the messages render correctly but some of them print an error message

@pmuellr
Copy link
Member Author

pmuellr commented Apr 20, 2023

I used your example action message in a server log logging at info level and left it running for a bit and saw some errors show up but as part of the server log message:

I'm not sure there was an easy way to force errors within Mustache processing before we had the lambdas. It was easy to get Mustache to choke, by giving it something like {{invalid} - and then the message with the template and error message makes some sense.

With the lambdas, whole new opportunities for runtime errors! I'm kinda wondering if we can do better. Maybe we should render just that lambda as an error message, so you'd see everything else rendered, and then the error message "in place" where the valid value would go. Something to think about ...

I'm curious what you were filtering on. It appears from the message like the duration was "empty"

round(/1000/1000/1000, 1)

The duration field should have been between ( and /. Which would happen for an event log doc that doesn't have a duration, like provider: alerting / action: execute-start (and probably some others).

@ymao1
Copy link
Contributor

ymao1 commented Apr 20, 2023

Yes, that's probably it. I did a match all docs in the event log so I would be getting docs without a duration. I wouldn't expect it to render in the action message with the error though...which is what it looks like it's doing?

@pmuellr
Copy link
Member Author

pmuellr commented Apr 24, 2023

@elasticmachine merge upstream

@pmuellr
Copy link
Member Author

pmuellr commented Apr 24, 2023

seems like the build got stuck, merge master again ...

@pmuellr
Copy link
Member Author

pmuellr commented Apr 24, 2023

@elasticmachine merge upstream

@kibana-ci
Copy link
Collaborator

kibana-ci commented Apr 24, 2023

💚 Build Succeeded

Metrics [docs]

Unknown metric groups

ESLint disabled line counts

id before after diff
actions 21 22 +1
enterpriseSearch 17 19 +2
securitySolution 395 398 +3
total +6

Total ESLint disabled count

id before after diff
actions 23 24 +1
enterpriseSearch 18 20 +2
securitySolution 475 478 +3
total +6

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@pmuellr pmuellr merged commit 4382e1c into elastic:main Apr 24, 2023
@kibanamachine kibanamachine added the backport:skip This commit does not require backporting label Apr 24, 2023
pmuellr added a commit that referenced this pull request May 14, 2023
…array (#155417)

resolves: #155408

## Summary

adds doc for function added in [adds mustache lambdas and
array.asJSON](#150572)
kibanamachine pushed a commit to kibanamachine/kibana that referenced this pull request May 14, 2023
…array (elastic#155417)

resolves: elastic#155408

## Summary

adds doc for function added in [adds mustache lambdas and
array.asJSON](elastic#150572)

(cherry picked from commit 120fa44)
kibanamachine referenced this pull request May 14, 2023
…N for array (#155417) (#157668)

# Backport

This will backport the following commits from `main` to `8.8`:
- [[ResponseOps][docs] add docs for new mustache lambdas and asJSON for
array (#155417)](#155417)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Patrick
Mueller","email":"patrick.mueller@elastic.co"},"sourceCommit":{"committedDate":"2023-05-14T18:39:25Z","message":"[ResponseOps][docs]
add docs for new mustache lambdas and asJSON for array
(#155417)\n\nresolves:
https://github.com/elastic/kibana/issues/155408\r\n\r\n##
Summary\r\n\r\nadds doc for function added in [adds mustache lambdas
and\r\narray.asJSON](https://github.com/elastic/kibana/pull/150572)","sha":"120fa44afd6b87764d114475caa97d862c55f343","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Feature:Actions","Team:ResponseOps","docs","backport:prev-minor","v8.9.0"],"number":155417,"url":"https://github.com/elastic/kibana/pull/155417","mergeCommit":{"message":"[ResponseOps][docs]
add docs for new mustache lambdas and asJSON for array
(#155417)\n\nresolves:
https://github.com/elastic/kibana/issues/155408\r\n\r\n##
Summary\r\n\r\nadds doc for function added in [adds mustache lambdas
and\r\narray.asJSON](https://github.com/elastic/kibana/pull/150572)","sha":"120fa44afd6b87764d114475caa97d862c55f343"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/155417","number":155417,"mergeCommit":{"message":"[ResponseOps][docs]
add docs for new mustache lambdas and asJSON for array
(#155417)\n\nresolves:
https://github.com/elastic/kibana/issues/155408\r\n\r\n##
Summary\r\n\r\nadds doc for function added in [adds mustache lambdas
and\r\narray.asJSON](https://github.com/elastic/kibana/pull/150572)","sha":"120fa44afd6b87764d114475caa97d862c55f343"}}]}]
BACKPORT-->

Co-authored-by: Patrick Mueller <patrick.mueller@elastic.co>
jasonrhodes pushed a commit that referenced this pull request May 17, 2023
…array (#155417)

resolves: #155408

## Summary

adds doc for function added in [adds mustache lambdas and
array.asJSON](#150572)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This commit does not require backporting ci:cloud-deploy Create or update a Cloud deployment Feature:Actions release_note:enhancement Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) v8.8.0
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

6 participants