mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard scenes: Editing library panels. (#83223)
* wip * Refactor find panel by key * clean up lint, make isLoading optional * change library panel so that the dashboard key is attached to the panel instead of the library panel * do not reload everything when the library panel is already loaded * Progress on library panel options in options pane * We can skip building the edit scene until we have the library panel loaded * undo changes to findLibraryPanelbyKey, changes not necessary when the panel has the findable id instead of the library panel * fix undo * make sure the save model gets the id from the panel and not the library panel * remove non necessary links and data providers from dummy loading panel * change library panel so that the dashboard key is attached to the panel instead of the library panel * make sure the save model gets the id from the panel and not the library panel * do not reload everything when the library panel is already loaded * Fix merge issue * Clean up * lint cleanup * wip saving * working save * use title from panel model * move library panel api functions * fix issue from merge * Add confirm save modal. Update library panel to response from save request. Add library panel information box to panel options * Better naming * Remove library panel from viz panel state, use sourcePanel.parent instead. Fix edited by time formatting * Add tests for editing library panels * implement changed from review feedback * minor refactor from feedback
This commit is contained in:
@@ -14,7 +14,7 @@ import appEvents from 'app/core/app_events';
|
||||
import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer';
|
||||
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
|
||||
import { createDashboardEditViewFor } from '../settings/utils';
|
||||
import { findVizPanelByKey, getDashboardSceneFor, isLibraryPanelChild, isPanelClone } from '../utils/utils';
|
||||
import { findVizPanelByKey, getDashboardSceneFor, getLibraryPanel, isPanelClone } from '../utils/utils';
|
||||
|
||||
import { DashboardScene, DashboardSceneState } from './DashboardScene';
|
||||
import { LibraryVizPanel } from './LibraryVizPanel';
|
||||
@@ -66,7 +66,7 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLibraryPanelChild(panel)) {
|
||||
if (getLibraryPanel(panel)) {
|
||||
this._handleLibraryPanel(panel, (p) => {
|
||||
if (p.state.key === undefined) {
|
||||
// Inspect drawer require a panel key to be set
|
||||
@@ -105,7 +105,7 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLibraryPanelChild(panel)) {
|
||||
if (getLibraryPanel(panel)) {
|
||||
this._handleLibraryPanel(panel, (p) => this._buildLibraryPanelViewScene(p));
|
||||
return;
|
||||
}
|
||||
@@ -119,6 +119,7 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
|
||||
if (typeof values.editPanel === 'string') {
|
||||
const panel = findVizPanelByKey(this._scene, values.editPanel);
|
||||
if (!panel) {
|
||||
console.warn(`Panel ${values.editPanel} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,10 +127,11 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
|
||||
if (!isEditing) {
|
||||
this._scene.onEnterEditMode();
|
||||
}
|
||||
if (isLibraryPanelChild(panel)) {
|
||||
if (getLibraryPanel(panel)) {
|
||||
this._handleLibraryPanel(panel, (p) => {
|
||||
this._scene.setState({ editPanel: buildPanelEditScene(p) });
|
||||
});
|
||||
return;
|
||||
}
|
||||
update.editPanel = buildPanelEditScene(panel);
|
||||
} else if (editPanel && values.editPanel === null) {
|
||||
|
||||
@@ -51,64 +51,67 @@ export class LibraryVizPanel extends SceneObjectBase<LibraryVizPanelState> {
|
||||
}
|
||||
};
|
||||
|
||||
public setPanelFromLibPanel(libPanel: LibraryPanel) {
|
||||
if (this.state._loadedPanel?.version === libPanel.version) {
|
||||
return;
|
||||
}
|
||||
|
||||
const libPanelModel = new PanelModel(libPanel.model);
|
||||
|
||||
const vizPanelState: VizPanelState = {
|
||||
title: libPanelModel.title,
|
||||
key: this.state.panelKey,
|
||||
options: libPanelModel.options ?? {},
|
||||
fieldConfig: libPanelModel.fieldConfig,
|
||||
pluginId: libPanelModel.type,
|
||||
pluginVersion: libPanelModel.pluginVersion,
|
||||
displayMode: libPanelModel.transparent ? 'transparent' : undefined,
|
||||
description: libPanelModel.description,
|
||||
$data: createPanelDataProvider(libPanelModel),
|
||||
menu: new VizPanelMenu({ $behaviors: [panelMenuBehavior] }),
|
||||
titleItems: [
|
||||
new VizPanelLinks({
|
||||
rawLinks: libPanelModel.links,
|
||||
menu: new VizPanelLinksMenu({ $behaviors: [panelLinksBehavior] }),
|
||||
}),
|
||||
new PanelNotices(),
|
||||
],
|
||||
};
|
||||
|
||||
const panel = new VizPanel(vizPanelState);
|
||||
const gridItem = this.parent;
|
||||
|
||||
if (libPanelModel.repeat && gridItem instanceof SceneGridItem && gridItem.parent instanceof SceneGridLayout) {
|
||||
this._parent = undefined;
|
||||
const repeater = new PanelRepeaterGridItem({
|
||||
key: gridItem.state.key,
|
||||
x: gridItem.state.x,
|
||||
y: gridItem.state.y,
|
||||
width: libPanelModel.repeatDirection === 'h' ? 24 : gridItem.state.width,
|
||||
height: gridItem.state.height,
|
||||
itemHeight: gridItem.state.height,
|
||||
source: this,
|
||||
variableName: libPanelModel.repeat,
|
||||
repeatedPanels: [],
|
||||
repeatDirection: libPanelModel.repeatDirection === 'h' ? 'h' : 'v',
|
||||
maxPerRow: libPanelModel.maxPerRow,
|
||||
});
|
||||
gridItem.parent.setState({
|
||||
children: gridItem.parent.state.children.map((child) =>
|
||||
child.state.key === gridItem.state.key ? repeater : child
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ panel, _loadedPanel: libPanel, isLoaded: true, name: libPanel.name });
|
||||
}
|
||||
|
||||
private async loadLibraryPanelFromPanelModel() {
|
||||
let vizPanel = this.state.panel!;
|
||||
|
||||
try {
|
||||
const libPanel = await getLibraryPanel(this.state.uid, true);
|
||||
|
||||
if (this.state._loadedPanel?.version === libPanel.version) {
|
||||
return;
|
||||
}
|
||||
|
||||
const libPanelModel = new PanelModel(libPanel.model);
|
||||
|
||||
const vizPanelState: VizPanelState = {
|
||||
title: this.state.title,
|
||||
key: this.state.panelKey,
|
||||
options: libPanelModel.options ?? {},
|
||||
fieldConfig: libPanelModel.fieldConfig,
|
||||
pluginId: libPanelModel.type,
|
||||
pluginVersion: libPanelModel.pluginVersion,
|
||||
displayMode: libPanelModel.transparent ? 'transparent' : undefined,
|
||||
description: libPanelModel.description,
|
||||
$data: createPanelDataProvider(libPanelModel),
|
||||
menu: new VizPanelMenu({ $behaviors: [panelMenuBehavior] }),
|
||||
titleItems: [
|
||||
new VizPanelLinks({
|
||||
rawLinks: libPanelModel.links,
|
||||
menu: new VizPanelLinksMenu({ $behaviors: [panelLinksBehavior] }),
|
||||
}),
|
||||
new PanelNotices(),
|
||||
],
|
||||
};
|
||||
|
||||
const panel = new VizPanel(vizPanelState);
|
||||
const gridItem = this.parent;
|
||||
|
||||
if (libPanelModel.repeat && gridItem instanceof SceneGridItem && gridItem.parent instanceof SceneGridLayout) {
|
||||
this._parent = undefined;
|
||||
const repeater = new PanelRepeaterGridItem({
|
||||
key: gridItem.state.key,
|
||||
x: gridItem.state.x,
|
||||
y: gridItem.state.y,
|
||||
width: libPanelModel.repeatDirection === 'h' ? 24 : gridItem.state.width,
|
||||
height: gridItem.state.height,
|
||||
itemHeight: gridItem.state.height,
|
||||
source: this,
|
||||
variableName: libPanelModel.repeat,
|
||||
repeatedPanels: [],
|
||||
repeatDirection: libPanelModel.repeatDirection === 'h' ? 'h' : 'v',
|
||||
maxPerRow: libPanelModel.maxPerRow,
|
||||
});
|
||||
gridItem.parent.setState({
|
||||
children: gridItem.parent.state.children.map((child) =>
|
||||
child.state.key === gridItem.state.key ? repeater : child
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ panel, _loadedPanel: libPanel, isLoaded: true });
|
||||
this.setPanelFromLibPanel(libPanel);
|
||||
} catch (err) {
|
||||
vizPanel.setState({
|
||||
_pluginLoadError: `Unable to load library panel: ${this.state.uid}`,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { dynamicDashNavActions } from '../utils/registerDynamicDashNavAction';
|
||||
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { GoToSnapshotOriginButton } from './GoToSnapshotOriginButton';
|
||||
import { LibraryVizPanel } from './LibraryVizPanel';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardScene;
|
||||
@@ -56,6 +57,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 hasCopiedPanel = Boolean(copiedPanel);
|
||||
|
||||
toolbarActions.push({
|
||||
@@ -233,7 +237,7 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||
|
||||
toolbarActions.push({
|
||||
group: 'back-button',
|
||||
condition: isViewingPanel || isEditingPanel,
|
||||
condition: (isViewingPanel || isEditingPanel) && !isEditingLibraryPanel,
|
||||
render: () => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -347,7 +351,7 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||
|
||||
toolbarActions.push({
|
||||
group: 'main-buttons',
|
||||
condition: isEditingPanel && !editview && !meta.isNew && !isViewingPanel,
|
||||
condition: isEditingPanel && !isEditingLibraryPanel && !editview && !meta.isNew && !isViewingPanel,
|
||||
render: () => (
|
||||
<Button
|
||||
onClick={editPanel?.onDiscard}
|
||||
@@ -364,7 +368,41 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||
|
||||
toolbarActions.push({
|
||||
group: 'main-buttons',
|
||||
condition: isEditing && (meta.canSave || canSaveAs),
|
||||
condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel,
|
||||
render: () => (
|
||||
<Button
|
||||
onClick={editPanel?.onDiscard}
|
||||
tooltip="Discard library panel changes"
|
||||
size="sm"
|
||||
key="discardLibraryPanel"
|
||||
fill="outline"
|
||||
variant="destructive"
|
||||
>
|
||||
Discard library panel changes
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
|
||||
toolbarActions.push({
|
||||
group: 'main-buttons',
|
||||
condition: isEditingPanel && isEditingLibraryPanel && !editview && !isViewingPanel,
|
||||
render: () => (
|
||||
<Button
|
||||
onClick={editPanel?.onSaveLibraryPanel}
|
||||
tooltip="Save library panel"
|
||||
size="sm"
|
||||
key="saveLibraryPanel"
|
||||
fill="outline"
|
||||
variant="primary"
|
||||
>
|
||||
Save library panel
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
|
||||
toolbarActions.push({
|
||||
group: 'main-buttons',
|
||||
condition: isEditing && !isEditingLibraryPanel && (meta.canSave || canSaveAs),
|
||||
render: () => {
|
||||
// if we only can save
|
||||
if (meta.isNew) {
|
||||
|
||||
@@ -43,7 +43,6 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
|
||||
const items: PanelMenuItem[] = [];
|
||||
const moreSubMenu: PanelMenuItem[] = [];
|
||||
const panelId = getPanelIdForVizPanel(panel);
|
||||
const dashboard = getDashboardSceneFor(panel);
|
||||
const { isEmbedded } = dashboard.state.meta;
|
||||
const exploreMenuItem = await getExploreMenuItem(panel);
|
||||
@@ -72,7 +71,7 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
|
||||
iconClassName: 'eye',
|
||||
shortcut: 'e',
|
||||
onClick: () => DashboardInteractions.panelMenuItemClicked('edit'),
|
||||
href: getEditPanelUrl(panelId),
|
||||
href: getEditPanelUrl(getPanelIdForVizPanel(panel)),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user