eventsRequest.spec.jsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import {doEventsRequest} from 'sentry/actionCreators/events';
  3. import EventsRequest from 'sentry/components/charts/eventsRequest';
  4. const COUNT_OBJ = {
  5. count: 123,
  6. };
  7. jest.mock('sentry/actionCreators/events', () => ({
  8. doEventsRequest: jest.fn(),
  9. }));
  10. describe('EventsRequest', function () {
  11. const project = TestStubs.Project();
  12. const organization = TestStubs.Organization();
  13. const mock = jest.fn(() => null);
  14. const DEFAULTS = {
  15. api: new MockApiClient(),
  16. projects: [parseInt(project.id, 10)],
  17. environments: [],
  18. period: '24h',
  19. organization,
  20. tag: 'release',
  21. includePrevious: false,
  22. includeTimeseries: true,
  23. };
  24. let wrapper;
  25. describe('with props changes', function () {
  26. beforeAll(function () {
  27. doEventsRequest.mockImplementation(() =>
  28. Promise.resolve({
  29. data: [[new Date(), [COUNT_OBJ]]],
  30. })
  31. );
  32. wrapper = mountWithTheme(<EventsRequest {...DEFAULTS}>{mock}</EventsRequest>);
  33. });
  34. it('makes requests', function () {
  35. expect(mock).toHaveBeenNthCalledWith(
  36. 1,
  37. expect.objectContaining({
  38. loading: true,
  39. })
  40. );
  41. expect(mock).toHaveBeenLastCalledWith(
  42. expect.objectContaining({
  43. loading: false,
  44. timeseriesData: [
  45. {
  46. seriesName: expect.anything(),
  47. data: [
  48. expect.objectContaining({
  49. name: expect.any(Number),
  50. value: 123,
  51. }),
  52. ],
  53. },
  54. ],
  55. originalTimeseriesData: [[expect.anything(), expect.anything()]],
  56. })
  57. );
  58. expect(doEventsRequest).toHaveBeenCalled();
  59. });
  60. it('makes a new request if projects prop changes', async function () {
  61. doEventsRequest.mockClear();
  62. wrapper.setProps({projects: [123]});
  63. await tick();
  64. wrapper.update();
  65. expect(doEventsRequest).toHaveBeenCalledWith(
  66. expect.anything(),
  67. expect.objectContaining({
  68. projects: [123],
  69. })
  70. );
  71. });
  72. it('makes a new request if environments prop changes', async function () {
  73. doEventsRequest.mockClear();
  74. wrapper.setProps({environments: ['dev']});
  75. await tick();
  76. wrapper.update();
  77. expect(doEventsRequest).toHaveBeenCalledWith(
  78. expect.anything(),
  79. expect.objectContaining({
  80. environments: ['dev'],
  81. })
  82. );
  83. });
  84. it('makes a new request if period prop changes', async function () {
  85. doEventsRequest.mockClear();
  86. wrapper.setProps({period: '7d'});
  87. await tick();
  88. wrapper.update();
  89. expect(doEventsRequest).toHaveBeenCalledWith(
  90. expect.anything(),
  91. expect.objectContaining({
  92. period: '7d',
  93. })
  94. );
  95. });
  96. });
  97. describe('transforms', function () {
  98. beforeEach(function () {
  99. doEventsRequest.mockClear();
  100. });
  101. it('expands period in query if `includePrevious`', async function () {
  102. doEventsRequest.mockImplementation(() =>
  103. Promise.resolve({
  104. data: [
  105. [
  106. new Date(),
  107. [
  108. {...COUNT_OBJ, count: 321},
  109. {...COUNT_OBJ, count: 79},
  110. ],
  111. ],
  112. [new Date(), [COUNT_OBJ]],
  113. ],
  114. })
  115. );
  116. wrapper = mountWithTheme(
  117. <EventsRequest {...DEFAULTS} includePrevious>
  118. {mock}
  119. </EventsRequest>
  120. );
  121. await tick();
  122. wrapper.update();
  123. // actionCreator handles expanding the period when calling the API
  124. expect(doEventsRequest).toHaveBeenCalledWith(
  125. expect.anything(),
  126. expect.objectContaining({
  127. period: '24h',
  128. })
  129. );
  130. expect(mock).toHaveBeenLastCalledWith(
  131. expect.objectContaining({
  132. loading: false,
  133. allTimeseriesData: [
  134. [
  135. expect.anything(),
  136. [
  137. expect.objectContaining({count: 321}),
  138. expect.objectContaining({count: 79}),
  139. ],
  140. ],
  141. [expect.anything(), [expect.objectContaining({count: 123})]],
  142. ],
  143. timeseriesData: [
  144. {
  145. seriesName: expect.anything(),
  146. data: [
  147. expect.objectContaining({
  148. name: expect.anything(),
  149. value: 123,
  150. }),
  151. ],
  152. },
  153. ],
  154. previousTimeseriesData: [
  155. expect.objectContaining({
  156. seriesName: 'Previous',
  157. data: [
  158. expect.objectContaining({
  159. name: expect.anything(),
  160. value: 400,
  161. }),
  162. ],
  163. }),
  164. ],
  165. originalTimeseriesData: [
  166. [expect.anything(), [expect.objectContaining({count: 123})]],
  167. ],
  168. originalPreviousTimeseriesData: [
  169. [
  170. expect.anything(),
  171. [
  172. expect.objectContaining({count: 321}),
  173. expect.objectContaining({count: 79}),
  174. ],
  175. ],
  176. ],
  177. })
  178. );
  179. });
  180. it('expands multiple periods in query if `includePrevious`', async function () {
  181. doEventsRequest.mockImplementation(() =>
  182. Promise.resolve({
  183. 'count()': {
  184. data: [
  185. [
  186. new Date(),
  187. [
  188. {...COUNT_OBJ, count: 321},
  189. {...COUNT_OBJ, count: 79},
  190. ],
  191. ],
  192. [new Date(), [COUNT_OBJ]],
  193. ],
  194. },
  195. 'failure_count()': {
  196. data: [
  197. [
  198. new Date(),
  199. [
  200. {...COUNT_OBJ, count: 421},
  201. {...COUNT_OBJ, count: 79},
  202. ],
  203. ],
  204. [new Date(), [COUNT_OBJ]],
  205. ],
  206. },
  207. })
  208. );
  209. const multiYOptions = {
  210. yAxis: ['count()', 'failure_count()'],
  211. previousSeriesNames: ['previous count()', 'previous failure_count()'],
  212. };
  213. wrapper = mountWithTheme(
  214. <EventsRequest {...DEFAULTS} {...multiYOptions} includePrevious>
  215. {mock}
  216. </EventsRequest>
  217. );
  218. await tick();
  219. wrapper.update();
  220. // actionCreator handles expanding the period when calling the API
  221. expect(doEventsRequest).toHaveBeenCalledWith(
  222. expect.anything(),
  223. expect.objectContaining({
  224. period: '24h',
  225. })
  226. );
  227. expect(mock).toHaveBeenLastCalledWith(
  228. expect.objectContaining({
  229. loading: false,
  230. yAxis: ['count()', 'failure_count()'],
  231. previousSeriesNames: ['previous count()', 'previous failure_count()'],
  232. results: [
  233. expect.objectContaining({
  234. data: [expect.objectContaining({name: expect.anything(), value: 123})],
  235. seriesName: 'count()',
  236. }),
  237. expect.objectContaining({
  238. data: [expect.objectContaining({name: expect.anything(), value: 123})],
  239. seriesName: 'failure_count()',
  240. }),
  241. ],
  242. previousTimeseriesData: [
  243. expect.objectContaining({
  244. data: [expect.objectContaining({name: expect.anything(), value: 400})],
  245. seriesName: 'previous count()',
  246. stack: 'previous',
  247. }),
  248. expect.objectContaining({
  249. data: [expect.objectContaining({name: expect.anything(), value: 500})],
  250. seriesName: 'previous failure_count()',
  251. stack: 'previous',
  252. }),
  253. ],
  254. })
  255. );
  256. });
  257. it('aggregates counts per timestamp only when `includeTimeAggregation` prop is true', async function () {
  258. doEventsRequest.mockImplementation(() =>
  259. Promise.resolve({
  260. data: [[new Date(), [COUNT_OBJ, {...COUNT_OBJ, count: 100}]]],
  261. })
  262. );
  263. wrapper = mountWithTheme(
  264. <EventsRequest {...DEFAULTS} includeTimeseries>
  265. {mock}
  266. </EventsRequest>
  267. );
  268. await tick();
  269. wrapper.update();
  270. expect(mock).toHaveBeenLastCalledWith(
  271. expect.objectContaining({
  272. timeAggregatedData: {},
  273. })
  274. );
  275. wrapper.setProps({
  276. includeTimeAggregation: true,
  277. timeAggregationSeriesName: 'aggregated series',
  278. });
  279. await tick();
  280. wrapper.update();
  281. expect(mock).toHaveBeenLastCalledWith(
  282. expect.objectContaining({
  283. timeAggregatedData: {
  284. seriesName: 'aggregated series',
  285. data: [{name: expect.anything(), value: 223}],
  286. },
  287. })
  288. );
  289. });
  290. it('aggregates all counts per timestamp when category name identical', async function () {
  291. doEventsRequest.mockImplementation(() =>
  292. Promise.resolve({
  293. data: [[new Date(), [COUNT_OBJ, {...COUNT_OBJ, count: 100}]]],
  294. })
  295. );
  296. wrapper = mountWithTheme(
  297. <EventsRequest {...DEFAULTS} includeTimeseries>
  298. {mock}
  299. </EventsRequest>
  300. );
  301. await tick();
  302. wrapper.update();
  303. expect(mock).toHaveBeenLastCalledWith(
  304. expect.objectContaining({
  305. timeAggregatedData: {},
  306. })
  307. );
  308. wrapper.setProps({
  309. includeTimeAggregation: true,
  310. timeAggregationSeriesName: 'aggregated series',
  311. });
  312. await tick();
  313. wrapper.update();
  314. expect(mock).toHaveBeenLastCalledWith(
  315. expect.objectContaining({
  316. timeAggregatedData: {
  317. seriesName: 'aggregated series',
  318. data: [{name: expect.anything(), value: 223}],
  319. },
  320. })
  321. );
  322. });
  323. });
  324. describe('yAxis', function () {
  325. beforeEach(function () {
  326. doEventsRequest.mockClear();
  327. });
  328. it('supports yAxis', async function () {
  329. doEventsRequest.mockImplementation(() =>
  330. Promise.resolve({
  331. data: [
  332. [
  333. new Date(),
  334. [
  335. {...COUNT_OBJ, count: 321},
  336. {...COUNT_OBJ, count: 79},
  337. ],
  338. ],
  339. [new Date(), [COUNT_OBJ]],
  340. ],
  341. })
  342. );
  343. wrapper = mountWithTheme(
  344. <EventsRequest {...DEFAULTS} includePrevious yAxis="apdex()">
  345. {mock}
  346. </EventsRequest>
  347. );
  348. await tick();
  349. wrapper.update();
  350. expect(mock).toHaveBeenLastCalledWith(
  351. expect.objectContaining({
  352. loading: false,
  353. allTimeseriesData: [
  354. [
  355. expect.anything(),
  356. [
  357. expect.objectContaining({count: 321}),
  358. expect.objectContaining({count: 79}),
  359. ],
  360. ],
  361. [expect.anything(), [expect.objectContaining({count: 123})]],
  362. ],
  363. timeseriesData: [
  364. {
  365. seriesName: expect.anything(),
  366. data: [
  367. expect.objectContaining({
  368. name: expect.anything(),
  369. value: 123,
  370. }),
  371. ],
  372. },
  373. ],
  374. previousTimeseriesData: [
  375. expect.objectContaining({
  376. seriesName: 'Previous',
  377. data: [
  378. expect.objectContaining({
  379. name: expect.anything(),
  380. value: 400,
  381. }),
  382. ],
  383. }),
  384. ],
  385. originalTimeseriesData: [
  386. [expect.anything(), [expect.objectContaining({count: 123})]],
  387. ],
  388. originalPreviousTimeseriesData: [
  389. [
  390. expect.anything(),
  391. [
  392. expect.objectContaining({count: 321}),
  393. expect.objectContaining({count: 79}),
  394. ],
  395. ],
  396. ],
  397. })
  398. );
  399. });
  400. it('supports multiple yAxis', async function () {
  401. doEventsRequest.mockImplementation(() =>
  402. Promise.resolve({
  403. 'epm()': {
  404. data: [
  405. [
  406. new Date(),
  407. [
  408. {...COUNT_OBJ, count: 321},
  409. {...COUNT_OBJ, count: 79},
  410. ],
  411. ],
  412. [new Date(), [COUNT_OBJ]],
  413. ],
  414. },
  415. 'apdex()': {
  416. data: [
  417. [
  418. new Date(),
  419. [
  420. {...COUNT_OBJ, count: 321},
  421. {...COUNT_OBJ, count: 79},
  422. ],
  423. ],
  424. [new Date(), [COUNT_OBJ]],
  425. ],
  426. },
  427. })
  428. );
  429. wrapper = mountWithTheme(
  430. <EventsRequest {...DEFAULTS} yAxis={['apdex()', 'epm()']}>
  431. {mock}
  432. </EventsRequest>
  433. );
  434. await tick();
  435. wrapper.update();
  436. const generateExpected = name => {
  437. return {
  438. seriesName: name,
  439. data: [
  440. {name: expect.anything(), value: 400},
  441. {name: expect.anything(), value: 123},
  442. ],
  443. };
  444. };
  445. expect(mock).toHaveBeenLastCalledWith(
  446. expect.objectContaining({
  447. loading: false,
  448. results: [generateExpected('epm()'), generateExpected('apdex()')],
  449. })
  450. );
  451. });
  452. });
  453. describe('topEvents', function () {
  454. beforeEach(function () {
  455. doEventsRequest.mockClear();
  456. });
  457. it('supports topEvents parameter', async function () {
  458. doEventsRequest.mockImplementation(() =>
  459. Promise.resolve({
  460. 'project1,error': {
  461. data: [
  462. [
  463. new Date(),
  464. [
  465. {...COUNT_OBJ, count: 321},
  466. {...COUNT_OBJ, count: 79},
  467. ],
  468. ],
  469. [new Date(), [COUNT_OBJ]],
  470. ],
  471. },
  472. 'project1,warning': {
  473. data: [
  474. [
  475. new Date(),
  476. [
  477. {...COUNT_OBJ, count: 321},
  478. {...COUNT_OBJ, count: 79},
  479. ],
  480. ],
  481. [new Date(), [COUNT_OBJ]],
  482. ],
  483. },
  484. })
  485. );
  486. wrapper = mountWithTheme(
  487. <EventsRequest {...DEFAULTS} field={['project', 'level']} topEvents={2}>
  488. {mock}
  489. </EventsRequest>
  490. );
  491. await tick();
  492. wrapper.update();
  493. const generateExpected = name => {
  494. return {
  495. seriesName: name,
  496. data: [
  497. {name: expect.anything(), value: 400},
  498. {name: expect.anything(), value: 123},
  499. ],
  500. };
  501. };
  502. expect(mock).toHaveBeenLastCalledWith(
  503. expect.objectContaining({
  504. loading: false,
  505. results: [
  506. generateExpected('project1,error'),
  507. generateExpected('project1,warning'),
  508. ],
  509. })
  510. );
  511. });
  512. });
  513. describe('out of retention', function () {
  514. beforeEach(function () {
  515. doEventsRequest.mockClear();
  516. });
  517. it('does not make request', function () {
  518. wrapper = mountWithTheme(
  519. <EventsRequest {...DEFAULTS} expired>
  520. {mock}
  521. </EventsRequest>
  522. );
  523. expect(doEventsRequest).not.toHaveBeenCalled();
  524. });
  525. it('errors', function () {
  526. wrapper = mountWithTheme(
  527. <EventsRequest {...DEFAULTS} expired>
  528. {mock}
  529. </EventsRequest>
  530. );
  531. expect(mock).toHaveBeenLastCalledWith(
  532. expect.objectContaining({
  533. expired: true,
  534. errored: true,
  535. })
  536. );
  537. });
  538. });
  539. describe('timeframe', function () {
  540. beforeEach(function () {
  541. doEventsRequest.mockClear();
  542. });
  543. it('passes query timeframe start and end to the child if supplied by timeseriesData', async function () {
  544. doEventsRequest.mockImplementation(() =>
  545. Promise.resolve({
  546. p95: {
  547. data: [[new Date(), [COUNT_OBJ]]],
  548. start: 1627402280,
  549. end: 1627402398,
  550. },
  551. })
  552. );
  553. wrapper = mountWithTheme(<EventsRequest {...DEFAULTS}>{mock}</EventsRequest>);
  554. await tick();
  555. wrapper.update();
  556. expect(mock).toHaveBeenLastCalledWith(
  557. expect.objectContaining({
  558. timeframe: {
  559. start: 1627402280000,
  560. end: 1627402398000,
  561. },
  562. })
  563. );
  564. });
  565. });
  566. });