From 26233e98a8f6d5de37ca2958257964b95884863f Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 14 Jun 2024 10:56:25 +0100 Subject: [PATCH] Alerting: Fix saving telegram contact point to Cloud AM config (#89182) * Add test for saving telegram contact point to AM config * Fix default value for select options in AM config --- .../alerting/unified/Receivers.test.tsx | 74 +++++++++++++++++++ .../features/alerting/unified/Receivers.tsx | 3 +- .../receivers/form/fields/OptionField.tsx | 2 +- .../components/settings/__mocks__/server.ts | 38 ++++++++-- 4 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 public/app/features/alerting/unified/Receivers.test.tsx diff --git a/public/app/features/alerting/unified/Receivers.test.tsx b/public/app/features/alerting/unified/Receivers.test.tsx new file mode 100644 index 00000000000..ac966805830 --- /dev/null +++ b/public/app/features/alerting/unified/Receivers.test.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { selectOptionInTest } from 'test/helpers/selectOptionInTest'; +import { render, screen, waitFor, userEvent } from 'test/test-utils'; + +import { + EXTERNAL_VANILLA_ALERTMANAGER_UID, + setupVanillaAlertmanagerServer, +} from 'app/features/alerting/unified/components/settings/__mocks__/server'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { grantUserPermissions, mockDataSource } from 'app/features/alerting/unified/mocks'; +import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; +import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; +import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; +import { AccessControlAction } from 'app/types'; + +import ContactPoints from './Receivers'; + +import 'core-js/stable/structured-clone'; + +const server = setupMswServer(); + +const mockDataSources = { + [EXTERNAL_VANILLA_ALERTMANAGER_UID]: mockDataSource({ + uid: EXTERNAL_VANILLA_ALERTMANAGER_UID, + name: EXTERNAL_VANILLA_ALERTMANAGER_UID, + type: DataSourceType.Alertmanager, + jsonData: { + implementation: AlertManagerImplementation.prometheus, + }, + }), +}; + +beforeEach(() => { + grantUserPermissions([ + AccessControlAction.AlertingNotificationsRead, + AccessControlAction.AlertingNotificationsWrite, + AccessControlAction.AlertingNotificationsExternalRead, + AccessControlAction.AlertingNotificationsExternalWrite, + ]); +}); + +it('can save a contact point with a select dropdown', async () => { + setupVanillaAlertmanagerServer(server); + setupDataSources(mockDataSources[EXTERNAL_VANILLA_ALERTMANAGER_UID]); + + const user = userEvent.setup(); + + render(, { + historyOptions: { + initialEntries: [`/alerting/notifications/receivers/new?alertmanager=${EXTERNAL_VANILLA_ALERTMANAGER_UID}`], + }, + }); + + // Fill out contact point name + const contactPointName = await screen.findByPlaceholderText(/name/i); + await user.type(contactPointName, 'contact point with select'); + + // Select Telegram option (this is we expect the form to contain a dropdown) + const integrationDropdown = screen.getByLabelText(/integration/i); + await selectOptionInTest(integrationDropdown, /telegram/i); + + // Fill out basic fields necessary for contact point to be saved + const botToken = await screen.findByLabelText(/bot token/i); + const chatId = await screen.findByLabelText(/chat id/i); + + await user.type(botToken, 'sometoken'); + await user.type(chatId, '-123'); + + await user.click(await screen.findByRole('button', { name: /save contact point/i })); + + // TODO: Have a better way to assert that the contact point was saved. This is instead asserting on some + // text that's present on the list page, as there's a lot of overlap in text between the form and the list page + await waitFor(() => expect(screen.getByText(/search by name or type/i)).toBeInTheDocument(), { timeout: 2000 }); +}); diff --git a/public/app/features/alerting/unified/Receivers.tsx b/public/app/features/alerting/unified/Receivers.tsx index fc37703fba1..367a1f21376 100644 --- a/public/app/features/alerting/unified/Receivers.tsx +++ b/public/app/features/alerting/unified/Receivers.tsx @@ -3,7 +3,6 @@ import { Route, Switch } from 'react-router-dom'; import { withErrorBoundary } from '@grafana/ui'; import { SafeDynamicImport } from 'app/core/components/DynamicImports/SafeDynamicImport'; -import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { AlertmanagerPageWrapper } from './components/AlertingPageWrapper'; @@ -12,7 +11,7 @@ const EditContactPoint = SafeDynamicImport(() => import('./components/contact-po const NewContactPoint = SafeDynamicImport(() => import('./components/contact-points/NewContactPoint')); const GlobalConfig = SafeDynamicImport(() => import('./components/contact-points/components/GlobalConfig')); -const ContactPoints = (_props: GrafanaRouteComponentProps): JSX.Element => ( +const ContactPoints = (): JSX.Element => ( diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx index 05bc1c00a5d..705ddc79eae 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx @@ -161,7 +161,7 @@ const OptionInput: FC = ({ )} control={control} name={name} - defaultValue={option.defaultValue} + defaultValue={option.defaultValue?.value} rules={{ validate: { customValidator: (v) => (customValidator ? customValidator(v) : true), diff --git a/public/app/features/alerting/unified/components/settings/__mocks__/server.ts b/public/app/features/alerting/unified/components/settings/__mocks__/server.ts index 428b6f54de6..41ead147008 100644 --- a/public/app/features/alerting/unified/components/settings/__mocks__/server.ts +++ b/public/app/features/alerting/unified/components/settings/__mocks__/server.ts @@ -2,7 +2,13 @@ import { delay, http, HttpResponse } from 'msw'; import { SetupServerApi } from 'msw/lib/node'; import { setDataSourceSrv } from '@grafana/runtime'; -import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; +import { + AlertManagerCortexConfig, + AlertManagerDataSourceJsonData, + AlertManagerImplementation, + AlertmanagerReceiver, + Receiver, +} from 'app/plugins/datasource/alertmanager/types'; import { mockDataSource, MockDataSourceSrv } from '../../../mocks'; import * as config from '../../../utils/config'; @@ -70,7 +76,7 @@ export function setupVanillaAlertmanagerServer(server: SetupServerApi) { server.use( createVanillaAlertmanagerConfigurationHandler(EXTERNAL_VANILLA_ALERTMANAGER_UID), - ...createAlertmanagerConfigurationHandlers(PROVISIONED_MIMIR_ALERTMANAGER_UID) + ...createAlertmanagerConfigurationHandlers() ); return server; @@ -82,11 +88,33 @@ const createExternalAlertmanagersHandler = () => { return http.get('/api/v1/ngalert/alertmanagers', () => HttpResponse.json(alertmanagers)); }; -const createAlertmanagerConfigurationHandlers = (name = 'grafana') => { +const createAlertmanagerConfigurationHandlers = () => { + // Dirty check to type guard against us having a non-Grafana managed receiver + const contactPointIsAMReceiver = (receiver: Receiver): receiver is AlertmanagerReceiver => { + return !receiver.grafana_managed_receiver_configs; + }; + return [ - http.get(`/api/alertmanager/${name}/config/api/v1/alerts`, () => HttpResponse.json(internalAlertmanagerConfig)), - http.post(`/api/alertmanager/${name}/config/api/v1/alerts`, async () => { + http.get(`/api/alertmanager/:name/config/api/v1/alerts`, () => HttpResponse.json(internalAlertmanagerConfig)), + http.post(`/api/alertmanager/:name/config/api/v1/alerts`, async ({ request }) => { await delay(1000); // simulate some time + + // Specifically mock and check for the case of an invalid telegram config, + // and return a 400 error in this case + // This is to test against us accidentally sending a `{label, value}` object instead of a string + const body = await request.json(); + const invalidConfig = body.alertmanager_config.receivers?.some((receiver) => { + if (!contactPointIsAMReceiver(receiver)) { + return false; + } + + return (receiver.telegram_configs || []).some((config) => typeof config.parse_mode === 'object'); + }); + + if (invalidConfig) { + return HttpResponse.json({ message: 'bad request data' }, { status: 400 }); + } + return HttpResponse.json({ message: 'configuration created' }); }), ];