diff --git a/src/components/CheckForm/CheckForm.tsx b/src/components/CheckForm/CheckForm.tsx index 32fbd00ac..cae1cf11d 100644 --- a/src/components/CheckForm/CheckForm.tsx +++ b/src/components/CheckForm/CheckForm.tsx @@ -21,6 +21,7 @@ import { toFormValues } from 'components/CheckEditor/checkFormTransformations'; import { CheckJobName } from 'components/CheckEditor/FormComponents/CheckJobName'; import { ChooseCheckType } from 'components/CheckEditor/FormComponents/ChooseCheckType'; import { ProbeOptions } from 'components/CheckEditor/ProbeOptions'; +import { checkHasChanges } from 'components/CheckForm/checkForm.utils'; import { DNSCheckLayout } from 'components/CheckForm/FormLayouts/CheckDNSLayout'; import { GRPCCheckLayout } from 'components/CheckForm/FormLayouts/CheckGrpcLayout'; import { HttpCheckLayout } from 'components/CheckForm/FormLayouts/CheckHttpLayout'; @@ -93,9 +94,10 @@ export const CheckForm = ({ check, disabled }: CheckFormProps) => { (checkType === CheckType.Browser && isOverBrowserLimit) || ([CheckType.MULTI_HTTP, CheckType.Scripted].includes(checkType) && isOverScriptedLimit); const isDisabled = disabled || !canWriteChecks || getLimitDisabled({ isExistingCheck, isLoading, overLimit }); + const defaultValues = toFormValues(initialCheck, checkType); const formMethods = useForm({ - defaultValues: toFormValues(initialCheck, checkType), + defaultValues, shouldFocusError: false, // we manage focus manually resolver: zodResolver(schema), }); @@ -158,9 +160,10 @@ export const CheckForm = ({ check, disabled }: CheckFormProps) => { ); - const { isDirty, isSubmitSuccessful } = formMethods.formState; - // since we navigate on submit, we need this to not trigger the confirmation modal - const hasUnsavedChanges = isDirty && !isSubmitSuccessful; + const hasUnsavedChanges = error + ? true + : checkHasChanges(defaultValues, formMethods.getValues()) && !formMethods.formState.isSubmitSuccessful; + const navModel = useMemo(() => { return isExistingCheck ? createNavModel( diff --git a/src/components/CheckForm/checkForm.utils.ts b/src/components/CheckForm/checkForm.utils.ts index c517620c6..01262eb88 100644 --- a/src/components/CheckForm/checkForm.utils.ts +++ b/src/components/CheckForm/checkForm.utils.ts @@ -5,6 +5,10 @@ import { PROBES_FILTER_ID } from 'components/CheckEditor/CheckProbes/ProbesFilte import { SCRIPT_TEXTAREA_ID } from 'components/CheckEditor/FormComponents/ScriptedCheckScript'; import { CHECK_FORM_ERROR_EVENT } from 'components/constants'; +export function checkHasChanges(existing: CheckFormValues, incoming: CheckFormValues) { + return JSON.stringify(existing) !== JSON.stringify(incoming); +} + export function flattenKeys(errs: FieldErrors) { const build: string[] = []; diff --git a/src/page/EditCheck/__tests__/ApiEndPointChecks/HTTPCheck/Submit.test.tsx b/src/page/EditCheck/__tests__/ApiEndPointChecks/HTTPCheck/Submit.test.tsx deleted file mode 100644 index 12f6d3eed..000000000 --- a/src/page/EditCheck/__tests__/ApiEndPointChecks/HTTPCheck/Submit.test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { screen } from '@testing-library/react'; -import { PRIVATE_PROBE } from 'test/fixtures/probes'; -import { apiRoute } from 'test/handlers'; -import { server } from 'test/server'; - -import { HTTPCheck, HttpMethod, IpVersion } from 'types'; -import { renderEditForm } from 'page/__testHelpers__/checkForm'; - -import { DataTestIds } from '../../../../../test/dataTestIds'; - -const MIN_HTTP_CHECK: HTTPCheck = { - id: 1, - tenantId: 1, - frequency: 60000, - timeout: 3000, - enabled: true, - labels: [], - settings: { - http: { - ipVersion: IpVersion.V4, - method: HttpMethod.GET, - noFollowRedirects: false, - failIfSSL: false, - failIfNotSSL: false, - tlsConfig: {}, - }, - }, - probes: [PRIVATE_PROBE.id] as number[], - target: 'https://http.com', - job: 'Job name for http', - basicMetricsOnly: true, - alertSensitivity: 'none', -}; - -it(`HTTPCheck -- can not submit an existing check without editing`, async () => { - server.use( - apiRoute(`listChecks`, { - result: () => { - return { - json: [MIN_HTTP_CHECK], - }; - }, - }) - ); - - await renderEditForm(MIN_HTTP_CHECK.id); - expect(await screen.findByTestId(DataTestIds.CHECK_FORM_SUBMIT_BUTTON)).not.toBeEnabled(); -}); diff --git a/src/page/EditCheck/__tests__/ApiEndPointChecks/TCPCheck/Submit.test.tsx b/src/page/EditCheck/__tests__/ApiEndPointChecks/TCPCheck/Submit.test.tsx deleted file mode 100644 index c0b402890..000000000 --- a/src/page/EditCheck/__tests__/ApiEndPointChecks/TCPCheck/Submit.test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { screen } from '@testing-library/react'; -import { PRIVATE_PROBE } from 'test/fixtures/probes'; -import { apiRoute } from 'test/handlers'; -import { server } from 'test/server'; - -import { IpVersion, TCPCheck } from 'types'; -import { renderEditForm } from 'page/__testHelpers__/checkForm'; - -import { DataTestIds } from '../../../../../test/dataTestIds'; - -const MIN_TCP_CHECK: TCPCheck = { - id: 1, - tenantId: 1, - frequency: 60000, - timeout: 3000, - enabled: true, - labels: [], - settings: { - tcp: { - ipVersion: IpVersion.V4, - tlsConfig: {}, - }, - }, - probes: [PRIVATE_PROBE.id] as number[], - target: 'grafana.com:43', - job: 'Job name for tcp', - basicMetricsOnly: true, - alertSensitivity: 'none', -}; - -it(`TCPCheck -- can not submit an existing check without editing`, async () => { - server.use( - apiRoute(`listChecks`, { - result: () => { - return { - json: [MIN_TCP_CHECK], - }; - }, - }) - ); - - await renderEditForm(MIN_TCP_CHECK.id); - - expect(await screen.findByTestId(DataTestIds.CHECK_FORM_SUBMIT_BUTTON)).not.toBeEnabled(); -}); diff --git a/src/page/EditCheck/__tests__/EditCheck.test.tsx b/src/page/EditCheck/__tests__/EditCheck.test.tsx index 875811d80..4477dcb61 100644 --- a/src/page/EditCheck/__tests__/EditCheck.test.tsx +++ b/src/page/EditCheck/__tests__/EditCheck.test.tsx @@ -62,4 +62,9 @@ describe(``, () => { const submitButton = await screen.findByTestId(DataTestIds.CHECK_FORM_SUBMIT_BUTTON); expect(submitButton).toBeDisabled(); }); + + it(`disables the save button when no edits have been made`, async () => { + await renderEditForm(BASIC_HTTP_CHECK.id); + expect(await screen.findByTestId(DataTestIds.CHECK_FORM_SUBMIT_BUTTON)).not.toBeEnabled(); + }); }); diff --git a/src/page/NewCheck/__tests__/NewCheck.journey.test.tsx b/src/page/NewCheck/__tests__/NewCheck.journey.test.tsx index 9c75d0e89..391fe29df 100644 --- a/src/page/NewCheck/__tests__/NewCheck.journey.test.tsx +++ b/src/page/NewCheck/__tests__/NewCheck.journey.test.tsx @@ -177,6 +177,42 @@ describe(` journey`, () => { expect(testButton).toBeDisabled(); }); + it(`disables the submit button by default`, async () => { + await renderNewForm(CheckType.HTTP); + expect(screen.getByTestId(DataTestIds.CHECK_FORM_SUBMIT_BUTTON)).not.toBeEnabled(); + }); + + it(`enables the submit button when a field is edited`, async () => { + const { user } = await renderNewForm(CheckType.HTTP); + const jobNameInput = await screen.findByLabelText('Job name', { exact: false }); + await user.type(jobNameInput, 'My Job Name'); + const submitButton = await screen.findByTestId(DataTestIds.CHECK_FORM_SUBMIT_BUTTON); + expect(submitButton).toBeEnabled(); + }); + + it(`has the save button enabled after a failed submission`, async () => { + const { user } = await renderNewForm(CheckType.HTTP); + + server.use( + apiRoute(`addCheck`, { + result: () => { + return { + status: 409, + json: { + err: 'target/job combination already exists', + msg: 'Failed to add check to database', + }, + }; + }, + }) + ); + + await fillMandatoryFields({ user, checkType: CheckType.HTTP }); + await submitForm(user); + const submitButton = await screen.findByTestId(DataTestIds.CHECK_FORM_SUBMIT_BUTTON); + expect(submitButton).toBeEnabled(); + }); + // jsdom doesn't give us back the submitter of the form, so we can't test this // https://github.com/jsdom/jsdom/issues/3117 it.skip(`should show an error message when it fails to test a check`, async () => {});