PanelAlertTabContent.test.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. import { render, act, waitFor } from '@testing-library/react';
  2. import React from 'react';
  3. import { Provider } from 'react-redux';
  4. import { Router } from 'react-router-dom';
  5. import { byTestId } from 'testing-library-selector';
  6. import { DataSourceApi } from '@grafana/data';
  7. import { locationService, setDataSourceSrv } from '@grafana/runtime';
  8. import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend';
  9. import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
  10. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  11. import { toggleOption } from 'app/features/variables/pickers/OptionsPicker/reducer';
  12. import { toKeyedAction } from 'app/features/variables/state/keyedVariablesReducer';
  13. import { PrometheusDatasource } from 'app/plugins/datasource/prometheus/datasource';
  14. import { PromOptions } from 'app/plugins/datasource/prometheus/types';
  15. import { configureStore } from 'app/store/configureStore';
  16. import { PanelAlertTabContent } from './PanelAlertTabContent';
  17. import { fetchRules } from './api/prometheus';
  18. import { fetchRulerRules } from './api/ruler';
  19. import {
  20. disableRBAC,
  21. mockDataSource,
  22. MockDataSourceSrv,
  23. mockPromAlertingRule,
  24. mockPromRuleGroup,
  25. mockPromRuleNamespace,
  26. mockRulerGrafanaRule,
  27. } from './mocks';
  28. import { getAllDataSources } from './utils/config';
  29. import { Annotation } from './utils/constants';
  30. import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
  31. import * as ruleFormUtils from './utils/rule-form';
  32. jest.mock('./api/prometheus');
  33. jest.mock('./api/ruler');
  34. jest.mock('./utils/config');
  35. const dataSources = {
  36. prometheus: mockDataSource<PromOptions>({
  37. name: 'Prometheus',
  38. type: DataSourceType.Prometheus,
  39. isDefault: false,
  40. }),
  41. default: mockDataSource<PromOptions>({
  42. name: 'Default',
  43. type: DataSourceType.Prometheus,
  44. isDefault: true,
  45. }),
  46. };
  47. dataSources.prometheus.meta.alerting = true;
  48. dataSources.default.meta.alerting = true;
  49. const mocks = {
  50. getAllDataSources: jest.mocked(getAllDataSources),
  51. api: {
  52. fetchRules: jest.mocked(fetchRules),
  53. fetchRulerRules: jest.mocked(fetchRulerRules),
  54. },
  55. };
  56. const renderAlertTabContent = (
  57. dashboard: DashboardModel,
  58. panel: PanelModel,
  59. initialStore?: ReturnType<typeof configureStore>
  60. ) => {
  61. const store = initialStore ?? configureStore();
  62. return act(async () => {
  63. render(
  64. <Provider store={store}>
  65. <Router history={locationService.getHistory()}>
  66. <PanelAlertTabContent dashboard={dashboard} panel={panel} />
  67. </Router>
  68. </Provider>
  69. );
  70. });
  71. };
  72. const rules = [
  73. mockPromRuleNamespace({
  74. name: 'default',
  75. groups: [
  76. mockPromRuleGroup({
  77. name: 'mygroup',
  78. rules: [
  79. mockPromAlertingRule({
  80. name: 'dashboardrule1',
  81. annotations: {
  82. [Annotation.dashboardUID]: '12',
  83. [Annotation.panelID]: '34',
  84. },
  85. }),
  86. ],
  87. }),
  88. mockPromRuleGroup({
  89. name: 'othergroup',
  90. rules: [
  91. mockPromAlertingRule({
  92. name: 'dashboardrule2',
  93. annotations: {
  94. [Annotation.dashboardUID]: '121',
  95. [Annotation.panelID]: '341',
  96. },
  97. }),
  98. ],
  99. }),
  100. ],
  101. }),
  102. ];
  103. const rulerRules = {
  104. default: [
  105. {
  106. name: 'mygroup',
  107. rules: [
  108. mockRulerGrafanaRule(
  109. {
  110. annotations: {
  111. [Annotation.dashboardUID]: '12',
  112. [Annotation.panelID]: '34',
  113. },
  114. },
  115. {
  116. title: 'dashboardrule1',
  117. }
  118. ),
  119. ],
  120. },
  121. {
  122. name: 'othergroup',
  123. rules: [
  124. mockRulerGrafanaRule(
  125. {
  126. annotations: {
  127. [Annotation.dashboardUID]: '121',
  128. [Annotation.panelID]: '341',
  129. },
  130. },
  131. {
  132. title: 'dashboardrule2',
  133. }
  134. ),
  135. ],
  136. },
  137. ],
  138. };
  139. const dashboard = {
  140. uid: '12',
  141. time: {
  142. from: 'now-6h',
  143. to: 'now',
  144. },
  145. meta: {
  146. canSave: true,
  147. folderId: 1,
  148. folderTitle: 'super folder',
  149. },
  150. } as DashboardModel;
  151. const panel = new PanelModel({
  152. datasource: {
  153. type: 'prometheus',
  154. uid: dataSources.prometheus.uid,
  155. },
  156. title: 'mypanel',
  157. id: 34,
  158. targets: [
  159. {
  160. expr: 'sum(some_metric [$__interval])) by (app)',
  161. refId: 'A',
  162. },
  163. ],
  164. });
  165. const ui = {
  166. row: byTestId('row'),
  167. createButton: byTestId<HTMLAnchorElement>('create-alert-rule-button'),
  168. };
  169. describe('PanelAlertTabContent', () => {
  170. beforeEach(() => {
  171. jest.resetAllMocks();
  172. mocks.getAllDataSources.mockReturnValue(Object.values(dataSources));
  173. const dsService = new MockDataSourceSrv(dataSources);
  174. dsService.datasources[dataSources.prometheus.uid] = new PrometheusDatasource(
  175. dataSources.prometheus
  176. ) as DataSourceApi<any, any>;
  177. dsService.datasources[dataSources.default.uid] = new PrometheusDatasource(dataSources.default) as DataSourceApi<
  178. any,
  179. any
  180. >;
  181. setDataSourceSrv(dsService);
  182. disableRBAC();
  183. });
  184. it('Will take into account panel maxDataPoints', async () => {
  185. await renderAlertTabContent(
  186. dashboard,
  187. new PanelModel({
  188. ...panel,
  189. maxDataPoints: 100,
  190. interval: '10s',
  191. })
  192. );
  193. const button = await ui.createButton.find();
  194. const href = button.href;
  195. const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
  196. expect(match).toHaveLength(2);
  197. const defaults = JSON.parse(decodeURIComponent(match![1]));
  198. expect(defaults.queries[0].model).toEqual({
  199. expr: 'sum(some_metric [5m])) by (app)',
  200. refId: 'A',
  201. datasource: {
  202. type: 'prometheus',
  203. uid: 'mock-ds-2',
  204. },
  205. interval: '',
  206. intervalMs: 300000,
  207. maxDataPoints: 100,
  208. });
  209. });
  210. it('Will work with default datasource', async () => {
  211. await renderAlertTabContent(
  212. dashboard,
  213. new PanelModel({
  214. ...panel,
  215. datasource: undefined,
  216. maxDataPoints: 100,
  217. interval: '10s',
  218. })
  219. );
  220. const button = await ui.createButton.find();
  221. const href = button.href;
  222. const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
  223. expect(match).toHaveLength(2);
  224. const defaults = JSON.parse(decodeURIComponent(match![1]));
  225. expect(defaults.queries[0].model).toEqual({
  226. expr: 'sum(some_metric [5m])) by (app)',
  227. refId: 'A',
  228. datasource: {
  229. type: 'prometheus',
  230. uid: 'mock-ds-3',
  231. },
  232. interval: '',
  233. intervalMs: 300000,
  234. maxDataPoints: 100,
  235. });
  236. });
  237. it('Will take into account datasource minInterval', async () => {
  238. (getDatasourceSrv() as any as MockDataSourceSrv).datasources[dataSources.prometheus.uid].interval = '7m';
  239. await renderAlertTabContent(
  240. dashboard,
  241. new PanelModel({
  242. ...panel,
  243. maxDataPoints: 100,
  244. })
  245. );
  246. const button = await ui.createButton.find();
  247. const href = button.href;
  248. const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
  249. expect(match).toHaveLength(2);
  250. const defaults = JSON.parse(decodeURIComponent(match![1]));
  251. expect(defaults.queries[0].model).toEqual({
  252. expr: 'sum(some_metric [7m])) by (app)',
  253. refId: 'A',
  254. datasource: {
  255. type: 'prometheus',
  256. uid: 'mock-ds-2',
  257. },
  258. interval: '',
  259. intervalMs: 420000,
  260. maxDataPoints: 100,
  261. });
  262. });
  263. it('Will render alerts belonging to panel and a button to create alert from panel queries', async () => {
  264. mocks.api.fetchRules.mockResolvedValue(rules);
  265. mocks.api.fetchRulerRules.mockResolvedValue(rulerRules);
  266. await renderAlertTabContent(dashboard, panel);
  267. const rows = await ui.row.findAll();
  268. expect(rows).toHaveLength(1);
  269. expect(rows[0]).toHaveTextContent(/dashboardrule1/);
  270. expect(rows[0]).not.toHaveTextContent(/dashboardrule2/);
  271. const button = await ui.createButton.find();
  272. const href = button.href;
  273. const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
  274. expect(match).toHaveLength(2);
  275. const defaults = JSON.parse(decodeURIComponent(match![1]));
  276. expect(defaults).toEqual({
  277. type: 'grafana',
  278. folder: { id: 1, title: 'super folder' },
  279. queries: [
  280. {
  281. refId: 'A',
  282. queryType: '',
  283. relativeTimeRange: { from: 21600, to: 0 },
  284. datasourceUid: 'mock-ds-2',
  285. model: {
  286. expr: 'sum(some_metric [15s])) by (app)',
  287. refId: 'A',
  288. datasource: {
  289. type: 'prometheus',
  290. uid: 'mock-ds-2',
  291. },
  292. interval: '',
  293. intervalMs: 15000,
  294. },
  295. },
  296. {
  297. refId: 'B',
  298. datasourceUid: '-100',
  299. queryType: '',
  300. model: {
  301. refId: 'B',
  302. hide: false,
  303. type: 'classic_conditions',
  304. datasource: {
  305. type: ExpressionDatasourceRef.type,
  306. uid: '-100',
  307. },
  308. conditions: [
  309. {
  310. type: 'query',
  311. evaluator: { params: [3], type: 'gt' },
  312. operator: { type: 'and' },
  313. query: { params: ['A'] },
  314. reducer: { params: [], type: 'last' },
  315. },
  316. ],
  317. },
  318. },
  319. ],
  320. name: 'mypanel',
  321. condition: 'B',
  322. annotations: [
  323. { key: '__dashboardUid__', value: '12' },
  324. { key: '__panelId__', value: '34' },
  325. ],
  326. });
  327. expect(mocks.api.fetchRulerRules).toHaveBeenCalledWith(
  328. { dataSourceName: GRAFANA_RULES_SOURCE_NAME, apiVersion: 'legacy' },
  329. {
  330. dashboardUID: dashboard.uid,
  331. panelId: panel.id,
  332. }
  333. );
  334. expect(mocks.api.fetchRules).toHaveBeenCalledWith(GRAFANA_RULES_SOURCE_NAME, {
  335. dashboardUID: dashboard.uid,
  336. panelId: panel.id,
  337. });
  338. });
  339. it('Update NewRuleFromPanel button url when template changes', async () => {
  340. const panelToRuleValuesSpy = jest.spyOn(ruleFormUtils, 'panelToRuleFormValues');
  341. const store = configureStore();
  342. await renderAlertTabContent(dashboard, panel, store);
  343. store.dispatch(
  344. toKeyedAction(
  345. 'optionKey',
  346. toggleOption({
  347. option: { value: 'optionValue', selected: true, text: 'Option' },
  348. clearOthers: false,
  349. forceSelect: false,
  350. })
  351. )
  352. );
  353. await waitFor(() => expect(panelToRuleValuesSpy).toHaveBeenCalledTimes(2));
  354. });
  355. });