mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard scenes: Fixes inspect library panels (#82879)
* working except for links * Make sure the links are present on the library panels * add tests, add empty links before panel is loaded, refactor legacy representation * Update --------- Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
parent
1f98028962
commit
b56f6ed0dc
@ -9,10 +9,13 @@ import {
|
|||||||
SceneGridLayout,
|
SceneGridLayout,
|
||||||
VizPanel,
|
VizPanel,
|
||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
|
import * as libpanels from 'app/features/library-panels/state/api';
|
||||||
import { getStandardTransformers } from 'app/features/transformers/standardTransformers';
|
import { getStandardTransformers } from 'app/features/transformers/standardTransformers';
|
||||||
|
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
|
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
|
||||||
|
import { vizPanelToPanel } from '../serialization/transformSceneToSaveModel';
|
||||||
import { activateFullSceneTree } from '../utils/test-utils';
|
import { activateFullSceneTree } from '../utils/test-utils';
|
||||||
import { findVizPanelByKey } from '../utils/utils';
|
import { findVizPanelByKey } from '../utils/utils';
|
||||||
|
|
||||||
@ -25,6 +28,14 @@ setPluginImportUtils({
|
|||||||
getPanelPluginFromCache: (id: string) => undefined,
|
getPanelPluginFromCache: (id: string) => undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...jest.requireActual('@grafana/runtime'),
|
||||||
|
setPluginExtensionGetter: jest.fn(),
|
||||||
|
getPluginLinkExtensions: jest.fn(() => ({
|
||||||
|
extensions: [],
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('InspectJsonTab', () => {
|
describe('InspectJsonTab', () => {
|
||||||
it('Can show panel json', async () => {
|
it('Can show panel json', async () => {
|
||||||
const { tab } = await buildTestScene();
|
const { tab } = await buildTestScene();
|
||||||
@ -34,6 +45,15 @@ describe('InspectJsonTab', () => {
|
|||||||
expect(tab.isEditable()).toBe(true);
|
expect(tab.isEditable()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Can show panel json for library panels', async () => {
|
||||||
|
const { tab } = await buildTestSceneWithLibraryPanel();
|
||||||
|
|
||||||
|
const obj = JSON.parse(tab.state.jsonText);
|
||||||
|
expect(obj.gridPos).toEqual({ x: 0, y: 0, w: 10, h: 12 });
|
||||||
|
expect(obj.type).toEqual('table');
|
||||||
|
expect(tab.isEditable()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('Can show panel data with field config', async () => {
|
it('Can show panel data with field config', async () => {
|
||||||
const { tab } = await buildTestScene();
|
const { tab } = await buildTestScene();
|
||||||
tab.onChangeSource({ value: 'panel-data' });
|
tab.onChangeSource({ value: 'panel-data' });
|
||||||
@ -86,8 +106,8 @@ describe('InspectJsonTab', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function buildTestScene() {
|
function buildTestPanel() {
|
||||||
const panel = new VizPanel({
|
return new VizPanel({
|
||||||
title: 'Panel A',
|
title: 'Panel A',
|
||||||
pluginId: 'table',
|
pluginId: 'table',
|
||||||
key: 'panel-12',
|
key: 'panel-12',
|
||||||
@ -129,7 +149,10 @@ async function buildTestScene() {
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildTestScene() {
|
||||||
|
const panel = buildTestPanel();
|
||||||
const scene = new DashboardScene({
|
const scene = new DashboardScene({
|
||||||
title: 'hello',
|
title: 'hello',
|
||||||
uid: 'dash-1',
|
uid: 'dash-1',
|
||||||
@ -161,3 +184,50 @@ async function buildTestScene() {
|
|||||||
|
|
||||||
return { scene, tab, panel };
|
return { scene, tab, panel };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function buildTestSceneWithLibraryPanel() {
|
||||||
|
const panel = vizPanelToPanel(buildTestPanel());
|
||||||
|
|
||||||
|
const libraryPanelState = {
|
||||||
|
name: 'LibraryPanel A',
|
||||||
|
title: 'LibraryPanel A title',
|
||||||
|
uid: '111',
|
||||||
|
key: 'panel-22',
|
||||||
|
model: panel,
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.spyOn(libpanels, 'getLibraryPanel').mockResolvedValue({ ...libraryPanelState, ...panel });
|
||||||
|
const libraryPanel = new LibraryVizPanel(libraryPanelState);
|
||||||
|
|
||||||
|
const scene = new DashboardScene({
|
||||||
|
title: 'hello',
|
||||||
|
uid: 'dash-1',
|
||||||
|
meta: {
|
||||||
|
canEdit: true,
|
||||||
|
},
|
||||||
|
body: new SceneGridLayout({
|
||||||
|
children: [
|
||||||
|
new SceneGridItem({
|
||||||
|
key: 'griditem-1',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 10,
|
||||||
|
height: 12,
|
||||||
|
body: libraryPanel,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
activateFullSceneTree(scene);
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 1));
|
||||||
|
|
||||||
|
const tab = new InspectJsonTab({
|
||||||
|
panelRef: libraryPanel.state.panel!.getRef(),
|
||||||
|
onClose: jest.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { scene, tab, panel };
|
||||||
|
}
|
||||||
|
@ -26,9 +26,10 @@ import { InspectTab } from 'app/features/inspector/types';
|
|||||||
import { getPrettyJSON } from 'app/features/inspector/utils/utils';
|
import { getPrettyJSON } from 'app/features/inspector/utils/utils';
|
||||||
import { reportPanelInspectInteraction } from 'app/features/search/page/reporting';
|
import { reportPanelInspectInteraction } from 'app/features/search/page/reporting';
|
||||||
|
|
||||||
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
|
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
|
||||||
import { buildGridItemForPanel } from '../serialization/transformSaveModelToScene';
|
import { buildGridItemForPanel } from '../serialization/transformSaveModelToScene';
|
||||||
import { gridItemToPanel } from '../serialization/transformSceneToSaveModel';
|
import { gridItemToPanel, vizPanelToPanel } from '../serialization/transformSceneToSaveModel';
|
||||||
import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
|
import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
|
||||||
|
|
||||||
export type ShowContent = 'panel-json' | 'panel-data' | 'data-frames';
|
export type ShowContent = 'panel-json' | 'panel-data' | 'data-frames';
|
||||||
@ -202,6 +203,8 @@ function getJsonText(show: ShowContent, panel: VizPanel): string {
|
|||||||
|
|
||||||
if (panel.parent instanceof SceneGridItem || panel.parent instanceof PanelRepeaterGridItem) {
|
if (panel.parent instanceof SceneGridItem || panel.parent instanceof PanelRepeaterGridItem) {
|
||||||
objToStringify = gridItemToPanel(panel.parent);
|
objToStringify = gridItemToPanel(panel.parent);
|
||||||
|
} else if (panel.parent instanceof LibraryVizPanel) {
|
||||||
|
objToStringify = libraryPanelChildToLegacyRepresentation(panel);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -234,6 +237,30 @@ function getJsonText(show: ShowContent, panel: VizPanel): string {
|
|||||||
return getPrettyJSON(objToStringify);
|
return getPrettyJSON(objToStringify);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param panel Must be child of a LibraryVizPanel that is in turn the child of a SceneGridItem
|
||||||
|
* @returns object representation of the legacy library panel structure.
|
||||||
|
*/
|
||||||
|
function libraryPanelChildToLegacyRepresentation(panel: VizPanel<{}, {}>) {
|
||||||
|
if (!(panel.parent instanceof LibraryVizPanel)) {
|
||||||
|
throw 'Panel not child of LibraryVizPanel';
|
||||||
|
}
|
||||||
|
if (!(panel.parent.parent instanceof SceneGridItem)) {
|
||||||
|
throw 'LibraryPanel not child of SceneGridItem';
|
||||||
|
}
|
||||||
|
const gridItem = panel.parent.parent;
|
||||||
|
const libraryPanelObj = gridItemToPanel(gridItem);
|
||||||
|
const panelObj = vizPanelToPanel(panel);
|
||||||
|
panelObj.gridPos = {
|
||||||
|
x: gridItem.state.x || 0,
|
||||||
|
y: gridItem.state.y || 0,
|
||||||
|
h: gridItem.state.height || 0,
|
||||||
|
w: gridItem.state.width || 0,
|
||||||
|
};
|
||||||
|
return { ...libraryPanelObj, ...panelObj };
|
||||||
|
}
|
||||||
|
|
||||||
function hasGridPosChanged(a: SceneGridItemStateLike, b: SceneGridItemStateLike) {
|
function hasGridPosChanged(a: SceneGridItemStateLike, b: SceneGridItemStateLike) {
|
||||||
return a.x !== b.x || a.y !== b.y || a.width !== b.width || a.height !== b.height;
|
return a.x !== b.x || a.y !== b.y || a.width !== b.width || a.height !== b.height;
|
||||||
}
|
}
|
||||||
|
@ -6,31 +6,26 @@ import { getLibraryPanel } from 'app/features/library-panels/state/api';
|
|||||||
|
|
||||||
import { createPanelDataProvider } from '../utils/createPanelDataProvider';
|
import { createPanelDataProvider } from '../utils/createPanelDataProvider';
|
||||||
|
|
||||||
import { panelMenuBehavior } from './PanelMenuBehavior';
|
import { VizPanelLinks, VizPanelLinksMenu } from './PanelLinks';
|
||||||
|
import { panelLinksBehavior, panelMenuBehavior } from './PanelMenuBehavior';
|
||||||
|
import { PanelNotices } from './PanelNotices';
|
||||||
|
|
||||||
interface LibraryVizPanelState extends SceneObjectState {
|
interface LibraryVizPanelState extends SceneObjectState {
|
||||||
// Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it.
|
// Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it.
|
||||||
title: string;
|
title: string;
|
||||||
uid: string;
|
uid: string;
|
||||||
name: string;
|
name: string;
|
||||||
panel: VizPanel;
|
panel?: VizPanel;
|
||||||
|
_loadedVersion?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LibraryVizPanel extends SceneObjectBase<LibraryVizPanelState> {
|
export class LibraryVizPanel extends SceneObjectBase<LibraryVizPanelState> {
|
||||||
static Component = LibraryPanelRenderer;
|
static Component = LibraryPanelRenderer;
|
||||||
|
|
||||||
constructor({ uid, title, key, name }: Pick<LibraryVizPanelState, 'uid' | 'title' | 'key' | 'name'>) {
|
constructor(state: LibraryVizPanelState) {
|
||||||
super({
|
super({
|
||||||
uid,
|
panel: state.panel ?? getLoadingPanel(state.title),
|
||||||
title,
|
...state,
|
||||||
key,
|
|
||||||
name,
|
|
||||||
panel: new VizPanel({
|
|
||||||
title,
|
|
||||||
menu: new VizPanelMenu({
|
|
||||||
$behaviors: [panelMenuBehavior],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addActivationHandler(this._onActivate);
|
this.addActivationHandler(this._onActivate);
|
||||||
@ -41,29 +36,54 @@ export class LibraryVizPanel extends SceneObjectBase<LibraryVizPanelState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private async loadLibraryPanelFromPanelModel() {
|
private async loadLibraryPanelFromPanelModel() {
|
||||||
let vizPanel = this.state.panel;
|
let vizPanel = this.state.panel!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const libPanel = await getLibraryPanel(this.state.uid, true);
|
const libPanel = await getLibraryPanel(this.state.uid, true);
|
||||||
|
|
||||||
|
if (this.state._loadedVersion === libPanel.version) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const libPanelModel = new PanelModel(libPanel.model);
|
const libPanelModel = new PanelModel(libPanel.model);
|
||||||
vizPanel = vizPanel.clone({
|
|
||||||
|
const panel = new VizPanel({
|
||||||
|
title: this.state.title,
|
||||||
options: libPanelModel.options ?? {},
|
options: libPanelModel.options ?? {},
|
||||||
fieldConfig: libPanelModel.fieldConfig,
|
fieldConfig: libPanelModel.fieldConfig,
|
||||||
|
pluginId: libPanelModel.type,
|
||||||
pluginVersion: libPanelModel.pluginVersion,
|
pluginVersion: libPanelModel.pluginVersion,
|
||||||
displayMode: libPanelModel.transparent ? 'transparent' : undefined,
|
displayMode: libPanelModel.transparent ? 'transparent' : undefined,
|
||||||
description: libPanelModel.description,
|
description: libPanelModel.description,
|
||||||
pluginId: libPanel.type,
|
|
||||||
$data: createPanelDataProvider(libPanelModel),
|
$data: createPanelDataProvider(libPanelModel),
|
||||||
|
menu: new VizPanelMenu({ $behaviors: [panelMenuBehavior] }),
|
||||||
|
titleItems: [
|
||||||
|
new VizPanelLinks({
|
||||||
|
rawLinks: libPanelModel.links,
|
||||||
|
menu: new VizPanelLinksMenu({ $behaviors: [panelLinksBehavior] }),
|
||||||
|
}),
|
||||||
|
new PanelNotices(),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.setState({ panel, _loadedVersion: libPanel.version });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
vizPanel.setState({
|
vizPanel.setState({
|
||||||
_pluginLoadError: 'Unable to load library panel: ' + this.state.uid,
|
_pluginLoadError: 'Unable to load library panel: ' + this.state.uid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ panel: vizPanel });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLoadingPanel(title: string) {
|
||||||
|
return new VizPanel({
|
||||||
|
title,
|
||||||
|
menu: new VizPanelMenu({
|
||||||
|
$behaviors: [panelMenuBehavior],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function LibraryPanelRenderer({ model }: SceneComponentProps<LibraryVizPanel>) {
|
function LibraryPanelRenderer({ model }: SceneComponentProps<LibraryVizPanel>) {
|
||||||
const { panel } = model.useState();
|
const { panel } = model.useState();
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
defaultDashboard,
|
defaultDashboard,
|
||||||
defaultTimePickerConfig,
|
defaultTimePickerConfig,
|
||||||
FieldConfigSource,
|
FieldConfigSource,
|
||||||
|
GridPos,
|
||||||
Panel,
|
Panel,
|
||||||
RowPanel,
|
RowPanel,
|
||||||
TimePickerConfig,
|
TimePickerConfig,
|
||||||
@ -200,11 +201,22 @@ export function gridItemToPanel(gridItem: SceneGridItemLike, isSnapshot = false)
|
|||||||
throw new Error('Unsupported grid item type');
|
throw new Error('Unsupported grid item type');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const panel: Panel = vizPanelToPanel(vizPanel, { x, y, h, w }, isSnapshot, gridItem);
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function vizPanelToPanel(
|
||||||
|
vizPanel: VizPanel,
|
||||||
|
gridPos?: GridPos,
|
||||||
|
isSnapshot = false,
|
||||||
|
gridItem?: SceneGridItemLike
|
||||||
|
) {
|
||||||
const panel: Panel = {
|
const panel: Panel = {
|
||||||
id: getPanelIdForVizPanel(vizPanel),
|
id: getPanelIdForVizPanel(vizPanel),
|
||||||
type: vizPanel.state.pluginId,
|
type: vizPanel.state.pluginId,
|
||||||
title: vizPanel.state.title,
|
title: vizPanel.state.title,
|
||||||
gridPos: { x, y, w, h },
|
gridPos,
|
||||||
options: vizPanel.state.options,
|
options: vizPanel.state.options,
|
||||||
fieldConfig: (vizPanel.state.fieldConfig as FieldConfigSource) ?? { defaults: {}, overrides: [] },
|
fieldConfig: (vizPanel.state.fieldConfig as FieldConfigSource) ?? { defaults: {}, overrides: [] },
|
||||||
transformations: [],
|
transformations: [],
|
||||||
@ -241,7 +253,6 @@ export function gridItemToPanel(gridItem: SceneGridItemLike, isSnapshot = false)
|
|||||||
if (!panel.transparent) {
|
if (!panel.transparent) {
|
||||||
delete panel.transparent;
|
delete panel.transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user