mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: Keep save drawer open for unhandled errors (#70434)
* user essentials mob! 🔱 * user essentials mob! 🔱 lastFile:public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx * user essentials mob! 🔱 lastFile:public/app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer.tsx * user essentials mob! 🔱 lastFile:public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx * rename isHandledError fn * fix prettier --------- Co-authored-by: Joao Silva <joao.silva@grafana.com> Co-authored-by: Laura Benz <laura.benz@grafana.com> Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
This commit is contained in:
parent
ac78146091
commit
b1e76a7036
@ -2,14 +2,14 @@ import React, { useMemo, useState } from 'react';
|
|||||||
import { useAsync } from 'react-use';
|
import { useAsync } from 'react-use';
|
||||||
|
|
||||||
import { config, isFetchError } from '@grafana/runtime';
|
import { config, isFetchError } from '@grafana/runtime';
|
||||||
import { Drawer, Spinner, Tab, TabsBar } from '@grafana/ui';
|
import { Drawer, Tab, TabsBar } from '@grafana/ui';
|
||||||
import { backendSrv } from 'app/core/services/backend_srv';
|
import { backendSrv } from 'app/core/services/backend_srv';
|
||||||
|
|
||||||
import { jsonDiff } from '../VersionHistory/utils';
|
import { jsonDiff } from '../VersionHistory/utils';
|
||||||
|
|
||||||
import DashboardValidation from './DashboardValidation';
|
import DashboardValidation from './DashboardValidation';
|
||||||
import { SaveDashboardDiff } from './SaveDashboardDiff';
|
import { SaveDashboardDiff } from './SaveDashboardDiff';
|
||||||
import { SaveDashboardErrorProxy } from './SaveDashboardErrorProxy';
|
import { proxyHandlesError, SaveDashboardErrorProxy } from './SaveDashboardErrorProxy';
|
||||||
import { SaveDashboardAsForm } from './forms/SaveDashboardAsForm';
|
import { SaveDashboardAsForm } from './forms/SaveDashboardAsForm';
|
||||||
import { SaveDashboardForm } from './forms/SaveDashboardForm';
|
import { SaveDashboardForm } from './forms/SaveDashboardForm';
|
||||||
import { SaveProvisionedDashboardForm } from './forms/SaveProvisionedDashboardForm';
|
import { SaveProvisionedDashboardForm } from './forms/SaveProvisionedDashboardForm';
|
||||||
@ -72,18 +72,11 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop
|
|||||||
return <SaveDashboardDiff diff={data.diff} oldValue={previous.value} newValue={data.clone} />;
|
return <SaveDashboardDiff diff={data.diff} oldValue={previous.value} newValue={data.clone} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.loading) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNew || isCopy) {
|
if (isNew || isCopy) {
|
||||||
return (
|
return (
|
||||||
<SaveDashboardAsForm
|
<SaveDashboardAsForm
|
||||||
dashboard={dashboard}
|
dashboard={dashboard}
|
||||||
|
isLoading={state.loading}
|
||||||
onCancel={onDismiss}
|
onCancel={onDismiss}
|
||||||
onSuccess={onSuccess}
|
onSuccess={onSuccess}
|
||||||
onSubmit={onDashboardSave}
|
onSubmit={onDashboardSave}
|
||||||
@ -99,6 +92,7 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop
|
|||||||
return (
|
return (
|
||||||
<SaveDashboardForm
|
<SaveDashboardForm
|
||||||
dashboard={dashboard}
|
dashboard={dashboard}
|
||||||
|
isLoading={state.loading}
|
||||||
saveModel={data}
|
saveModel={data}
|
||||||
onCancel={onDismiss}
|
onCancel={onDismiss}
|
||||||
onSuccess={onSuccess}
|
onSuccess={onSuccess}
|
||||||
@ -109,7 +103,12 @@ export const SaveDashboardDrawer = ({ dashboard, onDismiss, onSaveSuccess, isCop
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (state.error && isFetchError(state.error) && !state.error.isHandled) {
|
if (
|
||||||
|
state.error &&
|
||||||
|
isFetchError(state.error) &&
|
||||||
|
!state.error.isHandled &&
|
||||||
|
proxyHandlesError(state.error.data.status)
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<SaveDashboardErrorProxy
|
<SaveDashboardErrorProxy
|
||||||
error={state.error}
|
error={state.error}
|
||||||
|
@ -28,7 +28,7 @@ export const SaveDashboardErrorProxy = ({
|
|||||||
const { onDashboardSave } = useDashboardSave(dashboard);
|
const { onDashboardSave } = useDashboardSave(dashboard);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error.data && isHandledError(error.data.status)) {
|
if (error.data && proxyHandlesError(error.data.status)) {
|
||||||
error.isHandled = true;
|
error.isHandled = true;
|
||||||
}
|
}
|
||||||
}, [error]);
|
}, [error]);
|
||||||
@ -109,7 +109,7 @@ const ConfirmPluginDashboardSaveModal = ({ onDismiss, dashboard }: SaveDashboard
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isHandledError = (errorStatus: string) => {
|
export const proxyHandlesError = (errorStatus: string) => {
|
||||||
switch (errorStatus) {
|
switch (errorStatus) {
|
||||||
case 'version-mismatch':
|
case 'version-mismatch':
|
||||||
case 'name-exists':
|
case 'name-exists':
|
||||||
|
@ -37,6 +37,7 @@ const renderAndSubmitForm = async (
|
|||||||
) => {
|
) => {
|
||||||
render(
|
render(
|
||||||
<SaveDashboardAsForm
|
<SaveDashboardAsForm
|
||||||
|
isLoading={false}
|
||||||
dashboard={dashboard as DashboardModel}
|
dashboard={dashboard as DashboardModel}
|
||||||
onCancel={() => {}}
|
onCancel={() => {}}
|
||||||
onSuccess={() => {}}
|
onSuccess={() => {}}
|
||||||
|
@ -40,7 +40,14 @@ export interface SaveDashboardAsFormProps extends SaveDashboardFormProps {
|
|||||||
isNew?: boolean;
|
isNew?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SaveDashboardAsForm = ({ dashboard, isNew, onSubmit, onCancel, onSuccess }: SaveDashboardAsFormProps) => {
|
export const SaveDashboardAsForm = ({
|
||||||
|
dashboard,
|
||||||
|
isLoading,
|
||||||
|
isNew,
|
||||||
|
onSubmit,
|
||||||
|
onCancel,
|
||||||
|
onSuccess,
|
||||||
|
}: SaveDashboardAsFormProps) => {
|
||||||
const defaultValues: SaveDashboardAsFormDTO = {
|
const defaultValues: SaveDashboardAsFormDTO = {
|
||||||
title: isNew ? dashboard.title : `${dashboard.title} Copy`,
|
title: isNew ? dashboard.title : `${dashboard.title} Copy`,
|
||||||
$folder: {
|
$folder: {
|
||||||
@ -129,8 +136,8 @@ export const SaveDashboardAsForm = ({ dashboard, isNew, onSubmit, onCancel, onSu
|
|||||||
<Button type="button" variant="secondary" onClick={onCancel} fill="outline">
|
<Button type="button" variant="secondary" onClick={onCancel} fill="outline">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" aria-label="Save dashboard button">
|
<Button disabled={isLoading} type="submit" aria-label="Save dashboard button">
|
||||||
Save
|
{isLoading ? 'Saving...' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
</>
|
</>
|
||||||
|
@ -34,6 +34,7 @@ const prepareDashboardMock = (
|
|||||||
const renderAndSubmitForm = async (dashboard: DashboardModel, submitSpy: jest.Mock) => {
|
const renderAndSubmitForm = async (dashboard: DashboardModel, submitSpy: jest.Mock) => {
|
||||||
render(
|
render(
|
||||||
<SaveDashboardForm
|
<SaveDashboardForm
|
||||||
|
isLoading={false}
|
||||||
dashboard={dashboard}
|
dashboard={dashboard}
|
||||||
onCancel={() => {}}
|
onCancel={() => {}}
|
||||||
onSuccess={() => {}}
|
onSuccess={() => {}}
|
||||||
@ -62,6 +63,7 @@ describe('SaveDashboardAsForm', () => {
|
|||||||
it('renders switches when variables or timerange', () => {
|
it('renders switches when variables or timerange', () => {
|
||||||
render(
|
render(
|
||||||
<SaveDashboardForm
|
<SaveDashboardForm
|
||||||
|
isLoading={false}
|
||||||
dashboard={prepareDashboardMock(true, true, jest.fn(), jest.fn())}
|
dashboard={prepareDashboardMock(true, true, jest.fn(), jest.fn())}
|
||||||
onCancel={() => {}}
|
onCancel={() => {}}
|
||||||
onSuccess={() => {}}
|
onSuccess={() => {}}
|
||||||
@ -124,6 +126,7 @@ describe('SaveDashboardAsForm', () => {
|
|||||||
it('renders saved message draft if it was filled before', () => {
|
it('renders saved message draft if it was filled before', () => {
|
||||||
render(
|
render(
|
||||||
<SaveDashboardForm
|
<SaveDashboardForm
|
||||||
|
isLoading={false}
|
||||||
dashboard={createDashboardModelFixture()}
|
dashboard={createDashboardModelFixture()}
|
||||||
onCancel={() => {}}
|
onCancel={() => {}}
|
||||||
onSuccess={() => {}}
|
onSuccess={() => {}}
|
||||||
|
@ -13,6 +13,7 @@ interface FormDTO {
|
|||||||
|
|
||||||
export type SaveProps = {
|
export type SaveProps = {
|
||||||
dashboard: DashboardModel; // original
|
dashboard: DashboardModel; // original
|
||||||
|
isLoading: boolean;
|
||||||
saveModel: SaveDashboardData; // already cloned
|
saveModel: SaveDashboardData; // already cloned
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
@ -23,6 +24,7 @@ export type SaveProps = {
|
|||||||
|
|
||||||
export const SaveDashboardForm = ({
|
export const SaveDashboardForm = ({
|
||||||
dashboard,
|
dashboard,
|
||||||
|
isLoading,
|
||||||
saveModel,
|
saveModel,
|
||||||
options,
|
options,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@ -108,11 +110,11 @@ export const SaveDashboardForm = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!saveModel.hasChanges}
|
disabled={!saveModel.hasChanges || isLoading}
|
||||||
icon={saving ? 'fa fa-spinner' : undefined}
|
icon={saving ? 'fa fa-spinner' : undefined}
|
||||||
aria-label={selectors.pages.SaveDashboardModal.save}
|
aria-label={selectors.pages.SaveDashboardModal.save}
|
||||||
>
|
>
|
||||||
Save
|
{isLoading ? 'Saving...' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
{!saveModel.hasChanges && <div>No changes to save</div>}
|
{!saveModel.hasChanges && <div>No changes to save</div>}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -7,7 +7,7 @@ import { Button, ClipboardButton, HorizontalGroup, TextArea } from '@grafana/ui'
|
|||||||
|
|
||||||
import { SaveDashboardFormProps } from '../types';
|
import { SaveDashboardFormProps } from '../types';
|
||||||
|
|
||||||
export const SaveProvisionedDashboardForm = ({ dashboard, onCancel }: SaveDashboardFormProps) => {
|
export const SaveProvisionedDashboardForm = ({ dashboard, onCancel }: Omit<SaveDashboardFormProps, 'isLoading'>) => {
|
||||||
const [dashboardJSON, setDashboardJson] = useState(() => {
|
const [dashboardJSON, setDashboardJson] = useState(() => {
|
||||||
const clone = dashboard.getSaveModelClone();
|
const clone = dashboard.getSaveModelClone();
|
||||||
delete clone.id;
|
delete clone.id;
|
||||||
|
@ -26,6 +26,7 @@ export interface SaveDashboardCommand {
|
|||||||
|
|
||||||
export interface SaveDashboardFormProps {
|
export interface SaveDashboardFormProps {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
|
isLoading: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
onSubmit?: (clone: DashboardModel, options: SaveDashboardOptions, dashboard: DashboardModel) => Promise<any>;
|
onSubmit?: (clone: DashboardModel, options: SaveDashboardOptions, dashboard: DashboardModel) => Promise<any>;
|
||||||
|
Loading…
Reference in New Issue
Block a user