diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts index a5afac1ee04..c17cc0cfbdb 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.test.ts @@ -1392,8 +1392,8 @@ describe('DashboardSceneSerializer', () => { serializer.initializeDSReferencesMapping(v1SaveModel as unknown as DashboardV2Spec); expect(serializer.getDSReferencesMapping()).toEqual({ panels: new Map(), - variables: new Set(), - annotations: new Set(), + variables: new Map(), + annotations: new Map(), }); expect(serializer.getDSReferencesMapping().panels.size).toBe(0); }); @@ -1410,8 +1410,8 @@ describe('DashboardSceneSerializer', () => { serializer.initializeDSReferencesMapping(undefined); expect(serializer.getDSReferencesMapping()).toEqual({ panels: expect.any(Map), - variables: expect.any(Set), - annotations: expect.any(Set), + variables: expect.any(Map), + annotations: expect.any(Map), }); expect(serializer.getDSReferencesMapping().panels.size).toBe(0); }); diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts index 92e10154735..dda09ade2d4 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts @@ -99,9 +99,12 @@ interface DynamicDashboardsTrackingInformationLayoutParsing } export interface DSReferencesMapping { - panels: Map>; - variables: Set; - annotations: Set; + // panel id as keys, map as value. Map as value, if undefined, it means the datasource type was not defined + panels: Map>; + // variable name as keys, group as value, if undefined, it means the datasource type was not defined + variables: Map; + // annotation name as keys, group as value, if undefined, it means the datasource type was not defined + annotations: Map; } export class V1DashboardSerializer @@ -110,10 +113,10 @@ export class V1DashboardSerializer initialSaveModel?: Dashboard; metadata?: DashboardMeta; protected elementPanelMap = new Map(); - protected defaultDsReferencesMap = { - panels: new Map>(), // refIds as keys - variables: new Set(), // variable names as keys - annotations: new Set(), // annotation names as keys + protected defaultDsReferencesMap: DSReferencesMapping = { + panels: new Map(), + variables: new Map(), + annotations: new Map(), }; initializeElementMapping(saveModel: Dashboard | undefined) { @@ -274,10 +277,10 @@ export class V2DashboardSerializer metadata?: DashboardWithAccessInfo['metadata']; protected elementPanelMap = new Map(); // map of elementId that will contain all the queries, variables and annotations that dont have a ds defined - protected defaultDsReferencesMap = { - panels: new Map>(), // refIds as keys - variables: new Set(), // variable names as keys - annotations: new Set(), // annotation names as keys + protected defaultDsReferencesMap: DSReferencesMapping = { + panels: new Map(), + variables: new Map(), + annotations: new Map(), }; getElementPanelMapping() { @@ -308,13 +311,9 @@ export class V2DashboardSerializer return; } // initialize the object - this.defaultDsReferencesMap = { - panels: new Map>(), - variables: new Set(), - annotations: new Set(), - }; + this.defaultDsReferencesMap = { panels: new Map(), variables: new Map(), annotations: new Map() }; - // get all the element keys + // initialize autossigned panel queries ds references map const elementKeys = Object.keys(saveModel?.elements || {}); elementKeys.forEach((key) => { const elementPanel = saveModel?.elements[key]; @@ -324,14 +323,13 @@ export class V2DashboardSerializer for (const query of panelQueries) { if (!query.spec.query.datasource?.name) { + // Datasources without UID. Here we're saving elements with only type! const elementId = this.getElementIdForPanel(elementPanel.spec.id); - if (!this.defaultDsReferencesMap.panels.has(elementId)) { - this.defaultDsReferencesMap.panels.set(elementId, new Set()); - } + const panelDsqueries = this.defaultDsReferencesMap.panels.get(elementId) || new Map(); + const datasourceType = query.spec.query.group || undefined; - const panelDsqueries = this.defaultDsReferencesMap.panels.get(elementId)!; - - panelDsqueries.add(query.spec.refId); + panelDsqueries.set(query.spec.refId, datasourceType); + this.defaultDsReferencesMap.panels.set(elementId, panelDsqueries); } } } @@ -342,7 +340,8 @@ export class V2DashboardSerializer for (const variable of saveModel.variables) { // for query variables that dont have a ds defined add them to the list if (variable.kind === 'QueryVariable' && !variable.spec.query.datasource?.name) { - this.defaultDsReferencesMap.variables.add(variable.spec.name); + const datasourceType = variable.spec.query.group || undefined; + this.defaultDsReferencesMap.variables.set(variable.spec.name, datasourceType); } } } @@ -351,7 +350,8 @@ export class V2DashboardSerializer if (saveModel?.annotations) { for (const annotation of saveModel.annotations) { if (!annotation.spec.query?.datasource?.name) { - this.defaultDsReferencesMap.annotations.add(annotation.spec.name); + const datasourceType = annotation.spec.query.group || undefined; + this.defaultDsReferencesMap.annotations.set(annotation.spec.name, datasourceType); } } } diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index 458bfe86115..f136ddc1998 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -470,6 +470,11 @@ describe('transformSceneToSaveModelSchemaV2', () => { datasource: { uid: 'prometheus', type: 'prometheus' }, }; + const queryWithOnlyDSType: SceneDataQuery = { + refId: 'C', + datasource: { type: 'prometheus' }, + }; + // Mock query runner with runtime-resolved datasource const queryRunner = new SceneQueryRunner({ queries: [queryWithoutDS, queryWithDS], @@ -477,7 +482,7 @@ describe('transformSceneToSaveModelSchemaV2', () => { }); // Get a reference to the DS references mapping - const dsReferencesMap = new Set(['A']); + const dsReferencesMap = new Map([['A', undefined]]); // Test the query without DS originally - should return undefined const resultA = getPersistedDSFor(queryWithoutDS, dsReferencesMap, 'query', queryRunner); @@ -487,13 +492,17 @@ describe('transformSceneToSaveModelSchemaV2', () => { const resultB = getPersistedDSFor(queryWithDS, dsReferencesMap, 'query', queryRunner); expect(resultB).toEqual({ uid: 'prometheus', type: 'prometheus' }); + // Test the query with only type defined - should return the type + const resultC = getPersistedDSFor(queryWithOnlyDSType, dsReferencesMap, 'query', queryRunner); + expect(resultC).toEqual({ type: 'prometheus' }); + // Test a query with no DS originally but not in the mapping - should get the runner's datasource const queryNotInMapping: SceneDataQuery = { - refId: 'C', + refId: 'D', // No datasource, but not in mapping }; - const resultC = getPersistedDSFor(queryNotInMapping, dsReferencesMap, 'query', queryRunner); - expect(resultC).toEqual({ uid: 'default-ds', type: 'default' }); + const resultD = getPersistedDSFor(queryNotInMapping, dsReferencesMap, 'query', queryRunner); + expect(resultD).toEqual({ uid: 'default-ds', type: 'default' }); }); }); @@ -511,8 +520,14 @@ describe('transformSceneToSaveModelSchemaV2', () => { datasource: { uid: 'prometheus', type: 'prometheus' }, }); + // Variable with only type defined + const variableWithOnlyDSType = new QueryVariable({ + name: 'C', + datasource: { type: 'prometheus' }, + }); + // Get a reference to the DS references mapping - const dsReferencesMap = new Set(['A']); + const dsReferencesMap = new Map([['A', undefined]]); // Test the variable without DS originally - should return undefined const resultA = getPersistedDSFor(variableWithoutDS, dsReferencesMap, 'variable'); @@ -522,13 +537,17 @@ describe('transformSceneToSaveModelSchemaV2', () => { const resultB = getPersistedDSFor(variableWithDS, dsReferencesMap, 'variable'); expect(resultB).toEqual({ uid: 'prometheus', type: 'prometheus' }); - // Test a variable with no DS originally but not in the mapping - should get empty object + // Test the variable with only type defined - should return the type + const resultC = getPersistedDSFor(variableWithOnlyDSType, dsReferencesMap, 'variable'); + expect(resultC).toEqual({ type: 'prometheus' }); + + // Test a variable with no DS originally but not in the mapping - should return undefined const variableNotInMapping = new QueryVariable({ - name: 'C', + name: 'D', // No datasource, but not in mapping }); - const resultC = getPersistedDSFor(variableNotInMapping, dsReferencesMap, 'variable'); - expect(resultC).toEqual({}); + const resultD = getPersistedDSFor(variableNotInMapping, dsReferencesMap, 'variable'); + expect(resultD).toBeUndefined(); }); }); @@ -647,6 +666,11 @@ describe('getElementDatasource', () => { refId: 'A', }; + const queryWithOnlyType: SceneDataQuery = { + refId: 'C', + datasource: { type: 'prometheus' }, + }; + // Mock query runner const queryRunner = new SceneQueryRunner({ queries: [queryWithoutDS, queryWithDS], @@ -655,9 +679,9 @@ describe('getElementDatasource', () => { // Mock dsReferencesMapping const dsReferencesMapping = { - panels: new Map(new Set([['panel-1', new Set(['A'])]])), - variables: new Set(), - annotations: new Set(), + panels: new Map>([['panel-1', new Map([['A', '']])]]), + variables: new Map(), + annotations: new Map(), }; // Call the function with the panel and query with DS @@ -667,6 +691,16 @@ describe('getElementDatasource', () => { // Call the function with the panel and query without DS const resultWithoutDS = getElementDatasource(vizPanel, queryWithoutDS, 'panel', queryRunner, dsReferencesMapping); expect(resultWithoutDS).toBeUndefined(); + + // Call the function with the panel and query with only type + const resultWithOnlyType = getElementDatasource( + vizPanel, + queryWithOnlyType, + 'panel', + queryRunner, + dsReferencesMapping + ); + expect(resultWithOnlyType).toEqual({ type: 'prometheus' }); }); it('should handle variable datasources correctly', () => { @@ -692,9 +726,9 @@ describe('getElementDatasource', () => { // Mock dsReferencesMapping const dsReferencesMapping = { - panels: new Map(new Set([['panel-1', new Set(['A'])]])), - variables: new Set(['A']), - annotations: new Set(), + panels: new Map>([['panel-1', new Map([['A', '']])]]), + variables: new Map([['A', '']]), + annotations: new Map(), }; // Call the function with variables @@ -781,11 +815,25 @@ describe('getElementDatasource', () => { iconColor: 'blue', }; + // Create an annotation query with only type defined + const annotationLayerWithOnlyType = new dataLayers.AnnotationsDataLayer({ + name: 'Annotation with only datasource type', + isEnabled: true, + isHidden: false, + query: { + name: 'Test Annotation', + enable: true, + hide: false, + iconColor: 'blue', + datasource: { type: 'prometheus' }, + }, + }); + // Mock dsReferencesMapping const dsReferencesMapping = { - panels: new Map([['panel-1', new Set(['A'])]]), - variables: new Set(), - annotations: new Set(['No DS Annotation']), + panels: new Map>([['panel-1', new Map([['A', '']])]]), + variables: new Map(), + annotations: new Map(), }; // Test with annotation that has datasource defined @@ -807,6 +855,16 @@ describe('getElementDatasource', () => { dsReferencesMapping ); expect(resultWithoutDS).toBeUndefined(); + + // Test with annotation that has only type defined + const resultWithOnlyType = getElementDatasource( + annotationLayer, + annotationLayerWithOnlyType.state.query, + 'annotation', + undefined, + dsReferencesMapping + ); + expect(resultWithOnlyType).toEqual({ type: 'prometheus' }); }); it('should handle invalid input combinations', () => { @@ -878,9 +936,9 @@ describe('getVizPanelQueries', () => { // Mock dsReferencesMapping const dsReferencesMapping = { - panels: new Map(new Set([['panel-1', new Set(['A'])]])), - variables: new Set(), - annotations: new Set(), + panels: new Map>([['panel-1', new Map([['A', '']])]]), + variables: new Map(), + annotations: new Map(), }; const result = getVizPanelQueries(vizPanel, dsReferencesMapping); diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index 16604f87c6c..e40dcd41d5f 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -808,13 +808,13 @@ export function getAutoAssignedDSRef( element: VizPanel | SceneVariables | dataLayers.AnnotationsDataLayer, type: 'panels' | 'variables' | 'annotations', elementMapReferences?: DSReferencesMapping -): Set { +): Map { if (!elementMapReferences) { - return new Set(); + return new Map(); } if (type === 'panels' && isVizPanel(element)) { const elementKey = dashboardSceneGraph.getElementIdentifierForVizPanel(element); - return elementMapReferences.panels.get(elementKey) || new Set(); + return elementMapReferences.panels.get(elementKey) || new Map(); } if (type === 'variables') { @@ -830,20 +830,26 @@ export function getAutoAssignedDSRef( } /** - * Determines if a data source reference should be persisted for a query or variable + * Returns the datasource value that should be persisted for a panel query, variable or annotation + * - Undefined if the datasource was not defined in the initial save model + * - { type: string } if the datasource was autossigned defined by the initial group value + * - { uid: string, type: string } if the datasource was defined in the initial save model */ export function getPersistedDSFor( element: T, - autoAssignedDsRef: Set, + autoAssignedDsRef: Map, + type: 'query' | 'variable' | 'annotation', context?: SceneQueryRunner ): DataSourceRef | undefined { // Get the element identifier - refId for queries, name for variables const elementId = getElementIdentifier(element, type); - // If the element is in the auto-assigned set, it didn't have a datasource specified + // If the ds was autossigned, return the datasource initial ds value. if (autoAssignedDsRef?.has(elementId)) { - return undefined; + const dsType = autoAssignedDsRef.get(elementId); + // If the ds type was not undefined means the datasource was autossigned, so return the datasource with only the type + return dsType ? { type: dsType } : undefined; } // Return appropriate datasource reference based on element type @@ -858,11 +864,11 @@ export function getPersistedDSFor 0 ? result : undefined; }