V2: Ensure refIds for queries (#115404)

Ensure refIds for queries
This commit is contained in:
Ivan Ortega Alba
2025-12-16 13:34:08 +01:00
committed by GitHub
parent 18837682cc
commit 195bf681d1
2 changed files with 94 additions and 2 deletions
@@ -1,6 +1,6 @@
import { defaultDataQueryKind, PanelQueryKind } from '@grafana/schema/dist/esm/schema/dashboard/v2';
import { getRuntimePanelDataSource } from './utils';
import { ensureUniqueRefIds, getRuntimePanelDataSource } from './utils';
describe('getRuntimePanelDataSource', () => {
it('should return uid and type when explicit datasource UID is provided', () => {
@@ -140,3 +140,76 @@ describe('getRuntimePanelDataSource', () => {
expect(result).toBeUndefined();
});
});
describe('ensureUniqueRefIds', () => {
const createQuery = (refId: string): PanelQueryKind => ({
kind: 'PanelQuery',
spec: {
refId,
hidden: false,
query: {
kind: 'DataQuery',
version: defaultDataQueryKind().version,
group: 'prometheus',
spec: {},
},
},
});
it('should assign unique refIds to queries without refIds', () => {
const queries: PanelQueryKind[] = [createQuery(''), createQuery(''), createQuery('')];
const result = ensureUniqueRefIds(queries);
expect(result[0].spec.refId).toBe('A');
expect(result[1].spec.refId).toBe('B');
expect(result[2].spec.refId).toBe('C');
});
it('should preserve existing refIds and fill gaps', () => {
const queries: PanelQueryKind[] = [createQuery('A'), createQuery(''), createQuery('D'), createQuery('')];
const result = ensureUniqueRefIds(queries);
expect(result[0].spec.refId).toBe('A');
expect(result[1].spec.refId).toBe('B');
expect(result[2].spec.refId).toBe('D');
expect(result[3].spec.refId).toBe('C');
});
it('should handle all queries having existing refIds', () => {
const queries: PanelQueryKind[] = [createQuery('A'), createQuery('B'), createQuery('C')];
const result = ensureUniqueRefIds(queries);
expect(result[0].spec.refId).toBe('A');
expect(result[1].spec.refId).toBe('B');
expect(result[2].spec.refId).toBe('C');
});
it('should only modify queries without refIds', () => {
const queries: PanelQueryKind[] = [createQuery('A'), createQuery(''), createQuery('C')];
const result = ensureUniqueRefIds(queries);
// Existing refIds should be preserved
expect(result[0].spec.refId).toBe('A');
expect(result[2].spec.refId).toBe('C');
// Missing refId should be assigned
expect(result[1].spec.refId).toBe('B');
});
it('should handle empty array', () => {
const result = ensureUniqueRefIds([]);
expect(result).toEqual([]);
});
it('should handle single query without refId', () => {
const queries: PanelQueryKind[] = [createQuery('')];
const result = ensureUniqueRefIds(queries);
expect(result[0].spec.refId).toBe('A');
});
});
@@ -1,3 +1,4 @@
import { getNextRefId } from '@grafana/data';
import { config } from '@grafana/runtime';
import {
SceneDataProvider,
@@ -164,12 +165,15 @@ export function createPanelDataProvider(panelKind: PanelKind): SceneDataProvider
return undefined;
}
// Ensure all queries have unique refIds before converting to scene queries
const queriesWithUniqueRefIds = ensureUniqueRefIds(targets);
let dataProvider: SceneDataProvider | undefined = undefined;
const datasource = getPanelDataSource(panelKind);
dataProvider = new SceneQueryRunner({
datasource,
queries: targets.map(panelQueryKindToSceneQuery),
queries: queriesWithUniqueRefIds.map(panelQueryKindToSceneQuery),
maxDataPoints: panel.data.spec.queryOptions.maxDataPoints ?? undefined,
maxDataPointsFromWidth: true,
cacheTimeout: panel.data.spec.queryOptions.cacheTimeout,
@@ -341,6 +345,21 @@ export function getDataSourceForQuery(querySpecDS: DataSourceRef | undefined | n
};
}
export function ensureUniqueRefIds(queries: PanelQueryKind[]): PanelQueryKind[] {
// Adapter to make PanelQueryKind[] work with getNextRefId (which expects { refId }[])
const refIdAdapter = queries.map((q) => ({ refId: q.spec.refId }));
for (let i = 0; i < queries.length; i++) {
if (!queries[i].spec.refId) {
const newRefId = getNextRefId(refIdAdapter);
queries[i] = { ...queries[i], spec: { ...queries[i].spec, refId: newRefId } };
refIdAdapter[i] = { refId: newRefId };
}
}
return queries;
}
function panelQueryKindToSceneQuery(query: PanelQueryKind): SceneDataQuery {
// Add datasource to match Go backend V2→V1 conversion:
// - If explicit UID (datasource.name) exists → add { uid, type }