index.spec.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {
  3. generateSuspectSpansResponse,
  4. SAMPLE_SPANS,
  5. } from 'sentry-test/performance/initializePerformanceData';
  6. import {
  7. act,
  8. fireEvent,
  9. mountWithTheme,
  10. screen,
  11. within,
  12. } from 'sentry-test/reactTestingLibrary';
  13. import ProjectsStore from 'sentry/stores/projectsStore';
  14. import {getShortEventId} from 'sentry/utils/events';
  15. import TransactionSpans from 'sentry/views/performance/transactionSummary/transactionSpans';
  16. import {
  17. SpanSortOthers,
  18. SpanSortPercentiles,
  19. } from 'sentry/views/performance/transactionSummary/transactionSpans/types';
  20. function initializeData({query} = {query: {}}) {
  21. const features = ['performance-view', 'performance-suspect-spans-view'];
  22. const organization = TestStubs.Organization({
  23. features,
  24. projects: [TestStubs.Project()],
  25. });
  26. const initialData = initializeOrg({
  27. organization,
  28. router: {
  29. location: {
  30. query: {
  31. transaction: 'Test Transaction',
  32. project: '1',
  33. ...query,
  34. },
  35. },
  36. },
  37. });
  38. act(() => void ProjectsStore.loadInitialData(initialData.organization.projects));
  39. return initialData;
  40. }
  41. describe('Performance > Transaction Spans', function () {
  42. let eventsV2Mock;
  43. let eventsSpanOpsMock;
  44. let eventsSpansPerformanceMock;
  45. beforeEach(function () {
  46. MockApiClient.addMockResponse({
  47. url: '/organizations/org-slug/projects/',
  48. body: [],
  49. });
  50. MockApiClient.addMockResponse({
  51. url: '/prompts-activity/',
  52. body: {},
  53. });
  54. MockApiClient.addMockResponse({
  55. url: '/organizations/org-slug/sdk-updates/',
  56. body: [],
  57. });
  58. MockApiClient.addMockResponse({
  59. url: '/organizations/org-slug/events-has-measurements/',
  60. body: {measurements: false},
  61. });
  62. eventsV2Mock = MockApiClient.addMockResponse({
  63. url: '/organizations/org-slug/eventsv2/',
  64. body: 100,
  65. });
  66. eventsSpanOpsMock = MockApiClient.addMockResponse({
  67. url: '/organizations/org-slug/events-span-ops/',
  68. body: [],
  69. });
  70. });
  71. afterEach(function () {
  72. MockApiClient.clearMockResponses();
  73. ProjectsStore.reset();
  74. });
  75. describe('Without Span Data', function () {
  76. beforeEach(function () {
  77. eventsSpansPerformanceMock = MockApiClient.addMockResponse({
  78. url: '/organizations/org-slug/events-spans-performance/',
  79. body: [],
  80. });
  81. });
  82. it('renders empty state', async function () {
  83. const initialData = initializeData({
  84. query: {sort: SpanSortOthers.SUM_EXCLUSIVE_TIME},
  85. });
  86. mountWithTheme(
  87. <TransactionSpans
  88. organization={initialData.organization}
  89. location={initialData.router.location}
  90. />,
  91. {context: initialData.routerContext}
  92. );
  93. expect(await screen.findByText('No span data found')).toBeInTheDocument();
  94. });
  95. });
  96. describe('With Span Data', function () {
  97. beforeEach(function () {
  98. eventsSpansPerformanceMock = MockApiClient.addMockResponse({
  99. url: '/organizations/org-slug/events-spans-performance/',
  100. body: generateSuspectSpansResponse(),
  101. });
  102. });
  103. it('renders basic UI elements', async function () {
  104. const initialData = initializeData({
  105. query: {sort: SpanSortOthers.SUM_EXCLUSIVE_TIME},
  106. });
  107. mountWithTheme(
  108. <TransactionSpans
  109. organization={initialData.organization}
  110. location={initialData.router.location}
  111. />,
  112. {context: initialData.routerContext}
  113. );
  114. const cards = await screen.findAllByTestId('suspect-card');
  115. expect(cards).toHaveLength(2);
  116. for (let i = 0; i < cards.length; i++) {
  117. const card = cards[i];
  118. // need to narrow the search to the upper half of the card
  119. const upper = await within(card).findByTestId('suspect-card-upper');
  120. // these headers should be present by default
  121. expect(await within(upper).findByText('Span Operation')).toBeInTheDocument();
  122. expect(await within(upper).findByText('p75 Exclusive Time')).toBeInTheDocument();
  123. expect(await within(upper).findByText('Frequency')).toBeInTheDocument();
  124. expect(
  125. await within(upper).findByText('Total Exclusive Time')
  126. ).toBeInTheDocument();
  127. // only 2 examples show by default
  128. for (const example of SAMPLE_SPANS[i].examples.slice(0, 2)) {
  129. expect(
  130. await within(card).findByText(getShortEventId(example.id))
  131. ).toBeInTheDocument();
  132. }
  133. }
  134. expect(eventsV2Mock).toHaveBeenCalledTimes(1);
  135. expect(eventsSpanOpsMock).toHaveBeenCalledTimes(1);
  136. expect(eventsSpansPerformanceMock).toHaveBeenCalledTimes(1);
  137. });
  138. [
  139. {sort: SpanSortPercentiles.P50_EXCLUSIVE_TIME, label: 'p50 Exclusive Time'},
  140. {sort: SpanSortPercentiles.P75_EXCLUSIVE_TIME, label: 'p75 Exclusive Time'},
  141. {sort: SpanSortPercentiles.P95_EXCLUSIVE_TIME, label: 'p95 Exclusive Time'},
  142. {sort: SpanSortPercentiles.P99_EXCLUSIVE_TIME, label: 'p99 Exclusive Time'},
  143. ].forEach(({sort, label}) => {
  144. it('renders the right percentile header', async function () {
  145. const initialData = initializeData({query: {sort}});
  146. mountWithTheme(
  147. <TransactionSpans
  148. organization={initialData.organization}
  149. location={initialData.router.location}
  150. />,
  151. {context: initialData.routerContext}
  152. );
  153. const cards = await screen.findAllByTestId('suspect-card');
  154. expect(cards).toHaveLength(2);
  155. for (let i = 0; i < cards.length; i++) {
  156. const card = cards[i];
  157. // need to narrow the search to the upper half of the card
  158. const upper = await within(card).findByTestId('suspect-card-upper');
  159. // these headers should be present by default
  160. expect(await within(upper).findByText('Span Operation')).toBeInTheDocument();
  161. expect(await within(upper).findByText(label)).toBeInTheDocument();
  162. expect(await within(upper).findByText('Frequency')).toBeInTheDocument();
  163. expect(
  164. await within(upper).findByText('Total Exclusive Time')
  165. ).toBeInTheDocument();
  166. const arrow = await within(upper).findByTestId('span-sort-arrow');
  167. expect(arrow).toBeInTheDocument();
  168. expect(
  169. await within(arrow.closest('div')!).findByText(label)
  170. ).toBeInTheDocument();
  171. }
  172. });
  173. });
  174. it('renders the right count header', async function () {
  175. const initialData = initializeData({query: {sort: SpanSortOthers.COUNT}});
  176. mountWithTheme(
  177. <TransactionSpans
  178. organization={initialData.organization}
  179. location={initialData.router.location}
  180. />,
  181. {context: initialData.routerContext}
  182. );
  183. const cards = await screen.findAllByTestId('suspect-card');
  184. expect(cards).toHaveLength(2);
  185. for (let i = 0; i < cards.length; i++) {
  186. const card = cards[i];
  187. // need to narrow the search to the upper half of the card
  188. const upper = await within(card).findByTestId('suspect-card-upper');
  189. // these headers should be present by default
  190. expect(await within(upper).findByText('Span Operation')).toBeInTheDocument();
  191. expect(await within(upper).findByText('p75 Exclusive Time')).toBeInTheDocument();
  192. expect(await within(upper).findByText('Total Count')).toBeInTheDocument();
  193. expect(
  194. await within(upper).findByText('Total Exclusive Time')
  195. ).toBeInTheDocument();
  196. const arrow = await within(upper).findByTestId('span-sort-arrow');
  197. expect(arrow).toBeInTheDocument();
  198. expect(
  199. await within(arrow.closest('div')!).findByText('Total Count')
  200. ).toBeInTheDocument();
  201. }
  202. });
  203. it('renders the right avg occurrence header', async function () {
  204. const initialData = initializeData({query: {sort: SpanSortOthers.AVG_OCCURRENCE}});
  205. mountWithTheme(
  206. <TransactionSpans
  207. organization={initialData.organization}
  208. location={initialData.router.location}
  209. />,
  210. {context: initialData.routerContext}
  211. );
  212. const cards = await screen.findAllByTestId('suspect-card');
  213. expect(cards).toHaveLength(2);
  214. for (let i = 0; i < cards.length; i++) {
  215. const card = cards[i];
  216. // need to narrow the search to the upper half of the card
  217. const upper = await within(card).findByTestId('suspect-card-upper');
  218. // these headers should be present by default
  219. expect(await within(upper).findByText('Span Operation')).toBeInTheDocument();
  220. expect(await within(upper).findByText('p75 Exclusive Time')).toBeInTheDocument();
  221. expect(await within(upper).findByText('Average Count')).toBeInTheDocument();
  222. expect(
  223. await within(upper).findByText('Total Exclusive Time')
  224. ).toBeInTheDocument();
  225. const arrow = await within(upper).findByTestId('span-sort-arrow');
  226. expect(arrow).toBeInTheDocument();
  227. expect(
  228. await within(arrow.closest('div')!).findByText('Average Count')
  229. ).toBeInTheDocument();
  230. }
  231. });
  232. it('renders the right table headers', async function () {
  233. const initialData = initializeData();
  234. mountWithTheme(
  235. <TransactionSpans
  236. organization={initialData.organization}
  237. location={initialData.router.location}
  238. />,
  239. {context: initialData.routerContext}
  240. );
  241. const cards = await screen.findAllByTestId('suspect-card');
  242. expect(cards).toHaveLength(2);
  243. for (let i = 0; i < cards.length; i++) {
  244. const card = cards[i];
  245. const lower = await within(card).findByTestId('suspect-card-lower');
  246. expect(await within(lower).findByText('Example Transaction')).toBeInTheDocument();
  247. expect(await within(lower).findByText('Timestamp')).toBeInTheDocument();
  248. expect(await within(lower).findByText('Span Duration')).toBeInTheDocument();
  249. expect(await within(lower).findByText('Count')).toBeInTheDocument();
  250. expect(await within(lower).findByText('Cumulative Duration')).toBeInTheDocument();
  251. }
  252. });
  253. it('allows toggling of more examples', async function () {
  254. const initialData = initializeData();
  255. mountWithTheme(
  256. <TransactionSpans
  257. organization={initialData.organization}
  258. location={initialData.router.location}
  259. />,
  260. {context: initialData.routerContext}
  261. );
  262. const cards = await screen.findAllByTestId('suspect-card');
  263. expect(cards).toHaveLength(2);
  264. for (let i = 0; i < cards.length; i++) {
  265. const card = cards[i];
  266. expect(
  267. within(card).queryByText('Show More Transaction Examples')
  268. ).toBeInTheDocument();
  269. expect(
  270. within(card).queryByText('Hide Transaction Examples')
  271. ).not.toBeInTheDocument();
  272. // only 2 examples show by default
  273. for (const example of SAMPLE_SPANS[i].examples.slice(0, 2)) {
  274. expect(
  275. await within(card).findByText(getShortEventId(example.id))
  276. ).toBeInTheDocument();
  277. }
  278. fireEvent.click(await within(card).findByText('Show More Transaction Examples'));
  279. expect(within(card).queryByText('Hide Transaction Examples')).toBeInTheDocument();
  280. expect(
  281. within(card).queryByText('Show More Transaction Examples')
  282. ).not.toBeInTheDocument();
  283. // each span has 3 examples
  284. expect(SAMPLE_SPANS[i].examples).toHaveLength(3);
  285. // all the examples should be shown now
  286. for (const example of SAMPLE_SPANS[i].examples) {
  287. expect(
  288. await within(card).findByText(getShortEventId(example.id))
  289. ).toBeInTheDocument();
  290. }
  291. }
  292. });
  293. });
  294. });