mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
Variables: Url sync for MultiValueVariable (#59371)
* Initial take on variable url sync * Url sync is working * Fixing test
This commit is contained in:
@@ -151,14 +151,13 @@ export function isSceneObject(obj: any): obj is SceneObject {
|
||||
return obj.useState !== undefined;
|
||||
}
|
||||
|
||||
/** These functions are still just temporary until this get's refined */
|
||||
export interface SceneObjectWithUrlSync<TState> extends SceneObject {
|
||||
getUrlState(state: TState): SceneObjectUrlValues;
|
||||
updateFromUrl(values: SceneObjectUrlValues): void;
|
||||
}
|
||||
|
||||
export interface SceneObjectUrlSyncHandler<TState> {
|
||||
getKeys(): Set<string>;
|
||||
getKeys(): string[];
|
||||
getUrlState(state: TState): SceneObjectUrlValues;
|
||||
updateFromUrl(values: SceneObjectUrlValues): void;
|
||||
}
|
||||
|
||||
@@ -6,17 +6,17 @@ import {
|
||||
} from '../core/types';
|
||||
|
||||
interface SceneObjectUrlSyncConfigOptions {
|
||||
keys?: string[];
|
||||
keys: string[];
|
||||
}
|
||||
|
||||
export class SceneObjectUrlSyncConfig<TState extends SceneObjectState> implements SceneObjectUrlSyncHandler<TState> {
|
||||
private _keys: Set<string>;
|
||||
private _keys: string[];
|
||||
|
||||
public constructor(private _sceneObject: SceneObjectWithUrlSync<TState>, _options: SceneObjectUrlSyncConfigOptions) {
|
||||
this._keys = new Set(_options.keys);
|
||||
this._keys = _options.keys;
|
||||
}
|
||||
|
||||
public getKeys(): Set<string> {
|
||||
public getKeys(): string[] {
|
||||
return this._keys;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,7 @@ interface TestObjectState extends SceneLayoutChildState {
|
||||
}
|
||||
|
||||
class TestObj extends SceneObjectBase<TestObjectState> {
|
||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, {
|
||||
keys: ['name', 'array'],
|
||||
});
|
||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['name', 'array'] });
|
||||
|
||||
public getUrlState(state: TestObjectState) {
|
||||
return { name: state.name, array: state.array };
|
||||
@@ -74,12 +72,6 @@ describe('UrlSyncManager', () => {
|
||||
|
||||
// Should not update url
|
||||
expect(locationUpdates.length).toBe(1);
|
||||
|
||||
// When clearing url (via go back)
|
||||
locationService.getHistory().goBack();
|
||||
|
||||
// Should restore to initial state
|
||||
expect(obj.state.name).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,7 +96,8 @@ describe('UrlSyncManager', () => {
|
||||
expect(obj.state.name).toBe('test2');
|
||||
|
||||
// When relevant key is cleared (say go back)
|
||||
locationService.partial({ name: null });
|
||||
locationService.getHistory().goBack();
|
||||
|
||||
// Should revert to initial state
|
||||
expect(obj.state.name).toBe('test');
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export class UrlSyncManager {
|
||||
}
|
||||
|
||||
if (Object.keys(mappedUpdated).length > 0) {
|
||||
locationService.partial(mappedUpdated, false);
|
||||
locationService.partial(mappedUpdated, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -153,4 +153,64 @@ describe('MultiValueVariable', () => {
|
||||
expect(variable.getValue()).toEqual(['1', '2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Url syncing', () => {
|
||||
it('getUrlState should return single value state if value is single value', async () => {
|
||||
const variable = new ExampleVariable({
|
||||
name: 'test',
|
||||
options: [],
|
||||
optionsToReturn: [],
|
||||
value: '1',
|
||||
text: 'A',
|
||||
});
|
||||
|
||||
expect(variable.urlSync?.getUrlState(variable.state)).toEqual({ ['var-test']: '1' });
|
||||
});
|
||||
|
||||
it('getUrlState should return string array if value is string array', async () => {
|
||||
const variable = new ExampleVariable({
|
||||
name: 'test',
|
||||
options: [],
|
||||
optionsToReturn: [],
|
||||
value: ['1', '2'],
|
||||
text: ['A', 'B'],
|
||||
});
|
||||
|
||||
expect(variable.urlSync?.getUrlState(variable.state)).toEqual({ ['var-test']: ['1', '2'] });
|
||||
});
|
||||
|
||||
it('fromUrlState should update value for single value', async () => {
|
||||
const variable = new ExampleVariable({
|
||||
name: 'test',
|
||||
options: [
|
||||
{ label: 'A', value: '1' },
|
||||
{ label: 'B', value: '2' },
|
||||
],
|
||||
optionsToReturn: [],
|
||||
value: '1',
|
||||
text: 'A',
|
||||
});
|
||||
|
||||
variable.urlSync?.updateFromUrl({ ['var-test']: '2' });
|
||||
expect(variable.state.value).toEqual('2');
|
||||
expect(variable.state.text).toEqual('B');
|
||||
});
|
||||
|
||||
it('fromUrlState should update value for array value', async () => {
|
||||
const variable = new ExampleVariable({
|
||||
name: 'test',
|
||||
options: [
|
||||
{ label: 'A', value: '1' },
|
||||
{ label: 'B', value: '2' },
|
||||
],
|
||||
optionsToReturn: [],
|
||||
value: '1',
|
||||
text: 'A',
|
||||
});
|
||||
|
||||
variable.urlSync?.updateFromUrl({ ['var-test']: ['2', '1'] });
|
||||
expect(variable.state.value).toEqual(['2', '1']);
|
||||
expect(variable.state.text).toEqual(['B', 'A']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { map, Observable } from 'rxjs';
|
||||
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/constants';
|
||||
|
||||
import { SceneObjectBase } from '../../core/SceneObjectBase';
|
||||
import { SceneObject } from '../../core/types';
|
||||
import { SceneObject, SceneObjectUrlSyncHandler, SceneObjectUrlValues } from '../../core/types';
|
||||
import {
|
||||
SceneVariable,
|
||||
SceneVariableValueChangedEvent,
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ValidateAndUpdateResult,
|
||||
VariableValue,
|
||||
VariableValueOption,
|
||||
VariableValueSingle,
|
||||
} from '../types';
|
||||
|
||||
export interface MultiValueVariableState extends SceneVariableState {
|
||||
@@ -29,6 +30,8 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
|
||||
extends SceneObjectBase<TState>
|
||||
implements SceneVariable<TState>
|
||||
{
|
||||
protected _urlSync: SceneObjectUrlSyncHandler<TState> = new MultiValueUrlSyncHandler(this);
|
||||
|
||||
/**
|
||||
* The source of value options.
|
||||
*/
|
||||
@@ -124,20 +127,38 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
|
||||
return value === ALL_VARIABLE_VALUE || (Array.isArray(value) && value[0] === ALL_VARIABLE_VALUE);
|
||||
}
|
||||
|
||||
private setStateAndPublishValueChangedEvent(state: Partial<MultiValueVariableState>) {
|
||||
this.setStateHelper(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the value and publish SceneVariableValueChangedEvent event
|
||||
*/
|
||||
public changeValueTo(value: VariableValue, text?: VariableValue) {
|
||||
if (value !== this.state.value || text !== this.state.text) {
|
||||
this.setStateAndPublishValueChangedEvent({ value, text, loading: false });
|
||||
if (!text) {
|
||||
if (Array.isArray(value)) {
|
||||
text = value.map((v) => this.findLabelTextForValue(v));
|
||||
} else {
|
||||
text = this.findLabelTextForValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
this.setStateHelper({ value, text, loading: false });
|
||||
this.publishEvent(new SceneVariableValueChangedEvent(this), true);
|
||||
}
|
||||
}
|
||||
|
||||
private findLabelTextForValue(value: VariableValueSingle): VariableValueSingle {
|
||||
const option = this.state.options.find((x) => x.value === value);
|
||||
if (option) {
|
||||
return option.label;
|
||||
}
|
||||
|
||||
const optionByLabel = this.state.options.find((x) => x.label === value);
|
||||
if (optionByLabel) {
|
||||
return optionByLabel.label;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper function is to counter the contravariance of setState
|
||||
*/
|
||||
@@ -146,3 +167,38 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
|
||||
test.setState(state);
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiValueUrlSyncHandler<TState extends MultiValueVariableState = MultiValueVariableState>
|
||||
implements SceneObjectUrlSyncHandler<TState>
|
||||
{
|
||||
public constructor(private _sceneObject: MultiValueVariable<TState>) {}
|
||||
|
||||
private getKey(): string {
|
||||
return `var-${this._sceneObject.state.name}`;
|
||||
}
|
||||
|
||||
public getKeys(): string[] {
|
||||
return [this.getKey()];
|
||||
}
|
||||
|
||||
public getUrlState(state: TState): SceneObjectUrlValues {
|
||||
let urlValue: string | string[] | null = null;
|
||||
let value = state.value;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
urlValue = value.map(String);
|
||||
} else {
|
||||
urlValue = String(value);
|
||||
}
|
||||
|
||||
return { [this.getKey()]: urlValue };
|
||||
}
|
||||
|
||||
public updateFromUrl(values: SceneObjectUrlValues): void {
|
||||
const urlValue = values[this.getKey()];
|
||||
|
||||
if (urlValue != null) {
|
||||
this._sceneObject.changeValueTo(urlValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user