From 25d5e08286c58d7705089ccb97b26548ad41c244 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Wed, 14 Feb 2024 13:20:08 +0100 Subject: [PATCH] VariableDependencyConfig: Support `*` to extract dependencies from every state path (#599) --- docusaurus/docs/advanced-variables.md | 18 +++++++--------- .../VariableDependencyConfig.test.ts | 14 ++++++++++--- .../src/variables/VariableDependencyConfig.ts | 21 +++++++++++-------- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/docusaurus/docs/advanced-variables.md b/docusaurus/docs/advanced-variables.md index a7c64b2b1..0fc21f102 100644 --- a/docusaurus/docs/advanced-variables.md +++ b/docusaurus/docs/advanced-variables.md @@ -120,7 +120,7 @@ class TextInterpolator extends SceneObjectBase { `VariableDependencyConfig` accepts an object with the following configuration options: -- `statePaths` - Configures which properties of the object state can contain variables. +- `statePaths` - Configures which properties of the object state can contain variables. Use `['*']` to refer to any property of the object state. - `onReferencedVariableValueChanged` - Configures a callback that will be executed when variable(s) that the object depends on are changed. :::note @@ -154,8 +154,8 @@ The preceding code will render a scene with a template variable, text input, and You can register a custom variable macro using `sceneUtils.registerVariableMacro`. A variable macro is useful for variable expressions you want to be evaluted dynamically based on some context. Examples of core variables that are implemented as macros. -* ${__url.params:include:var-from,var-to} -* ${__user.login} +- ${\_\_url.params:include:var-from,var-to} +- ${\_\_user.login} Example: @@ -221,14 +221,12 @@ Variables: A, B, C (B depends on A, C depends on B). A depends on time range so SceneQueryRunner with a query that depends on variable C -* 1. Time range changes value -* 2. Variable A starts loading -* 3. SceneQueryRunner responds to time range change tries to start new query, but before new query is issued calls `variableDependency.hasDependencyInLoadingState`. This checks if variable C is loading wich it is not, so then checks if variable B is loading (since it's a dependency of C), which it is not so then checks A, A is loading so it returns true and SceneQueryRunner will skip issuing a new query. When this happens the VariableDependencyConfig will set an internal flag that it is waiting for a variable dependency, this makes sure that the moment a next variable completes onVariableUpdateCompleted is called (no matter if the variable that was completed is a direct dependency or if it has changed value or not, we just care that it completed loading). -* 4. Variable A completes loading. The options (possible values) are the same so no change value. -* 5. SceneQueryRunner's VariableDependencyConfig receives the notification that variable A has completed it's loading phase, since it is in a waiting for variables state it will call the onVariableUpdateCompleted callback even though A is not a direct dependency and it has not changed value. +- 1. Time range changes value +- 2. Variable A starts loading +- 3. SceneQueryRunner responds to time range change tries to start new query, but before new query is issued calls `variableDependency.hasDependencyInLoadingState`. This checks if variable C is loading wich it is not, so then checks if variable B is loading (since it's a dependency of C), which it is not so then checks A, A is loading so it returns true and SceneQueryRunner will skip issuing a new query. When this happens the VariableDependencyConfig will set an internal flag that it is waiting for a variable dependency, this makes sure that the moment a next variable completes onVariableUpdateCompleted is called (no matter if the variable that was completed is a direct dependency or if it has changed value or not, we just care that it completed loading). +- 4. Variable A completes loading. The options (possible values) are the same so no change value. +- 5. SceneQueryRunner's VariableDependencyConfig receives the notification that variable A has completed it's loading phase, since it is in a waiting for variables state it will call the onVariableUpdateCompleted callback even though A is not a direct dependency and it has not changed value. ## Source code [View the example source code](https://github.com/grafana/scenes/tree/main/docusaurus/docs/advanced-variables.tsx) - - diff --git a/packages/scenes/src/variables/VariableDependencyConfig.test.ts b/packages/scenes/src/variables/VariableDependencyConfig.test.ts index 678b6978b..dd76b4bcb 100644 --- a/packages/scenes/src/variables/VariableDependencyConfig.test.ts +++ b/packages/scenes/src/variables/VariableDependencyConfig.test.ts @@ -32,11 +32,19 @@ class TestObj extends SceneObjectBase { describe('VariableDependencyConfig', () => { it('Should be able to extract dependencies from all state', () => { const sceneObj = new TestObj(); - const deps = new VariableDependencyConfig(sceneObj, {}); + const deps = new VariableDependencyConfig(sceneObj, { statePaths: ['*'] }); expect(deps.getNames()).toEqual(new Set(['queryVarA', 'queryVarB', 'nestedVarA', 'otherPropA'])); }); + it('Should not extract dependencies from all state if no statePaths or variableName is defined', () => { + const sceneObj = new TestObj(); + const deps = new VariableDependencyConfig(sceneObj, {}); + + expect(deps.scanCount).toBe(0); + expect(deps.getNames()).toEqual(new Set([])); + }); + it('Should be able to extract dependencies from statePaths', () => { const sceneObj = new TestObj(); const deps = new VariableDependencyConfig(sceneObj, { statePaths: ['query', 'nested'] }); @@ -90,7 +98,7 @@ describe('VariableDependencyConfig', () => { it('variableValuesChanged should only call onReferencedVariableValueChanged if dependent variable has changed', () => { const sceneObj = new TestObj(); const fn = jest.fn(); - const deps = new VariableDependencyConfig(sceneObj, { onReferencedVariableValueChanged: fn }); + const deps = new VariableDependencyConfig(sceneObj, { onReferencedVariableValueChanged: fn, statePaths: ['*'] }); deps.variableUpdateCompleted(new ConstantVariable({ name: 'not-dep', value: '1' }), true); expect(fn.mock.calls.length).toBe(0); @@ -102,7 +110,7 @@ describe('VariableDependencyConfig', () => { it('Can update explicit depenendencies', () => { const sceneObj = new TestObj(); const fn = jest.fn(); - const deps = new VariableDependencyConfig(sceneObj, { onReferencedVariableValueChanged: fn }); + const deps = new VariableDependencyConfig(sceneObj, { onReferencedVariableValueChanged: fn, statePaths: ['*'] }); deps.variableUpdateCompleted(new ConstantVariable({ name: 'not-dep', value: '1' }), true); expect(fn.mock.calls.length).toBe(0); diff --git a/packages/scenes/src/variables/VariableDependencyConfig.ts b/packages/scenes/src/variables/VariableDependencyConfig.ts index 6092726c6..8cdb21a34 100644 --- a/packages/scenes/src/variables/VariableDependencyConfig.ts +++ b/packages/scenes/src/variables/VariableDependencyConfig.ts @@ -10,7 +10,7 @@ interface VariableDependencyConfigOptions { /** * State paths to scan / extract variable dependencies from. Leave empty to scan all paths. */ - statePaths?: Array; + statePaths?: Array; /** * Explicit list of variable names to depend on. Leave empty to scan state for dependencies. @@ -40,7 +40,7 @@ interface VariableDependencyConfigOptions { export class VariableDependencyConfig implements SceneVariableDependencyConfigLike { private _state: TState | undefined; private _dependencies = new Set(); - private _statePaths?: Array; + private _statePaths?: Array; private _isWaitingForVariables = false; public scanCount = 0; @@ -123,7 +123,7 @@ export class VariableDependencyConfig implement if (newState !== prevState) { if (this._statePaths) { for (const path of this._statePaths) { - if (newState[path] !== prevState[path]) { + if (path === '*' || newState[path] !== prevState[path]) { this.scanStateForDependencies(newState); break; } @@ -144,7 +144,7 @@ export class VariableDependencyConfig implement this.scanStateForDependencies(this._state!); } - public setPaths(paths: Array) { + public setPaths(paths: Array) { this._statePaths = paths; } @@ -159,13 +159,16 @@ export class VariableDependencyConfig implement } else { if (this._statePaths) { for (const path of this._statePaths) { - const value = state[path]; - if (value) { - this.extractVariablesFrom(value); + if (path === '*') { + this.extractVariablesFrom(state); + break; + } else { + const value = state[path]; + if (value) { + this.extractVariablesFrom(value); + } } } - } else { - this.extractVariablesFrom(state); } } }