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

telemetry and reference extraction/injection for expression service #72438

Merged
merged 16 commits into from
Oct 12, 2020

Conversation

ppisljar
Copy link
Member

@ppisljar ppisljar commented Jul 20, 2020

Summary

every expression function can provide three additional functions:

  • telemetry: (state: ExpressionAstFunction, telemetryData: Record<String, any>) => Record<String, any>;
extract: (
    state: ExpressionAstFunction['arguments']
  ) => { args: ExpressionAstFunction['arguments']; references: SavedObjectReference[] };
inject: (
    state: ExpressionAstFunction['arguments'],
    references: SavedObjectReference[]
  ) => ExpressionAstFunction['arguments'];

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.

@ppisljar ppisljar added WIP Work in progress Feature:ExpressionLanguage Interpreter expression language (aka canvas pipeline) Team:AppArch labels Jul 20, 2020
@ppisljar ppisljar requested review from timroes and lukeelmers July 20, 2020 14:11
@ppisljar ppisljar requested a review from a team as a code owner July 20, 2020 14:11
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-app-arch (Team:AppArch)

Copy link
Member

@lukeelmers lukeelmers left a 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.

@ppisljar
Copy link
Member Author

@elasticmachine merge upstream

Copy link
Contributor

@timroes timroes left a 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 same version 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 and extract, I think we need some clear documentation on when to call migrate 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 :-)

src/plugins/expressions/common/execution/execution.ts Outdated Show resolved Hide resolved
@ppisljar ppisljar removed the WIP Work in progress label Jul 28, 2020
@ppisljar
Copy link
Member Author

@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.

@ppisljar
Copy link
Member Author

@elasticmachine merge upstream

Copy link
Member

@lukeelmers lukeelmers left a 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.` });
Copy link
Member

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?

@ppisljar
Copy link
Member Author

ppisljar commented Aug 3, 2020

@elasticmachine merge upstream

Copy link
Member

@lukeelmers lukeelmers left a 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') {
Copy link
Member

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?

Copy link
Member

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

@ppisljar
Copy link
Member Author

@elasticmachine merge upstream

Copy link
Contributor

@timroes timroes left a 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));
Copy link
Contributor

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.

Copy link
Contributor

@flash1293 flash1293 left a 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);
Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Member Author

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
Copy link
Contributor

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"

@ppisljar ppisljar force-pushed the expressions/migrations branch from 695b496 to 49b03d0 Compare September 14, 2020 10:32
@ppisljar
Copy link
Member Author

@elasticmachine merge upstream

@ppisljar ppisljar force-pushed the expressions/migrations branch 2 times, most recently from 884d127 to 7e6862b Compare September 29, 2020 07:42
@ppisljar ppisljar force-pushed the expressions/migrations branch from 7e6862b to 8582a4e Compare September 29, 2020 09:45
@ppisljar ppisljar force-pushed the expressions/migrations branch from 8582a4e to a9d28d7 Compare September 29, 2020 10:29
@ppisljar ppisljar changed the title migration and reference extraction/injection for expression service telemetry and reference extraction/injection for expression service Sep 29, 2020
# Conflicts:
#	src/plugins/expressions/common/service/expressions_services.ts
@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

page load bundle size

id before after diff
expressions 194.6KB 198.4KB +3.7KB

History

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:ExpressionLanguage Interpreter expression language (aka canvas pipeline) release_note:skip Skip the PR/issue when compiling release notes v7.11.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants