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:
Oscar Kilhed
2024-03-11 20:48:27 +01:00
committed by GitHub
parent efbcd53119
commit 0b2640e9ff
16 changed files with 596 additions and 81 deletions

View File

@@ -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) {

View File

@@ -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}`,

View File

@@ -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) {

View File

@@ -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)),
});
}