Skip to content

Commit

Permalink
UrlSync: Export new util functions (#529)
Browse files Browse the repository at this point in the history
  • Loading branch information
torkelo authored Jan 20, 2024
1 parent 550048a commit 57ade21
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 150 deletions.
6 changes: 3 additions & 3 deletions packages/scenes/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@

#### 🚀 Enhancement

- Variables: Query - Add optional `definition` prop to state [#489](https://github.com/grafana/scenes/pull/489) ([@axelavargas](https://github.com/axelavargas))
- Variables: Query - Add optional `definition` prop to state [#489](https://github.com/grafana/scenes/pull/489) ([@axelavargas](https://github.com/axelavargas))

#### 🐛 Bug Fix

Expand All @@ -164,7 +164,7 @@

#### 🚀 Enhancement

- Macros: Support $_interval[_ms] variable [#487](https://github.com/grafana/scenes/pull/487) ([@dprokop](https://github.com/dprokop))
- Macros: Support $\_interval[_ms] variable [#487](https://github.com/grafana/scenes/pull/487) ([@dprokop](https://github.com/dprokop))

#### 🐛 Bug Fix

Expand Down Expand Up @@ -338,7 +338,7 @@
- Variables: Support for variables on lower levels to depend on variables on higher levels [#443](https://github.com/grafana/scenes/pull/443) ([@torkelo](https://github.com/torkelo))
- VizPanel: Handle empty arrays when merging new panel options [#447](https://github.com/grafana/scenes/pull/447) ([@javiruiz01](https://github.com/javiruiz01))
- PanelContext: Eventbus should not filter out local events [#445](https://github.com/grafana/scenes/pull/445) ([@torkelo](https://github.com/torkelo))
- Variables: Support __org and __user variable macros [#449](https://github.com/grafana/scenes/pull/449) ([@torkelo](https://github.com/torkelo))
- Variables: Support **org and **user variable macros [#449](https://github.com/grafana/scenes/pull/449) ([@torkelo](https://github.com/torkelo))
- SceneQueryRunner: Fixes adhoc filters when using a variable data source [#422](https://github.com/grafana/scenes/pull/422) ([@torkelo](https://github.com/torkelo))
- VizPanel: Support passing legacyPanelId to PanelProps [#446](https://github.com/grafana/scenes/pull/446) ([@torkelo](https://github.com/torkelo))

Expand Down
5 changes: 4 additions & 1 deletion packages/scenes/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getUrlWithAppState } from './components/SceneApp/utils';
import { registerRuntimePanelPlugin } from './components/VizPanel/registerRuntimePanelPlugin';
import { cloneSceneObjectState } from './core/sceneGraph/utils';
import { registerRuntimeDataSource } from './querying/RuntimeDataSource';
import { getUrlState, syncStateFromSearchParams } from './services/utils';
import { registerVariableMacro } from './variables/macros';
import { renderPrometheusLabelFilters } from './variables/utils';
import {
Expand Down Expand Up @@ -50,7 +51,7 @@ export { AdHocFilterSet } from './variables/adhoc/AdHocFiltersSet';
export { AdHocFiltersVariable } from './variables/adhoc/AdHocFiltersVariable';
export { type MacroVariableConstructor } from './variables/macros/types';

export { type UrlSyncManagerLike as UrlSyncManager, getUrlSyncManager } from './services/UrlSyncManager';
export { type UrlSyncManagerLike, UrlSyncManager, getUrlSyncManager } from './services/UrlSyncManager';
export { SceneObjectUrlSyncConfig } from './services/SceneObjectUrlSyncConfig';

export { EmbeddedScene, type EmbeddedSceneState } from './components/EmbeddedScene';
Expand Down Expand Up @@ -98,6 +99,8 @@ export const sceneUtils = {
registerRuntimeDataSource,
registerVariableMacro,
cloneSceneObjectState,
syncStateFromSearchParams,
getUrlState,
renderPrometheusLabelFilters,

// Variable guards
Expand Down
45 changes: 45 additions & 0 deletions packages/scenes/src/services/UniqueUrlKeyMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { SceneObject } from '../core/types';

export interface SceneObjectWithDepth {
sceneObject: SceneObject;
depth: number;
}

export class UniqueUrlKeyMapper {
private index = new Map<string, SceneObjectWithDepth[]>();

public getUniqueKey(key: string, obj: SceneObject) {
const objectsWithKey = this.index.get(key);
if (!objectsWithKey) {
throw new Error("Cannot find any scene object that uses the key '" + key + "'");
}

const address = objectsWithKey.findIndex((o) => o.sceneObject === obj);
if (address > 0) {
return `${key}-${address + 1}`;
}

return key;
}

public rebuildIndex(root: SceneObject) {
this.index.clear();
this.buildIndex(root, 0);
}

private buildIndex(sceneObject: SceneObject, depth: number) {
if (sceneObject.urlSync) {
for (const key of sceneObject.urlSync.getKeys()) {
const hit = this.index.get(key);
if (hit) {
hit.push({ sceneObject, depth });
hit.sort((a, b) => a.depth - b.depth);
} else {
this.index.set(key, [{ sceneObject, depth }]);
}
}
}

sceneObject.forEachChild((child) => this.buildIndex(child, depth + 1));
}
}
74 changes: 54 additions & 20 deletions packages/scenes/src/services/UrlSyncManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,29 @@ import { locationService } from '@grafana/runtime';
import { SceneFlexItem, SceneFlexLayout } from '../components/layout/SceneFlexLayout';
import { SceneObjectBase } from '../core/SceneObjectBase';
import { SceneTimeRange } from '../core/SceneTimeRange';
import { SceneObjectState, SceneObject, SceneObjectUrlValues } from '../core/types';
import { SceneObjectState, SceneObjectUrlValues } from '../core/types';

import { SceneObjectUrlSyncConfig } from './SceneObjectUrlSyncConfig';
import { isUrlValueEqual, UrlSyncManager } from './UrlSyncManager';
import { UrlSyncManager } from './UrlSyncManager';

interface TestObjectState extends SceneObjectState {
name: string;
optional?: string;
array?: string[];
other?: string;
nested?: TestObj;
}

class TestObj extends SceneObjectBase<TestObjectState> {
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['name', 'array', 'optional'] });
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['name', 'array', 'optional', 'nested'] });

public getUrlState() {
return { name: this.state.name, array: this.state.array, optional: this.state.optional };
return {
name: this.state.name,
array: this.state.array,
optional: this.state.optional,
nested: this.state.nested ? 'nested' : undefined,
};
}

public updateFromUrl(values: SceneObjectUrlValues) {
Expand All @@ -36,14 +42,20 @@ class TestObj extends SceneObjectBase<TestObjectState> {
if (values.hasOwnProperty('optional')) {
this.setState({ optional: typeof values.optional === 'string' ? values.optional : undefined });
}

if (values.hasOwnProperty('nested')) {
this.setState({ nested: new TestObj({ name: 'default name' }) });
} else if (this.state.nested) {
this.setState({ nested: undefined });
}
}
}

describe('UrlSyncManager', () => {
let urlManager: UrlSyncManager;
let locationUpdates: Location[] = [];
let listenUnregister: () => void;
let scene: SceneObject;
let scene: SceneFlexLayout;
let deactivate = () => {};

beforeEach(() => {
Expand All @@ -57,6 +69,7 @@ describe('UrlSyncManager', () => {
deactivate();
locationService.push('/');
listenUnregister();
urlManager.cleanUp(scene);
});

describe('getUrlState', () => {
Expand Down Expand Up @@ -103,6 +116,42 @@ describe('UrlSyncManager', () => {
});
});

describe('Initiating state from url', () => {
it('Should sync nested objects created during sync', () => {
const obj = new TestObj({ name: 'test' });
scene = new SceneFlexLayout({
children: [new SceneFlexItem({ body: obj })],
});

locationService.partial({ name: 'name-from-url', nested: 'nested', 'name-2': 'nested name from initial url' });

urlManager = new UrlSyncManager();
urlManager.initSync(scene);

deactivate = scene.activate();

expect(obj.state.nested?.state.name).toEqual('nested name from initial url');
});

// it('Should get url state from with objects created after initial sync', () => {
// const obj = new TestObj({ name: 'test' });
// scene = new SceneFlexLayout({
// children: [],
// });

// locationService.partial({ name: 'name-from-url' });

// urlManager = new UrlSyncManager();
// urlManager.initSync(scene);

// deactivate = scene.activate();

// scene.setState({ children: [new SceneFlexItem({ body: obj })] });

// expect(obj.state.name).toEqual('name-from-url');
// });
});

describe('When url changes', () => {
it('should update state', () => {
const obj = new TestObj({ name: 'test' });
Expand Down Expand Up @@ -372,18 +421,3 @@ describe('UrlSyncManager', () => {
});
});
});

describe('isUrlValueEqual', () => {
it('should handle all cases', () => {
expect(isUrlValueEqual([], [])).toBe(true);
expect(isUrlValueEqual([], undefined)).toBe(true);
expect(isUrlValueEqual([], null)).toBe(true);

expect(isUrlValueEqual(['asd'], 'asd')).toBe(true);
expect(isUrlValueEqual(['asd'], ['asd'])).toBe(true);
expect(isUrlValueEqual(['asd', '2'], ['asd', '2'])).toBe(true);

expect(isUrlValueEqual(['asd', '2'], 'asd')).toBe(false);
expect(isUrlValueEqual(['asd2'], 'asd')).toBe(false);
});
});
Loading

0 comments on commit 57ade21

Please sign in to comment.