mirror of https://github.com/grafana/grafana.git
				
				
				
			Dashboard Scene: Sync variable state with TemplateSrv result (#93327)
* Generate options for variables through TemplateSrv * Add refresh when object changes * Remove unnecesary static function * Extract logic * Add extra test case when variable changes and refresh event is triggered * bring back old logic, query options should not live in the dashboard json * add missing change * Add support to keep variable options in query * tests * move refreshEvent to DashboardVariableDependency --------- Co-authored-by: alexandra vargas <alexa1866@gmail.com> Co-authored-by: Victor Marin <victor.marin@grafana.com>
This commit is contained in:
		
							parent
							
								
									2b320b0f9e
								
							
						
					
					
						commit
						d09017f7a2
					
				|  | @ -1,5 +1,5 @@ | ||||||
| import { CoreApp, GrafanaConfig, LoadingState, getDefaultTimeRange, locationUtil, store } from '@grafana/data'; | import { CoreApp, GrafanaConfig, LoadingState, getDefaultTimeRange, locationUtil, store } from '@grafana/data'; | ||||||
| import { locationService } from '@grafana/runtime'; | import { locationService, RefreshEvent } from '@grafana/runtime'; | ||||||
| import { | import { | ||||||
|   sceneGraph, |   sceneGraph, | ||||||
|   SceneGridLayout, |   SceneGridLayout, | ||||||
|  | @ -750,6 +750,21 @@ describe('DashboardScene', () => { | ||||||
|       variable.setState({ name: 'A' }); |       variable.setState({ name: 'A' }); | ||||||
|       expect(scene.state.isDirty).toBe(false); |       expect(scene.state.isDirty).toBe(false); | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|  |     it('should trigger scene RefreshEvent when a scene variable changes', () => { | ||||||
|  |       const varA = new TestVariable({ name: 'A', query: 'A.*', value: 'A.AA', text: '', options: [], delayMs: 0 }); | ||||||
|  |       const scene = buildTestScene({ | ||||||
|  |         $variables: new SceneVariableSet({ variables: [varA] }), | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       scene.activate(); | ||||||
|  | 
 | ||||||
|  |       const eventHandler = jest.fn(); | ||||||
|  |       // this RefreshEvent is from the scenes library
 | ||||||
|  |       scene.subscribeToEvent(RefreshEvent, eventHandler); | ||||||
|  |       varA.changeValueTo('A.AB'); | ||||||
|  |       expect(eventHandler).toHaveBeenCalledTimes(1); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   describe('When a dashboard is restored', () => { |   describe('When a dashboard is restored', () => { | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ import { | ||||||
|   DataSourceGetTagKeysOptions, |   DataSourceGetTagKeysOptions, | ||||||
|   DataSourceGetTagValuesOptions, |   DataSourceGetTagValuesOptions, | ||||||
| } from '@grafana/data'; | } from '@grafana/data'; | ||||||
| import { config, locationService } from '@grafana/runtime'; | import { config, locationService, RefreshEvent } from '@grafana/runtime'; | ||||||
| import { | import { | ||||||
|   sceneGraph, |   sceneGraph, | ||||||
|   SceneGridRow, |   SceneGridRow, | ||||||
|  | @ -716,6 +716,9 @@ export class DashboardVariableDependency implements SceneVariableDependencyConfi | ||||||
|     if (hasChanged) { |     if (hasChanged) { | ||||||
|       // Temp solution for some core panels (like dashlist) to know that variables have changed
 |       // Temp solution for some core panels (like dashlist) to know that variables have changed
 | ||||||
|       appEvents.publish(new VariablesChanged({ refreshAll: true, panelIds: [] })); |       appEvents.publish(new VariablesChanged({ refreshAll: true, panelIds: [] })); | ||||||
|  |       // Backwards compat with plugins that rely on the RefreshEvent when a
 | ||||||
|  |       // variable changes. TODO: We should redirect plugin devs to use VariablesChanged event
 | ||||||
|  |       this._dashboard.publishEvent(new RefreshEvent()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (variable.state.name === PANEL_SEARCH_VAR) { |     if (variable.state.name === PANEL_SEARCH_VAR) { | ||||||
|  |  | ||||||
|  | @ -190,6 +190,62 @@ describe('sceneVariablesSetToVariables', () => { | ||||||
|     `);
 |     `);
 | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |   it('should handle Query variable when sceneVariablesSetToVariables should discard options', () => { | ||||||
|  |     const variable = new QueryVariable({ | ||||||
|  |       name: 'test', | ||||||
|  |       label: 'test-label', | ||||||
|  |       description: 'test-desc', | ||||||
|  |       value: ['selected-value'], | ||||||
|  |       text: ['selected-value-text'], | ||||||
|  |       datasource: { uid: 'fake-std', type: 'fake-std' }, | ||||||
|  |       query: 'query', | ||||||
|  |       options: [ | ||||||
|  |         { label: 'test', value: 'test' }, | ||||||
|  |         { label: 'test1', value: 'test1' }, | ||||||
|  |         { label: 'test2', value: 'test2' }, | ||||||
|  |       ], | ||||||
|  |       includeAll: true, | ||||||
|  |       allValue: 'test-all', | ||||||
|  |       isMulti: true, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     const set = new SceneVariableSet({ | ||||||
|  |       variables: [variable], | ||||||
|  |     }); | ||||||
|  |     const result = sceneVariablesSetToVariables(set); | ||||||
|  |     expect(result).toHaveLength(1); | ||||||
|  |     expect(result[0].options).toEqual([]); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('should handle Query variable when sceneVariablesSetToVariables shoudl keep options', () => { | ||||||
|  |     const variable = new QueryVariable({ | ||||||
|  |       name: 'test', | ||||||
|  |       label: 'test-label', | ||||||
|  |       description: 'test-desc', | ||||||
|  |       value: ['test'], | ||||||
|  |       text: ['test'], | ||||||
|  |       datasource: { uid: 'fake-std', type: 'fake-std' }, | ||||||
|  |       query: 'query', | ||||||
|  |       options: [ | ||||||
|  |         { label: 'test', value: 'test' }, | ||||||
|  |         { label: 'test1', value: 'test1' }, | ||||||
|  |         { label: 'test2', value: 'test2' }, | ||||||
|  |       ], | ||||||
|  |       includeAll: true, | ||||||
|  |       allValue: 'test-all', | ||||||
|  |       isMulti: true, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     const set = new SceneVariableSet({ | ||||||
|  |       variables: [variable], | ||||||
|  |     }); | ||||||
|  |     const keepQueryOptions = true; | ||||||
|  |     const result = sceneVariablesSetToVariables(set, keepQueryOptions); | ||||||
|  |     expect(result).toHaveLength(1); | ||||||
|  |     expect(result[0].options).not.toEqual([]); | ||||||
|  |     expect(result[0].options?.length).toEqual(3); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|   it('should handle DatasourceVariable', () => { |   it('should handle DatasourceVariable', () => { | ||||||
|     const variable = new DataSourceVariable({ |     const variable = new DataSourceVariable({ | ||||||
|       name: 'test', |       name: 'test', | ||||||
|  |  | ||||||
|  | @ -4,7 +4,16 @@ import { VariableHide, VariableModel, VariableOption, VariableRefresh, VariableS | ||||||
| 
 | 
 | ||||||
| import { getIntervalsQueryFromNewIntervalModel } from '../utils/utils'; | import { getIntervalsQueryFromNewIntervalModel } from '../utils/utils'; | ||||||
| 
 | 
 | ||||||
| export function sceneVariablesSetToVariables(set: SceneVariables) { | /** | ||||||
|  |  * Converts a SceneVariables object into an array of VariableModel objects. | ||||||
|  |  * @param set - The SceneVariables object containing the variables to convert. | ||||||
|  |  * @param keepQueryOptions - (Optional) A boolean flag indicating whether to keep the options for query variables. | ||||||
|  |  *                           This should be set to `false` when variables are saved in the dashboard model, | ||||||
|  |  *                           but should be set to `true` when variables are used in the templateSrv to keep them in sync. | ||||||
|  |  *                           If `true`, the options for query variables are kept. | ||||||
|  |  *  */ | ||||||
|  | 
 | ||||||
|  | export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptions?: boolean) { | ||||||
|   const variables: VariableModel[] = []; |   const variables: VariableModel[] = []; | ||||||
|   for (const variable of set.state.variables) { |   for (const variable of set.state.variables) { | ||||||
|     const commonProperties = { |     const commonProperties = { | ||||||
|  | @ -19,7 +28,7 @@ export function sceneVariablesSetToVariables(set: SceneVariables) { | ||||||
|       let options: VariableOption[] = []; |       let options: VariableOption[] = []; | ||||||
|       // Not sure if we actually have to still support this option given
 |       // Not sure if we actually have to still support this option given
 | ||||||
|       // that it's not exposed in the UI
 |       // that it's not exposed in the UI
 | ||||||
|       if (variable.state.refresh === VariableRefresh.never) { |       if (variable.state.refresh === VariableRefresh.never || keepQueryOptions) { | ||||||
|         options = variableValueOptionsToVariableOptions(variable.state); |         options = variableValueOptionsToVariableOptions(variable.state); | ||||||
|       } |       } | ||||||
|       variables.push({ |       variables.push({ | ||||||
|  |  | ||||||
|  | @ -5,7 +5,13 @@ import { sceneVariablesSetToVariables } from '../serialization/sceneVariablesSet | ||||||
| 
 | 
 | ||||||
| export function getVariablesCompatibility(sceneObject: SceneObject): TypedVariableModel[] { | export function getVariablesCompatibility(sceneObject: SceneObject): TypedVariableModel[] { | ||||||
|   const set = sceneGraph.getVariables(sceneObject); |   const set = sceneGraph.getVariables(sceneObject); | ||||||
|   const legacyModels = sceneVariablesSetToVariables(set); |   const keepQueryOptions = true; | ||||||
|  | 
 | ||||||
|  |   // `sceneVariablesSetToVariables` is also used when transforming the scene to a save model.
 | ||||||
|  |   // In those cases, query options will be stripped out.
 | ||||||
|  |   // However, when `getVariablesCompatibility` is called from `templateSrv`, it is used to get all variables in the scene.
 | ||||||
|  |   // Therefore, options should be kept.
 | ||||||
|  |   const legacyModels = sceneVariablesSetToVariables(set, keepQueryOptions); | ||||||
| 
 | 
 | ||||||
|   // Sadly templateSrv.getVariables returns TypedVariableModel but sceneVariablesSetToVariables return persisted schema model
 |   // Sadly templateSrv.getVariables returns TypedVariableModel but sceneVariablesSetToVariables return persisted schema model
 | ||||||
|   // They look close to identical (differ in what is optional in some places).
 |   // They look close to identical (differ in what is optional in some places).
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue