-
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
[Persistable state services] Support migrations, exporting references, and telemetry for visualizations that aren't part of the visualization library #62950
Comments
@stacey-gammon this popped up in unlabelled issues. I can definitely try my best to label this, but you'll probably do better |
Pinging @elastic/kibana-app-arch (Team:AppArch) |
Pinging @elastic/kibana-platform (Team:Platform) |
What would be an example, for example, in Expressions? When you say "registries that contain state that may end up persisted somewhere" do you mean that for example an alert can be created using |
Expressions are tricky because the string is user visible. I had a conversation with @timroes about this as well. The options we have are:
Neither are good options for expressions, but I prefer the first. We can also offer some additional options specific to expressions, like introducing the concept of deprecated arguments. A dev could add a new one, e.g. Version 1
version 2
The function itself would need handle on the fly migration from the deprecated argument to the new one. Auto complete would only show the new version. Still, there could be times where that alone wouldn't be sufficient. Providing these capabilities would give us an out for those, hopefully very rare, situations. I'm open to other options, I just really don't like the idea of storing a version number in the expression string. I think it's cleaner to have a migration from an old expression function to a new one, even though that creates complications with choosing a good name.
Not quite, the out for migrating is to change the expression functions name. We have these expression strings stored in saved objects already and no formal way to migrate them. Like I said earlier, I think we would first try to stick with the same name and do some magic inside the expression function itself to convert to a new version. Another temporary solution we could opt for now is to migrate the saved objects that we know about. There is just no guarantee that we know all the saved objects expression strings are stored in. There is also no guarantee that we know about all the expression function migrations that need to take place (what if one third party developer wants to migrate their expression function, and another third party developer is storing it in a saved object - what could we do to help the developer write a migration without breaking all the other developers saved objects?) |
i think the key here is the difference between expression and persisted expression. if our persistable state plugin exposes a registry, where every plugin can register its persistable state id together with:
then the persister (consumer of persistable state who wants to persist it) can lookup the persistable state in this registry and:
for the expressions, expression plugin would register its state in case of expressions this function would:
If expression function name would change, we would add an entry to persistable state registyr with the old function name and the migrate function would just return the new function name (with new parameters) |
After getting up to speed on this I have two questions - do we have agreement on the problem definition? If so, can we start expressing this in code? I think the problem has enough complexity that we might need some discussion regarding the optimal code expression of the problem even before we work on a solution. |
Yes, this should go through an RFC with code examples, and an arch review. @ppisljar has started a presentation with some code examples. We talked this morning regarding this gist: https://gist.github.com/stacey-gammon/ad6e60637404e779bdaba8502e54ac03 and related questions.
For 1. we decided going with a single migration function is better because it eliminates the possibility of a nested migration function being skipped. If you had it per version, you could get into this situation: // EmbeddableInput gets migrated from 7.1.0 to 7.3.0. There were no migrations registered
// for 7.2.0.
persistableStateMigrations.register('embeddable', '7.1.0', (state) => {
return {
newVersion: '7.3.0',
newState: {
// Migrate `EmbeddableInput` state
...migrateEmbeddableInput710To730(state.input),
enhancements: {
...state.input.enhancements.map(
key => persistableStateMigrations.migrate(`embeddableEnhancements-${key}`, '7.1.0').newState)
}
}
};
}
// However, an enhancement registered a migration for 7.2.0. It never gets called!
persistableStateMigrations.register(`embeddableEnhancements-${key}`, '7.2.0', (state) => {
return { newState: migratedStateTo730(state ), newVersion: '7.3.0' };
}); So we have to do two things:
As long as state ownership is isolated, we can do something like this: const embeddableMigrator: PersistableStateMigrationFn (state: { type: string; input: unknown }, version: string): State {
return {
...migrateInputToLatest(state.input),
enhancements: {
...state.input.enhancements.map(enhancement =>
persistableStateMigrations.get(enhancement).migrate(state.input.enhancements[extension], version)),
},
specializedState: persistableStateMigrations.get(state.type).migrate(state.input.specializedState, version)),
}
} The key here is that we have to separate out the state owned by // We start out with the base class having an attribute of `title`.
interface EmbeddableInputV1 {
title: string;
}
// `Title` is taken by the base class, so Visualize plugin adds `defaultTitle`.
interface VisualizeEmbeddableInputV1 implements EmbeddableInputV1 {
defaultTitle: string;
}
// In a future version, EmbeddableInput author decides to rename `title` => `defaultTitle`.
// They don't know about `VisualizeEmbeddableInput` so don't realize there is going to be
// a key collision.
interface EmbeddableInputV2 {
defaultTitle: string;
} It's probably a rare occurrence, but it's also very risky. tl;dr: We need to avoid shared persistable state. We can't do things like have base interfaces that other plugins extend, if that state is going to be serialized. We should fix embeddables. We might want to think of not using |
closing this due to inactivity, please re-open if its still relevant |
@ThomThomson do we have a separate issue for catching proper telemetry from embedded visualizations (so we can collect the same telemetry that we do on visualization library) besides this one? Or was this the one used for tracking that effort? |
What
A generic solution for the problems that come with registries that contain state that may end up persisted somewhere, usually in a saved object (although could also be in a URL bookmark).
Any time someone registers something that includes state that may be persisted they need to handle:
**1. Saved object references (inject/extract) **
Consider a dashboard which supports dynamically adding embeddables and storing this state. This state may contain references to other saved objects. If the user wants to export this dashboard, or copy it to another space, the dashboard needs to know if any of the state it has pulled off a registry contains a reference to a saved object.
2. Migrations
Similar to the above example, any persisted state needs to know how it will migrate old state to maintain BWC. We don't want the author of an expression function to change the state they support and accidentally break all existing saved objects that contain older state.
**3. Telemetry **
Since the container state doesn't know details for the nested state, it needs to provide telemetry information so we can support the same level of telemetry we have today. For example, a visualization nested "by value" inside a dashboard should still be counted towards the telemetry that counts the types of visualizations that are by reference.
Implementation plan
Rather than implement a global solution, we are implementing this on a per registry basis. Once we have a few concrete end to end examples, we can determine if a global plugin is necessary, or if we can make do with helper utilities.
Current registries which would need to switch over to this
Related
The text was updated successfully, but these errors were encountered: