RuleList.test.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. import { SerializedError } from '@reduxjs/toolkit';
  2. import { render, waitFor } from '@testing-library/react';
  3. import userEvent from '@testing-library/user-event';
  4. import React from 'react';
  5. import { Provider } from 'react-redux';
  6. import { Router } from 'react-router-dom';
  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 { configureStore } from 'app/store/configureStore';
  11. import { AccessControlAction } from 'app/types';
  12. import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto';
  13. import RuleList from './RuleList';
  14. import { discoverFeatures } from './api/buildInfo';
  15. import { fetchRules } from './api/prometheus';
  16. import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler';
  17. import {
  18. disableRBAC,
  19. enableRBAC,
  20. grantUserPermissions,
  21. mockDataSource,
  22. MockDataSourceSrv,
  23. mockPromAlert,
  24. mockPromAlertingRule,
  25. mockPromRecordingRule,
  26. mockPromRuleGroup,
  27. mockPromRuleNamespace,
  28. somePromRules,
  29. someRulerRules,
  30. } from './mocks';
  31. import { getAllDataSources } from './utils/config';
  32. import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
  33. jest.mock('./api/buildInfo');
  34. jest.mock('./api/prometheus');
  35. jest.mock('./api/ruler');
  36. jest.mock('./utils/config');
  37. jest.mock('app/core/core', () => ({
  38. appEvents: {
  39. subscribe: () => {
  40. return { unsubscribe: () => {} };
  41. },
  42. emit: () => {},
  43. },
  44. }));
  45. const mocks = {
  46. getAllDataSourcesMock: jest.mocked(getAllDataSources),
  47. api: {
  48. discoverFeatures: jest.mocked(discoverFeatures),
  49. fetchRules: jest.mocked(fetchRules),
  50. fetchRulerRules: jest.mocked(fetchRulerRules),
  51. deleteGroup: jest.mocked(deleteRulerRulesGroup),
  52. deleteNamespace: jest.mocked(deleteNamespace),
  53. setRulerRuleGroup: jest.mocked(setRulerRuleGroup),
  54. },
  55. };
  56. const renderRuleList = () => {
  57. const store = configureStore();
  58. locationService.push('/');
  59. return render(
  60. <Provider store={store}>
  61. <Router history={locationService.getHistory()}>
  62. <RuleList />
  63. </Router>
  64. </Provider>
  65. );
  66. };
  67. const dataSources = {
  68. prom: mockDataSource({
  69. name: 'Prometheus',
  70. type: DataSourceType.Prometheus,
  71. }),
  72. promdisabled: mockDataSource({
  73. name: 'Prometheus-disabled',
  74. type: DataSourceType.Prometheus,
  75. jsonData: {
  76. manageAlerts: false,
  77. },
  78. }),
  79. loki: mockDataSource({
  80. name: 'Loki',
  81. type: DataSourceType.Loki,
  82. }),
  83. promBroken: mockDataSource({
  84. name: 'Prometheus-broken',
  85. type: DataSourceType.Prometheus,
  86. }),
  87. };
  88. const ui = {
  89. ruleGroup: byTestId('rule-group'),
  90. cloudRulesSourceErrors: byTestId('cloud-rulessource-errors'),
  91. groupCollapseToggle: byTestId('group-collapse-toggle'),
  92. ruleCollapseToggle: byTestId('collapse-toggle'),
  93. rulesTable: byTestId('rules-table'),
  94. ruleRow: byTestId('row'),
  95. expandedContent: byTestId('expanded-content'),
  96. rulesFilterInput: byTestId('search-query-input'),
  97. moreErrorsButton: byRole('button', { name: /more errors/ }),
  98. editCloudGroupIcon: byTestId('edit-group'),
  99. newRuleButton: byRole('link', { name: 'New alert rule' }),
  100. editGroupModal: {
  101. namespaceInput: byLabelText('Namespace'),
  102. ruleGroupInput: byLabelText('Rule group'),
  103. intervalInput: byLabelText('Rule group evaluation interval'),
  104. saveButton: byRole('button', { name: /Save changes/ }),
  105. },
  106. };
  107. describe('RuleList', () => {
  108. beforeEach(() => {
  109. contextSrv.isEditor = true;
  110. });
  111. afterEach(() => {
  112. jest.resetAllMocks();
  113. setDataSourceSrv(undefined as any);
  114. });
  115. it('load & show rule groups from multiple cloud data sources', async () => {
  116. disableRBAC();
  117. mocks.getAllDataSourcesMock.mockReturnValue(Object.values(dataSources));
  118. setDataSourceSrv(new MockDataSourceSrv(dataSources));
  119. mocks.api.discoverFeatures.mockResolvedValue({
  120. application: PromApplication.Prometheus,
  121. features: {
  122. rulerApiEnabled: true,
  123. },
  124. });
  125. mocks.api.fetchRules.mockImplementation((dataSourceName: string) => {
  126. if (dataSourceName === dataSources.prom.name) {
  127. return Promise.resolve([
  128. mockPromRuleNamespace({
  129. name: 'default',
  130. dataSourceName: dataSources.prom.name,
  131. groups: [
  132. mockPromRuleGroup({
  133. name: 'group-2',
  134. }),
  135. mockPromRuleGroup({
  136. name: 'group-1',
  137. }),
  138. ],
  139. }),
  140. ]);
  141. } else if (dataSourceName === dataSources.loki.name) {
  142. return Promise.resolve([
  143. mockPromRuleNamespace({
  144. name: 'default',
  145. dataSourceName: dataSources.loki.name,
  146. groups: [
  147. mockPromRuleGroup({
  148. name: 'group-1',
  149. }),
  150. ],
  151. }),
  152. mockPromRuleNamespace({
  153. name: 'lokins',
  154. dataSourceName: dataSources.loki.name,
  155. groups: [
  156. mockPromRuleGroup({
  157. name: 'group-1',
  158. }),
  159. ],
  160. }),
  161. ]);
  162. } else if (dataSourceName === dataSources.promBroken.name) {
  163. return Promise.reject({ message: 'this datasource is broken' } as SerializedError);
  164. } else if (dataSourceName === GRAFANA_RULES_SOURCE_NAME) {
  165. return Promise.resolve([
  166. mockPromRuleNamespace({
  167. name: 'foofolder',
  168. dataSourceName: GRAFANA_RULES_SOURCE_NAME,
  169. groups: [
  170. mockPromRuleGroup({
  171. name: 'grafana-group',
  172. rules: [
  173. mockPromAlertingRule({
  174. query: '[]',
  175. }),
  176. ],
  177. }),
  178. ],
  179. }),
  180. ]);
  181. }
  182. return Promise.reject(new Error(`unexpected datasourceName: ${dataSourceName}`));
  183. });
  184. await renderRuleList();
  185. await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(4));
  186. const groups = await ui.ruleGroup.findAll();
  187. expect(groups).toHaveLength(5);
  188. expect(groups[0]).toHaveTextContent('foofolder');
  189. expect(groups[1]).toHaveTextContent('default group-1');
  190. expect(groups[2]).toHaveTextContent('default group-1');
  191. expect(groups[3]).toHaveTextContent('default group-2');
  192. expect(groups[4]).toHaveTextContent('lokins group-1');
  193. const errors = await ui.cloudRulesSourceErrors.find();
  194. expect(errors).not.toHaveTextContent(
  195. 'Failed to load rules state from Prometheus-broken: this datasource is broken'
  196. );
  197. await userEvent.click(ui.moreErrorsButton.get());
  198. expect(errors).toHaveTextContent('Failed to load rules state from Prometheus-broken: this datasource is broken');
  199. });
  200. it('expand rule group, rule and alert details', async () => {
  201. mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
  202. setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
  203. mocks.api.discoverFeatures.mockResolvedValue({
  204. application: PromApplication.Lotex,
  205. features: {
  206. rulerApiEnabled: true,
  207. },
  208. });
  209. mocks.api.fetchRules.mockImplementation((dataSourceName: string) => {
  210. if (dataSourceName === GRAFANA_RULES_SOURCE_NAME) {
  211. return Promise.resolve([]);
  212. } else {
  213. return Promise.resolve([
  214. mockPromRuleNamespace({
  215. groups: [
  216. mockPromRuleGroup({
  217. name: 'group-1',
  218. }),
  219. mockPromRuleGroup({
  220. name: 'group-2',
  221. rules: [
  222. mockPromRecordingRule({
  223. name: 'recordingrule',
  224. }),
  225. mockPromAlertingRule({
  226. name: 'alertingrule',
  227. labels: {
  228. severity: 'warning',
  229. foo: 'bar',
  230. },
  231. query: 'topk(5, foo)[5m]',
  232. annotations: {
  233. message: 'great alert',
  234. },
  235. alerts: [
  236. mockPromAlert({
  237. labels: {
  238. foo: 'bar',
  239. severity: 'warning',
  240. },
  241. value: '2e+10',
  242. annotations: {
  243. message: 'first alert message',
  244. },
  245. }),
  246. mockPromAlert({
  247. labels: {
  248. foo: 'baz',
  249. severity: 'error',
  250. },
  251. value: '3e+11',
  252. annotations: {
  253. message: 'first alert message',
  254. },
  255. }),
  256. ],
  257. }),
  258. mockPromAlertingRule({
  259. name: 'p-rule',
  260. alerts: [],
  261. state: PromAlertingRuleState.Pending,
  262. }),
  263. mockPromAlertingRule({
  264. name: 'i-rule',
  265. alerts: [],
  266. state: PromAlertingRuleState.Inactive,
  267. }),
  268. ],
  269. }),
  270. ],
  271. }),
  272. ]);
  273. }
  274. });
  275. await renderRuleList();
  276. const groups = await ui.ruleGroup.findAll();
  277. expect(groups).toHaveLength(2);
  278. expect(groups[0]).toHaveTextContent('1 rule');
  279. expect(groups[1]).toHaveTextContent('4 rules: 1 firing, 1 pending');
  280. // expand second group to see rules table
  281. expect(ui.rulesTable.query()).not.toBeInTheDocument();
  282. await userEvent.click(ui.groupCollapseToggle.get(groups[1]));
  283. const table = await ui.rulesTable.find(groups[1]);
  284. // check that rule rows are rendered properly
  285. let ruleRows = ui.ruleRow.getAll(table);
  286. expect(ruleRows).toHaveLength(4);
  287. expect(ruleRows[0]).toHaveTextContent('Recording rule');
  288. expect(ruleRows[0]).toHaveTextContent('recordingrule');
  289. expect(ruleRows[1]).toHaveTextContent('Firing');
  290. expect(ruleRows[1]).toHaveTextContent('alertingrule');
  291. expect(ruleRows[2]).toHaveTextContent('Pending');
  292. expect(ruleRows[2]).toHaveTextContent('p-rule');
  293. expect(ruleRows[3]).toHaveTextContent('Normal');
  294. expect(ruleRows[3]).toHaveTextContent('i-rule');
  295. expect(byText('Labels').query()).not.toBeInTheDocument();
  296. // expand alert details
  297. await userEvent.click(ui.ruleCollapseToggle.get(ruleRows[1]));
  298. const ruleDetails = ui.expandedContent.get(ruleRows[1]);
  299. expect(ruleDetails).toHaveTextContent('Labelsseverity=warningfoo=bar');
  300. expect(ruleDetails).toHaveTextContent('Expressiontopk ( 5 , foo ) [ 5m ]');
  301. expect(ruleDetails).toHaveTextContent('messagegreat alert');
  302. expect(ruleDetails).toHaveTextContent('Matching instances');
  303. // finally, check instances table
  304. const instancesTable = byTestId('dynamic-table').get(ruleDetails);
  305. expect(instancesTable).toBeInTheDocument();
  306. const instanceRows = byTestId('row').getAll(instancesTable);
  307. expect(instanceRows).toHaveLength(2);
  308. expect(instanceRows![0]).toHaveTextContent('Firingfoo=barseverity=warning2021-03-18 13:47:05');
  309. expect(instanceRows![1]).toHaveTextContent('Firingfoo=bazseverity=error2021-03-18 13:47:05');
  310. // expand details of an instance
  311. await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0]));
  312. const alertDetails = byTestId('expanded-content').get(instanceRows[0]);
  313. expect(alertDetails).toHaveTextContent('Value2e+10');
  314. expect(alertDetails).toHaveTextContent('messagefirst alert message');
  315. // collapse everything again
  316. await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0]));
  317. expect(byTestId('expanded-content').query(instanceRows[0])).not.toBeInTheDocument();
  318. await userEvent.click(ui.ruleCollapseToggle.getAll(ruleRows[1])[0]);
  319. await userEvent.click(ui.groupCollapseToggle.get(groups[1]));
  320. expect(ui.rulesTable.query()).not.toBeInTheDocument();
  321. });
  322. it('filters rules and alerts by labels', async () => {
  323. mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
  324. setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
  325. mocks.api.discoverFeatures.mockResolvedValue({
  326. application: PromApplication.Lotex,
  327. features: {
  328. rulerApiEnabled: true,
  329. },
  330. });
  331. mocks.api.fetchRulerRules.mockResolvedValue({});
  332. mocks.api.fetchRules.mockImplementation((dataSourceName: string) => {
  333. if (dataSourceName === GRAFANA_RULES_SOURCE_NAME) {
  334. return Promise.resolve([]);
  335. } else {
  336. return Promise.resolve([
  337. mockPromRuleNamespace({
  338. groups: [
  339. mockPromRuleGroup({
  340. name: 'group-1',
  341. rules: [
  342. mockPromAlertingRule({
  343. name: 'alertingrule',
  344. labels: {
  345. severity: 'warning',
  346. foo: 'bar',
  347. },
  348. query: 'topk(5, foo)[5m]',
  349. annotations: {
  350. message: 'great alert',
  351. },
  352. alerts: [
  353. mockPromAlert({
  354. labels: {
  355. foo: 'bar',
  356. severity: 'warning',
  357. },
  358. value: '2e+10',
  359. annotations: {
  360. message: 'first alert message',
  361. },
  362. }),
  363. mockPromAlert({
  364. labels: {
  365. foo: 'baz',
  366. severity: 'error',
  367. },
  368. value: '3e+11',
  369. annotations: {
  370. message: 'first alert message',
  371. },
  372. }),
  373. ],
  374. }),
  375. ],
  376. }),
  377. mockPromRuleGroup({
  378. name: 'group-2',
  379. rules: [
  380. mockPromAlertingRule({
  381. name: 'alertingrule2',
  382. labels: {
  383. severity: 'error',
  384. foo: 'buzz',
  385. },
  386. query: 'topk(5, foo)[5m]',
  387. annotations: {
  388. message: 'great alert',
  389. },
  390. alerts: [
  391. mockPromAlert({
  392. labels: {
  393. foo: 'buzz',
  394. severity: 'error',
  395. region: 'EU',
  396. },
  397. value: '2e+10',
  398. annotations: {
  399. message: 'alert message',
  400. },
  401. }),
  402. mockPromAlert({
  403. labels: {
  404. foo: 'buzz',
  405. severity: 'error',
  406. region: 'US',
  407. },
  408. value: '3e+11',
  409. annotations: {
  410. message: 'alert message',
  411. },
  412. }),
  413. ],
  414. }),
  415. ],
  416. }),
  417. ],
  418. }),
  419. ]);
  420. }
  421. });
  422. await renderRuleList();
  423. const groups = await ui.ruleGroup.findAll();
  424. expect(groups).toHaveLength(2);
  425. const filterInput = ui.rulesFilterInput.get();
  426. await userEvent.type(filterInput, '{{foo="bar"}');
  427. // Input is debounced so wait for it to be visible
  428. await waitFor(() => expect(filterInput).toHaveValue('{foo="bar"}'));
  429. // Group doesn't contain matching labels
  430. await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(1));
  431. await userEvent.click(ui.groupCollapseToggle.get(groups[0]));
  432. const ruleRows = ui.ruleRow.getAll(groups[0]);
  433. expect(ruleRows).toHaveLength(1);
  434. await userEvent.click(ui.ruleCollapseToggle.get(ruleRows[0]));
  435. const ruleDetails = ui.expandedContent.get(ruleRows[0]);
  436. expect(ruleDetails).toHaveTextContent('Labelsseverity=warningfoo=bar');
  437. // Check for different label matchers
  438. await userEvent.clear(filterInput);
  439. await userEvent.type(filterInput, '{{foo!="bar",foo!="baz"}');
  440. // Group doesn't contain matching labels
  441. await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(1));
  442. await waitFor(() => expect(ui.ruleGroup.get()).toHaveTextContent('group-2'));
  443. await userEvent.clear(filterInput);
  444. await userEvent.type(filterInput, '{{foo=~"b.+"}');
  445. await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(2));
  446. await userEvent.clear(filterInput);
  447. await userEvent.type(filterInput, '{{region="US"}');
  448. await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(1));
  449. await waitFor(() => expect(ui.ruleGroup.get()).toHaveTextContent('group-2'));
  450. });
  451. describe('edit lotex groups, namespaces', () => {
  452. const testDatasources = {
  453. prom: dataSources.prom,
  454. };
  455. function testCase(name: string, fn: () => Promise<void>) {
  456. it(name, async () => {
  457. mocks.getAllDataSourcesMock.mockReturnValue(Object.values(testDatasources));
  458. setDataSourceSrv(new MockDataSourceSrv(testDatasources));
  459. mocks.api.discoverFeatures.mockResolvedValue({
  460. application: PromApplication.Lotex,
  461. features: {
  462. rulerApiEnabled: true,
  463. },
  464. });
  465. mocks.api.fetchRules.mockImplementation((sourceName) =>
  466. Promise.resolve(sourceName === testDatasources.prom.name ? somePromRules() : [])
  467. );
  468. mocks.api.fetchRulerRules.mockImplementation(({ dataSourceName }) =>
  469. Promise.resolve(dataSourceName === testDatasources.prom.name ? someRulerRules : {})
  470. );
  471. mocks.api.setRulerRuleGroup.mockResolvedValue();
  472. mocks.api.deleteNamespace.mockResolvedValue();
  473. await renderRuleList();
  474. expect(await ui.rulesFilterInput.find()).toHaveValue('');
  475. const groups = await ui.ruleGroup.findAll();
  476. expect(groups).toHaveLength(3);
  477. // open edit dialog
  478. await userEvent.click(ui.editCloudGroupIcon.get(groups[0]));
  479. expect(ui.editGroupModal.namespaceInput.get()).toHaveValue('namespace1');
  480. expect(ui.editGroupModal.ruleGroupInput.get()).toHaveValue('group1');
  481. await fn();
  482. });
  483. }
  484. testCase('rename both lotex namespace and group', async () => {
  485. // make changes to form
  486. await userEvent.clear(ui.editGroupModal.namespaceInput.get());
  487. await userEvent.type(ui.editGroupModal.namespaceInput.get(), 'super namespace');
  488. await userEvent.clear(ui.editGroupModal.ruleGroupInput.get());
  489. await userEvent.type(ui.editGroupModal.ruleGroupInput.get(), 'super group');
  490. await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m');
  491. // submit, check that appropriate calls were made
  492. await userEvent.click(ui.editGroupModal.saveButton.get());
  493. await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument());
  494. expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledTimes(2);
  495. expect(mocks.api.deleteNamespace).toHaveBeenCalledTimes(1);
  496. expect(mocks.api.deleteGroup).not.toHaveBeenCalled();
  497. expect(mocks.api.fetchRulerRules).toHaveBeenCalledTimes(4);
  498. expect(mocks.api.setRulerRuleGroup).toHaveBeenNthCalledWith(
  499. 1,
  500. { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' },
  501. 'super namespace',
  502. {
  503. ...someRulerRules['namespace1'][0],
  504. name: 'super group',
  505. interval: '5m',
  506. }
  507. );
  508. expect(mocks.api.setRulerRuleGroup).toHaveBeenNthCalledWith(
  509. 2,
  510. { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' },
  511. 'super namespace',
  512. someRulerRules['namespace1'][1]
  513. );
  514. expect(mocks.api.deleteNamespace).toHaveBeenLastCalledWith(
  515. { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' },
  516. 'namespace1'
  517. );
  518. });
  519. testCase('rename just the lotex group', async () => {
  520. // make changes to form
  521. await userEvent.clear(ui.editGroupModal.ruleGroupInput.get());
  522. await userEvent.type(ui.editGroupModal.ruleGroupInput.get(), 'super group');
  523. await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m');
  524. // submit, check that appropriate calls were made
  525. await userEvent.click(ui.editGroupModal.saveButton.get());
  526. await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument());
  527. expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledTimes(1);
  528. expect(mocks.api.deleteGroup).toHaveBeenCalledTimes(1);
  529. expect(mocks.api.deleteNamespace).not.toHaveBeenCalled();
  530. expect(mocks.api.fetchRulerRules).toHaveBeenCalledTimes(4);
  531. expect(mocks.api.setRulerRuleGroup).toHaveBeenNthCalledWith(
  532. 1,
  533. { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' },
  534. 'namespace1',
  535. {
  536. ...someRulerRules['namespace1'][0],
  537. name: 'super group',
  538. interval: '5m',
  539. }
  540. );
  541. expect(mocks.api.deleteGroup).toHaveBeenLastCalledWith(
  542. { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' },
  543. 'namespace1',
  544. 'group1'
  545. );
  546. });
  547. testCase('edit lotex group eval interval, no renaming', async () => {
  548. // make changes to form
  549. await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m');
  550. // submit, check that appropriate calls were made
  551. await userEvent.click(ui.editGroupModal.saveButton.get());
  552. await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument());
  553. expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledTimes(1);
  554. expect(mocks.api.deleteGroup).not.toHaveBeenCalled();
  555. expect(mocks.api.deleteNamespace).not.toHaveBeenCalled();
  556. expect(mocks.api.fetchRulerRules).toHaveBeenCalledTimes(4);
  557. expect(mocks.api.setRulerRuleGroup).toHaveBeenNthCalledWith(
  558. 1,
  559. { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' },
  560. 'namespace1',
  561. {
  562. ...someRulerRules['namespace1'][0],
  563. interval: '5m',
  564. }
  565. );
  566. });
  567. });
  568. describe('RBAC Enabled', () => {
  569. describe('Grafana Managed Alerts', () => {
  570. it('New alert button should be visible when the user has alert rule create and folder read permissions and no rules exists', async () => {
  571. enableRBAC();
  572. grantUserPermissions([
  573. AccessControlAction.FoldersRead,
  574. AccessControlAction.AlertingRuleCreate,
  575. AccessControlAction.AlertingRuleRead,
  576. ]);
  577. mocks.getAllDataSourcesMock.mockReturnValue([]);
  578. setDataSourceSrv(new MockDataSourceSrv({}));
  579. mocks.api.fetchRules.mockResolvedValue([]);
  580. mocks.api.fetchRulerRules.mockResolvedValue({});
  581. renderRuleList();
  582. await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1));
  583. expect(ui.newRuleButton.get()).toBeInTheDocument();
  584. });
  585. it('New alert button should be visible when the user has alert rule create and folder read permissions and rules already exists', async () => {
  586. enableRBAC();
  587. grantUserPermissions([
  588. AccessControlAction.FoldersRead,
  589. AccessControlAction.AlertingRuleCreate,
  590. AccessControlAction.AlertingRuleRead,
  591. ]);
  592. mocks.getAllDataSourcesMock.mockReturnValue([]);
  593. setDataSourceSrv(new MockDataSourceSrv({}));
  594. mocks.api.fetchRules.mockResolvedValue(somePromRules('grafana'));
  595. mocks.api.fetchRulerRules.mockResolvedValue(someRulerRules);
  596. renderRuleList();
  597. await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1));
  598. expect(ui.newRuleButton.get()).toBeInTheDocument();
  599. });
  600. });
  601. describe('Cloud Alerts', () => {
  602. it('New alert button should be visible when the user has the alert rule external write and datasource read permissions and no rules exists', async () => {
  603. enableRBAC();
  604. grantUserPermissions([
  605. // AccessControlAction.AlertingRuleRead,
  606. AccessControlAction.DataSourcesRead,
  607. AccessControlAction.AlertingRuleExternalRead,
  608. AccessControlAction.AlertingRuleExternalWrite,
  609. ]);
  610. mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
  611. setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
  612. mocks.api.discoverFeatures.mockResolvedValue({
  613. application: PromApplication.Lotex,
  614. features: {
  615. rulerApiEnabled: true,
  616. },
  617. });
  618. mocks.api.fetchRules.mockResolvedValue([]);
  619. mocks.api.fetchRulerRules.mockResolvedValue({});
  620. renderRuleList();
  621. await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1));
  622. expect(ui.newRuleButton.get()).toBeInTheDocument();
  623. });
  624. it('New alert button should be visible when the user has the alert rule external write and data source read permissions and rules already exists', async () => {
  625. enableRBAC();
  626. grantUserPermissions([
  627. AccessControlAction.DataSourcesRead,
  628. AccessControlAction.AlertingRuleExternalRead,
  629. AccessControlAction.AlertingRuleExternalWrite,
  630. ]);
  631. mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
  632. setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
  633. mocks.api.discoverFeatures.mockResolvedValue({
  634. application: PromApplication.Lotex,
  635. features: {
  636. rulerApiEnabled: true,
  637. },
  638. });
  639. mocks.api.fetchRules.mockResolvedValue(somePromRules('Cortex'));
  640. mocks.api.fetchRulerRules.mockResolvedValue(someRulerRules);
  641. renderRuleList();
  642. await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1));
  643. expect(ui.newRuleButton.get()).toBeInTheDocument();
  644. });
  645. });
  646. });
  647. });