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
This commit is contained in:
Tom Ratcliffe 2024-06-14 10:56:25 +01:00 committed by GitHub
parent 84590901e5
commit 26233e98a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 109 additions and 8 deletions

View File

@ -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<AlertManagerDataSourceJsonData>({
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(<ContactPoints />, {
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 });
});

View File

@ -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 => (
<AlertmanagerPageWrapper navId="receivers" accessType="notification">
<Switch>
<Route exact={true} path="/alerting/notifications" component={ContactPointsV2} />

View File

@ -161,7 +161,7 @@ const OptionInput: FC<Props & { id: string; pathIndex?: string }> = ({
)}
control={control}
name={name}
defaultValue={option.defaultValue}
defaultValue={option.defaultValue?.value}
rules={{
validate: {
customValidator: (v) => (customValidator ? customValidator(v) : true),

View File

@ -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<never, AlertManagerCortexConfig>(`/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' });
}),
];