-
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
[State Management] State syncing utilities #53582
[State Management] State syncing utilities #53582
Conversation
Pinging @elastic/kibana-app-arch (Team:AppArch) |
…agment/state-sync # Conflicts: # package.json
One use case that may be broken here: Opening a navlink from the Chrome with Cmd+Click will not open to the same location since the At one point in time, I thought this could be solved by using localStorage instead, but if I remember correctly, using localStorage caused tabs to override each other's last state when clicking between apps. One way to solve this would be to actually update the Chrome navLink URLs, which there is an API for: |
@joshdover, I just looked into https://github.com/elastic/kibana/blob/master/src/core/public/chrome/nav_links/nav_link.ts#L88 I am not sure if there any other way to update that url in nav, but if we decide that we want to leave that behaviour, likely we will indeed will have to support that Theoretically, I liked the idea of having all state restoration responsibilities on app itself. Less code in core and less dependencies on core.
Just my opinion as a user, as I was digging into that url preservation behaviour yesterday for the first time: I was surprised that clicking on a link of currently active app or opening it in a new tab doesn't restore the state to the default state. I think, as I user, I am used to that clicking on a "home" button will get me do default state, and, for some reason, I expected to see that happening for the active app button. |
Totally agree for the new tab / clicking the active app icon scenario, we should just make sure to keep the current behavior when switching between apps in the same tab. It's also a slight enough behavior change that at least IMHO we don't have to wait till 8.0 to make it. |
src/plugins/kibana_utils/public/state_management/url/url_tracker.ts
Outdated
Show resolved
Hide resolved
@joshdover, I bumped into this issue #53692 which is related to sidenav navigation. |
Per discussion with @lizozom: |
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.
Left some nits, but I didn't get through everything. LGTM so far. I will continue on Friday, so don't block merging on me.
src/plugins/kibana_utils/common/distinct_until_changed_with_initial_value.ts
Outdated
Show resolved
Hide resolved
src/plugins/kibana_utils/public/state_containers/create_state_container.ts
Show resolved
Hide resolved
src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts
Show resolved
Hide resolved
import rison, { RisonValue } from 'rison-node'; | ||
import { isStateHash, retrieveState, persistState } from '../state_hash'; | ||
|
||
export function decodeState<State>(expandedOrHashedState: string): 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.
Would it make sense in this file to restrict the State
type param to RisonValue
?
export function decodeState<State extends RisonValue>(expandedOrHashedState: string): 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.
Was trying to make it nicer for a while.
Bumped into problem, that if I start to specifically expect RisonValue here, then the whole type chain up to BaseStateContainer breaks, as in state_containers
we don't have any restrictions on State shape.
So likely the best approach would be to reconsider if we want to have proper restrictions on base state shape which would satisfy RisonValue.
I will address this separately later.
src/plugins/kibana_utils/public/state_management/state_encoder/index.ts
Outdated
Show resolved
Hide resolved
src/plugins/kibana_utils/public/state_management/state_hash/index.ts
Outdated
Show resolved
Hide resolved
@@ -0,0 +1,246 @@ | |||
/* |
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.
nit: Why are all of these prefixed with Kbn
? I don't have strong feeling about this, but it looks like we could just call it url_storage
. Maybe I'm missing something 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.
Initially it was without Kbn
prefix, but I reconsidered and added Kbn
just to emphasise this is type of storage is something which is coming from legacy Kibana and Kibana App.
I thought that later we could have differently named url storage, which would allow to preserve state in nicer and simpler user friendly format (e.g by serialising using regular query params like &query="test"&time="test&filter="filter1,filter2"), where Kbn
is something took from old Kibana, with rison, storing in hash part of url and other details.
Looked through the code, pulled down and was able to add url tracking to the embeddable explorer. I would like to keep playing around with it to get a better feel, but the direction looks solid, and I think we can iterate as we use it in more real world use cases (like dashboard). Great example plugins! LGTM. |
1ae6f70
to
b8d808c
Compare
Today, apps rely on AppState and GlobalState in the ui/state_management module to deal with internal (app) and shared (global) state. These classes give apps an ability to read/write state, when is then synced to the URL as well as sessionStorage. They also react to changes in the URL and automatically update state & emit events when changes occur. This PR introduces new state synching utilities, which together with state containers src/plugins/kibana_utils/public/state_containers will be a replacement for AppState and GlobalState in New Platform.
Today, apps rely on AppState and GlobalState in the ui/state_management module to deal with internal (app) and shared (global) state. These classes give apps an ability to read/write state, when is then synced to the URL as well as sessionStorage. They also react to changes in the URL and automatically update state & emit events when changes occur. This PR introduces new state synching utilities, which together with state containers src/plugins/kibana_utils/public/state_containers will be a replacement for AppState and GlobalState in New Platform. Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Some maintenance and minor fixes to state containers based on experience while working with them in #53582 Patch unit tests to use current "terminology" (e.g. "transition" vs "mutation") Fix docs where "store" was used instead of "state container" Allow to create state container without transition. Fix freeze function to deeply freeze objects. Restrict State to BaseState with extends object. in set() function, make sure the flow goes through dispatch to make sure middleware see this update Improve type inference for useTransition() Improve type inference for createStateContainer(). Other issues noticed, but didn't fix in reasonable time: Can't use addMiddleware without explicit type casting #54438 Transitions and Selectors allow any state, not bind to container's state #54439
Some maintenance and minor fixes to state containers based on experience while working with them in elastic#53582 Patch unit tests to use current "terminology" (e.g. "transition" vs "mutation") Fix docs where "store" was used instead of "state container" Allow to create state container without transition. Fix freeze function to deeply freeze objects. Restrict State to BaseState with extends object. in set() function, make sure the flow goes through dispatch to make sure middleware see this update Improve type inference for useTransition() Improve type inference for createStateContainer(). Other issues noticed, but didn't fix in reasonable time: Can't use addMiddleware without explicit type casting elastic#54438 Transitions and Selectors allow any state, not bind to container's state elastic#54439
💚 Build SucceededHistory
To update your PR or re-run it, just comment with: |
Some maintenance and minor fixes to state containers based on experience while working with them in #53582 Patch unit tests to use current "terminology" (e.g. "transition" vs "mutation") Fix docs where "store" was used instead of "state container" Allow to create state container without transition. Fix freeze function to deeply freeze objects. Restrict State to BaseState with extends object. in set() function, make sure the flow goes through dispatch to make sure middleware see this update Improve type inference for useTransition() Improve type inference for createStateContainer(). Other issues noticed, but didn't fix in reasonable time: Can't use addMiddleware without explicit type casting #54438 Transitions and Selectors allow any state, not bind to container's state #54439
Some maintenance and minor fixes to state containers based on experience while working with them in elastic#53582 Patch unit tests to use current "terminology" (e.g. "transition" vs "mutation") Fix docs where "store" was used instead of "state container" Allow to create state container without transition. Fix freeze function to deeply freeze objects. Restrict State to BaseState with extends object. in set() function, make sure the flow goes through dispatch to make sure middleware see this update Improve type inference for useTransition() Improve type inference for createStateContainer(). Other issues noticed, but didn't fix in reasonable time: Can't use addMiddleware without explicit type casting elastic#54438 Transitions and Selectors allow any state, not bind to container's state elastic#54439
Summary
Today, apps rely on AppState and GlobalState in the
ui/state_management
module to deal with internal (app) and shared (global) state. These classes give apps an ability to read/write state, when is then synced to the URL as well as sessionStorage. They also react to changes in the URL and automatically update state & emit events when changes occur.This PR introduces new state synching utilities, which together with state containers
src/plugins/kibana_utils/public/state_containers
will be a replacement for AppState and GlobalState in New Platform.This PR is part of #44151.
This PR is implementation of #51692 POC, but also covers a bunch of advances use cases such as:
_g
using state syncing utilitysrc/legacy/ui/public/chrome/api/nav.ts
)[POC] Application state syncing utility. #51692 (comment)
Example App
There are 2 todo apps with different configs. React routers have different setup (browser and hash history). syncState util have different setup (different syncKeys)
The todo state is synched with different keys, so the state is not shared between apps.
The '_g' state is using the same key, so it is shared between apps.
video: https://drive.google.com/open?id=1q4RH75u59FhMn83d4a2Q5woI4TbiMVu3
To run with example todo apps:
yarn start --run-examples
Example covers this advanced cases:
Replicate Global State (
_g
) using state syncing utilityTo replicate
GlobalState
we just can define a common state container as static utility, use it in multiple apps and setupsyncState
utility in the same way in all the apps we need it (important to use the same storage keys and state storages).Preserve app state during navigation between different apps
If all the app state, that we need to preserve is located inside state containers, then we can just normally use
syncState
utility with synching state tosessionStorage
. This state will be restored fromsessionStorage
when navigated back to the app.But looking at
src/legacy/ui/public/chrome/api/nav.ts
, it is clear that not all the state is in the state containers. Some of important pieces could be part or routing. e.g./edit/:id/#?_g=
- hereedit/:id/
won't be part of state container.To cover this scenario - separate tiny
urlTracker
util was introduced and it is used in example app to track state from react-router routes which are not part of state container.Caveats:
src/legacy/ui/public/chrome/api/nav.ts
is part of chrome and it changes the app links in sidebar. As our utility would be decoupled from chrome, the links to apps will be static. The url will be restored by the application itself. [State Management] State syncing utilities #53582 (comment).React Router
To make syncState utils work seamlessly with
react-router
, had to make sure that they use the samehistory
instance. Hope it won't be needed in future, as apparently in v5 versionhistory
will be a singleton - https://github.com/ReactTraining/history/releases/tag/v5.0.0-beta.2Also makes sure it can work with react router in both modes: history and browser.
I also opened issues to platform:
Other Example
Dev Docs
#43511