mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
398 lines
12 KiB
TypeScript
398 lines
12 KiB
TypeScript
import { InitialEntry } from 'history';
|
|
import { last } from 'lodash';
|
|
import { Route, Routes } from 'react-router-dom-v5-compat';
|
|
import { render, within, userEvent, screen } from 'test/test-utils';
|
|
import { byTestId } from 'testing-library-selector';
|
|
|
|
import { config } from '@grafana/runtime';
|
|
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
|
|
import { setAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
|
|
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
|
|
import { MOCK_DATASOURCE_EXTERNAL_VANILLA_ALERTMANAGER_UID } from 'app/features/alerting/unified/mocks/server/handlers/datasources';
|
|
import {
|
|
TIME_INTERVAL_NAME_FILE_PROVISIONED,
|
|
TIME_INTERVAL_NAME_HAPPY_PATH,
|
|
} from 'app/features/alerting/unified/mocks/server/handlers/k8s/timeIntervals.k8s';
|
|
import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources';
|
|
import { AlertManagerCortexConfig, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
|
|
import { AccessControlAction } from 'app/types';
|
|
|
|
import EditMuteTimingPage from './components/mute-timings/EditMuteTiming';
|
|
import NewMuteTimingPage from './components/mute-timings/NewMuteTiming';
|
|
import { grantUserPermissions, mockDataSource } from './mocks';
|
|
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
|
|
|
const indexPageText = 'redirected routes page';
|
|
const Index = () => {
|
|
return <div>{indexPageText}</div>;
|
|
};
|
|
const renderMuteTimings = (location?: InitialEntry) => {
|
|
render(
|
|
<Routes>
|
|
<Route path={'/alerting/routes'} element={<Index />} />
|
|
<Route path={'/alerting/routes/new'} element={<NewMuteTimingPage />} />
|
|
<Route path={'/alerting/routes/edit'} element={<EditMuteTimingPage />} />
|
|
</Routes>,
|
|
{ historyOptions: location ? { initialEntries: [location] } : undefined }
|
|
);
|
|
};
|
|
|
|
const alertmanagerName = 'alertmanager';
|
|
|
|
const dataSources = {
|
|
am: mockDataSource({
|
|
name: alertmanagerName,
|
|
uid: MOCK_DATASOURCE_EXTERNAL_VANILLA_ALERTMANAGER_UID,
|
|
type: DataSourceType.Alertmanager,
|
|
}),
|
|
};
|
|
|
|
const ui = {
|
|
nameField: byTestId('mute-timing-name'),
|
|
|
|
startsAt: byTestId('mute-timing-starts-at'),
|
|
endsAt: byTestId('mute-timing-ends-at'),
|
|
|
|
weekdays: byTestId('mute-timing-weekdays'),
|
|
days: byTestId('mute-timing-days'),
|
|
months: byTestId('mute-timing-months'),
|
|
years: byTestId('mute-timing-years'),
|
|
};
|
|
|
|
const muteTimeInterval: MuteTimeInterval = {
|
|
name: 'default-mute',
|
|
time_intervals: [
|
|
{
|
|
times: [
|
|
{
|
|
start_time: '12:00',
|
|
end_time: '24:00',
|
|
},
|
|
],
|
|
days_of_month: ['15', '-1'],
|
|
months: ['august:december', 'march'],
|
|
},
|
|
],
|
|
};
|
|
const muteTimeInterval2: MuteTimeInterval = {
|
|
name: 'default-mute2',
|
|
time_intervals: [
|
|
{
|
|
times: [
|
|
{
|
|
start_time: '12:00',
|
|
end_time: '24:00',
|
|
},
|
|
],
|
|
days_of_month: ['15', '-1'],
|
|
months: ['august:december', 'march'],
|
|
},
|
|
],
|
|
};
|
|
|
|
/** Alertmanager config where time intervals are stored in `mute_time_intervals` property */
|
|
export const defaultConfig: AlertManagerCortexConfig = {
|
|
alertmanager_config: {
|
|
receivers: [{ name: 'default' }, { name: 'critical' }],
|
|
route: {
|
|
receiver: 'default',
|
|
group_by: ['alertname'],
|
|
routes: [
|
|
{
|
|
matchers: ['env=prod', 'region!=EU'],
|
|
mute_time_intervals: [muteTimeInterval.name],
|
|
},
|
|
],
|
|
},
|
|
templates: [],
|
|
mute_time_intervals: [muteTimeInterval],
|
|
},
|
|
template_files: {},
|
|
};
|
|
|
|
/** Alertmanager config where time intervals are stored in `time_intervals` property */
|
|
const defaultConfigWithNewTimeIntervalsField: AlertManagerCortexConfig = {
|
|
alertmanager_config: {
|
|
receivers: [{ name: 'default' }, { name: 'critical' }],
|
|
route: {
|
|
receiver: 'default',
|
|
group_by: ['alertname'],
|
|
routes: [
|
|
{
|
|
matchers: ['env=prod', 'region!=EU'],
|
|
mute_time_intervals: [muteTimeInterval.name],
|
|
},
|
|
],
|
|
},
|
|
templates: [],
|
|
time_intervals: [muteTimeInterval],
|
|
},
|
|
template_files: {},
|
|
};
|
|
|
|
/** Alertmanager config where time intervals are stored in both `time_intervals` and `mute_time_intervals` properties */
|
|
const defaultConfigWithBothTimeIntervalsField: AlertManagerCortexConfig = {
|
|
alertmanager_config: {
|
|
receivers: [{ name: 'default' }, { name: 'critical' }],
|
|
route: {
|
|
receiver: 'default',
|
|
group_by: ['alertname'],
|
|
routes: [
|
|
{
|
|
matchers: ['env=prod', 'region!=EU'],
|
|
mute_time_intervals: [muteTimeInterval.name],
|
|
},
|
|
],
|
|
},
|
|
templates: [],
|
|
time_intervals: [muteTimeInterval],
|
|
mute_time_intervals: [muteTimeInterval2],
|
|
},
|
|
template_files: {},
|
|
};
|
|
|
|
const expectToHaveRedirectedToRoutesRoute = async () =>
|
|
expect(await screen.findByText(indexPageText)).toBeInTheDocument();
|
|
|
|
const fillOutForm = async ({
|
|
name,
|
|
startsAt,
|
|
endsAt,
|
|
days,
|
|
months,
|
|
years,
|
|
}: {
|
|
name?: string;
|
|
startsAt?: string;
|
|
endsAt?: string;
|
|
days?: string;
|
|
months?: string;
|
|
years?: string;
|
|
}) => {
|
|
const user = userEvent.setup();
|
|
name && (await user.type(ui.nameField.get(), name));
|
|
startsAt && (await user.type(ui.startsAt.get(), startsAt));
|
|
endsAt && (await user.type(ui.endsAt.get(), endsAt));
|
|
days && (await user.type(ui.days.get(), days));
|
|
months && (await user.type(ui.months.get(), months));
|
|
years && (await user.type(ui.years.get(), years));
|
|
};
|
|
|
|
const saveMuteTiming = async () => {
|
|
const user = userEvent.setup();
|
|
await user.click(await screen.findByText(/save mute timing/i));
|
|
};
|
|
|
|
setupMswServer();
|
|
|
|
const getAlertmanagerConfigUpdate = async (requests: Request[]): Promise<AlertManagerCortexConfig> => {
|
|
const alertmanagerUpdate = requests.find(
|
|
(r) => r.url.match('/alertmanager/(.*)/config/api/v1/alert') && r.method === 'POST'
|
|
);
|
|
|
|
return alertmanagerUpdate!.clone().json();
|
|
};
|
|
|
|
describe('Mute timings', () => {
|
|
beforeEach(() => {
|
|
setupDataSources(dataSources.am);
|
|
// FIXME: scope down
|
|
grantUserPermissions(Object.values(AccessControlAction));
|
|
|
|
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, defaultConfig);
|
|
|
|
// TODO: Add this at a higher level to ensure that no tests depend on others running first
|
|
// Without this, the selected alertmanager in a previous test can affect the next, meaning tests
|
|
// pass/fail depending on the order they are run/if they are focused
|
|
window.localStorage.clear();
|
|
});
|
|
|
|
it('creates a new mute timing, with mute_time_intervals in config', async () => {
|
|
const capture = captureRequests();
|
|
renderMuteTimings('/alerting/routes/new');
|
|
|
|
await screen.findByText(/add mute timing/i);
|
|
|
|
await fillOutForm({
|
|
name: 'maintenance period',
|
|
startsAt: '22:00',
|
|
endsAt: '24:00',
|
|
days: '-1',
|
|
months: 'january, july',
|
|
});
|
|
|
|
await saveMuteTiming();
|
|
|
|
await expectToHaveRedirectedToRoutesRoute();
|
|
|
|
const requests = await capture;
|
|
const alertmanagerUpdate = await getAlertmanagerConfigUpdate(requests);
|
|
const lastAdded = last(alertmanagerUpdate.alertmanager_config.time_intervals);
|
|
|
|
// Check that the last mute_time_interval is the one we just submitted via the form
|
|
expect(lastAdded?.name).toEqual('maintenance period');
|
|
});
|
|
|
|
it('creates a new mute timing, with time_intervals in config', async () => {
|
|
const capture = captureRequests();
|
|
setAlertmanagerConfig(dataSources.am.uid, defaultConfigWithNewTimeIntervalsField);
|
|
renderMuteTimings({ pathname: '/alerting/routes/new', search: `?alertmanager=${dataSources.am.name}` });
|
|
|
|
await fillOutForm({
|
|
name: 'maintenance period',
|
|
startsAt: '22:01',
|
|
endsAt: '24:00',
|
|
days: '-1',
|
|
months: 'january, july',
|
|
});
|
|
|
|
await saveMuteTiming();
|
|
await expectToHaveRedirectedToRoutesRoute();
|
|
|
|
const requests = await capture;
|
|
const alertmanagerUpdate = await getAlertmanagerConfigUpdate(requests);
|
|
const lastAdded = last(alertmanagerUpdate.alertmanager_config.time_intervals);
|
|
|
|
expect(lastAdded?.name).toEqual('maintenance period');
|
|
});
|
|
|
|
it('creates a new mute timing, with time_intervals and mute_time_intervals in config', async () => {
|
|
setAlertmanagerConfig(dataSources.am.uid, defaultConfigWithBothTimeIntervalsField);
|
|
renderMuteTimings({ pathname: '/alerting/routes/new', search: `?alertmanager=${dataSources.am.name}` });
|
|
|
|
expect(ui.nameField.get()).toBeInTheDocument();
|
|
|
|
await fillOutForm({
|
|
name: 'maintenance period',
|
|
startsAt: '22:00',
|
|
endsAt: '24:00',
|
|
days: '-1',
|
|
months: 'january, july',
|
|
});
|
|
|
|
await saveMuteTiming();
|
|
await expectToHaveRedirectedToRoutesRoute();
|
|
});
|
|
|
|
it('prepopulates the form when editing a mute timing', async () => {
|
|
const capture = captureRequests();
|
|
|
|
renderMuteTimings({
|
|
pathname: '/alerting/routes/edit',
|
|
search: `?muteName=${encodeURIComponent(muteTimeInterval.name)}`,
|
|
});
|
|
|
|
expect(await ui.nameField.find()).toBeInTheDocument();
|
|
expect(ui.nameField.get()).toHaveValue(muteTimeInterval.name);
|
|
expect(ui.months.get()).toHaveValue(muteTimeInterval.time_intervals[0].months?.join(', '));
|
|
|
|
await userEvent.clear(ui.startsAt.getAll()?.[0]);
|
|
await userEvent.clear(ui.endsAt.getAll()?.[0]);
|
|
await userEvent.clear(ui.days.get());
|
|
await userEvent.clear(ui.months.get());
|
|
await userEvent.clear(ui.years.get());
|
|
|
|
const monday = within(ui.weekdays.get()).getByText('Mon');
|
|
await userEvent.click(monday);
|
|
|
|
const formValues = {
|
|
days: '-7:-1',
|
|
months: '3, 6, 9, 12',
|
|
years: '2021:2024',
|
|
};
|
|
|
|
await fillOutForm(formValues);
|
|
|
|
await saveMuteTiming();
|
|
await expectToHaveRedirectedToRoutesRoute();
|
|
|
|
const requests = await capture;
|
|
const alertmanagerUpdate = await getAlertmanagerConfigUpdate(requests);
|
|
const lastAdded = last(alertmanagerUpdate.alertmanager_config.mute_time_intervals);
|
|
|
|
expect(lastAdded?.time_intervals[0]).toMatchObject({
|
|
days_of_month: [formValues.days],
|
|
months: formValues.months.split(', '),
|
|
years: [formValues.years],
|
|
});
|
|
});
|
|
|
|
it('form is invalid with duplicate mute timing name', async () => {
|
|
renderMuteTimings('/alerting/routes/new');
|
|
|
|
await fillOutForm({ name: muteTimeInterval.name, days: '1' });
|
|
|
|
await saveMuteTiming();
|
|
|
|
expect(await screen.findByRole('alert')).toBeInTheDocument();
|
|
});
|
|
|
|
it('replaces mute timings in routes when the mute timing name is changed', async () => {
|
|
renderMuteTimings({
|
|
pathname: '/alerting/routes/edit',
|
|
search: `?muteName=${encodeURIComponent(muteTimeInterval.name)}`,
|
|
});
|
|
|
|
expect(await ui.nameField.find()).toBeInTheDocument();
|
|
expect(ui.nameField.get()).toHaveValue(muteTimeInterval.name);
|
|
|
|
await userEvent.clear(ui.nameField.get());
|
|
await fillOutForm({ name: 'Lunch breaks' });
|
|
await saveMuteTiming();
|
|
|
|
await expectToHaveRedirectedToRoutesRoute();
|
|
});
|
|
|
|
it('shows error when mute timing does not exist', async () => {
|
|
renderMuteTimings({
|
|
pathname: '/alerting/routes/edit',
|
|
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${'does not exist'}`,
|
|
});
|
|
|
|
expect(await screen.findByText(/No matching mute timing found/i)).toBeInTheDocument();
|
|
});
|
|
|
|
describe('alertingApiServer feature toggle', () => {
|
|
beforeEach(() => {
|
|
config.featureToggles.alertingApiServer = true;
|
|
});
|
|
|
|
it('allows creation of new mute timings', async () => {
|
|
renderMuteTimings('/alerting/routes/new');
|
|
|
|
await fillOutForm({ name: 'a new mute timing' });
|
|
|
|
await saveMuteTiming();
|
|
await expectToHaveRedirectedToRoutesRoute();
|
|
});
|
|
|
|
it('shows error when mute timing does not exist', async () => {
|
|
renderMuteTimings({
|
|
pathname: '/alerting/routes/edit',
|
|
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_NAME_HAPPY_PATH + '_force_breakage'}`,
|
|
});
|
|
|
|
expect(await screen.findByText(/No matching mute timing found/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('loads edit form correctly and allows saving', async () => {
|
|
renderMuteTimings({
|
|
pathname: '/alerting/routes/edit',
|
|
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_NAME_HAPPY_PATH}`,
|
|
});
|
|
|
|
await saveMuteTiming();
|
|
await expectToHaveRedirectedToRoutesRoute();
|
|
});
|
|
|
|
it('loads view form for provisioned interval', async () => {
|
|
renderMuteTimings({
|
|
pathname: '/alerting/routes/edit',
|
|
search: `?muteName=${TIME_INTERVAL_NAME_FILE_PROVISIONED}`,
|
|
});
|
|
|
|
expect(await screen.findByText(/This mute timing cannot be edited through the UI/i)).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|