diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.test.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.test.tsx
new file mode 100644
index 000000000000..38406ee22d5e
--- /dev/null
+++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.test.tsx
@@ -0,0 +1,96 @@
+import { render, waitFor, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+import { Provider } from 'react-redux';
+
+import { configureStore } from 'app/store/configureStore';
+
+import { DashboardModel } from '../../state';
+
+import { SaveDashboardDrawer } from './SaveDashboardDrawer';
+
+jest.mock('app/core/core', () => ({
+ ...jest.requireActual('app/core/core'),
+ contextSrv: {},
+}));
+
+jest.mock('@grafana/runtime', () => ({
+ ...jest.requireActual('@grafana/runtime'),
+ getBackendSrv: () => ({
+ post: mockPost,
+ }),
+}));
+
+jest.mock('app/core/services/backend_srv', () => ({
+ backendSrv: {
+ getDashboardByUid: jest.fn().mockResolvedValue({ dashboard: {} }),
+ },
+}));
+
+const store = configureStore();
+const mockPost = jest.fn();
+const buildMocks = () => ({
+ dashboard: new DashboardModel({
+ uid: 'mockDashboardUid',
+ version: 1,
+ }),
+ error: {
+ status: 412,
+ data: {
+ status: 'plugin-dashboard',
+ },
+ config: {},
+ },
+ onDismiss: jest.fn(),
+});
+
+interface CompProps {
+ dashboard: DashboardModel;
+ onDismiss: () => void;
+}
+const CompWithProvider = (props: CompProps) => (
+
+
+
+);
+
+const setup = (options: CompProps) => waitFor(() => render());
+
+describe('SaveDashboardDrawer', () => {
+ beforeEach(() => {
+ mockPost.mockClear();
+ jest.spyOn(console, 'error').mockImplementation();
+ });
+
+ it("renders a modal if there's an unhandled error", async () => {
+ const { onDismiss, dashboard, error } = buildMocks();
+ mockPost.mockRejectedValueOnce(error);
+
+ await setup({ dashboard, onDismiss });
+
+ await userEvent.click(screen.getByRole('button', { name: /save/i }));
+
+ expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /save as/i })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /overwrite/i })).toBeInTheDocument();
+ });
+
+ it('should render corresponding save modal once the errror is handled', async () => {
+ const { onDismiss, dashboard, error } = buildMocks();
+ mockPost.mockRejectedValueOnce(error);
+
+ const { rerender } = await setup({ dashboard, onDismiss });
+
+ await userEvent.click(screen.getByRole('button', { name: /save/i }));
+ rerender();
+
+ mockPost.mockClear();
+ mockPost.mockRejectedValueOnce({ ...error, isHandled: true });
+
+ await userEvent.click(screen.getByRole('button', { name: /save/i }));
+
+ expect(screen.getByText(/save dashboard/i)).toBeInTheDocument();
+ expect(screen.queryByRole('button', { name: /save as/i })).not.toBeInTheDocument();
+ expect(screen.queryByRole('button', { name: /overwrite/i })).not.toBeInTheDocument();
+ });
+});
diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx
index 6addf5d65a80..1d5adc2269e1 100644
--- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx
+++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx
@@ -126,7 +126,7 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop
);
};
- if (state.error && isFetchError(state.error)) {
+ if (state.error && isFetchError(state.error) && !state.error.isHandled) {
return (
{
const notifyApp = useAppNotification();
useEffect(() => {
- if (state.error) {
+ if (state.error && !state.loading) {
notifyApp.error(state.error.message ?? 'Error saving dashboard');
}
if (state.value) {