mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard scenes: Unlink library panel inside edit mode (#84355)
Unlink library panel inside edit mode
This commit is contained in:
parent
cf6bed7ae5
commit
0fe5b62fa5
@ -20,6 +20,7 @@ export interface PanelEditorState extends SceneObjectState {
|
|||||||
dataPane?: PanelDataPane;
|
dataPane?: PanelDataPane;
|
||||||
vizManager: VizPanelManager;
|
vizManager: VizPanelManager;
|
||||||
showLibraryPanelSaveModal?: boolean;
|
showLibraryPanelSaveModal?: boolean;
|
||||||
|
showLibraryPanelUnlinkModal?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PanelEditor extends SceneObjectBase<PanelEditorState> {
|
export class PanelEditor extends SceneObjectBase<PanelEditorState> {
|
||||||
@ -212,9 +213,22 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
|
|||||||
locationService.partial({ editPanel: null });
|
locationService.partial({ editPanel: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
public onDismissLibraryPanelModal = () => {
|
public onDismissLibraryPanelSaveModal = () => {
|
||||||
this.setState({ showLibraryPanelSaveModal: false });
|
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 {
|
export function buildPanelEditScene(panel: VizPanel): PanelEditor {
|
||||||
|
@ -6,6 +6,7 @@ import { SceneComponentProps } from '@grafana/scenes';
|
|||||||
import { Button, ToolbarButton, useStyles2 } from '@grafana/ui';
|
import { Button, ToolbarButton, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||||
|
import { UnlinkModal } from '../scene/UnlinkModal';
|
||||||
import { getDashboardSceneFor, getLibraryPanel } from '../utils/utils';
|
import { getDashboardSceneFor, getLibraryPanel } from '../utils/utils';
|
||||||
|
|
||||||
import { PanelEditor } from './PanelEditor';
|
import { PanelEditor } from './PanelEditor';
|
||||||
@ -58,7 +59,7 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
|
|||||||
|
|
||||||
function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
|
function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
|
||||||
const dashboard = getDashboardSceneFor(model);
|
const dashboard = getDashboardSceneFor(model);
|
||||||
const { vizManager, dataPane, showLibraryPanelSaveModal } = model.useState();
|
const { vizManager, dataPane, showLibraryPanelSaveModal, showLibraryPanelUnlinkModal } = model.useState();
|
||||||
const { sourcePanel } = vizManager.useState();
|
const { sourcePanel } = vizManager.useState();
|
||||||
const libraryPanel = getLibraryPanel(sourcePanel.resolve());
|
const libraryPanel = getLibraryPanel(sourcePanel.resolve());
|
||||||
const { controls } = dashboard.useState();
|
const { controls } = dashboard.useState();
|
||||||
@ -88,11 +89,18 @@ function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
|
|||||||
{showLibraryPanelSaveModal && libraryPanel && (
|
{showLibraryPanelSaveModal && libraryPanel && (
|
||||||
<SaveLibraryVizPanelModal
|
<SaveLibraryVizPanelModal
|
||||||
libraryPanel={libraryPanel}
|
libraryPanel={libraryPanel}
|
||||||
onDismiss={model.onDismissLibraryPanelModal}
|
onDismiss={model.onDismissLibraryPanelSaveModal}
|
||||||
onConfirm={model.onConfirmSaveLibraryPanel}
|
onConfirm={model.onConfirmSaveLibraryPanel}
|
||||||
onDiscard={model.onDiscard}
|
onDiscard={model.onDiscard}
|
||||||
></SaveLibraryVizPanelModal>
|
></SaveLibraryVizPanelModal>
|
||||||
)}
|
)}
|
||||||
|
{showLibraryPanelUnlinkModal && libraryPanel && (
|
||||||
|
<UnlinkModal
|
||||||
|
onDismiss={model.onDismissUnlinkLibraryPanelModal}
|
||||||
|
onConfirm={model.onConfirmUnlinkLibraryPanel}
|
||||||
|
isOpen
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{dataPane && (
|
{dataPane && (
|
||||||
<>
|
<>
|
||||||
<div {...splitterProps} />
|
<div {...splitterProps} />
|
||||||
|
@ -250,6 +250,40 @@ describe('VizPanelManager', () => {
|
|||||||
|
|
||||||
expect(apiCall.mock.calls[0][0].state.panel?.state.title).toBe('new title');
|
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', () => {
|
describe('query options', () => {
|
||||||
|
@ -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() {
|
public commitChanges() {
|
||||||
const sourcePanel = this.state.sourcePanel.resolve();
|
const sourcePanel = this.state.sourcePanel.resolve();
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
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 { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
|
||||||
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator';
|
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator';
|
||||||
import { contextSrv } from 'app/core/core';
|
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 { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||||
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
|
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
|
||||||
|
|
||||||
|
import { PanelEditor } from '../panel-edit/PanelEditor';
|
||||||
import { ShareModal } from '../sharing/ShareModal';
|
import { ShareModal } from '../sharing/ShareModal';
|
||||||
import { DashboardInteractions } from '../utils/interactions';
|
import { DashboardInteractions } from '../utils/interactions';
|
||||||
import { dynamicDashNavActions } from '../utils/registerDynamicDashNavAction';
|
import { dynamicDashNavActions } from '../utils/registerDynamicDashNavAction';
|
||||||
@ -57,9 +58,9 @@ export function ToolbarActions({ dashboard }: Props) {
|
|||||||
const buttonWithExtraMargin = useStyles2(getStyles);
|
const buttonWithExtraMargin = useStyles2(getStyles);
|
||||||
const isEditingPanel = Boolean(editPanel);
|
const isEditingPanel = Boolean(editPanel);
|
||||||
const isViewingPanel = Boolean(viewPanelScene);
|
const isViewingPanel = Boolean(viewPanelScene);
|
||||||
const isEditingLibraryPanel = Boolean(
|
|
||||||
editPanel?.state.vizManager.state.sourcePanel.resolve().parent instanceof LibraryVizPanel
|
const isEditingLibraryPanel = useEditingLibraryPanel(editPanel);
|
||||||
);
|
|
||||||
const hasCopiedPanel = Boolean(copiedPanel);
|
const hasCopiedPanel = Boolean(copiedPanel);
|
||||||
|
|
||||||
toolbarActions.push({
|
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({
|
toolbarActions.push({
|
||||||
group: 'main-buttons',
|
group: 'main-buttons',
|
||||||
condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel,
|
condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel,
|
||||||
@ -504,6 +522,25 @@ export function ToolbarActions({ dashboard }: Props) {
|
|||||||
return actionElements;
|
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 {
|
interface ToolbarAction {
|
||||||
group: string;
|
group: string;
|
||||||
condition?: boolean | string;
|
condition?: boolean | string;
|
||||||
|
Loading…
Reference in New Issue
Block a user