Dashboard Schema V2: Preserve ds refs that only have type (#114734)
Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com> Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
co-authored by
Ivan Ortega
Dominik Prokop
parent
612af5ef55
commit
f3ed3a999d
@@ -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);
|
||||
});
|
||||
|
||||
@@ -99,9 +99,12 @@ interface DynamicDashboardsTrackingInformationLayoutParsing
|
||||
}
|
||||
|
||||
export interface DSReferencesMapping {
|
||||
panels: Map<string, Set<string>>;
|
||||
variables: Set<string>;
|
||||
annotations: Set<string>;
|
||||
// panel id as keys, map as value. Map<refId, group> as value, if undefined, it means the datasource type was not defined
|
||||
panels: Map<string, Map<string, string | undefined>>;
|
||||
// variable name as keys, group as value, if undefined, it means the datasource type was not defined
|
||||
variables: Map<string, string | undefined>;
|
||||
// annotation name as keys, group as value, if undefined, it means the datasource type was not defined
|
||||
annotations: Map<string, string | undefined>;
|
||||
}
|
||||
|
||||
export class V1DashboardSerializer
|
||||
@@ -110,10 +113,10 @@ export class V1DashboardSerializer
|
||||
initialSaveModel?: Dashboard;
|
||||
metadata?: DashboardMeta;
|
||||
protected elementPanelMap = new Map<string, number>();
|
||||
protected defaultDsReferencesMap = {
|
||||
panels: new Map<string, Set<string>>(), // refIds as keys
|
||||
variables: new Set<string>(), // variable names as keys
|
||||
annotations: new Set<string>(), // 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<DashboardV2Spec>['metadata'];
|
||||
protected elementPanelMap = new Map<string, number>();
|
||||
// map of elementId that will contain all the queries, variables and annotations that dont have a ds defined
|
||||
protected defaultDsReferencesMap = {
|
||||
panels: new Map<string, Set<string>>(), // refIds as keys
|
||||
variables: new Set<string>(), // variable names as keys
|
||||
annotations: new Set<string>(), // 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<string, Set<string>>(),
|
||||
variables: new Set<string>(),
|
||||
annotations: new Set<string>(),
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+79
-21
@@ -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<string, string | undefined>([['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<string, string | undefined>([['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<string>(['A'])]])),
|
||||
variables: new Set<string>(),
|
||||
annotations: new Set<string>(),
|
||||
panels: new Map<string, Map<string, string>>([['panel-1', new Map<string, string>([['A', '']])]]),
|
||||
variables: new Map<string, string>(),
|
||||
annotations: new Map<string, string>(),
|
||||
};
|
||||
|
||||
// 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<string>(['A'])]])),
|
||||
variables: new Set<string>(['A']),
|
||||
annotations: new Set<string>(),
|
||||
panels: new Map<string, Map<string, string>>([['panel-1', new Map<string, string>([['A', '']])]]),
|
||||
variables: new Map<string, string>([['A', '']]),
|
||||
annotations: new Map<string, string>(),
|
||||
};
|
||||
|
||||
// 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<string>(),
|
||||
annotations: new Set<string>(['No DS Annotation']),
|
||||
panels: new Map<string, Map<string, string>>([['panel-1', new Map<string, string>([['A', '']])]]),
|
||||
variables: new Map<string, string>(),
|
||||
annotations: new Map<string, string>(),
|
||||
};
|
||||
|
||||
// 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<string>(['A'])]])),
|
||||
variables: new Set<string>(),
|
||||
annotations: new Set<string>(),
|
||||
panels: new Map<string, Map<string, string>>([['panel-1', new Map<string, string>([['A', '']])]]),
|
||||
variables: new Map<string, string>(),
|
||||
annotations: new Map<string, string>(),
|
||||
};
|
||||
|
||||
const result = getVizPanelQueries(vizPanel, dsReferencesMapping);
|
||||
|
||||
+22
-13
@@ -808,13 +808,13 @@ export function getAutoAssignedDSRef(
|
||||
element: VizPanel | SceneVariables | dataLayers.AnnotationsDataLayer,
|
||||
type: 'panels' | 'variables' | 'annotations',
|
||||
elementMapReferences?: DSReferencesMapping
|
||||
): Set<string> {
|
||||
): Map<string, string | undefined> {
|
||||
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<T extends SceneDataQuery | QueryVariable | AnnotationQuery>(
|
||||
element: T,
|
||||
autoAssignedDsRef: Set<string>,
|
||||
autoAssignedDsRef: Map<string, string | undefined>,
|
||||
|
||||
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<T extends SceneDataQuery | QueryVariable | Ann
|
||||
}
|
||||
|
||||
if (type === 'variable' && 'state' in element && 'datasource' in element.state) {
|
||||
return element.state.datasource || {};
|
||||
return element.state.datasource || undefined;
|
||||
}
|
||||
|
||||
if (type === 'annotation' && 'datasource' in element) {
|
||||
return element.datasource || {};
|
||||
return element.datasource || undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -920,11 +926,14 @@ function isQueryVariable(query: SceneDataQuery | QueryVariable | AnnotationQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the persisted datasource for a query or variable
|
||||
* When a query or variable is created it could not have a datasource set
|
||||
* Get the persisted datasource for a panel query, annotation or variable
|
||||
* When a panel query, annotation or variable is created it could not have a datasource set
|
||||
* we want to respect that and not overwrite it with the auto assigned datasources
|
||||
* resolved in runtime
|
||||
*
|
||||
* - 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 getElementDatasource(
|
||||
element: VizPanel | SceneVariables | dataLayers.AnnotationsDataLayer,
|
||||
@@ -961,7 +970,7 @@ export function getElementDatasource(
|
||||
const autoAssignedRefs = getAutoAssignedDSRef(element, 'annotations', dsReferencesMapping);
|
||||
result = getPersistedDSFor(queryElement, autoAssignedRefs, 'annotation');
|
||||
}
|
||||
// Important: Only return the datasource if it's not in auto-assigned refs
|
||||
// and if the result would not be an empty object
|
||||
|
||||
// Avoid returning an empty object
|
||||
return Object.keys(result || {}).length > 0 ? result : undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user