groupEvents.spec.jsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. import {browserHistory} from 'react-router';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {
  4. render,
  5. screen,
  6. userEvent,
  7. waitForElementToBeRemoved,
  8. } from 'sentry-test/reactTestingLibrary';
  9. import {GroupEvents} from 'sentry/views/organizationGroupDetails/groupEvents';
  10. describe('groupEvents', function () {
  11. let request;
  12. let discoverRequest;
  13. let attachmentsRequest;
  14. const {organization, routerContext} = initializeOrg();
  15. beforeEach(function () {
  16. request = MockApiClient.addMockResponse({
  17. url: '/issues/1/events/',
  18. body: [
  19. TestStubs.Event({
  20. eventID: '12345',
  21. id: '1',
  22. message: 'ApiException',
  23. groupID: '1',
  24. }),
  25. TestStubs.Event({
  26. crashFile: {
  27. sha1: 'sha1',
  28. name: 'name.dmp',
  29. dateCreated: '2019-05-21T18:01:48.762Z',
  30. headers: {'Content-Type': 'application/octet-stream'},
  31. id: '12345',
  32. size: 123456,
  33. type: 'event.minidump',
  34. },
  35. culprit: '',
  36. dateCreated: '2019-05-21T18:00:23Z',
  37. 'event.type': 'error',
  38. eventID: '123456',
  39. groupID: '1',
  40. id: '98654',
  41. location: 'main.js',
  42. message: 'TestException',
  43. platform: 'native',
  44. projectID: '123',
  45. tags: [{value: 'production', key: 'production'}],
  46. title: 'TestException',
  47. }),
  48. ],
  49. });
  50. browserHistory.push = jest.fn();
  51. discoverRequest = MockApiClient.addMockResponse({
  52. url: '/organizations/org-slug/events/',
  53. headers: {
  54. Link: `<https://sentry.io/api/0/issues/1/events/?limit=50&cursor=0:0:1>; rel="previous"; results="true"; cursor="0:0:1", <https://sentry.io/api/0/issues/1/events/?limit=50&cursor=0:200:0>; rel="next"; results="true"; cursor="0:200:0"`,
  55. },
  56. body: {
  57. data: [
  58. {
  59. timestamp: '2022-09-11T15:01:10+00:00',
  60. transaction: '/api',
  61. release: 'backend@1.2.3',
  62. 'transaction.duration': 1803,
  63. environment: 'prod',
  64. 'user.display': 'sentry@sentry.sentry',
  65. id: 'id123',
  66. trace: 'trace123',
  67. 'project.name': 'project123',
  68. },
  69. ],
  70. meta: {
  71. fields: {
  72. timestamp: 'date',
  73. transaction: 'string',
  74. release: 'string',
  75. 'transaction.duration': 'duration',
  76. environment: 'string',
  77. 'user.display': 'string',
  78. id: 'string',
  79. trace: 'string',
  80. 'project.name': 'string',
  81. },
  82. units: {
  83. timestamp: null,
  84. transaction: null,
  85. release: null,
  86. 'transaction.duration': 'millisecond',
  87. environment: null,
  88. 'user.display': null,
  89. id: null,
  90. trace: null,
  91. 'project.name': null,
  92. },
  93. isMetricsData: false,
  94. tips: {query: null, columns: null},
  95. },
  96. },
  97. });
  98. attachmentsRequest = MockApiClient.addMockResponse({
  99. url: '/api/0/issues/1/attachments/?per_page=50&types=event.minidump&event_id=id123',
  100. body: [],
  101. });
  102. });
  103. afterEach(() => {
  104. MockApiClient.clearMockResponses();
  105. jest.clearAllMocks();
  106. });
  107. it('renders', function () {
  108. const wrapper = render(
  109. <GroupEvents
  110. organization={organization}
  111. api={new MockApiClient()}
  112. group={TestStubs.Group()}
  113. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  114. location={{query: {}}}
  115. />,
  116. {context: routerContext, organization}
  117. );
  118. expect(wrapper.container).toSnapshot();
  119. });
  120. it('handles search', function () {
  121. render(
  122. <GroupEvents
  123. organization={organization}
  124. api={new MockApiClient()}
  125. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  126. group={TestStubs.Group()}
  127. location={{query: {}}}
  128. />,
  129. {context: routerContext, organization}
  130. );
  131. const list = [
  132. {searchTerm: '', expectedQuery: ''},
  133. {searchTerm: 'test', expectedQuery: 'test'},
  134. {searchTerm: 'environment:production test', expectedQuery: 'test'},
  135. ];
  136. const input = screen.getByPlaceholderText('Search events by id, message, or tags');
  137. for (const item of list) {
  138. userEvent.clear(input);
  139. userEvent.type(input, `${item.searchTerm}{enter}`);
  140. expect(browserHistory.push).toHaveBeenCalledWith(
  141. expect.objectContaining({
  142. query: {query: item.expectedQuery},
  143. })
  144. );
  145. }
  146. });
  147. it('handles environment filtering', function () {
  148. render(
  149. <GroupEvents
  150. organization={organization}
  151. api={new MockApiClient()}
  152. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  153. group={TestStubs.Group()}
  154. location={{query: {environment: ['prod', 'staging']}}}
  155. />,
  156. {context: routerContext, organization}
  157. );
  158. expect(request).toHaveBeenCalledWith(
  159. '/issues/1/events/',
  160. expect.objectContaining({
  161. query: {limit: 50, query: '', environment: ['prod', 'staging']},
  162. })
  163. );
  164. });
  165. describe('When the performance flag is enabled', () => {
  166. let org;
  167. let group;
  168. beforeEach(() => {
  169. org = initializeOrg({
  170. organization: {
  171. features: ['performance-issues-all-events-tab', 'event-attachments'],
  172. },
  173. });
  174. group = TestStubs.Group();
  175. });
  176. it('renders new events table for performance', function () {
  177. group.issueCategory = 'performance';
  178. render(
  179. <GroupEvents
  180. organization={org.organization}
  181. api={new MockApiClient()}
  182. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  183. group={group}
  184. location={{query: {environment: ['prod', 'staging']}}}
  185. />,
  186. {context: routerContext, organization}
  187. );
  188. expect(discoverRequest).toHaveBeenCalledWith(
  189. '/organizations/org-slug/events/',
  190. expect.objectContaining({
  191. query: expect.objectContaining({
  192. query: 'performance.issue_ids:1 event.type:transaction ',
  193. }),
  194. })
  195. );
  196. const perfEventsColumn = screen.getByText('transaction');
  197. expect(perfEventsColumn).toBeInTheDocument();
  198. expect(request).not.toHaveBeenCalled();
  199. });
  200. it('renders event and trace link correctly', async () => {
  201. group.issueCategory = 'performance';
  202. render(
  203. <GroupEvents
  204. organization={org.organization}
  205. api={new MockApiClient()}
  206. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  207. group={group}
  208. location={{query: {environment: ['prod', 'staging']}}}
  209. />,
  210. {context: routerContext, organization}
  211. );
  212. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  213. const eventIdATag = screen.getByText('id123').closest('a');
  214. expect(eventIdATag).toHaveAttribute(
  215. 'href',
  216. '/organizations/org-slug/issues/1/events/id123/'
  217. );
  218. });
  219. it('does not make attachments request, when feature not enabled', async () => {
  220. org = initializeOrg({
  221. organization: {
  222. features: ['performance-issues-all-events-tab'],
  223. },
  224. });
  225. render(
  226. <GroupEvents
  227. organization={org.organization}
  228. api={new MockApiClient()}
  229. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  230. group={group}
  231. location={{query: {environment: ['prod', 'staging']}}}
  232. />,
  233. {context: routerContext, organization}
  234. );
  235. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  236. const attachmentsColumn = screen.queryByText('attachments');
  237. expect(attachmentsColumn).not.toBeInTheDocument();
  238. expect(attachmentsRequest).not.toHaveBeenCalled();
  239. });
  240. it('does not display attachments column with no attachments', async () => {
  241. render(
  242. <GroupEvents
  243. organization={org.organization}
  244. api={new MockApiClient()}
  245. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  246. group={group}
  247. location={{query: {environment: ['prod', 'staging']}}}
  248. />,
  249. {context: routerContext, organization}
  250. );
  251. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  252. const attachmentsColumn = screen.queryByText('attachments');
  253. expect(attachmentsColumn).not.toBeInTheDocument();
  254. expect(attachmentsRequest).toHaveBeenCalled();
  255. });
  256. it('does not display minidump column with no minidumps', async () => {
  257. render(
  258. <GroupEvents
  259. organization={org.organization}
  260. api={new MockApiClient()}
  261. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  262. group={group}
  263. location={{query: {environment: ['prod', 'staging']}}}
  264. />,
  265. {context: routerContext, organization}
  266. );
  267. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  268. const minidumpColumn = screen.queryByText('minidump');
  269. expect(minidumpColumn).not.toBeInTheDocument();
  270. });
  271. it('displays minidumps', async () => {
  272. attachmentsRequest = MockApiClient.addMockResponse({
  273. url: '/api/0/issues/1/attachments/?per_page=50&types=event.minidump&event_id=id123',
  274. body: [
  275. {
  276. id: 'id123',
  277. name: 'dc42a8b9-fc22-4de1-8a29-45b3006496d8.dmp',
  278. headers: {
  279. 'Content-Type': 'application/octet-stream',
  280. },
  281. mimetype: 'application/octet-stream',
  282. size: 1294340,
  283. sha1: '742127552a1191f71fcf6ba7bc5afa0a837350e2',
  284. dateCreated: '2022-09-28T09:04:38.659307Z',
  285. type: 'event.minidump',
  286. event_id: 'd54cb9246ee241ffbdb39bf7a9fafbb7',
  287. },
  288. ],
  289. });
  290. render(
  291. <GroupEvents
  292. organization={org.organization}
  293. api={new MockApiClient()}
  294. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  295. group={group}
  296. location={{query: {environment: ['prod', 'staging']}}}
  297. />,
  298. {context: routerContext, organization}
  299. );
  300. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  301. const minidumpColumn = screen.queryByText('minidump');
  302. expect(minidumpColumn).toBeInTheDocument();
  303. });
  304. it('does not display attachments but displays minidump', async () => {
  305. attachmentsRequest = MockApiClient.addMockResponse({
  306. url: '/api/0/issues/1/attachments/?per_page=50&types=event.minidump&event_id=id123',
  307. body: [
  308. {
  309. id: 'id123',
  310. name: 'dc42a8b9-fc22-4de1-8a29-45b3006496d8.dmp',
  311. headers: {
  312. 'Content-Type': 'application/octet-stream',
  313. },
  314. mimetype: 'application/octet-stream',
  315. size: 1294340,
  316. sha1: '742127552a1191f71fcf6ba7bc5afa0a837350e2',
  317. dateCreated: '2022-09-28T09:04:38.659307Z',
  318. type: 'event.minidump',
  319. event_id: 'd54cb9246ee241ffbdb39bf7a9fafbb7',
  320. },
  321. ],
  322. });
  323. render(
  324. <GroupEvents
  325. organization={org.organization}
  326. api={new MockApiClient()}
  327. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  328. group={group}
  329. location={{query: {environment: ['prod', 'staging']}}}
  330. />,
  331. {context: routerContext, organization}
  332. );
  333. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  334. const attachmentsColumn = screen.queryByText('attachments');
  335. const minidumpColumn = screen.queryByText('minidump');
  336. expect(attachmentsColumn).not.toBeInTheDocument();
  337. expect(minidumpColumn).toBeInTheDocument();
  338. expect(attachmentsRequest).toHaveBeenCalled();
  339. });
  340. it('renders new events table if error', function () {
  341. render(
  342. <GroupEvents
  343. organization={org.organization}
  344. api={new MockApiClient()}
  345. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  346. group={group}
  347. location={{query: {environment: ['prod', 'staging']}}}
  348. />,
  349. {context: routerContext, organization}
  350. );
  351. expect(discoverRequest).toHaveBeenCalledWith(
  352. '/organizations/org-slug/events/',
  353. expect.objectContaining({
  354. query: expect.objectContaining({
  355. query: 'issue.id:1 ',
  356. field: expect.not.arrayContaining(['attachments', 'minidump']),
  357. }),
  358. })
  359. );
  360. const perfEventsColumn = screen.getByText('transaction');
  361. expect(perfEventsColumn).toBeInTheDocument();
  362. });
  363. it('removes sort if unsupported by the events table', function () {
  364. render(
  365. <GroupEvents
  366. organization={org.organization}
  367. api={new MockApiClient()}
  368. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  369. group={group}
  370. location={{query: {environment: ['prod', 'staging'], sort: 'user'}}}
  371. />,
  372. {context: routerContext, organization}
  373. );
  374. expect(discoverRequest).toHaveBeenCalledWith(
  375. '/organizations/org-slug/events/',
  376. expect.objectContaining({query: expect.not.objectContaining({sort: 'user'})})
  377. );
  378. });
  379. it('only request for a single projectId', function () {
  380. render(
  381. <GroupEvents
  382. organization={org.organization}
  383. api={new MockApiClient()}
  384. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  385. group={group}
  386. location={{
  387. query: {
  388. environment: ['prod', 'staging'],
  389. sort: 'user',
  390. project: [group.project.id, '456'],
  391. },
  392. }}
  393. />,
  394. {context: routerContext, organization}
  395. );
  396. expect(discoverRequest).toHaveBeenCalledWith(
  397. '/organizations/org-slug/events/',
  398. expect.objectContaining({
  399. query: expect.objectContaining({project: [group.project.id]}),
  400. })
  401. );
  402. });
  403. it('shows discover query error message', async () => {
  404. discoverRequest = MockApiClient.addMockResponse({
  405. url: '/organizations/org-slug/events/',
  406. statusCode: 500,
  407. body: {
  408. detail: 'Internal Error',
  409. errorId: '69ab396e73704cdba9342ff8dcd59795',
  410. },
  411. });
  412. render(
  413. <GroupEvents
  414. organization={org.organization}
  415. api={new MockApiClient()}
  416. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  417. group={group}
  418. location={{query: {environment: ['prod', 'staging']}}}
  419. />,
  420. {context: routerContext, organization}
  421. );
  422. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  423. expect(screen.getByTestId('loading-error')).toHaveTextContent('Internal Error');
  424. });
  425. it('requests for backend columns if backend project', async () => {
  426. group.project.platform = 'node-express';
  427. render(
  428. <GroupEvents
  429. organization={org.organization}
  430. api={new MockApiClient()}
  431. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  432. group={group}
  433. location={{query: {environment: ['prod', 'staging']}}}
  434. />,
  435. {context: routerContext, organization}
  436. );
  437. await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator'));
  438. expect(discoverRequest).toHaveBeenCalledWith(
  439. '/organizations/org-slug/events/',
  440. expect.objectContaining({
  441. query: expect.objectContaining({
  442. field: expect.arrayContaining(['url', 'runtime']),
  443. }),
  444. })
  445. );
  446. expect(discoverRequest).toHaveBeenCalledWith(
  447. '/organizations/org-slug/events/',
  448. expect.objectContaining({
  449. query: expect.objectContaining({
  450. field: expect.not.arrayContaining(['browser']),
  451. }),
  452. })
  453. );
  454. });
  455. });
  456. it('does not renders new events table if error', function () {
  457. const org = initializeOrg();
  458. const group = TestStubs.Group();
  459. render(
  460. <GroupEvents
  461. organization={org.organization}
  462. api={new MockApiClient()}
  463. params={{orgId: 'orgId', projectId: 'projectId', groupId: '1'}}
  464. group={group}
  465. location={{query: {environment: ['prod', 'staging']}}}
  466. />,
  467. {context: routerContext, organization}
  468. );
  469. const perfEventsColumn = screen.queryByText('transaction');
  470. expect(perfEventsColumn).not.toBeInTheDocument();
  471. });
  472. });