eventsTable.spec.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {render, screen} from 'sentry-test/reactTestingLibrary';
  3. import {t} from 'sentry/locale';
  4. import ProjectsStore from 'sentry/stores/projectsStore';
  5. import EventView from 'sentry/utils/discover/eventView';
  6. import {
  7. SPAN_OP_BREAKDOWN_FIELDS,
  8. SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
  9. } from 'sentry/utils/discover/fields';
  10. import EventsTable from 'sentry/views/performance/transactionSummary/transactionEvents/eventsTable';
  11. type Data = {
  12. features?: string[];
  13. };
  14. export const MOCK_EVENTS_TABLE_DATA = [
  15. {
  16. id: 'deadbeef',
  17. 'user.display': 'uhoh@example.com',
  18. 'transaction.duration': 400,
  19. 'project.id': 1,
  20. timestamp: '2020-05-21T15:31:18+00:00',
  21. trace: '1234',
  22. 'span_ops_breakdown.relative': '',
  23. 'spans.browser': 100,
  24. 'spans.db': 30,
  25. 'spans.http': 170,
  26. 'spans.resource': 100,
  27. 'spans.total.time': 400,
  28. },
  29. {
  30. id: 'moredeadbeef',
  31. 'user.display': 'moreuhoh@example.com',
  32. 'transaction.duration': 600,
  33. 'project.id': 1,
  34. timestamp: '2020-05-22T15:31:18+00:00',
  35. trace: '4321',
  36. 'span_ops_breakdown.relative': '',
  37. 'spans.browser': 100,
  38. 'spans.db': 300,
  39. 'spans.http': 100,
  40. 'spans.resource': 100,
  41. 'spans.total.time': 600,
  42. },
  43. ];
  44. export const EVENTS_TABLE_RESPONSE_FIELDS = [
  45. 'id',
  46. 'user.display',
  47. SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
  48. 'transaction.duration',
  49. 'trace',
  50. 'timestamp',
  51. 'spans.total.time',
  52. ...SPAN_OP_BREAKDOWN_FIELDS,
  53. ];
  54. function initializeData({features: additionalFeatures = []}: Data = {}) {
  55. const features = ['discover-basic', 'performance-view', ...additionalFeatures];
  56. const organization = TestStubs.Organization({
  57. features,
  58. projects: [TestStubs.Project()],
  59. apdexThreshold: 400,
  60. });
  61. const initialData = initializeOrg({
  62. organization,
  63. router: {
  64. location: {
  65. query: {
  66. transaction: '/performance',
  67. project: 1,
  68. transactionCursor: '1:0:0',
  69. },
  70. },
  71. },
  72. project: 1,
  73. projects: [],
  74. });
  75. ProjectsStore.loadInitialData(initialData.organization.projects);
  76. return initialData;
  77. }
  78. describe('Performance GridEditable Table', function () {
  79. const transactionsListTitles = [
  80. t('event id'),
  81. t('user'),
  82. t('operation duration'),
  83. t('total duration'),
  84. t('trace id'),
  85. t('timestamp'),
  86. ];
  87. const totalEventCount = '100';
  88. let fields = EVENTS_TABLE_RESPONSE_FIELDS;
  89. const organization = TestStubs.Organization();
  90. const transactionName = 'transactionName';
  91. let data;
  92. const query =
  93. 'transaction.duration:<15m event.type:transaction transaction:/api/0/organizations/{organization_slug}/events/';
  94. beforeEach(function () {
  95. MockApiClient.addMockResponse({
  96. url: '/organizations/org-slug/projects/',
  97. body: [],
  98. });
  99. MockApiClient.addMockResponse({
  100. url: '/prompts-activity/',
  101. body: {},
  102. });
  103. MockApiClient.addMockResponse({
  104. url: '/organizations/org-slug/sdk-updates/',
  105. body: [],
  106. });
  107. data = MOCK_EVENTS_TABLE_DATA;
  108. // Transaction list response
  109. MockApiClient.addMockResponse({
  110. url: '/organizations/org-slug/events/',
  111. headers: {
  112. Link:
  113. '<http://localhost/api/0/organizations/org-slug/events/?cursor=2:0:0>; rel="next"; results="true"; cursor="2:0:0",' +
  114. '<http://localhost/api/0/organizations/org-slug/events/?cursor=1:0:0>; rel="previous"; results="false"; cursor="1:0:0"',
  115. },
  116. body: {
  117. meta: {
  118. fields: {
  119. id: 'string',
  120. 'user.display': 'string',
  121. 'transaction.duration': 'duration',
  122. 'project.id': 'integer',
  123. timestamp: 'date',
  124. },
  125. },
  126. data,
  127. },
  128. match: [
  129. (_url, options) => {
  130. return options.query?.field?.includes('user.display');
  131. },
  132. ],
  133. });
  134. });
  135. afterEach(function () {
  136. MockApiClient.clearMockResponses();
  137. ProjectsStore.reset();
  138. jest.clearAllMocks();
  139. });
  140. it('renders ops breakdown bar when querying for span_ops_breakdown.relative', async function () {
  141. const initialData = initializeData();
  142. const eventView = EventView.fromNewQueryWithLocation(
  143. {
  144. id: undefined,
  145. version: 2,
  146. name: 'transactionName',
  147. fields,
  148. query,
  149. projects: [],
  150. orderby: '-timestamp',
  151. },
  152. initialData.router.location
  153. );
  154. render(
  155. <EventsTable
  156. totalEventCount={totalEventCount}
  157. eventView={eventView}
  158. organization={organization}
  159. routes={initialData.router.routes}
  160. location={initialData.router.location}
  161. setError={() => {}}
  162. columnTitles={transactionsListTitles}
  163. transactionName={transactionName}
  164. />,
  165. {context: initialData.routerContext}
  166. );
  167. expect(await screen.findAllByTestId('relative-ops-breakdown')).toHaveLength(2);
  168. expect(screen.getAllByRole('columnheader')).toHaveLength(6);
  169. expect(screen.getByText('operation duration')).toBeInTheDocument();
  170. expect(screen.queryByTestId('grid-head-cell-static')).not.toBeInTheDocument();
  171. });
  172. it('renders basic columns without ops breakdown when not querying for span_ops_breakdown.relative', function () {
  173. const initialData = initializeData();
  174. fields = [
  175. 'id',
  176. 'user.display',
  177. 'transaction.duration',
  178. 'trace',
  179. 'timestamp',
  180. 'spans.http',
  181. ];
  182. data.forEach(result => {
  183. delete result['span_ops_breakdown.relative'];
  184. delete result['spans.resource'];
  185. delete result['spans.browser'];
  186. delete result['spans.db'];
  187. delete result['spans.total.time'];
  188. });
  189. const eventView = EventView.fromNewQueryWithLocation(
  190. {
  191. id: undefined,
  192. version: 2,
  193. name: 'transactionName',
  194. fields,
  195. query,
  196. projects: [],
  197. orderby: '-timestamp',
  198. },
  199. initialData.router.location
  200. );
  201. render(
  202. <EventsTable
  203. totalEventCount={totalEventCount}
  204. eventView={eventView}
  205. organization={organization}
  206. routes={initialData.router.routes}
  207. location={initialData.router.location}
  208. setError={() => {}}
  209. columnTitles={transactionsListTitles}
  210. transactionName={transactionName}
  211. />,
  212. {context: initialData.routerContext}
  213. );
  214. expect(screen.getAllByRole('columnheader')).toHaveLength(6);
  215. expect(screen.queryByText(SPAN_OP_RELATIVE_BREAKDOWN_FIELD)).not.toBeInTheDocument();
  216. expect(screen.queryByTestId('relative-ops-breakdown')).not.toBeInTheDocument();
  217. expect(screen.queryByTestId('grid-head-cell-static')).not.toBeInTheDocument();
  218. });
  219. it('renders event id and trace id url', async function () {
  220. const initialData = initializeData();
  221. const eventView = EventView.fromNewQueryWithLocation(
  222. {
  223. id: undefined,
  224. version: 2,
  225. name: 'transactionName',
  226. fields,
  227. query,
  228. projects: [],
  229. orderby: '-timestamp',
  230. },
  231. initialData.router.location
  232. );
  233. render(
  234. <EventsTable
  235. totalEventCount={totalEventCount}
  236. eventView={eventView}
  237. organization={organization}
  238. routes={initialData.router.routes}
  239. location={initialData.router.location}
  240. setError={() => {}}
  241. columnTitles={transactionsListTitles}
  242. transactionName={transactionName}
  243. />,
  244. {context: initialData.routerContext}
  245. );
  246. expect(await screen.findByRole('link', {name: 'deadbeef'})).toHaveAttribute(
  247. 'href',
  248. '/organizations/org-slug/performance/undefined:deadbeef/?project=1&transaction=transactionName&transactionCursor=1%3A0%3A0'
  249. );
  250. expect(screen.getByRole('link', {name: '1234'})).toHaveAttribute(
  251. 'href',
  252. '/organizations/org-slug/performance/trace/1234/?'
  253. );
  254. });
  255. });