Dashboard Save: Fix the issue of clicking Save button that wouldn't trigger save (#113134)
* fix the issue of clicking Save button that wouldn't trigger save * clean up
This commit is contained in:
@@ -2110,11 +2110,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import debounce from 'debounce-promise';
|
||||
import { ChangeEvent, useState, useEffect } from 'react';
|
||||
import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { UseFormSetValue, useForm } from 'react-hook-form';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
@@ -43,19 +42,48 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
||||
},
|
||||
});
|
||||
|
||||
const { errors, isValid, validatingFields } = formState;
|
||||
const { errors, isValid } = formState;
|
||||
const formValues = watch();
|
||||
|
||||
const { state, onSaveDashboard } = useSaveDashboard(false);
|
||||
|
||||
const [contentSent, setContentSent] = useState<{ title?: string; folderUid?: string }>({});
|
||||
|
||||
const validationTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
// Validate title on form mount to catch invalid default values
|
||||
useEffect(() => {
|
||||
trigger('title');
|
||||
}, [trigger]);
|
||||
|
||||
// Cleanup timeout on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimeout(validationTimeoutRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleTitleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue('title', e.target.value, { shouldDirty: true });
|
||||
clearTimeout(validationTimeoutRef.current);
|
||||
validationTimeoutRef.current = setTimeout(() => {
|
||||
trigger('title');
|
||||
}, 400);
|
||||
},
|
||||
[setValue, trigger]
|
||||
);
|
||||
|
||||
const onSave = async (overwrite: boolean) => {
|
||||
clearTimeout(validationTimeoutRef.current);
|
||||
|
||||
const isTitleValid = await trigger('title');
|
||||
|
||||
// This prevents the race between the new input and old validation state
|
||||
if (!isTitleValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = getValues();
|
||||
|
||||
const result = await onSaveDashboard(dashboard, {
|
||||
@@ -88,16 +116,7 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
||||
);
|
||||
|
||||
const saveButton = (overwrite: boolean) => {
|
||||
const isTitleValidating = !!validatingFields.title;
|
||||
|
||||
return (
|
||||
<SaveButton
|
||||
isValid={isValid && !isTitleValidating}
|
||||
isLoading={state.loading}
|
||||
onSave={onSave}
|
||||
overwrite={overwrite}
|
||||
/>
|
||||
);
|
||||
return <SaveButton isValid={isValid} isLoading={state.loading} onSave={onSave} overwrite={overwrite} />;
|
||||
};
|
||||
function renderFooter(error?: Error) {
|
||||
const formValuesMatchContentSent =
|
||||
@@ -128,23 +147,27 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(() => onSave(false))}>
|
||||
<Field label={<TitleFieldLabel onChange={setValue} />} invalid={!!errors.title} error={errors.title?.message}>
|
||||
<Field
|
||||
noMargin
|
||||
label={<TitleFieldLabel onChange={setValue} />}
|
||||
invalid={!!errors.title}
|
||||
error={errors.title?.message}
|
||||
>
|
||||
<Input
|
||||
{...register('title', {
|
||||
required: t('dashboard-scene.save-dashboard-as-form.required', 'Required'),
|
||||
validate: validateDashboardName,
|
||||
onChange: handleTitleChange,
|
||||
})}
|
||||
aria-label={t(
|
||||
'dashboard-scene.save-dashboard-as-form.aria-label-save-dashboard-title-field',
|
||||
'Save dashboard title field'
|
||||
)}
|
||||
data-testid={selectors.components.Drawer.DashboardSaveDrawer.saveAsTitleInput}
|
||||
onChange={debounce(async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue('title', e.target.value, { shouldValidate: true });
|
||||
}, 400)}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
noMargin
|
||||
label={<DescriptionLabel onChange={setValue} />}
|
||||
invalid={!!errors.description}
|
||||
error={errors.description?.message}
|
||||
@@ -159,7 +182,7 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label={t('dashboard-scene.save-dashboard-as-form.label-folder', 'Folder')}>
|
||||
<Field noMargin label={t('dashboard-scene.save-dashboard-as-form.label-folder', 'Folder')}>
|
||||
<FolderPicker
|
||||
onChange={async (uid: string | undefined, title: string | undefined) => {
|
||||
setValue('folder', { uid, title });
|
||||
@@ -177,7 +200,7 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) {
|
||||
/>
|
||||
</Field>
|
||||
{!changeInfo.isNew && (
|
||||
<Field label={t('dashboard-scene.save-dashboard-as-form.label-copy-tags', 'Copy tags')}>
|
||||
<Field noMargin label={t('dashboard-scene.save-dashboard-as-form.label-copy-tags', 'Copy tags')}>
|
||||
<Switch {...register('copyTags')} />
|
||||
</Field>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user