AmRoutes.test.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. import { render, waitFor } from '@testing-library/react';
  2. import userEvent from '@testing-library/user-event';
  3. import React from 'react';
  4. import { Provider } from 'react-redux';
  5. import { Router } from 'react-router-dom';
  6. import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
  7. import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector';
  8. import { locationService, setDataSourceSrv } from '@grafana/runtime';
  9. import { contextSrv } from 'app/core/services/context_srv';
  10. import {
  11. AlertManagerCortexConfig,
  12. AlertManagerDataSourceJsonData,
  13. AlertManagerImplementation,
  14. MuteTimeInterval,
  15. Route,
  16. } from 'app/plugins/datasource/alertmanager/types';
  17. import { configureStore } from 'app/store/configureStore';
  18. import { AccessControlAction } from 'app/types';
  19. import AmRoutes from './AmRoutes';
  20. import { fetchAlertManagerConfig, fetchStatus, updateAlertManagerConfig } from './api/alertmanager';
  21. import { mockDataSource, MockDataSourceSrv, someCloudAlertManagerConfig, someCloudAlertManagerStatus } from './mocks';
  22. import { getAllDataSources } from './utils/config';
  23. import { ALERTMANAGER_NAME_QUERY_KEY } from './utils/constants';
  24. import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
  25. jest.mock('./api/alertmanager');
  26. jest.mock('./utils/config');
  27. jest.mock('app/core/services/context_srv');
  28. const mocks = {
  29. getAllDataSourcesMock: jest.mocked(getAllDataSources),
  30. api: {
  31. fetchAlertManagerConfig: jest.mocked(fetchAlertManagerConfig),
  32. updateAlertManagerConfig: jest.mocked(updateAlertManagerConfig),
  33. fetchStatus: jest.mocked(fetchStatus),
  34. },
  35. contextSrv: jest.mocked(contextSrv),
  36. };
  37. const renderAmRoutes = (alertManagerSourceName?: string) => {
  38. const store = configureStore();
  39. locationService.push(location);
  40. locationService.push(
  41. '/alerting/routes' + (alertManagerSourceName ? `?${ALERTMANAGER_NAME_QUERY_KEY}=${alertManagerSourceName}` : '')
  42. );
  43. return render(
  44. <Provider store={store}>
  45. <Router history={locationService.getHistory()}>
  46. <AmRoutes />
  47. </Router>
  48. </Provider>
  49. );
  50. };
  51. const dataSources = {
  52. am: mockDataSource({
  53. name: 'Alertmanager',
  54. type: DataSourceType.Alertmanager,
  55. }),
  56. promAlertManager: mockDataSource<AlertManagerDataSourceJsonData>({
  57. name: 'PromManager',
  58. type: DataSourceType.Alertmanager,
  59. jsonData: {
  60. implementation: AlertManagerImplementation.prometheus,
  61. },
  62. }),
  63. };
  64. const ui = {
  65. rootReceiver: byTestId('am-routes-root-receiver'),
  66. rootGroupBy: byTestId('am-routes-root-group-by'),
  67. rootTimings: byTestId('am-routes-root-timings'),
  68. row: byTestId('am-routes-row'),
  69. rootRouteContainer: byTestId('am-root-route-container'),
  70. editButton: byRole('button', { name: 'Edit' }),
  71. saveButton: byRole('button', { name: 'Save' }),
  72. editRouteButton: byLabelText('Edit route'),
  73. deleteRouteButton: byLabelText('Delete route'),
  74. newPolicyButton: byRole('button', { name: /New policy/ }),
  75. newPolicyCTAButton: byRole('button', { name: /New specific policy/ }),
  76. savePolicyButton: byRole('button', { name: /save policy/i }),
  77. receiverSelect: byTestId('am-receiver-select'),
  78. groupSelect: byTestId('am-group-select'),
  79. muteTimingSelect: byTestId('am-mute-timing-select'),
  80. groupWaitContainer: byTestId('am-group-wait'),
  81. groupIntervalContainer: byTestId('am-group-interval'),
  82. groupRepeatContainer: byTestId('am-repeat-interval'),
  83. confirmDeleteModal: byRole('dialog'),
  84. confirmDeleteButton: byLabelText('Confirm Modal Danger Button'),
  85. };
  86. describe('AmRoutes', () => {
  87. const subroutes: Route[] = [
  88. {
  89. match: {
  90. sub1matcher1: 'sub1value1',
  91. sub1matcher2: 'sub1value2',
  92. },
  93. match_re: {
  94. sub1matcher3: 'sub1value3',
  95. sub1matcher4: 'sub1value4',
  96. },
  97. group_by: ['sub1group1', 'sub1group2'],
  98. receiver: 'a-receiver',
  99. continue: true,
  100. group_wait: '3s',
  101. group_interval: '2m',
  102. repeat_interval: '1s',
  103. routes: [
  104. {
  105. match: {
  106. sub1sub1matcher1: 'sub1sub1value1',
  107. sub1sub1matcher2: 'sub1sub1value2',
  108. },
  109. match_re: {
  110. sub1sub1matcher3: 'sub1sub1value3',
  111. sub1sub1matcher4: 'sub1sub1value4',
  112. },
  113. group_by: ['sub1sub1group1', 'sub1sub1group2'],
  114. receiver: 'another-receiver',
  115. },
  116. {
  117. match: {
  118. sub1sub2matcher1: 'sub1sub2value1',
  119. sub1sub2matcher2: 'sub1sub2value2',
  120. },
  121. match_re: {
  122. sub1sub2matcher3: 'sub1sub2value3',
  123. sub1sub2matcher4: 'sub1sub2value4',
  124. },
  125. group_by: ['sub1sub2group1', 'sub1sub2group2'],
  126. receiver: 'another-receiver',
  127. },
  128. ],
  129. },
  130. {
  131. match: {
  132. sub2matcher1: 'sub2value1',
  133. sub2matcher2: 'sub2value2',
  134. },
  135. match_re: {
  136. sub2matcher3: 'sub2value3',
  137. sub2matcher4: 'sub2value4',
  138. },
  139. receiver: 'another-receiver',
  140. },
  141. ];
  142. const emptyRoute: Route = {};
  143. const simpleRoute: Route = {
  144. receiver: 'simple-receiver',
  145. matchers: ['hello=world', 'foo!=bar'],
  146. };
  147. const rootRoute: Route = {
  148. receiver: 'default-receiver',
  149. group_by: ['a-group', 'another-group'],
  150. group_wait: '1s',
  151. group_interval: '2m',
  152. repeat_interval: '3d',
  153. routes: subroutes,
  154. };
  155. const muteInterval: MuteTimeInterval = {
  156. name: 'default-mute',
  157. time_intervals: [
  158. {
  159. times: [{ start_time: '12:00', end_time: '24:00' }],
  160. weekdays: ['monday:friday'],
  161. days_of_month: ['1:7', '-1:-7'],
  162. months: ['january:june'],
  163. years: ['2020:2022'],
  164. },
  165. ],
  166. };
  167. beforeEach(() => {
  168. mocks.getAllDataSourcesMock.mockReturnValue(Object.values(dataSources));
  169. mocks.contextSrv.hasAccess.mockImplementation(() => true);
  170. mocks.contextSrv.hasPermission.mockImplementation(() => true);
  171. mocks.contextSrv.evaluatePermission.mockImplementation(() => []);
  172. setDataSourceSrv(new MockDataSourceSrv(dataSources));
  173. });
  174. afterEach(() => {
  175. jest.resetAllMocks();
  176. setDataSourceSrv(undefined as any);
  177. });
  178. it('loads and shows routes', async () => {
  179. mocks.api.fetchAlertManagerConfig.mockResolvedValue({
  180. alertmanager_config: {
  181. route: rootRoute,
  182. receivers: [
  183. {
  184. name: 'default-receiver',
  185. },
  186. {
  187. name: 'a-receiver',
  188. },
  189. {
  190. name: 'another-receiver',
  191. },
  192. ],
  193. },
  194. template_files: {},
  195. });
  196. await renderAmRoutes();
  197. await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1));
  198. expect(ui.rootReceiver.get()).toHaveTextContent(rootRoute.receiver!);
  199. expect(ui.rootGroupBy.get()).toHaveTextContent(rootRoute.group_by!.join(', '));
  200. const rootTimings = ui.rootTimings.get();
  201. expect(rootTimings).toHaveTextContent(rootRoute.group_wait!);
  202. expect(rootTimings).toHaveTextContent(rootRoute.group_interval!);
  203. expect(rootTimings).toHaveTextContent(rootRoute.repeat_interval!);
  204. const rows = await ui.row.findAll();
  205. expect(rows).toHaveLength(2);
  206. subroutes.forEach((route, index) => {
  207. Object.entries(route.match ?? {}).forEach(([label, value]) => {
  208. expect(rows[index]).toHaveTextContent(`${label}=${value}`);
  209. });
  210. Object.entries(route.match_re ?? {}).forEach(([label, value]) => {
  211. expect(rows[index]).toHaveTextContent(`${label}=~${value}`);
  212. });
  213. if (route.group_by) {
  214. expect(rows[index]).toHaveTextContent(route.group_by.join(', '));
  215. }
  216. if (route.receiver) {
  217. expect(rows[index]).toHaveTextContent(route.receiver);
  218. }
  219. });
  220. });
  221. it('can edit root route if one is already defined', async () => {
  222. const defaultConfig: AlertManagerCortexConfig = {
  223. alertmanager_config: {
  224. receivers: [{ name: 'default' }, { name: 'critical' }],
  225. route: {
  226. receiver: 'default',
  227. group_by: ['alertname'],
  228. },
  229. templates: [],
  230. },
  231. template_files: {},
  232. };
  233. const currentConfig = { current: defaultConfig };
  234. mocks.api.updateAlertManagerConfig.mockImplementation((amSourceName, newConfig) => {
  235. currentConfig.current = newConfig;
  236. return Promise.resolve();
  237. });
  238. mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
  239. return Promise.resolve(currentConfig.current);
  240. });
  241. await renderAmRoutes();
  242. expect(await ui.rootReceiver.find()).toHaveTextContent('default');
  243. expect(ui.rootGroupBy.get()).toHaveTextContent('alertname');
  244. // open root route for editing
  245. const rootRouteContainer = await ui.rootRouteContainer.find();
  246. await userEvent.click(ui.editButton.get(rootRouteContainer));
  247. // configure receiver & group by
  248. const receiverSelect = await ui.receiverSelect.find();
  249. await clickSelectOption(receiverSelect, 'critical');
  250. const groupSelect = ui.groupSelect.get();
  251. await userEvent.type(byRole('combobox').get(groupSelect), 'namespace{enter}');
  252. // configure timing intervals
  253. await userEvent.click(byText('Timing options').get(rootRouteContainer));
  254. await updateTiming(ui.groupWaitContainer.get(), '1', 'Minutes');
  255. await updateTiming(ui.groupIntervalContainer.get(), '4', 'Minutes');
  256. await updateTiming(ui.groupRepeatContainer.get(), '5', 'Hours');
  257. //save
  258. await userEvent.click(ui.saveButton.get(rootRouteContainer));
  259. // wait for it to go out of edit mode
  260. await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument());
  261. // check that appropriate api calls were made
  262. expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(3);
  263. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledTimes(1);
  264. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(GRAFANA_RULES_SOURCE_NAME, {
  265. alertmanager_config: {
  266. receivers: [{ name: 'default' }, { name: 'critical' }],
  267. route: {
  268. continue: false,
  269. group_by: ['alertname', 'namespace'],
  270. receiver: 'critical',
  271. routes: [],
  272. group_interval: '4m',
  273. group_wait: '1m',
  274. repeat_interval: '5h',
  275. mute_time_intervals: [],
  276. },
  277. templates: [],
  278. },
  279. template_files: {},
  280. });
  281. // check that new config values are rendered
  282. await waitFor(() => expect(ui.rootReceiver.query()).toHaveTextContent('critical'));
  283. expect(ui.rootGroupBy.get()).toHaveTextContent('alertname, namespace');
  284. });
  285. it('can edit root route if one is not defined yet', async () => {
  286. mocks.api.fetchAlertManagerConfig.mockResolvedValue({
  287. alertmanager_config: {
  288. receivers: [{ name: 'default' }],
  289. },
  290. template_files: {},
  291. });
  292. await renderAmRoutes();
  293. // open root route for editing
  294. const rootRouteContainer = await ui.rootRouteContainer.find();
  295. await userEvent.click(ui.editButton.get(rootRouteContainer));
  296. // configure receiver & group by
  297. const receiverSelect = await ui.receiverSelect.find();
  298. await clickSelectOption(receiverSelect, 'default');
  299. const groupSelect = ui.groupSelect.get();
  300. await userEvent.type(byRole('combobox').get(groupSelect), 'severity{enter}');
  301. await userEvent.type(byRole('combobox').get(groupSelect), 'namespace{enter}');
  302. //save
  303. await userEvent.click(ui.saveButton.get(rootRouteContainer));
  304. // wait for it to go out of edit mode
  305. await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument());
  306. // check that appropriate api calls were made
  307. expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(3);
  308. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledTimes(1);
  309. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(GRAFANA_RULES_SOURCE_NAME, {
  310. alertmanager_config: {
  311. receivers: [{ name: 'default' }],
  312. route: {
  313. continue: false,
  314. group_by: ['severity', 'namespace'],
  315. receiver: 'default',
  316. routes: [],
  317. mute_time_intervals: [],
  318. },
  319. },
  320. template_files: {},
  321. });
  322. });
  323. it('hides create and edit button if user does not have permission', () => {
  324. mocks.contextSrv.hasAccess.mockImplementation((action) =>
  325. [AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsRead].includes(
  326. action as AccessControlAction
  327. )
  328. );
  329. renderAmRoutes();
  330. expect(ui.newPolicyButton.query()).not.toBeInTheDocument();
  331. expect(ui.editButton.query()).not.toBeInTheDocument();
  332. });
  333. it('Show error message if loading Alertmanager config fails', async () => {
  334. mocks.api.fetchAlertManagerConfig.mockRejectedValue({
  335. status: 500,
  336. data: {
  337. message: "Alertmanager has exploded. it's gone. Forget about it.",
  338. },
  339. });
  340. await renderAmRoutes();
  341. await waitFor(() => expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalledTimes(1));
  342. expect(await byText("Alertmanager has exploded. it's gone. Forget about it.").find()).toBeInTheDocument();
  343. expect(ui.rootReceiver.query()).not.toBeInTheDocument();
  344. expect(ui.editButton.query()).not.toBeInTheDocument();
  345. });
  346. it('Converts matchers to object_matchers for grafana alertmanager', async () => {
  347. const defaultConfig: AlertManagerCortexConfig = {
  348. alertmanager_config: {
  349. receivers: [{ name: 'default' }, { name: 'critical' }],
  350. route: {
  351. continue: false,
  352. receiver: 'default',
  353. group_by: ['alertname'],
  354. routes: [simpleRoute],
  355. group_interval: '4m',
  356. group_wait: '1m',
  357. repeat_interval: '5h',
  358. },
  359. templates: [],
  360. },
  361. template_files: {},
  362. };
  363. const currentConfig = { current: defaultConfig };
  364. mocks.api.updateAlertManagerConfig.mockImplementation((amSourceName, newConfig) => {
  365. currentConfig.current = newConfig;
  366. return Promise.resolve();
  367. });
  368. mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
  369. return Promise.resolve(currentConfig.current);
  370. });
  371. await renderAmRoutes(GRAFANA_RULES_SOURCE_NAME);
  372. expect(await ui.rootReceiver.find()).toHaveTextContent('default');
  373. expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled();
  374. // Toggle a save to test new object_matchers
  375. const rootRouteContainer = await ui.rootRouteContainer.find();
  376. await userEvent.click(ui.editButton.get(rootRouteContainer));
  377. await userEvent.click(ui.saveButton.get(rootRouteContainer));
  378. await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument());
  379. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled();
  380. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(GRAFANA_RULES_SOURCE_NAME, {
  381. alertmanager_config: {
  382. receivers: [{ name: 'default' }, { name: 'critical' }],
  383. route: {
  384. continue: false,
  385. group_by: ['alertname'],
  386. group_interval: '4m',
  387. group_wait: '1m',
  388. receiver: 'default',
  389. repeat_interval: '5h',
  390. mute_time_intervals: [],
  391. routes: [
  392. {
  393. continue: false,
  394. group_by: [],
  395. object_matchers: [
  396. ['hello', '=', 'world'],
  397. ['foo', '!=', 'bar'],
  398. ],
  399. receiver: 'simple-receiver',
  400. mute_time_intervals: [],
  401. routes: [],
  402. },
  403. ],
  404. },
  405. templates: [],
  406. },
  407. template_files: {},
  408. });
  409. });
  410. it('Should be able to delete an empty route', async () => {
  411. const routeConfig = {
  412. continue: false,
  413. receiver: 'default',
  414. group_by: ['alertname'],
  415. routes: [emptyRoute],
  416. group_interval: '4m',
  417. group_wait: '1m',
  418. repeat_interval: '5h',
  419. mute_time_intervals: [],
  420. };
  421. const defaultConfig: AlertManagerCortexConfig = {
  422. alertmanager_config: {
  423. receivers: [{ name: 'default' }, { name: 'critical' }],
  424. route: routeConfig,
  425. templates: [],
  426. },
  427. template_files: {},
  428. };
  429. mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
  430. return Promise.resolve(defaultConfig);
  431. });
  432. mocks.api.updateAlertManagerConfig.mockResolvedValue(Promise.resolve());
  433. await renderAmRoutes(GRAFANA_RULES_SOURCE_NAME);
  434. expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled();
  435. const deleteButtons = await ui.deleteRouteButton.findAll();
  436. expect(deleteButtons).toHaveLength(1);
  437. await userEvent.click(deleteButtons[0]);
  438. const confirmDeleteButton = ui.confirmDeleteButton.get(ui.confirmDeleteModal.get());
  439. expect(confirmDeleteButton).toBeInTheDocument();
  440. await userEvent.click(confirmDeleteButton);
  441. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith<[string, AlertManagerCortexConfig]>(
  442. GRAFANA_RULES_SOURCE_NAME,
  443. {
  444. ...defaultConfig,
  445. alertmanager_config: {
  446. ...defaultConfig.alertmanager_config,
  447. route: {
  448. ...routeConfig,
  449. routes: [],
  450. },
  451. },
  452. }
  453. );
  454. });
  455. it('Keeps matchers for non-grafana alertmanager sources', async () => {
  456. const defaultConfig: AlertManagerCortexConfig = {
  457. alertmanager_config: {
  458. receivers: [{ name: 'default' }, { name: 'critical' }],
  459. route: {
  460. continue: false,
  461. receiver: 'default',
  462. group_by: ['alertname'],
  463. routes: [simpleRoute],
  464. group_interval: '4m',
  465. group_wait: '1m',
  466. repeat_interval: '5h',
  467. },
  468. templates: [],
  469. },
  470. template_files: {},
  471. };
  472. const currentConfig = { current: defaultConfig };
  473. mocks.api.updateAlertManagerConfig.mockImplementation((amSourceName, newConfig) => {
  474. currentConfig.current = newConfig;
  475. return Promise.resolve();
  476. });
  477. mocks.api.fetchAlertManagerConfig.mockImplementation(() => {
  478. return Promise.resolve(currentConfig.current);
  479. });
  480. await renderAmRoutes(dataSources.am.name);
  481. expect(await ui.rootReceiver.find()).toHaveTextContent('default');
  482. expect(mocks.api.fetchAlertManagerConfig).toHaveBeenCalled();
  483. // Toggle a save to test new object_matchers
  484. const rootRouteContainer = await ui.rootRouteContainer.find();
  485. await userEvent.click(ui.editButton.get(rootRouteContainer));
  486. await userEvent.click(ui.saveButton.get(rootRouteContainer));
  487. await waitFor(() => expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument());
  488. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled();
  489. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(dataSources.am.name, {
  490. alertmanager_config: {
  491. receivers: [{ name: 'default' }, { name: 'critical' }],
  492. route: {
  493. continue: false,
  494. group_by: ['alertname'],
  495. group_interval: '4m',
  496. group_wait: '1m',
  497. matchers: [],
  498. receiver: 'default',
  499. repeat_interval: '5h',
  500. mute_time_intervals: [],
  501. routes: [
  502. {
  503. continue: false,
  504. group_by: [],
  505. matchers: ['hello=world', 'foo!=bar'],
  506. receiver: 'simple-receiver',
  507. routes: [],
  508. mute_time_intervals: [],
  509. },
  510. ],
  511. },
  512. templates: [],
  513. },
  514. template_files: {},
  515. });
  516. });
  517. it('Prometheus Alertmanager routes cannot be edited', async () => {
  518. mocks.api.fetchStatus.mockResolvedValue({
  519. ...someCloudAlertManagerStatus,
  520. config: someCloudAlertManagerConfig.alertmanager_config,
  521. });
  522. await renderAmRoutes(dataSources.promAlertManager.name);
  523. const rootRouteContainer = await ui.rootRouteContainer.find();
  524. expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument();
  525. const rows = await ui.row.findAll();
  526. expect(rows).toHaveLength(2);
  527. expect(ui.editRouteButton.query()).not.toBeInTheDocument();
  528. expect(ui.deleteRouteButton.query()).not.toBeInTheDocument();
  529. expect(ui.saveButton.query()).not.toBeInTheDocument();
  530. expect(mocks.api.fetchAlertManagerConfig).not.toHaveBeenCalled();
  531. expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1);
  532. });
  533. it('Prometheus Alertmanager has no CTA button if there are no specific policies', async () => {
  534. mocks.api.fetchStatus.mockResolvedValue({
  535. ...someCloudAlertManagerStatus,
  536. config: {
  537. ...someCloudAlertManagerConfig.alertmanager_config,
  538. route: {
  539. ...someCloudAlertManagerConfig.alertmanager_config.route,
  540. routes: undefined,
  541. },
  542. },
  543. });
  544. await renderAmRoutes(dataSources.promAlertManager.name);
  545. const rootRouteContainer = await ui.rootRouteContainer.find();
  546. expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument();
  547. expect(ui.newPolicyCTAButton.query()).not.toBeInTheDocument();
  548. expect(mocks.api.fetchAlertManagerConfig).not.toHaveBeenCalled();
  549. expect(mocks.api.fetchStatus).toHaveBeenCalledTimes(1);
  550. });
  551. it('Can add a mute timing to a route', async () => {
  552. const defaultConfig: AlertManagerCortexConfig = {
  553. alertmanager_config: {
  554. receivers: [{ name: 'default' }, { name: 'critical' }],
  555. route: {
  556. continue: false,
  557. receiver: 'default',
  558. group_by: ['alertname'],
  559. routes: [simpleRoute],
  560. group_interval: '4m',
  561. group_wait: '1m',
  562. repeat_interval: '5h',
  563. },
  564. templates: [],
  565. mute_time_intervals: [muteInterval],
  566. },
  567. template_files: {},
  568. };
  569. const currentConfig = { current: defaultConfig };
  570. mocks.api.updateAlertManagerConfig.mockImplementation((amSourceName, newConfig) => {
  571. currentConfig.current = newConfig;
  572. return Promise.resolve();
  573. });
  574. mocks.api.fetchAlertManagerConfig.mockResolvedValue(defaultConfig);
  575. await renderAmRoutes(dataSources.am.name);
  576. const rows = await ui.row.findAll();
  577. expect(rows).toHaveLength(1);
  578. await userEvent.click(ui.editRouteButton.get(rows[0]));
  579. const muteTimingSelect = ui.muteTimingSelect.get();
  580. await clickSelectOption(muteTimingSelect, 'default-mute');
  581. expect(muteTimingSelect).toHaveTextContent('default-mute');
  582. const savePolicyButton = ui.savePolicyButton.get();
  583. expect(savePolicyButton).toBeInTheDocument();
  584. await userEvent.click(savePolicyButton);
  585. await waitFor(() => expect(savePolicyButton).not.toBeInTheDocument());
  586. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalled();
  587. expect(mocks.api.updateAlertManagerConfig).toHaveBeenCalledWith(dataSources.am.name, {
  588. ...defaultConfig,
  589. alertmanager_config: {
  590. ...defaultConfig.alertmanager_config,
  591. route: {
  592. ...defaultConfig.alertmanager_config.route,
  593. mute_time_intervals: [],
  594. matchers: [],
  595. routes: [
  596. {
  597. ...simpleRoute,
  598. mute_time_intervals: [muteInterval.name],
  599. routes: [],
  600. continue: false,
  601. group_by: [],
  602. },
  603. ],
  604. },
  605. },
  606. });
  607. });
  608. });
  609. const clickSelectOption = async (selectElement: HTMLElement, optionText: string): Promise<void> => {
  610. await userEvent.click(byRole('combobox').get(selectElement));
  611. await selectOptionInTest(selectElement, optionText);
  612. };
  613. const updateTiming = async (selectElement: HTMLElement, value: string, timeUnit: string): Promise<void> => {
  614. const input = byRole('textbox').get(selectElement);
  615. const select = byRole('combobox').get(selectElement);
  616. await userEvent.clear(input);
  617. await userEvent.type(input, value);
  618. await userEvent.click(select);
  619. await selectOptionInTest(selectElement, timeUnit);
  620. };