mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Scenes: Add 'Import from library' functionality (#83498)
* wip * tests + refactor ad panel func * Add row functionality * update row state only when there are children * Add new row + copy paste panels * Add library panel functionality * tests * PR mods * reafctor + tests * reafctor * fix test * refactor * fix bug on cancelling lib widget * dashboard now saves with lib panel widget * add lib panels widget works in rows as well * split add lib panel func to another PR * Add library panel functionality * refactor * take panelKey into account when getting next panel id in dashboard * fix tests
This commit is contained in:
parent
393b12f49f
commit
04539ffccb
@ -0,0 +1,252 @@
|
|||||||
|
import { SceneGridItem, SceneGridLayout, SceneGridRow, SceneTimeRange } from '@grafana/scenes';
|
||||||
|
import { LibraryPanel } from '@grafana/schema/dist/esm/index.gen';
|
||||||
|
|
||||||
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
|
import { activateFullSceneTree } from '../utils/test-utils';
|
||||||
|
|
||||||
|
import { AddLibraryPanelWidget } from './AddLibraryPanelWidget';
|
||||||
|
import { LibraryVizPanel } from './LibraryVizPanel';
|
||||||
|
|
||||||
|
describe('AddLibraryPanelWidget', () => {
|
||||||
|
let dashboard: DashboardScene;
|
||||||
|
let addLibPanelWidget: AddLibraryPanelWidget;
|
||||||
|
const mockEvent = {
|
||||||
|
preventDefault: jest.fn(),
|
||||||
|
} as unknown as React.MouseEvent<HTMLButtonElement>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const result = await buildTestScene();
|
||||||
|
dashboard = result.dashboard;
|
||||||
|
addLibPanelWidget = result.addLibPanelWidget;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the dashboard', () => {
|
||||||
|
expect(addLibPanelWidget.getDashboard()).toBe(dashboard);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cancel adding a lib panel', () => {
|
||||||
|
addLibPanelWidget.onCancelAddPanel(mockEvent);
|
||||||
|
|
||||||
|
const body = dashboard.state.body as SceneGridLayout;
|
||||||
|
|
||||||
|
expect(body.state.children.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cancel lib panel at correct position', () => {
|
||||||
|
const anotherLibPanelWidget = new AddLibraryPanelWidget({ key: 'panel-2' });
|
||||||
|
const body = dashboard.state.body as SceneGridLayout;
|
||||||
|
|
||||||
|
body.setState({
|
||||||
|
children: [
|
||||||
|
...body.state.children,
|
||||||
|
new SceneGridItem({
|
||||||
|
key: 'griditem-2',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 10,
|
||||||
|
height: 12,
|
||||||
|
body: anotherLibPanelWidget,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
dashboard.setState({ body });
|
||||||
|
|
||||||
|
anotherLibPanelWidget.onCancelAddPanel(mockEvent);
|
||||||
|
|
||||||
|
const gridItem = body.state.children[0] as SceneGridItem;
|
||||||
|
|
||||||
|
expect(body.state.children.length).toBe(1);
|
||||||
|
expect(gridItem.state.body!.state.key).toBe(addLibPanelWidget.state.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cancel lib panel inside a row child', () => {
|
||||||
|
const anotherLibPanelWidget = new AddLibraryPanelWidget({ key: 'panel-2' });
|
||||||
|
dashboard.setState({
|
||||||
|
body: new SceneGridLayout({
|
||||||
|
children: [
|
||||||
|
new SceneGridRow({
|
||||||
|
key: 'panel-2',
|
||||||
|
children: [
|
||||||
|
new SceneGridItem({
|
||||||
|
key: 'griditem-2',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 10,
|
||||||
|
height: 12,
|
||||||
|
body: anotherLibPanelWidget,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = dashboard.state.body as SceneGridLayout;
|
||||||
|
|
||||||
|
anotherLibPanelWidget.onCancelAddPanel(mockEvent);
|
||||||
|
|
||||||
|
const gridRow = body.state.children[0] as SceneGridRow;
|
||||||
|
|
||||||
|
expect(body.state.children.length).toBe(1);
|
||||||
|
expect(gridRow.state.children.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add library panel from menu', () => {
|
||||||
|
const panelInfo: LibraryPanel = {
|
||||||
|
uid: 'uid',
|
||||||
|
model: {
|
||||||
|
type: 'timeseries',
|
||||||
|
},
|
||||||
|
name: 'name',
|
||||||
|
version: 1,
|
||||||
|
type: 'timeseries',
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = dashboard.state.body as SceneGridLayout;
|
||||||
|
const gridItem = body.state.children[0] as SceneGridItem;
|
||||||
|
|
||||||
|
expect(gridItem.state.body!).toBeInstanceOf(AddLibraryPanelWidget);
|
||||||
|
|
||||||
|
addLibPanelWidget.onAddLibraryPanel(panelInfo);
|
||||||
|
|
||||||
|
expect(body.state.children.length).toBe(1);
|
||||||
|
expect(gridItem.state.body!).toBeInstanceOf(LibraryVizPanel);
|
||||||
|
expect((gridItem.state.body! as LibraryVizPanel).state.panelKey).toBe(addLibPanelWidget.state.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a lib panel at correct position', () => {
|
||||||
|
const anotherLibPanelWidget = new AddLibraryPanelWidget({ key: 'panel-2' });
|
||||||
|
const body = dashboard.state.body as SceneGridLayout;
|
||||||
|
|
||||||
|
body.setState({
|
||||||
|
children: [
|
||||||
|
...body.state.children,
|
||||||
|
new SceneGridItem({
|
||||||
|
key: 'griditem-2',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 10,
|
||||||
|
height: 12,
|
||||||
|
body: anotherLibPanelWidget,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
dashboard.setState({ body });
|
||||||
|
|
||||||
|
const panelInfo: LibraryPanel = {
|
||||||
|
uid: 'uid',
|
||||||
|
model: {
|
||||||
|
type: 'timeseries',
|
||||||
|
},
|
||||||
|
name: 'name',
|
||||||
|
version: 1,
|
||||||
|
type: 'timeseries',
|
||||||
|
};
|
||||||
|
|
||||||
|
anotherLibPanelWidget.onAddLibraryPanel(panelInfo);
|
||||||
|
|
||||||
|
const gridItemOne = body.state.children[0] as SceneGridItem;
|
||||||
|
const gridItemTwo = body.state.children[1] as SceneGridItem;
|
||||||
|
|
||||||
|
expect(body.state.children.length).toBe(2);
|
||||||
|
expect(gridItemOne.state.body!).toBeInstanceOf(AddLibraryPanelWidget);
|
||||||
|
expect((gridItemTwo.state.body! as LibraryVizPanel).state.panelKey).toBe(anotherLibPanelWidget.state.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add library panel from menu to a row child', () => {
|
||||||
|
const anotherLibPanelWidget = new AddLibraryPanelWidget({ key: 'panel-2' });
|
||||||
|
dashboard.setState({
|
||||||
|
body: new SceneGridLayout({
|
||||||
|
children: [
|
||||||
|
new SceneGridRow({
|
||||||
|
key: 'panel-2',
|
||||||
|
children: [
|
||||||
|
new SceneGridItem({
|
||||||
|
key: 'griditem-2',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 10,
|
||||||
|
height: 12,
|
||||||
|
body: anotherLibPanelWidget,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const panelInfo: LibraryPanel = {
|
||||||
|
uid: 'uid',
|
||||||
|
model: {
|
||||||
|
type: 'timeseries',
|
||||||
|
},
|
||||||
|
name: 'name',
|
||||||
|
version: 1,
|
||||||
|
type: 'timeseries',
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = dashboard.state.body as SceneGridLayout;
|
||||||
|
|
||||||
|
anotherLibPanelWidget.onAddLibraryPanel(panelInfo);
|
||||||
|
|
||||||
|
const gridRow = body.state.children[0] as SceneGridRow;
|
||||||
|
const gridItem = gridRow.state.children[0] as SceneGridItem;
|
||||||
|
|
||||||
|
expect(body.state.children.length).toBe(1);
|
||||||
|
expect(gridItem.state.body!).toBeInstanceOf(LibraryVizPanel);
|
||||||
|
expect((gridItem.state.body! as LibraryVizPanel).state.panelKey).toBe(anotherLibPanelWidget.state.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if adding lib panel in a layout that is not SceneGridLayout', () => {
|
||||||
|
dashboard.setState({
|
||||||
|
body: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => addLibPanelWidget.onAddLibraryPanel({} as LibraryPanel)).toThrow(
|
||||||
|
'Trying to add a library panel in a layout that is not SceneGridLayout'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if removing the library panel widget in a layout that is not SceneGridLayout', () => {
|
||||||
|
dashboard.setState({
|
||||||
|
body: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => addLibPanelWidget.onCancelAddPanel(mockEvent)).toThrow(
|
||||||
|
'Trying to remove the library panel widget in a layout that is not SceneGridLayout'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function buildTestScene() {
|
||||||
|
const addLibPanelWidget = new AddLibraryPanelWidget({ key: 'panel-1' });
|
||||||
|
const dashboard = new DashboardScene({
|
||||||
|
$timeRange: new SceneTimeRange({}),
|
||||||
|
title: 'hello',
|
||||||
|
uid: 'dash-1',
|
||||||
|
version: 4,
|
||||||
|
meta: {
|
||||||
|
canEdit: true,
|
||||||
|
},
|
||||||
|
body: new SceneGridLayout({
|
||||||
|
children: [
|
||||||
|
new SceneGridItem({
|
||||||
|
key: 'griditem-1',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 10,
|
||||||
|
height: 12,
|
||||||
|
body: addLibPanelWidget,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
activateFullSceneTree(dashboard);
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 1));
|
||||||
|
|
||||||
|
dashboard.onEnterEditMode();
|
||||||
|
|
||||||
|
return { dashboard, addLibPanelWidget };
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
import { css, cx, keyframes } from '@emotion/css';
|
||||||
|
import React from 'react';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import {
|
||||||
|
SceneComponentProps,
|
||||||
|
SceneGridItem,
|
||||||
|
SceneGridLayout,
|
||||||
|
SceneGridRow,
|
||||||
|
SceneObjectBase,
|
||||||
|
SceneObjectState,
|
||||||
|
} from '@grafana/scenes';
|
||||||
|
import { LibraryPanel } from '@grafana/schema';
|
||||||
|
import { IconButton, useStyles2 } from '@grafana/ui';
|
||||||
|
import { Trans } from 'app/core/internationalization';
|
||||||
|
import {
|
||||||
|
LibraryPanelsSearch,
|
||||||
|
LibraryPanelsSearchVariant,
|
||||||
|
} from 'app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch';
|
||||||
|
|
||||||
|
import { getDashboardSceneFor } from '../utils/utils';
|
||||||
|
|
||||||
|
import { DashboardScene } from './DashboardScene';
|
||||||
|
import { LibraryVizPanel } from './LibraryVizPanel';
|
||||||
|
|
||||||
|
export interface AddLibraryPanelWidgetState extends SceneObjectState {
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AddLibraryPanelWidget extends SceneObjectBase<AddLibraryPanelWidgetState> {
|
||||||
|
public constructor(state: AddLibraryPanelWidgetState) {
|
||||||
|
super({
|
||||||
|
...state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _dashboard(): DashboardScene {
|
||||||
|
return getDashboardSceneFor(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDashboard(): DashboardScene {
|
||||||
|
return this._dashboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onCancelAddPanel = (evt: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
if (!(this._dashboard.state.body instanceof SceneGridLayout)) {
|
||||||
|
throw new Error('Trying to remove the library panel widget in a layout that is not SceneGridLayout');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sceneGridLayout = this._dashboard.state.body;
|
||||||
|
const children = [];
|
||||||
|
|
||||||
|
for (const child of sceneGridLayout.state.children) {
|
||||||
|
if (child.state.key !== this.parent?.state.key) {
|
||||||
|
children.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child instanceof SceneGridRow) {
|
||||||
|
const rowChildren = [];
|
||||||
|
|
||||||
|
for (const rowChild of child.state.children) {
|
||||||
|
if (rowChild instanceof SceneGridItem && rowChild.state.key !== this.parent?.state.key) {
|
||||||
|
rowChildren.push(rowChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
child.setState({ children: rowChildren });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sceneGridLayout.setState({ children });
|
||||||
|
};
|
||||||
|
|
||||||
|
public onAddLibraryPanel = (panelInfo: LibraryPanel) => {
|
||||||
|
if (!(this._dashboard.state.body instanceof SceneGridLayout)) {
|
||||||
|
throw new Error('Trying to add a library panel in a layout that is not SceneGridLayout');
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = new LibraryVizPanel({
|
||||||
|
title: 'Panel Title',
|
||||||
|
uid: panelInfo.uid,
|
||||||
|
name: panelInfo.name,
|
||||||
|
panelKey: this.state.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.parent instanceof SceneGridItem) {
|
||||||
|
this.parent.setState({ body });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static Component = ({ model }: SceneComponentProps<AddLibraryPanelWidget>) => {
|
||||||
|
const dashboard = model.getDashboard();
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<div className={cx('panel-container', styles.callToAction)}>
|
||||||
|
<div className={cx(styles.headerRow, `grid-drag-handle-${dashboard.state.body.state.key}`)}>
|
||||||
|
<span>
|
||||||
|
<Trans i18nKey="library-panel.add-widget.title">Add panel from panel library</Trans>
|
||||||
|
</span>
|
||||||
|
<div className="flex-grow-1" />
|
||||||
|
<IconButton
|
||||||
|
aria-label="Close 'Add Panel' widget"
|
||||||
|
name="times"
|
||||||
|
onClick={model.onCancelAddPanel}
|
||||||
|
tooltip="Close widget"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<LibraryPanelsSearch
|
||||||
|
onClick={model.onAddLibraryPanel}
|
||||||
|
variant={LibraryPanelsSearchVariant.Tight}
|
||||||
|
showPanelFilter
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
const pulsate = keyframes({
|
||||||
|
'0%': {
|
||||||
|
boxShadow: `0 0 0 2px ${theme.colors.background.canvas}, 0 0 0px 4px ${theme.colors.primary.main}`,
|
||||||
|
},
|
||||||
|
'50%': {
|
||||||
|
boxShadow: `0 0 0 2px ${theme.components.dashboard.background}, 0 0 0px 4px ${tinycolor(theme.colors.primary.main)
|
||||||
|
.darken(20)
|
||||||
|
.toHexString()}`,
|
||||||
|
},
|
||||||
|
'100%': {
|
||||||
|
boxShadow: `0 0 0 2px ${theme.components.dashboard.background}, 0 0 0px 4px ${theme.colors.primary.main}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
// wrapper is used to make sure box-shadow animation isn't cut off in dashboard page
|
||||||
|
wrapper: css({
|
||||||
|
height: '100%',
|
||||||
|
paddingTop: `${theme.spacing(0.5)}`,
|
||||||
|
}),
|
||||||
|
headerRow: css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '38px',
|
||||||
|
flexShrink: 0,
|
||||||
|
width: '100%',
|
||||||
|
fontSize: theme.typography.fontSize,
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
paddingLeft: `${theme.spacing(1)}`,
|
||||||
|
transition: 'background-color 0.1s ease-in-out',
|
||||||
|
cursor: 'move',
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
background: `${theme.colors.background.secondary}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
callToAction: css({
|
||||||
|
overflow: 'hidden',
|
||||||
|
outline: '2px dotted transparent',
|
||||||
|
outlineOffset: '2px',
|
||||||
|
boxShadow: '0 0 0 2px black, 0 0 0px 4px #1f60c4',
|
||||||
|
animation: `${pulsate} 2s ease infinite`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
@ -264,6 +264,17 @@ describe('DashboardScene', () => {
|
|||||||
expect(gridItem.state.y).toBe(0);
|
expect(gridItem.state.y).toBe(0);
|
||||||
expect(scene.state.hasCopiedPanel).toBe(false);
|
expect(scene.state.hasCopiedPanel).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should create a new add library panel widget', () => {
|
||||||
|
scene.onCreateLibPanelWidget();
|
||||||
|
|
||||||
|
const body = scene.state.body as SceneGridLayout;
|
||||||
|
const gridItem = body.state.children[0] as SceneGridItem;
|
||||||
|
|
||||||
|
expect(body.state.children.length).toBe(5);
|
||||||
|
expect(gridItem.state.body!.state.key).toBe('panel-5');
|
||||||
|
expect(gridItem.state.y).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ import {
|
|||||||
isPanelClone,
|
isPanelClone,
|
||||||
} from '../utils/utils';
|
} from '../utils/utils';
|
||||||
|
|
||||||
|
import { AddLibraryPanelWidget } from './AddLibraryPanelWidget';
|
||||||
import { DashboardControls } from './DashboardControls';
|
import { DashboardControls } from './DashboardControls';
|
||||||
import { DashboardSceneUrlSync } from './DashboardSceneUrlSync';
|
import { DashboardSceneUrlSync } from './DashboardSceneUrlSync';
|
||||||
import { PanelRepeaterGridItem } from './PanelRepeaterGridItem';
|
import { PanelRepeaterGridItem } from './PanelRepeaterGridItem';
|
||||||
@ -612,6 +613,29 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
|||||||
locationService.partial({ editview: 'settings' });
|
locationService.partial({ editview: 'settings' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public onCreateLibPanelWidget() {
|
||||||
|
if (!(this.state.body instanceof SceneGridLayout)) {
|
||||||
|
throw new Error('Trying to add a panel in a layout that is not SceneGridLayout');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sceneGridLayout = this.state.body;
|
||||||
|
|
||||||
|
const panelId = dashboardSceneGraph.getNextPanelId(this);
|
||||||
|
|
||||||
|
const newGridItem = new SceneGridItem({
|
||||||
|
height: NEW_PANEL_HEIGHT,
|
||||||
|
width: NEW_PANEL_WIDTH,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
body: new AddLibraryPanelWidget({ key: getVizPanelKeyForPanelId(panelId) }),
|
||||||
|
key: `grid-item-${panelId}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
sceneGridLayout.setState({
|
||||||
|
children: [newGridItem, ...sceneGridLayout.state.children],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public onCreateNewRow() {
|
public onCreateNewRow() {
|
||||||
const row = getDefaultRow(this);
|
const row = getDefaultRow(this);
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ describe('NavToolbarActions', () => {
|
|||||||
expect(screen.queryByLabelText('Add visualization')).not.toBeInTheDocument();
|
expect(screen.queryByLabelText('Add visualization')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByLabelText('Add row')).not.toBeInTheDocument();
|
expect(screen.queryByLabelText('Add row')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByLabelText('Paste panel')).not.toBeInTheDocument();
|
expect(screen.queryByLabelText('Paste panel')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByLabelText('Add library panel')).not.toBeInTheDocument();
|
||||||
expect(await screen.findByText('Edit')).toBeInTheDocument();
|
expect(await screen.findByText('Edit')).toBeInTheDocument();
|
||||||
expect(await screen.findByText('Share')).toBeInTheDocument();
|
expect(await screen.findByText('Share')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@ -32,6 +33,7 @@ describe('NavToolbarActions', () => {
|
|||||||
expect(await screen.findByLabelText('Add visualization')).toBeInTheDocument();
|
expect(await screen.findByLabelText('Add visualization')).toBeInTheDocument();
|
||||||
expect(await screen.findByLabelText('Add row')).toBeInTheDocument();
|
expect(await screen.findByLabelText('Add row')).toBeInTheDocument();
|
||||||
expect(await screen.findByLabelText('Paste panel')).toBeInTheDocument();
|
expect(await screen.findByLabelText('Paste panel')).toBeInTheDocument();
|
||||||
|
expect(await screen.findByLabelText('Add library panel')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Edit')).not.toBeInTheDocument();
|
expect(screen.queryByText('Edit')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Share')).not.toBeInTheDocument();
|
expect(screen.queryByText('Share')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -71,6 +71,22 @@ export function ToolbarActions({ dashboard }: Props) {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
toolbarActions.push({
|
||||||
|
group: 'icon-actions',
|
||||||
|
condition: isEditing && !editview && !isViewingPanel && !isEditingPanel,
|
||||||
|
render: () => (
|
||||||
|
<ToolbarButton
|
||||||
|
key="add-library-panel"
|
||||||
|
tooltip={'Add library panel'}
|
||||||
|
icon="library-panel"
|
||||||
|
onClick={() => {
|
||||||
|
dashboard.onCreateLibPanelWidget();
|
||||||
|
DashboardInteractions.toolbarAddButtonClicked({ item: 'add_library_panel' });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
toolbarActions.push({
|
toolbarActions.push({
|
||||||
group: 'icon-actions',
|
group: 'icon-actions',
|
||||||
condition: isEditing && !editview && !isViewingPanel && !isEditingPanel,
|
condition: isEditing && !editview && !isViewingPanel && !isEditingPanel,
|
||||||
|
@ -42,11 +42,13 @@ import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard';
|
|||||||
import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types';
|
import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types';
|
||||||
import { DashboardDataDTO } from 'app/types';
|
import { DashboardDataDTO } from 'app/types';
|
||||||
|
|
||||||
|
import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
||||||
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
|
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
|
||||||
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
||||||
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
|
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
|
||||||
import { NEW_LINK } from '../settings/links/utils';
|
import { NEW_LINK } from '../settings/links/utils';
|
||||||
import { getQueryRunnerFor } from '../utils/utils';
|
import { getQueryRunnerFor, getVizPanelKeyForPanelId } from '../utils/utils';
|
||||||
|
|
||||||
import { buildNewDashboardSaveModel } from './buildNewDashboardSaveModel';
|
import { buildNewDashboardSaveModel } from './buildNewDashboardSaveModel';
|
||||||
import { GRAFANA_DATASOURCE_REF } from './const';
|
import { GRAFANA_DATASOURCE_REF } from './const';
|
||||||
@ -58,6 +60,8 @@ import {
|
|||||||
createSceneVariableFromVariableModel,
|
createSceneVariableFromVariableModel,
|
||||||
transformSaveModelToScene,
|
transformSaveModelToScene,
|
||||||
convertOldSnapshotToScenesSnapshot,
|
convertOldSnapshotToScenesSnapshot,
|
||||||
|
buildGridItemForLibPanel,
|
||||||
|
buildGridItemForLibraryPanelWidget,
|
||||||
} from './transformSaveModelToScene';
|
} from './transformSaveModelToScene';
|
||||||
|
|
||||||
describe('transformSaveModelToScene', () => {
|
describe('transformSaveModelToScene', () => {
|
||||||
@ -453,6 +457,37 @@ describe('transformSaveModelToScene', () => {
|
|||||||
expect(runner.state.cacheTimeout).toBe('10');
|
expect(runner.state.cacheTimeout).toBe('10');
|
||||||
expect(runner.state.queryCachingTTL).toBe(200000);
|
expect(runner.state.queryCachingTTL).toBe(200000);
|
||||||
});
|
});
|
||||||
|
it('should convert saved lib widget to AddLibraryPanelWidget', () => {
|
||||||
|
const panel = {
|
||||||
|
id: 10,
|
||||||
|
type: 'add-library-panel',
|
||||||
|
};
|
||||||
|
|
||||||
|
const gridItem = buildGridItemForLibraryPanelWidget(new PanelModel(panel))!;
|
||||||
|
const libPanelWidget = gridItem.state.body as AddLibraryPanelWidget;
|
||||||
|
|
||||||
|
expect(libPanelWidget.state.key).toEqual(getVizPanelKeyForPanelId(panel.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert saved lib panel to LibraryVizPanel', () => {
|
||||||
|
const panel = {
|
||||||
|
title: 'Panel',
|
||||||
|
gridPos: { x: 0, y: 0, w: 12, h: 8 },
|
||||||
|
transparent: true,
|
||||||
|
libraryPanel: {
|
||||||
|
uid: '123',
|
||||||
|
name: 'My Panel',
|
||||||
|
folderUid: '456',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const gridItem = buildGridItemForLibPanel(new PanelModel(panel))!;
|
||||||
|
const libVizPanel = gridItem.state.body as LibraryVizPanel;
|
||||||
|
|
||||||
|
expect(libVizPanel.state.uid).toEqual(panel.libraryPanel.uid);
|
||||||
|
expect(libVizPanel.state.name).toEqual(panel.libraryPanel.name);
|
||||||
|
expect(libVizPanel.state.title).toEqual(panel.title);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when creating variables objects', () => {
|
describe('when creating variables objects', () => {
|
||||||
|
@ -34,6 +34,7 @@ import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
|||||||
import { trackDashboardLoaded } from 'app/features/dashboard/utils/tracking';
|
import { trackDashboardLoaded } from 'app/features/dashboard/utils/tracking';
|
||||||
import { DashboardDTO } from 'app/types';
|
import { DashboardDTO } from 'app/types';
|
||||||
|
|
||||||
|
import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
||||||
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer';
|
||||||
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer';
|
||||||
import { DashboardControls } from '../scene/DashboardControls';
|
import { DashboardControls } from '../scene/DashboardControls';
|
||||||
@ -110,6 +111,12 @@ export function createSceneObjectsForPanels(oldPanels: PanelModel[]): SceneGridI
|
|||||||
currentRowPanels = [];
|
currentRowPanels = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (panel.type === 'add-library-panel') {
|
||||||
|
const gridItem = buildGridItemForLibraryPanelWidget(panel);
|
||||||
|
|
||||||
|
if (gridItem) {
|
||||||
|
panels.push(gridItem);
|
||||||
|
}
|
||||||
} else if (panel.libraryPanel?.uid && !('model' in panel.libraryPanel)) {
|
} else if (panel.libraryPanel?.uid && !('model' in panel.libraryPanel)) {
|
||||||
const gridItem = buildGridItemForLibPanel(panel);
|
const gridItem = buildGridItemForLibPanel(panel);
|
||||||
if (gridItem) {
|
if (gridItem) {
|
||||||
@ -399,6 +406,24 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildGridItemForLibraryPanelWidget(panel: PanelModel) {
|
||||||
|
if (panel.type !== 'add-library-panel') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = new AddLibraryPanelWidget({
|
||||||
|
key: getVizPanelKeyForPanelId(panel.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
return new SceneGridItem({
|
||||||
|
body,
|
||||||
|
y: panel.gridPos.y,
|
||||||
|
x: panel.gridPos.x,
|
||||||
|
width: panel.gridPos.w,
|
||||||
|
height: panel.gridPos.h,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function buildGridItemForLibPanel(panel: PanelModel) {
|
export function buildGridItemForLibPanel(panel: PanelModel) {
|
||||||
if (!panel.libraryPanel) {
|
if (!panel.libraryPanel) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -41,6 +41,7 @@ import snapshotableDashboardJson from './testfiles/snapshotable_dashboard.json';
|
|||||||
import snapshotableWithRowsDashboardJson from './testfiles/snapshotable_with_rows.json';
|
import snapshotableWithRowsDashboardJson from './testfiles/snapshotable_with_rows.json';
|
||||||
import {
|
import {
|
||||||
buildGridItemForLibPanel,
|
buildGridItemForLibPanel,
|
||||||
|
buildGridItemForLibraryPanelWidget,
|
||||||
buildGridItemForPanel,
|
buildGridItemForPanel,
|
||||||
transformSaveModelToScene,
|
transformSaveModelToScene,
|
||||||
} from './transformSaveModelToScene';
|
} from './transformSaveModelToScene';
|
||||||
@ -351,6 +352,30 @@ describe('transformSceneToSaveModel', () => {
|
|||||||
expect(result.transformations).toBeUndefined();
|
expect(result.transformations).toBeUndefined();
|
||||||
expect(result.fieldConfig).toBeUndefined();
|
expect(result.fieldConfig).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('given a library panel widget', () => {
|
||||||
|
const panel = buildGridItemFromPanelSchema({
|
||||||
|
id: 4,
|
||||||
|
gridPos: {
|
||||||
|
h: 8,
|
||||||
|
w: 12,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
type: 'add-library-panel',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = gridItemToPanel(panel);
|
||||||
|
|
||||||
|
expect(result.id).toBe(4);
|
||||||
|
expect(result.gridPos).toEqual({
|
||||||
|
h: 8,
|
||||||
|
w: 12,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
expect(result.type).toBe('add-library-panel');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Annotations', () => {
|
describe('Annotations', () => {
|
||||||
@ -897,6 +922,9 @@ describe('transformSceneToSaveModel', () => {
|
|||||||
export function buildGridItemFromPanelSchema(panel: Partial<Panel>): SceneGridItemLike {
|
export function buildGridItemFromPanelSchema(panel: Partial<Panel>): SceneGridItemLike {
|
||||||
if (panel.libraryPanel) {
|
if (panel.libraryPanel) {
|
||||||
return buildGridItemForLibPanel(new PanelModel(panel))!;
|
return buildGridItemForLibPanel(new PanelModel(panel))!;
|
||||||
|
} else if (panel.type === 'add-library-panel') {
|
||||||
|
return buildGridItemForLibraryPanelWidget(new PanelModel(panel))!;
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildGridItemForPanel(new PanelModel(panel));
|
return buildGridItemForPanel(new PanelModel(panel));
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import { getPanelDataFrames } from 'app/features/dashboard/components/HelpWizard
|
|||||||
import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator';
|
import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator';
|
||||||
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
|
||||||
|
|
||||||
|
import { AddLibraryPanelWidget } from '../scene/AddLibraryPanelWidget';
|
||||||
import { DashboardScene } from '../scene/DashboardScene';
|
import { DashboardScene } from '../scene/DashboardScene';
|
||||||
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
import { LibraryVizPanel } from '../scene/LibraryVizPanel';
|
||||||
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
|
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
|
||||||
@ -167,6 +168,20 @@ export function gridItemToPanel(gridItem: SceneGridItemLike, isSnapshot = false)
|
|||||||
} as Panel;
|
} as Panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle library panel widget as well and exit early
|
||||||
|
if (gridItem.state.body instanceof AddLibraryPanelWidget) {
|
||||||
|
x = gridItem.state.x ?? 0;
|
||||||
|
y = gridItem.state.y ?? 0;
|
||||||
|
w = gridItem.state.width ?? 0;
|
||||||
|
h = gridItem.state.height ?? 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: getPanelIdForVizPanel(gridItem.state.body),
|
||||||
|
type: 'add-library-panel',
|
||||||
|
gridPos: { x, y, w, h },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!(gridItem.state.body instanceof VizPanel)) {
|
if (!(gridItem.state.body instanceof VizPanel)) {
|
||||||
throw new Error('SceneGridItem body expected to be VizPanel');
|
throw new Error('SceneGridItem body expected to be VizPanel');
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user