Dashboard scenes: Unlink library panel inside edit mode (#84355)

Unlink library panel inside edit mode
This commit is contained in:
Oscar Kilhed 2024-03-13 18:22:22 +01:00 committed by GitHub
parent cf6bed7ae5
commit 0fe5b62fa5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 119 additions and 8 deletions

View File

@ -20,6 +20,7 @@ export interface PanelEditorState extends SceneObjectState {
dataPane?: PanelDataPane;
vizManager: VizPanelManager;
showLibraryPanelSaveModal?: boolean;
showLibraryPanelUnlinkModal?: boolean;
}
export class PanelEditor extends SceneObjectBase<PanelEditorState> {
@ -212,9 +213,22 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
locationService.partial({ editPanel: null });
};
public onDismissLibraryPanelModal = () => {
public onDismissLibraryPanelSaveModal = () => {
this.setState({ showLibraryPanelSaveModal: false });
};
public onUnlinkLibraryPanel = () => {
this.setState({ showLibraryPanelUnlinkModal: true });
};
public onDismissUnlinkLibraryPanelModal = () => {
this.setState({ showLibraryPanelUnlinkModal: false });
};
public onConfirmUnlinkLibraryPanel = () => {
this.state.vizManager.unlinkLibraryPanel();
this.setState({ showLibraryPanelUnlinkModal: false });
};
}
export function buildPanelEditScene(panel: VizPanel): PanelEditor {

View File

@ -6,6 +6,7 @@ import { SceneComponentProps } from '@grafana/scenes';
import { Button, ToolbarButton, useStyles2 } from '@grafana/ui';
import { NavToolbarActions } from '../scene/NavToolbarActions';
import { UnlinkModal } from '../scene/UnlinkModal';
import { getDashboardSceneFor, getLibraryPanel } from '../utils/utils';
import { PanelEditor } from './PanelEditor';
@ -58,7 +59,7 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
const dashboard = getDashboardSceneFor(model);
const { vizManager, dataPane, showLibraryPanelSaveModal } = model.useState();
const { vizManager, dataPane, showLibraryPanelSaveModal, showLibraryPanelUnlinkModal } = model.useState();
const { sourcePanel } = vizManager.useState();
const libraryPanel = getLibraryPanel(sourcePanel.resolve());
const { controls } = dashboard.useState();
@ -88,11 +89,18 @@ function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
{showLibraryPanelSaveModal && libraryPanel && (
<SaveLibraryVizPanelModal
libraryPanel={libraryPanel}
onDismiss={model.onDismissLibraryPanelModal}
onDismiss={model.onDismissLibraryPanelSaveModal}
onConfirm={model.onConfirmSaveLibraryPanel}
onDiscard={model.onDiscard}
></SaveLibraryVizPanelModal>
)}
{showLibraryPanelUnlinkModal && libraryPanel && (
<UnlinkModal
onDismiss={model.onDismissUnlinkLibraryPanelModal}
onConfirm={model.onConfirmUnlinkLibraryPanel}
isOpen
/>
)}
{dataPane && (
<>
<div {...splitterProps} />

View File

@ -250,6 +250,40 @@ describe('VizPanelManager', () => {
expect(apiCall.mock.calls[0][0].state.panel?.state.title).toBe('new title');
});
it('unlinks library panel', () => {
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 libraryPanel = new LibraryVizPanel({
isLoaded: true,
title: libraryPanelModel.title,
uid: libraryPanelModel.uid,
name: libraryPanelModel.name,
panelKey: panel.state.key!,
panel: panel,
_loadedPanel: libraryPanelModel,
});
const gridItem = new SceneGridItem({ body: libraryPanel });
const panelManager = VizPanelManager.createFor(panel);
panelManager.unlinkLibraryPanel();
const sourcePanel = panelManager.state.sourcePanel.resolve();
expect(sourcePanel.parent?.state.key).toBe(gridItem.state.key);
});
});
describe('query options', () => {

View File

@ -338,6 +338,24 @@ export class VizPanelManager extends SceneObjectBase<VizPanelManagerState> {
});
}
public unlinkLibraryPanel() {
const sourcePanel = this.state.sourcePanel.resolve();
if (!(sourcePanel.parent instanceof LibraryVizPanel)) {
throw new Error('VizPanel is not a child of a library panel');
}
const gridItem = sourcePanel.parent.parent;
if (!(gridItem instanceof SceneGridItem)) {
throw new Error('Library panel not a child of a grid item');
}
const newSourcePanel = this.state.panel.clone({ $data: this.state.$data?.clone() });
gridItem.setState({
body: newSourcePanel,
});
this.setState({ sourcePanel: newSourcePanel.getRef() });
}
public commitChanges() {
const sourcePanel = this.state.sourcePanel.resolve();

View File

@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
@ -8,10 +8,11 @@ import { Button, ButtonGroup, Dropdown, Icon, Menu, ToolbarButton, ToolbarButton
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator';
import { contextSrv } from 'app/core/core';
import { t, Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
import { PanelEditor } from '../panel-edit/PanelEditor';
import { ShareModal } from '../sharing/ShareModal';
import { DashboardInteractions } from '../utils/interactions';
import { dynamicDashNavActions } from '../utils/registerDynamicDashNavAction';
@ -57,9 +58,9 @@ export function ToolbarActions({ dashboard }: Props) {
const buttonWithExtraMargin = useStyles2(getStyles);
const isEditingPanel = Boolean(editPanel);
const isViewingPanel = Boolean(viewPanelScene);
const isEditingLibraryPanel = Boolean(
editPanel?.state.vizManager.state.sourcePanel.resolve().parent instanceof LibraryVizPanel
);
const isEditingLibraryPanel = useEditingLibraryPanel(editPanel);
const hasCopiedPanel = Boolean(copiedPanel);
toolbarActions.push({
@ -383,6 +384,23 @@ export function ToolbarActions({ dashboard }: Props) {
),
});
toolbarActions.push({
group: 'main-buttons',
condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel,
render: () => (
<Button
onClick={editPanel?.onUnlinkLibraryPanel}
tooltip="Unlink library panel"
size="sm"
key="unlinkLibraryPanel"
fill="outline"
variant="secondary"
>
Unlink library panel
</Button>
),
});
toolbarActions.push({
group: 'main-buttons',
condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel,
@ -504,6 +522,25 @@ export function ToolbarActions({ dashboard }: Props) {
return actionElements;
}
function useEditingLibraryPanel(panelEditor?: PanelEditor) {
const [isEditingLibraryPanel, setEditingLibraryPanel] = useState<Boolean>(false);
useEffect(() => {
if (panelEditor) {
const unsub = panelEditor.state.vizManager.subscribeToState((vizManagerState) =>
setEditingLibraryPanel(vizManagerState.sourcePanel.resolve().parent instanceof LibraryVizPanel)
);
return () => {
unsub.unsubscribe();
};
}
setEditingLibraryPanel(false);
return;
}, [panelEditor]);
return isEditingLibraryPanel;
}
interface ToolbarAction {
group: string;
condition?: boolean | string;