DashboardScene: Allow unlinking a library panel (#83956)

* DashboardScene: Allow unlinking a library panel

* Betterer

* Revert

* Review
This commit is contained in:
Dominik Prokop
2024-03-07 18:11:34 +01:00
committed by GitHub
parent f5dab6b5a5
commit 1da78ac846
7 changed files with 110 additions and 4 deletions

View File

@ -490,6 +490,39 @@ describe('DashboardScene', () => {
const gridRow = body.state.children[2] as SceneGridRow;
expect(gridRow.state.children.length).toBe(1);
});
it('Should unlink a library panel', () => {
const libPanel = new LibraryVizPanel({
title: 'title',
uid: 'abc',
name: 'lib panel',
panelKey: 'panel-1',
isLoaded: true,
panel: new VizPanel({
title: 'Panel B',
pluginId: 'table',
}),
});
const scene = buildTestScene({
body: new SceneGridLayout({
children: [
new SceneGridItem({
key: 'griditem-2',
body: libPanel,
}),
],
}),
});
scene.unlinkLibraryPanel(libPanel);
const body = scene.state.body as SceneGridLayout;
const gridItem = body.state.children[0] as SceneGridItem;
expect(body.state.children.length).toBe(1);
expect(gridItem.state.body).toBeInstanceOf(VizPanel);
});
});
});

View File

@ -603,6 +603,23 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
}
}
public unlinkLibraryPanel(panel: LibraryVizPanel) {
if (!panel.parent) {
return;
}
const gridItem = panel.parent;
if (!(gridItem instanceof SceneGridItem || gridItem instanceof PanelRepeaterGridItem)) {
console.error('Trying to duplicate a panel in a layout that is not SceneGridItem or PanelRepeaterGridItem');
return;
}
gridItem?.setState({
body: panel.state.panel?.clone(),
});
}
public showModal(modal: SceneObject) {
this.setState({ overlay: modal });
}

View File

@ -28,6 +28,7 @@ import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '
import { DashboardScene } from './DashboardScene';
import { LibraryVizPanel } from './LibraryVizPanel';
import { VizPanelLinks, VizPanelLinksMenu } from './PanelLinks';
import { UnlinkLibraryPanelModal } from './UnlinkLibraryPanelModal';
/**
* Behavior is called when VizPanelMenu is activated (ie when it's opened).
@ -37,6 +38,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
// hm.. add another generic param to SceneObject to specify parent type?
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const panel = menu.parent as VizPanel;
const parent = panel.parent;
const plugin = panel.getPlugin();
const items: PanelMenuItem[] = [];
@ -101,8 +103,18 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
},
});
if (panel.parent instanceof LibraryVizPanel) {
// TODO: Implement lib panel unlinking
if (parent instanceof LibraryVizPanel) {
moreSubMenu.push({
text: t('panel.header-menu.unlink-library-panel', `Unlink library panel`),
onClick: () => {
DashboardInteractions.panelMenuItemClicked('unlinkLibraryPanel');
dashboard.showModal(
new UnlinkLibraryPanelModal({
panelRef: parent.getRef(),
})
);
},
});
} else {
moreSubMenu.push({
text: t('panel.header-menu.create-library-panel', `Create library panel`),

View File

@ -0,0 +1,44 @@
import React from 'react';
import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState } from '@grafana/scenes';
import { ModalSceneObjectLike } from '../sharing/types';
import { getDashboardSceneFor } from '../utils/utils';
import { LibraryVizPanel } from './LibraryVizPanel';
import { UnlinkModal } from './UnlinkModal';
interface UnlinkLibraryPanelModalState extends SceneObjectState {
panelRef?: SceneObjectRef<LibraryVizPanel>;
}
export class UnlinkLibraryPanelModal
extends SceneObjectBase<UnlinkLibraryPanelModalState>
implements ModalSceneObjectLike
{
static Component = UnlinkLibraryPanelModalRenderer;
public onDismiss = () => {
const dashboard = getDashboardSceneFor(this);
dashboard.closeModal();
};
public onConfirm = () => {
const dashboard = getDashboardSceneFor(this);
dashboard.unlinkLibraryPanel(this.state.panelRef!.resolve());
dashboard.closeModal();
};
}
function UnlinkLibraryPanelModalRenderer({ model }: SceneComponentProps<UnlinkLibraryPanelModal>) {
return (
<UnlinkModal
isOpen={true}
onConfirm={() => {
model.onConfirm();
model.onDismiss();
}}
onDismiss={model.onDismiss}
/>
);
}

View File

@ -33,7 +33,7 @@ import { StoreState } from 'app/types';
import { PanelOptionsChangedEvent, ShowModalReactEvent } from 'app/types/events';
import { notifyApp } from '../../../../core/actions';
import { UnlinkModal } from '../../../library-panels/components/UnlinkModal/UnlinkModal';
import { UnlinkModal } from '../../../dashboard-scene/scene/UnlinkModal';
import { isPanelModelLibraryPanel } from '../../../library-panels/guard';
import { getVariablesByKey } from '../../../variables/state/selectors';
import { DashboardPanel } from '../../dashgrid/DashboardPanel';

View File

@ -9,8 +9,8 @@ import store from 'app/core/store';
import { ShareModal } from 'app/features/dashboard/components/ShareModal';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import { UnlinkModal } from 'app/features/dashboard-scene/scene/UnlinkModal';
import { AddLibraryPanelModal } from 'app/features/library-panels/components/AddLibraryPanelModal/AddLibraryPanelModal';
import { UnlinkModal } from 'app/features/library-panels/components/UnlinkModal/UnlinkModal';
import { cleanUpPanelState } from 'app/features/panel/state/actions';
import { dispatch } from 'app/store/store';