Dashboard Schema V2: Fix panel query tab (#115276)
fix panel query tab for v2 schema
This commit is contained in:
+101
-1
@@ -25,10 +25,17 @@ import { DashboardDataDTO } from 'app/types/dashboard';
|
||||
|
||||
import { PanelInspectDrawer } from '../../inspect/PanelInspectDrawer';
|
||||
import { PanelTimeRange, PanelTimeRangeState } from '../../scene/panel-timerange/PanelTimeRange';
|
||||
import { DashboardLayoutManager } from '../../scene/types/DashboardLayoutManager';
|
||||
import { transformSaveModelSchemaV2ToScene } from '../../serialization/transformSaveModelSchemaV2ToScene';
|
||||
import { transformSaveModelToScene } from '../../serialization/transformSaveModelToScene';
|
||||
import { findVizPanelByKey } from '../../utils/utils';
|
||||
import { buildPanelEditScene } from '../PanelEditor';
|
||||
import { testDashboard, panelWithTransformations, panelWithQueriesOnly } from '../testfiles/testDashboard';
|
||||
import {
|
||||
testDashboard,
|
||||
panelWithTransformations,
|
||||
panelWithQueriesOnly,
|
||||
testDashboardV2,
|
||||
} from '../testfiles/testDashboard';
|
||||
|
||||
import { PanelDataQueriesTab, PanelDataQueriesTabRendered } from './PanelDataQueriesTab';
|
||||
|
||||
@@ -824,6 +831,78 @@ describe('PanelDataQueriesTab', () => {
|
||||
expect(queriesTab.state.dsSettings?.uid).toBe('gdev-testdata');
|
||||
});
|
||||
});
|
||||
|
||||
describe('V2 schema behavior - panel datasource undefined but queries have datasource', () => {
|
||||
it('should load datasource from first query for V2 panel with prometheus datasource', async () => {
|
||||
// panel-1 has a query with prometheus datasource
|
||||
const { queriesTab } = await setupV2Scene('panel-1');
|
||||
|
||||
// V2 panels have undefined panel-level datasource for non-mixed panels
|
||||
expect(queriesTab.queryRunner.state.datasource).toBeUndefined();
|
||||
|
||||
// But the query has its own datasource
|
||||
expect(queriesTab.queryRunner.state.queries[0].datasource).toEqual({
|
||||
type: 'grafana-prometheus-datasource',
|
||||
uid: 'gdev-prometheus',
|
||||
});
|
||||
|
||||
// Should load the datasource from the first query
|
||||
expect(queriesTab.state.datasource?.uid).toBe('gdev-prometheus');
|
||||
expect(queriesTab.state.dsSettings?.uid).toBe('gdev-prometheus');
|
||||
});
|
||||
|
||||
it('should load datasource from first query for V2 panel with testdata datasource', async () => {
|
||||
// panel-2 has a query with testdata datasource
|
||||
const { queriesTab } = await setupV2Scene('panel-2');
|
||||
|
||||
// V2 panels have undefined panel-level datasource for non-mixed panels
|
||||
expect(queriesTab.queryRunner.state.datasource).toBeUndefined();
|
||||
|
||||
// But the query has its own datasource
|
||||
expect(queriesTab.queryRunner.state.queries[0].datasource).toEqual({
|
||||
type: 'grafana-testdata-datasource',
|
||||
uid: 'gdev-testdata',
|
||||
});
|
||||
|
||||
// Should load the datasource from the first query
|
||||
expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata');
|
||||
expect(queriesTab.state.dsSettings?.uid).toBe('gdev-testdata');
|
||||
});
|
||||
|
||||
it('should fall back to last used datasource when V2 query has no explicit datasource', async () => {
|
||||
store.exists.mockReturnValue(true);
|
||||
store.getObject.mockImplementation((key: string, def: unknown) => {
|
||||
if (key === PANEL_EDIT_LAST_USED_DATASOURCE) {
|
||||
return {
|
||||
dashboardUid: 'v2-dashboard-uid',
|
||||
datasourceUid: 'gdev-testdata',
|
||||
};
|
||||
}
|
||||
return def;
|
||||
});
|
||||
|
||||
// panel-3 has a query with NO explicit datasource (datasource.name is undefined)
|
||||
const { queriesTab } = await setupV2Scene('panel-3');
|
||||
|
||||
// V2 panel with no explicit datasource on query should fall back to last used
|
||||
expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata');
|
||||
expect(queriesTab.state.dsSettings?.uid).toBe('gdev-testdata');
|
||||
});
|
||||
|
||||
it('should use panel-level datasource when available (V1 behavior preserved)', async () => {
|
||||
const { queriesTab } = await setupScene('panel-1');
|
||||
|
||||
// V1 panels have panel-level datasource set
|
||||
expect(queriesTab.queryRunner.state.datasource).toEqual({
|
||||
uid: 'gdev-testdata',
|
||||
type: 'grafana-testdata-datasource',
|
||||
});
|
||||
|
||||
// Should use the panel-level datasource
|
||||
expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata');
|
||||
expect(queriesTab.state.dsSettings?.uid).toBe('gdev-testdata');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -844,3 +923,24 @@ async function setupScene(panelId: string) {
|
||||
|
||||
return { panel, scene: dashboard, queriesTab };
|
||||
}
|
||||
|
||||
// Setup V2 scene - uses transformSaveModelSchemaV2ToScene
|
||||
async function setupV2Scene(panelKey: string) {
|
||||
const dashboard = transformSaveModelSchemaV2ToScene(testDashboardV2);
|
||||
|
||||
const vizPanels = (dashboard.state.body as DashboardLayoutManager).getVizPanels();
|
||||
const panel = vizPanels.find((p) => p.state.key === panelKey)!;
|
||||
|
||||
const panelEditor = buildPanelEditScene(panel);
|
||||
dashboard.setState({ editPanel: panelEditor });
|
||||
|
||||
deactivators.push(dashboard.activate());
|
||||
deactivators.push(panelEditor.activate());
|
||||
|
||||
const queriesTab = panelEditor.state.dataPane!.state.tabs[0] as PanelDataQueriesTab;
|
||||
deactivators.push(queriesTab.activate());
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
return { panel, scene: dashboard, queriesTab };
|
||||
}
|
||||
|
||||
@@ -86,6 +86,17 @@ export class PanelDataQueriesTab extends SceneObjectBase<PanelDataQueriesTabStat
|
||||
let datasource: DataSourceApi | undefined;
|
||||
let dsSettings: DataSourceInstanceSettings | undefined;
|
||||
|
||||
// If no panel-level datasource (V2 schema non-mixed case), infer from first query
|
||||
// This also improves the V1 behavior because it doesn't make sense to rely on last used
|
||||
// if underlying queries have different datasources
|
||||
if (!datasourceToLoad) {
|
||||
const queries = this.queryRunner.state.queries;
|
||||
const firstQueryDs = queries[0]?.datasource;
|
||||
if (firstQueryDs) {
|
||||
datasourceToLoad = firstQueryDs;
|
||||
}
|
||||
}
|
||||
|
||||
if (!datasourceToLoad) {
|
||||
const dashboardScene = getDashboardSceneFor(this);
|
||||
const dashboardUid = dashboardScene.state.uid ?? '';
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Spec as DashboardV2Spec, defaultDataQueryKind } from '@grafana/schema/dist/esm/schema/dashboard/v2';
|
||||
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
||||
|
||||
export const panelWithQueriesOnly = {
|
||||
datasource: {
|
||||
type: 'grafana-testdata-datasource',
|
||||
@@ -751,3 +754,223 @@ export const testDashboard = {
|
||||
version: 6,
|
||||
weekStart: '',
|
||||
};
|
||||
|
||||
// V2 Dashboard fixture - panels have queries with datasources but NO panel-level datasource
|
||||
export const testDashboardV2: DashboardWithAccessInfo<DashboardV2Spec> = {
|
||||
kind: 'DashboardWithAccessInfo',
|
||||
metadata: {
|
||||
name: 'v2-dashboard-uid',
|
||||
namespace: 'default',
|
||||
labels: {},
|
||||
generation: 1,
|
||||
resourceVersion: '1',
|
||||
creationTimestamp: new Date().toISOString(),
|
||||
},
|
||||
spec: {
|
||||
title: 'V2 Test Dashboard',
|
||||
description: 'Test dashboard for V2 schema',
|
||||
tags: [],
|
||||
cursorSync: 'Off',
|
||||
liveNow: false,
|
||||
editable: true,
|
||||
preload: false,
|
||||
links: [],
|
||||
variables: [],
|
||||
annotations: [],
|
||||
timeSettings: {
|
||||
from: 'now-6h',
|
||||
to: 'now',
|
||||
autoRefresh: '',
|
||||
autoRefreshIntervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'],
|
||||
fiscalYearStartMonth: 0,
|
||||
hideTimepicker: false,
|
||||
timezone: '',
|
||||
weekStart: undefined,
|
||||
quickRanges: [],
|
||||
},
|
||||
elements: {
|
||||
'panel-1': {
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
id: 1,
|
||||
title: 'Panel with Prometheus datasource',
|
||||
description: '',
|
||||
links: [],
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries: [
|
||||
{
|
||||
kind: 'PanelQuery',
|
||||
spec: {
|
||||
refId: 'A',
|
||||
hidden: false,
|
||||
query: {
|
||||
kind: 'DataQuery',
|
||||
version: defaultDataQueryKind().version,
|
||||
group: 'grafana-prometheus-datasource',
|
||||
datasource: {
|
||||
name: 'gdev-prometheus',
|
||||
},
|
||||
spec: {
|
||||
expr: 'up',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
transformations: [],
|
||||
queryOptions: {},
|
||||
},
|
||||
},
|
||||
vizConfig: {
|
||||
kind: 'VizConfig',
|
||||
group: 'timeseries',
|
||||
version: '1.0.0',
|
||||
spec: {
|
||||
options: {},
|
||||
fieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'panel-2': {
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
id: 2,
|
||||
title: 'Panel with TestData datasource',
|
||||
description: '',
|
||||
links: [],
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries: [
|
||||
{
|
||||
kind: 'PanelQuery',
|
||||
spec: {
|
||||
refId: 'A',
|
||||
hidden: false,
|
||||
query: {
|
||||
kind: 'DataQuery',
|
||||
version: defaultDataQueryKind().version,
|
||||
group: 'grafana-testdata-datasource',
|
||||
datasource: {
|
||||
name: 'gdev-testdata',
|
||||
},
|
||||
spec: {
|
||||
scenarioId: 'random_walk',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
transformations: [],
|
||||
queryOptions: {},
|
||||
},
|
||||
},
|
||||
vizConfig: {
|
||||
kind: 'VizConfig',
|
||||
group: 'timeseries',
|
||||
version: '1.0.0',
|
||||
spec: {
|
||||
options: {},
|
||||
fieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'panel-3': {
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
id: 3,
|
||||
title: 'Panel with no datasource on query',
|
||||
description: '',
|
||||
links: [],
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries: [
|
||||
{
|
||||
kind: 'PanelQuery',
|
||||
spec: {
|
||||
refId: 'A',
|
||||
hidden: false,
|
||||
query: {
|
||||
kind: 'DataQuery',
|
||||
version: defaultDataQueryKind().version,
|
||||
group: 'grafana-testdata-datasource',
|
||||
// No datasource.name - simulates panel with no explicit datasource
|
||||
spec: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
transformations: [],
|
||||
queryOptions: {},
|
||||
},
|
||||
},
|
||||
vizConfig: {
|
||||
kind: 'VizConfig',
|
||||
group: 'timeseries',
|
||||
version: '1.0.0',
|
||||
spec: {
|
||||
options: {},
|
||||
fieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
kind: 'GridLayout',
|
||||
spec: {
|
||||
items: [
|
||||
{
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 12,
|
||||
height: 8,
|
||||
element: { kind: 'ElementReference', name: 'panel-1' },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
x: 12,
|
||||
y: 0,
|
||||
width: 12,
|
||||
height: 8,
|
||||
element: { kind: 'ElementReference', name: 'panel-2' },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
x: 0,
|
||||
y: 8,
|
||||
width: 12,
|
||||
height: 8,
|
||||
element: { kind: 'ElementReference', name: 'panel-3' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
access: {
|
||||
url: '/d/v2-dashboard-uid',
|
||||
slug: 'v2-test-dashboard',
|
||||
},
|
||||
apiVersion: 'v2',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user