Snapshots: Fix V2 Snapshot data coupling (#115278)
* Snapshots: Potential fix for rendering V2 snaps * removing comments * Added unit test
This commit is contained in:
+101
-1
@@ -1,4 +1,4 @@
|
||||
import { VariableRefresh } from '@grafana/data';
|
||||
import { VariableRefresh, PanelData, LoadingState, toDataFrame, FieldType, getDefaultTimeRange } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import {
|
||||
AdHocFiltersVariable,
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
VizPanel,
|
||||
SceneDataQuery,
|
||||
SceneQueryRunner,
|
||||
SceneDataTransformer,
|
||||
SceneDataNode,
|
||||
sceneUtils,
|
||||
dataLayers,
|
||||
} from '@grafana/scenes';
|
||||
@@ -33,6 +35,7 @@ import {
|
||||
TabsLayoutSpec,
|
||||
defaultDataQueryKind,
|
||||
} from '@grafana/schema/dist/esm/schema/dashboard/v2';
|
||||
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
||||
|
||||
import { DashboardEditPane } from '../edit-pane/DashboardEditPane';
|
||||
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||
@@ -954,6 +957,103 @@ describe('getVizPanelQueries', () => {
|
||||
expect(result[1].spec.query.group).toBe('prometheus');
|
||||
expect(result[1].spec.query.version).toBe('v0');
|
||||
});
|
||||
|
||||
describe('snapshot mode', () => {
|
||||
it('should return empty queries when isSnapshot is true but panel has no data provider', () => {
|
||||
const vizPanel = new VizPanel({
|
||||
key: 'panel-1',
|
||||
pluginId: 'timeseries',
|
||||
// No $data provider
|
||||
});
|
||||
|
||||
const result = getVizPanelQueries(vizPanel, undefined, true);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should create snapshot query from SceneQueryRunner data when isSnapshot is true', () => {
|
||||
const mockDataFrame = toDataFrame({
|
||||
name: 'test-series',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000, 3000] },
|
||||
{ name: 'value', type: FieldType.number, values: [1, 2, 3] },
|
||||
],
|
||||
});
|
||||
|
||||
const panelData: PanelData = {
|
||||
series: [mockDataFrame],
|
||||
state: LoadingState.Done,
|
||||
timeRange: getDefaultTimeRange(),
|
||||
};
|
||||
|
||||
const queryRunner = new SceneQueryRunner({
|
||||
queries: [],
|
||||
data: panelData,
|
||||
});
|
||||
|
||||
const vizPanel = new VizPanel({
|
||||
key: 'panel-1',
|
||||
pluginId: 'timeseries',
|
||||
$data: queryRunner,
|
||||
});
|
||||
|
||||
const result = getVizPanelQueries(vizPanel, undefined, true);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].kind).toBe('PanelQuery');
|
||||
expect(result[0].spec.refId).toBe('A');
|
||||
expect(result[0].spec.hidden).toBe(false);
|
||||
expect(result[0].spec.query.kind).toBe('DataQuery');
|
||||
expect(result[0].spec.query.version).toBe(defaultDataQueryKind().version);
|
||||
expect(result[0].spec.query.group).toBe('grafana');
|
||||
expect(result[0].spec.query.datasource).toEqual({ name: 'grafana' });
|
||||
expect(result[0].spec.query.spec.queryType).toBe(GrafanaQueryType.Snapshot);
|
||||
expect(result[0].spec.query.spec.snapshot).toBeDefined();
|
||||
expect(result[0].spec.query.spec.snapshot).toHaveLength(1);
|
||||
expect(result[0].spec.query.spec.snapshot[0].schema?.fields).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create snapshot query from SceneDataTransformer data when isSnapshot is true', () => {
|
||||
const mockDataFrame = toDataFrame({
|
||||
name: 'transformed-series',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [1000, 2000] },
|
||||
{ name: 'transformed', type: FieldType.number, values: [10, 20] },
|
||||
],
|
||||
});
|
||||
|
||||
const panelData: PanelData = {
|
||||
series: [mockDataFrame],
|
||||
state: LoadingState.Done,
|
||||
timeRange: getDefaultTimeRange(),
|
||||
};
|
||||
|
||||
const dataNode = new SceneDataNode({
|
||||
data: panelData,
|
||||
});
|
||||
|
||||
const dataTransformer = new SceneDataTransformer({
|
||||
$data: dataNode,
|
||||
transformations: [],
|
||||
});
|
||||
|
||||
const vizPanel = new VizPanel({
|
||||
key: 'panel-1',
|
||||
pluginId: 'timeseries',
|
||||
$data: dataTransformer,
|
||||
});
|
||||
|
||||
const result = getVizPanelQueries(vizPanel, undefined, true);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].kind).toBe('PanelQuery');
|
||||
expect(result[0].spec.query.kind).toBe('DataQuery');
|
||||
expect(result[0].spec.query.spec.queryType).toBe(GrafanaQueryType.Snapshot);
|
||||
expect(result[0].spec.query.spec.snapshot).toBeDefined();
|
||||
expect(result[0].spec.query.spec.snapshot).toHaveLength(1);
|
||||
// Verify it gets data from the nested $data (SceneDataNode) not the transformer
|
||||
expect(result[0].spec.query.spec.snapshot[0].schema?.fields).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getMinimalSceneState(body: DashboardLayoutManager): Partial<DashboardSceneState> {
|
||||
|
||||
+59
-13
@@ -13,8 +13,10 @@ import {
|
||||
SceneVariableSet,
|
||||
VizPanel,
|
||||
} from '@grafana/scenes';
|
||||
import { DataSourceRef, VariableRefresh } from '@grafana/schema';
|
||||
import { DataSourceRef } from '@grafana/schema';
|
||||
import { sortedDeepCloneWithoutNulls } from 'app/core/utils/object';
|
||||
import { getPanelDataFrames } from 'app/features/dashboard/components/HelpWizard/utils';
|
||||
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
||||
|
||||
import {
|
||||
Spec as DashboardV2Spec,
|
||||
@@ -127,7 +129,7 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps
|
||||
// EOF variables
|
||||
|
||||
// elements
|
||||
elements: getElements(scene, dsReferencesMapping),
|
||||
elements: getElements(scene, dsReferencesMapping, isSnapshot),
|
||||
// EOF elements
|
||||
|
||||
// annotations
|
||||
@@ -170,17 +172,18 @@ function getLiveNow(state: DashboardSceneState) {
|
||||
return Boolean(liveNow);
|
||||
}
|
||||
|
||||
function getElements(scene: DashboardScene, dsReferencesMapping?: DSReferencesMapping) {
|
||||
function getElements(scene: DashboardScene, dsReferencesMapping?: DSReferencesMapping, isSnapshot = false) {
|
||||
const panels = scene.state.body.getVizPanels() ?? [];
|
||||
const panelsArray = panels.map((vizPanel) => {
|
||||
return vizPanelToSchemaV2(vizPanel, dsReferencesMapping);
|
||||
return vizPanelToSchemaV2(vizPanel, dsReferencesMapping, isSnapshot);
|
||||
});
|
||||
return createElements(panelsArray, scene);
|
||||
}
|
||||
|
||||
export function vizPanelToSchemaV2(
|
||||
vizPanel: VizPanel,
|
||||
dsReferencesMapping?: DSReferencesMapping
|
||||
dsReferencesMapping?: DSReferencesMapping,
|
||||
isSnapshot = false
|
||||
): PanelKind | LibraryPanelKind {
|
||||
if (isLibraryPanel(vizPanel)) {
|
||||
const behavior = getLibraryPanelBehavior(vizPanel)!;
|
||||
@@ -216,7 +219,7 @@ export function vizPanelToSchemaV2(
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries: getVizPanelQueries(vizPanel, dsReferencesMapping),
|
||||
queries: getVizPanelQueries(vizPanel, dsReferencesMapping, isSnapshot),
|
||||
transformations: getVizPanelTransformations(vizPanel),
|
||||
queryOptions: getVizPanelQueryOptions(vizPanel),
|
||||
},
|
||||
@@ -290,9 +293,51 @@ function getPanelLinks(panel: VizPanel): DataLink[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getVizPanelQueries(vizPanel: VizPanel, dsReferencesMapping?: DSReferencesMapping): PanelQueryKind[] {
|
||||
export function getVizPanelQueries(
|
||||
vizPanel: VizPanel,
|
||||
dsReferencesMapping?: DSReferencesMapping,
|
||||
isSnapshot = false
|
||||
): PanelQueryKind[] {
|
||||
const queries: PanelQueryKind[] = [];
|
||||
const queryRunner = getQueryRunnerFor(vizPanel);
|
||||
|
||||
if (isSnapshot) {
|
||||
const dataProvider = vizPanel.state.$data;
|
||||
if (!dataProvider) {
|
||||
return queries;
|
||||
}
|
||||
|
||||
let snapshotData = getPanelDataFrames(dataProvider.state.data);
|
||||
if (dataProvider instanceof SceneDataTransformer) {
|
||||
snapshotData = getPanelDataFrames(dataProvider.state.$data!.state.data);
|
||||
}
|
||||
|
||||
const snapshotQuery: DataQueryKind = {
|
||||
kind: 'DataQuery',
|
||||
version: defaultDataQueryKind().version,
|
||||
group: 'grafana',
|
||||
datasource: {
|
||||
name: 'grafana',
|
||||
},
|
||||
spec: {
|
||||
queryType: GrafanaQueryType.Snapshot,
|
||||
snapshot: snapshotData,
|
||||
},
|
||||
};
|
||||
|
||||
queries.push({
|
||||
kind: 'PanelQuery',
|
||||
spec: {
|
||||
query: snapshotQuery,
|
||||
refId: 'A',
|
||||
hidden: false,
|
||||
},
|
||||
});
|
||||
|
||||
return queries;
|
||||
}
|
||||
|
||||
// Regular query handling (non-snapshot)
|
||||
const vizPanelQueries = queryRunner?.state.queries;
|
||||
|
||||
if (vizPanelQueries) {
|
||||
@@ -631,15 +676,16 @@ export function trimDashboardForSnapshot(title: string, time: TimeRange, dash: D
|
||||
|
||||
if (spec.variables) {
|
||||
spec.variables.forEach((variable) => {
|
||||
if ('query' in variable) {
|
||||
variable.query = '';
|
||||
if ('query' in variable.spec) {
|
||||
variable.spec.query = '';
|
||||
}
|
||||
if ('options' in variable && 'current' in variable) {
|
||||
variable.options = variable.current && !isEmptyObject(variable.current) ? [variable.current] : [];
|
||||
if ('options' in variable.spec && 'current' in variable.spec) {
|
||||
variable.spec.options =
|
||||
variable.spec.current && !isEmptyObject(variable.spec.current) ? [variable.spec.current] : [];
|
||||
}
|
||||
|
||||
if ('refresh' in variable) {
|
||||
variable.refresh = VariableRefresh.never;
|
||||
if ('refresh' in variable.spec) {
|
||||
variable.spec.refresh = 'never';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user