eventsRequest.spec.jsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import {doEventsRequest} from 'app/actionCreators/events';
  3. import EventsRequest from 'app/components/charts/eventsRequest';
  4. const COUNT_OBJ = {
  5. count: 123,
  6. };
  7. jest.mock('app/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', async 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. seriesName: 'Previous',
  156. data: [
  157. expect.objectContaining({
  158. name: expect.anything(),
  159. value: 400,
  160. }),
  161. ],
  162. },
  163. originalTimeseriesData: [
  164. [expect.anything(), [expect.objectContaining({count: 123})]],
  165. ],
  166. originalPreviousTimeseriesData: [
  167. [
  168. expect.anything(),
  169. [
  170. expect.objectContaining({count: 321}),
  171. expect.objectContaining({count: 79}),
  172. ],
  173. ],
  174. ],
  175. })
  176. );
  177. });
  178. it('aggregates counts per timestamp only when `includeTimeAggregation` prop is true', async function () {
  179. doEventsRequest.mockImplementation(() =>
  180. Promise.resolve({
  181. data: [[new Date(), [COUNT_OBJ, {...COUNT_OBJ, count: 100}]]],
  182. })
  183. );
  184. wrapper = mountWithTheme(
  185. <EventsRequest {...DEFAULTS} includeTimeseries>
  186. {mock}
  187. </EventsRequest>
  188. );
  189. await tick();
  190. wrapper.update();
  191. expect(mock).toHaveBeenLastCalledWith(
  192. expect.objectContaining({
  193. timeAggregatedData: {},
  194. })
  195. );
  196. wrapper.setProps({
  197. includeTimeAggregation: true,
  198. timeAggregationSeriesName: 'aggregated series',
  199. });
  200. await tick();
  201. wrapper.update();
  202. expect(mock).toHaveBeenLastCalledWith(
  203. expect.objectContaining({
  204. timeAggregatedData: {
  205. seriesName: 'aggregated series',
  206. data: [{name: expect.anything(), value: 223}],
  207. },
  208. })
  209. );
  210. });
  211. it('aggregates all counts per timestamp when category name identical', async function () {
  212. doEventsRequest.mockImplementation(() =>
  213. Promise.resolve({
  214. data: [[new Date(), [COUNT_OBJ, {...COUNT_OBJ, count: 100}]]],
  215. })
  216. );
  217. wrapper = mountWithTheme(
  218. <EventsRequest {...DEFAULTS} includeTimeseries>
  219. {mock}
  220. </EventsRequest>
  221. );
  222. await tick();
  223. wrapper.update();
  224. expect(mock).toHaveBeenLastCalledWith(
  225. expect.objectContaining({
  226. timeAggregatedData: {},
  227. })
  228. );
  229. wrapper.setProps({
  230. includeTimeAggregation: true,
  231. timeAggregationSeriesName: 'aggregated series',
  232. });
  233. await tick();
  234. wrapper.update();
  235. expect(mock).toHaveBeenLastCalledWith(
  236. expect.objectContaining({
  237. timeAggregatedData: {
  238. seriesName: 'aggregated series',
  239. data: [{name: expect.anything(), value: 223}],
  240. },
  241. })
  242. );
  243. });
  244. });
  245. describe('yAxis', function () {
  246. beforeEach(function () {
  247. doEventsRequest.mockClear();
  248. });
  249. it('supports yAxis', async function () {
  250. doEventsRequest.mockImplementation(() =>
  251. Promise.resolve({
  252. data: [
  253. [
  254. new Date(),
  255. [
  256. {...COUNT_OBJ, count: 321},
  257. {...COUNT_OBJ, count: 79},
  258. ],
  259. ],
  260. [new Date(), [COUNT_OBJ]],
  261. ],
  262. })
  263. );
  264. wrapper = mountWithTheme(
  265. <EventsRequest {...DEFAULTS} includePrevious yAxis="apdex()">
  266. {mock}
  267. </EventsRequest>
  268. );
  269. await tick();
  270. wrapper.update();
  271. expect(mock).toHaveBeenLastCalledWith(
  272. expect.objectContaining({
  273. loading: false,
  274. allTimeseriesData: [
  275. [
  276. expect.anything(),
  277. [
  278. expect.objectContaining({count: 321}),
  279. expect.objectContaining({count: 79}),
  280. ],
  281. ],
  282. [expect.anything(), [expect.objectContaining({count: 123})]],
  283. ],
  284. timeseriesData: [
  285. {
  286. seriesName: expect.anything(),
  287. data: [
  288. expect.objectContaining({
  289. name: expect.anything(),
  290. value: 123,
  291. }),
  292. ],
  293. },
  294. ],
  295. previousTimeseriesData: {
  296. seriesName: 'Previous',
  297. data: [
  298. expect.objectContaining({
  299. name: expect.anything(),
  300. value: 400,
  301. }),
  302. ],
  303. },
  304. originalTimeseriesData: [
  305. [expect.anything(), [expect.objectContaining({count: 123})]],
  306. ],
  307. originalPreviousTimeseriesData: [
  308. [
  309. expect.anything(),
  310. [
  311. expect.objectContaining({count: 321}),
  312. expect.objectContaining({count: 79}),
  313. ],
  314. ],
  315. ],
  316. })
  317. );
  318. });
  319. it('supports multiple yAxis', async function () {
  320. doEventsRequest.mockImplementation(() =>
  321. Promise.resolve({
  322. 'epm()': {
  323. data: [
  324. [
  325. new Date(),
  326. [
  327. {...COUNT_OBJ, count: 321},
  328. {...COUNT_OBJ, count: 79},
  329. ],
  330. ],
  331. [new Date(), [COUNT_OBJ]],
  332. ],
  333. },
  334. 'apdex()': {
  335. data: [
  336. [
  337. new Date(),
  338. [
  339. {...COUNT_OBJ, count: 321},
  340. {...COUNT_OBJ, count: 79},
  341. ],
  342. ],
  343. [new Date(), [COUNT_OBJ]],
  344. ],
  345. },
  346. })
  347. );
  348. wrapper = mountWithTheme(
  349. <EventsRequest {...DEFAULTS} includePrevious yAxis={['apdex()', 'epm()']}>
  350. {mock}
  351. </EventsRequest>
  352. );
  353. await tick();
  354. wrapper.update();
  355. const generateExpected = name => {
  356. return {
  357. seriesName: name,
  358. data: [
  359. {name: expect.anything(), value: 400},
  360. {name: expect.anything(), value: 123},
  361. ],
  362. };
  363. };
  364. expect(mock).toHaveBeenLastCalledWith(
  365. expect.objectContaining({
  366. loading: false,
  367. results: [generateExpected('epm()'), generateExpected('apdex()')],
  368. })
  369. );
  370. });
  371. });
  372. describe('topEvents', function () {
  373. beforeEach(function () {
  374. doEventsRequest.mockClear();
  375. });
  376. it('supports topEvents parameter', async function () {
  377. doEventsRequest.mockImplementation(() =>
  378. Promise.resolve({
  379. 'project1,error': {
  380. data: [
  381. [
  382. new Date(),
  383. [
  384. {...COUNT_OBJ, count: 321},
  385. {...COUNT_OBJ, count: 79},
  386. ],
  387. ],
  388. [new Date(), [COUNT_OBJ]],
  389. ],
  390. },
  391. 'project1,warning': {
  392. data: [
  393. [
  394. new Date(),
  395. [
  396. {...COUNT_OBJ, count: 321},
  397. {...COUNT_OBJ, count: 79},
  398. ],
  399. ],
  400. [new Date(), [COUNT_OBJ]],
  401. ],
  402. },
  403. })
  404. );
  405. wrapper = mountWithTheme(
  406. <EventsRequest
  407. {...DEFAULTS}
  408. includePrevious
  409. field={['project', 'level']}
  410. topEvents={2}
  411. >
  412. {mock}
  413. </EventsRequest>
  414. );
  415. await tick();
  416. wrapper.update();
  417. const generateExpected = name => {
  418. return {
  419. seriesName: name,
  420. data: [
  421. {name: expect.anything(), value: 400},
  422. {name: expect.anything(), value: 123},
  423. ],
  424. };
  425. };
  426. expect(mock).toHaveBeenLastCalledWith(
  427. expect.objectContaining({
  428. loading: false,
  429. results: [
  430. generateExpected('project1,error'),
  431. generateExpected('project1,warning'),
  432. ],
  433. })
  434. );
  435. });
  436. });
  437. describe('out of retention', function () {
  438. beforeEach(function () {
  439. doEventsRequest.mockClear();
  440. });
  441. it('does not make request', async function () {
  442. wrapper = mountWithTheme(
  443. <EventsRequest {...DEFAULTS} expired>
  444. {mock}
  445. </EventsRequest>
  446. );
  447. expect(doEventsRequest).not.toHaveBeenCalled();
  448. });
  449. it('errors', async function () {
  450. wrapper = mountWithTheme(
  451. <EventsRequest {...DEFAULTS} expired>
  452. {mock}
  453. </EventsRequest>
  454. );
  455. expect(mock).toHaveBeenLastCalledWith(
  456. expect.objectContaining({
  457. expired: true,
  458. errored: true,
  459. })
  460. );
  461. });
  462. });
  463. describe('timeframe', function () {
  464. beforeEach(function () {
  465. doEventsRequest.mockClear();
  466. });
  467. it('passes query timeframe start and end to the child if supplied by timeseriesData', async function () {
  468. doEventsRequest.mockImplementation(() =>
  469. Promise.resolve({
  470. p95: {
  471. data: [[new Date(), [COUNT_OBJ]]],
  472. start: 1627402280,
  473. end: 1627402398,
  474. },
  475. })
  476. );
  477. wrapper = mountWithTheme(<EventsRequest {...DEFAULTS}>{mock}</EventsRequest>);
  478. await tick();
  479. wrapper.update();
  480. expect(mock).toHaveBeenLastCalledWith(
  481. expect.objectContaining({
  482. timeframe: {
  483. start: 1627402280000,
  484. end: 1627402398000,
  485. },
  486. })
  487. );
  488. });
  489. });
  490. });