-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
telemetry and reference extraction/injection for expression service #72438
Conversation
Pinging @elastic/kibana-app-arch (Team:AppArch) |
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.
Gave this an initial once over and added a few thoughts...
To do that you would have fn1 provide a migrate function which would return a fn2 function (new name) and fn1 would be marked as disabled.
I know we discussed this a bit before, but I still wish there were a better way for us to version functions so that we wouldn't need to create completely new ones. But I can't really think of a way to do this without requiring users to explicitly provide a version number or something similar in the expression.
@elasticmachine merge upstream |
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.
Done some quick review (didn't do an in-depth code review on the walk function right now). Left a couple of minor comments. Overall it looks good to me, but have some general notes:
- What is the
version
we pass into the migration method? Will we use the Kibana version for that? Since all functions need to use the sameversion
in their migration there isn't anything else that really would make sense? If we anyway require this to be the Kibana version, would it make sense not to pass that into as a parameter but always have that function use the current Kibana version automatically? - While I understand who needs to call
inject
andextract
, I think we need some clear documentation on when to callmigrate
on the executor service? (My current understand is, it needs to called EVERY time before you are executing an expression that was stored somewhere?) - In general I think we need to provide some more documentation on those functions, once we're going to merge it :-)
@timroes i agree regarding the docs, i added some jsdocs. in general, you should never need to use inject or extract unless you are creating a new saved object type (like lens does). you should never need to call migrate unless you are getting the state from some storage (url, localstorage, user input?, saved objects inside saved object migration scripts), so hopefully most of the code would not be doing it and we have services that perform this. the version we pass in is the version of the state we are sending to migrate function. the migrate function will always return state of the latest version. open to suggestions here, we could remove the version and have the migrate function have to figure out the actual version of state passed in on its own ? if we don't need to provide the version this gives us a nice benefit of not having to ever call migrate function, as we could call it internally when creating the execution. that would mean that above functions are only called from saved object types internally. |
@elasticmachine merge upstream |
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.
On the whole the updates here make sense to me, I still need to test though. Added a few other comments/questions.
@@ -220,6 +220,10 @@ export class Execution< | |||
return createError({ message: `Function ${fnName} could not be found.` }); | |||
} | |||
|
|||
if (fn.disabled) { | |||
return createError({ message: `Function ${fnName} is disabled.` }); |
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.
I wonder if we should be running these through i18n as they are automatically surfaced in the UI on the client?
@elasticmachine merge upstream |
a5e4b3d
to
1980d16
Compare
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 -- reviewed the updates & I think this all makes sense.
// if any of arguments are expressions we should migrate those first | ||
link.arguments = mapValues(fnArgs, (asts, argName) => { | ||
return asts.map((arg) => { | ||
if (typeof arg === 'object') { |
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.
will the fact that typeof null === 'object'
cause any problems here?
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.
i guess it should be fine since null
isn't a valid value for an AST arg anyway
@elasticmachine merge upstream |
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.
Code LGTM.
I would ask if we could please add a couple of tests still for the inject/extract functions, that will especially test, that not only the functions will be called, but do some tests where the inject/extract functions do actually a rewrite of the AST that this is rewritten correctly in the returned AST.
const newAst = this.walkAst(ast, (fn, link) => { | ||
const { args, references } = fn.extract(link.arguments); | ||
link.arguments = args; | ||
references.forEach((ref) => allReferences.push(ref)); |
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.
🎨 allReferences.push(...references)
maybe reads slightly nicer.
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.
Added a few nits
ast: ExpressionAstExpression, | ||
action: (fn: ExpressionFunction, link: ExpressionAstFunction) => void | ||
) { | ||
const newAST = cloneDeep(ast); |
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.
Is it necessary to do this cloneDeep
at every recursion step? It seems like it would be enough to clone the original ast once, then use that copy for the complete walk
* @param state old version of expression AST | ||
*/ | ||
public readonly migrate = (state: ExpressionAstExpression) => { | ||
return this.executor.migrate(state); |
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.
Should we pass a version identifier down here? Currently the migrate
function has to work with clues in the ast to decide whether something needs to be migrated or not. This might not be possible in all cases.
E.g. There is an expression function search type="_myType"
- the underscore has to be removed due to changes in the underlying system, but search type="__myType"
is a valid value. If we know from what version we are migrating we can always decide to do the right thing, if we just get the current state without knowing what's the source version, we might not know whether to change something or not.
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.
the problem is we might not know the version at all. we might add an optional version parameter, but we will still need to figure out by clues what version it actually is if that is not provided, so i would leave this out for now. we can always add it later.
@@ -87,6 +94,32 @@ export interface ExpressionFunctionDefinition< | |||
*/ | |||
fn(input: Input, args: Arguments, context: Context): Output; | |||
|
|||
/** | |||
* migrate function |
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.
I would suggest to include in these docs the order of how migrations/injections/extractions are applied.
Something like:
"This function will be executed for arguments of an expression function before being executed for the current expression function. This means all arguments will already be migrated when this method is called on an expression function"
695b496
to
49b03d0
Compare
@elasticmachine merge upstream |
884d127
to
7e6862b
Compare
Co-authored-by: Luke Elmers <lukeelmers@gmail.com>
Co-authored-by: Luke Elmers <lukeelmers@gmail.com>
Co-authored-by: Luke Elmers <lukeelmers@gmail.com>
7e6862b
to
8582a4e
Compare
8582a4e
to
a9d28d7
Compare
# Conflicts: # src/plugins/expressions/common/service/expressions_services.ts
💚 Build SucceededMetrics [docs]page load bundle size
History
To update your PR or re-run it, just comment with: |
Summary
every expression function can provide three additional functions:
telemetry: (state: ExpressionAstFunction, telemetryData: Record<String, any>) => Record<String, any>;
also, a new flag
disabled
was added, which allows us to disable certain functions, yet still keep their state handling part in the registry.Checklist
Delete any items that are not applicable to this PR.