traceTimeline.spec.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import {EventFixture} from 'sentry-fixture/event';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {ProjectFixture} from 'sentry-fixture/project';
  4. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  5. import ProjectsStore from 'sentry/stores/projectsStore';
  6. import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
  7. import {TraceTimeline} from './traceTimeline';
  8. import type {TraceEventResponse} from './useTraceTimelineEvents';
  9. jest.mock('sentry/utils/routeAnalytics/useRouteAnalyticsParams');
  10. describe('TraceTimeline', () => {
  11. const organization = OrganizationFixture();
  12. const event = EventFixture({
  13. dateCreated: '2024-01-24T09:09:03+00:00',
  14. contexts: {
  15. trace: {
  16. trace_id: '123',
  17. },
  18. },
  19. });
  20. const project = ProjectFixture();
  21. const emptyBody: TraceEventResponse = {data: [], meta: {fields: {}, units: {}}};
  22. const issuePlatformBody: TraceEventResponse = {
  23. data: [
  24. {
  25. timestamp: '2024-01-24T09:09:03+00:00',
  26. 'issue.id': 1000,
  27. project: project.slug,
  28. 'project.name': project.name,
  29. title: 'Slow DB Query',
  30. id: 'abc',
  31. transaction: '/api/slow/',
  32. },
  33. ],
  34. meta: {fields: {}, units: {}},
  35. };
  36. const discoverBody: TraceEventResponse = {
  37. data: [
  38. {
  39. timestamp: '2024-01-23T22:11:42+00:00',
  40. 'issue.id': 4909507143,
  41. project: project.slug,
  42. 'project.name': project.name,
  43. title: 'AttributeError: Something Failed',
  44. id: event.id,
  45. transaction: 'important.task',
  46. 'event.type': 'error',
  47. 'stack.function': ['important.task', 'task.run'],
  48. },
  49. ],
  50. meta: {fields: {}, units: {}},
  51. };
  52. beforeEach(() => {
  53. ProjectsStore.loadInitialData([project]);
  54. jest.clearAllMocks();
  55. });
  56. it('renders items and highlights the current event', async () => {
  57. MockApiClient.addMockResponse({
  58. url: `/organizations/${organization.slug}/events/`,
  59. body: issuePlatformBody,
  60. match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
  61. });
  62. MockApiClient.addMockResponse({
  63. url: `/organizations/${organization.slug}/events/`,
  64. body: discoverBody,
  65. match: [MockApiClient.matchQuery({dataset: 'discover'})],
  66. });
  67. render(<TraceTimeline event={event} />, {organization});
  68. expect(await screen.findByLabelText('Current Event')).toBeInTheDocument();
  69. await userEvent.hover(screen.getByTestId('trace-timeline-tooltip-1'));
  70. expect(await screen.findByText('You are here')).toBeInTheDocument();
  71. expect(useRouteAnalyticsParams).toHaveBeenCalledWith({
  72. trace_timeline_status: 'shown',
  73. });
  74. });
  75. it('displays nothing if the only event is the current event', async () => {
  76. MockApiClient.addMockResponse({
  77. url: `/organizations/${organization.slug}/events/`,
  78. body: emptyBody,
  79. match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
  80. });
  81. MockApiClient.addMockResponse({
  82. url: `/organizations/${organization.slug}/events/`,
  83. body: discoverBody,
  84. match: [MockApiClient.matchQuery({dataset: 'discover'})],
  85. });
  86. const {container} = render(<TraceTimeline event={event} />, {organization});
  87. await waitFor(() =>
  88. expect(useRouteAnalyticsParams).toHaveBeenCalledWith({
  89. trace_timeline_status: 'empty',
  90. })
  91. );
  92. expect(container).toBeEmptyDOMElement();
  93. });
  94. it('displays nothing if there are no events', async () => {
  95. MockApiClient.addMockResponse({
  96. url: `/organizations/${organization.slug}/events/`,
  97. body: emptyBody,
  98. match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
  99. });
  100. MockApiClient.addMockResponse({
  101. url: `/organizations/${organization.slug}/events/`,
  102. body: emptyBody,
  103. match: [MockApiClient.matchQuery({dataset: 'discover'})],
  104. });
  105. const {container} = render(<TraceTimeline event={event} />, {organization});
  106. await waitFor(() =>
  107. expect(useRouteAnalyticsParams).toHaveBeenCalledWith({
  108. trace_timeline_status: 'empty',
  109. })
  110. );
  111. expect(container).toBeEmptyDOMElement();
  112. });
  113. it('shows seconds for very short timelines', async () => {
  114. MockApiClient.addMockResponse({
  115. url: `/organizations/${organization.slug}/events/`,
  116. body: issuePlatformBody,
  117. match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
  118. });
  119. MockApiClient.addMockResponse({
  120. url: `/organizations/${organization.slug}/events/`,
  121. body: emptyBody,
  122. match: [MockApiClient.matchQuery({dataset: 'discover'})],
  123. });
  124. render(<TraceTimeline event={event} />, {organization});
  125. // Checking for the presence of seconds
  126. expect(await screen.findAllByText(/\d{1,2}:\d{2}:\d{2} (AM|PM)/)).toHaveLength(5);
  127. });
  128. it('adds the current event if not in the api response', async () => {
  129. MockApiClient.addMockResponse({
  130. url: `/organizations/${organization.slug}/events/`,
  131. body: issuePlatformBody,
  132. match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
  133. });
  134. MockApiClient.addMockResponse({
  135. url: `/organizations/${organization.slug}/events/`,
  136. body: emptyBody,
  137. match: [MockApiClient.matchQuery({dataset: 'discover'})],
  138. });
  139. render(<TraceTimeline event={event} />, {organization});
  140. expect(await screen.findByLabelText('Current Event')).toBeInTheDocument();
  141. });
  142. it('skips the timeline and shows related issues (2 issues)', async () => {
  143. MockApiClient.addMockResponse({
  144. url: `/organizations/${organization.slug}/events/`,
  145. body: issuePlatformBody,
  146. match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
  147. });
  148. MockApiClient.addMockResponse({
  149. url: `/organizations/${organization.slug}/events/`,
  150. body: discoverBody,
  151. match: [MockApiClient.matchQuery({dataset: 'discover'})],
  152. });
  153. // I believe the call to projects is to determine what projects a user belongs to
  154. MockApiClient.addMockResponse({
  155. url: `/organizations/${organization.slug}/projects/`,
  156. body: [],
  157. });
  158. render(<TraceTimeline event={event} />, {
  159. organization: OrganizationFixture({
  160. features: ['related-issues-issue-details-page'],
  161. }),
  162. });
  163. // Instead of a timeline, we should see related issues
  164. expect(await screen.findByText('Slow DB Query')).toBeInTheDocument();
  165. expect(
  166. await screen.findByText('AttributeError: Something Failed')
  167. ).toBeInTheDocument();
  168. expect(screen.queryByLabelText('Current Event')).not.toBeInTheDocument();
  169. expect(useRouteAnalyticsParams).toHaveBeenCalledWith({
  170. trace_timeline_status: 'empty',
  171. });
  172. let element = await screen.getByTestId('this-event-1');
  173. expect(element).toHaveTextContent('This event');
  174. element = await screen.getByTestId('this-event-abc');
  175. expect(element).toHaveTextContent('');
  176. });
  177. it('skips the timeline and shows NO related issues (only 1 issue)', async () => {
  178. MockApiClient.addMockResponse({
  179. url: `/organizations/${organization.slug}/events/`,
  180. body: emptyBody,
  181. match: [MockApiClient.matchQuery({dataset: 'issuePlatform'})],
  182. });
  183. MockApiClient.addMockResponse({
  184. url: `/organizations/${organization.slug}/events/`,
  185. // Only 1 issue
  186. body: discoverBody,
  187. match: [MockApiClient.matchQuery({dataset: 'discover'})],
  188. });
  189. // I believe the call to projects is to determine what projects a user belongs to
  190. MockApiClient.addMockResponse({
  191. url: `/organizations/${organization.slug}/projects/`,
  192. body: [],
  193. });
  194. render(<TraceTimeline event={event} />, {
  195. organization: OrganizationFixture({
  196. features: ['related-issues-issue-details-page'],
  197. }),
  198. });
  199. // We do not display any related issues because we only have 1 issue
  200. expect(await screen.queryByText('Slow DB Query')).not.toBeInTheDocument();
  201. expect(
  202. await screen.queryByText('AttributeError: Something Failed')
  203. ).not.toBeInTheDocument();
  204. // We do not display the timeline because we only have 1 event
  205. expect(await screen.queryByLabelText('Current Event')).not.toBeInTheDocument();
  206. expect(useRouteAnalyticsParams).toHaveBeenCalledWith({
  207. trace_timeline_status: 'empty',
  208. });
  209. });
  210. });