mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Fix notification policies tests and implement "stateful" mock endpoints (#94732)
This commit is contained in:
parent
a46ff09bf9
commit
4c5483ee15
@ -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(<NewMuteTimingPage />, {
|
||||
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(<NewMuteTimingPage />, {
|
||||
search: `?alertmanager=${alertmanagerName}`,
|
||||
search: `?alertmanager=${dataSources.am.name}`,
|
||||
});
|
||||
|
||||
expect(ui.nameField.get()).toBeInTheDocument();
|
||||
|
@ -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(<NotificationPolicies />, {
|
||||
historyOptions: {
|
||||
initialEntries: [
|
||||
'/alerting/routes' +
|
||||
(alertManagerSourceName ? `?${ALERTMANAGER_NAME_QUERY_KEY}=${alertManagerSourceName}` : ''),
|
||||
],
|
||||
},
|
||||
});
|
||||
const updateTiming = async (selectElement: HTMLElement, value: string): Promise<void> => {
|
||||
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(
|
||||
<>
|
||||
<AppNotificationList />
|
||||
<NotificationPolicies />
|
||||
</>,
|
||||
{
|
||||
historyOptions: {
|
||||
initialEntries: [
|
||||
'/alerting/routes' +
|
||||
(alertManagerSourceName ? `?${ALERTMANAGER_NAME_QUERY_KEY}=${alertManagerSourceName}` : ''),
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const dataSources = {
|
||||
am: mockDataSource({
|
||||
name: 'Alertmanager',
|
||||
@ -67,30 +88,36 @@ const dataSources = {
|
||||
promAlertManager: mockDataSource<AlertManagerDataSourceJsonData>({
|
||||
name: 'PromManager',
|
||||
type: DataSourceType.Alertmanager,
|
||||
uid: 'prometheusAlertManager',
|
||||
jsonData: {
|
||||
implementation: AlertManagerImplementation.prometheus,
|
||||
},
|
||||
}),
|
||||
mimir: mockDataSource<AlertManagerDataSourceJsonData>({
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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);
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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": []
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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 && (
|
||||
<Dropdown overlay={<Menu>{dropdownMenuActions}</Menu>}>
|
||||
<Button
|
||||
icon="ellipsis-h"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
type="button"
|
||||
aria-label="more-actions"
|
||||
<MoreButton
|
||||
aria-label={isDefaultPolicy ? 'more actions for default policy' : 'more actions for policy'}
|
||||
data-testid="more-actions"
|
||||
/>
|
||||
</Dropdown>
|
||||
@ -469,7 +466,7 @@ function MetadataRow({
|
||||
{contactPoint && (
|
||||
<MetaText icon="at" data-testid="contact-point">
|
||||
<span>
|
||||
<Trans i18nKey="alerting.policies.metadata.delivered-to">Delivered to</Trans>
|
||||
<Trans i18nKey="alerting.policies.metadata.delivered-to">Delivered to</Trans>{' '}
|
||||
</span>
|
||||
<ContactPointsHoverDetails
|
||||
alertManagerSourceName={alertManagerSourceName}
|
||||
@ -483,7 +480,7 @@ function MetadataRow({
|
||||
{customGrouping && (
|
||||
<MetaText icon="layer-group" data-testid="grouping">
|
||||
<span>
|
||||
<Trans i18nKey="alerting.policies.metadata.grouped-by">Grouped by</Trans>
|
||||
<Trans i18nKey="alerting.policies.metadata.grouped-by">Grouped by</Trans>{' '}
|
||||
</span>
|
||||
<Text color="primary">{groupBy.join(', ')}</Text>
|
||||
</MetaText>
|
||||
@ -507,7 +504,7 @@ function MetadataRow({
|
||||
{hasMuteTimings && (
|
||||
<MetaText icon="calendar-slash" data-testid="mute-timings">
|
||||
<span>
|
||||
<Trans i18nKey="alerting.policies.metadata.mute-time">Muted when</Trans>
|
||||
<Trans i18nKey="alerting.policies.metadata.mute-time">Muted when</Trans>{' '}
|
||||
</span>
|
||||
<TimeIntervals timings={muteTimings} alertManagerSourceName={alertManagerSourceName} />
|
||||
</MetaText>
|
||||
@ -515,7 +512,7 @@ function MetadataRow({
|
||||
{hasActiveTimings && (
|
||||
<MetaText icon="calendar-alt" data-testid="active-timings">
|
||||
<span>
|
||||
<Trans i18nKey="alerting.policies.metadata.active-time">Active when</Trans>
|
||||
<Trans i18nKey="alerting.policies.metadata.active-time">Active when</Trans>{' '}
|
||||
</span>
|
||||
<TimeIntervals timings={activeTimings} alertManagerSourceName={alertManagerSourceName} />
|
||||
</MetaText>
|
||||
|
@ -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');
|
||||
|
@ -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) => (
|
||||
|
@ -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(() => {
|
||||
|
@ -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',
|
||||
|
@ -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<FolderDTO>) => {
|
||||
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<typeof getErrorResponse> = defaultError
|
||||
) => {
|
||||
server.use(updateAlertmanagerConfigHandler(responseOverride));
|
||||
};
|
||||
|
||||
/** Make fetching alertmanager config fail */
|
||||
export const makeAllAlertmanagerConfigFetchFail = (
|
||||
responseOverride: ReturnType<typeof getErrorResponse> = defaultError
|
||||
) => {
|
||||
server.use(getAlertmanagerConfigHandler(responseOverride));
|
||||
};
|
||||
|
@ -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;
|
@ -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<string, AlertManagerCortexConfig> = {
|
||||
// TODO in followup PR: Move mock AM config to TS file rather than JSON
|
||||
[GRAFANA_RULES_SOURCE_NAME]: grafanaAlertmanagerConfig,
|
||||
};
|
||||
|
||||
let ALERTMANAGER_CONFIG_MAP: Map<string, AlertManagerCortexConfig> = 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<string, AlertmanagerStatus> = {};
|
||||
|
||||
let ALERTMANAGER_STATUS_MAP: Map<string, AlertmanagerStatus> = 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)!;
|
||||
};
|
@ -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<JsonBodyType>) =>
|
||||
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;
|
||||
|
@ -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;
|
||||
|
@ -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 =
|
||||
|
@ -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(
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user