-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
🌊 Add type safety to Painless conditions #202603
🌊 Add type safety to Painless conditions #202603
Conversation
Thanks for setting this up, @simianhacker - works really well, the only thing I think would make sense to add is to cast to number types when doing gt, gte and so on both for values and fields. Otherwise this situation will not work as I would expect:
|
The same thing we should probably do the other way for contains and equals so everything is a string always? The UI only gives one input field for the value, so we can't really tell whether we should do an |
…e-checks-painless
@flash1293 I added some type safety to |
Thanks for the changes @simianhacker , this works great, I just found one more case we should handle which is eq/neq:
|
@flash1293 I wonder if we shouldn't keep the |
@simianhacker how would the user use eq/neq in the UI? Would the user need to enter This is not just a theoretical concern, it's pretty common for numbers to be sent as strings instead, but it's also not always the case. I'm happy to solve this general concern in a different way, but I think we need to solve it somehow - casts just seem like the easiest way to do so. Maybe we need a "like" operator? :) |
@flash1293 I see you point, that's kind of a limitation of HTML forms is that it's really hard to discern a number vs a string when the API can accept both. Base on our Slack conversation, let's move towards "ease of use". The distinction in the API of I'll add the same checks/conversion to |
Should we follow through and only allow strings on the API? |
💚 Build Succeeded
Metrics [docs]
History
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks for all the changes!
Starting backport for target branches: 8.x |
## 🍒 Summary This PR closes elastic/streams-program#18 by adding some basic type checking to the painless output for the Stream conditions. The new code will check to ensure that none of the fields used in the condition are `Map` objects. Then it wraps the if statement in a `try/catch`. ### Condition ```Typescript { and: [ { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' }, { or: [ { field: 'log.level', operator: 'eq' as const, value: 'error' }, { field: 'log.level', operator: 'eq' as const, value: 'ERROR' }, ], }, ], } ``` ### Before ``` (ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR")) ``` ### After ``` if (ctx.log?.logger instanceof Map || ctx.log?.level instanceof Map) { return false; } try { if ((ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR"))) { return true; } return false; } catch (Exception e) { return false; } ``` (cherry picked from commit fe56d6d)
💚 All backports created successfully
Note: Successful backport PRs will be merged automatically after passing CI. Questions ?Please refer to the Backport tool documentation |
# Backport This will backport the following commits from `main` to `8.x`: - [🌊 Add type safety to Painless conditions (#202603)](#202603) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Chris Cowan","email":"chris@elastic.co"},"sourceCommit":{"committedDate":"2024-12-05T13:58:36Z","message":"🌊 Add type safety to Painless conditions (#202603)\n\n## 🍒 Summary\r\n\r\nThis PR closes elastic/streams-program#18 by\r\nadding some basic type checking to the painless output for the Stream\r\nconditions. The new code will check to ensure that none of the fields\r\nused in the condition are `Map` objects. Then it wraps the if statement\r\nin a `try/catch`.\r\n\r\n### Condition\r\n```Typescript\r\n{\r\n and: [\r\n { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' },\r\n {\r\n or: [\r\n { field: 'log.level', operator: 'eq' as const, value: 'error' },\r\n { field: 'log.level', operator: 'eq' as const, value: 'ERROR' },\r\n ],\r\n },\r\n ],\r\n}\r\n```\r\n\r\n### Before\r\n\r\n```\r\n(ctx.log?.logger !== null && ctx.log?.logger == \"nginx_proxy\") && ((ctx.log?.level !== null && ctx.log?.level == \"error\") || (ctx.log?.level !== null && ctx.log?.level == \"ERROR\"))\r\n```\r\n\r\n### After\r\n\r\n```\r\nif (ctx.log?.logger instanceof Map || ctx.log?.level instanceof Map) {\r\n return false;\r\n}\r\ntry {\r\n if ((ctx.log?.logger !== null && ctx.log?.logger == \"nginx_proxy\") && ((ctx.log?.level !== null && ctx.log?.level == \"error\") || (ctx.log?.level !== null && ctx.log?.level == \"ERROR\"))) {\r\n return true;\r\n }\r\n return false;\r\n} catch (Exception e) {\r\n return false;\r\n}\r\n```","sha":"fe56d6d90af45b971ff82731c1ec7eb4c4c0eff3","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","v8.18.0","Feature:Streams"],"title":"🌊 Add type safety to Painless conditions","number":202603,"url":"https://github.com/elastic/kibana/pull/202603","mergeCommit":{"message":"🌊 Add type safety to Painless conditions (#202603)\n\n## 🍒 Summary\r\n\r\nThis PR closes elastic/streams-program#18 by\r\nadding some basic type checking to the painless output for the Stream\r\nconditions. The new code will check to ensure that none of the fields\r\nused in the condition are `Map` objects. Then it wraps the if statement\r\nin a `try/catch`.\r\n\r\n### Condition\r\n```Typescript\r\n{\r\n and: [\r\n { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' },\r\n {\r\n or: [\r\n { field: 'log.level', operator: 'eq' as const, value: 'error' },\r\n { field: 'log.level', operator: 'eq' as const, value: 'ERROR' },\r\n ],\r\n },\r\n ],\r\n}\r\n```\r\n\r\n### Before\r\n\r\n```\r\n(ctx.log?.logger !== null && ctx.log?.logger == \"nginx_proxy\") && ((ctx.log?.level !== null && ctx.log?.level == \"error\") || (ctx.log?.level !== null && ctx.log?.level == \"ERROR\"))\r\n```\r\n\r\n### After\r\n\r\n```\r\nif (ctx.log?.logger instanceof Map || ctx.log?.level instanceof Map) {\r\n return false;\r\n}\r\ntry {\r\n if ((ctx.log?.logger !== null && ctx.log?.logger == \"nginx_proxy\") && ((ctx.log?.level !== null && ctx.log?.level == \"error\") || (ctx.log?.level !== null && ctx.log?.level == \"ERROR\"))) {\r\n return true;\r\n }\r\n return false;\r\n} catch (Exception e) {\r\n return false;\r\n}\r\n```","sha":"fe56d6d90af45b971ff82731c1ec7eb4c4c0eff3"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/202603","number":202603,"mergeCommit":{"message":"🌊 Add type safety to Painless conditions (#202603)\n\n## 🍒 Summary\r\n\r\nThis PR closes elastic/streams-program#18 by\r\nadding some basic type checking to the painless output for the Stream\r\nconditions. The new code will check to ensure that none of the fields\r\nused in the condition are `Map` objects. Then it wraps the if statement\r\nin a `try/catch`.\r\n\r\n### Condition\r\n```Typescript\r\n{\r\n and: [\r\n { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' },\r\n {\r\n or: [\r\n { field: 'log.level', operator: 'eq' as const, value: 'error' },\r\n { field: 'log.level', operator: 'eq' as const, value: 'ERROR' },\r\n ],\r\n },\r\n ],\r\n}\r\n```\r\n\r\n### Before\r\n\r\n```\r\n(ctx.log?.logger !== null && ctx.log?.logger == \"nginx_proxy\") && ((ctx.log?.level !== null && ctx.log?.level == \"error\") || (ctx.log?.level !== null && ctx.log?.level == \"ERROR\"))\r\n```\r\n\r\n### After\r\n\r\n```\r\nif (ctx.log?.logger instanceof Map || ctx.log?.level instanceof Map) {\r\n return false;\r\n}\r\ntry {\r\n if ((ctx.log?.logger !== null && ctx.log?.logger == \"nginx_proxy\") && ((ctx.log?.level !== null && ctx.log?.level == \"error\") || (ctx.log?.level !== null && ctx.log?.level == \"ERROR\"))) {\r\n return true;\r\n }\r\n return false;\r\n} catch (Exception e) {\r\n return false;\r\n}\r\n```","sha":"fe56d6d90af45b971ff82731c1ec7eb4c4c0eff3"}},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Chris Cowan <chris@elastic.co>
## 🍒 Summary This PR closes elastic/streams-program#18 by adding some basic type checking to the painless output for the Stream conditions. The new code will check to ensure that none of the fields used in the condition are `Map` objects. Then it wraps the if statement in a `try/catch`. ### Condition ```Typescript { and: [ { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' }, { or: [ { field: 'log.level', operator: 'eq' as const, value: 'error' }, { field: 'log.level', operator: 'eq' as const, value: 'ERROR' }, ], }, ], } ``` ### Before ``` (ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR")) ``` ### After ``` if (ctx.log?.logger instanceof Map || ctx.log?.level instanceof Map) { return false; } try { if ((ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR"))) { return true; } return false; } catch (Exception e) { return false; } ```
## 🍒 Summary This PR closes elastic/streams-program#18 by adding some basic type checking to the painless output for the Stream conditions. The new code will check to ensure that none of the fields used in the condition are `Map` objects. Then it wraps the if statement in a `try/catch`. ### Condition ```Typescript { and: [ { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' }, { or: [ { field: 'log.level', operator: 'eq' as const, value: 'error' }, { field: 'log.level', operator: 'eq' as const, value: 'ERROR' }, ], }, ], } ``` ### Before ``` (ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR")) ``` ### After ``` if (ctx.log?.logger instanceof Map || ctx.log?.level instanceof Map) { return false; } try { if ((ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR"))) { return true; } return false; } catch (Exception e) { return false; } ```
## 🍒 Summary This PR closes elastic/streams-program#18 by adding some basic type checking to the painless output for the Stream conditions. The new code will check to ensure that none of the fields used in the condition are `Map` objects. Then it wraps the if statement in a `try/catch`. ### Condition ```Typescript { and: [ { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' }, { or: [ { field: 'log.level', operator: 'eq' as const, value: 'error' }, { field: 'log.level', operator: 'eq' as const, value: 'ERROR' }, ], }, ], } ``` ### Before ``` (ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR")) ``` ### After ``` if (ctx.log?.logger instanceof Map || ctx.log?.level instanceof Map) { return false; } try { if ((ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR"))) { return true; } return false; } catch (Exception e) { return false; } ```
## 🍒 Summary This PR closes elastic/streams-program#18 by adding some basic type checking to the painless output for the Stream conditions. The new code will check to ensure that none of the fields used in the condition are `Map` objects. Then it wraps the if statement in a `try/catch`. ### Condition ```Typescript { and: [ { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' }, { or: [ { field: 'log.level', operator: 'eq' as const, value: 'error' }, { field: 'log.level', operator: 'eq' as const, value: 'ERROR' }, ], }, ], } ``` ### Before ``` (ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR")) ``` ### After ``` if (ctx.log?.logger instanceof Map || ctx.log?.level instanceof Map) { return false; } try { if ((ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR"))) { return true; } return false; } catch (Exception e) { return false; } ```
## 🍒 Summary This PR closes elastic/streams-program#18 by adding some basic type checking to the painless output for the Stream conditions. The new code will check to ensure that none of the fields used in the condition are `Map` objects. Then it wraps the if statement in a `try/catch`. ### Condition ```Typescript { and: [ { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' }, { or: [ { field: 'log.level', operator: 'eq' as const, value: 'error' }, { field: 'log.level', operator: 'eq' as const, value: 'ERROR' }, ], }, ], } ``` ### Before ``` (ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR")) ``` ### After ``` if (ctx.log?.logger instanceof Map || ctx.log?.level instanceof Map) { return false; } try { if ((ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR"))) { return true; } return false; } catch (Exception e) { return false; } ```
## 🍒 Summary This PR closes elastic/streams-program#18 by adding some basic type checking to the painless output for the Stream conditions. The new code will check to ensure that none of the fields used in the condition are `Map` objects. Then it wraps the if statement in a `try/catch`. ### Condition ```Typescript { and: [ { field: 'log.logger', operator: 'eq' as const, value: 'nginx_proxy' }, { or: [ { field: 'log.level', operator: 'eq' as const, value: 'error' }, { field: 'log.level', operator: 'eq' as const, value: 'ERROR' }, ], }, ], } ``` ### Before ``` (ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR")) ``` ### After ``` if (ctx.log?.logger instanceof Map || ctx.log?.level instanceof Map) { return false; } try { if ((ctx.log?.logger !== null && ctx.log?.logger == "nginx_proxy") && ((ctx.log?.level !== null && ctx.log?.level == "error") || (ctx.log?.level !== null && ctx.log?.level == "ERROR"))) { return true; } return false; } catch (Exception e) { return false; } ```
🍒 Summary
This PR closes https://github.com/elastic/streams-program/issues/18 by adding some basic type checking to the painless output for the Stream conditions. The new code will check to ensure that none of the fields used in the condition are
Map
objects. Then it wraps the if statement in atry/catch
.Condition
Before
After