From dfd64ed4697ff0215cc43422ea9411eb93aa331b Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 31 May 2024 16:19:01 +0100 Subject: [PATCH] Alerting: Ensure we fetch AM config before saving new configuration (#88458) --- .../alerting/unified/Templates.test.tsx | 37 +++++++++++++++++-- .../components/receivers/TemplateForm.tsx | 1 + .../unified/mocks/server/configure.ts | 14 ++++++- .../mocks/server/handlers/alertmanagers.ts | 25 ++++++++++++- .../alerting/unified/state/actions.ts | 6 ++- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/public/app/features/alerting/unified/Templates.test.tsx b/public/app/features/alerting/unified/Templates.test.tsx index d782eea1ea7..61e859042cf 100644 --- a/public/app/features/alerting/unified/Templates.test.tsx +++ b/public/app/features/alerting/unified/Templates.test.tsx @@ -1,18 +1,22 @@ import React from 'react'; -import { render, screen } from 'test/test-utils'; +import { render, screen, userEvent } from 'test/test-utils'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { setGrafanaAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/configure'; +import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; import Templates from './Templates'; -import setupGrafanaManagedServer from './components/contact-points/__mocks__/grafanaManagedServer'; import { grantUserPermissions } from './mocks'; -const server = setupMswServer(); +jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ + AppChromeUpdate: ({ actions }: { actions: React.ReactNode }) =>
{actions}
, +})); + +setupMswServer(); beforeEach(() => { grantUserPermissions([AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsWrite]); - setupGrafanaManagedServer(server); }); describe('Templates routes', () => { @@ -25,4 +29,29 @@ describe('Templates routes', () => { expect(await screen.findByText('Edit payload')).toBeInTheDocument(); }); + + it('shows an error when remote AM config has been updated ', async () => { + const originalConfig: AlertManagerCortexConfig = { + template_files: {}, + alertmanager_config: {}, + }; + setGrafanaAlertmanagerConfig(originalConfig); + + const user = userEvent.setup(); + render(, { + historyOptions: { + initialEntries: ['/alerting/notifications/templates/new'], + }, + }); + + await user.type(await screen.findByLabelText(/template name/i), 'a'); + + // Once the user has loaded the page and started creating their template, + // update the API behaviour as if another user has also edited the config and added something in + setGrafanaAlertmanagerConfig({ ...originalConfig, template_files: { a: 'b' } }); + + await user.click(screen.getByRole('button', { name: /save/i })); + + expect(await screen.findByText(/a newer alertmanager configuration is available/i)).toBeInTheDocument(); + }); }); diff --git a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx index 275c69cecdd..abbc2dd34f9 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx @@ -220,6 +220,7 @@ export const TemplateForm = ({ existing, alertManagerSourceName, config, provena placeholder="Give your template a name" width={42} autoFocus={true} + id="new-template-name" /> diff --git a/public/app/features/alerting/unified/mocks/server/configure.ts b/public/app/features/alerting/unified/mocks/server/configure.ts index 2566a218e1e..7bfdacaee50 100644 --- a/public/app/features/alerting/unified/mocks/server/configure.ts +++ b/public/app/features/alerting/unified/mocks/server/configure.ts @@ -1,8 +1,11 @@ import server from 'app/features/alerting/unified/mockApi'; import { mockFolder } from 'app/features/alerting/unified/mocks'; -import { grafanaAlertingConfigurationStatusHandler } from 'app/features/alerting/unified/mocks/server/handlers/alertmanagers'; +import { + getGrafanaAlertmanagerConfigHandler, + grafanaAlertingConfigurationStatusHandler, +} from 'app/features/alerting/unified/mocks/server/handlers/alertmanagers'; import { getFolderHandler } from 'app/features/alerting/unified/mocks/server/handlers/folders'; -import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; +import { AlertManagerCortexConfig, AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; import { FolderDTO } from 'app/types'; /** @@ -23,3 +26,10 @@ export const setAlertmanagerChoices = (alertmanagersChoice: AlertmanagerChoice, export const setFolderAccessControl = (accessControl: FolderDTO['accessControl']) => { server.use(getFolderHandler(mockFolder({ hasAcl: true, accessControl }))); }; + +/** + * Makes the mock server respond with different Grafana Alertmanager config + */ +export const setGrafanaAlertmanagerConfig = (config: AlertManagerCortexConfig) => { + server.use(getGrafanaAlertmanagerConfigHandler(config)); +}; 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 06fdf657ebd..e789139f65b 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts @@ -1,9 +1,10 @@ import { http, HttpResponse } from 'msw'; +import alertmanagerConfigMock from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json'; import { MOCK_SILENCE_ID_EXISTING, mockAlertmanagerAlert } from 'app/features/alerting/unified/mocks'; import { defaultGrafanaAlertingConfigurationStatusResponse } from 'app/features/alerting/unified/mocks/alertmanagerApi'; import { MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER } from 'app/features/alerting/unified/mocks/server/handlers/datasources'; -import { AlertState } from 'app/plugins/datasource/alertmanager/types'; +import { AlertManagerCortexConfig, AlertState } from 'app/plugins/datasource/alertmanager/types'; export const grafanaAlertingConfigurationStatusHandler = ( response = defaultGrafanaAlertingConfigurationStatusResponse @@ -26,5 +27,25 @@ export const alertmanagerAlertsListHandler = () => ]); }); -const handlers = [alertmanagerAlertsListHandler(), grafanaAlertingConfigurationStatusHandler()]; +export const getGrafanaAlertmanagerConfigHandler = (config: AlertManagerCortexConfig = alertmanagerConfigMock) => + http.get('/api/alertmanager/grafana/config/api/v1/alerts', () => HttpResponse.json(config)); + +const updateGrafanaAlertmanagerConfigHandler = () => + http.post('/api/alertmanager/grafana/config/api/v1/alerts', () => + HttpResponse.json({ message: 'configuration created' }) + ); + +const getGrafanaAlertmanagerTemplatePreview = () => + http.post('/api/alertmanager/grafana/config/api/v1/templates/test', () => + // TODO: Scaffold out template preview response as needed by tests + HttpResponse.json({}) + ); + +const handlers = [ + alertmanagerAlertsListHandler(), + grafanaAlertingConfigurationStatusHandler(), + getGrafanaAlertmanagerConfigHandler(), + updateGrafanaAlertmanagerConfigHandler(), + getGrafanaAlertmanagerTemplatePreview(), +]; export default handlers; diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index 5724e3e1341..d72184b4443 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -491,7 +491,11 @@ export const updateAlertManagerConfigAction = createAsyncThunk { const latestConfig = await thunkAPI - .dispatch(alertmanagerApi.endpoints.getAlertmanagerConfiguration.initiate(alertManagerSourceName)) + .dispatch( + alertmanagerApi.endpoints.getAlertmanagerConfiguration.initiate(alertManagerSourceName, { + forceRefetch: true, + }) + ) .unwrap(); const isLatestConfigEmpty = isEmpty(latestConfig.alertmanager_config) && isEmpty(latestConfig.template_files);