quickContext.spec.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. import {browserHistory} from 'react-router';
  2. import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
  3. import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
  4. import ConfigStore from 'sentry/stores/configStore';
  5. import {Commit, Repository, User} from 'sentry/types';
  6. import {
  7. EntryType,
  8. Event,
  9. EventError,
  10. EventOrGroupType,
  11. ExceptionType,
  12. ExceptionValue,
  13. Frame,
  14. } from 'sentry/types/event';
  15. import EventView, {EventData} from 'sentry/utils/discover/eventView';
  16. import {useLocation} from 'sentry/utils/useLocation';
  17. import {ContextType, QuickContextHoverWrapper} from './quickContext';
  18. const defaultRow: EventData = {
  19. id: '6b43e285de834ec5b5fe30d62d549b20',
  20. issue: 'SENTRY-VVY',
  21. release: 'backend@22.10.0+aaf33944f93dc8fa4234ca046a8d88fb1dccfb76',
  22. title: 'error: Error -3 while decompressing data: invalid stored block lengths',
  23. 'issue.id': 3512441874,
  24. 'project.name': 'sentry',
  25. };
  26. let mockedGroup = TestStubs.Group({
  27. id: '3512441874',
  28. project: {
  29. id: '1',
  30. slug: 'cool-team',
  31. },
  32. status: 'ignored',
  33. assignedTo: {
  34. id: '12312',
  35. name: 'ingest',
  36. type: 'team',
  37. },
  38. });
  39. const mockEventView = EventView.fromSavedQuery({
  40. id: '',
  41. name: 'test query',
  42. version: 2,
  43. fields: ['title', 'issue'],
  44. projects: [1],
  45. });
  46. const mockedCommit: Commit = {
  47. dateCreated: '2020-11-30T18:46:31Z',
  48. id: 'f7f395d14b2fe29a4e253bf1d3094d61e6ad4434',
  49. message: 'ref(commitRow): refactor to fc\n',
  50. author: {
  51. id: '0',
  52. username: 'author',
  53. ip_address: '192.168.1.1',
  54. email: 'author@commit.com',
  55. name: 'Author',
  56. } as User,
  57. repository: {
  58. id: '1',
  59. integrationId: '2',
  60. name: 'getsentry/sentry',
  61. dateCreated: '2019-11-30T18:46:31Z',
  62. } as Repository,
  63. releases: [],
  64. };
  65. const mockedUser1 = {
  66. id: '2',
  67. username: 'author456',
  68. ip_address: '192.168.1.1',
  69. email: 'author1@commit.com',
  70. name: 'Key Name',
  71. } as User;
  72. const mockedUser2 = {
  73. id: '3',
  74. username: 'author123',
  75. ip_address: '192.168.1.3',
  76. email: 'author2@commit.com',
  77. name: 'Value Name',
  78. } as User;
  79. const mockedReleaseWithHealth = TestStubs.Release({
  80. id: '1',
  81. shortVersion: 'sentry-android-shop@1.2.0',
  82. version: 'sentry-android-shop@1.2.0',
  83. dateCreated: '2010-05-17T02:41:20Z',
  84. lastEvent: '2011-10-17T02:41:20Z',
  85. firstEvent: '2010-05-17T02:41:20Z',
  86. status: 'open',
  87. commitCount: 4,
  88. lastCommit: mockedCommit,
  89. newGroups: 21,
  90. authors: [mockedUser1, mockedUser2],
  91. });
  92. const queryClient = new QueryClient();
  93. const renderQuickContextContent = (
  94. dataRow: EventData = defaultRow,
  95. contextType: ContextType = ContextType.ISSUE,
  96. eventView?: EventView
  97. ) => {
  98. const organization = TestStubs.Organization();
  99. render(
  100. <QueryClientProvider client={queryClient}>
  101. <QuickContextHoverWrapper
  102. dataRow={dataRow}
  103. contextType={contextType}
  104. organization={organization}
  105. eventView={eventView}
  106. >
  107. Text from Child
  108. </QuickContextHoverWrapper>
  109. </QueryClientProvider>,
  110. {organization}
  111. );
  112. };
  113. const makeEvent = (event: Partial<Event> = {}): Event => {
  114. const evt: Event = {
  115. ...TestStubs.Event(),
  116. ...event,
  117. };
  118. return evt;
  119. };
  120. jest.mock('sentry/utils/useLocation');
  121. const mockUseLocation = useLocation as jest.MockedFunction<typeof useLocation>;
  122. describe('Quick Context', function () {
  123. describe('Quick Context default behaviour', function () {
  124. beforeEach(() => {
  125. MockApiClient.addMockResponse({
  126. url: '/issues/3512441874/events/oldest/',
  127. method: 'GET',
  128. body: [],
  129. });
  130. });
  131. afterEach(() => {
  132. queryClient.clear();
  133. MockApiClient.clearMockResponses();
  134. mockUseLocation.mockReset();
  135. });
  136. it('Renders child', async () => {
  137. renderQuickContextContent();
  138. expect(await screen.findByText(/Text from Child/i)).toBeInTheDocument();
  139. });
  140. it('Renders quick context hover body', async () => {
  141. MockApiClient.addMockResponse({
  142. url: '/organizations/org-slug/users/',
  143. body: [],
  144. });
  145. MockApiClient.addMockResponse({
  146. url: '/projects/org-slug/cool-team/events/6b43e285de834ec5b5fe30d62d549b20/committers/',
  147. body: [],
  148. });
  149. MockApiClient.addMockResponse({
  150. url: '/issues/3512441874/',
  151. method: 'GET',
  152. body: mockedGroup,
  153. });
  154. renderQuickContextContent();
  155. userEvent.hover(screen.getByText('Text from Child'));
  156. expect(await screen.findByTestId('quick-context-hover-body')).toBeInTheDocument();
  157. });
  158. it('Renders quick context failure message', async () => {
  159. MockApiClient.addMockResponse({
  160. url: '/organizations/org-slug/users/',
  161. body: [],
  162. });
  163. MockApiClient.addMockResponse({
  164. url: '/projects/org-slug/cool-team/events/6b43e285de834ec5b5fe30d62d549b20/committers/',
  165. body: [],
  166. });
  167. MockApiClient.addMockResponse({
  168. url: '/issues/3512441874/',
  169. statusCode: 400,
  170. });
  171. renderQuickContextContent();
  172. userEvent.hover(screen.getByText('Text from Child'));
  173. // Error is expected, do not fail when calling console.error
  174. jest.spyOn(console, 'error').mockImplementation();
  175. expect(
  176. await screen.findByText(/Failed to load context for column./i)
  177. ).toBeInTheDocument();
  178. });
  179. });
  180. describe('Quick Context Content Issue Column', function () {
  181. beforeEach(() => {
  182. MockApiClient.addMockResponse({
  183. url: '/organizations/org-slug/users/',
  184. body: [],
  185. });
  186. MockApiClient.addMockResponse({
  187. url: '/projects/org-slug/cool-team/events/6b43e285de834ec5b5fe30d62d549b20/committers/',
  188. body: [],
  189. });
  190. MockApiClient.addMockResponse({
  191. url: '/issues/3512441874/events/oldest/',
  192. method: 'GET',
  193. body: [],
  194. });
  195. });
  196. afterEach(function () {
  197. queryClient.clear();
  198. MockApiClient.clearMockResponses();
  199. });
  200. it('Renders issue context header with copy button', async () => {
  201. MockApiClient.addMockResponse({
  202. url: '/issues/3512441874/',
  203. method: 'GET',
  204. body: mockedGroup,
  205. });
  206. renderQuickContextContent();
  207. userEvent.hover(screen.getByText('Text from Child'));
  208. expect(await screen.findByText(/Issue/i)).toBeInTheDocument();
  209. expect(screen.getByText(/SENTRY-VVY/i)).toBeInTheDocument();
  210. expect(
  211. screen.getByTestId('quick-context-hover-header-copy-icon')
  212. ).toBeInTheDocument();
  213. });
  214. it('Renders ignored Issue status context when data is loaded', async () => {
  215. MockApiClient.addMockResponse({
  216. url: '/issues/3512441874/',
  217. method: 'GET',
  218. body: mockedGroup,
  219. });
  220. renderQuickContextContent();
  221. userEvent.hover(screen.getByText('Text from Child'));
  222. expect(await screen.findByText(/Issue Status/i)).toBeInTheDocument();
  223. expect(screen.getByText(/Ignored/i)).toBeInTheDocument();
  224. expect(screen.getByTestId('quick-context-ignored-icon')).toBeInTheDocument();
  225. });
  226. it('Renders resolved Issue status context when data is loaded', async () => {
  227. mockedGroup = {...mockedGroup, status: 'resolved'};
  228. MockApiClient.addMockResponse({
  229. url: '/issues/3512441874/',
  230. method: 'GET',
  231. body: mockedGroup,
  232. });
  233. renderQuickContextContent();
  234. userEvent.hover(screen.getByText('Text from Child'));
  235. expect(await screen.findByText(/Issue Status/i)).toBeInTheDocument();
  236. expect(screen.getByText(/Resolved/i)).toBeInTheDocument();
  237. expect(screen.getByTestId('icon-check-mark')).toBeInTheDocument();
  238. });
  239. it('Renders unresolved Issue status context when data is loaded', async () => {
  240. mockedGroup = {...mockedGroup, status: 'unresolved'};
  241. MockApiClient.addMockResponse({
  242. url: '/issues/3512441874/',
  243. method: 'GET',
  244. body: mockedGroup,
  245. });
  246. renderQuickContextContent();
  247. userEvent.hover(screen.getByText('Text from Child'));
  248. expect(await screen.findByText(/Issue Status/i)).toBeInTheDocument();
  249. expect(screen.getByText(/Unresolved/i)).toBeInTheDocument();
  250. expect(screen.getByTestId('quick-context-unresolved-icon')).toBeInTheDocument();
  251. });
  252. it('Renders assigned To context when data is loaded', async () => {
  253. MockApiClient.addMockResponse({
  254. url: '/issues/3512441874/',
  255. method: 'GET',
  256. body: mockedGroup,
  257. });
  258. renderQuickContextContent();
  259. userEvent.hover(screen.getByText('Text from Child'));
  260. expect(await screen.findByText(/Assigned To/i)).toBeInTheDocument();
  261. expect(screen.getByText(/#ingest/i)).toBeInTheDocument();
  262. });
  263. it('Renders Suspect Commits', async () => {
  264. MockApiClient.addMockResponse({
  265. url: '/issues/3512441874/',
  266. method: 'GET',
  267. body: mockedGroup,
  268. });
  269. MockApiClient.addMockResponse({
  270. url: '/issues/3512441874/events/oldest/',
  271. method: 'GET',
  272. body: {
  273. eventID: '6b43e285de834ec5b5fe30d62d549b20',
  274. },
  275. });
  276. MockApiClient.addMockResponse({
  277. method: 'GET',
  278. url: '/projects/org-slug/cool-team/events/6b43e285de834ec5b5fe30d62d549b20/committers/',
  279. body: {
  280. committers: [
  281. {
  282. author: {name: 'Max Bittker', id: '1'},
  283. commits: [
  284. {
  285. message: 'feat: Added new feature',
  286. score: 4,
  287. id: 'ab2709293d0c9000829084ac7b1c9221fb18437c',
  288. repository: TestStubs.Repository(),
  289. dateCreated: '2018-03-02T18:30:26Z',
  290. pullRequest: {
  291. externalUrl: 'url',
  292. },
  293. },
  294. ],
  295. },
  296. ],
  297. },
  298. });
  299. renderQuickContextContent();
  300. userEvent.hover(screen.getByText('Text from Child'));
  301. expect(await screen.findByText(/Suspect Commits/i)).toBeInTheDocument();
  302. expect(screen.getByText(/MB/i)).toBeInTheDocument();
  303. expect(screen.getByText(/View commit/i)).toBeInTheDocument();
  304. expect(screen.getByText(/by/i)).toBeInTheDocument();
  305. expect(screen.getByText(/You/i)).toBeInTheDocument();
  306. });
  307. });
  308. describe('Quick Context Content Release Column', function () {
  309. afterEach(() => {
  310. queryClient.clear();
  311. MockApiClient.clearMockResponses();
  312. });
  313. it('Renders release header with copy button', async () => {
  314. MockApiClient.addMockResponse({
  315. url: '/organizations/org-slug/releases/backend@22.10.0+aaf33944f93dc8fa4234ca046a8d88fb1dccfb76/',
  316. body: mockedReleaseWithHealth,
  317. });
  318. renderQuickContextContent(defaultRow, ContextType.RELEASE);
  319. userEvent.hover(screen.getByText('Text from Child'));
  320. expect(await screen.findByText(/Release/i)).toBeInTheDocument();
  321. expect(screen.getByText(/22.10.0/i)).toBeInTheDocument();
  322. expect(screen.getByText(/(aaf33944f93d)/i)).toBeInTheDocument();
  323. expect(
  324. screen.getByTestId('quick-context-hover-header-copy-icon')
  325. ).toBeInTheDocument();
  326. });
  327. it('Renders Release details for release', async () => {
  328. MockApiClient.addMockResponse({
  329. url: '/organizations/org-slug/releases/backend@22.10.0+aaf33944f93dc8fa4234ca046a8d88fb1dccfb76/',
  330. body: mockedReleaseWithHealth,
  331. });
  332. renderQuickContextContent(defaultRow, ContextType.RELEASE);
  333. userEvent.hover(screen.getByText('Text from Child'));
  334. expect(await screen.findByText(/Created/i)).toBeInTheDocument();
  335. expect(screen.getByText(/7 years ago/i)).toBeInTheDocument();
  336. expect(screen.getByText(/Last Event/i)).toBeInTheDocument();
  337. expect(screen.getByText(/6 years ago/i)).toBeInTheDocument();
  338. expect(screen.getByText(/New Issues/i)).toBeInTheDocument();
  339. expect(screen.getByText(/21/i)).toBeInTheDocument();
  340. });
  341. it('Renders Last Commit', async () => {
  342. MockApiClient.addMockResponse({
  343. url: '/organizations/org-slug/releases/backend@22.10.0+aaf33944f93dc8fa4234ca046a8d88fb1dccfb76/',
  344. body: mockedReleaseWithHealth,
  345. });
  346. renderQuickContextContent(defaultRow, ContextType.RELEASE);
  347. userEvent.hover(screen.getByText('Text from Child'));
  348. expect(await screen.findByText(/Last Commit/i)).toBeInTheDocument();
  349. expect(screen.getByTestId('quick-context-commit-row')).toBeInTheDocument();
  350. });
  351. it('Renders Commit Count and Author when user is NOT in list of authors', async () => {
  352. MockApiClient.addMockResponse({
  353. url: '/organizations/org-slug/releases/backend@22.10.0+aaf33944f93dc8fa4234ca046a8d88fb1dccfb76/',
  354. body: mockedReleaseWithHealth,
  355. });
  356. renderQuickContextContent(defaultRow, ContextType.RELEASE);
  357. userEvent.hover(screen.getByText('Text from Child'));
  358. const authorsSectionHeader = within(
  359. await screen.findByTestId('quick-context-release-author-header')
  360. );
  361. expect(authorsSectionHeader.getByText(/4/i)).toBeInTheDocument();
  362. expect(authorsSectionHeader.getByText(/commits by/i)).toBeInTheDocument();
  363. expect(authorsSectionHeader.getByText(/2/i)).toBeInTheDocument();
  364. expect(authorsSectionHeader.getByText(/authors/i)).toBeInTheDocument();
  365. expect(screen.getByText(/KN/i)).toBeInTheDocument();
  366. expect(screen.getByText(/VN/i)).toBeInTheDocument();
  367. });
  368. it('Renders Commit Count and Author when user is in list of authors', async () => {
  369. jest.spyOn(ConfigStore, 'get').mockImplementation(() => mockedUser1);
  370. MockApiClient.addMockResponse({
  371. url: '/organizations/org-slug/releases/backend@22.10.0+aaf33944f93dc8fa4234ca046a8d88fb1dccfb76/',
  372. body: mockedReleaseWithHealth,
  373. });
  374. renderQuickContextContent(defaultRow, ContextType.RELEASE);
  375. userEvent.hover(screen.getByText('Text from Child'));
  376. expect(await screen.findByText(/4/i)).toBeInTheDocument();
  377. expect(screen.getByText(/commits by you and 1 other/i)).toBeInTheDocument();
  378. expect(screen.getByText(/KN/i)).toBeInTheDocument();
  379. expect(screen.getByText(/VN/i)).toBeInTheDocument();
  380. });
  381. });
  382. describe('Quick Context Content: Event ID Column', function () {
  383. it('Renders transaction duration context', async () => {
  384. const currentTime = Date.now();
  385. mockUseLocation.mockReturnValueOnce(
  386. TestStubs.location({
  387. query: {
  388. field: 'title',
  389. },
  390. })
  391. );
  392. MockApiClient.addMockResponse({
  393. url: '/organizations/org-slug/events/sentry:6b43e285de834ec5b5fe30d62d549b20/',
  394. body: makeEvent({
  395. type: EventOrGroupType.TRANSACTION,
  396. entries: [],
  397. endTimestamp: currentTime,
  398. startTimestamp: currentTime - 2,
  399. }),
  400. });
  401. renderQuickContextContent(defaultRow, ContextType.EVENT);
  402. userEvent.hover(screen.getByText('Text from Child'));
  403. expect(await screen.findByText(/Transaction Duration/i)).toBeInTheDocument();
  404. expect(screen.getByText(/2.00s/i)).toBeInTheDocument();
  405. const addAsColumnButton = screen.getByTestId(
  406. 'quick-context-transaction-duration-add-button'
  407. );
  408. expect(addAsColumnButton).toBeInTheDocument();
  409. userEvent.click(addAsColumnButton);
  410. expect(browserHistory.push).toHaveBeenCalledWith(
  411. expect.objectContaining({
  412. pathname: '/mock-pathname/',
  413. query: expect.objectContaining({
  414. field: ['title', 'transaction.duration'],
  415. }),
  416. })
  417. );
  418. });
  419. it('Renders transaction status context', async () => {
  420. const currentTime = Date.now();
  421. mockUseLocation.mockReturnValueOnce(
  422. TestStubs.location({
  423. query: {
  424. field: 'title',
  425. },
  426. })
  427. );
  428. MockApiClient.addMockResponse({
  429. url: '/organizations/org-slug/events/sentry:6b43e285de834ec5b5fe30d62d549b20/',
  430. body: makeEvent({
  431. type: EventOrGroupType.TRANSACTION,
  432. entries: [],
  433. endTimestamp: currentTime,
  434. startTimestamp: currentTime - 2,
  435. contexts: {
  436. trace: {
  437. status: 'ok',
  438. },
  439. },
  440. tags: [
  441. {
  442. key: 'http.status_code',
  443. value: '200',
  444. },
  445. ],
  446. }),
  447. });
  448. renderQuickContextContent(defaultRow, ContextType.EVENT);
  449. userEvent.hover(screen.getByText('Text from Child'));
  450. expect(await screen.findByText(/Status/i)).toBeInTheDocument();
  451. expect(screen.getByText(/ok/i)).toBeInTheDocument();
  452. expect(screen.getByText(/HTTP 200/i)).toBeInTheDocument();
  453. const addAsColumnButton = screen.getByTestId(
  454. 'quick-context-http-status-add-button'
  455. );
  456. expect(addAsColumnButton).toBeInTheDocument();
  457. userEvent.click(addAsColumnButton);
  458. expect(browserHistory.push).toHaveBeenCalledWith(
  459. expect.objectContaining({
  460. pathname: '/mock-pathname/',
  461. query: expect.objectContaining({
  462. field: ['title', 'http.status_code'],
  463. }),
  464. })
  465. );
  466. });
  467. it('Adds columns for saved query', async () => {
  468. const currentTime = Date.now();
  469. mockUseLocation.mockReturnValueOnce(
  470. TestStubs.location({
  471. query: {
  472. field: null,
  473. },
  474. })
  475. );
  476. MockApiClient.addMockResponse({
  477. url: '/organizations/org-slug/events/sentry:6b43e285de834ec5b5fe30d62d549b20/',
  478. body: makeEvent({
  479. type: EventOrGroupType.TRANSACTION,
  480. entries: [],
  481. endTimestamp: currentTime,
  482. startTimestamp: currentTime - 2,
  483. }),
  484. });
  485. renderQuickContextContent(defaultRow, ContextType.EVENT, mockEventView);
  486. userEvent.hover(screen.getByText('Text from Child'));
  487. const addAsColumnButton = await screen.findByTestId(
  488. 'quick-context-transaction-duration-add-button'
  489. );
  490. expect(addAsColumnButton).toBeInTheDocument();
  491. userEvent.click(addAsColumnButton);
  492. expect(browserHistory.push).toHaveBeenCalledWith(
  493. expect.objectContaining({
  494. pathname: '/mock-pathname/',
  495. query: expect.objectContaining({
  496. field: ['title', 'issue', 'transaction.duration'],
  497. }),
  498. })
  499. );
  500. });
  501. it('Renders NO stack trace message for error events without stackTraces', async () => {
  502. jest.spyOn(ConfigStore, 'get').mockImplementation(() => null);
  503. MockApiClient.addMockResponse({
  504. url: '/organizations/org-slug/events/sentry:6b43e285de834ec5b5fe30d62d549b20/',
  505. body: makeEvent({type: EventOrGroupType.ERROR, entries: []}),
  506. });
  507. renderQuickContextContent(defaultRow, ContextType.EVENT);
  508. userEvent.hover(screen.getByText('Text from Child'));
  509. expect(
  510. await screen.findByText(/There is no stack trace available for this event./i)
  511. ).toBeInTheDocument();
  512. });
  513. it('Renders event id header', async () => {
  514. jest.spyOn(ConfigStore, 'get').mockImplementation(() => null);
  515. MockApiClient.addMockResponse({
  516. url: '/organizations/org-slug/events/sentry:6b43e285de834ec5b5fe30d62d549b20/',
  517. body: makeEvent({type: EventOrGroupType.ERROR, entries: []}),
  518. });
  519. renderQuickContextContent(defaultRow, ContextType.EVENT);
  520. userEvent.hover(screen.getByText('Text from Child'));
  521. expect(await screen.findByText(/Event ID/i)).toBeInTheDocument();
  522. expect(screen.getByText(/6b43e285/i)).toBeInTheDocument();
  523. expect(
  524. screen.getByTestId('quick-context-hover-header-copy-icon')
  525. ).toBeInTheDocument();
  526. });
  527. it('Renders stack trace as context', async () => {
  528. const frame: Frame = {
  529. colNo: 0,
  530. filename: 'file.js',
  531. function: 'throwError',
  532. lineNo: 0,
  533. absPath: null,
  534. context: [],
  535. errors: null,
  536. inApp: false,
  537. instructionAddr: null,
  538. module: null,
  539. package: null,
  540. platform: null,
  541. rawFunction: null,
  542. symbol: null,
  543. symbolAddr: null,
  544. trust: undefined,
  545. vars: null,
  546. };
  547. const thread: ExceptionValue = {
  548. stacktrace: {
  549. hasSystemFrames: false,
  550. registers: {},
  551. framesOmitted: 0,
  552. frames: [frame],
  553. },
  554. mechanism: null,
  555. module: null,
  556. rawStacktrace: null,
  557. threadId: null,
  558. type: '',
  559. value: '',
  560. };
  561. const exceptionValue: ExceptionType = {
  562. values: [thread],
  563. excOmitted: undefined,
  564. hasSystemFrames: false,
  565. };
  566. const errorEvent: Event = {
  567. id: '6b43e285de834ec5b5fe30d62d549b20',
  568. type: EventOrGroupType.ERROR,
  569. entries: [
  570. {
  571. type: EntryType.EXCEPTION,
  572. data: exceptionValue,
  573. },
  574. ],
  575. } as EventError;
  576. mockUseLocation.mockReturnValue(
  577. TestStubs.location({
  578. query: {
  579. field: ['issue', 'transaction.duration'],
  580. },
  581. })
  582. );
  583. MockApiClient.addMockResponse({
  584. url: '/organizations/org-slug/events/sentry:6b43e285de834ec5b5fe30d62d549b20/',
  585. body: makeEvent(errorEvent),
  586. });
  587. const dataRow = {
  588. ...defaultRow,
  589. };
  590. delete dataRow.title;
  591. renderQuickContextContent(dataRow, ContextType.EVENT);
  592. userEvent.hover(screen.getByText('Text from Child'));
  593. expect(await screen.findByTestId('stack-trace-content')).toBeInTheDocument();
  594. const addAsColumnButton = screen.getByTestId('quick-context-title-add-button');
  595. expect(addAsColumnButton).toBeInTheDocument();
  596. expect(screen.getByText(/Title/i)).toBeInTheDocument();
  597. userEvent.click(addAsColumnButton);
  598. expect(browserHistory.push).toHaveBeenCalledWith(
  599. expect.objectContaining({
  600. pathname: '/mock-pathname/',
  601. query: expect.objectContaining({
  602. field: ['issue', 'transaction.duration', 'title'],
  603. }),
  604. })
  605. );
  606. });
  607. });
  608. });