diff --git a/public/app/features/scenes/core/types.ts b/public/app/features/scenes/core/types.ts index 4520e0f2def..18a332e96a6 100644 --- a/public/app/features/scenes/core/types.ts +++ b/public/app/features/scenes/core/types.ts @@ -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 extends SceneObject { getUrlState(state: TState): SceneObjectUrlValues; updateFromUrl(values: SceneObjectUrlValues): void; } export interface SceneObjectUrlSyncHandler { - getKeys(): Set; + getKeys(): string[]; getUrlState(state: TState): SceneObjectUrlValues; updateFromUrl(values: SceneObjectUrlValues): void; } diff --git a/public/app/features/scenes/services/SceneObjectUrlSyncConfig.ts b/public/app/features/scenes/services/SceneObjectUrlSyncConfig.ts index 81c926b086b..12859e8ff20 100644 --- a/public/app/features/scenes/services/SceneObjectUrlSyncConfig.ts +++ b/public/app/features/scenes/services/SceneObjectUrlSyncConfig.ts @@ -6,17 +6,17 @@ import { } from '../core/types'; interface SceneObjectUrlSyncConfigOptions { - keys?: string[]; + keys: string[]; } export class SceneObjectUrlSyncConfig implements SceneObjectUrlSyncHandler { - private _keys: Set; + private _keys: string[]; public constructor(private _sceneObject: SceneObjectWithUrlSync, _options: SceneObjectUrlSyncConfigOptions) { - this._keys = new Set(_options.keys); + this._keys = _options.keys; } - public getKeys(): Set { + public getKeys(): string[] { return this._keys; } diff --git a/public/app/features/scenes/services/UrlSyncManager.test.ts b/public/app/features/scenes/services/UrlSyncManager.test.ts index 165346ca1fb..22c488ab086 100644 --- a/public/app/features/scenes/services/UrlSyncManager.test.ts +++ b/public/app/features/scenes/services/UrlSyncManager.test.ts @@ -17,9 +17,7 @@ interface TestObjectState extends SceneLayoutChildState { } class TestObj extends SceneObjectBase { - 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'); diff --git a/public/app/features/scenes/services/UrlSyncManager.ts b/public/app/features/scenes/services/UrlSyncManager.ts index 65a361da0a7..dce5b4bfc0c 100644 --- a/public/app/features/scenes/services/UrlSyncManager.ts +++ b/public/app/features/scenes/services/UrlSyncManager.ts @@ -63,7 +63,7 @@ export class UrlSyncManager { } if (Object.keys(mappedUpdated).length > 0) { - locationService.partial(mappedUpdated, false); + locationService.partial(mappedUpdated, true); } } }; diff --git a/public/app/features/scenes/variables/variants/MultiValueVariable.test.ts b/public/app/features/scenes/variables/variants/MultiValueVariable.test.ts index 2df3a4dcb09..8a9dfbb9205 100644 --- a/public/app/features/scenes/variables/variants/MultiValueVariable.test.ts +++ b/public/app/features/scenes/variables/variants/MultiValueVariable.test.ts @@ -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']); + }); + }); }); diff --git a/public/app/features/scenes/variables/variants/MultiValueVariable.ts b/public/app/features/scenes/variables/variants/MultiValueVariable.ts index 941865b9bf4..27544ab4234 100644 --- a/public/app/features/scenes/variables/variants/MultiValueVariable.ts +++ b/public/app/features/scenes/variables/variants/MultiValueVariable.ts @@ -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 implements SceneVariable { + protected _urlSync: SceneObjectUrlSyncHandler = new MultiValueUrlSyncHandler(this); + /** * The source of value options. */ @@ -124,20 +127,38 @@ export abstract class MultiValueVariable) { - 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 + implements SceneObjectUrlSyncHandler +{ + public constructor(private _sceneObject: MultiValueVariable) {} + + 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); + } + } +}