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 { config } from '@grafana/runtime';
|
||||||
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
|
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
|
||||||
import {
|
import { setAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
|
||||||
setAlertmanagerConfig,
|
|
||||||
setGrafanaAlertmanagerConfig,
|
|
||||||
} from 'app/features/alerting/unified/mocks/server/configure';
|
|
||||||
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
|
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 { MOCK_DATASOURCE_EXTERNAL_VANILLA_ALERTMANAGER_UID } from 'app/features/alerting/unified/mocks/server/handlers/datasources';
|
||||||
import {
|
import {
|
||||||
@ -206,8 +203,12 @@ describe('Mute timings', () => {
|
|||||||
// FIXME: scope down
|
// FIXME: scope down
|
||||||
grantUserPermissions(Object.values(AccessControlAction));
|
grantUserPermissions(Object.values(AccessControlAction));
|
||||||
|
|
||||||
setGrafanaAlertmanagerConfig(defaultConfig);
|
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, defaultConfig);
|
||||||
setAlertmanagerConfig(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 () => {
|
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 () => {
|
it('creates a new mute timing, with time_intervals in config', async () => {
|
||||||
const capture = captureRequests();
|
const capture = captureRequests();
|
||||||
setAlertmanagerConfig(defaultConfigWithNewTimeIntervalsField);
|
setAlertmanagerConfig(dataSources.am.uid, defaultConfigWithNewTimeIntervalsField);
|
||||||
renderMuteTimings(<NewMuteTimingPage />, {
|
renderMuteTimings(<NewMuteTimingPage />, {
|
||||||
search: `?alertmanager=${alertmanagerName}`,
|
search: `?alertmanager=${dataSources.am.name}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await fillOutForm({
|
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 () => {
|
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 />, {
|
renderMuteTimings(<NewMuteTimingPage />, {
|
||||||
search: `?alertmanager=${alertmanagerName}`,
|
search: `?alertmanager=${dataSources.am.name}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(ui.nameField.get()).toBeInTheDocument();
|
expect(ui.nameField.get()).toBeInTheDocument();
|
||||||
|
@ -1,64 +1,85 @@
|
|||||||
import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
import 'core-js/stable/structured-clone';
|
||||||
import { render, userEvent, waitFor, within } from 'test/test-utils';
|
import { clickSelectOption } from 'test/helpers/selectOptionInTest';
|
||||||
|
import { render, screen, userEvent } from 'test/test-utils';
|
||||||
import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector';
|
import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector';
|
||||||
|
|
||||||
import { DataSourceSrv, setDataSourceSrv } from '@grafana/runtime';
|
import { AppNotificationList } from 'app/core/components/AppNotifications/AppNotificationList';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
|
||||||
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
|
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 {
|
import {
|
||||||
AlertManagerCortexConfig,
|
AlertManagerCortexConfig,
|
||||||
AlertManagerDataSourceJsonData,
|
AlertManagerDataSourceJsonData,
|
||||||
AlertManagerImplementation,
|
AlertManagerImplementation,
|
||||||
MatcherOperator,
|
MatcherOperator,
|
||||||
MuteTimeInterval,
|
|
||||||
Route,
|
|
||||||
RouteWithID,
|
RouteWithID,
|
||||||
} from 'app/plugins/datasource/alertmanager/types';
|
} from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { AccessControlAction } from 'app/types';
|
import { AccessControlAction } from 'app/types';
|
||||||
|
|
||||||
import NotificationPolicies, { findRoutesMatchingFilters } from './NotificationPolicies';
|
import NotificationPolicies, { findRoutesMatchingFilters } from './NotificationPolicies';
|
||||||
import { fetchAlertManagerConfig, fetchStatus, updateAlertManagerConfig } from './api/alertmanager';
|
import {
|
||||||
import { alertmanagerApi } from './api/alertmanagerApi';
|
grantUserPermissions,
|
||||||
import { discoverAlertmanagerFeatures } from './api/buildInfo';
|
mockDataSource,
|
||||||
import { MockDataSourceSrv, mockDataSource, someCloudAlertManagerConfig, someCloudAlertManagerStatus } from './mocks';
|
someCloudAlertManagerConfig,
|
||||||
import { defaultGroupBy } from './utils/amroutes';
|
someCloudAlertManagerStatus,
|
||||||
import { getAllDataSources } from './utils/config';
|
} from './mocks';
|
||||||
import { ALERTMANAGER_NAME_QUERY_KEY } from './utils/constants';
|
import { ALERTMANAGER_NAME_QUERY_KEY } from './utils/constants';
|
||||||
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
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');
|
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();
|
setupMswServer();
|
||||||
|
|
||||||
const renderNotificationPolicies = (alertManagerSourceName?: string) => {
|
const updateTiming = async (selectElement: HTMLElement, value: string): Promise<void> => {
|
||||||
return render(<NotificationPolicies />, {
|
const user = userEvent.setup();
|
||||||
historyOptions: {
|
const input = byRole('textbox').get(selectElement);
|
||||||
initialEntries: [
|
await user.clear(input);
|
||||||
'/alerting/routes' +
|
await user.type(input, value);
|
||||||
(alertManagerSourceName ? `?${ALERTMANAGER_NAME_QUERY_KEY}=${alertManagerSourceName}` : ''),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 = {
|
const dataSources = {
|
||||||
am: mockDataSource({
|
am: mockDataSource({
|
||||||
name: 'Alertmanager',
|
name: 'Alertmanager',
|
||||||
@ -67,30 +88,36 @@ const dataSources = {
|
|||||||
promAlertManager: mockDataSource<AlertManagerDataSourceJsonData>({
|
promAlertManager: mockDataSource<AlertManagerDataSourceJsonData>({
|
||||||
name: 'PromManager',
|
name: 'PromManager',
|
||||||
type: DataSourceType.Alertmanager,
|
type: DataSourceType.Alertmanager,
|
||||||
|
uid: 'prometheusAlertManager',
|
||||||
jsonData: {
|
jsonData: {
|
||||||
implementation: AlertManagerImplementation.prometheus,
|
implementation: AlertManagerImplementation.prometheus,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
mimir: mockDataSource<AlertManagerDataSourceJsonData>({
|
||||||
|
name: 'mimir',
|
||||||
|
type: DataSourceType.Alertmanager,
|
||||||
|
uid: 'mimir',
|
||||||
|
jsonData: {
|
||||||
|
implementation: AlertManagerImplementation.mimir,
|
||||||
|
},
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const ui = {
|
const ui = {
|
||||||
rootReceiver: byTestId('am-routes-root-receiver'),
|
/** Row of policy tree containing default policy */
|
||||||
rootGroupBy: byTestId('am-routes-root-group-by'),
|
|
||||||
rootTimings: byTestId('am-routes-root-timings'),
|
|
||||||
row: byTestId('am-routes-row'),
|
|
||||||
|
|
||||||
rootRouteContainer: byTestId('am-root-route-container'),
|
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' }),
|
newChildPolicyButton: byRole('button', { name: /New child policy/ }),
|
||||||
saveButton: byRole('button', { name: 'Save' }),
|
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'),
|
saveButton: byRole('button', { name: /update (default )?policy/i }),
|
||||||
deleteRouteButton: byLabelText('Delete route'),
|
deleteRouteButton: byRole('menuitem', { name: 'Delete' }),
|
||||||
newPolicyButton: byRole('button', { name: /Add policy/ }),
|
|
||||||
newPolicyCTAButton: byRole('button', { name: /Add specific policy/ }),
|
|
||||||
savePolicyButton: byRole('button', { name: /save policy/i }),
|
|
||||||
|
|
||||||
receiverSelect: byTestId('am-receiver-select'),
|
receiverSelect: byTestId('am-receiver-select'),
|
||||||
groupSelect: byTestId('am-group-select'),
|
groupSelect: byTestId('am-group-select'),
|
||||||
@ -101,451 +128,217 @@ const ui = {
|
|||||||
groupRepeatContainer: byTestId('am-repeat-interval'),
|
groupRepeatContainer: byTestId('am-repeat-interval'),
|
||||||
|
|
||||||
confirmDeleteModal: byRole('dialog'),
|
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', () => {
|
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(() => {
|
beforeEach(() => {
|
||||||
mocks.getAllDataSourcesMock.mockReturnValue(Object.values(dataSources));
|
setupDataSources(...Object.values(dataSources));
|
||||||
mocks.contextSrv.hasPermission.mockImplementation(() => true);
|
grantUserPermissions([
|
||||||
mocks.contextSrv.evaluatePermission.mockImplementation(() => []);
|
AccessControlAction.AlertingInstanceRead,
|
||||||
mocks.api.discoverAlertmanagerFeatures.mockResolvedValue({ lazyConfigInit: false });
|
AccessControlAction.AlertingInstanceCreate,
|
||||||
setDataSourceSrv(new MockDataSourceSrv(dataSources));
|
AccessControlAction.AlertingInstanceUpdate,
|
||||||
|
AccessControlAction.AlertingInstancesExternalRead,
|
||||||
|
AccessControlAction.AlertingInstancesExternalWrite,
|
||||||
|
AccessControlAction.AlertingNotificationsRead,
|
||||||
|
AccessControlAction.AlertingNotificationsWrite,
|
||||||
|
AccessControlAction.AlertingNotificationsExternalRead,
|
||||||
|
AccessControlAction.AlertingNotificationsExternalWrite,
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
it('loads and shows routes', async () => {
|
||||||
jest.resetAllMocks();
|
const { alertmanager_config: testConfig } = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
|
||||||
|
|
||||||
setDataSourceSrv(undefined as unknown as DataSourceSrv);
|
const { route: defaultRoute } = testConfig;
|
||||||
});
|
|
||||||
|
|
||||||
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: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
renderNotificationPolicies();
|
renderNotificationPolicies();
|
||||||
|
const rootRouteEl = await getRootRoute();
|
||||||
|
|
||||||
await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1));
|
expect(rootRouteEl).toHaveTextContent(new RegExp(`delivered to ${defaultRoute?.receiver}`, 'i'));
|
||||||
|
expect(rootRouteEl).toHaveTextContent(new RegExp(`grouped by ${defaultRoute?.group_by?.join(', ')}`, 'i'));
|
||||||
expect(ui.rootReceiver.get()).toHaveTextContent(rootRoute.receiver!);
|
expect(rootRouteEl).toHaveTextContent(/wait 30s to group/i);
|
||||||
expect(ui.rootGroupBy.get()).toHaveTextContent(rootRoute.group_by!.join(', '));
|
expect(rootRouteEl).toHaveTextContent(/wait 5m before sending/i);
|
||||||
const rootTimings = ui.rootTimings.get();
|
expect(rootRouteEl).toHaveTextContent(/repeated every 4h/i);
|
||||||
expect(rootTimings).toHaveTextContent(rootRoute.group_wait!);
|
|
||||||
expect(rootTimings).toHaveTextContent(rootRoute.group_interval!);
|
|
||||||
expect(rootTimings).toHaveTextContent(rootRoute.repeat_interval!);
|
|
||||||
|
|
||||||
const rows = await ui.row.findAll();
|
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]) => {
|
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]) => {
|
Object.entries(route.match_re ?? {}).forEach(([label, value]) => {
|
||||||
expect(rows[index]).toHaveTextContent(`${label}=~${value}`);
|
expect(screen.getByText(`${label} =~ ${value}`)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (route.group_by) {
|
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) {
|
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 () => {
|
it('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);
|
|
||||||
});
|
|
||||||
|
|
||||||
const { user } = renderNotificationPolicies();
|
const { user } = renderNotificationPolicies();
|
||||||
expect(await ui.rootReceiver.find()).toHaveTextContent('default');
|
let rootRoute = await getRootRoute();
|
||||||
expect(ui.rootGroupBy.get()).toHaveTextContent('alertname');
|
|
||||||
|
|
||||||
// open root route for editing
|
expect(rootRoute).toHaveTextContent('default policy');
|
||||||
const rootRouteContainer = await ui.rootRouteContainer.find();
|
expect(rootRoute).toHaveTextContent(/delivered to grafana-default-email/i);
|
||||||
await user.click(ui.editButton.get(rootRouteContainer));
|
expect(rootRoute).toHaveTextContent(/grouped by alertname/i);
|
||||||
|
|
||||||
|
await openDefaultPolicyEditModal();
|
||||||
|
|
||||||
// configure receiver & group by
|
// configure receiver & group by
|
||||||
const receiverSelect = await ui.receiverSelect.find();
|
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();
|
const groupSelect = ui.groupSelect.get();
|
||||||
await user.type(byRole('combobox').get(groupSelect), 'namespace{enter}');
|
await user.type(byRole('combobox').get(groupSelect), 'namespace{enter}');
|
||||||
|
|
||||||
// configure timing intervals
|
// 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.groupWaitContainer.get(), '1m');
|
||||||
await updateTiming(ui.groupIntervalContainer.get(), '4', 'Minutes');
|
await updateTiming(ui.groupIntervalContainer.get(), '4m');
|
||||||
await updateTiming(ui.groupRepeatContainer.get(), '5', 'Hours');
|
await updateTiming(ui.groupRepeatContainer.get(), '5h');
|
||||||
|
|
||||||
//save
|
//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
|
// 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' }, { 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: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
// check that new config values are rendered
|
// check that new config values are rendered
|
||||||
await waitFor(() => expect(ui.rootReceiver.query()).toHaveTextContent('critical'));
|
rootRoute = await getRootRoute();
|
||||||
expect(ui.rootGroupBy.get()).toHaveTextContent('alertname, namespace');
|
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 () => {
|
it('can edit root route if one is not defined yet', async () => {
|
||||||
mocks.api.fetchAlertManagerConfig.mockResolvedValue({
|
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, {
|
||||||
alertmanager_config: {
|
alertmanager_config: {
|
||||||
receivers: [{ name: 'default' }],
|
route: {},
|
||||||
|
receivers: [{ name: 'grafana-default-email' }],
|
||||||
},
|
},
|
||||||
template_files: {},
|
template_files: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { user } = renderNotificationPolicies();
|
const { user } = renderNotificationPolicies();
|
||||||
|
|
||||||
// open root route for editing
|
await openDefaultPolicyEditModal();
|
||||||
const rootRouteContainer = await ui.rootRouteContainer.find();
|
|
||||||
await user.click(ui.editButton.get(rootRouteContainer));
|
|
||||||
|
|
||||||
// configure receiver & group by
|
// configure receiver & group by
|
||||||
const receiverSelect = await ui.receiverSelect.find();
|
const receiverSelect = await ui.receiverSelect.find();
|
||||||
await clickSelectOption(receiverSelect, 'default');
|
await clickSelectOption(receiverSelect, 'lotsa-emails');
|
||||||
|
|
||||||
const groupSelect = ui.groupSelect.get();
|
const groupSelect = ui.groupSelect.get();
|
||||||
await user.type(byRole('combobox').get(groupSelect), 'severity{enter}');
|
await user.type(byRole('combobox').get(groupSelect), 'severity{enter}');
|
||||||
await user.type(byRole('combobox').get(groupSelect), 'namespace{enter}');
|
await user.type(byRole('combobox').get(groupSelect), 'namespace{enter}');
|
||||||
//save
|
//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
|
expect(await screen.findByText(/updated notification policies/i)).toBeInTheDocument();
|
||||||
await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument());
|
|
||||||
|
|
||||||
// check that appropriate api calls were made
|
const rootRoute = await getRootRoute();
|
||||||
expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(3);
|
expect(rootRoute).toHaveTextContent(/delivered to lotsa-emails/i);
|
||||||
expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledTimes(1);
|
expect(rootRoute).toHaveTextContent(/grouped by severity, namespace/i);
|
||||||
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: {},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides create and edit button if user does not have permission', async () => {
|
it('hides create and edit button if user does not have permission', async () => {
|
||||||
mocks.contextSrv.hasPermission.mockImplementation((action) =>
|
grantUserPermissions([
|
||||||
[AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsRead].includes(
|
AccessControlAction.AlertingInstanceRead,
|
||||||
action as AccessControlAction
|
AccessControlAction.AlertingInstancesExternalRead,
|
||||||
)
|
AccessControlAction.AlertingNotificationsRead,
|
||||||
);
|
AccessControlAction.AlertingNotificationsExternalRead,
|
||||||
|
]);
|
||||||
|
|
||||||
renderNotificationPolicies();
|
const { user } = renderNotificationPolicies();
|
||||||
await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1));
|
|
||||||
|
|
||||||
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();
|
expect(ui.editButton.query()).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Show error message if loading Alertmanager config fails', async () => {
|
it('Show error message if loading Alertmanager config fails', async () => {
|
||||||
mocks.api.fetchAlertManagerConfig.mockRejectedValue({
|
makeAllAlertmanagerConfigFetchFail(getErrorResponse("Alertmanager has exploded. it's gone. Forget about it."));
|
||||||
status: 500,
|
|
||||||
data: {
|
|
||||||
message: "Alertmanager has exploded. it's gone. Forget about it.",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(alertmanagerApi, 'useGetAlertmanagerAlertGroupsQuery').mockImplementation(() => ({
|
|
||||||
currentData: [],
|
|
||||||
refetch: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
renderNotificationPolicies();
|
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(await byText("Alertmanager has exploded. it's gone. Forget about it.").find()).toBeInTheDocument();
|
||||||
expect(ui.rootReceiver.query()).not.toBeInTheDocument();
|
expect(ui.rootRouteContainer.query()).not.toBeInTheDocument();
|
||||||
expect(ui.editButton.query()).not.toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('Converts matchers to object_matchers for grafana alertmanager', async () => {
|
it('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);
|
|
||||||
});
|
|
||||||
|
|
||||||
const { user } = renderNotificationPolicies(GRAFANA_RULES_SOURCE_NAME);
|
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 policyIndex = 0;
|
||||||
const rootRouteContainer = await ui.rootRouteContainer.find();
|
await openEditModal(policyIndex);
|
||||||
await user.click(ui.editButton.get(rootRouteContainer));
|
|
||||||
await user.click(ui.saveButton.get(rootRouteContainer));
|
|
||||||
|
|
||||||
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(await screen.findByRole('status')).toHaveTextContent(/updated notification policies/i);
|
||||||
expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(GRAFANA_RULES_SOURCE_NAME, {
|
|
||||||
alertmanager_config: {
|
const updatedConfig = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
|
||||||
receivers: [{ name: 'default' }, { name: 'critical' }],
|
expect(updatedConfig.alertmanager_config.route?.routes?.[policyIndex].object_matchers).toMatchSnapshot();
|
||||||
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: {},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('Should be able to delete an empty route', async () => {
|
it('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: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultConfig: AlertManagerCortexConfig = {
|
const defaultConfig: AlertManagerCortexConfig = {
|
||||||
alertmanager_config: {
|
alertmanager_config: {
|
||||||
receivers: [{ name: 'default' }, { name: 'critical' }],
|
route: {
|
||||||
route: routeConfig,
|
routes: [{}],
|
||||||
templates: [],
|
},
|
||||||
},
|
},
|
||||||
template_files: {},
|
template_files: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
|
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, defaultConfig);
|
||||||
return Promise.resolve(defaultConfig);
|
|
||||||
});
|
|
||||||
|
|
||||||
mocks.api.updateAlertManagerConfig.mockResolvedValue(Promise.resolve());
|
|
||||||
|
|
||||||
const { user } = renderNotificationPolicies(GRAFANA_RULES_SOURCE_NAME);
|
const { user } = renderNotificationPolicies(GRAFANA_RULES_SOURCE_NAME);
|
||||||
await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled());
|
|
||||||
|
|
||||||
const deleteButtons = await ui.deleteRouteButton.findAll();
|
await user.click(await ui.moreActions.find());
|
||||||
expect(deleteButtons).toHaveLength(1);
|
const deleteButtons = await ui.deleteRouteButton.find();
|
||||||
|
|
||||||
await user.click(deleteButtons[0]);
|
await user.click(deleteButtons);
|
||||||
|
|
||||||
const confirmDeleteButton = ui.confirmDeleteButton.get(ui.confirmDeleteModal.get());
|
const confirmDeleteButton = ui.confirmDeleteButton.get(ui.confirmDeleteModal.get());
|
||||||
expect(confirmDeleteButton).toBeInTheDocument();
|
expect(confirmDeleteButton).toBeInTheDocument();
|
||||||
|
|
||||||
await user.click(confirmDeleteButton);
|
await user.click(confirmDeleteButton);
|
||||||
|
|
||||||
expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith<[string, AlertManagerCortexConfig]>(
|
expect(await screen.findByRole('status')).toHaveTextContent(/updated notification policies/i);
|
||||||
GRAFANA_RULES_SOURCE_NAME,
|
|
||||||
{
|
expect(ui.row.query()).not.toBeInTheDocument();
|
||||||
...defaultConfig,
|
|
||||||
alertmanager_config: {
|
|
||||||
...defaultConfig.alertmanager_config,
|
|
||||||
route: {
|
|
||||||
...routeConfig,
|
|
||||||
routes: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('Keeps matchers for non-grafana alertmanager sources', async () => {
|
it('Keeps matchers for non-grafana alertmanager sources', async () => {
|
||||||
const defaultConfig: AlertManagerCortexConfig = {
|
setAlertmanagerConfig(dataSources.am.uid, {
|
||||||
alertmanager_config: {
|
alertmanager_config: {
|
||||||
receivers: [{ name: 'default' }, { name: 'critical' }],
|
receivers: [{ name: 'default' }, { name: 'critical' }],
|
||||||
route: {
|
route: {
|
||||||
continue: false,
|
continue: false,
|
||||||
receiver: 'default',
|
receiver: 'default',
|
||||||
group_by: ['alertname'],
|
group_by: ['alertname'],
|
||||||
routes: [simpleRoute],
|
routes: [
|
||||||
|
{
|
||||||
|
receiver: 'simple-receiver',
|
||||||
|
matchers: ['hello=world', 'foo!=bar'],
|
||||||
|
},
|
||||||
|
],
|
||||||
group_interval: '4m',
|
group_interval: '4m',
|
||||||
group_wait: '1m',
|
group_wait: '1m',
|
||||||
repeat_interval: '5h',
|
repeat_interval: '5h',
|
||||||
@ -553,79 +346,38 @@ describe('NotificationPolicies', () => {
|
|||||||
templates: [],
|
templates: [],
|
||||||
},
|
},
|
||||||
template_files: {},
|
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);
|
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 policyIndex = 0;
|
||||||
const rootRouteContainer = await ui.rootRouteContainer.find();
|
await openEditModal(policyIndex);
|
||||||
await user.click(ui.editButton.get(rootRouteContainer));
|
|
||||||
await user.click(ui.saveButton.get(rootRouteContainer));
|
|
||||||
|
|
||||||
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();
|
const updatedConfig = getAlertmanagerConfig(dataSources.am.uid);
|
||||||
expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(dataSources.am.name, {
|
expect(updatedConfig.alertmanager_config.route?.routes?.[policyIndex].matchers).toMatchSnapshot();
|
||||||
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: {},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('Prometheus Alertmanager routes cannot be edited', async () => {
|
it('Prometheus Alertmanager routes cannot be edited', async () => {
|
||||||
mocks.api.fetchStatus.mockResolvedValue({
|
setAlertmanagerStatus(dataSources.promAlertManager.uid, {
|
||||||
...someCloudAlertManagerStatus,
|
...someCloudAlertManagerStatus,
|
||||||
config: someCloudAlertManagerConfig.alertmanager_config,
|
config: someCloudAlertManagerConfig.alertmanager_config,
|
||||||
});
|
});
|
||||||
renderNotificationPolicies(dataSources.promAlertManager.name);
|
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();
|
const rows = await ui.row.findAll();
|
||||||
expect(rows).toHaveLength(2);
|
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(ui.moreActions.query()).not.toBeInTheDocument();
|
||||||
expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1);
|
expect(ui.moreActionsDefaultPolicy.query()).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Prometheus Alertmanager has no CTA button if there are no specific policies', async () => {
|
it('Prometheus Alertmanager has no CTA button if there are no specific policies', async () => {
|
||||||
mocks.api.fetchStatus.mockResolvedValue({
|
setAlertmanagerStatus(dataSources.promAlertManager.uid, {
|
||||||
...someCloudAlertManagerStatus,
|
...someCloudAlertManagerStatus,
|
||||||
config: {
|
config: {
|
||||||
...someCloudAlertManagerConfig.alertmanager_config,
|
...someCloudAlertManagerConfig.alertmanager_config,
|
||||||
@ -636,102 +388,39 @@ describe('NotificationPolicies', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.spyOn(alertmanagerApi, 'useGetAlertmanagerAlertGroupsQuery').mockImplementation(() => ({
|
|
||||||
currentData: [],
|
|
||||||
refetch: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
renderNotificationPolicies(dataSources.promAlertManager.name);
|
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(await ui.rootRouteContainer.find()).toBeInTheDocument();
|
||||||
expect(ui.newPolicyCTAButton.query()).not.toBeInTheDocument();
|
|
||||||
expect(mocks.api.fetchAlertManagerConfig).not.toHaveBeenCalled();
|
expect(ui.newChildPolicyButton.query()).not.toBeInTheDocument();
|
||||||
expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1);
|
expect(ui.newSiblingPolicyButton.query()).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('Can add a mute timing to a route', async () => {
|
it('Can add a mute timing to a route', async () => {
|
||||||
const defaultConfig: AlertManagerCortexConfig = {
|
const { user } = renderNotificationPolicies();
|
||||||
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: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentConfig = { current: defaultConfig };
|
await openEditModal(0);
|
||||||
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]));
|
|
||||||
|
|
||||||
const muteTimingSelect = ui.muteTimingSelect.get();
|
const muteTimingSelect = ui.muteTimingSelect.get();
|
||||||
await clickSelectOption(muteTimingSelect, 'default-mute');
|
await clickSelectOption(muteTimingSelect, TIME_INTERVAL_NAME_HAPPY_PATH);
|
||||||
expect(muteTimingSelect).toHaveTextContent('default-mute');
|
await clickSelectOption(muteTimingSelect, TIME_INTERVAL_NAME_FILE_PROVISIONED);
|
||||||
|
|
||||||
const savePolicyButton = ui.savePolicyButton.get();
|
await user.click(ui.saveButton.get());
|
||||||
expect(savePolicyButton).toBeInTheDocument();
|
|
||||||
|
|
||||||
await user.click(savePolicyButton);
|
expect(await screen.findByRole('status')).toHaveTextContent(/updated notification policies/i);
|
||||||
|
|
||||||
await waitFor(() => expect(savePolicyButton).not.toBeInTheDocument());
|
const policy = (await ui.row.findAll())[0];
|
||||||
|
expect(policy).toHaveTextContent(
|
||||||
expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled();
|
`Muted when ${TIME_INTERVAL_NAME_HAPPY_PATH}, ${TIME_INTERVAL_NAME_FILE_PROVISIONED}`
|
||||||
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: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('Shows an empty config when config returns an error and the AM supports lazy config initialization', async () => {
|
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({
|
expect(await ui.rootRouteContainer.find()).toBeInTheDocument();
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -806,19 +495,3 @@ describe('findRoutesMatchingFilters', () => {
|
|||||||
expect(matchingRoutes).toMatchSnapshot();
|
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 { GrafanaTheme2, UrlQueryMap } from '@grafana/data';
|
||||||
import { Alert, LoadingPlaceholder, Stack, Tab, TabContent, TabsBar, useStyles2, withErrorBoundary } from '@grafana/ui';
|
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 { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
import { useMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
|
import { useMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
|
||||||
import { ObjectMatcher, Route, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
|
import { ObjectMatcher, Route, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
|
||||||
@ -52,6 +53,7 @@ enum ActiveTab {
|
|||||||
const AmRoutes = () => {
|
const AmRoutes = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
const appNotification = useAppNotification();
|
||||||
|
|
||||||
const { useGetAlertmanagerAlertGroupsQuery } = alertmanagerApi;
|
const { useGetAlertmanagerAlertGroupsQuery } = alertmanagerApi;
|
||||||
|
|
||||||
@ -175,11 +177,11 @@ const AmRoutes = () => {
|
|||||||
},
|
},
|
||||||
oldConfig: result,
|
oldConfig: result,
|
||||||
alertManagerSourceName: selectedAlertmanager!,
|
alertManagerSourceName: selectedAlertmanager!,
|
||||||
successMessage: 'Updated notification policies',
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
appNotification.success('Updated notification policies');
|
||||||
if (selectedAlertmanager) {
|
if (selectedAlertmanager) {
|
||||||
refetchAlertGroups();
|
refetchAlertGroups();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,37 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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`] = `
|
exports[`findRoutesMatchingFilters should not match non-existing 1`] = `
|
||||||
{
|
{
|
||||||
"filtersApplied": true,
|
"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 { config } from '@grafana/runtime';
|
||||||
import { defaultConfig } from 'app/features/alerting/unified/MuteTimings.test';
|
import { defaultConfig } from 'app/features/alerting/unified/MuteTimings.test';
|
||||||
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
|
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
|
||||||
import {
|
import { setMuteTimingsListError } from 'app/features/alerting/unified/mocks/server/configure';
|
||||||
setGrafanaAlertmanagerConfig,
|
import { setAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
|
||||||
setMuteTimingsListError,
|
|
||||||
} from 'app/features/alerting/unified/mocks/server/configure';
|
|
||||||
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
|
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
|
||||||
import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { AccessControlAction } from 'app/types';
|
import { AccessControlAction } from 'app/types';
|
||||||
@ -31,7 +29,7 @@ setupMswServer();
|
|||||||
describe('MuteTimingsTable', () => {
|
describe('MuteTimingsTable', () => {
|
||||||
describe('with necessary permissions', () => {
|
describe('with necessary permissions', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setGrafanaAlertmanagerConfig(defaultConfig);
|
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, defaultConfig);
|
||||||
config.featureToggles.alertingApiServer = false;
|
config.featureToggles.alertingApiServer = false;
|
||||||
grantUserPermissions([
|
grantUserPermissions([
|
||||||
AccessControlAction.AlertingNotificationsRead,
|
AccessControlAction.AlertingNotificationsRead,
|
||||||
|
@ -133,8 +133,8 @@ describe('Policy', () => {
|
|||||||
expect(within(firstPolicy).getByTestId('continue-matching')).toBeInTheDocument();
|
expect(within(firstPolicy).getByTestId('continue-matching')).toBeInTheDocument();
|
||||||
// expect(within(firstPolicy).getByTestId('matching-instances')).toHaveTextContent('0instances');
|
// expect(within(firstPolicy).getByTestId('matching-instances')).toHaveTextContent('0instances');
|
||||||
expect(within(firstPolicy).getByTestId('contact-point')).toHaveTextContent('provisioned-contact-point');
|
expect(within(firstPolicy).getByTestId('contact-point')).toHaveTextContent('provisioned-contact-point');
|
||||||
expect(within(firstPolicy).getByTestId('mute-timings')).toHaveTextContent('Muted whenmt-1');
|
expect(within(firstPolicy).getByTestId('mute-timings')).toHaveTextContent('Muted when mt-1');
|
||||||
expect(within(firstPolicy).getByTestId('active-timings')).toHaveTextContent('Active whenmt-2');
|
expect(within(firstPolicy).getByTestId('active-timings')).toHaveTextContent('Active when mt-2');
|
||||||
expect(within(firstPolicy).getByTestId('inherited-properties')).toHaveTextContent('Inherited2 properties');
|
expect(within(firstPolicy).getByTestId('inherited-properties')).toHaveTextContent('Inherited2 properties');
|
||||||
|
|
||||||
// second custom policy should be correct
|
// second custom policy should be correct
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { t, Trans } from 'app/core/internationalization';
|
import { t, Trans } from 'app/core/internationalization';
|
||||||
import ConditionalWrap from 'app/features/alerting/unified/components/ConditionalWrap';
|
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 { PrimaryText } from 'app/features/alerting/unified/components/common/TextVariants';
|
||||||
import {
|
import {
|
||||||
AlertmanagerGroup,
|
AlertmanagerGroup,
|
||||||
@ -308,12 +309,8 @@ const Policy = (props: PolicyComponentProps) => {
|
|||||||
)}
|
)}
|
||||||
{dropdownMenuActions.length > 0 && (
|
{dropdownMenuActions.length > 0 && (
|
||||||
<Dropdown overlay={<Menu>{dropdownMenuActions}</Menu>}>
|
<Dropdown overlay={<Menu>{dropdownMenuActions}</Menu>}>
|
||||||
<Button
|
<MoreButton
|
||||||
icon="ellipsis-h"
|
aria-label={isDefaultPolicy ? 'more actions for default policy' : 'more actions for policy'}
|
||||||
variant="secondary"
|
|
||||||
size="sm"
|
|
||||||
type="button"
|
|
||||||
aria-label="more-actions"
|
|
||||||
data-testid="more-actions"
|
data-testid="more-actions"
|
||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@ -469,7 +466,7 @@ function MetadataRow({
|
|||||||
{contactPoint && (
|
{contactPoint && (
|
||||||
<MetaText icon="at" data-testid="contact-point">
|
<MetaText icon="at" data-testid="contact-point">
|
||||||
<span>
|
<span>
|
||||||
<Trans i18nKey="alerting.policies.metadata.delivered-to">Delivered to</Trans>
|
<Trans i18nKey="alerting.policies.metadata.delivered-to">Delivered to</Trans>{' '}
|
||||||
</span>
|
</span>
|
||||||
<ContactPointsHoverDetails
|
<ContactPointsHoverDetails
|
||||||
alertManagerSourceName={alertManagerSourceName}
|
alertManagerSourceName={alertManagerSourceName}
|
||||||
@ -483,7 +480,7 @@ function MetadataRow({
|
|||||||
{customGrouping && (
|
{customGrouping && (
|
||||||
<MetaText icon="layer-group" data-testid="grouping">
|
<MetaText icon="layer-group" data-testid="grouping">
|
||||||
<span>
|
<span>
|
||||||
<Trans i18nKey="alerting.policies.metadata.grouped-by">Grouped by</Trans>
|
<Trans i18nKey="alerting.policies.metadata.grouped-by">Grouped by</Trans>{' '}
|
||||||
</span>
|
</span>
|
||||||
<Text color="primary">{groupBy.join(', ')}</Text>
|
<Text color="primary">{groupBy.join(', ')}</Text>
|
||||||
</MetaText>
|
</MetaText>
|
||||||
@ -507,7 +504,7 @@ function MetadataRow({
|
|||||||
{hasMuteTimings && (
|
{hasMuteTimings && (
|
||||||
<MetaText icon="calendar-slash" data-testid="mute-timings">
|
<MetaText icon="calendar-slash" data-testid="mute-timings">
|
||||||
<span>
|
<span>
|
||||||
<Trans i18nKey="alerting.policies.metadata.mute-time">Muted when</Trans>
|
<Trans i18nKey="alerting.policies.metadata.mute-time">Muted when</Trans>{' '}
|
||||||
</span>
|
</span>
|
||||||
<TimeIntervals timings={muteTimings} alertManagerSourceName={alertManagerSourceName} />
|
<TimeIntervals timings={muteTimings} alertManagerSourceName={alertManagerSourceName} />
|
||||||
</MetaText>
|
</MetaText>
|
||||||
@ -515,7 +512,7 @@ function MetadataRow({
|
|||||||
{hasActiveTimings && (
|
{hasActiveTimings && (
|
||||||
<MetaText icon="calendar-alt" data-testid="active-timings">
|
<MetaText icon="calendar-alt" data-testid="active-timings">
|
||||||
<span>
|
<span>
|
||||||
<Trans i18nKey="alerting.policies.metadata.active-time">Active when</Trans>
|
<Trans i18nKey="alerting.policies.metadata.active-time">Active when</Trans>{' '}
|
||||||
</span>
|
</span>
|
||||||
<TimeIntervals timings={activeTimings} alertManagerSourceName={alertManagerSourceName} />
|
<TimeIntervals timings={activeTimings} alertManagerSourceName={alertManagerSourceName} />
|
||||||
</MetaText>
|
</MetaText>
|
||||||
|
@ -3,7 +3,7 @@ import { Route } from 'react-router';
|
|||||||
import { render, screen } from 'test/test-utils';
|
import { render, screen } from 'test/test-utils';
|
||||||
import { byLabelText, byPlaceholderText, byRole, byTestId } from 'testing-library-selector';
|
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 { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
|
||||||
import { AccessControlAction } from 'app/types';
|
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 () => {
|
it('does not redirect when creating contact point and API errors', async () => {
|
||||||
makeGrafanaAlertmanagerConfigUpdateFail();
|
makeAlertmanagerConfigUpdateFail();
|
||||||
const { user } = renderForm();
|
const { user } = renderForm();
|
||||||
|
|
||||||
await user.type(await ui.inputs.name.find(), 'receiver that should fail');
|
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 { render, screen, userEvent } from 'test/test-utils';
|
||||||
|
|
||||||
import { CodeEditorProps } from '@grafana/ui/src/components/Monaco/types';
|
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 { setupMswServer } from 'app/features/alerting/unified/mockApi';
|
||||||
import { grantUserPermissions } from 'app/features/alerting/unified/mocks';
|
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 { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext';
|
||||||
import { testWithFeatureToggles } from 'app/features/alerting/unified/test/test-utils';
|
import { testWithFeatureToggles } from 'app/features/alerting/unified/test/test-utils';
|
||||||
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
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 { getTemplateOptions, TemplatesPicker } from './TemplateSelector';
|
||||||
import { parseTemplates } from './utils';
|
import { parseTemplates } from './utils';
|
||||||
|
|
||||||
|
const alertmanagerConfigMock = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
|
||||||
|
|
||||||
jest.mock('@grafana/ui', () => ({
|
jest.mock('@grafana/ui', () => ({
|
||||||
...jest.requireActual('@grafana/ui'),
|
...jest.requireActual('@grafana/ui'),
|
||||||
CodeEditor: ({ value, onChange }: CodeEditorProps) => (
|
CodeEditor: ({ value, onChange }: CodeEditorProps) => (
|
||||||
|
@ -5,6 +5,10 @@ import { DataSourceInstanceSettings } from '@grafana/data';
|
|||||||
import { setBackendSrv } from '@grafana/runtime';
|
import { setBackendSrv } from '@grafana/runtime';
|
||||||
import { AlertGroupUpdated } from 'app/features/alerting/unified/api/alertRuleApi';
|
import { AlertGroupUpdated } from 'app/features/alerting/unified/api/alertRuleApi';
|
||||||
import allHandlers from 'app/features/alerting/unified/mocks/server/all-handlers';
|
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 { DashboardDTO, FolderDTO, OrgUser } from 'app/types';
|
||||||
import {
|
import {
|
||||||
PromBuildInfoResponse,
|
PromBuildInfoResponse,
|
||||||
@ -289,6 +293,10 @@ export function setupMswServer() {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
server.resetHandlers();
|
server.resetHandlers();
|
||||||
|
|
||||||
|
// Reset any other necessary mock entities/state
|
||||||
|
setupAlertmanagerConfigMapDefaultState();
|
||||||
|
setupAlertmanagerStatusMapDefaultState();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
@ -512,6 +512,7 @@ export const someGrafanaAlertManagerConfig: AlertManagerCortexConfig = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @deprecated Move into alertmanager status entities */
|
||||||
export const someCloudAlertManagerStatus: AlertmanagerStatus = {
|
export const someCloudAlertManagerStatus: AlertmanagerStatus = {
|
||||||
cluster: {
|
cluster: {
|
||||||
peers: [],
|
peers: [],
|
||||||
@ -543,6 +544,7 @@ export const someCloudAlertManagerStatus: AlertmanagerStatus = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @deprecated Move into alertmanager config entities */
|
||||||
export const someCloudAlertManagerConfig: AlertManagerCortexConfig = {
|
export const someCloudAlertManagerConfig: AlertManagerCortexConfig = {
|
||||||
template_files: {
|
template_files: {
|
||||||
'foo template': 'foo content',
|
'foo template': 'foo content',
|
||||||
|
@ -4,11 +4,9 @@ import { config } from '@grafana/runtime';
|
|||||||
import server, { mockFeatureDiscoveryApi } from 'app/features/alerting/unified/mockApi';
|
import server, { mockFeatureDiscoveryApi } from 'app/features/alerting/unified/mockApi';
|
||||||
import { mockDataSource, mockFolder } from 'app/features/alerting/unified/mocks';
|
import { mockDataSource, mockFolder } from 'app/features/alerting/unified/mocks';
|
||||||
import {
|
import {
|
||||||
ALERTMANAGER_UPDATE_ERROR_RESPONSE,
|
|
||||||
getAlertmanagerConfigHandler,
|
getAlertmanagerConfigHandler,
|
||||||
getGrafanaAlertmanagerConfigHandler,
|
|
||||||
grafanaAlertingConfigurationStatusHandler,
|
grafanaAlertingConfigurationStatusHandler,
|
||||||
updateGrafanaAlertmanagerConfigHandler,
|
updateAlertmanagerConfigHandler,
|
||||||
} from 'app/features/alerting/unified/mocks/server/handlers/alertmanagers';
|
} from 'app/features/alerting/unified/mocks/server/handlers/alertmanagers';
|
||||||
import { getFolderHandler } from 'app/features/alerting/unified/mocks/server/handlers/folders';
|
import { getFolderHandler } from 'app/features/alerting/unified/mocks/server/handlers/folders';
|
||||||
import { listNamespacedTimeIntervalHandler } from 'app/features/alerting/unified/mocks/server/handlers/k8s/timeIntervals.k8s';
|
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';
|
} from 'app/features/alerting/unified/mocks/server/handlers/plugins';
|
||||||
import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges';
|
import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges';
|
||||||
import { clearPluginSettingsCache } from 'app/features/plugins/pluginSettings';
|
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 { FolderDTO } from 'app/types';
|
||||||
|
|
||||||
import { setupDataSources } from '../../testSetup/datasources';
|
import { setupDataSources } from '../../testSetup/datasources';
|
||||||
@ -60,20 +58,6 @@ export const setFolderResponse = (response: Partial<FolderDTO>) => {
|
|||||||
server.use(handler);
|
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
|
* 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));
|
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 */
|
/** Make alertmanager config update fail */
|
||||||
export const makeGrafanaAlertmanagerConfigUpdateFail = () => {
|
export const makeAlertmanagerConfigUpdateFail = (
|
||||||
server.use(updateGrafanaAlertmanagerConfigHandler(ALERTMANAGER_UPDATE_ERROR_RESPONSE));
|
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 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 { MOCK_SILENCE_ID_EXISTING, mockAlertmanagerAlert } from 'app/features/alerting/unified/mocks';
|
||||||
import { defaultGrafanaAlertingConfigurationStatusResponse } from 'app/features/alerting/unified/mocks/alertmanagerApi';
|
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 { 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 { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
||||||
import { AlertManagerCortexConfig, AlertState } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertManagerCortexConfig, AlertState } from 'app/plugins/datasource/alertmanager/types';
|
||||||
@ -58,11 +62,32 @@ export const alertmanagerAlertsListHandler = () =>
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getGrafanaAlertmanagerConfigHandler = (config: AlertManagerCortexConfig = alertmanagerConfigMock) =>
|
export const getAlertmanagerConfigHandler = (responseOverride?: StrictResponse<JsonBodyType>) =>
|
||||||
http.get('/api/alertmanager/grafana/config/api/v1/alerts', () => HttpResponse.json(config));
|
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) =>
|
const configToReturn = getAlertmanagerConfig(alertmanagerName);
|
||||||
http.get('/api/alertmanager/:name/config/api/v1/alerts', () => HttpResponse.json(config));
|
|
||||||
|
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 });
|
export const ALERTMANAGER_UPDATE_ERROR_RESPONSE = HttpResponse.json({ message: 'bad request' }, { status: 400 });
|
||||||
|
|
||||||
@ -92,20 +117,20 @@ const validateGrafanaAlertmanagerConfig = (config: AlertManagerCortexConfig) =>
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateGrafanaAlertmanagerConfigHandler = (responseOverride?: typeof ALERTMANAGER_UPDATE_ERROR_RESPONSE) =>
|
export const updateAlertmanagerConfigHandler = (responseOverride?: typeof ALERTMANAGER_UPDATE_ERROR_RESPONSE) =>
|
||||||
http.post('/api/alertmanager/grafana/config/api/v1/alerts', async ({ request }) => {
|
http.post<{ name: string }>('/api/alertmanager/:name/config/api/v1/alerts', async ({ request, params }) => {
|
||||||
if (responseOverride) {
|
if (responseOverride) {
|
||||||
return responseOverride;
|
return responseOverride;
|
||||||
}
|
}
|
||||||
|
const { name: alertmanagerName } = params;
|
||||||
const body: AlertManagerCortexConfig = await request.clone().json();
|
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);
|
const potentialError = validateGrafanaAlertmanagerConfig(body);
|
||||||
return potentialError ? potentialError : HttpResponse.json({ message: 'configuration created' });
|
if (!potentialError) {
|
||||||
});
|
// Only update the mock entity the endpoint is going to "succeed"
|
||||||
|
setAlertmanagerConfig(alertmanagerName, body);
|
||||||
const updateAlertmanagerConfigHandler = () =>
|
}
|
||||||
http.post('/api/alertmanager/:name/config/api/v1/alerts', async ({ request }) => {
|
|
||||||
const body: AlertManagerCortexConfig = await request.clone().json();
|
|
||||||
const potentialError = validateGrafanaAlertmanagerConfig(body);
|
|
||||||
return potentialError ? potentialError : HttpResponse.json({ message: 'configuration created' });
|
return potentialError ? potentialError : HttpResponse.json({ message: 'configuration created' });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -140,13 +165,12 @@ const getGroupsHandler = () =>
|
|||||||
const handlers = [
|
const handlers = [
|
||||||
alertmanagerAlertsListHandler(),
|
alertmanagerAlertsListHandler(),
|
||||||
grafanaAlertingConfigurationStatusHandler(),
|
grafanaAlertingConfigurationStatusHandler(),
|
||||||
getGrafanaAlertmanagerConfigHandler(),
|
|
||||||
getAlertmanagerConfigHandler(),
|
getAlertmanagerConfigHandler(),
|
||||||
updateGrafanaAlertmanagerConfigHandler(),
|
|
||||||
updateAlertmanagerConfigHandler(),
|
updateAlertmanagerConfigHandler(),
|
||||||
getGrafanaAlertmanagerTemplatePreview(),
|
getGrafanaAlertmanagerTemplatePreview(),
|
||||||
getReceiversHandler(),
|
getReceiversHandler(),
|
||||||
testReceiversHandler(),
|
testReceiversHandler(),
|
||||||
getGroupsHandler(),
|
getGroupsHandler(),
|
||||||
|
getAlertmanagerStatusHandler(),
|
||||||
];
|
];
|
||||||
export default handlers;
|
export default handlers;
|
||||||
|
@ -1,15 +1,32 @@
|
|||||||
import { http, HttpResponse } from 'msw';
|
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 */
|
/** UID of the alertmanager that is expected to be broken in tests */
|
||||||
export const MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER = 'FwkfQfEmYlAthB';
|
export const MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER = 'FwkfQfEmYlAthB';
|
||||||
/** Display name of the alertmanager that is expected to be broken in tests */
|
/** 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_NAME_BROKEN_ALERTMANAGER = 'broken alertmanager';
|
||||||
export const MOCK_DATASOURCE_EXTERNAL_VANILLA_ALERTMANAGER_UID = 'vanilla-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_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
|
// TODO: Add more accurate endpoint responses as tests require
|
||||||
export const datasourceBuildInfoHandler = () =>
|
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()];
|
const datasourcesHandlers = [datasourceBuildInfoHandler()];
|
||||||
export default datasourcesHandlers;
|
export default datasourcesHandlers;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { camelCase } from 'lodash';
|
import { camelCase } from 'lodash';
|
||||||
import { HttpResponse, http } from 'msw';
|
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 { 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 { 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 { 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
|
// Turn our mock alertmanager config into the format that we expect to be returned by the k8s API
|
||||||
const mappedReceivers =
|
const mappedReceivers =
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { HttpResponse, http } from 'msw';
|
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 { 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 { 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 { 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
|
// Map alertmanager templates to k8s templates
|
||||||
const mappedTemplates = Object.entries(
|
const mappedTemplates = Object.entries(
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { HttpResponse, http } from 'msw';
|
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 { GrafanaManagedContactPoint, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
|
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
||||||
|
|
||||||
const defaultReceiversResponse: GrafanaManagedContactPoint[] = alertmanagerConfig.alertmanager_config.receivers;
|
const alertmanagerConfig = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
|
||||||
|
const defaultReceiversResponse = alertmanagerConfig.alertmanager_config.receivers;
|
||||||
const defaultTimeIntervalsResponse: MuteTimeInterval[] = alertmanagerConfig.alertmanager_config.time_intervals;
|
const defaultTimeIntervalsResponse = alertmanagerConfig.alertmanager_config.time_intervals;
|
||||||
|
|
||||||
const getNotificationReceiversHandler = (response = defaultReceiversResponse) =>
|
const getNotificationReceiversHandler = (response = defaultReceiversResponse) =>
|
||||||
http.get('/api/v1/notifications/receivers', () => HttpResponse.json(response));
|
http.get('/api/v1/notifications/receivers', () => HttpResponse.json(response));
|
||||||
|
Loading…
Reference in New Issue
Block a user