transactionEvents.spec.tsx 9.0 KB

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