diff --git a/public/app/features/alerting/unified/MuteTimings.test.tsx b/public/app/features/alerting/unified/MuteTimings.test.tsx
index c583d45599a..6e3eba6611d 100644
--- a/public/app/features/alerting/unified/MuteTimings.test.tsx
+++ b/public/app/features/alerting/unified/MuteTimings.test.tsx
@@ -7,10 +7,7 @@ import { byRole, byTestId, byText } from 'testing-library-selector';
import { config } from '@grafana/runtime';
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
-import {
- setAlertmanagerConfig,
- setGrafanaAlertmanagerConfig,
-} from 'app/features/alerting/unified/mocks/server/configure';
+import { setAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
import { MOCK_DATASOURCE_EXTERNAL_VANILLA_ALERTMANAGER_UID } from 'app/features/alerting/unified/mocks/server/handlers/datasources';
import {
@@ -206,8 +203,12 @@ describe('Mute timings', () => {
// FIXME: scope down
grantUserPermissions(Object.values(AccessControlAction));
- setGrafanaAlertmanagerConfig(defaultConfig);
- setAlertmanagerConfig(defaultConfig);
+ setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, defaultConfig);
+
+ // TODO: Add this at a higher level to ensure that no tests depend on others running first
+ // Without this, the selected alertmanager in a previous test can affect the next, meaning tests
+ // pass/fail depending on the order they are run/if they are focused
+ window.localStorage.clear();
});
it('creates a new mute timing, with mute_time_intervals in config', async () => {
@@ -238,9 +239,9 @@ describe('Mute timings', () => {
it('creates a new mute timing, with time_intervals in config', async () => {
const capture = captureRequests();
- setAlertmanagerConfig(defaultConfigWithNewTimeIntervalsField);
+ setAlertmanagerConfig(dataSources.am.uid, defaultConfigWithNewTimeIntervalsField);
renderMuteTimings(, {
- search: `?alertmanager=${alertmanagerName}`,
+ search: `?alertmanager=${dataSources.am.name}`,
});
await fillOutForm({
@@ -262,9 +263,9 @@ describe('Mute timings', () => {
});
it('creates a new mute timing, with time_intervals and mute_time_intervals in config', async () => {
- setGrafanaAlertmanagerConfig(defaultConfigWithBothTimeIntervalsField);
+ setAlertmanagerConfig(dataSources.am.uid, defaultConfigWithBothTimeIntervalsField);
renderMuteTimings(, {
- search: `?alertmanager=${alertmanagerName}`,
+ search: `?alertmanager=${dataSources.am.name}`,
});
expect(ui.nameField.get()).toBeInTheDocument();
diff --git a/public/app/features/alerting/unified/NotificationPolicies.test.tsx b/public/app/features/alerting/unified/NotificationPolicies.test.tsx
index f9cc962e05e..9cbc98ccf37 100644
--- a/public/app/features/alerting/unified/NotificationPolicies.test.tsx
+++ b/public/app/features/alerting/unified/NotificationPolicies.test.tsx
@@ -1,64 +1,85 @@
-import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
-import { render, userEvent, waitFor, within } from 'test/test-utils';
+import 'core-js/stable/structured-clone';
+import { clickSelectOption } from 'test/helpers/selectOptionInTest';
+import { render, screen, userEvent } from 'test/test-utils';
import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector';
-import { DataSourceSrv, setDataSourceSrv } from '@grafana/runtime';
-import { contextSrv } from 'app/core/services/context_srv';
+import { AppNotificationList } from 'app/core/components/AppNotifications/AppNotificationList';
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
+import {
+ getErrorResponse,
+ makeAllAlertmanagerConfigFetchFail,
+} from 'app/features/alerting/unified/mocks/server/configure';
+import {
+ getAlertmanagerConfig,
+ setAlertmanagerConfig,
+ setAlertmanagerStatus,
+} from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
+import {
+ TIME_INTERVAL_NAME_FILE_PROVISIONED,
+ TIME_INTERVAL_NAME_HAPPY_PATH,
+} from 'app/features/alerting/unified/mocks/server/handlers/k8s/timeIntervals.k8s';
+import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources';
import {
AlertManagerCortexConfig,
AlertManagerDataSourceJsonData,
AlertManagerImplementation,
MatcherOperator,
- MuteTimeInterval,
- Route,
RouteWithID,
} from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types';
import NotificationPolicies, { findRoutesMatchingFilters } from './NotificationPolicies';
-import { fetchAlertManagerConfig, fetchStatus, updateAlertManagerConfig } from './api/alertmanager';
-import { alertmanagerApi } from './api/alertmanagerApi';
-import { discoverAlertmanagerFeatures } from './api/buildInfo';
-import { MockDataSourceSrv, mockDataSource, someCloudAlertManagerConfig, someCloudAlertManagerStatus } from './mocks';
-import { defaultGroupBy } from './utils/amroutes';
-import { getAllDataSources } from './utils/config';
+import {
+ grantUserPermissions,
+ mockDataSource,
+ someCloudAlertManagerConfig,
+ someCloudAlertManagerStatus,
+} from './mocks';
import { ALERTMANAGER_NAME_QUERY_KEY } from './utils/constants';
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
-import 'core-js/stable/structured-clone';
-
-jest.mock('./api/alertmanager');
-jest.mock('./utils/config');
-jest.mock('app/core/services/context_srv');
-jest.mock('./api/buildInfo');
jest.mock('./useRouteGroupsMatcher');
-const mocks = {
- getAllDataSourcesMock: jest.mocked(getAllDataSources),
-
- api: {
- fetchAlertManagerConfig: jest.mocked(fetchAlertManagerConfig),
- updateAlertManagerConfig: jest.mocked(updateAlertManagerConfig),
- fetchStatus: jest.mocked(fetchStatus),
- discoverAlertmanagerFeatures: jest.mocked(discoverAlertmanagerFeatures),
- },
- contextSrv: jest.mocked(contextSrv),
-};
-
setupMswServer();
-const renderNotificationPolicies = (alertManagerSourceName?: string) => {
- return render(, {
- historyOptions: {
- initialEntries: [
- '/alerting/routes' +
- (alertManagerSourceName ? `?${ALERTMANAGER_NAME_QUERY_KEY}=${alertManagerSourceName}` : ''),
- ],
- },
- });
+const updateTiming = async (selectElement: HTMLElement, value: string): Promise => {
+ const user = userEvent.setup();
+ const input = byRole('textbox').get(selectElement);
+ await user.clear(input);
+ await user.type(input, value);
};
+const openDefaultPolicyEditModal = async () => {
+ const user = userEvent.setup();
+ await user.click(await ui.moreActionsDefaultPolicy.find());
+ await user.click(await ui.editButton.find());
+};
+
+const openEditModal = async (
+ /** (zero-based) Index of the policy in the list to open the edit modal for */
+ index: number
+) => {
+ const user = userEvent.setup();
+ await user.click((await ui.moreActions.findAll())[index]);
+ await user.click(await ui.editButton.find());
+};
+
+const renderNotificationPolicies = (alertManagerSourceName: string = GRAFANA_RULES_SOURCE_NAME) =>
+ render(
+ <>
+
+
+ >,
+ {
+ historyOptions: {
+ initialEntries: [
+ '/alerting/routes' +
+ (alertManagerSourceName ? `?${ALERTMANAGER_NAME_QUERY_KEY}=${alertManagerSourceName}` : ''),
+ ],
+ },
+ }
+ );
+
const dataSources = {
am: mockDataSource({
name: 'Alertmanager',
@@ -67,30 +88,36 @@ const dataSources = {
promAlertManager: mockDataSource({
name: 'PromManager',
type: DataSourceType.Alertmanager,
+ uid: 'prometheusAlertManager',
jsonData: {
implementation: AlertManagerImplementation.prometheus,
},
}),
+ mimir: mockDataSource({
+ name: 'mimir',
+ type: DataSourceType.Alertmanager,
+ uid: 'mimir',
+ jsonData: {
+ implementation: AlertManagerImplementation.mimir,
+ },
+ }),
};
const ui = {
- rootReceiver: byTestId('am-routes-root-receiver'),
- rootGroupBy: byTestId('am-routes-root-group-by'),
- rootTimings: byTestId('am-routes-root-timings'),
- row: byTestId('am-routes-row'),
-
+ /** Row of policy tree containing default policy */
rootRouteContainer: byTestId('am-root-route-container'),
+ /** (deeply) Nested rows of policies under the default/root policy */
+ row: byTestId('am-route-container'),
- editButton: byRole('button', { name: 'Edit' }),
- saveButton: byRole('button', { name: 'Save' }),
+ newChildPolicyButton: byRole('button', { name: /New child policy/ }),
+ newSiblingPolicyButton: byRole('button', { name: /Add new policy/ }),
- setDefaultReceiverCTA: byRole('button', { name: 'Set a default contact point' }),
+ moreActionsDefaultPolicy: byLabelText(/more actions for default policy/i),
+ moreActions: byLabelText(/more actions for policy/i),
+ editButton: byRole('menuitem', { name: 'Edit' }),
- editRouteButton: byLabelText('Edit route'),
- deleteRouteButton: byLabelText('Delete route'),
- newPolicyButton: byRole('button', { name: /Add policy/ }),
- newPolicyCTAButton: byRole('button', { name: /Add specific policy/ }),
- savePolicyButton: byRole('button', { name: /save policy/i }),
+ saveButton: byRole('button', { name: /update (default )?policy/i }),
+ deleteRouteButton: byRole('menuitem', { name: 'Delete' }),
receiverSelect: byTestId('am-receiver-select'),
groupSelect: byTestId('am-group-select'),
@@ -101,451 +128,217 @@ const ui = {
groupRepeatContainer: byTestId('am-repeat-interval'),
confirmDeleteModal: byRole('dialog'),
- confirmDeleteButton: byLabelText('Confirm Modal Danger Button'),
+ confirmDeleteButton: byRole('button', { name: /yes, delete policy/i }),
+};
+
+const getRootRoute = async () => {
+ return ui.rootRouteContainer.find();
};
describe('NotificationPolicies', () => {
- const subroutes: Route[] = [
- {
- match: {
- sub1matcher1: 'sub1value1',
- sub1matcher2: 'sub1value2',
- },
- match_re: {
- sub1matcher3: 'sub1value3',
- sub1matcher4: 'sub1value4',
- },
- group_by: ['sub1group1', 'sub1group2'],
- receiver: 'a-receiver',
- continue: true,
- group_wait: '3s',
- group_interval: '2m',
- repeat_interval: '1s',
- routes: [
- {
- match: {
- sub1sub1matcher1: 'sub1sub1value1',
- sub1sub1matcher2: 'sub1sub1value2',
- },
- match_re: {
- sub1sub1matcher3: 'sub1sub1value3',
- sub1sub1matcher4: 'sub1sub1value4',
- },
- group_by: ['sub1sub1group1', 'sub1sub1group2'],
- receiver: 'another-receiver',
- },
- {
- match: {
- sub1sub2matcher1: 'sub1sub2value1',
- sub1sub2matcher2: 'sub1sub2value2',
- },
- match_re: {
- sub1sub2matcher3: 'sub1sub2value3',
- sub1sub2matcher4: 'sub1sub2value4',
- },
- group_by: ['sub1sub2group1', 'sub1sub2group2'],
- receiver: 'another-receiver',
- },
- ],
- },
- {
- match: {
- sub2matcher1: 'sub2value1',
- sub2matcher2: 'sub2value2',
- },
- match_re: {
- sub2matcher3: 'sub2value3',
- sub2matcher4: 'sub2value4',
- },
- receiver: 'another-receiver',
- },
- ];
-
- const emptyRoute: Route = {};
-
- const simpleRoute: Route = {
- receiver: 'simple-receiver',
- matchers: ['hello=world', 'foo!=bar'],
- };
-
- const rootRoute: Route = {
- receiver: 'default-receiver',
- group_by: ['a-group', 'another-group'],
- group_wait: '1s',
- group_interval: '2m',
- repeat_interval: '3d',
- routes: subroutes,
- };
-
- const muteInterval: MuteTimeInterval = {
- name: 'default-mute',
- time_intervals: [
- {
- times: [{ start_time: '12:00', end_time: '24:00' }],
- weekdays: ['monday:friday'],
- days_of_month: ['1:7', '-1:-7'],
- months: ['january:june'],
- years: ['2020:2022'],
- },
- ],
- };
-
beforeEach(() => {
- mocks.getAllDataSourcesMock.mockReturnValue(Object.values(dataSources));
- mocks.contextSrv.hasPermission.mockImplementation(() => true);
- mocks.contextSrv.evaluatePermission.mockImplementation(() => []);
- mocks.api.discoverAlertmanagerFeatures.mockResolvedValue({ lazyConfigInit: false });
- setDataSourceSrv(new MockDataSourceSrv(dataSources));
+ setupDataSources(...Object.values(dataSources));
+ grantUserPermissions([
+ AccessControlAction.AlertingInstanceRead,
+ AccessControlAction.AlertingInstanceCreate,
+ AccessControlAction.AlertingInstanceUpdate,
+ AccessControlAction.AlertingInstancesExternalRead,
+ AccessControlAction.AlertingInstancesExternalWrite,
+ AccessControlAction.AlertingNotificationsRead,
+ AccessControlAction.AlertingNotificationsWrite,
+ AccessControlAction.AlertingNotificationsExternalRead,
+ AccessControlAction.AlertingNotificationsExternalWrite,
+ ]);
});
- afterEach(() => {
- jest.resetAllMocks();
+ it('loads and shows routes', async () => {
+ const { alertmanager_config: testConfig } = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
- setDataSourceSrv(undefined as unknown as DataSourceSrv);
- });
-
- it.skip('loads and shows routes', async () => {
- mocks.api.fetchAlertManagerConfig.mockResolvedValue({
- alertmanager_config: {
- route: rootRoute,
- receivers: [
- {
- name: 'default-receiver',
- },
- {
- name: 'a-receiver',
- },
- {
- name: 'another-receiver',
- },
- ],
- },
- template_files: {},
- });
+ const { route: defaultRoute } = testConfig;
renderNotificationPolicies();
+ const rootRouteEl = await getRootRoute();
- await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1));
-
- expect(ui.rootReceiver.get()).toHaveTextContent(rootRoute.receiver!);
- expect(ui.rootGroupBy.get()).toHaveTextContent(rootRoute.group_by!.join(', '));
- const rootTimings = ui.rootTimings.get();
- expect(rootTimings).toHaveTextContent(rootRoute.group_wait!);
- expect(rootTimings).toHaveTextContent(rootRoute.group_interval!);
- expect(rootTimings).toHaveTextContent(rootRoute.repeat_interval!);
+ expect(rootRouteEl).toHaveTextContent(new RegExp(`delivered to ${defaultRoute?.receiver}`, 'i'));
+ expect(rootRouteEl).toHaveTextContent(new RegExp(`grouped by ${defaultRoute?.group_by?.join(', ')}`, 'i'));
+ expect(rootRouteEl).toHaveTextContent(/wait 30s to group/i);
+ expect(rootRouteEl).toHaveTextContent(/wait 5m before sending/i);
+ expect(rootRouteEl).toHaveTextContent(/repeated every 4h/i);
const rows = await ui.row.findAll();
- expect(rows).toHaveLength(2);
+ expect(rows).toHaveLength(5);
- subroutes.forEach((route, index) => {
+ defaultRoute?.routes?.forEach((route) => {
Object.entries(route.match ?? {}).forEach(([label, value]) => {
- expect(rows[index]).toHaveTextContent(`${label}=${value}`);
+ expect(screen.getByText(`${label} = ${value}`)).toBeInTheDocument();
});
Object.entries(route.match_re ?? {}).forEach(([label, value]) => {
- expect(rows[index]).toHaveTextContent(`${label}=~${value}`);
+ expect(screen.getByText(`${label} =~ ${value}`)).toBeInTheDocument();
});
if (route.group_by) {
- expect(rows[index]).toHaveTextContent(route.group_by.join(', '));
+ expect(rows.some((row) => row?.textContent?.includes(`Grouped by ${route.group_by?.join(', ')}`))).toBe(true);
}
if (route.receiver) {
- expect(rows[index]).toHaveTextContent(route.receiver);
+ expect(rows.some((row) => row?.textContent?.includes(`Delivered to ${route.receiver}`))).toBe(true);
}
});
});
- it.skip('can edit root route if one is already defined', async () => {
- const defaultConfig: AlertManagerCortexConfig = {
- alertmanager_config: {
- receivers: [{ name: 'default' }, { name: 'critical' }],
- route: {
- receiver: 'default',
- group_by: ['alertname'],
- },
- templates: [],
- },
- template_files: {},
- };
- const currentConfig = { current: defaultConfig };
- mocks.api.updateAlertManagerConfig.mockImplementation((amSourceName, newConfig) => {
- currentConfig.current = newConfig;
- return Promise.resolve();
- });
-
- mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
- return Promise.resolve(currentConfig.current);
- });
-
+ it('can edit root route if one is already defined', async () => {
const { user } = renderNotificationPolicies();
- expect(await ui.rootReceiver.find()).toHaveTextContent('default');
- expect(ui.rootGroupBy.get()).toHaveTextContent('alertname');
+ let rootRoute = await getRootRoute();
- // open root route for editing
- const rootRouteContainer = await ui.rootRouteContainer.find();
- await user.click(ui.editButton.get(rootRouteContainer));
+ expect(rootRoute).toHaveTextContent('default policy');
+ expect(rootRoute).toHaveTextContent(/delivered to grafana-default-email/i);
+ expect(rootRoute).toHaveTextContent(/grouped by alertname/i);
+
+ await openDefaultPolicyEditModal();
// configure receiver & group by
const receiverSelect = await ui.receiverSelect.find();
- await clickSelectOption(receiverSelect, 'critical');
+
+ // The contact points are fetched from the k8s API, which we aren't overriding here
+ // when we use a different
+ await clickSelectOption(receiverSelect, 'lotsa-emails');
const groupSelect = ui.groupSelect.get();
await user.type(byRole('combobox').get(groupSelect), 'namespace{enter}');
// configure timing intervals
- await user.click(byText('Timing options').get(rootRouteContainer));
+ await user.click(screen.getByText(/timing options/i));
- await updateTiming(ui.groupWaitContainer.get(), '1', 'Minutes');
- await updateTiming(ui.groupIntervalContainer.get(), '4', 'Minutes');
- await updateTiming(ui.groupRepeatContainer.get(), '5', 'Hours');
+ await updateTiming(ui.groupWaitContainer.get(), '1m');
+ await updateTiming(ui.groupIntervalContainer.get(), '4m');
+ await updateTiming(ui.groupRepeatContainer.get(), '5h');
//save
- await user.click(ui.saveButton.get(rootRouteContainer));
+ await user.click(await screen.findByRole('button', { name: /update default policy/i }));
// wait for it to go out of edit mode
- await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument());
-
- // check that appropriate api calls were made
- expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(3);
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledTimes(1);
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(GRAFANA_RULES_SOURCE_NAME, {
- alertmanager_config: {
- receivers: [{ name: 'default' }, { name: 'critical' }],
- route: {
- continue: false,
- group_by: ['alertname', 'namespace'],
- receiver: 'critical',
- routes: [],
- group_interval: '4m',
- group_wait: '1m',
- repeat_interval: '5h',
- mute_time_intervals: [],
- },
- templates: [],
- },
- template_files: {},
- });
+ expect(await screen.findByText(/updated notification policies/i)).toBeInTheDocument();
// check that new config values are rendered
- await waitFor(() => expect(ui.rootReceiver.query()).toHaveTextContent('critical'));
- expect(ui.rootGroupBy.get()).toHaveTextContent('alertname, namespace');
+ rootRoute = await getRootRoute();
+ expect(rootRoute).toHaveTextContent(/delivered to lotsa-emails/i);
+ expect(rootRoute).toHaveTextContent(/grouped by alertname, namespace/i);
});
- it.skip('can edit root route if one is not defined yet', async () => {
- mocks.api.fetchAlertManagerConfig.mockResolvedValue({
+ it('can edit root route if one is not defined yet', async () => {
+ setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, {
alertmanager_config: {
- receivers: [{ name: 'default' }],
+ route: {},
+ receivers: [{ name: 'grafana-default-email' }],
},
template_files: {},
});
-
const { user } = renderNotificationPolicies();
- // open root route for editing
- const rootRouteContainer = await ui.rootRouteContainer.find();
- await user.click(ui.editButton.get(rootRouteContainer));
+ await openDefaultPolicyEditModal();
// configure receiver & group by
const receiverSelect = await ui.receiverSelect.find();
- await clickSelectOption(receiverSelect, 'default');
+ await clickSelectOption(receiverSelect, 'lotsa-emails');
const groupSelect = ui.groupSelect.get();
await user.type(byRole('combobox').get(groupSelect), 'severity{enter}');
await user.type(byRole('combobox').get(groupSelect), 'namespace{enter}');
//save
- await user.click(ui.saveButton.get(rootRouteContainer));
+ await user.click(await screen.findByRole('button', { name: /update default policy/i }));
- // wait for it to go out of edit mode
- await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument());
+ expect(await screen.findByText(/updated notification policies/i)).toBeInTheDocument();
- // check that appropriate api calls were made
- expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(3);
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledTimes(1);
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(GRAFANA_RULES_SOURCE_NAME, {
- alertmanager_config: {
- receivers: [{ name: 'default' }],
- route: {
- continue: false,
- group_by: defaultGroupBy.concat(['severity', 'namespace']),
- receiver: 'default',
- routes: [],
- mute_time_intervals: [],
- },
- },
- template_files: {},
- });
+ const rootRoute = await getRootRoute();
+ expect(rootRoute).toHaveTextContent(/delivered to lotsa-emails/i);
+ expect(rootRoute).toHaveTextContent(/grouped by severity, namespace/i);
});
it('hides create and edit button if user does not have permission', async () => {
- mocks.contextSrv.hasPermission.mockImplementation((action) =>
- [AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsRead].includes(
- action as AccessControlAction
- )
- );
+ grantUserPermissions([
+ AccessControlAction.AlertingInstanceRead,
+ AccessControlAction.AlertingInstancesExternalRead,
+ AccessControlAction.AlertingNotificationsRead,
+ AccessControlAction.AlertingNotificationsExternalRead,
+ ]);
- renderNotificationPolicies();
- await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1));
+ const { user } = renderNotificationPolicies();
- expect(ui.newPolicyButton.query()).not.toBeInTheDocument();
+ expect(ui.newChildPolicyButton.query()).not.toBeInTheDocument();
+ expect(ui.newSiblingPolicyButton.query()).not.toBeInTheDocument();
+
+ await user.click(await ui.moreActionsDefaultPolicy.find());
expect(ui.editButton.query()).not.toBeInTheDocument();
});
it('Show error message if loading Alertmanager config fails', async () => {
- mocks.api.fetchAlertManagerConfig.mockRejectedValue({
- status: 500,
- data: {
- message: "Alertmanager has exploded. it's gone. Forget about it.",
- },
- });
-
- jest.spyOn(alertmanagerApi, 'useGetAlertmanagerAlertGroupsQuery').mockImplementation(() => ({
- currentData: [],
- refetch: jest.fn(),
- }));
+ makeAllAlertmanagerConfigFetchFail(getErrorResponse("Alertmanager has exploded. it's gone. Forget about it."));
renderNotificationPolicies();
- await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1));
+ await screen.findByText(/error loading alertmanager config/i);
expect(await byText("Alertmanager has exploded. it's gone. Forget about it.").find()).toBeInTheDocument();
- expect(ui.rootReceiver.query()).not.toBeInTheDocument();
- expect(ui.editButton.query()).not.toBeInTheDocument();
+ expect(ui.rootRouteContainer.query()).not.toBeInTheDocument();
});
- it.skip('Converts matchers to object_matchers for grafana alertmanager', async () => {
- const defaultConfig: AlertManagerCortexConfig = {
- alertmanager_config: {
- receivers: [{ name: 'default' }, { name: 'critical' }],
- route: {
- continue: false,
- receiver: 'default',
- group_by: ['alertname'],
- routes: [simpleRoute],
- group_interval: '4m',
- group_wait: '1m',
- repeat_interval: '5h',
- },
- templates: [],
- },
- template_files: {},
- };
-
- const currentConfig = { current: defaultConfig };
- mocks.api.updateAlertManagerConfig.mockImplementation((amSourceName, newConfig) => {
- currentConfig.current = newConfig;
- return Promise.resolve();
- });
-
- mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
- return Promise.resolve(currentConfig.current);
- });
-
+ it('Converts matchers to object_matchers for grafana alertmanager', async () => {
const { user } = renderNotificationPolicies(GRAFANA_RULES_SOURCE_NAME);
- expect(await ui.rootReceiver.find()).toHaveTextContent('default');
- expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled();
- // Toggle a save to test new object_matchers
- const rootRouteContainer = await ui.rootRouteContainer.find();
- await user.click(ui.editButton.get(rootRouteContainer));
- await user.click(ui.saveButton.get(rootRouteContainer));
+ const policyIndex = 0;
+ await openEditModal(policyIndex);
- await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument());
+ // Save policy to test that format is converted to object_matchers
+ await user.click(await ui.saveButton.find());
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled();
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(GRAFANA_RULES_SOURCE_NAME, {
- alertmanager_config: {
- receivers: [{ name: 'default' }, { name: 'critical' }],
- route: {
- continue: false,
- group_by: ['alertname'],
- group_interval: '4m',
- group_wait: '1m',
- receiver: 'default',
- repeat_interval: '5h',
- mute_time_intervals: [],
- routes: [
- {
- continue: false,
- group_by: [],
- object_matchers: [
- ['hello', '=', 'world'],
- ['foo', '!=', 'bar'],
- ],
- receiver: 'simple-receiver',
- mute_time_intervals: [],
- routes: [],
- },
- ],
- },
- templates: [],
- },
- template_files: {},
- });
+ expect(await screen.findByRole('status')).toHaveTextContent(/updated notification policies/i);
+
+ const updatedConfig = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
+ expect(updatedConfig.alertmanager_config.route?.routes?.[policyIndex].object_matchers).toMatchSnapshot();
});
- it.skip('Should be able to delete an empty route', async () => {
- const routeConfig = {
- continue: false,
- receiver: 'default',
- group_by: ['alertname'],
- routes: [emptyRoute],
- group_interval: '4m',
- group_wait: '1m',
- repeat_interval: '5h',
- mute_time_intervals: [],
- };
-
+ it('Should be able to delete an empty route', async () => {
const defaultConfig: AlertManagerCortexConfig = {
alertmanager_config: {
- receivers: [{ name: 'default' }, { name: 'critical' }],
- route: routeConfig,
- templates: [],
+ route: {
+ routes: [{}],
+ },
},
template_files: {},
};
- mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
- return Promise.resolve(defaultConfig);
- });
-
- mocks.api.updateAlertManagerConfig.mockResolvedValue(Promise.resolve());
+ setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, defaultConfig);
const { user } = renderNotificationPolicies(GRAFANA_RULES_SOURCE_NAME);
- await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled());
- const deleteButtons = await ui.deleteRouteButton.findAll();
- expect(deleteButtons).toHaveLength(1);
+ await user.click(await ui.moreActions.find());
+ const deleteButtons = await ui.deleteRouteButton.find();
- await user.click(deleteButtons[0]);
+ await user.click(deleteButtons);
const confirmDeleteButton = ui.confirmDeleteButton.get(ui.confirmDeleteModal.get());
expect(confirmDeleteButton).toBeInTheDocument();
await user.click(confirmDeleteButton);
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith<[string, AlertManagerCortexConfig]>(
- GRAFANA_RULES_SOURCE_NAME,
- {
- ...defaultConfig,
- alertmanager_config: {
- ...defaultConfig.alertmanager_config,
- route: {
- ...routeConfig,
- routes: [],
- },
- },
- }
- );
+ expect(await screen.findByRole('status')).toHaveTextContent(/updated notification policies/i);
+
+ expect(ui.row.query()).not.toBeInTheDocument();
});
- it.skip('Keeps matchers for non-grafana alertmanager sources', async () => {
- const defaultConfig: AlertManagerCortexConfig = {
+ it('Keeps matchers for non-grafana alertmanager sources', async () => {
+ setAlertmanagerConfig(dataSources.am.uid, {
alertmanager_config: {
receivers: [{ name: 'default' }, { name: 'critical' }],
route: {
continue: false,
receiver: 'default',
group_by: ['alertname'],
- routes: [simpleRoute],
+ routes: [
+ {
+ receiver: 'simple-receiver',
+ matchers: ['hello=world', 'foo!=bar'],
+ },
+ ],
group_interval: '4m',
group_wait: '1m',
repeat_interval: '5h',
@@ -553,79 +346,38 @@ describe('NotificationPolicies', () => {
templates: [],
},
template_files: {},
- };
-
- const currentConfig = { current: defaultConfig };
- mocks.api.updateAlertManagerConfig.mockImplementation((amSourceName, newConfig) => {
- currentConfig.current = newConfig;
- return Promise.resolve();
- });
-
- mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
- return Promise.resolve(currentConfig.current);
});
const { user } = renderNotificationPolicies(dataSources.am.name);
- expect(await ui.rootReceiver.find()).toHaveTextContent('default');
- expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled();
- // Toggle a save to test new object_matchers
- const rootRouteContainer = await ui.rootRouteContainer.find();
- await user.click(ui.editButton.get(rootRouteContainer));
- await user.click(ui.saveButton.get(rootRouteContainer));
+ const policyIndex = 0;
+ await openEditModal(policyIndex);
- await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument());
+ // Save policy to test that format is NOT converted
+ await user.click(await ui.saveButton.find());
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled();
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(dataSources.am.name, {
- alertmanager_config: {
- receivers: [{ name: 'default' }, { name: 'critical' }],
- route: {
- continue: false,
- group_by: ['alertname'],
- group_interval: '4m',
- group_wait: '1m',
- matchers: [],
- receiver: 'default',
- repeat_interval: '5h',
- mute_time_intervals: [],
- routes: [
- {
- continue: false,
- group_by: [],
- matchers: ['hello=world', 'foo!=bar'],
- receiver: 'simple-receiver',
- routes: [],
- mute_time_intervals: [],
- },
- ],
- },
- templates: [],
- },
- template_files: {},
- });
+ const updatedConfig = getAlertmanagerConfig(dataSources.am.uid);
+ expect(updatedConfig.alertmanager_config.route?.routes?.[policyIndex].matchers).toMatchSnapshot();
});
- it.skip('Prometheus Alertmanager routes cannot be edited', async () => {
- mocks.api.fetchStatus.mockResolvedValue({
+ it('Prometheus Alertmanager routes cannot be edited', async () => {
+ setAlertmanagerStatus(dataSources.promAlertManager.uid, {
...someCloudAlertManagerStatus,
config: someCloudAlertManagerConfig.alertmanager_config,
});
renderNotificationPolicies(dataSources.promAlertManager.name);
- const rootRouteContainer = await ui.rootRouteContainer.find();
- expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument();
+
+ expect(await ui.rootRouteContainer.find()).toBeInTheDocument();
+
const rows = await ui.row.findAll();
expect(rows).toHaveLength(2);
- expect(ui.editRouteButton.query()).not.toBeInTheDocument();
- expect(ui.deleteRouteButton.query()).not.toBeInTheDocument();
- expect(ui.saveButton.query()).not.toBeInTheDocument();
- expect(mocks.api.fetchAlertManagerConfig).not.toHaveBeenCalled();
- expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1);
+ expect(ui.moreActions.query()).not.toBeInTheDocument();
+ expect(ui.moreActionsDefaultPolicy.query()).not.toBeInTheDocument();
});
it('Prometheus Alertmanager has no CTA button if there are no specific policies', async () => {
- mocks.api.fetchStatus.mockResolvedValue({
+ setAlertmanagerStatus(dataSources.promAlertManager.uid, {
...someCloudAlertManagerStatus,
config: {
...someCloudAlertManagerConfig.alertmanager_config,
@@ -636,102 +388,39 @@ describe('NotificationPolicies', () => {
},
});
- jest.spyOn(alertmanagerApi, 'useGetAlertmanagerAlertGroupsQuery').mockImplementation(() => ({
- currentData: [],
- refetch: jest.fn(),
- }));
-
renderNotificationPolicies(dataSources.promAlertManager.name);
- const rootRouteContainer = await ui.rootRouteContainer.find();
- await waitFor(() =>
- expect(within(rootRouteContainer).getByTestId('matching-instances')).toHaveTextContent('0instance')
- );
- expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument();
- expect(ui.newPolicyCTAButton.query()).not.toBeInTheDocument();
- expect(mocks.api.fetchAlertManagerConfig).not.toHaveBeenCalled();
- expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1);
+ expect(await ui.rootRouteContainer.find()).toBeInTheDocument();
+
+ expect(ui.newChildPolicyButton.query()).not.toBeInTheDocument();
+ expect(ui.newSiblingPolicyButton.query()).not.toBeInTheDocument();
});
- it.skip('Can add a mute timing to a route', async () => {
- const defaultConfig: AlertManagerCortexConfig = {
- alertmanager_config: {
- receivers: [{ name: 'default' }, { name: 'critical' }],
- route: {
- continue: false,
- receiver: 'default',
- group_by: ['alertname'],
- routes: [simpleRoute],
- group_interval: '4m',
- group_wait: '1m',
- repeat_interval: '5h',
- },
- templates: [],
- mute_time_intervals: [muteInterval],
- },
- template_files: {},
- };
+ it('Can add a mute timing to a route', async () => {
+ const { user } = renderNotificationPolicies();
- const currentConfig = { current: defaultConfig };
- mocks.api.updateAlertManagerConfig.mockImplementation((amSourceName, newConfig) => {
- currentConfig.current = newConfig;
- return Promise.resolve();
- });
-
- mocks.api.fetchAlertManagerConfig.mockResolvedValue(defaultConfig);
-
- const { user } = renderNotificationPolicies(dataSources.am.name);
- const rows = await ui.row.findAll();
- expect(rows).toHaveLength(1);
- await user.click(ui.editRouteButton.get(rows[0]));
+ await openEditModal(0);
const muteTimingSelect = ui.muteTimingSelect.get();
- await clickSelectOption(muteTimingSelect, 'default-mute');
- expect(muteTimingSelect).toHaveTextContent('default-mute');
+ await clickSelectOption(muteTimingSelect, TIME_INTERVAL_NAME_HAPPY_PATH);
+ await clickSelectOption(muteTimingSelect, TIME_INTERVAL_NAME_FILE_PROVISIONED);
- const savePolicyButton = ui.savePolicyButton.get();
- expect(savePolicyButton).toBeInTheDocument();
+ await user.click(ui.saveButton.get());
- await user.click(savePolicyButton);
+ expect(await screen.findByRole('status')).toHaveTextContent(/updated notification policies/i);
- await waitFor(() => expect(savePolicyButton).not.toBeInTheDocument());
-
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled();
- expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(dataSources.am.name, {
- ...defaultConfig,
- alertmanager_config: {
- ...defaultConfig.alertmanager_config,
- route: {
- ...defaultConfig.alertmanager_config.route,
- mute_time_intervals: [],
- matchers: [],
- routes: [
- {
- ...simpleRoute,
- mute_time_intervals: [muteInterval.name],
- routes: [],
- continue: false,
- group_by: [],
- },
- ],
- },
- },
- });
+ const policy = (await ui.row.findAll())[0];
+ expect(policy).toHaveTextContent(
+ `Muted when ${TIME_INTERVAL_NAME_HAPPY_PATH}, ${TIME_INTERVAL_NAME_FILE_PROVISIONED}`
+ );
});
it.skip('Shows an empty config when config returns an error and the AM supports lazy config initialization', async () => {
- mocks.api.discoverAlertmanagerFeatures.mockResolvedValue({ lazyConfigInit: true });
+ makeAllAlertmanagerConfigFetchFail(getErrorResponse('alertmanager storage object not found'));
+ setAlertmanagerStatus(dataSources.mimir.uid, someCloudAlertManagerStatus);
+ renderNotificationPolicies(dataSources.mimir.name);
- mocks.api.fetchAlertManagerConfig.mockRejectedValue({
- message: 'alertmanager storage object not found',
- });
-
- renderNotificationPolicies();
-
- await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1));
-
- expect(ui.rootReceiver.query()).toBeInTheDocument();
- expect(ui.setDefaultReceiverCTA.query()).toBeInTheDocument();
+ expect(await ui.rootRouteContainer.find()).toBeInTheDocument();
});
});
@@ -806,19 +495,3 @@ describe('findRoutesMatchingFilters', () => {
expect(matchingRoutes).toMatchSnapshot();
});
});
-
-const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise => {
- const user = userEvent.setup();
- await user.click(byRole('combobox').get(selectElement));
- await selectOptionInTest(selectElement, optionText);
-};
-
-const updateTiming = async (selectElement: HTMLElement, value: string, timeUnit: string): Promise => {
- const user = userEvent.setup();
- const input = byRole('textbox').get(selectElement);
- const select = byRole('combobox').get(selectElement);
- await user.clear(input);
- await user.type(input, value);
- await user.click(select);
- await selectOptionInTest(selectElement, timeUnit);
-};
diff --git a/public/app/features/alerting/unified/NotificationPolicies.tsx b/public/app/features/alerting/unified/NotificationPolicies.tsx
index ba1f5025330..beb1b908abe 100644
--- a/public/app/features/alerting/unified/NotificationPolicies.tsx
+++ b/public/app/features/alerting/unified/NotificationPolicies.tsx
@@ -4,6 +4,7 @@ import { useAsyncFn } from 'react-use';
import { GrafanaTheme2, UrlQueryMap } from '@grafana/data';
import { Alert, LoadingPlaceholder, Stack, Tab, TabContent, TabsBar, useStyles2, withErrorBoundary } from '@grafana/ui';
+import { useAppNotification } from 'app/core/copy/appNotification';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { useMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
import { ObjectMatcher, Route, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
@@ -52,6 +53,7 @@ enum ActiveTab {
const AmRoutes = () => {
const dispatch = useDispatch();
const styles = useStyles2(getStyles);
+ const appNotification = useAppNotification();
const { useGetAlertmanagerAlertGroupsQuery } = alertmanagerApi;
@@ -175,11 +177,11 @@ const AmRoutes = () => {
},
oldConfig: result,
alertManagerSourceName: selectedAlertmanager!,
- successMessage: 'Updated notification policies',
})
)
.unwrap()
.then(() => {
+ appNotification.success('Updated notification policies');
if (selectedAlertmanager) {
refetchAlertGroups();
}
diff --git a/public/app/features/alerting/unified/__snapshots__/NotificationPolicies.test.tsx.snap b/public/app/features/alerting/unified/__snapshots__/NotificationPolicies.test.tsx.snap
index 9011ec04694..17c630e4c21 100644
--- a/public/app/features/alerting/unified/__snapshots__/NotificationPolicies.test.tsx.snap
+++ b/public/app/features/alerting/unified/__snapshots__/NotificationPolicies.test.tsx.snap
@@ -1,5 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`NotificationPolicies Converts matchers to object_matchers for grafana alertmanager 1`] = `
+[
+ [
+ "sub1matcher1",
+ "=",
+ "sub1value1",
+ ],
+ [
+ "sub1matcher2",
+ "=",
+ "sub1value2",
+ ],
+ [
+ "sub1matcher3",
+ "=~",
+ "sub1value3",
+ ],
+ [
+ "sub1matcher4",
+ "=~",
+ "sub1value4",
+ ],
+]
+`;
+
+exports[`NotificationPolicies Keeps matchers for non-grafana alertmanager sources 1`] = `
+[
+ "hello="world"",
+ "foo!="bar"",
+]
+`;
+
exports[`findRoutesMatchingFilters should not match non-existing 1`] = `
{
"filtersApplied": true,
diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json b/public/app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json
deleted file mode 100644
index 0077f89f3a0..00000000000
--- a/public/app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json
+++ /dev/null
@@ -1,106 +0,0 @@
-{
- "template_files": {
- "slack-template": "{{ define \"slack-template\" }} Custom slack template {{ end }}",
- "custom-email": "{{ define \"custom-email\" }} Custom email template {{ end }}",
- "provisioned-template": "{{ define \"provisioned-template\" }} Custom provisioned template {{ end }}",
- "template with spaces": "{{ define \"template with spaces\" }} Custom template with spaces in the name {{ end }}",
- "misconfigured-template": "{{ define \"misconfigured template\" }} Template that is defined in template_files but not templates {{ end }}",
- "misconfigured and provisioned": "{{ define \"misconfigured and provisioned template\" }} Provisioned template that is defined in template_files but not templates {{ end }}"
- },
- "template_file_provenances": {
- "provisioned-template": "api",
- "misconfigured and provisioned": "api"
- },
- "alertmanager_config": {
- "route": {
- "receiver": "grafana-default-email",
- "routes": [
- {
- "receiver": "provisioned-contact-point"
- }
- ]
- },
- "receivers": [
- {
- "name": "grafana-default-email",
- "grafana_managed_receiver_configs": [
- {
- "uid": "xeKQrBrnk",
- "name": "grafana-default-email",
- "type": "email",
- "disableResolveMessage": false,
- "settings": { "addresses": "gilles.demey@grafana.com", "singleEmail": false },
- "secureFields": {}
- }
- ]
- },
- {
- "name": "provisioned-contact-point",
- "grafana_managed_receiver_configs": [
- {
- "uid": "s8SdCVjnk",
- "name": "provisioned-contact-point",
- "type": "email",
- "disableResolveMessage": false,
- "settings": { "addresses": "gilles.demey@grafana.com", "singleEmail": false },
- "secureFields": {},
- "provenance": "api"
- }
- ]
- },
- {
- "name": "lotsa-emails",
- "grafana_managed_receiver_configs": [
- {
- "uid": "af306c96-35a2-4d6e-908a-4993e245dbb2",
- "name": "lotsa-emails",
- "type": "email",
- "disableResolveMessage": false,
- "settings": {
- "addresses": "gilles.demey+1@grafana.com, gilles.demey+2@grafana.com, gilles.demey+3@grafana.com, gilles.demey+4@grafana.com",
- "singleEmail": false
- },
- "secureFields": {}
- }
- ]
- },
- {
- "name": "Slack with multiple channels",
- "grafana_managed_receiver_configs": [
- {
- "uid": "c02ad56a-31da-46b9-becb-4348ec0890fd",
- "name": "Slack with multiple channels",
- "type": "slack",
- "disableResolveMessage": false,
- "settings": { "recipient": "test-alerts" },
- "secureFields": { "token": true }
- },
- {
- "uid": "b286a3be-f690-49e2-8605-b075cbace2df",
- "name": "Slack with multiple channels",
- "type": "slack",
- "disableResolveMessage": false,
- "settings": { "recipient": "test-alerts2" },
- "secureFields": { "token": true }
- }
- ]
- },
- {
- "name": "OnCall Conctact point",
- "grafana_managed_receiver_configs": [
- {
- "name": "Oncall-integration",
- "type": "oncall",
- "settings": {
- "url": "https://oncall-endpoint.example.com"
- },
- "disableResolveMessage": false
- }
- ]
- }
- ],
- "templates": ["slack-template", "custom-email", "provisioned-template", "template with spaces"],
- "time_intervals": [],
- "mute_time_intervals": []
- }
-}
diff --git a/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx
index cac835fad21..eb63d6cbec6 100644
--- a/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx
+++ b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx
@@ -3,10 +3,8 @@ import { render, screen, userEvent, within } from 'test/test-utils';
import { config } from '@grafana/runtime';
import { defaultConfig } from 'app/features/alerting/unified/MuteTimings.test';
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
-import {
- setGrafanaAlertmanagerConfig,
- setMuteTimingsListError,
-} from 'app/features/alerting/unified/mocks/server/configure';
+import { setMuteTimingsListError } from 'app/features/alerting/unified/mocks/server/configure';
+import { setAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types';
@@ -31,7 +29,7 @@ setupMswServer();
describe('MuteTimingsTable', () => {
describe('with necessary permissions', () => {
beforeEach(() => {
- setGrafanaAlertmanagerConfig(defaultConfig);
+ setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, defaultConfig);
config.featureToggles.alertingApiServer = false;
grantUserPermissions([
AccessControlAction.AlertingNotificationsRead,
diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx
index 1e3a0af1583..2985c7e20a6 100644
--- a/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx
+++ b/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx
@@ -133,8 +133,8 @@ describe('Policy', () => {
expect(within(firstPolicy).getByTestId('continue-matching')).toBeInTheDocument();
// expect(within(firstPolicy).getByTestId('matching-instances')).toHaveTextContent('0instances');
expect(within(firstPolicy).getByTestId('contact-point')).toHaveTextContent('provisioned-contact-point');
- expect(within(firstPolicy).getByTestId('mute-timings')).toHaveTextContent('Muted whenmt-1');
- expect(within(firstPolicy).getByTestId('active-timings')).toHaveTextContent('Active whenmt-2');
+ expect(within(firstPolicy).getByTestId('mute-timings')).toHaveTextContent('Muted when mt-1');
+ expect(within(firstPolicy).getByTestId('active-timings')).toHaveTextContent('Active when mt-2');
expect(within(firstPolicy).getByTestId('inherited-properties')).toHaveTextContent('Inherited2 properties');
// second custom policy should be correct
diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx
index 0dbbc61072e..d5e4f4c8b04 100644
--- a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx
+++ b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx
@@ -23,6 +23,7 @@ import {
} from '@grafana/ui';
import { t, Trans } from 'app/core/internationalization';
import ConditionalWrap from 'app/features/alerting/unified/components/ConditionalWrap';
+import MoreButton from 'app/features/alerting/unified/components/MoreButton';
import { PrimaryText } from 'app/features/alerting/unified/components/common/TextVariants';
import {
AlertmanagerGroup,
@@ -308,12 +309,8 @@ const Policy = (props: PolicyComponentProps) => {
)}
{dropdownMenuActions.length > 0 && (
{dropdownMenuActions}}>
-
@@ -469,7 +466,7 @@ function MetadataRow({
{contactPoint && (
- Delivered to
+ Delivered to{' '}
- Grouped by
+ Grouped by{' '}
{groupBy.join(', ')}
@@ -507,7 +504,7 @@ function MetadataRow({
{hasMuteTimings && (
- Muted when
+ Muted when{' '}
@@ -515,7 +512,7 @@ function MetadataRow({
{hasActiveTimings && (
- Active when
+ Active when{' '}
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 93a0114bc17..9f0b8b7620b 100644
--- a/public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx
+++ b/public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx
@@ -3,7 +3,7 @@ import { Route } from 'react-router';
import { render, screen } from 'test/test-utils';
import { byLabelText, byPlaceholderText, byRole, byTestId } from 'testing-library-selector';
-import { makeGrafanaAlertmanagerConfigUpdateFail } from 'app/features/alerting/unified/mocks/server/configure';
+import { makeAlertmanagerConfigUpdateFail } from 'app/features/alerting/unified/mocks/server/configure';
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
import { AccessControlAction } from 'app/types';
@@ -108,7 +108,7 @@ describe('alerting API server disabled', () => {
});
it('does not redirect when creating contact point and API errors', async () => {
- makeGrafanaAlertmanagerConfigUpdateFail();
+ makeAlertmanagerConfigUpdateFail();
const { user } = renderForm();
await user.type(await ui.inputs.name.find(), 'receiver that should fail');
diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.test.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.test.tsx
index 03266950cac..48ca29c88e0 100644
--- a/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.test.tsx
+++ b/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.test.tsx
@@ -2,9 +2,9 @@ import { ReactNode } from 'react';
import { render, screen, userEvent } from 'test/test-utils';
import { CodeEditorProps } from '@grafana/ui/src/components/Monaco/types';
-import alertmanagerConfigMock from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json';
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { grantUserPermissions } from 'app/features/alerting/unified/mocks';
+import { getAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext';
import { testWithFeatureToggles } from 'app/features/alerting/unified/test/test-utils';
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
@@ -15,6 +15,8 @@ import { AccessControlAction, NotificationChannelOption } from 'app/types';
import { getTemplateOptions, TemplatesPicker } from './TemplateSelector';
import { parseTemplates } from './utils';
+const alertmanagerConfigMock = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
+
jest.mock('@grafana/ui', () => ({
...jest.requireActual('@grafana/ui'),
CodeEditor: ({ value, onChange }: CodeEditorProps) => (
diff --git a/public/app/features/alerting/unified/mockApi.ts b/public/app/features/alerting/unified/mockApi.ts
index 6fb410301d3..d3c7077edd7 100644
--- a/public/app/features/alerting/unified/mockApi.ts
+++ b/public/app/features/alerting/unified/mockApi.ts
@@ -5,6 +5,10 @@ import { DataSourceInstanceSettings } from '@grafana/data';
import { setBackendSrv } from '@grafana/runtime';
import { AlertGroupUpdated } from 'app/features/alerting/unified/api/alertRuleApi';
import allHandlers from 'app/features/alerting/unified/mocks/server/all-handlers';
+import {
+ setupAlertmanagerConfigMapDefaultState,
+ setupAlertmanagerStatusMapDefaultState,
+} from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
import { DashboardDTO, FolderDTO, OrgUser } from 'app/types';
import {
PromBuildInfoResponse,
@@ -289,6 +293,10 @@ export function setupMswServer() {
afterEach(() => {
server.resetHandlers();
+
+ // Reset any other necessary mock entities/state
+ setupAlertmanagerConfigMapDefaultState();
+ setupAlertmanagerStatusMapDefaultState();
});
afterAll(() => {
diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts
index 632868a90e0..4c5dfdd11e9 100644
--- a/public/app/features/alerting/unified/mocks.ts
+++ b/public/app/features/alerting/unified/mocks.ts
@@ -512,6 +512,7 @@ export const someGrafanaAlertManagerConfig: AlertManagerCortexConfig = {
},
};
+/** @deprecated Move into alertmanager status entities */
export const someCloudAlertManagerStatus: AlertmanagerStatus = {
cluster: {
peers: [],
@@ -543,6 +544,7 @@ export const someCloudAlertManagerStatus: AlertmanagerStatus = {
},
};
+/** @deprecated Move into alertmanager config entities */
export const someCloudAlertManagerConfig: AlertManagerCortexConfig = {
template_files: {
'foo template': 'foo content',
diff --git a/public/app/features/alerting/unified/mocks/server/configure.ts b/public/app/features/alerting/unified/mocks/server/configure.ts
index 30dda904f48..8d0ba294de1 100644
--- a/public/app/features/alerting/unified/mocks/server/configure.ts
+++ b/public/app/features/alerting/unified/mocks/server/configure.ts
@@ -4,11 +4,9 @@ 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,
+ updateAlertmanagerConfigHandler,
} 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';
@@ -18,7 +16,7 @@ import {
} from 'app/features/alerting/unified/mocks/server/handlers/plugins';
import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges';
import { clearPluginSettingsCache } from 'app/features/plugins/pluginSettings';
-import { AlertManagerCortexConfig, AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
+import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
import { FolderDTO } from 'app/types';
import { setupDataSources } from '../../testSetup/datasources';
@@ -60,20 +58,6 @@ export const setFolderResponse = (response: Partial) => {
server.use(handler);
};
-/**
- * Makes the mock server respond with different Grafana Alertmanager config
- */
-export const setGrafanaAlertmanagerConfig = (config: AlertManagerCortexConfig) => {
- server.use(getGrafanaAlertmanagerConfigHandler(config));
-};
-
-/**
- * Makes the mock server respond with different (other) Alertmanager config
- */
-export const setAlertmanagerConfig = (config: AlertManagerCortexConfig) => {
- server.use(getAlertmanagerConfigHandler(config));
-};
-
/**
* Makes the mock server respond with different responses for updating a ruler namespace
*/
@@ -139,7 +123,26 @@ export const disablePlugin = (pluginId: SupportedPlugin) => {
server.use(getDisabledPluginHandler(pluginId));
};
+/** Get an error response for use in a API response, in the format:
+ * ```
+ * {
+ * message: string,
+ * }
+ * ```
+ */
+export const getErrorResponse = (message: string, status = 500) => HttpResponse.json({ message }, { status });
+
+const defaultError = getErrorResponse('Unknown error');
/** Make alertmanager config update fail */
-export const makeGrafanaAlertmanagerConfigUpdateFail = () => {
- server.use(updateGrafanaAlertmanagerConfigHandler(ALERTMANAGER_UPDATE_ERROR_RESPONSE));
+export const makeAlertmanagerConfigUpdateFail = (
+ responseOverride: ReturnType = defaultError
+) => {
+ server.use(updateAlertmanagerConfigHandler(responseOverride));
+};
+
+/** Make fetching alertmanager config fail */
+export const makeAllAlertmanagerConfigFetchFail = (
+ responseOverride: ReturnType = defaultError
+) => {
+ server.use(getAlertmanagerConfigHandler(responseOverride));
};
diff --git a/public/app/features/alerting/unified/mocks/server/entities/alertmanager-config/grafana-alertmanager-config.ts b/public/app/features/alerting/unified/mocks/server/entities/alertmanager-config/grafana-alertmanager-config.ts
new file mode 100644
index 00000000000..1ddaa209483
--- /dev/null
+++ b/public/app/features/alerting/unified/mocks/server/entities/alertmanager-config/grafana-alertmanager-config.ts
@@ -0,0 +1,190 @@
+import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
+
+const grafanaAlertmanagerConfig: AlertManagerCortexConfig = {
+ template_files: {
+ 'slack-template': '{{ define "slack-template" }} Custom slack template {{ end }}',
+ 'custom-email': '{{ define "custom-email" }} Custom email template {{ end }}',
+ 'provisioned-template': '{{ define "provisioned-template" }} Custom provisioned template {{ end }}',
+ 'template with spaces': '{{ define "template with spaces" }} Custom template with spaces in the name {{ end }}',
+ 'misconfigured-template':
+ '{{ define "misconfigured template" }} Template that is defined in template_files but not templates {{ end }}',
+ 'misconfigured and provisioned':
+ '{{ define "misconfigured and provisioned template" }} Provisioned template that is defined in template_files but not templates {{ end }}',
+ },
+ template_file_provenances: {
+ 'provisioned-template': 'api',
+ 'misconfigured and provisioned': 'api',
+ },
+ alertmanager_config: {
+ route: {
+ group_by: ['alertname'],
+ receiver: 'grafana-default-email',
+ routes: [
+ {
+ match: {
+ sub1matcher1: 'sub1value1',
+ sub1matcher2: 'sub1value2',
+ },
+ match_re: {
+ sub1matcher3: 'sub1value3',
+ sub1matcher4: 'sub1value4',
+ },
+ group_by: ['sub1group1', 'sub1group2'],
+ receiver: 'a-receiver',
+ continue: true,
+ group_wait: '3s',
+ group_interval: '2m',
+ repeat_interval: '3m',
+ routes: [
+ {
+ match: {
+ sub1sub1matcher1: 'sub1sub1value1',
+ sub1sub1matcher2: 'sub1sub1value2',
+ },
+ match_re: {
+ sub1sub1matcher3: 'sub1sub1value3',
+ sub1sub1matcher4: 'sub1sub1value4',
+ },
+ group_by: ['sub1sub1group1', 'sub1sub1group2'],
+ receiver: 'another-receiver',
+ },
+ {
+ match: {
+ sub1sub2matcher1: 'sub1sub2value1',
+ sub1sub2matcher2: 'sub1sub2value2',
+ },
+ match_re: {
+ sub1sub2matcher3: 'sub1sub2value3',
+ sub1sub2matcher4: 'sub1sub2value4',
+ },
+ group_by: ['sub1sub2group1', 'sub1sub2group2'],
+ receiver: 'another-receiver',
+ },
+ ],
+ },
+ {
+ match: {
+ sub2matcher1: 'sub2value1',
+ sub2matcher2: 'sub2value2',
+ },
+ match_re: {
+ sub2matcher3: 'sub2value3',
+ sub2matcher4: 'sub2value4',
+ },
+ receiver: 'another-receiver',
+ },
+ {
+ receiver: 'provisioned-contact-point',
+ },
+ ],
+ },
+ receivers: [
+ {
+ name: 'grafana-default-email',
+ grafana_managed_receiver_configs: [
+ {
+ uid: 'xeKQrBrnk',
+ name: 'grafana-default-email',
+ type: 'email',
+ disableResolveMessage: false,
+ settings: {
+ addresses: 'gilles.demey@grafana.com',
+ singleEmail: false,
+ },
+ secureFields: {},
+ },
+ ],
+ },
+ {
+ name: 'provisioned-contact-point',
+ grafana_managed_receiver_configs: [
+ {
+ uid: 's8SdCVjnk',
+ name: 'provisioned-contact-point',
+ type: 'email',
+ disableResolveMessage: false,
+ settings: {
+ addresses: 'gilles.demey@grafana.com',
+ singleEmail: false,
+ },
+ secureFields: {},
+ provenance: 'api',
+ },
+ ],
+ },
+ {
+ name: 'lotsa-emails',
+ grafana_managed_receiver_configs: [
+ {
+ uid: 'af306c96-35a2-4d6e-908a-4993e245dbb2',
+ name: 'lotsa-emails',
+ type: 'email',
+ disableResolveMessage: false,
+ settings: {
+ addresses:
+ 'gilles.demey+1@grafana.com, gilles.demey+2@grafana.com, gilles.demey+3@grafana.com, gilles.demey+4@grafana.com',
+ singleEmail: false,
+ },
+ secureFields: {},
+ },
+ ],
+ },
+ {
+ name: 'Slack with multiple channels',
+ grafana_managed_receiver_configs: [
+ {
+ uid: 'c02ad56a-31da-46b9-becb-4348ec0890fd',
+ name: 'Slack with multiple channels',
+ type: 'slack',
+ disableResolveMessage: false,
+ settings: {
+ recipient: 'test-alerts',
+ },
+ secureFields: {
+ token: true,
+ },
+ },
+ {
+ uid: 'b286a3be-f690-49e2-8605-b075cbace2df',
+ name: 'Slack with multiple channels',
+ type: 'slack',
+ disableResolveMessage: false,
+ settings: {
+ recipient: 'test-alerts2',
+ },
+ secureFields: {
+ token: true,
+ },
+ },
+ ],
+ },
+ {
+ name: 'OnCall Conctact point',
+ grafana_managed_receiver_configs: [
+ {
+ name: 'Oncall-integration',
+ type: 'oncall',
+ settings: {
+ url: 'https://oncall-endpoint.example.com',
+ },
+ disableResolveMessage: false,
+ },
+ ],
+ },
+ ],
+ templates: ['slack-template', 'custom-email', 'provisioned-template', 'template with spaces'],
+ time_intervals: [
+ {
+ name: 'Some interval',
+ time_intervals: [],
+ },
+ {
+ name: 'A provisioned interval',
+ time_intervals: [],
+ },
+ ],
+ mute_time_intervals: [],
+ },
+} as const;
+
+export default grafanaAlertmanagerConfig;
diff --git a/public/app/features/alerting/unified/mocks/server/entities/alertmanagers.ts b/public/app/features/alerting/unified/mocks/server/entities/alertmanagers.ts
new file mode 100644
index 00000000000..061dba64e57
--- /dev/null
+++ b/public/app/features/alerting/unified/mocks/server/entities/alertmanagers.ts
@@ -0,0 +1,62 @@
+import grafanaAlertmanagerConfig from 'app/features/alerting/unified/mocks/server/entities/alertmanager-config/grafana-alertmanager-config';
+import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
+import { AlertManagerCortexConfig, AlertmanagerStatus } from 'app/plugins/datasource/alertmanager/types';
+
+//////////////////////////
+// Alertmanager configs //
+//////////////////////////
+
+/** **INITIAL** state of alertmanager configs for different scenarios */
+const ALERTMANAGER_CONFIGS: Record = {
+ // TODO in followup PR: Move mock AM config to TS file rather than JSON
+ [GRAFANA_RULES_SOURCE_NAME]: grafanaAlertmanagerConfig,
+};
+
+let ALERTMANAGER_CONFIG_MAP: Map = new Map(Object.entries(ALERTMANAGER_CONFIGS));
+
+/** Setup/reset alertmanager configs for our mock server */
+export const setupAlertmanagerConfigMapDefaultState = () => {
+ ALERTMANAGER_CONFIG_MAP = new Map(Object.entries(ALERTMANAGER_CONFIGS));
+};
+
+/**
+ * "Save" a new individual alertmanager config to our internal map
+ */
+export const setAlertmanagerConfig = (alertmanagerName: string, config: AlertManagerCortexConfig) => {
+ ALERTMANAGER_CONFIG_MAP.set(alertmanagerName, config);
+};
+
+/**
+ * Get alertmanager config from internal map, for use in assertions
+ */
+export const getAlertmanagerConfig = (alertmanagerName: string) => {
+ return ALERTMANAGER_CONFIG_MAP.get(alertmanagerName)!;
+};
+
+///////////////////////////
+// Alertmanager statuses //
+///////////////////////////
+
+/** **INITIAL** state of alertmanager configs for different scenarios */
+const ALERTMANAGER_STATUSES: Record = {};
+
+let ALERTMANAGER_STATUS_MAP: Map = new Map(Object.entries(ALERTMANAGER_STATUSES));
+
+/** Setup/reset alertmanager statuses for our mock server */
+export const setupAlertmanagerStatusMapDefaultState = () => {
+ ALERTMANAGER_STATUS_MAP = new Map(Object.entries(ALERTMANAGER_STATUSES));
+};
+
+/**
+ * "Save" a new individual alertmanager config to our internal map
+ */
+export const setAlertmanagerStatus = (alertmanagerName: string, config: AlertmanagerStatus) => {
+ ALERTMANAGER_STATUS_MAP.set(alertmanagerName, config);
+};
+
+/**
+ * Get alertmanager config from internal map, for use in assertions
+ */
+export const getAlertmanagerStatus = (alertmanagerName: string) => {
+ return ALERTMANAGER_STATUS_MAP.get(alertmanagerName)!;
+};
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 6677e2cace2..fad8569ced9 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,13 @@
-import { http, HttpResponse } from 'msw';
+import { http, HttpResponse, JsonBodyType, StrictResponse } from 'msw';
-import alertmanagerConfigMock from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json';
import receiversMock from 'app/features/alerting/unified/components/contact-points/__mocks__/receivers.mock.json';
import { MOCK_SILENCE_ID_EXISTING, mockAlertmanagerAlert } from 'app/features/alerting/unified/mocks';
import { defaultGrafanaAlertingConfigurationStatusResponse } from 'app/features/alerting/unified/mocks/alertmanagerApi';
+import {
+ getAlertmanagerConfig,
+ getAlertmanagerStatus,
+ setAlertmanagerConfig,
+} from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
import { MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER } from 'app/features/alerting/unified/mocks/server/handlers/datasources';
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
import { AlertManagerCortexConfig, AlertState } from 'app/plugins/datasource/alertmanager/types';
@@ -58,11 +62,32 @@ export const alertmanagerAlertsListHandler = () =>
]);
});
-export const getGrafanaAlertmanagerConfigHandler = (config: AlertManagerCortexConfig = alertmanagerConfigMock) =>
- http.get('/api/alertmanager/grafana/config/api/v1/alerts', () => HttpResponse.json(config));
+export const getAlertmanagerConfigHandler = (responseOverride?: StrictResponse) =>
+ http.get<{ name: string }>('/api/alertmanager/:name/config/api/v1/alerts', ({ params }) => {
+ if (responseOverride) {
+ return responseOverride;
+ }
+ const { name: alertmanagerName } = params;
-export const getAlertmanagerConfigHandler = (config: AlertManagerCortexConfig = alertmanagerConfigMock) =>
- http.get('/api/alertmanager/:name/config/api/v1/alerts', () => HttpResponse.json(config));
+ const configToReturn = getAlertmanagerConfig(alertmanagerName);
+
+ if (configToReturn) {
+ return HttpResponse.json(configToReturn);
+ }
+ return HttpResponse.json({ message: 'Not found.' }, { status: 404 });
+ });
+
+const getAlertmanagerStatusHandler = () =>
+ http.get<{ name: string }>('/api/alertmanager/:name/api/v2/status', ({ params }) => {
+ const { name: alertmanagerName } = params;
+
+ const statusToReturn = getAlertmanagerStatus(alertmanagerName);
+
+ if (statusToReturn) {
+ return HttpResponse.json(statusToReturn);
+ }
+ return HttpResponse.json({ message: 'data source not found', traceID: '' }, { status: 404 });
+ });
export const ALERTMANAGER_UPDATE_ERROR_RESPONSE = HttpResponse.json({ message: 'bad request' }, { status: 400 });
@@ -92,20 +117,20 @@ const validateGrafanaAlertmanagerConfig = (config: AlertManagerCortexConfig) =>
return null;
};
-export const updateGrafanaAlertmanagerConfigHandler = (responseOverride?: typeof ALERTMANAGER_UPDATE_ERROR_RESPONSE) =>
- http.post('/api/alertmanager/grafana/config/api/v1/alerts', async ({ request }) => {
+export const updateAlertmanagerConfigHandler = (responseOverride?: typeof ALERTMANAGER_UPDATE_ERROR_RESPONSE) =>
+ http.post<{ name: string }>('/api/alertmanager/:name/config/api/v1/alerts', async ({ request, params }) => {
if (responseOverride) {
return responseOverride;
}
+ const { name: alertmanagerName } = params;
const body: AlertManagerCortexConfig = await request.clone().json();
+ // TODO: Validate the config depending on alertmanager type
+ // e.g. validate other AMs differently where required for tests
const potentialError = validateGrafanaAlertmanagerConfig(body);
- return potentialError ? potentialError : HttpResponse.json({ message: 'configuration created' });
- });
-
-const updateAlertmanagerConfigHandler = () =>
- http.post('/api/alertmanager/:name/config/api/v1/alerts', async ({ request }) => {
- const body: AlertManagerCortexConfig = await request.clone().json();
- const potentialError = validateGrafanaAlertmanagerConfig(body);
+ if (!potentialError) {
+ // Only update the mock entity the endpoint is going to "succeed"
+ setAlertmanagerConfig(alertmanagerName, body);
+ }
return potentialError ? potentialError : HttpResponse.json({ message: 'configuration created' });
});
@@ -140,13 +165,12 @@ const getGroupsHandler = () =>
const handlers = [
alertmanagerAlertsListHandler(),
grafanaAlertingConfigurationStatusHandler(),
- getGrafanaAlertmanagerConfigHandler(),
getAlertmanagerConfigHandler(),
- updateGrafanaAlertmanagerConfigHandler(),
updateAlertmanagerConfigHandler(),
getGrafanaAlertmanagerTemplatePreview(),
getReceiversHandler(),
testReceiversHandler(),
getGroupsHandler(),
+ getAlertmanagerStatusHandler(),
];
export default handlers;
diff --git a/public/app/features/alerting/unified/mocks/server/handlers/datasources.ts b/public/app/features/alerting/unified/mocks/server/handlers/datasources.ts
index e07327c6a1a..7fd61088daa 100644
--- a/public/app/features/alerting/unified/mocks/server/handlers/datasources.ts
+++ b/public/app/features/alerting/unified/mocks/server/handlers/datasources.ts
@@ -1,15 +1,32 @@
import { http, HttpResponse } from 'msw';
+import { buildInfoResponse } from 'app/features/alerting/unified/testSetup/featureDiscovery';
+
/** UID of the alertmanager that is expected to be broken in tests */
export const MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER = 'FwkfQfEmYlAthB';
/** Display name of the alertmanager that is expected to be broken in tests */
export const MOCK_DATASOURCE_NAME_BROKEN_ALERTMANAGER = 'broken alertmanager';
export const MOCK_DATASOURCE_EXTERNAL_VANILLA_ALERTMANAGER_UID = 'vanilla-alertmanager';
export const MOCK_DATASOURCE_PROVISIONED_MIMIR_ALERTMANAGER_UID = 'provisioned-alertmanager';
+export const MOCK_DATASOURCE_GRAFANA_MIMIR = 'grafana-mimir';
+
+const isSupportedType = (uid: string): uid is keyof typeof buildInfoResponse => {
+ return uid in buildInfoResponse;
+};
// TODO: Add more accurate endpoint responses as tests require
export const datasourceBuildInfoHandler = () =>
- http.get('/api/datasources/proxy/uid/:datasourceUid/api/v1/status/buildinfo', () => HttpResponse.json({}));
+ http.get<{ datasourceUid: keyof typeof buildInfoResponse | string }>(
+ '/api/datasources/proxy/uid/:datasourceUid/api/v1/status/buildinfo',
+ ({ params }) => {
+ const { datasourceUid } = params;
+ if (isSupportedType(datasourceUid)) {
+ const response = buildInfoResponse[datasourceUid];
+ return HttpResponse.json(response);
+ }
+ return HttpResponse.json({});
+ }
+ );
const datasourcesHandlers = [datasourceBuildInfoHandler()];
export default datasourcesHandlers;
diff --git a/public/app/features/alerting/unified/mocks/server/handlers/k8s/receivers.k8s.ts b/public/app/features/alerting/unified/mocks/server/handlers/k8s/receivers.k8s.ts
index e4718b82498..84588563a0c 100644
--- a/public/app/features/alerting/unified/mocks/server/handlers/k8s/receivers.k8s.ts
+++ b/public/app/features/alerting/unified/mocks/server/handlers/k8s/receivers.k8s.ts
@@ -1,13 +1,13 @@
import { camelCase } from 'lodash';
import { HttpResponse, http } from 'msw';
-import alertmanagerConfig from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json';
+import { getAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
import { ALERTING_API_SERVER_BASE_URL, getK8sResponse } from 'app/features/alerting/unified/mocks/server/utils';
import { ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Receiver } from 'app/features/alerting/unified/openapi/receiversApi.gen';
+import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
import { PROVENANCE_NONE, K8sAnnotations } from 'app/features/alerting/unified/utils/k8s/constants';
-import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
-const config: AlertManagerCortexConfig = alertmanagerConfig;
+const config = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
// Turn our mock alertmanager config into the format that we expect to be returned by the k8s API
const mappedReceivers =
diff --git a/public/app/features/alerting/unified/mocks/server/handlers/k8s/templates.k8s.ts b/public/app/features/alerting/unified/mocks/server/handlers/k8s/templates.k8s.ts
index 1287944544f..f3baa318342 100644
--- a/public/app/features/alerting/unified/mocks/server/handlers/k8s/templates.k8s.ts
+++ b/public/app/features/alerting/unified/mocks/server/handlers/k8s/templates.k8s.ts
@@ -1,12 +1,12 @@
import { HttpResponse, http } from 'msw';
-import alertmanagerConfig from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json';
+import { getAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
import { ALERTING_API_SERVER_BASE_URL, getK8sResponse } from 'app/features/alerting/unified/mocks/server/utils';
import { ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TemplateGroup } from 'app/features/alerting/unified/openapi/templatesApi.gen';
+import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
import { PROVENANCE_ANNOTATION, PROVENANCE_NONE } from 'app/features/alerting/unified/utils/k8s/constants';
-import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
-const config: AlertManagerCortexConfig = alertmanagerConfig;
+const config = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
// Map alertmanager templates to k8s templates
const mappedTemplates = Object.entries(
diff --git a/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts b/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts
index 6628ef5485f..2e08a15c640 100644
--- a/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts
+++ b/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts
@@ -1,11 +1,11 @@
import { HttpResponse, http } from 'msw';
-import alertmanagerConfig from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json';
-import { GrafanaManagedContactPoint, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
+import { getAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
+import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
-const defaultReceiversResponse: GrafanaManagedContactPoint[] = alertmanagerConfig.alertmanager_config.receivers;
-
-const defaultTimeIntervalsResponse: MuteTimeInterval[] = alertmanagerConfig.alertmanager_config.time_intervals;
+const alertmanagerConfig = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
+const defaultReceiversResponse = alertmanagerConfig.alertmanager_config.receivers;
+const defaultTimeIntervalsResponse = alertmanagerConfig.alertmanager_config.time_intervals;
const getNotificationReceiversHandler = (response = defaultReceiversResponse) =>
http.get('/api/v1/notifications/receivers', () => HttpResponse.json(response));