content.spec.tsx 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {act, render, screen} from 'sentry-test/reactTestingLibrary';
  3. import {textWithMarkupMatcher} from 'sentry-test/utils';
  4. import {t} from 'sentry/locale';
  5. import ProjectsStore from 'sentry/stores/projectsStore';
  6. import EventView from 'sentry/utils/discover/eventView';
  7. import {
  8. SPAN_OP_BREAKDOWN_FIELDS,
  9. SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
  10. } from 'sentry/utils/discover/fields';
  11. import {WebVital} from 'sentry/utils/fields';
  12. import {OrganizationContext} from 'sentry/views/organizationContext';
  13. import {SpanOperationBreakdownFilter} from 'sentry/views/performance/transactionSummary/filter';
  14. import EventsPageContent from 'sentry/views/performance/transactionSummary/transactionEvents/content';
  15. import {EventsDisplayFilterName} from 'sentry/views/performance/transactionSummary/transactionEvents/utils';
  16. function initializeData() {
  17. const organization = TestStubs.Organization({
  18. features: ['discover-basic', 'performance-view'],
  19. projects: [TestStubs.Project()],
  20. apdexThreshold: 400,
  21. });
  22. const initialData = initializeOrg({
  23. organization,
  24. router: {
  25. location: {
  26. query: {
  27. transaction: '/performance',
  28. project: 1,
  29. transactionCursor: '1:0:0',
  30. },
  31. },
  32. },
  33. project: 1,
  34. projects: [],
  35. });
  36. act(() => void ProjectsStore.loadInitialData(initialData.organization.projects));
  37. return initialData;
  38. }
  39. describe('Performance Transaction Events Content', function () {
  40. let fields;
  41. let data;
  42. let transactionName;
  43. let eventView;
  44. let initialData;
  45. const query =
  46. 'transaction.duration:<15m event.type:transaction transaction:/api/0/organizations/{organization_slug}/events/';
  47. beforeEach(function () {
  48. transactionName = 'transactionName';
  49. fields = [
  50. 'id',
  51. 'user.display',
  52. SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
  53. 'transaction.duration',
  54. 'trace',
  55. 'timestamp',
  56. 'spans.total.time',
  57. ...SPAN_OP_BREAKDOWN_FIELDS,
  58. ];
  59. MockApiClient.addMockResponse({
  60. url: '/organizations/org-slug/projects/',
  61. body: [],
  62. });
  63. MockApiClient.addMockResponse({
  64. url: '/prompts-activity/',
  65. body: {},
  66. });
  67. MockApiClient.addMockResponse({
  68. url: '/organizations/org-slug/sdk-updates/',
  69. body: [],
  70. });
  71. data = [
  72. {
  73. id: 'deadbeef',
  74. 'user.display': 'uhoh@example.com',
  75. 'transaction.duration': 400,
  76. 'project.id': 1,
  77. timestamp: '2020-05-21T15:31:18+00:00',
  78. trace: '1234',
  79. 'span_ops_breakdown.relative': '',
  80. 'spans.browser': 100,
  81. 'spans.db': 30,
  82. 'spans.http': 170,
  83. 'spans.resource': 100,
  84. 'spans.total.time': 400,
  85. },
  86. {
  87. id: 'moredeadbeef',
  88. 'user.display': 'moreuhoh@example.com',
  89. 'transaction.duration': 600,
  90. 'project.id': 1,
  91. timestamp: '2020-05-22T15:31:18+00:00',
  92. trace: '4321',
  93. 'span_ops_breakdown.relative': '',
  94. 'spans.browser': 100,
  95. 'spans.db': 300,
  96. 'spans.http': 100,
  97. 'spans.resource': 100,
  98. 'spans.total.time': 600,
  99. },
  100. ];
  101. // Total events count response
  102. MockApiClient.addMockResponse({
  103. url: '/organizations/org-slug/events/',
  104. headers: {
  105. Link:
  106. '<http://localhost/api/0/organizations/org-slug/events/?cursor=2:0:0>; rel="next"; results="true"; cursor="2:0:0",' +
  107. '<http://localhost/api/0/organizations/org-slug/events/?cursor=1:0:0>; rel="previous"; results="false"; cursor="1:0:0"',
  108. },
  109. body: {
  110. meta: {
  111. fields: {
  112. 'count()': 'integer',
  113. },
  114. },
  115. data: [{'count()': 200}],
  116. },
  117. match: [
  118. (_url, options) => {
  119. return options.query?.field?.includes('count()');
  120. },
  121. ],
  122. });
  123. // Transaction list response
  124. MockApiClient.addMockResponse({
  125. url: '/organizations/org-slug/events/',
  126. headers: {
  127. Link:
  128. '<http://localhost/api/0/organizations/org-slug/events/?cursor=2:0:0>; rel="next"; results="true"; cursor="2:0:0",' +
  129. '<http://localhost/api/0/organizations/org-slug/events/?cursor=1:0:0>; rel="previous"; results="false"; cursor="1:0:0"',
  130. },
  131. body: {
  132. meta: {
  133. fields: {
  134. id: 'string',
  135. 'user.display': 'string',
  136. 'transaction.duration': 'duration',
  137. 'project.id': 'integer',
  138. timestamp: 'date',
  139. },
  140. },
  141. data,
  142. },
  143. match: [
  144. (_url, options) => {
  145. return options.query?.field?.includes('user.display');
  146. },
  147. ],
  148. });
  149. MockApiClient.addMockResponse({
  150. url: '/organizations/org-slug/events-has-measurements/',
  151. body: {measurements: false},
  152. });
  153. initialData = initializeData();
  154. eventView = EventView.fromNewQueryWithLocation(
  155. {
  156. id: undefined,
  157. version: 2,
  158. name: 'transactionName',
  159. fields,
  160. query,
  161. projects: [],
  162. orderby: '-timestamp',
  163. },
  164. initialData.router.location
  165. );
  166. });
  167. afterEach(function () {
  168. MockApiClient.clearMockResponses();
  169. ProjectsStore.reset();
  170. jest.clearAllMocks();
  171. });
  172. it('basic rendering', function () {
  173. render(
  174. <OrganizationContext.Provider value={initialData.organization}>
  175. <EventsPageContent
  176. eventView={eventView}
  177. organization={initialData.organization}
  178. location={initialData.router.location}
  179. transactionName={transactionName}
  180. spanOperationBreakdownFilter={SpanOperationBreakdownFilter.None}
  181. onChangeSpanOperationBreakdownFilter={() => {}}
  182. eventsDisplayFilterName={EventsDisplayFilterName.p100}
  183. onChangeEventsDisplayFilter={() => {}}
  184. setError={() => {}}
  185. projectId="123"
  186. projects={[]}
  187. />
  188. </OrganizationContext.Provider>,
  189. {context: initialData.routerContext}
  190. );
  191. expect(screen.getByTestId('events-table')).toBeInTheDocument();
  192. expect(screen.getByText(textWithMarkupMatcher('Percentilep100'))).toBeInTheDocument();
  193. expect(
  194. screen.getByPlaceholderText(t('Search for events, users, tags, and more'))
  195. ).toBeInTheDocument();
  196. expect(screen.getByRole('button', {name: 'Filter by operation'})).toBeInTheDocument();
  197. const columnTitles = screen
  198. .getAllByRole('columnheader')
  199. .map(elem => elem.textContent);
  200. expect(columnTitles).toEqual([
  201. t('event id'),
  202. t('user'),
  203. t('operation duration'),
  204. t('total duration'),
  205. t('trace id'),
  206. t('timestamp'),
  207. ]);
  208. });
  209. it('rendering with webvital selected', function () {
  210. render(
  211. <OrganizationContext.Provider value={initialData.organization}>
  212. <EventsPageContent
  213. eventView={eventView}
  214. organization={initialData.organization}
  215. location={initialData.router.location}
  216. transactionName={transactionName}
  217. spanOperationBreakdownFilter={SpanOperationBreakdownFilter.None}
  218. onChangeSpanOperationBreakdownFilter={() => {}}
  219. eventsDisplayFilterName={EventsDisplayFilterName.p100}
  220. onChangeEventsDisplayFilter={() => {}}
  221. webVital={WebVital.LCP}
  222. setError={() => {}}
  223. projectId="123"
  224. projects={[]}
  225. />
  226. </OrganizationContext.Provider>,
  227. {context: initialData.routerContext}
  228. );
  229. expect(screen.getByTestId('events-table')).toBeInTheDocument();
  230. expect(screen.getByText(textWithMarkupMatcher('Percentilep100'))).toBeInTheDocument();
  231. expect(
  232. screen.getByPlaceholderText(t('Search for events, users, tags, and more'))
  233. ).toBeInTheDocument();
  234. expect(screen.getByRole('button', {name: 'Filter by operation'})).toBeInTheDocument();
  235. const columnTitles = screen
  236. .getAllByRole('columnheader')
  237. .map(elem => elem.textContent);
  238. expect(columnTitles).toStrictEqual(expect.arrayContaining([t('measurements.lcp')]));
  239. });
  240. });