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

UrlSync: Variable changes adds browser history steps #882

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
1 change: 0 additions & 1 deletion packages/scenes/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ export function isSceneObject(obj: any): obj is SceneObject {
export interface SceneObjectWithUrlSync extends SceneObject {
getUrlState(): SceneObjectUrlValues;
updateFromUrl(values: SceneObjectUrlValues): void;
shouldCreateHistoryStep?(values: SceneObjectUrlValues): boolean;
}

export interface SceneObjectUrlSyncHandler {
Expand Down
8 changes: 4 additions & 4 deletions packages/scenes/src/services/SceneObjectUrlSyncConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export class SceneObjectUrlSyncConfig implements SceneObjectUrlSyncHandler {
this._sceneObject.updateFromUrl(values);
}

public shouldCreateHistoryStep(values: SceneObjectUrlValues): boolean {
return this._nextChangeShouldAddHistoryStep;
}

public performBrowserHistoryAction(callback: () => void) {
this._nextChangeShouldAddHistoryStep = true;
callback();
this._nextChangeShouldAddHistoryStep = false;
}

public shouldCreateHistoryStep(values: SceneObjectUrlValues): boolean {
return this._nextChangeShouldAddHistoryStep;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function VariableValueSelect({ model }: SceneComponentProps<MultiValueVar
options={filteredOptions}
data-testid={selectors.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(`${value}`)}
onChange={(newValue) => {
model.changeValueTo(newValue.value!, newValue.label!);
model.changeValueTo(newValue.value!, newValue.label!, true);
queryController?.startProfile(model);

if (hasCustomValue !== newValue.__isNew__) {
Expand Down Expand Up @@ -179,14 +179,14 @@ export function VariableValueSelectMulti({ model }: SceneComponentProps<MultiVal
hideSelectedOptions={false}
onInputChange={onInputChange}
onBlur={() => {
model.changeValueTo(uncommittedValue);
model.changeValueTo(uncommittedValue, undefined, true);
queryController?.startProfile(model);
}}
filterOption={filterNoOp}
data-testid={selectors.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts(`${uncommittedValue}`)}
onChange={(newValue, action) => {
if (action.action === 'clear' && noValueOnClear) {
model.changeValueTo([]);
model.changeValueTo([], undefined, true);
}
setUncommittedValue(newValue.map((x) => x.value!));
}}
Expand Down
5 changes: 3 additions & 2 deletions packages/scenes/src/variables/groupby/GroupByVariable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,13 @@ export function GroupByVariableRenderer({ model }: SceneComponentProps<MultiValu
onBlur={() => {
model.changeValueTo(
uncommittedValue.map((x) => x.value!),
uncommittedValue.map((x) => x.label!)
uncommittedValue.map((x) => x.label!),
true
);
}}
onChange={(newValue, action) => {
if (action.action === 'clear' && noValueOnClear) {
model.changeValueTo([]);
model.changeValueTo([], undefined, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to add an extra method to avoid repeatedly calling the changeValueTo method with undefined and true?

For example:

- variable.changeValueTo(value, undefined, true);
+ variable.changeValuePushToHistory(value);
+
+ public changeValuePushToHistory(value: VariableValue) {
+    this.changeValueTo(value, undefined, true);
+ }

It's just some syntactic sugar, but will make it easier to read in the plugin code. Alternatively each plugin can also implement their own util to handle this changeVariablePushToHistory(variable, value) if we don't want to add it to the library.

}
setUncommittedValue(newValue);
}}
Expand Down
29 changes: 26 additions & 3 deletions packages/scenes/src/variables/variants/MultiValueVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
extends SceneObjectBase<TState>
implements SceneVariable<TState>
{
protected _urlSync: SceneObjectUrlSyncHandler = new MultiValueUrlSyncHandler(this);
protected _urlSync: MultiValueUrlSyncHandler<TState> = new MultiValueUrlSyncHandler(this);

/**
* Set to true to skip next value validation to maintain the current value even it it's not among the options (ie valid values)
Expand Down Expand Up @@ -260,7 +260,7 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
/**
* Change the value and publish SceneVariableValueChangedEvent event.
*/
public changeValueTo(value: VariableValue, text?: VariableValue) {
public changeValueTo(value: VariableValue, text?: VariableValue, isUserAction = false) {
// Ignore if there is no change
if (value === this.state.value && text === this.state.text) {
return;
Expand Down Expand Up @@ -301,7 +301,18 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
return;
}

this.setStateHelper({ value, text, loading: false });
const stateChangeAction = () => this.setStateHelper({ value, text, loading: false });

/**
* Because variable state changes can cause a whole chain of downstream state changes in other variables (that also cause URL update)
* Only some variable changes should add new history items to make sure the browser history contains valid URL states to go back to.
*/
if (isUserAction) {
this._urlSync.performBrowserHistoryAction(stateChangeAction);
} else {
stateChangeAction();
}

this.publishEvent(new SceneVariableValueChangedEvent(this), true);
}

Expand Down Expand Up @@ -386,6 +397,8 @@ function findOptionMatchingCurrent(
export class MultiValueUrlSyncHandler<TState extends MultiValueVariableState = MultiValueVariableState>
implements SceneObjectUrlSyncHandler
{
private _nextChangeShouldAddHistoryStep = false;

public constructor(private _sceneObject: MultiValueVariable<TState>) {}

private getKey(): string {
Expand Down Expand Up @@ -446,6 +459,16 @@ export class MultiValueUrlSyncHandler<TState extends MultiValueVariableState = M
this._sceneObject.changeValueTo(urlValue);
}
}

public performBrowserHistoryAction(callback: () => void) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! 🎉 Do you think it could be the default behavior of SceneObjectUrlSyncConfig and AdHocFiltersVariableUrlSyncHandler

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, probably. pushed an update to #878 that adds it to SceneObjectUrlSyncConfig

this._nextChangeShouldAddHistoryStep = true;
callback();
this._nextChangeShouldAddHistoryStep = false;
}

public shouldCreateHistoryStep(values: SceneObjectUrlValues): boolean {
return this._nextChangeShouldAddHistoryStep;
}
}

function handleLegacyUrlAllValue(value: string | string[]) {
Expand Down
Loading