index.spec.tsx 8.2 KB

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