mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: Discard the entire panel if it is newly added (#87562)
This commit is contained in:
parent
c0881cc970
commit
66950c96f6
@ -1491,6 +1491,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "3"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "4"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/scene/NavToolbarActions.test.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
|
@ -59,6 +59,63 @@ describe('PanelEditor', () => {
|
||||
const updatedPanel = gridItem.state.body as VizPanel;
|
||||
expect(updatedPanel?.state.title).toBe('changed title');
|
||||
});
|
||||
|
||||
it('should discard changes when unmounted and discard changes is marked as true', () => {
|
||||
pluginToLoad = getTestPanelPlugin({ id: 'text', skipDataQuery: true });
|
||||
|
||||
const panel = new VizPanel({
|
||||
key: 'panel-1',
|
||||
pluginId: 'text',
|
||||
});
|
||||
|
||||
const gridItem = new DashboardGridItem({ body: panel });
|
||||
|
||||
const editScene = buildPanelEditScene(panel);
|
||||
const scene = new DashboardScene({
|
||||
editPanel: editScene,
|
||||
isEditing: true,
|
||||
body: new SceneGridLayout({
|
||||
children: [gridItem],
|
||||
}),
|
||||
});
|
||||
|
||||
const deactivate = activateFullSceneTree(scene);
|
||||
|
||||
editScene.state.vizManager.state.panel.setState({ title: 'changed title' });
|
||||
|
||||
editScene.onDiscard();
|
||||
deactivate();
|
||||
|
||||
const updatedPanel = gridItem.state.body as VizPanel;
|
||||
expect(updatedPanel?.state.title).toBe(panel.state.title);
|
||||
});
|
||||
|
||||
it('should discard a newly added panel', () => {
|
||||
pluginToLoad = getTestPanelPlugin({ id: 'text', skipDataQuery: true });
|
||||
|
||||
const panel = new VizPanel({
|
||||
key: 'panel-1',
|
||||
pluginId: 'text',
|
||||
});
|
||||
|
||||
const gridItem = new DashboardGridItem({ body: panel });
|
||||
|
||||
const editScene = buildPanelEditScene(panel, true);
|
||||
const scene = new DashboardScene({
|
||||
editPanel: editScene,
|
||||
isEditing: true,
|
||||
body: new SceneGridLayout({
|
||||
children: [gridItem],
|
||||
}),
|
||||
});
|
||||
|
||||
editScene.onDiscard();
|
||||
const deactivate = activateFullSceneTree(scene);
|
||||
|
||||
deactivate();
|
||||
|
||||
expect((scene.state.body as SceneGridLayout).state.children.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Handling library panels', () => {
|
||||
|
@ -14,6 +14,7 @@ import { PanelOptionsPane } from './PanelOptionsPane';
|
||||
import { VizPanelManager, VizPanelManagerState } from './VizPanelManager';
|
||||
|
||||
export interface PanelEditorState extends SceneObjectState {
|
||||
isNewPanel: boolean;
|
||||
isDirty?: boolean;
|
||||
panelId: number;
|
||||
optionsPane: PanelOptionsPane;
|
||||
@ -59,6 +60,8 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
|
||||
return () => {
|
||||
if (!this._discardChanges) {
|
||||
this.commitChanges();
|
||||
} else if (this.state.isNewPanel) {
|
||||
getDashboardSceneFor(this).removePanel(panelManager.state.sourcePanel.resolve()!);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -173,10 +176,11 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildPanelEditScene(panel: VizPanel): PanelEditor {
|
||||
export function buildPanelEditScene(panel: VizPanel, isNewPanel = false): PanelEditor {
|
||||
return new PanelEditor({
|
||||
panelId: getPanelIdForVizPanel(panel),
|
||||
optionsPane: new PanelOptionsPane({}),
|
||||
vizManager: VizPanelManager.createFor(panel),
|
||||
isNewPanel,
|
||||
});
|
||||
}
|
||||
|
@ -779,7 +779,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
return getPanelIdForVizPanel(row);
|
||||
}
|
||||
|
||||
public onCreateNewPanel(): number {
|
||||
public onCreateNewPanel(): VizPanel {
|
||||
if (!this.state.isEditing) {
|
||||
this.onEnterEditMode();
|
||||
}
|
||||
@ -788,7 +788,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
|
||||
this.addPanel(vizPanel);
|
||||
|
||||
return getPanelIdForVizPanel(vizPanel);
|
||||
return vizPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,6 +124,7 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
|
||||
// Handle edit panel state
|
||||
if (typeof values.editPanel === 'string') {
|
||||
const panel = findVizPanelByKey(this._scene, values.editPanel);
|
||||
|
||||
if (!panel) {
|
||||
console.warn(`Panel ${values.editPanel} not found`);
|
||||
return;
|
||||
@ -144,6 +145,7 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
update.editPanel = buildPanelEditScene(panel);
|
||||
} else if (editPanel && values.editPanel === null) {
|
||||
update.editPanel = undefined;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { screen, render } from '@testing-library/react';
|
||||
import { screen, render, act } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { TestProvider } from 'test/helpers/TestProvider';
|
||||
@ -6,11 +6,13 @@ import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SceneGridLayout, SceneQueryRunner, SceneTimeRange, VizPanel } from '@grafana/scenes';
|
||||
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
|
||||
|
||||
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||
import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel';
|
||||
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
|
||||
|
||||
import { DashboardGridItem } from './DashboardGridItem';
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { ToolbarActions } from './NavToolbarActions';
|
||||
|
||||
jest.mock('app/features/playlist/PlaylistSrv', () => ({
|
||||
@ -25,6 +27,17 @@ jest.mock('app/features/playlist/PlaylistSrv', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual<Record<string, any>>('@grafana/runtime'),
|
||||
getDataSourceSrv: () => ({
|
||||
get: jest.fn(),
|
||||
getInstanceSettings: jest.fn().mockReturnValue({
|
||||
uid: 'datasource-uid',
|
||||
name: 'datasource-name',
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('NavToolbarActions', () => {
|
||||
describe('Given an already saved dashboard', () => {
|
||||
it('Should show correct buttons when not in editing', async () => {
|
||||
@ -90,8 +103,9 @@ describe('NavToolbarActions', () => {
|
||||
});
|
||||
|
||||
it('Should show correct buttons when in settings menu', async () => {
|
||||
setup();
|
||||
const { dashboard } = setup();
|
||||
|
||||
dashboard.startUrlSync();
|
||||
await userEvent.click(await screen.findByText('Edit'));
|
||||
await userEvent.click(await screen.findByText('Settings'));
|
||||
|
||||
@ -101,6 +115,35 @@ describe('NavToolbarActions', () => {
|
||||
expect(screen.queryByText(selectors.pages.Dashboard.DashNav.playlistControls.stop)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(selectors.pages.Dashboard.DashNav.playlistControls.next)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should show correct buttons when editing a new panel', async () => {
|
||||
const { dashboard } = setup();
|
||||
await act(() => {
|
||||
dashboard.onEnterEditMode();
|
||||
const editingPanel = ((dashboard.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state
|
||||
.body as VizPanel;
|
||||
dashboard.setState({ editPanel: buildPanelEditScene(editingPanel, true) });
|
||||
});
|
||||
|
||||
expect(await screen.findByText('Save dashboard')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Discard panel')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Back to dashboard')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should show correct buttons when editing an existing panel', async () => {
|
||||
const { dashboard } = setup();
|
||||
|
||||
await act(() => {
|
||||
dashboard.onEnterEditMode();
|
||||
const editingPanel = ((dashboard.state.body as SceneGridLayout).state.children[0] as DashboardGridItem).state
|
||||
.body as VizPanel;
|
||||
dashboard.setState({ editPanel: buildPanelEditScene(editingPanel) });
|
||||
});
|
||||
|
||||
expect(await screen.findByText('Save dashboard')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Discard panel changes')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Back to dashboard')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Given new sharing button', () => {
|
||||
@ -115,40 +158,51 @@ describe('NavToolbarActions', () => {
|
||||
config.featureToggles.newDashboardSharingComponent = true;
|
||||
setup();
|
||||
|
||||
expect(screen.queryByTestId(selectors.pages.Dashboard.DashNav.shareButton)).not.toBeInTheDocument();
|
||||
expect(await screen.queryByTestId(selectors.pages.Dashboard.DashNav.shareButton)).not.toBeInTheDocument();
|
||||
const newShareButton = screen.getByTestId(selectors.pages.Dashboard.DashNav.newShareButton.container);
|
||||
expect(newShareButton).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let cleanUp = () => {};
|
||||
|
||||
function setup() {
|
||||
const dashboard = transformSaveModelToScene({
|
||||
dashboard: {
|
||||
title: 'hello',
|
||||
uid: 'my-uid',
|
||||
schemaVersion: 30,
|
||||
panels: [],
|
||||
version: 10,
|
||||
},
|
||||
const dashboard = new DashboardScene({
|
||||
$timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }),
|
||||
meta: {
|
||||
canEdit: true,
|
||||
isNew: false,
|
||||
canMakeEditable: true,
|
||||
canSave: true,
|
||||
canShare: true,
|
||||
canStar: true,
|
||||
canAdmin: true,
|
||||
canDelete: true,
|
||||
},
|
||||
title: 'hello',
|
||||
uid: 'dash-1',
|
||||
body: new SceneGridLayout({
|
||||
children: [
|
||||
new DashboardGridItem({
|
||||
key: 'griditem-1',
|
||||
x: 0,
|
||||
body: new VizPanel({
|
||||
title: 'Panel A',
|
||||
key: 'panel-1',
|
||||
pluginId: 'table',
|
||||
$data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
|
||||
}),
|
||||
}),
|
||||
new DashboardGridItem({
|
||||
body: new VizPanel({
|
||||
title: 'Panel B',
|
||||
key: 'panel-2',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
// Clear any data layers
|
||||
dashboard.setState({ $data: undefined });
|
||||
|
||||
const initialSaveModel = transformSceneToSaveModel(dashboard);
|
||||
dashboard.setInitialSaveModel(initialSaveModel);
|
||||
|
||||
dashboard.startUrlSync();
|
||||
|
||||
cleanUp();
|
||||
cleanUp = dashboard.activate();
|
||||
|
||||
const context = getGrafanaContextMock();
|
||||
|
||||
render(
|
||||
|
@ -22,7 +22,7 @@ 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 { PanelEditor, buildPanelEditScene } from '../panel-edit/PanelEditor';
|
||||
import ShareButton from '../sharing/ShareButton/ShareButton';
|
||||
import { ShareModal } from '../sharing/ShareModal';
|
||||
import { DashboardInteractions } from '../utils/interactions';
|
||||
@ -170,9 +170,9 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||
testId={selectors.pages.AddDashboard.itemButton('Add new visualization menu item')}
|
||||
label={t('dashboard.add-menu.visualization', 'Visualization')}
|
||||
onClick={() => {
|
||||
const id = dashboard.onCreateNewPanel();
|
||||
const vizPanel = dashboard.onCreateNewPanel();
|
||||
DashboardInteractions.toolbarAddButtonClicked({ item: 'add_visualization' });
|
||||
locationService.partial({ editPanel: id });
|
||||
dashboard.setState({ editPanel: buildPanelEditScene(vizPanel, true) });
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
@ -415,11 +415,11 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||
|
||||
toolbarActions.push({
|
||||
group: 'main-buttons',
|
||||
condition: isEditingPanel && !isEditingLibraryPanel && !editview && !meta.isNew && !isViewingPanel,
|
||||
condition: isEditingPanel && !isEditingLibraryPanel && !editview && !isViewingPanel,
|
||||
render: () => (
|
||||
<Button
|
||||
onClick={editPanel?.onDiscard}
|
||||
tooltip="Discard panel changes"
|
||||
tooltip={editPanel?.state.isNewPanel ? 'Discard panel' : 'Discard panel changes'}
|
||||
size="sm"
|
||||
disabled={!isEditedPanelDirty || !isDirty}
|
||||
key="discard"
|
||||
@ -427,7 +427,7 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||
variant="destructive"
|
||||
data-testid={selectors.components.NavToolbar.editDashboard.discardChangesButton}
|
||||
>
|
||||
Discard panel changes
|
||||
{editPanel?.state.isNewPanel ? 'Discard panel' : 'Discard panel changes'}
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
|
@ -76,6 +76,7 @@ it('creates new visualization when clicked Add visualization', () => {
|
||||
|
||||
expect(reportInteraction).toHaveBeenCalledWith('dashboards_emptydashboard_clicked', { item: 'add_visualization' });
|
||||
expect(locationService.partial).toHaveBeenCalled();
|
||||
expect(locationService.partial).toHaveBeenCalledWith({ editPanel: undefined, firstPanel: true });
|
||||
expect(onCreateNewPanel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
onCreateNewPanel,
|
||||
onImportDashboard,
|
||||
} from 'app/features/dashboard/utils/dashboard';
|
||||
import { buildPanelEditScene } from 'app/features/dashboard-scene/panel-edit/PanelEditor';
|
||||
import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene';
|
||||
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';
|
||||
import { useDispatch, useSelector } from 'app/types';
|
||||
@ -31,13 +32,15 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
|
||||
const onAddVisualization = () => {
|
||||
let id;
|
||||
if (dashboard instanceof DashboardScene) {
|
||||
id = dashboard.onCreateNewPanel();
|
||||
const panel = dashboard.onCreateNewPanel();
|
||||
dashboard.setState({ editPanel: buildPanelEditScene(panel, true) });
|
||||
locationService.partial({ firstPanel: true });
|
||||
} else {
|
||||
id = onCreateNewPanel(dashboard, initialDatasource);
|
||||
dispatch(setInitialDatasource(undefined));
|
||||
locationService.partial({ editPanel: id, firstPanel: true });
|
||||
}
|
||||
|
||||
locationService.partial({ editPanel: id, firstPanel: true });
|
||||
DashboardInteractions.emptyDashboardButtonClicked({ item: 'add_visualization' });
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user