transactionEvents.spec.tsx 8.6 KB

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