RuleEditor.test.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. import { Matcher, render, waitFor, screen } from '@testing-library/react';
  2. import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event';
  3. import React from 'react';
  4. import { Provider } from 'react-redux';
  5. import { Route, Router } from 'react-router-dom';
  6. import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
  7. import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector';
  8. import { DataSourceInstanceSettings } from '@grafana/data';
  9. import { BackendSrv, locationService, setBackendSrv, setDataSourceSrv } from '@grafana/runtime';
  10. import { contextSrv } from 'app/core/services/context_srv';
  11. import { DashboardSearchHit } from 'app/features/search/types';
  12. import { configureStore } from 'app/store/configureStore';
  13. import { GrafanaAlertStateDecision, PromApplication } from 'app/types/unified-alerting-dto';
  14. import { searchFolders } from '../../../../app/features/manage-dashboards/state/actions';
  15. import RuleEditor from './RuleEditor';
  16. import { discoverFeatures } from './api/buildInfo';
  17. import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler';
  18. import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor';
  19. import { disableRBAC, mockDataSource, MockDataSourceSrv } from './mocks';
  20. import { getAllDataSources } from './utils/config';
  21. import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
  22. import { getDefaultQueries } from './utils/rule-form';
  23. jest.mock('./components/rule-editor/ExpressionEditor', () => ({
  24. // eslint-disable-next-line react/display-name
  25. ExpressionEditor: ({ value, onChange }: ExpressionEditorProps) => (
  26. <input value={value} data-testid="expr" onChange={(e) => onChange(e.target.value)} />
  27. ),
  28. }));
  29. jest.mock('./api/buildInfo');
  30. jest.mock('./api/ruler');
  31. jest.mock('./utils/config');
  32. jest.mock('../../../../app/features/manage-dashboards/state/actions');
  33. // there's no angular scope in test and things go terribly wrong when trying to render the query editor row.
  34. // lets just skip it
  35. jest.mock('app/features/query/components/QueryEditorRow', () => ({
  36. // eslint-disable-next-line react/display-name
  37. QueryEditorRow: () => <p>hi</p>,
  38. }));
  39. const mocks = {
  40. getAllDataSources: jest.mocked(getAllDataSources),
  41. searchFolders: jest.mocked(searchFolders),
  42. api: {
  43. discoverFeatures: jest.mocked(discoverFeatures),
  44. fetchRulerRulesGroup: jest.mocked(fetchRulerRulesGroup),
  45. setRulerRuleGroup: jest.mocked(setRulerRuleGroup),
  46. fetchRulerRulesNamespace: jest.mocked(fetchRulerRulesNamespace),
  47. fetchRulerRules: jest.mocked(fetchRulerRules),
  48. },
  49. };
  50. function renderRuleEditor(identifier?: string) {
  51. const store = configureStore();
  52. locationService.push(identifier ? `/alerting/${identifier}/edit` : `/alerting/new`);
  53. return render(
  54. <Provider store={store}>
  55. <Router history={locationService.getHistory()}>
  56. <Route path={['/alerting/new', '/alerting/:id/edit']} component={RuleEditor} />
  57. </Router>
  58. </Provider>
  59. );
  60. }
  61. const ui = {
  62. inputs: {
  63. name: byLabelText('Rule name'),
  64. alertType: byTestId('alert-type-picker'),
  65. dataSource: byTestId('datasource-picker'),
  66. folder: byTestId('folder-picker'),
  67. namespace: byTestId('namespace-picker'),
  68. group: byTestId('group-picker'),
  69. annotationKey: (idx: number) => byTestId(`annotation-key-${idx}`),
  70. annotationValue: (idx: number) => byTestId(`annotation-value-${idx}`),
  71. labelKey: (idx: number) => byTestId(`label-key-${idx}`),
  72. labelValue: (idx: number) => byTestId(`label-value-${idx}`),
  73. expr: byTestId('expr'),
  74. },
  75. buttons: {
  76. save: byRole('button', { name: 'Save' }),
  77. addAnnotation: byRole('button', { name: /Add info/ }),
  78. addLabel: byRole('button', { name: /Add label/ }),
  79. // alert type buttons
  80. grafanaManagedAlert: byRole('button', { name: /Grafana managed/ }),
  81. lotexAlert: byRole('button', { name: /Mimir or Loki alert/ }),
  82. lotexRecordingRule: byRole('button', { name: /Mimir or Loki recording rule/ }),
  83. },
  84. };
  85. describe('RuleEditor', () => {
  86. beforeEach(() => {
  87. jest.resetAllMocks();
  88. contextSrv.isEditor = true;
  89. contextSrv.hasEditPermissionInFolders = true;
  90. });
  91. disableRBAC();
  92. it('can create a new cloud alert', async () => {
  93. const dataSources = {
  94. default: mockDataSource(
  95. {
  96. type: 'prometheus',
  97. name: 'Prom',
  98. isDefault: true,
  99. },
  100. { alerting: true }
  101. ),
  102. };
  103. setDataSourceSrv(new MockDataSourceSrv(dataSources));
  104. mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
  105. mocks.api.setRulerRuleGroup.mockResolvedValue();
  106. mocks.api.fetchRulerRulesNamespace.mockResolvedValue([]);
  107. mocks.api.fetchRulerRulesGroup.mockResolvedValue({
  108. name: 'group2',
  109. rules: [],
  110. });
  111. mocks.api.fetchRulerRules.mockResolvedValue({
  112. namespace1: [
  113. {
  114. name: 'group1',
  115. rules: [],
  116. },
  117. ],
  118. namespace2: [
  119. {
  120. name: 'group2',
  121. rules: [],
  122. },
  123. ],
  124. });
  125. mocks.searchFolders.mockResolvedValue([]);
  126. mocks.api.discoverFeatures.mockResolvedValue({
  127. application: PromApplication.Lotex,
  128. features: {
  129. rulerApiEnabled: true,
  130. },
  131. });
  132. await renderRuleEditor();
  133. await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
  134. await waitFor(() => expect(mocks.api.discoverFeatures).toHaveBeenCalled());
  135. await userEvent.click(await ui.buttons.lotexAlert.find());
  136. const dataSourceSelect = ui.inputs.dataSource.get();
  137. await userEvent.click(byRole('combobox').get(dataSourceSelect));
  138. await clickSelectOption(dataSourceSelect, 'Prom (default)');
  139. await waitFor(() => expect(mocks.api.fetchRulerRules).toHaveBeenCalled());
  140. await userEvent.type(await ui.inputs.expr.find(), 'up == 1');
  141. await userEvent.type(ui.inputs.name.get(), 'my great new rule');
  142. await clickSelectOption(ui.inputs.namespace.get(), 'namespace2');
  143. await clickSelectOption(ui.inputs.group.get(), 'group2');
  144. await userEvent.type(ui.inputs.annotationValue(0).get(), 'some summary');
  145. await userEvent.type(ui.inputs.annotationValue(1).get(), 'some description');
  146. // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed
  147. await userEvent.click(ui.buttons.addLabel.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never });
  148. await userEvent.type(ui.inputs.labelKey(0).get(), 'severity');
  149. await userEvent.type(ui.inputs.labelValue(0).get(), 'warn');
  150. await userEvent.type(ui.inputs.labelKey(1).get(), 'team');
  151. await userEvent.type(ui.inputs.labelValue(1).get(), 'the a-team');
  152. // save and check what was sent to backend
  153. await userEvent.click(ui.buttons.save.get());
  154. await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled());
  155. expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith(
  156. { dataSourceName: 'Prom', apiVersion: 'legacy' },
  157. 'namespace2',
  158. {
  159. name: 'group2',
  160. rules: [
  161. {
  162. alert: 'my great new rule',
  163. annotations: { description: 'some description', summary: 'some summary' },
  164. labels: { severity: 'warn', team: 'the a-team' },
  165. expr: 'up == 1',
  166. for: '1m',
  167. },
  168. ],
  169. }
  170. );
  171. });
  172. it('can create new grafana managed alert', async () => {
  173. const dataSources = {
  174. default: mockDataSource(
  175. {
  176. type: 'prometheus',
  177. name: 'Prom',
  178. isDefault: true,
  179. },
  180. { alerting: true }
  181. ),
  182. };
  183. setDataSourceSrv(new MockDataSourceSrv(dataSources));
  184. mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
  185. mocks.api.setRulerRuleGroup.mockResolvedValue();
  186. mocks.api.fetchRulerRulesNamespace.mockResolvedValue([]);
  187. mocks.api.fetchRulerRulesGroup.mockResolvedValue({
  188. name: 'group2',
  189. rules: [],
  190. });
  191. mocks.api.fetchRulerRules.mockResolvedValue({
  192. namespace1: [
  193. {
  194. name: 'group1',
  195. rules: [],
  196. },
  197. ],
  198. namespace2: [
  199. {
  200. name: 'group2',
  201. rules: [],
  202. },
  203. ],
  204. });
  205. mocks.searchFolders.mockResolvedValue([
  206. {
  207. title: 'Folder A',
  208. id: 1,
  209. },
  210. {
  211. title: 'Folder B',
  212. id: 2,
  213. },
  214. ] as DashboardSearchHit[]);
  215. mocks.api.discoverFeatures.mockResolvedValue({
  216. application: PromApplication.Prometheus,
  217. features: {
  218. rulerApiEnabled: false,
  219. },
  220. });
  221. // fill out the form
  222. await renderRuleEditor();
  223. await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
  224. await waitFor(() => expect(mocks.api.discoverFeatures).toHaveBeenCalled());
  225. await userEvent.type(await ui.inputs.name.find(), 'my great new rule');
  226. const folderInput = await ui.inputs.folder.find();
  227. await clickSelectOption(folderInput, 'Folder A');
  228. const groupInput = screen.getByRole('textbox', { name: /^Group/ });
  229. await userEvent.type(groupInput, 'my group');
  230. await userEvent.type(ui.inputs.annotationValue(0).get(), 'some summary');
  231. await userEvent.type(ui.inputs.annotationValue(1).get(), 'some description');
  232. // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed
  233. await userEvent.click(ui.buttons.addLabel.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never });
  234. await userEvent.type(ui.inputs.labelKey(0).get(), 'severity');
  235. await userEvent.type(ui.inputs.labelValue(0).get(), 'warn');
  236. await userEvent.type(ui.inputs.labelKey(1).get(), 'team');
  237. await userEvent.type(ui.inputs.labelValue(1).get(), 'the a-team');
  238. // save and check what was sent to backend
  239. await userEvent.click(ui.buttons.save.get());
  240. await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled());
  241. expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith(
  242. { dataSourceName: GRAFANA_RULES_SOURCE_NAME, apiVersion: 'legacy' },
  243. 'Folder A',
  244. {
  245. interval: '1m',
  246. name: 'my group',
  247. rules: [
  248. {
  249. annotations: { description: 'some description', summary: 'some summary' },
  250. labels: { severity: 'warn', team: 'the a-team' },
  251. for: '5m',
  252. grafana_alert: {
  253. condition: 'B',
  254. data: getDefaultQueries(),
  255. exec_err_state: 'Alerting',
  256. no_data_state: 'NoData',
  257. title: 'my great new rule',
  258. },
  259. },
  260. ],
  261. }
  262. );
  263. });
  264. it('can create a new cloud recording rule', async () => {
  265. const dataSources = {
  266. default: mockDataSource(
  267. {
  268. type: 'prometheus',
  269. name: 'Prom',
  270. isDefault: true,
  271. },
  272. { alerting: true }
  273. ),
  274. };
  275. setDataSourceSrv(new MockDataSourceSrv(dataSources));
  276. mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
  277. mocks.api.setRulerRuleGroup.mockResolvedValue();
  278. mocks.api.fetchRulerRulesNamespace.mockResolvedValue([]);
  279. mocks.api.fetchRulerRulesGroup.mockResolvedValue({
  280. name: 'group2',
  281. rules: [],
  282. });
  283. mocks.api.fetchRulerRules.mockResolvedValue({
  284. namespace1: [
  285. {
  286. name: 'group1',
  287. rules: [],
  288. },
  289. ],
  290. namespace2: [
  291. {
  292. name: 'group2',
  293. rules: [],
  294. },
  295. ],
  296. });
  297. mocks.searchFolders.mockResolvedValue([]);
  298. mocks.api.discoverFeatures.mockResolvedValue({
  299. application: PromApplication.Lotex,
  300. features: {
  301. rulerApiEnabled: true,
  302. },
  303. });
  304. await renderRuleEditor();
  305. await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
  306. await waitFor(() => expect(mocks.api.discoverFeatures).toHaveBeenCalled());
  307. await userEvent.type(await ui.inputs.name.find(), 'my great new recording rule');
  308. await userEvent.click(await ui.buttons.lotexRecordingRule.get());
  309. const dataSourceSelect = ui.inputs.dataSource.get();
  310. await userEvent.click(byRole('combobox').get(dataSourceSelect));
  311. await clickSelectOption(dataSourceSelect, 'Prom (default)');
  312. await waitFor(() => expect(mocks.api.fetchRulerRules).toHaveBeenCalled());
  313. await clickSelectOption(ui.inputs.namespace.get(), 'namespace2');
  314. await clickSelectOption(ui.inputs.group.get(), 'group2');
  315. await userEvent.type(await ui.inputs.expr.find(), 'up == 1');
  316. // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed
  317. await userEvent.click(ui.buttons.addLabel.get(), { pointerEventsCheck: PointerEventsCheckLevel.Never });
  318. await userEvent.type(ui.inputs.labelKey(1).get(), 'team');
  319. await userEvent.type(ui.inputs.labelValue(1).get(), 'the a-team');
  320. // try to save, find out that recording rule name is invalid
  321. await userEvent.click(ui.buttons.save.get());
  322. await waitFor(() =>
  323. expect(
  324. byText(
  325. 'Recording rule name must be valid metric name. It may only contain letters, numbers, and colons. It may not contain whitespace.'
  326. ).get()
  327. ).toBeInTheDocument()
  328. );
  329. expect(mocks.api.setRulerRuleGroup).not.toBeCalled();
  330. // fix name and re-submit
  331. await userEvent.clear(await ui.inputs.name.find());
  332. await userEvent.type(await ui.inputs.name.find(), 'my:great:new:recording:rule');
  333. // save and check what was sent to backend
  334. await userEvent.click(ui.buttons.save.get());
  335. await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled());
  336. expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith(
  337. { dataSourceName: 'Prom', apiVersion: 'legacy' },
  338. 'namespace2',
  339. {
  340. name: 'group2',
  341. rules: [
  342. {
  343. record: 'my:great:new:recording:rule',
  344. labels: { team: 'the a-team' },
  345. expr: 'up == 1',
  346. },
  347. ],
  348. }
  349. );
  350. });
  351. it('can edit grafana managed rule', async () => {
  352. const uid = 'FOOBAR123';
  353. const folder = {
  354. title: 'Folder A',
  355. uid: 'abcd',
  356. id: 1,
  357. };
  358. const getFolderByUid = jest.fn().mockResolvedValue({
  359. ...folder,
  360. canSave: true,
  361. });
  362. const dataSources = {
  363. default: mockDataSource({
  364. type: 'prometheus',
  365. name: 'Prom',
  366. isDefault: true,
  367. }),
  368. };
  369. const backendSrv = {
  370. getFolderByUid,
  371. } as any as BackendSrv;
  372. setBackendSrv(backendSrv);
  373. setDataSourceSrv(new MockDataSourceSrv(dataSources));
  374. mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
  375. mocks.api.setRulerRuleGroup.mockResolvedValue();
  376. mocks.api.fetchRulerRulesNamespace.mockResolvedValue([]);
  377. mocks.api.fetchRulerRules.mockResolvedValue({
  378. [folder.title]: [
  379. {
  380. interval: '1m',
  381. name: 'my great new rule',
  382. rules: [
  383. {
  384. annotations: { description: 'some description', summary: 'some summary' },
  385. labels: { severity: 'warn', team: 'the a-team' },
  386. for: '5m',
  387. grafana_alert: {
  388. uid,
  389. namespace_uid: 'abcd',
  390. namespace_id: 1,
  391. condition: 'B',
  392. data: getDefaultQueries(),
  393. exec_err_state: GrafanaAlertStateDecision.Alerting,
  394. no_data_state: GrafanaAlertStateDecision.NoData,
  395. title: 'my great new rule',
  396. },
  397. },
  398. ],
  399. },
  400. ],
  401. });
  402. mocks.searchFolders.mockResolvedValue([folder] as DashboardSearchHit[]);
  403. await renderRuleEditor(uid);
  404. await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
  405. await waitFor(() => expect(mocks.api.discoverFeatures).toHaveBeenCalled());
  406. await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
  407. // check that it's filled in
  408. const nameInput = await ui.inputs.name.find();
  409. expect(nameInput).toHaveValue('my great new rule');
  410. expect(ui.inputs.folder.get()).toHaveTextContent(new RegExp(folder.title));
  411. expect(ui.inputs.annotationValue(0).get()).toHaveValue('some description');
  412. expect(ui.inputs.annotationValue(1).get()).toHaveValue('some summary');
  413. // add an annotation
  414. await clickSelectOption(ui.inputs.annotationKey(2).get(), /Add new/);
  415. await userEvent.type(byRole('textbox').get(ui.inputs.annotationKey(2).get()), 'custom');
  416. await userEvent.type(ui.inputs.annotationValue(2).get(), 'value');
  417. //add a label
  418. await userEvent.type(ui.inputs.labelKey(2).get(), 'custom');
  419. await userEvent.type(ui.inputs.labelValue(2).get(), 'value');
  420. // save and check what was sent to backend
  421. await userEvent.click(ui.buttons.save.get());
  422. await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled());
  423. expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith(
  424. { dataSourceName: GRAFANA_RULES_SOURCE_NAME, apiVersion: 'legacy' },
  425. 'Folder A',
  426. {
  427. interval: '1m',
  428. name: 'my great new rule',
  429. rules: [
  430. {
  431. annotations: { description: 'some description', summary: 'some summary', custom: 'value' },
  432. labels: { severity: 'warn', team: 'the a-team', custom: 'value' },
  433. for: '5m',
  434. grafana_alert: {
  435. uid,
  436. condition: 'B',
  437. data: getDefaultQueries(),
  438. exec_err_state: 'Alerting',
  439. no_data_state: 'NoData',
  440. title: 'my great new rule',
  441. },
  442. },
  443. ],
  444. }
  445. );
  446. });
  447. it('for cloud alerts, should only allow to select editable rules sources', async () => {
  448. const dataSources: Record<string, DataSourceInstanceSettings<any>> = {
  449. // can edit rules
  450. loki: mockDataSource(
  451. {
  452. type: DataSourceType.Loki,
  453. name: 'loki with ruler',
  454. },
  455. { alerting: true }
  456. ),
  457. loki_disabled: mockDataSource(
  458. {
  459. type: DataSourceType.Loki,
  460. name: 'loki disabled for alerting',
  461. jsonData: {
  462. manageAlerts: false,
  463. },
  464. },
  465. { alerting: true }
  466. ),
  467. // can edit rules
  468. prom: mockDataSource(
  469. {
  470. type: DataSourceType.Prometheus,
  471. name: 'cortex with ruler',
  472. },
  473. { alerting: true }
  474. ),
  475. // cannot edit rules
  476. loki_local_rule_store: mockDataSource(
  477. {
  478. type: DataSourceType.Loki,
  479. name: 'loki with local rule store',
  480. },
  481. { alerting: true }
  482. ),
  483. // cannot edit rules
  484. prom_no_ruler_api: mockDataSource(
  485. {
  486. type: DataSourceType.Loki,
  487. name: 'cortex without ruler api',
  488. },
  489. { alerting: true }
  490. ),
  491. // not a supported datasource type
  492. splunk: mockDataSource(
  493. {
  494. type: 'splunk',
  495. name: 'splunk',
  496. },
  497. { alerting: true }
  498. ),
  499. };
  500. mocks.api.discoverFeatures.mockImplementation(async (dataSourceName) => {
  501. if (dataSourceName === 'loki with ruler' || dataSourceName === 'cortex with ruler') {
  502. return {
  503. application: PromApplication.Lotex,
  504. features: {
  505. rulerApiEnabled: true,
  506. alertManagerConfigApi: false,
  507. federatedRules: false,
  508. querySharding: false,
  509. },
  510. };
  511. }
  512. if (dataSourceName === 'loki with local rule store') {
  513. return {
  514. application: PromApplication.Lotex,
  515. features: {
  516. rulerApiEnabled: false,
  517. alertManagerConfigApi: false,
  518. federatedRules: false,
  519. querySharding: false,
  520. },
  521. };
  522. }
  523. if (dataSourceName === 'cortex without ruler api') {
  524. return {
  525. application: PromApplication.Lotex,
  526. features: {
  527. rulerApiEnabled: false,
  528. alertManagerConfigApi: false,
  529. federatedRules: false,
  530. querySharding: false,
  531. },
  532. };
  533. }
  534. throw new Error(`${dataSourceName} not handled`);
  535. });
  536. mocks.api.fetchRulerRulesGroup.mockImplementation(async ({ dataSourceName }) => {
  537. if (dataSourceName === 'loki with ruler' || dataSourceName === 'cortex with ruler') {
  538. return null;
  539. }
  540. if (dataSourceName === 'loki with local rule store') {
  541. throw {
  542. status: 400,
  543. data: {
  544. message: 'GetRuleGroup unsupported in rule local store',
  545. },
  546. };
  547. }
  548. if (dataSourceName === 'cortex without ruler api') {
  549. throw new Error('404 from rules config endpoint. Perhaps ruler API is not enabled?');
  550. }
  551. return null;
  552. });
  553. setDataSourceSrv(new MockDataSourceSrv(dataSources));
  554. mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
  555. mocks.searchFolders.mockResolvedValue([]);
  556. // render rule editor, select mimir/loki managed alerts
  557. await renderRuleEditor();
  558. await waitFor(() => expect(mocks.api.discoverFeatures).toHaveBeenCalled());
  559. await waitFor(() => expect(mocks.searchFolders).toHaveBeenCalled());
  560. await ui.inputs.name.find();
  561. await userEvent.click(await ui.buttons.lotexAlert.get());
  562. // check that only rules sources that have ruler available are there
  563. const dataSourceSelect = ui.inputs.dataSource.get();
  564. await userEvent.click(byRole('combobox').get(dataSourceSelect));
  565. expect(await byText('loki with ruler').query()).toBeInTheDocument();
  566. expect(byText('cortex with ruler').query()).toBeInTheDocument();
  567. expect(byText('loki with local rule store').query()).not.toBeInTheDocument();
  568. expect(byText('prom without ruler api').query()).not.toBeInTheDocument();
  569. expect(byText('splunk').query()).not.toBeInTheDocument();
  570. expect(byText('loki disabled for alerting').query()).not.toBeInTheDocument();
  571. });
  572. });
  573. const clickSelectOption = async (selectElement: HTMLElement, optionText: Matcher): Promise<void> => {
  574. await userEvent.click(byRole('combobox').get(selectElement));
  575. await selectOptionInTest(selectElement, optionText as string);
  576. };