transactionEvents.spec.tsx 8.4 KB

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