365 lines
11 KiB
TypeScript
365 lines
11 KiB
TypeScript
import { of } from 'rxjs';
|
|
|
|
import { DataQueryRequest, DataSourceApi, LoadingState, PanelPlugin } from '@grafana/data';
|
|
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
|
import {
|
|
CancelActivationHandler,
|
|
CustomVariable,
|
|
SceneDataTransformer,
|
|
sceneGraph,
|
|
SceneGridLayout,
|
|
SceneQueryRunner,
|
|
SceneTimeRange,
|
|
SceneVariableSet,
|
|
VizPanel,
|
|
} from '@grafana/scenes';
|
|
import { mockDataSource } from 'app/features/alerting/unified/mocks';
|
|
import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources';
|
|
import { DataSourceType } from 'app/features/alerting/unified/utils/datasource';
|
|
import * as libAPI from 'app/features/library-panels/state/api';
|
|
|
|
import { DashboardScene } from '../scene/DashboardScene';
|
|
import { LibraryPanelBehavior } from '../scene/LibraryPanelBehavior';
|
|
import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem';
|
|
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
|
|
import { vizPanelToPanel } from '../serialization/transformSceneToSaveModel';
|
|
import { activateFullSceneTree } from '../utils/test-utils';
|
|
import { findVizPanelByKey, getQueryRunnerFor } from '../utils/utils';
|
|
|
|
import { buildPanelEditScene } from './PanelEditor';
|
|
|
|
const runRequestMock = jest.fn().mockImplementation((ds: DataSourceApi, request: DataQueryRequest) => {
|
|
return of({
|
|
state: LoadingState.Loading,
|
|
series: [],
|
|
timeRange: request.range,
|
|
});
|
|
});
|
|
|
|
let pluginPromise: Promise<PanelPlugin> | undefined;
|
|
|
|
jest.mock('@grafana/runtime', () => ({
|
|
...jest.requireActual('@grafana/runtime'),
|
|
getRunRequest: () => (ds: DataSourceApi, request: DataQueryRequest) => {
|
|
return runRequestMock(ds, request);
|
|
},
|
|
getPluginImportUtils: () => ({
|
|
getPanelPluginFromCache: jest.fn(() => undefined),
|
|
importPanelPlugin: () => pluginPromise,
|
|
}),
|
|
config: {
|
|
...jest.requireActual('@grafana/runtime').config,
|
|
panels: {
|
|
text: {
|
|
skipDataQuery: true,
|
|
},
|
|
timeseries: {
|
|
skipDataQuery: false,
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
const dataSources = {
|
|
ds1: mockDataSource(
|
|
{
|
|
uid: 'ds1',
|
|
type: DataSourceType.Prometheus,
|
|
},
|
|
{ module: 'core:plugin/prometheus' }
|
|
),
|
|
};
|
|
|
|
setupDataSources(...Object.values(dataSources));
|
|
|
|
let deactivate: CancelActivationHandler | undefined;
|
|
|
|
describe('PanelEditor', () => {
|
|
afterEach(() => {
|
|
if (deactivate) {
|
|
deactivate();
|
|
deactivate = undefined;
|
|
}
|
|
});
|
|
|
|
describe('When initializing', () => {
|
|
it('should wait for panel plugin to load', async () => {
|
|
const { panelEditor, panel, pluginResolve, dashboard } = await setup({ skipWait: true });
|
|
|
|
expect(panel.state.options).toEqual({});
|
|
expect(panelEditor.state.isInitializing).toBe(true);
|
|
|
|
const pluginToLoad = getPanelPlugin({ id: 'text' }).setPanelOptions((build) => {
|
|
build.addBooleanSwitch({
|
|
path: 'showHeader',
|
|
name: 'Show header',
|
|
defaultValue: true,
|
|
});
|
|
});
|
|
|
|
pluginResolve(pluginToLoad);
|
|
|
|
await new Promise((r) => setTimeout(r, 1));
|
|
|
|
expect(panelEditor.state.isInitializing).toBe(false);
|
|
expect(panel.state.options).toEqual({ showHeader: true });
|
|
|
|
panel.onOptionsChange({ showHeader: false });
|
|
panelEditor.onDiscard();
|
|
|
|
const discardedPanel = findVizPanelByKey(dashboard, panel.state.key!)!;
|
|
expect(discardedPanel.state.options).toEqual({ showHeader: true });
|
|
});
|
|
});
|
|
|
|
describe('When discarding', () => {
|
|
it('should discard changes revert all changes', async () => {
|
|
const { panelEditor, panel, dashboard } = await setup();
|
|
|
|
panel.setState({ title: 'changed title' });
|
|
panelEditor.onDiscard();
|
|
|
|
const discardedPanel = findVizPanelByKey(dashboard, panel.state.key!)!;
|
|
|
|
expect(discardedPanel.state.title).toBe('original title');
|
|
});
|
|
|
|
it('should discard a newly added panel', async () => {
|
|
const { panelEditor, dashboard } = await setup({ isNewPanel: true });
|
|
panelEditor.onDiscard();
|
|
|
|
const panels = dashboard.state.body.getVizPanels();
|
|
expect(panels.length).toBe(0);
|
|
});
|
|
|
|
it('should discard query runner changes', async () => {
|
|
const { panelEditor, panel, dashboard } = await setup({});
|
|
|
|
const queryRunner = getQueryRunnerFor(panel);
|
|
queryRunner?.setState({ maxDataPoints: 123, queries: [{ refId: 'A' }, { refId: 'B' }] });
|
|
|
|
panelEditor.onDiscard();
|
|
|
|
const discardedPanel = findVizPanelByKey(dashboard, panel.state.key!)!;
|
|
const restoredQueryRunner = getQueryRunnerFor(discardedPanel);
|
|
expect(restoredQueryRunner?.state.maxDataPoints).toBe(500);
|
|
expect(restoredQueryRunner?.state.queries.length).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('When changes are made', () => {
|
|
it('Should set state to dirty', async () => {
|
|
const { panelEditor, panel } = await setup({});
|
|
|
|
expect(panelEditor.state.isDirty).toBe(undefined);
|
|
|
|
panel.setState({ title: 'changed title' });
|
|
|
|
expect(panelEditor.state.isDirty).toBe(true);
|
|
});
|
|
|
|
it('Should reset dirty and orginal state when dashboard is saved', async () => {
|
|
const { panelEditor, panel } = await setup({});
|
|
|
|
expect(panelEditor.state.isDirty).toBe(undefined);
|
|
|
|
panel.setState({ title: 'changed title' });
|
|
|
|
panelEditor.dashboardSaved();
|
|
|
|
expect(panelEditor.state.isDirty).toBe(false);
|
|
|
|
panel.setState({ title: 'changed title 2' });
|
|
|
|
expect(panelEditor.state.isDirty).toBe(true);
|
|
|
|
// Change back to already saved state
|
|
panel.setState({ title: 'changed title' });
|
|
expect(panelEditor.state.isDirty).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('When opening a repeated panel', () => {
|
|
it('Should default to the first variable value if panel is repeated', async () => {
|
|
const { panel } = await setup({ repeatByVariable: 'server' });
|
|
const variable = sceneGraph.lookupVariable('server', panel);
|
|
expect(variable?.getValue()).toBe('A');
|
|
});
|
|
});
|
|
|
|
describe('Handling library panels', () => {
|
|
it('should call the api with the updated panel', async () => {
|
|
pluginPromise = Promise.resolve(getPanelPlugin({ id: 'text', skipDataQuery: true }));
|
|
|
|
const panel = new VizPanel({ key: 'panel-1', pluginId: 'text' });
|
|
const libraryPanelModel = {
|
|
title: 'title',
|
|
uid: 'uid',
|
|
name: 'libraryPanelName',
|
|
model: vizPanelToPanel(panel),
|
|
type: 'panel',
|
|
version: 1,
|
|
};
|
|
|
|
const libPanelBehavior = new LibraryPanelBehavior({
|
|
isLoaded: true,
|
|
uid: libraryPanelModel.uid,
|
|
name: libraryPanelModel.name,
|
|
_loadedPanel: libraryPanelModel,
|
|
});
|
|
|
|
panel.setState({ $behaviors: [libPanelBehavior] });
|
|
|
|
const gridItem = new DashboardGridItem({ body: panel });
|
|
const editScene = buildPanelEditScene(panel);
|
|
const scene = new DashboardScene({
|
|
editPanel: editScene,
|
|
$timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }),
|
|
isEditing: true,
|
|
body: new DefaultGridLayoutManager({
|
|
grid: new SceneGridLayout({
|
|
children: [gridItem],
|
|
}),
|
|
}),
|
|
});
|
|
|
|
activateFullSceneTree(scene);
|
|
|
|
await new Promise((r) => setTimeout(r, 1));
|
|
|
|
panel.setState({ title: 'changed title' });
|
|
libPanelBehavior.setState({ name: 'changed name' });
|
|
|
|
jest.spyOn(libAPI, 'saveLibPanel').mockImplementation(async (panel) => {
|
|
const updatedPanel = { ...libAPI.libraryVizPanelToSaveModel(panel), version: 2 };
|
|
libPanelBehavior.setPanelFromLibPanel(updatedPanel);
|
|
});
|
|
|
|
editScene.onConfirmSaveLibraryPanel();
|
|
await new Promise(process.nextTick);
|
|
|
|
// Wait for mock api to return and update the library panel
|
|
expect(libPanelBehavior.state._loadedPanel?.version).toBe(2);
|
|
expect(libPanelBehavior.state.name).toBe('changed name');
|
|
expect(panel.state.title).toBe('changed title');
|
|
expect((gridItem.state.body as VizPanel).state.title).toBe('changed title');
|
|
});
|
|
|
|
it('unlinks library panel', () => {
|
|
const libraryPanelModel = {
|
|
title: 'title',
|
|
uid: 'uid',
|
|
name: 'libraryPanelName',
|
|
model: {
|
|
title: 'title',
|
|
type: 'text',
|
|
},
|
|
type: 'panel',
|
|
version: 1,
|
|
};
|
|
|
|
const libPanelBehavior = new LibraryPanelBehavior({
|
|
isLoaded: true,
|
|
uid: libraryPanelModel.uid,
|
|
name: libraryPanelModel.name,
|
|
_loadedPanel: libraryPanelModel,
|
|
});
|
|
|
|
// Just adding an extra stateless behavior to verify unlinking does not remvoe it
|
|
const otherBehavior = jest.fn();
|
|
const panel = new VizPanel({ key: 'panel-1', pluginId: 'text', $behaviors: [libPanelBehavior, otherBehavior] });
|
|
new DashboardGridItem({ body: panel });
|
|
|
|
const editScene = buildPanelEditScene(panel);
|
|
editScene.onConfirmUnlinkLibraryPanel();
|
|
|
|
expect(panel.state.$behaviors?.length).toBe(1);
|
|
expect(panel.state.$behaviors![0]).toBe(otherBehavior);
|
|
});
|
|
});
|
|
|
|
describe('PanelDataPane', () => {
|
|
it('should not exist if panel is skipDataQuery', async () => {
|
|
const { panelEditor, panel } = await setup({ pluginSkipDataQuery: true });
|
|
expect(panelEditor.state.dataPane).toBeUndefined();
|
|
|
|
expect(panel.state.$data).toBeUndefined();
|
|
});
|
|
|
|
it('should exist if panel is supporting querying', async () => {
|
|
const { panelEditor, panel } = await setup({ pluginSkipDataQuery: false });
|
|
expect(panelEditor.state.dataPane).toBeDefined();
|
|
|
|
expect(panel.state.$data).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
interface SetupOptions {
|
|
isNewPanel?: boolean;
|
|
pluginSkipDataQuery?: boolean;
|
|
repeatByVariable?: string;
|
|
skipWait?: boolean;
|
|
pluginLoadTime?: number;
|
|
}
|
|
|
|
async function setup(options: SetupOptions = {}) {
|
|
const pluginToLoad = getPanelPlugin({ id: 'text', skipDataQuery: options.pluginSkipDataQuery });
|
|
let pluginResolve = (plugin: PanelPlugin) => {};
|
|
|
|
pluginPromise = new Promise<PanelPlugin>((resolve) => {
|
|
pluginResolve = resolve;
|
|
});
|
|
|
|
const panel = new VizPanel({
|
|
key: 'panel-1',
|
|
pluginId: 'text',
|
|
title: 'original title',
|
|
$data: new SceneDataTransformer({
|
|
transformations: [],
|
|
$data: new SceneQueryRunner({
|
|
queries: [{ refId: 'A' }],
|
|
maxDataPoints: 500,
|
|
datasource: { uid: 'ds1' },
|
|
}),
|
|
}),
|
|
});
|
|
|
|
const gridItem = new DashboardGridItem({ body: panel, variableName: options.repeatByVariable });
|
|
|
|
const panelEditor = buildPanelEditScene(panel, options.isNewPanel);
|
|
const dashboard = new DashboardScene({
|
|
editPanel: panelEditor,
|
|
isEditing: true,
|
|
$timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }),
|
|
$variables: new SceneVariableSet({
|
|
variables: [
|
|
new CustomVariable({
|
|
name: 'server',
|
|
query: 'A,B,C',
|
|
isMulti: true,
|
|
value: ['A', 'B', 'C'],
|
|
text: ['A', 'B', 'C'],
|
|
}),
|
|
],
|
|
}),
|
|
body: new DefaultGridLayoutManager({
|
|
grid: new SceneGridLayout({
|
|
children: [gridItem],
|
|
}),
|
|
}),
|
|
});
|
|
|
|
panelEditor.debounceSaveModelDiff = false;
|
|
|
|
deactivate = activateFullSceneTree(dashboard);
|
|
|
|
if (!options.skipWait) {
|
|
//console.log('pluginResolve(pluginToLoad)');
|
|
pluginResolve(pluginToLoad);
|
|
await new Promise((r) => setTimeout(r, 1));
|
|
}
|
|
|
|
return { dashboard, panel, gridItem, panelEditor, pluginResolve };
|
|
}
|