transactionEvents.spec.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {render, screen} from 'sentry-test/reactTestingLibrary';
  3. import ProjectsStore from 'sentry/stores/projectsStore';
  4. import {WebVital} from 'sentry/utils/fields';
  5. import TransactionEvents from 'sentry/views/performance/transactionSummary/transactionEvents';
  6. // XXX(epurkhiser): This appears to also be tested by ./transactionSummary/transactionEvents/index.spec.tsx
  7. type Data = {
  8. features?: string[];
  9. query?: {
  10. webVital?: WebVital;
  11. };
  12. };
  13. function initializeData({features: additionalFeatures = [], query = {}}: Data = {}) {
  14. const features = ['discover-basic', 'performance-view', ...additionalFeatures];
  15. const organization = TestStubs.Organization({
  16. features,
  17. projects: [TestStubs.Project()],
  18. apdexThreshold: 400,
  19. });
  20. return initializeOrg({
  21. organization,
  22. router: {
  23. location: {
  24. query: {
  25. transaction: '/performance',
  26. project: '1',
  27. transactionCursor: '1:0:0',
  28. ...query,
  29. },
  30. },
  31. },
  32. project: 1,
  33. projects: [],
  34. });
  35. }
  36. describe('Performance > TransactionSummary', function () {
  37. beforeAll(function () {
  38. // @ts-ignore no-console
  39. // eslint-disable-next-line no-console
  40. jest.spyOn(console, 'error').mockImplementation(jest.fn());
  41. MockApiClient.addMockResponse({
  42. url: '/organizations/org-slug/projects/',
  43. body: [],
  44. });
  45. MockApiClient.addMockResponse({
  46. url: '/prompts-activity/',
  47. body: {},
  48. });
  49. MockApiClient.addMockResponse({
  50. url: '/organizations/org-slug/sdk-updates/',
  51. body: [],
  52. });
  53. MockApiClient.addMockResponse({
  54. url: '/organizations/org-slug/events/',
  55. body: {
  56. data: [
  57. {
  58. 'p100()': 9502,
  59. 'p99()': 9285.7,
  60. 'p95()': 7273.6,
  61. 'p75()': 3639.5,
  62. 'p50()': 755.5,
  63. },
  64. ],
  65. meta: {
  66. fields: {
  67. 'p100()': 'duration',
  68. 'p99()': 'duration',
  69. 'p95()': 'duration',
  70. 'p75()': 'duration',
  71. 'p50()': 'duration',
  72. },
  73. },
  74. },
  75. match: [
  76. (_, options) => {
  77. return options.query?.field?.includes('p95()');
  78. },
  79. ],
  80. });
  81. // Transaction list response
  82. MockApiClient.addMockResponse({
  83. url: '/organizations/org-slug/events/',
  84. headers: {
  85. Link:
  86. '<http://localhost/api/0/organizations/org-slug/events/?cursor=2:0:0>; rel="next"; results="true"; cursor="2:0:0",' +
  87. '<http://localhost/api/0/organizations/org-slug/events/?cursor=1:0:0>; rel="previous"; results="false"; cursor="1:0:0"',
  88. },
  89. body: {
  90. meta: {
  91. fields: {
  92. id: 'string',
  93. 'user.display': 'string',
  94. 'transaction.duration': 'duration',
  95. 'project.id': 'integer',
  96. timestamp: 'date',
  97. },
  98. },
  99. data: [
  100. {
  101. id: 'deadbeef',
  102. 'user.display': 'uhoh@example.com',
  103. 'transaction.duration': 400,
  104. 'project.id': 1,
  105. timestamp: '2020-05-21T15:31:18+00:00',
  106. trace: '1234',
  107. 'measurements.lcp': 200,
  108. },
  109. {
  110. id: 'moredeadbeef',
  111. 'user.display': 'moreuhoh@example.com',
  112. 'transaction.duration': 600,
  113. 'project.id': 1,
  114. timestamp: '2020-05-22T15:31:18+00:00',
  115. trace: '4321',
  116. 'measurements.lcp': 300,
  117. },
  118. ],
  119. },
  120. match: [
  121. (_url, options) => {
  122. return options.query?.field?.includes('user.display');
  123. },
  124. ],
  125. });
  126. MockApiClient.addMockResponse({
  127. url: '/organizations/org-slug/events/',
  128. body: {
  129. data: [{'count()': 5161}],
  130. },
  131. match: [
  132. (_url, options) => {
  133. return options.query?.field?.includes('count()');
  134. },
  135. ],
  136. });
  137. MockApiClient.addMockResponse({
  138. url: '/organizations/org-slug/events-has-measurements/',
  139. body: {measurements: false},
  140. });
  141. });
  142. it('renders basic UI elements', async function () {
  143. const {organization, router, routerContext} = initializeData();
  144. ProjectsStore.loadInitialData(organization.projects);
  145. render(<TransactionEvents organization={organization} location={router.location} />, {
  146. context: routerContext,
  147. });
  148. // Breadcrumb
  149. expect(screen.getByRole('link', {name: 'Performance'})).toHaveAttribute(
  150. 'href',
  151. '/organizations/org-slug/performance/?project=1&transactionCursor=1%3A0%3A0'
  152. );
  153. // Header
  154. expect(screen.getByRole('heading', {name: '/performance'})).toBeInTheDocument();
  155. expect(
  156. await screen.findByRole('textbox', {name: 'Search events'})
  157. ).toBeInTheDocument();
  158. expect(screen.getByRole('button', {name: 'Next'})).toBeInTheDocument();
  159. expect(screen.getByRole('button', {name: 'Previous'})).toBeInTheDocument();
  160. expect(screen.getByRole('table')).toBeInTheDocument();
  161. expect(screen.getByRole('tab', {name: 'Overview'})).toBeInTheDocument();
  162. expect(screen.getByRole('tab', {name: 'All Events'})).toBeInTheDocument();
  163. expect(screen.getByRole('tab', {name: 'Tags'})).toBeInTheDocument();
  164. ProjectsStore.reset();
  165. });
  166. it('renders relative span breakdown header when no filter selected', async function () {
  167. const {organization, router, routerContext} = initializeData();
  168. ProjectsStore.loadInitialData(organization.projects);
  169. render(<TransactionEvents organization={organization} location={router.location} />, {
  170. context: routerContext,
  171. });
  172. expect(await screen.findByText('operation duration')).toBeInTheDocument();
  173. expect(screen.getAllByRole('columnheader')).toHaveLength(6);
  174. ProjectsStore.reset();
  175. });
  176. it('renders event column results correctly', async function () {
  177. const {organization, router, routerContext} = initializeData();
  178. ProjectsStore.loadInitialData(organization.projects);
  179. render(<TransactionEvents organization={organization} location={router.location} />, {
  180. context: routerContext,
  181. });
  182. const tableHeader = await screen.findAllByRole('columnheader');
  183. expect(tableHeader).toHaveLength(6);
  184. expect(tableHeader[0]).toHaveTextContent('event id');
  185. expect(tableHeader[1]).toHaveTextContent('user');
  186. expect(tableHeader[2]).toHaveTextContent('operation duration');
  187. expect(tableHeader[3]).toHaveTextContent('total duration');
  188. expect(tableHeader[4]).toHaveTextContent('trace id');
  189. expect(tableHeader[5]).toHaveTextContent('timestamp');
  190. const tableFirstRowColumns = screen.getAllByRole('cell');
  191. expect(tableFirstRowColumns[0]).toHaveTextContent('deadbeef');
  192. expect(tableFirstRowColumns[1]).toHaveTextContent('Uuhoh@example.com');
  193. expect(tableFirstRowColumns[2]).toHaveTextContent('(no value)');
  194. expect(tableFirstRowColumns[3]).toHaveTextContent('400.00ms');
  195. expect(tableFirstRowColumns[4]).toHaveTextContent('1234');
  196. expect(tableFirstRowColumns[5]).toHaveTextContent('May 21, 2020 3:31:18 PM UTC');
  197. ProjectsStore.reset();
  198. });
  199. it('renders additional Web Vital column', async function () {
  200. const {organization, router, routerContext} = initializeData({
  201. query: {webVital: WebVital.LCP},
  202. });
  203. ProjectsStore.loadInitialData(organization.projects);
  204. render(<TransactionEvents organization={organization} location={router.location} />, {
  205. context: routerContext,
  206. });
  207. const tableHeader = await screen.findAllByRole('columnheader');
  208. expect(tableHeader).toHaveLength(7);
  209. expect(tableHeader[0]).toHaveTextContent('event id');
  210. expect(tableHeader[1]).toHaveTextContent('user');
  211. expect(tableHeader[2]).toHaveTextContent('operation duration');
  212. expect(tableHeader[3]).toHaveTextContent('measurements.lcp');
  213. expect(tableHeader[4]).toHaveTextContent('total duration');
  214. expect(tableHeader[5]).toHaveTextContent('trace id');
  215. expect(tableHeader[6]).toHaveTextContent('timestamp');
  216. const tableFirstRowColumns = screen.getAllByRole('cell');
  217. expect(tableFirstRowColumns[0]).toHaveTextContent('deadbeef');
  218. expect(tableFirstRowColumns[1]).toHaveTextContent('Uuhoh@example.com');
  219. expect(tableFirstRowColumns[2]).toHaveTextContent('(no value)');
  220. expect(tableFirstRowColumns[3]).toHaveTextContent('200');
  221. expect(tableFirstRowColumns[4]).toHaveTextContent('400.00ms');
  222. expect(tableFirstRowColumns[5]).toHaveTextContent('1234');
  223. expect(tableFirstRowColumns[6]).toHaveTextContent('May 21, 2020 3:31:18 PM UTC');
  224. ProjectsStore.reset();
  225. });
  226. });