index.spec.tsx 8.2 KB

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