mirror of https://github.com/grafana/grafana.git
				
				
				
			DashboardScene serialization: Handle variables and library panels (#76117)
* DashboardScene serialization: Library panels * DashboardScene serialization: Variables * TS fix * Review * betetrer -u
This commit is contained in:
		
							parent
							
								
									1a96f3742f
								
							
						
					
					
						commit
						2cfbe087fc
					
				|  | @ -3020,7 +3020,8 @@ exports[`better eslint`] = { | |||
|     ], | ||||
|     "public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts:5381": [ | ||||
|       [0, 0, 0, "Do not use any type assertions.", "0"], | ||||
|       [0, 0, 0, "Do not use any type assertions.", "1"] | ||||
|       [0, 0, 0, "Do not use any type assertions.", "1"], | ||||
|       [0, 0, 0, "Do not use any type assertions.", "2"] | ||||
|     ], | ||||
|     "public/app/features/dashboard-scene/utils/test-utils.ts:5381": [ | ||||
|       [0, 0, 0, "Do not use any type assertions.", "0"], | ||||
|  |  | |||
|  | @ -10,14 +10,15 @@ interface LibraryVizPanelState extends SceneObjectState { | |||
|   // Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it.
 | ||||
|   title: string; | ||||
|   uid: string; | ||||
|   name: string; | ||||
|   panel?: VizPanel; | ||||
| } | ||||
| 
 | ||||
| export class LibraryVizPanel extends SceneObjectBase<LibraryVizPanelState> { | ||||
|   static Component = LibraryPanelRenderer; | ||||
| 
 | ||||
|   constructor({ uid, title }: Pick<LibraryVizPanelState, 'uid' | 'title'>) { | ||||
|     super({ uid, title }); | ||||
|   constructor({ uid, title, key, name }: Pick<LibraryVizPanelState, 'uid' | 'title' | 'key' | 'name'>) { | ||||
|     super({ uid, title, key, name }); | ||||
| 
 | ||||
|     this.addActivationHandler(this._onActivate); | ||||
|   } | ||||
|  |  | |||
|  | @ -191,6 +191,48 @@ exports[`transformSceneToSaveModel Given a scene with rows Should transform back | |||
|   ], | ||||
|   "schemaVersion": 36, | ||||
|   "tags": [], | ||||
|   "templating": { | ||||
|     "list": [ | ||||
|       { | ||||
|         "current": { | ||||
|           "text": [ | ||||
|             "A", | ||||
|             "B", | ||||
|           ], | ||||
|           "value": [ | ||||
|             "A", | ||||
|             "B", | ||||
|           ], | ||||
|         }, | ||||
|         "hide": 0, | ||||
|         "includeAll": true, | ||||
|         "multi": true, | ||||
|         "name": "server", | ||||
|         "options": [], | ||||
|         "query": "A,B,C,D,E,F,E,G,H,I,J,K,L", | ||||
|         "skipUrlSync": false, | ||||
|       }, | ||||
|       { | ||||
|         "current": { | ||||
|           "text": [ | ||||
|             "1", | ||||
|             "2", | ||||
|           ], | ||||
|           "value": [ | ||||
|             "1", | ||||
|             "2", | ||||
|           ], | ||||
|         }, | ||||
|         "hide": 0, | ||||
|         "includeAll": true, | ||||
|         "multi": true, | ||||
|         "name": "pod", | ||||
|         "options": [], | ||||
|         "query": "Bob : 1, Rob : 2,Sod : 3, Hod : 4, Cod : 5", | ||||
|         "skipUrlSync": false, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   "time": { | ||||
|     "from": "now-6h", | ||||
|     "to": "now", | ||||
|  | @ -405,6 +447,9 @@ exports[`transformSceneToSaveModel Given a simple scene Should transform back to | |||
|   ], | ||||
|   "schemaVersion": 36, | ||||
|   "tags": [], | ||||
|   "templating": { | ||||
|     "list": [], | ||||
|   }, | ||||
|   "time": { | ||||
|     "from": "now-5m", | ||||
|     "to": "now", | ||||
|  |  | |||
|  | @ -0,0 +1,258 @@ | |||
| import { of } from 'rxjs'; | ||||
| 
 | ||||
| import { | ||||
|   DataSourceApi, | ||||
|   FieldType, | ||||
|   getDefaultTimeRange, | ||||
|   LoadingState, | ||||
|   PanelData, | ||||
|   PluginType, | ||||
|   ScopedVars, | ||||
|   toDataFrame, | ||||
|   VariableSupportType, | ||||
| } from '@grafana/data'; | ||||
| import { setRunRequest } from '@grafana/runtime'; | ||||
| import { ConstantVariable, CustomVariable, DataSourceVariable, QueryVariable, SceneVariableSet } from '@grafana/scenes'; | ||||
| import { DataSourceRef } from '@grafana/schema'; | ||||
| 
 | ||||
| import { sceneVariablesSetToVariables } from './sceneVariablesSetToVariables'; | ||||
| 
 | ||||
| const runRequestMock = jest.fn().mockReturnValue( | ||||
|   of<PanelData>({ | ||||
|     state: LoadingState.Done, | ||||
|     series: [ | ||||
|       toDataFrame({ | ||||
|         fields: [{ name: 'text', type: FieldType.string, values: ['val1', 'val2', 'val11'] }], | ||||
|       }), | ||||
|     ], | ||||
|     timeRange: getDefaultTimeRange(), | ||||
|   }) | ||||
| ); | ||||
| 
 | ||||
| setRunRequest(runRequestMock); | ||||
| 
 | ||||
| const getDataSourceMock = jest.fn(); | ||||
| 
 | ||||
| const fakeDsMock: DataSourceApi = { | ||||
|   name: 'fake-std', | ||||
|   type: 'fake-std', | ||||
|   getRef: () => ({ type: 'fake-std', uid: 'fake-std' }), | ||||
|   query: () => | ||||
|     Promise.resolve({ | ||||
|       data: [], | ||||
|     }), | ||||
|   testDatasource: () => Promise.resolve({ status: 'success', message: 'abc' }), | ||||
|   meta: { | ||||
|     id: 'fake-std', | ||||
|     type: PluginType.datasource, | ||||
|     module: 'fake-std', | ||||
|     baseUrl: '', | ||||
|     name: 'fake-std', | ||||
|     info: { | ||||
|       author: { name: '' }, | ||||
|       description: '', | ||||
|       links: [], | ||||
|       logos: { large: '', small: '' }, | ||||
|       updated: '', | ||||
|       version: '', | ||||
|       screenshots: [], | ||||
|     }, | ||||
|   }, | ||||
|   // Standard variable support
 | ||||
|   variables: { | ||||
|     getType: () => VariableSupportType.Standard, | ||||
|     toDataQuery: (q) => ({ ...q, refId: 'FakeDataSource-refId' }), | ||||
|   }, | ||||
|   id: 1, | ||||
|   uid: 'fake-std', | ||||
| }; | ||||
| 
 | ||||
| jest.mock('@grafana/runtime', () => ({ | ||||
|   ...jest.requireActual('@grafana/runtime'), | ||||
|   getDataSourceSrv: () => ({ | ||||
|     get: (ds: DataSourceRef, vars: ScopedVars): Promise<DataSourceApi> => { | ||||
|       getDataSourceMock(ds, vars); | ||||
|       return Promise.resolve(fakeDsMock); | ||||
|     }, | ||||
|   }), | ||||
| })); | ||||
| 
 | ||||
| describe('sceneVariablesSetToVariables', () => { | ||||
|   it('should handle QueryVariable', () => { | ||||
|     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', | ||||
|       includeAll: true, | ||||
|       allValue: 'test-all', | ||||
|       isMulti: true, | ||||
|     }); | ||||
|     const set = new SceneVariableSet({ | ||||
|       variables: [variable], | ||||
|     }); | ||||
| 
 | ||||
|     const result = sceneVariablesSetToVariables(set); | ||||
| 
 | ||||
|     expect(result).toHaveLength(1); | ||||
|     expect(result[0]).toMatchInlineSnapshot(` | ||||
|     { | ||||
|       "allValue": "test-all", | ||||
|       "current": { | ||||
|         "text": [ | ||||
|           "selected-value-text", | ||||
|         ], | ||||
|         "value": [ | ||||
|           "selected-value", | ||||
|         ], | ||||
|       }, | ||||
|       "datasource": { | ||||
|         "type": "fake-std", | ||||
|         "uid": "fake-std", | ||||
|       }, | ||||
|       "description": "test-desc", | ||||
|       "hide": 0, | ||||
|       "includeAll": true, | ||||
|       "label": "test-label", | ||||
|       "multi": true, | ||||
|       "name": "test", | ||||
|       "options": [], | ||||
|       "query": "query", | ||||
|       "refresh": 1, | ||||
|       "regex": "", | ||||
|       "skipUrlSync": false, | ||||
|       "sort": 1, | ||||
|     } | ||||
|     `);
 | ||||
|   }); | ||||
| 
 | ||||
|   it('should handle DatasourceVariable', () => { | ||||
|     const variable = new DataSourceVariable({ | ||||
|       name: 'test', | ||||
|       label: 'test-label', | ||||
|       description: 'test-desc', | ||||
|       value: ['selected-ds-1', 'selected-ds-2'], | ||||
|       text: ['selected-ds-1-text', 'selected-ds-2-text'], | ||||
|       pluginId: 'fake-std', | ||||
|       includeAll: true, | ||||
|       allValue: 'test-all', | ||||
|       isMulti: true, | ||||
|     }); | ||||
|     const set = new SceneVariableSet({ | ||||
|       variables: [variable], | ||||
|     }); | ||||
| 
 | ||||
|     const result = sceneVariablesSetToVariables(set); | ||||
| 
 | ||||
|     expect(result).toHaveLength(1); | ||||
|     expect(result[0]).toMatchInlineSnapshot(` | ||||
|     { | ||||
|       "allValue": "test-all", | ||||
|       "current": { | ||||
|         "text": [ | ||||
|           "selected-ds-1-text", | ||||
|           "selected-ds-2-text", | ||||
|         ], | ||||
|         "value": [ | ||||
|           "selected-ds-1", | ||||
|           "selected-ds-2", | ||||
|         ], | ||||
|       }, | ||||
|       "description": "test-desc", | ||||
|       "hide": 0, | ||||
|       "includeAll": true, | ||||
|       "label": "test-label", | ||||
|       "multi": true, | ||||
|       "name": "test", | ||||
|       "options": [], | ||||
|       "query": "fake-std", | ||||
|       "regex": "", | ||||
|       "skipUrlSync": false, | ||||
|     } | ||||
|     `);
 | ||||
|   }); | ||||
| 
 | ||||
|   it('should handle CustomVariable', () => { | ||||
|     const variable = new CustomVariable({ | ||||
|       name: 'test', | ||||
|       label: 'test-label', | ||||
|       description: 'test-desc', | ||||
|       value: ['test', 'test2'], | ||||
|       text: ['test', 'test2'], | ||||
|       query: 'test,test1,test2', | ||||
|       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]).toMatchInlineSnapshot(` | ||||
|     { | ||||
|       "allValue": "test-all", | ||||
|       "current": { | ||||
|         "text": [ | ||||
|           "test", | ||||
|           "test2", | ||||
|         ], | ||||
|         "value": [ | ||||
|           "test", | ||||
|           "test2", | ||||
|         ], | ||||
|       }, | ||||
|       "description": "test-desc", | ||||
|       "hide": 0, | ||||
|       "includeAll": true, | ||||
|       "label": "test-label", | ||||
|       "multi": true, | ||||
|       "name": "test", | ||||
|       "options": [], | ||||
|       "query": "test,test1,test2", | ||||
|       "skipUrlSync": false, | ||||
|     } | ||||
|     `);
 | ||||
|   }); | ||||
| 
 | ||||
|   it('should handle ConstantVariable', () => { | ||||
|     const variable = new ConstantVariable({ | ||||
|       name: 'test', | ||||
|       label: 'test-label', | ||||
|       description: 'test-desc', | ||||
|       value: 'constant value', | ||||
|       skipUrlSync: true, | ||||
|     }); | ||||
|     const set = new SceneVariableSet({ | ||||
|       variables: [variable], | ||||
|     }); | ||||
| 
 | ||||
|     const result = sceneVariablesSetToVariables(set); | ||||
| 
 | ||||
|     expect(result).toHaveLength(1); | ||||
|     expect(result[0]).toMatchInlineSnapshot(` | ||||
|     { | ||||
|       "current": { | ||||
|         "text": "constant value", | ||||
|         "value": "constant value", | ||||
|       }, | ||||
|       "description": "test-desc", | ||||
|       "hide": 2, | ||||
|       "label": "test-label", | ||||
|       "name": "test", | ||||
|       "query": "constant value", | ||||
|       "skipUrlSync": true, | ||||
|     } | ||||
|     `);
 | ||||
|   }); | ||||
| }); | ||||
|  | @ -0,0 +1,85 @@ | |||
| import { SceneVariableSet, QueryVariable, CustomVariable, DataSourceVariable, ConstantVariable } from '@grafana/scenes'; | ||||
| import { VariableModel, VariableHide } from '@grafana/schema'; | ||||
| 
 | ||||
| export function sceneVariablesSetToVariables(set: SceneVariableSet) { | ||||
|   const variables: VariableModel[] = []; | ||||
|   for (const variable of set.state.variables) { | ||||
|     const commonProperties = { | ||||
|       name: variable.state.name, | ||||
|       label: variable.state.label, | ||||
|       description: variable.state.description, | ||||
|       skipUrlSync: Boolean(variable.state.skipUrlSync), | ||||
|       hide: variable.state.hide || VariableHide.dontHide, | ||||
|     }; | ||||
|     if (variable instanceof QueryVariable) { | ||||
|       variables.push({ | ||||
|         ...commonProperties, | ||||
|         current: { | ||||
|           // @ts-expect-error
 | ||||
|           value: variable.state.value, | ||||
|           // @ts-expect-error
 | ||||
|           text: variable.state.text, | ||||
|         }, | ||||
|         options: [], | ||||
|         query: variable.state.query, | ||||
|         datasource: variable.state.datasource, | ||||
|         sort: variable.state.sort, | ||||
|         refresh: variable.state.refresh, | ||||
|         regex: variable.state.regex, | ||||
|         allValue: variable.state.allValue, | ||||
|         includeAll: variable.state.includeAll, | ||||
|         multi: variable.state.isMulti, | ||||
|         skipUrlSync: Boolean(variable.state.skipUrlSync), | ||||
|         hide: variable.state.hide || VariableHide.dontHide, | ||||
|       }); | ||||
|     } else if (variable instanceof CustomVariable) { | ||||
|       variables.push({ | ||||
|         ...commonProperties, | ||||
|         current: { | ||||
|           // @ts-expect-error
 | ||||
|           text: variable.state.value, | ||||
|           // @ts-expect-error
 | ||||
|           value: variable.state.value, | ||||
|         }, | ||||
|         options: [], | ||||
|         query: variable.state.query, | ||||
|         multi: variable.state.isMulti, | ||||
|         allValue: variable.state.allValue, | ||||
|         includeAll: variable.state.includeAll, | ||||
|       }); | ||||
|     } else if (variable instanceof DataSourceVariable) { | ||||
|       variables.push({ | ||||
|         ...commonProperties, | ||||
|         current: { | ||||
|           // @ts-expect-error
 | ||||
|           value: variable.state.value, | ||||
|           // @ts-expect-error
 | ||||
|           text: variable.state.text, | ||||
|         }, | ||||
|         options: [], | ||||
|         regex: variable.state.regex, | ||||
|         query: variable.state.pluginId, | ||||
|         multi: variable.state.isMulti, | ||||
|         allValue: variable.state.allValue, | ||||
|         includeAll: variable.state.includeAll, | ||||
|       }); | ||||
|     } else if (variable instanceof ConstantVariable) { | ||||
|       variables.push({ | ||||
|         ...commonProperties, | ||||
|         current: { | ||||
|           // @ts-expect-error
 | ||||
|           value: variable.state.value, | ||||
|           // @ts-expect-error
 | ||||
|           text: variable.state.value, | ||||
|         }, | ||||
|         // @ts-expect-error
 | ||||
|         query: variable.state.value, | ||||
|         hide: VariableHide.hideVariable, | ||||
|       }); | ||||
|     } else { | ||||
|       throw new Error('Unsupported variable type'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return variables; | ||||
| } | ||||
|  | @ -92,17 +92,10 @@ export function createSceneObjectsForPanels(oldPanels: PanelModel[]): SceneGridI | |||
|         } | ||||
|       } | ||||
|     } else if (panel.libraryPanel?.uid && !('model' in panel.libraryPanel)) { | ||||
|       const gridItem = new SceneGridItem({ | ||||
|         body: new LibraryVizPanel({ | ||||
|           title: panel.title, | ||||
|           uid: panel.libraryPanel.uid, | ||||
|         }), | ||||
|         y: panel.gridPos.y, | ||||
|         x: panel.gridPos.x, | ||||
|         width: panel.gridPos.w, | ||||
|         height: panel.gridPos.h, | ||||
|       }); | ||||
|       panels.push(gridItem); | ||||
|       const gridItem = buildGridItemForLibPanel(panel); | ||||
|       if (gridItem) { | ||||
|         panels.push(gridItem); | ||||
|       } | ||||
|     } else { | ||||
|       const panelObject = buildGridItemForPanel(panel); | ||||
| 
 | ||||
|  | @ -296,6 +289,24 @@ export function createSceneVariableFromVariableModel(variable: VariableModel): S | |||
|   } | ||||
| } | ||||
| 
 | ||||
| export function buildGridItemForLibPanel(panel: PanelModel) { | ||||
|   if (!panel.libraryPanel) { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   return new SceneGridItem({ | ||||
|     body: new LibraryVizPanel({ | ||||
|       title: panel.title, | ||||
|       uid: panel.libraryPanel.uid, | ||||
|       name: panel.libraryPanel.name, | ||||
|       key: getVizPanelKeyForPanelId(panel.id), | ||||
|     }), | ||||
|     y: panel.gridPos.y, | ||||
|     x: panel.gridPos.x, | ||||
|     width: panel.gridPos.w, | ||||
|     height: panel.gridPos.h, | ||||
|   }); | ||||
| } | ||||
| export function buildGridItemForPanel(panel: PanelModel): SceneGridItemLike { | ||||
|   const vizPanelState: VizPanelState = { | ||||
|     key: getVizPanelKeyForPanelId(panel.id), | ||||
|  |  | |||
|  | @ -14,7 +14,11 @@ import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; | |||
| 
 | ||||
| import dashboard_to_load1 from './testfiles/dashboard_to_load1.json'; | ||||
| import repeatingRowsAndPanelsDashboardJson from './testfiles/repeating_rows_and_panels.json'; | ||||
| import { buildGridItemForPanel, transformSaveModelToScene } from './transformSaveModelToScene'; | ||||
| import { | ||||
|   buildGridItemForLibPanel, | ||||
|   buildGridItemForPanel, | ||||
|   transformSaveModelToScene, | ||||
| } from './transformSaveModelToScene'; | ||||
| import { gridItemToPanel, transformSceneToSaveModel } from './transformSceneToSaveModel'; | ||||
| 
 | ||||
| describe('transformSceneToSaveModel', () => { | ||||
|  | @ -103,6 +107,47 @@ describe('transformSceneToSaveModel', () => { | |||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('Library panels', () => { | ||||
|     it('given a library panel', () => { | ||||
|       const panel = buildGridItemFromPanelSchema({ | ||||
|         id: 4, | ||||
|         gridPos: { | ||||
|           h: 8, | ||||
|           w: 12, | ||||
|           x: 0, | ||||
|           y: 0, | ||||
|         }, | ||||
|         libraryPanel: { | ||||
|           name: 'Some lib panel panel', | ||||
|           uid: 'lib-panel-uid', | ||||
|         }, | ||||
|         title: 'A panel', | ||||
|         transformations: [], | ||||
|         fieldConfig: { | ||||
|           defaults: {}, | ||||
|           overrides: [], | ||||
|         }, | ||||
|       }); | ||||
| 
 | ||||
|       const result = gridItemToPanel(panel); | ||||
| 
 | ||||
|       expect(result.id).toBe(4); | ||||
|       expect(result.libraryPanel).toEqual({ | ||||
|         name: 'Some lib panel panel', | ||||
|         uid: 'lib-panel-uid', | ||||
|       }); | ||||
|       expect(result.gridPos).toEqual({ | ||||
|         h: 8, | ||||
|         w: 12, | ||||
|         x: 0, | ||||
|         y: 0, | ||||
|       }); | ||||
|       expect(result.title).toBe('A panel'); | ||||
|       expect(result.transformations).toBeUndefined(); | ||||
|       expect(result.fieldConfig).toBeUndefined(); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('Annotations', () => { | ||||
|     it('should transform annotations to save model', () => { | ||||
|       const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} }); | ||||
|  | @ -318,5 +363,8 @@ describe('transformSceneToSaveModel', () => { | |||
| }); | ||||
| 
 | ||||
| export function buildGridItemFromPanelSchema(panel: Partial<Panel>): SceneGridItemLike { | ||||
|   if (panel.libraryPanel) { | ||||
|     return buildGridItemForLibPanel(new PanelModel(panel))!; | ||||
|   } | ||||
|   return buildGridItemForPanel(new PanelModel(panel)); | ||||
| } | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import { | |||
|   SceneDataLayerProvider, | ||||
|   SceneQueryRunner, | ||||
|   SceneDataTransformer, | ||||
|   SceneVariableSet, | ||||
| } from '@grafana/scenes'; | ||||
| import { | ||||
|   AnnotationQuery, | ||||
|  | @ -18,24 +19,31 @@ import { | |||
|   FieldConfigSource, | ||||
|   Panel, | ||||
|   RowPanel, | ||||
|   VariableModel, | ||||
| } from '@grafana/schema'; | ||||
| import { sortedDeepCloneWithoutNulls } from 'app/core/utils/object'; | ||||
| import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; | ||||
| 
 | ||||
| import { DashboardScene } from '../scene/DashboardScene'; | ||||
| import { LibraryVizPanel } from '../scene/LibraryVizPanel'; | ||||
| import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem'; | ||||
| import { PanelTimeRange } from '../scene/PanelTimeRange'; | ||||
| import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; | ||||
| import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider'; | ||||
| import { getPanelIdForVizPanel } from '../utils/utils'; | ||||
| 
 | ||||
| import { sceneVariablesSetToVariables } from './sceneVariablesSetToVariables'; | ||||
| 
 | ||||
| export function transformSceneToSaveModel(scene: DashboardScene): Dashboard { | ||||
|   const state = scene.state; | ||||
|   const timeRange = state.$timeRange!.state; | ||||
|   const data = state.$data; | ||||
|   const variablesSet = state.$variables; | ||||
|   const body = state.body; | ||||
|   const panels: Panel[] = []; | ||||
| 
 | ||||
|   let variables: VariableModel[] = []; | ||||
| 
 | ||||
|   if (body instanceof SceneGridLayout) { | ||||
|     for (const child of body.state.children) { | ||||
|       if (child instanceof SceneGridItem) { | ||||
|  | @ -55,10 +63,13 @@ export function transformSceneToSaveModel(scene: DashboardScene): Dashboard { | |||
|   let annotations: AnnotationQuery[] = []; | ||||
|   if (data instanceof SceneDataLayers) { | ||||
|     const layers = data.state.layers; | ||||
| 
 | ||||
|     annotations = dataLayersToAnnotations(layers); | ||||
|   } | ||||
| 
 | ||||
|   if (variablesSet instanceof SceneVariableSet) { | ||||
|     variables = sceneVariablesSetToVariables(variablesSet); | ||||
|   } | ||||
| 
 | ||||
|   const dashboard: Dashboard = { | ||||
|     ...defaultDashboard, | ||||
|     title: state.title, | ||||
|  | @ -71,6 +82,9 @@ export function transformSceneToSaveModel(scene: DashboardScene): Dashboard { | |||
|     annotations: { | ||||
|       list: annotations, | ||||
|     }, | ||||
|     templating: { | ||||
|       list: variables, | ||||
|     }, | ||||
|     timezone: timeRange.timeZone, | ||||
|     fiscalYearStartMonth: timeRange.fiscalYearStartMonth, | ||||
|     weekStart: timeRange.weekStart, | ||||
|  | @ -87,6 +101,24 @@ export function gridItemToPanel(gridItem: SceneGridItemLike): Panel { | |||
|     h = 0; | ||||
| 
 | ||||
|   if (gridItem instanceof SceneGridItem) { | ||||
|     // Handle library panels, early exit
 | ||||
|     if (gridItem.state.body instanceof LibraryVizPanel) { | ||||
|       x = gridItem.state.x ?? 0; | ||||
|       y = gridItem.state.y ?? 0; | ||||
|       w = gridItem.state.width ?? 0; | ||||
|       h = gridItem.state.height ?? 0; | ||||
| 
 | ||||
|       return { | ||||
|         id: getPanelIdForVizPanel(gridItem.state.body), | ||||
|         title: gridItem.state.body.state.title, | ||||
|         gridPos: { x, y, w, h }, | ||||
|         libraryPanel: { | ||||
|           name: gridItem.state.body.state.name, | ||||
|           uid: gridItem.state.body.state.uid, | ||||
|         }, | ||||
|       } as Panel; | ||||
|     } | ||||
| 
 | ||||
|     if (!(gridItem.state.body instanceof VizPanel)) { | ||||
|       throw new Error('SceneGridItem body expected to be VizPanel'); | ||||
|     } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue