diff --git a/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx b/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx index 6ad6738fed1..ef7bb63bbff 100644 --- a/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx +++ b/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx @@ -410,10 +410,12 @@ export const useCreateContactPoint = ({ alertmanager }: BaseAlertmanagerArgs) => oldConfig: config, alertManagerSourceName: alertmanager, }) - ).then(() => { - dispatch(alertingApi.util.invalidateTags(['AlertmanagerConfiguration', 'ContactPoint', 'ContactPointsStatus'])); - dispatch(generatedReceiversApi.util.invalidateTags(['Receiver'])); - }); + ) + .unwrap() + .then(() => { + dispatch(alertingApi.util.invalidateTags(['AlertmanagerConfiguration', 'ContactPoint', 'ContactPointsStatus'])); + dispatch(generatedReceiversApi.util.invalidateTags(['Receiver'])); + }); }; }; diff --git a/public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx b/public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx index e57687322af..fa6c20423ba 100644 --- a/public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx +++ b/public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx @@ -4,6 +4,7 @@ import { render, screen } from 'test/test-utils'; import { byLabelText, byPlaceholderText, byRole, byTestId } from 'testing-library-selector'; import { config } from '@grafana/runtime'; +import { makeGrafanaAlertmanagerConfigUpdateFail } from 'app/features/alerting/unified/mocks/server/configure'; import { captureRequests } from 'app/features/alerting/unified/mocks/server/events'; import { AccessControlAction } from 'app/types'; @@ -106,6 +107,20 @@ describe('alerting API server disabled', () => { expect([testBody]).toMatchSnapshot(); expect([saveBody]).toMatchSnapshot(); }); + + it('does not redirect when creating contact point and API errors', async () => { + makeGrafanaAlertmanagerConfigUpdateFail(); + const { user } = renderForm(); + + await user.type(await ui.inputs.name.find(), 'receiver that should fail'); + const email = ui.inputs.email.addresses.get(); + await user.clear(email); + await user.type(email, 'tester@grafana.com'); + + await user.click(ui.saveContactButton.get()); + + expect(screen.queryByText(/redirected/i)).not.toBeInTheDocument(); + }); }); const ui = { diff --git a/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx index 84a549ff4e5..2809617aac3 100644 --- a/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx @@ -54,12 +54,16 @@ export const CloudReceiverForm = ({ contactPoint, alertManagerSourceName, readOn const onSubmit = async (values: ReceiverFormValues) => { const newReceiver = formValuesToCloudReceiver(values, defaultChannelValues); - if (editMode) { - await updateContactPoint({ contactPoint: newReceiver, originalName: contactPoint!.name }); - } else { - await createContactPoint({ contactPoint: newReceiver }); + try { + if (editMode) { + await updateContactPoint({ contactPoint: newReceiver, originalName: contactPoint!.name }); + } else { + await createContactPoint({ contactPoint: newReceiver }); + } + locationService.push('/alerting/notifications'); + } catch (error) { + // React form validation will handle this for us } - locationService.push('/alerting/notifications'); }; // this basically checks if we can manage the selected alert manager data source, either because it's a Grafana Managed one diff --git a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx index 03e5c1e68fb..ade39241b34 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx @@ -81,17 +81,21 @@ export const GrafanaReceiverForm = ({ contactPoint, readOnly = false, editMode } const onSubmit = async (values: ReceiverFormValues) => { const newReceiver = formValuesToGrafanaReceiver(values, id2original, defaultChannelValues, grafanaNotifiers); - if (editMode) { - await updateContactPoint({ - contactPoint: newReceiver, - id: contactPoint!.id, - resourceVersion: contactPoint?.metadata?.resourceVersion, - originalName: contactPoint?.name, - }); - } else { - await createContactPoint({ contactPoint: newReceiver }); + try { + if (editMode) { + await updateContactPoint({ + contactPoint: newReceiver, + id: contactPoint!.id, + resourceVersion: contactPoint?.metadata?.resourceVersion, + originalName: contactPoint?.name, + }); + } else { + await createContactPoint({ contactPoint: newReceiver }); + } + locationService.push('/alerting/notifications'); + } catch (error) { + // React form validation will handle this for us } - locationService.push('/alerting/notifications'); }; const onTestChannel = (values: GrafanaChannelValues) => { diff --git a/public/app/features/alerting/unified/mocks/server/configure.ts b/public/app/features/alerting/unified/mocks/server/configure.ts index 63b976f2a44..db47b049265 100644 --- a/public/app/features/alerting/unified/mocks/server/configure.ts +++ b/public/app/features/alerting/unified/mocks/server/configure.ts @@ -4,9 +4,11 @@ import { config } from '@grafana/runtime'; import server, { mockFeatureDiscoveryApi } from 'app/features/alerting/unified/mockApi'; import { mockDataSource, mockFolder } from 'app/features/alerting/unified/mocks'; import { + ALERTMANAGER_UPDATE_ERROR_RESPONSE, getAlertmanagerConfigHandler, getGrafanaAlertmanagerConfigHandler, grafanaAlertingConfigurationStatusHandler, + updateGrafanaAlertmanagerConfigHandler, } from 'app/features/alerting/unified/mocks/server/handlers/alertmanagers'; import { getFolderHandler } from 'app/features/alerting/unified/mocks/server/handlers/folders'; import { listNamespacedTimeIntervalHandler } from 'app/features/alerting/unified/mocks/server/handlers/k8s/timeIntervals.k8s'; @@ -126,3 +128,8 @@ export const removePlugin = (pluginId: string) => { export const disablePlugin = (pluginId: SupportedPlugin) => { server.use(getDisabledPluginHandler(pluginId)); }; + +/** Make alertmanager config update fail */ +export const makeGrafanaAlertmanagerConfigUpdateFail = () => { + server.use(updateGrafanaAlertmanagerConfigHandler(ALERTMANAGER_UPDATE_ERROR_RESPONSE)); +}; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts b/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts index 7f3575500e1..37f7f8e1c46 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts @@ -35,7 +35,7 @@ export const getGrafanaAlertmanagerConfigHandler = (config: AlertManagerCortexCo export const getAlertmanagerConfigHandler = (config: AlertManagerCortexConfig = alertmanagerConfigMock) => http.get('/api/alertmanager/:name/config/api/v1/alerts', () => HttpResponse.json(config)); -const alertmanagerUpdateError = HttpResponse.json({ message: 'bad request' }, { status: 400 }); +export const ALERTMANAGER_UPDATE_ERROR_RESPONSE = HttpResponse.json({ message: 'bad request' }, { status: 400 }); /** Perform some basic validation on the config that we expect the backend to also do */ const validateGrafanaAlertmanagerConfig = (config: AlertManagerCortexConfig) => { @@ -46,27 +46,28 @@ const validateGrafanaAlertmanagerConfig = (config: AlertManagerCortexConfig) => const intervalsByName = new Set(intervals.map((interval) => interval.name)); const duplicatedIntervals = intervalsByName.size !== intervals.length; + let routesReferencingMissingMuteTimings = false; + if (route) { - const routesReferencingMissingMuteTimings = Boolean( + routesReferencingMissingMuteTimings = Boolean( route.routes?.find((route) => { return route.mute_time_intervals?.some((name) => !intervalsByName.has(name)); }) ); - - if (routesReferencingMissingMuteTimings) { - return alertmanagerUpdateError; - } } - if (duplicatedIntervals) { - return alertmanagerUpdateError; + if (routesReferencingMissingMuteTimings || duplicatedIntervals) { + return ALERTMANAGER_UPDATE_ERROR_RESPONSE; } return null; }; -const updateGrafanaAlertmanagerConfigHandler = () => +export const updateGrafanaAlertmanagerConfigHandler = (responseOverride?: typeof ALERTMANAGER_UPDATE_ERROR_RESPONSE) => http.post('/api/alertmanager/grafana/config/api/v1/alerts', async ({ request }) => { + if (responseOverride) { + return responseOverride; + } const body: AlertManagerCortexConfig = await request.clone().json(); const potentialError = validateGrafanaAlertmanagerConfig(body); return potentialError ? potentialError : HttpResponse.json({ message: 'configuration created' });