index.spec.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {generateSuspectSpansResponse} from 'sentry-test/performance/initializePerformanceData';
  4. import {
  5. act,
  6. render,
  7. screen,
  8. waitForElementToBeRemoved,
  9. within,
  10. } from 'sentry-test/reactTestingLibrary';
  11. import ProjectsStore from 'sentry/stores/projectsStore';
  12. import TransactionSpans from 'sentry/views/performance/transactionSummary/transactionSpans';
  13. import {
  14. SpanSortOthers,
  15. SpanSortPercentiles,
  16. } from 'sentry/views/performance/transactionSummary/transactionSpans/types';
  17. function initializeData(options: {query: {}; additionalFeatures?: string[]}) {
  18. const {query, additionalFeatures} = options;
  19. const defaultFeatures = ['performance-view'];
  20. const organization = OrganizationFixture({
  21. features: [...defaultFeatures, ...(additionalFeatures ? additionalFeatures : [])],
  22. });
  23. const initialData = initializeOrg({
  24. organization,
  25. router: {
  26. location: {
  27. query: {
  28. transaction: 'Test Transaction',
  29. project: '1',
  30. ...query,
  31. },
  32. },
  33. },
  34. });
  35. act(() => void ProjectsStore.loadInitialData(initialData.projects));
  36. return initialData;
  37. }
  38. describe('Performance > Transaction Spans', function () {
  39. let eventsMock;
  40. let eventsSpanOpsMock;
  41. let eventsSpansPerformanceMock;
  42. beforeEach(function () {
  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-has-measurements/',
  57. body: {measurements: false},
  58. });
  59. eventsMock = MockApiClient.addMockResponse({
  60. url: '/organizations/org-slug/events/',
  61. body: [{'count()': 100}],
  62. });
  63. eventsSpanOpsMock = MockApiClient.addMockResponse({
  64. url: '/organizations/org-slug/events-span-ops/',
  65. body: [],
  66. });
  67. MockApiClient.addMockResponse({
  68. url: '/organizations/org-slug/replay-count/',
  69. body: {},
  70. });
  71. MockApiClient.addMockResponse({
  72. url: '/organizations/org-slug/spans/fields/',
  73. body: [],
  74. });
  75. });
  76. afterEach(function () {
  77. MockApiClient.clearMockResponses();
  78. ProjectsStore.reset();
  79. });
  80. describe('Without Span Data', function () {
  81. beforeEach(function () {
  82. eventsSpansPerformanceMock = MockApiClient.addMockResponse({
  83. url: '/organizations/org-slug/events-spans-performance/',
  84. body: [],
  85. });
  86. });
  87. it('renders empty state', async function () {
  88. const initialData = initializeData({
  89. query: {sort: SpanSortOthers.SUM_EXCLUSIVE_TIME},
  90. });
  91. render(<TransactionSpans location={initialData.router.location} />, {
  92. router: initialData.router,
  93. organization: initialData.organization,
  94. });
  95. expect(
  96. await screen.findByText('No results found for your query')
  97. ).toBeInTheDocument();
  98. });
  99. });
  100. describe('With Span Data', function () {
  101. beforeEach(function () {
  102. eventsSpansPerformanceMock = MockApiClient.addMockResponse({
  103. url: '/organizations/org-slug/events-spans-performance/',
  104. body: generateSuspectSpansResponse({examples: 0}),
  105. });
  106. });
  107. it('renders basic UI elements', async function () {
  108. const initialData = initializeData({
  109. query: {sort: SpanSortOthers.SUM_EXCLUSIVE_TIME},
  110. });
  111. render(<TransactionSpans location={initialData.router.location} />, {
  112. router: initialData.router,
  113. organization: initialData.organization,
  114. });
  115. // default visible columns
  116. const grid = await screen.findByTestId('grid-editable');
  117. expect(await within(grid).findByText('Span Operation')).toBeInTheDocument();
  118. expect(await within(grid).findByText('Span Name')).toBeInTheDocument();
  119. expect(await within(grid).findByText('Frequency')).toBeInTheDocument();
  120. expect(await within(grid).findByText('P75 Self Time')).toBeInTheDocument();
  121. expect(await within(grid).findByText('Total Self Time')).toBeInTheDocument();
  122. // there should be a row for each of the spans
  123. expect(await within(grid).findByText('op1')).toBeInTheDocument();
  124. expect(await within(grid).findByText('op2')).toBeInTheDocument();
  125. expect(eventsMock).toHaveBeenCalledTimes(1);
  126. expect(eventsSpanOpsMock).toHaveBeenCalledTimes(1);
  127. expect(eventsSpansPerformanceMock).toHaveBeenCalledTimes(1);
  128. });
  129. [
  130. {sort: SpanSortPercentiles.P50_EXCLUSIVE_TIME, label: 'P50 Self Time'},
  131. {sort: SpanSortPercentiles.P75_EXCLUSIVE_TIME, label: 'P75 Self Time'},
  132. {sort: SpanSortPercentiles.P95_EXCLUSIVE_TIME, label: 'P95 Self Time'},
  133. {sort: SpanSortPercentiles.P99_EXCLUSIVE_TIME, label: 'P99 Self Time'},
  134. ].forEach(({sort, label}) => {
  135. it('renders the right percentile header', async function () {
  136. const initialData = initializeData({query: {sort}});
  137. render(<TransactionSpans location={initialData.router.location} />, {
  138. router: initialData.router,
  139. organization: initialData.organization,
  140. });
  141. const grid = await screen.findByTestId('grid-editable');
  142. expect(await within(grid).findByText('Span Operation')).toBeInTheDocument();
  143. expect(await within(grid).findByText('Span Name')).toBeInTheDocument();
  144. expect(await within(grid).findByText('Frequency')).toBeInTheDocument();
  145. expect(await within(grid).findByText(label)).toBeInTheDocument();
  146. expect(await within(grid).findByText('Total Self Time')).toBeInTheDocument();
  147. });
  148. });
  149. it('renders the right avg occurrence header', async function () {
  150. const initialData = initializeData({query: {sort: SpanSortOthers.AVG_OCCURRENCE}});
  151. render(<TransactionSpans location={initialData.router.location} />, {
  152. router: initialData.router,
  153. organization: initialData.organization,
  154. });
  155. const grid = await screen.findByTestId('grid-editable');
  156. expect(await within(grid).findByText('Span Operation')).toBeInTheDocument();
  157. expect(await within(grid).findByText('Span Name')).toBeInTheDocument();
  158. expect(await within(grid).findByText('Average Occurrences')).toBeInTheDocument();
  159. expect(await within(grid).findByText('Frequency')).toBeInTheDocument();
  160. expect(await within(grid).findByText('P75 Self Time')).toBeInTheDocument();
  161. expect(await within(grid).findByText('Total Self Time')).toBeInTheDocument();
  162. });
  163. });
  164. describe('Spans Tab V2', function () {
  165. it('does not propagate transaction search query and properly tokenizes span query', async function () {
  166. const initialData = initializeData({
  167. query: {query: 'http.method:POST', spansQuery: 'span.op:db span.action:SELECT'},
  168. additionalFeatures: [
  169. 'performance-view',
  170. 'performance-spans-new-ui',
  171. 'insights-initial-modules',
  172. ],
  173. });
  174. render(<TransactionSpans location={initialData.router.location} />, {
  175. router: initialData.router,
  176. organization: initialData.organization,
  177. });
  178. await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
  179. const searchTokens = await screen.findAllByTestId('filter-token');
  180. expect(searchTokens).toHaveLength(2);
  181. expect(searchTokens[0]).toHaveTextContent('span.op:db');
  182. expect(searchTokens[1]).toHaveTextContent('span.action:SELECT');
  183. expect(await screen.findByTestId('smart-search-bar')).not.toHaveTextContent(
  184. 'http.method:POST'
  185. );
  186. });
  187. });
  188. });