-
Notifications
You must be signed in to change notification settings - Fork 3
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
feat: create new browser history entry on some user actions #128
Conversation
if (typeof values.explorationType === 'string' && values.explorationType !== this.state.explorationType) { | ||
stateUpdate.explorationType = values.explorationType as ExplorationType; | ||
const type = values.explorationType as ExplorationType; | ||
this.setExplorationType({ type }); |
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.
Switching to the getter to get the body updated correctly when going back in 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.
ππΎ The only drawback is that the same body is built twice - only when landing on the page for the 1st time, if there's an explorationType
parameter in the URL.
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 one side, we should prevent it in onActivate
:
if (!this.state.explorationType) {
this.setExplorationType({
type: SceneProfilesExplorer.DEFAULT_EXPLORATION_TYPE,
});
}
And on the other side, updateFromUrl
should become:
updateFromUrl(values: SceneObjectUrlValues) {
if (typeof values.explorationType === 'string' && values.explorationType !== this.state.explorationType) {
const type = values.explorationType as ExplorationType;
this.setExplorationType({
type: Object.values(ExplorationType).includes(type) ? type : SceneProfilesExplorer.DEFAULT_EXPLORATION_TYPE,
});
}
}
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.
Thanks! Should be fixed by c66235c
@@ -103,6 +104,9 @@ export class ProfileMetricVariable extends QueryVariable { | |||
onSelect = (newValue: string) => { | |||
reportInteraction('g_pyroscope_app_profile_metric_selected'); | |||
|
|||
if (!this.state.skipUrlSync) { |
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.
Checking skipUrlSync because we use this variable with skipping in other place
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.
another variant would be
if (this.state.skipUrlSync) {
this.changeValueTo(newValue);
} else {
locationService.partial({ [`var-{this.state.name}`]: newValue })
}
need to URL encode the value there.
doing it this way would be compatible (not conflict) with any built-in browser history history support in UrlSyncManager / interfaces
@@ -14,7 +14,7 @@ export function useUrlSearchParams() { | |||
newSearchParams.set(key, value); | |||
} | |||
|
|||
history.push({ search: newSearchParams.toString() }); | |||
history.replace({ search: newSearchParams.toString() }); |
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.
Switching to replace to avoid creating redundant history items
@@ -225,6 +223,7 @@ export class SceneProfilesExplorer extends SceneObjectBase<SceneProfilesExplorer | |||
item?: GridItemData; | |||
}) { | |||
if (comesFromUserAction) { | |||
prepareHistoryEntry(); |
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.
could this not just do a locationService.partial as well (you would get history step and update state in one action)
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.
this is what we do for most URL synced "view" state in core dashboards (update the URL via normal link or locationService)
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.
You mean to update the URL instead of changing the state and let the updateFromUrl
handle the update? π€ Does it mean you need to know the final name of the variable when doing locationService.partial()
? How do you handle it if variable name may be different after processing with urlKeyMapper.getUniqueKey
in UrlSyncManager?
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.
true, you would need t know the real URL key (so would only work for things where you know this).
Was just an idea to automatically get a history step for a user action
been making progress on grafana/scenes#760 , can you check it out?
@@ -103,6 +104,9 @@ export class ProfileMetricVariable extends QueryVariable { | |||
onSelect = (newValue: string) => { | |||
reportInteraction('g_pyroscope_app_profile_metric_selected'); | |||
|
|||
if (!this.state.skipUrlSync) { |
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.
another variant would be
if (this.state.skipUrlSync) {
this.changeValueTo(newValue);
} else {
locationService.partial({ [`var-{this.state.name}`]: newValue })
}
need to URL encode the value there.
doing it this way would be compatible (not conflict) with any built-in browser history history support in UrlSyncManager / interfaces
Thank you for the PR! It works as expected except in two cases: We should also call
|
Thanks @grafakus π
Great catch! I've added prepping history directly to Should be fixed by 584db4b
Another nice catch! Turns out when variables are updated from the URL both Changed in e55d05e Alternatively we can also revert adding a new history for service and profile for now and think how to unhack Cascader first π€ |
@ifrost thank you for making the changes!
The tracking is centralized in SelectAction
I suppose what's lacking is to be able to control the Cascader value... In the meantime, if we end up creating a key by concatenating several values and stringifying options, I'd rather go with a simpler hack to force a render each time: return (
<Cascader
// TODO: ...
key={Math.random()}
... It shouldn't be a big perf issue, wdyt? |
It should be fine. We may get some additional renders (2 or 3) on first paint but shouldn't have a big impact. Essentially we need to re-render Cascader when value, loading state or options change which is anyway is the only props that cause the re-render of the parent component (and hence Cascader too) so it should have almost identical outcome. Updated in c05ef57 |
Thank you for helping on this @ifrost! |
β¨ Description
Related issue(s):
Fixes #121
π Summary of the changes
Adds basic support for browser history. A new history is created upon user actions:
Here's how it works:
browser-history.mp4
Technical details
The approach is to make sure we add new user actions to the history stack. Just before the user action handler is called we create a new empty entry in the stack and then UrlSyncManager takes care of replacing that history with any updates to variables. This will include multiple updates, and chain of updates. Any changes to URL that are not coming from the user action will be automatically added to the current history.
Alternative approaches:
That can work fine only when there's one variable updated upon user action. When there are multiple updates, they need to be somehow batched into single entry and it's tricky because the update is asynchronous. It's also visible during loading initial state as it usually requires multiple vars to be updated.
That works fine but requires some boilerplate just to support it in our case (SceneApp and SceneAppPage). It works great when it's used with native tabs coming with scenes but we don't use it for exploration type. Hence, to support drilldowns it would require even more boilerplate to create correct URLs for each exploration type (basically duplicating something that scenes handles internally with preserverUrlKeys
Instead of calling
variable.changeValueTo
we can change the value directly by modifying the URL. Dashboards do it to handle history (example). This way there's no need to depend onlocation.partial
happening after pushing a new entry (like in this PR). The drawback might be that if a variable name is not unique it's hard to determine it's name as it's mapped by UrlSyncManager/UniqueUrlKeyMapper.π§ͺ How to test?
It requires manual testing. Please try changing elements mentioned above (and others) and see if back/forward buttons behave as expected.