content.spec.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {ProjectFixture} from 'sentry-fixture/project';
  3. import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestingLibrary';
  4. import {useLocation} from 'sentry/utils/useLocation';
  5. import usePageFilters from 'sentry/utils/usePageFilters';
  6. import {useParams} from 'sentry/utils/useParams';
  7. import useProjects from 'sentry/utils/useProjects';
  8. import SpanSummary from 'sentry/views/performance/transactionSummary/transactionSpans/spanSummary/content';
  9. jest.mock('sentry/utils/useParams');
  10. jest.mock('sentry/utils/useLocation');
  11. jest.mock('sentry/utils/usePageFilters');
  12. jest.mock('sentry/utils/useProjects');
  13. describe('SpanSummaryPage', function () {
  14. const organization = OrganizationFixture();
  15. const project = ProjectFixture();
  16. jest.mocked(useLocation).mockReturnValue({
  17. pathname: '',
  18. search: '',
  19. query: {statsPeriod: '10d', project: '1'},
  20. hash: '',
  21. state: undefined,
  22. action: 'PUSH',
  23. key: '',
  24. });
  25. jest.mocked(useProjects).mockReturnValue({
  26. projects: [],
  27. onSearch: jest.fn(),
  28. reloadProjects: jest.fn(),
  29. placeholders: [],
  30. fetching: false,
  31. hasMore: null,
  32. fetchError: null,
  33. initiallyLoaded: false,
  34. });
  35. jest.mocked(usePageFilters).mockReturnValue({
  36. isReady: true,
  37. desyncedFilters: new Set(),
  38. pinnedFilters: new Set(),
  39. shouldPersist: true,
  40. selection: {
  41. datetime: {
  42. period: '10d',
  43. start: null,
  44. end: null,
  45. utc: false,
  46. },
  47. environments: [],
  48. projects: [parseInt(project.id, 10)],
  49. },
  50. });
  51. let headerDataMock: jest.Mock;
  52. let avgDurationChartMock: jest.Mock;
  53. let spanThroughputChartMock: jest.Mock;
  54. let transactionThroughputChartMock: jest.Mock;
  55. beforeEach(() => {
  56. jest.mocked(useParams).mockReturnValue({
  57. spanSlug: 'db:aaaaaaaa',
  58. });
  59. jest.clearAllMocks();
  60. avgDurationChartMock = MockApiClient.addMockResponse({
  61. url: '/organizations/org-slug/events-stats/',
  62. method: 'GET',
  63. match: [
  64. MockApiClient.matchQuery({
  65. referrer: 'api.performance.span-summary-duration-chart',
  66. }),
  67. ],
  68. body: {
  69. data: [
  70. [
  71. 1717102800,
  72. [
  73. {
  74. count: 4.924892746006871,
  75. },
  76. ],
  77. ],
  78. [
  79. 1717104600,
  80. [
  81. {
  82. count: 8.20925404044671,
  83. },
  84. ],
  85. ],
  86. [
  87. 1717106400,
  88. [
  89. {
  90. count: 7.218881600137195,
  91. },
  92. ],
  93. ],
  94. ],
  95. },
  96. });
  97. spanThroughputChartMock = MockApiClient.addMockResponse({
  98. url: '/organizations/org-slug/events-stats/',
  99. method: 'GET',
  100. match: [
  101. MockApiClient.matchQuery({
  102. referrer: 'api.performance.span-summary-throughput-chart',
  103. }),
  104. ],
  105. body: {
  106. data: [
  107. [
  108. 1717102800,
  109. [
  110. {
  111. count: 22580.666666666668,
  112. },
  113. ],
  114. ],
  115. [
  116. 1717104600,
  117. [
  118. {
  119. count: 258816.26666666666,
  120. },
  121. ],
  122. ],
  123. [
  124. 1717106400,
  125. [
  126. {
  127. count: 305550.4666666667,
  128. },
  129. ],
  130. ],
  131. ],
  132. },
  133. });
  134. transactionThroughputChartMock = MockApiClient.addMockResponse({
  135. url: '/organizations/org-slug/events-stats/',
  136. method: 'GET',
  137. match: [
  138. MockApiClient.matchQuery({
  139. referrer: 'api.performance.span-summary-transaction-throughput-chart',
  140. }),
  141. ],
  142. body: {
  143. data: [
  144. [
  145. 1717102800,
  146. [
  147. {
  148. count: 152823.58333333334,
  149. },
  150. ],
  151. ],
  152. [
  153. 1717106400,
  154. [
  155. {
  156. count: 143062.61666666667,
  157. },
  158. ],
  159. ],
  160. [
  161. 1717110000,
  162. [
  163. {
  164. count: 158031.58333333334,
  165. },
  166. ],
  167. ],
  168. ],
  169. },
  170. });
  171. MockApiClient.addMockResponse({
  172. url: '/organizations/org-slug/events/',
  173. body: {
  174. data: [
  175. {
  176. 'transaction.id': '93b88037ba134225bdf67d05a69de9ab',
  177. project: 'sentry',
  178. 'project.name': 'sentry',
  179. span_id: '9b6e1f295ce7e875',
  180. id: '9b6e1f295ce7e875',
  181. 'span.duration': 0.860929,
  182. trace: '80a8718f4b3847eb8d6f3b5715602558',
  183. timestamp: '2024-05-16T14:45:15+00:00',
  184. },
  185. ],
  186. },
  187. });
  188. MockApiClient.addMockResponse({
  189. url: '/organizations/org-slug/events/',
  190. body: {
  191. data: [
  192. {
  193. 'transaction.duration': 160,
  194. id: '93b88037ba134225bdf67d05a69de9ab',
  195. 'project.name': 'sentry',
  196. },
  197. {
  198. 'transaction.duration': 50,
  199. id: '2a2c0e1a7cf941f6bcd8ab22b0c4d8c9',
  200. 'project.name': 'sentry',
  201. },
  202. ],
  203. },
  204. });
  205. });
  206. it('correctly renders the details in the header', async function () {
  207. MockApiClient.addMockResponse({
  208. url: '/organizations/org-slug/spans/fields/',
  209. body: [],
  210. });
  211. headerDataMock = MockApiClient.addMockResponse({
  212. url: '/organizations/org-slug/events/',
  213. body: {
  214. data: [
  215. {
  216. 'span.description': 'SELECT thing FROM my_cool_db WHERE value = %s',
  217. 'avg(span.duration)': 1.7381229881349218,
  218. 'count()': 3677407172,
  219. 'sum(span.duration)': 6391491809.035965,
  220. },
  221. ],
  222. meta: {
  223. fields: {
  224. 'span.description': 'string',
  225. 'sum(span.duration)': 'duration',
  226. 'count()': 'integer',
  227. 'avg(span.duration)': 'duration',
  228. },
  229. units: {
  230. 'span.description': null,
  231. 'sum(span.duration)': 'millisecond',
  232. 'count()': null,
  233. 'avg(span.duration)': 'millisecond',
  234. },
  235. isMetricsData: false,
  236. isMetricsExtractedData: false,
  237. tips: {},
  238. datasetReason: 'unchanged',
  239. dataset: 'spansMetrics',
  240. },
  241. },
  242. });
  243. render(
  244. <SpanSummary
  245. spanSlug={{group: 'aaaaaaaa', op: 'db'}}
  246. transactionName="transaction"
  247. organization={organization}
  248. project={undefined}
  249. />
  250. );
  251. expect(headerDataMock).toHaveBeenCalled();
  252. await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
  253. expect(await screen.findByTestId('operation-name')).toHaveTextContent('db');
  254. expect(await screen.findByTestId('header-span-description')).toHaveTextContent(
  255. 'SELECT thing FROM my_cool_db WHERE value = %s'
  256. );
  257. expect(await screen.findByTestId('header-avg-duration')).toHaveTextContent('1.74ms');
  258. expect(await screen.findByTestId('header-total-time-spent')).toHaveTextContent(
  259. '2.43mo'
  260. );
  261. expect(await screen.findByTestId('total-span-count')).toHaveTextContent('3.6b spans');
  262. });
  263. it('renders the charts', async () => {
  264. MockApiClient.addMockResponse({
  265. url: '/organizations/org-slug/spans/fields/',
  266. body: [],
  267. });
  268. render(
  269. <SpanSummary
  270. spanSlug={{group: 'aaaaaaaa', op: 'db'}}
  271. transactionName="transaction"
  272. organization={organization}
  273. project={project}
  274. />
  275. );
  276. expect(avgDurationChartMock).toHaveBeenCalled();
  277. expect(spanThroughputChartMock).toHaveBeenCalled();
  278. expect(transactionThroughputChartMock).toHaveBeenCalled();
  279. await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
  280. const chartHeaders = await screen.findAllByTestId('chart-panel-header');
  281. expect(chartHeaders[0]).toHaveTextContent('Average Duration');
  282. expect(chartHeaders[1]).toHaveTextContent('Span Throughput');
  283. expect(chartHeaders[2]).toHaveTextContent('Transaction Throughput');
  284. });
  285. });