uniformRateModal.spec.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import {
  2. render,
  3. screen,
  4. userEvent,
  5. waitForElementToBeRemoved,
  6. } from 'sentry-test/reactTestingLibrary';
  7. import {textWithMarkupMatcher} from 'sentry-test/utils';
  8. import {openModal} from 'sentry/actionCreators/modal';
  9. import GlobalModal from 'sentry/components/globalModal';
  10. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  11. import {UniformRateModal} from 'sentry/views/settings/project/server-side-sampling/modals/uniformRateModal';
  12. import {SERVER_SIDE_SAMPLING_DOC_LINK} from 'sentry/views/settings/project/server-side-sampling/utils';
  13. import {getMockData, outcomesWithoutClientDiscarded} from '../utils';
  14. jest.mock('sentry/utils/analytics/trackAdvancedAnalyticsEvent');
  15. describe('Server-Side Sampling - Uniform Rate Modal', function () {
  16. beforeAll(function () {
  17. MockApiClient.addMockResponse({
  18. url: '/organizations/org-slug/stats_v2/',
  19. body: TestStubs.OutcomesWithReason(),
  20. });
  21. });
  22. it('render next button', async function () {
  23. const {organization, project} = getMockData();
  24. const handleSubmit = jest.fn();
  25. const handleReadDocs = jest.fn();
  26. const {container} = render(<GlobalModal />);
  27. openModal(modalProps => (
  28. <UniformRateModal
  29. {...modalProps}
  30. organization={organization}
  31. project={project}
  32. projectStats={TestStubs.Outcomes()}
  33. rules={[]}
  34. onSubmit={handleSubmit}
  35. onReadDocs={handleReadDocs}
  36. />
  37. ));
  38. // Header
  39. expect(
  40. await screen.findByRole('heading', {
  41. name: 'Set a global sample rate',
  42. })
  43. ).toBeInTheDocument();
  44. expect(
  45. screen.getByText(
  46. textWithMarkupMatcher(
  47. 'Set a server-side sample rate for all transactions using our suggestion as a starting point.'
  48. )
  49. )
  50. ).toBeInTheDocument();
  51. // Content
  52. expect(screen.getByText('Transactions (Last 30 days)')).toBeInTheDocument(); // Chart
  53. expect(screen.getByRole('radio', {name: 'Current'})).toBeChecked();
  54. expect(screen.getByRole('radio', {name: 'Suggested'})).not.toBeChecked();
  55. expect(screen.getByText('100%')).toBeInTheDocument(); // Current client-side sample rate
  56. expect(screen.getByText('N/A')).toBeInTheDocument(); // Current server-side sample rate
  57. expect(screen.getAllByRole('spinbutton')[0]).toHaveValue(95); // Suggested client-side sample rate
  58. expect(screen.queryByTestId('invalid-client-rate')).not.toBeInTheDocument(); // Client input warning is not visible
  59. expect(screen.getAllByTestId('more-information')).toHaveLength(2); // Client input help is visible
  60. expect(screen.getAllByRole('spinbutton')[1]).toHaveValue(95); // Suggested server-side sample rate
  61. expect(screen.queryByLabelText('Reset to suggested values')).not.toBeInTheDocument();
  62. expect(screen.queryByTestId('invalid-server-rate')).not.toBeInTheDocument(); // Server input warning is not visible
  63. // Enter invalid client-side sample rate
  64. userEvent.clear(screen.getAllByRole('spinbutton')[0]);
  65. userEvent.hover(screen.getByTestId('invalid-client-rate')); // Client input warning is visible
  66. expect(await screen.findByText('Set a value between 0 and 100')).toBeInTheDocument();
  67. expect(screen.queryByTestId('more-information')).not.toBeInTheDocument(); // Client input help is not visible
  68. // Hover over next button
  69. userEvent.hover(screen.getByRole('button', {name: 'Next'}));
  70. expect(await screen.findByText('Sample rate is not valid')).toBeInTheDocument();
  71. // Enter valid custom client-sample rate
  72. userEvent.type(screen.getAllByRole('spinbutton')[0], '20{enter}');
  73. expect(screen.queryByText('Suggested')).not.toBeInTheDocument();
  74. expect(screen.getAllByRole('spinbutton')[0]).toHaveValue(20); // Custom client-side sample rate
  75. expect(screen.getByRole('radio', {name: 'New'})).toBeChecked();
  76. expect(screen.getByLabelText('Reset to suggested values')).toBeInTheDocument();
  77. // Enter invalid server-side sample rate
  78. userEvent.clear(screen.getAllByRole('spinbutton')[1]);
  79. userEvent.hover(screen.getByTestId('invalid-server-rate')); // Server input warning is visible
  80. expect(await screen.findByText('Set a value between 0 and 100')).toBeInTheDocument();
  81. // Enter a server-side sample rate higher than the client-side rate
  82. userEvent.type(screen.getAllByRole('spinbutton')[1], '30{enter}');
  83. userEvent.hover(screen.getByTestId('invalid-server-rate')); // Server input warning is visible
  84. expect(
  85. await screen.findByText(
  86. 'Server sample rate shall not be higher than client sample rate'
  87. )
  88. ).toBeInTheDocument();
  89. // Reset sample rates to suggested values
  90. userEvent.click(screen.getByLabelText('Reset to suggested values'));
  91. expect(screen.getByText('Suggested')).toBeInTheDocument();
  92. expect(screen.getAllByRole('spinbutton')[0]).toHaveValue(95); // Suggested client-side sample rate
  93. expect(screen.getAllByRole('spinbutton')[1]).toHaveValue(95); // Suggested server-side sample rate
  94. expect(screen.queryByTestId('invalid-client-rate')).not.toBeInTheDocument();
  95. expect(screen.getAllByTestId('more-information')).toHaveLength(2); // Question marks (help components) are visible
  96. expect(screen.queryByTestId('invalid-server-rate')).not.toBeInTheDocument();
  97. // Footer
  98. expect(screen.getByRole('button', {name: 'Read Docs'})).toHaveAttribute(
  99. 'href',
  100. SERVER_SIDE_SAMPLING_DOC_LINK
  101. );
  102. expect(screen.getByText('Step 1 of 2')).toBeInTheDocument();
  103. expect(screen.getByRole('button', {name: 'Cancel'})).toBeInTheDocument();
  104. expect(screen.getByRole('button', {name: 'Next'})).toBeInTheDocument();
  105. // Take screenshot (this is good as we can not test the chart)
  106. expect(container).toSnapshot();
  107. // Click on docs button
  108. userEvent.click(screen.getByRole('button', {name: 'Read Docs'}));
  109. expect(handleReadDocs).toHaveBeenCalled();
  110. expect(trackAdvancedAnalyticsEvent).toHaveBeenCalledWith(
  111. 'sampling.settings.modal.uniform.rate_read_docs',
  112. expect.objectContaining({
  113. organization,
  114. project_id: project.id,
  115. })
  116. );
  117. // Click on next button
  118. userEvent.click(screen.getByRole('button', {name: 'Next'}));
  119. expect(handleSubmit).not.toHaveBeenCalled();
  120. expect(trackAdvancedAnalyticsEvent).toHaveBeenCalledWith(
  121. 'sampling.settings.modal.uniform.rate_next',
  122. expect.objectContaining({
  123. organization,
  124. project_id: project.id,
  125. })
  126. );
  127. // Click on close button
  128. userEvent.click(screen.getByLabelText('Close Modal'));
  129. await waitForElementToBeRemoved(() => screen.queryByLabelText('Close Modal'));
  130. });
  131. it('render done button', async function () {
  132. const {organization, project} = getMockData();
  133. const handleSubmit = jest.fn();
  134. const {container} = render(<GlobalModal />);
  135. openModal(modalProps => (
  136. <UniformRateModal
  137. {...modalProps}
  138. organization={organization}
  139. project={project}
  140. projectStats={{...TestStubs.Outcomes(), groups: []}}
  141. rules={[]}
  142. onSubmit={handleSubmit}
  143. onReadDocs={jest.fn()}
  144. />
  145. ));
  146. // Content
  147. const suggestedSampleRates = await screen.findAllByRole('spinbutton');
  148. expect(suggestedSampleRates[0]).toHaveValue(100); // Suggested client-side sample rate
  149. expect(suggestedSampleRates[1]).toHaveValue(100); // Suggested server-side sample rate
  150. expect(trackAdvancedAnalyticsEvent).toHaveBeenCalledWith(
  151. 'sampling.settings.modal.uniform.rate_switch_current',
  152. expect.objectContaining({
  153. organization,
  154. project_id: project.id,
  155. })
  156. );
  157. // Footer
  158. expect(screen.getByRole('button', {name: 'Done'})).toBeDisabled();
  159. expect(screen.queryByText('Step 1 of 2')).not.toBeInTheDocument();
  160. // Hover over done button
  161. userEvent.hover(screen.getByRole('button', {name: 'Done'}));
  162. expect(
  163. await screen.findByText('Current sampling values selected')
  164. ).toBeInTheDocument();
  165. // Switch to suggested sample rates
  166. userEvent.click(screen.getByText('Suggested'));
  167. expect(screen.getByRole('button', {name: 'Done'})).toBeEnabled();
  168. expect(trackAdvancedAnalyticsEvent).toHaveBeenCalledWith(
  169. 'sampling.settings.modal.uniform.rate_switch_recommended',
  170. expect.objectContaining({
  171. organization,
  172. project_id: project.id,
  173. })
  174. );
  175. // Take screenshot (this is good as we can not test the chart)
  176. expect(container).toSnapshot();
  177. // Submit
  178. userEvent.click(screen.getByRole('button', {name: 'Done'}));
  179. expect(handleSubmit).toHaveBeenCalledWith(
  180. expect.objectContaining({
  181. sampleRate: 1,
  182. recommendedSampleRate: true,
  183. uniformRateModalOrigin: true,
  184. rule: undefined,
  185. })
  186. );
  187. // Click on close button
  188. userEvent.click(screen.getByLabelText('Close Modal'));
  189. await waitForElementToBeRemoved(() => screen.queryByLabelText('Done'));
  190. });
  191. it('cancel flow', async function () {
  192. const {organization, project} = getMockData();
  193. render(<GlobalModal />);
  194. openModal(modalProps => (
  195. <UniformRateModal
  196. {...modalProps}
  197. organization={organization}
  198. project={project}
  199. projectStats={{...TestStubs.Outcomes(), groups: []}}
  200. rules={[]}
  201. onSubmit={jest.fn()}
  202. onReadDocs={jest.fn()}
  203. />
  204. ));
  205. await screen.findByRole('heading', {name: 'Set a global sample rate'});
  206. // Cancel
  207. userEvent.click(screen.getByRole('button', {name: 'Cancel'}));
  208. await waitForElementToBeRemoved(() => screen.queryByLabelText('Cancel'));
  209. expect(trackAdvancedAnalyticsEvent).toHaveBeenCalledWith(
  210. 'sampling.settings.modal.uniform.rate_cancel',
  211. expect.objectContaining({
  212. organization,
  213. project_id: project.id,
  214. })
  215. );
  216. });
  217. it('display "Specify client rate modal" content as a first step', async function () {
  218. MockApiClient.addMockResponse({
  219. url: '/organizations/org-slug/stats_v2/',
  220. body: outcomesWithoutClientDiscarded,
  221. });
  222. const {organization, project} = getMockData();
  223. render(<GlobalModal />);
  224. openModal(modalProps => (
  225. <UniformRateModal
  226. {...modalProps}
  227. organization={organization}
  228. project={project}
  229. projectStats={outcomesWithoutClientDiscarded}
  230. rules={[]}
  231. onSubmit={jest.fn()}
  232. onReadDocs={jest.fn()}
  233. />
  234. ));
  235. expect(
  236. await screen.findByRole('heading', {
  237. name: 'Specify current client(SDK) sample rate',
  238. })
  239. ).toBeInTheDocument();
  240. expect(screen.getByRole('button', {name: 'Next'})).toBeDisabled();
  241. // Enter valid specified client-sample rate
  242. userEvent.type(screen.getByRole('spinbutton'), '0.2{enter}');
  243. userEvent.click(screen.getByRole('button', {name: 'Next'}));
  244. expect(
  245. await screen.findByRole('heading', {name: 'Set a global sample rate'})
  246. ).toBeInTheDocument();
  247. // Content
  248. expect(screen.getByText('20%')).toBeInTheDocument(); // Current client-side sample rate
  249. expect(screen.getByText('N/A')).toBeInTheDocument(); // Current server-side sample rate
  250. expect(screen.getAllByRole('spinbutton')[0]).toHaveValue(100); // Suggested client-side sample rate
  251. expect(screen.getAllByRole('spinbutton')[1]).toHaveValue(20); // Suggested server-side sample rate
  252. // Footer
  253. expect(screen.getByText('Step 2 of 3')).toBeInTheDocument();
  254. // Go Back
  255. userEvent.click(screen.getByRole('button', {name: 'Back'}));
  256. // Specified sample rate has to still be there
  257. expect(screen.getByRole('spinbutton')).toHaveValue(0.2);
  258. // Close Modal
  259. userEvent.click(screen.getByRole('button', {name: 'Cancel'}));
  260. await waitForElementToBeRemoved(() => screen.queryByLabelText('Cancel'));
  261. });
  262. it('does not display "Specify client rate modal" if no groups', async function () {
  263. const outcomesWithoutGroups = {...outcomesWithoutClientDiscarded, groups: []};
  264. MockApiClient.addMockResponse({
  265. url: '/organizations/org-slug/stats_v2/',
  266. body: outcomesWithoutGroups,
  267. });
  268. const {organization, project} = getMockData();
  269. render(<GlobalModal />);
  270. openModal(modalProps => (
  271. <UniformRateModal
  272. {...modalProps}
  273. organization={organization}
  274. project={project}
  275. projectStats={outcomesWithoutGroups}
  276. rules={[]}
  277. onSubmit={jest.fn()}
  278. onReadDocs={jest.fn()}
  279. />
  280. ));
  281. expect(
  282. await screen.findByRole('heading', {name: 'Set a global sample rate'})
  283. ).toBeInTheDocument();
  284. // Close Modal
  285. userEvent.click(screen.getByRole('button', {name: 'Cancel'}));
  286. await waitForElementToBeRemoved(() => screen.queryByLabelText('Cancel'));
  287. });
  288. it('display request error message', async function () {
  289. MockApiClient.addMockResponse({
  290. url: '/organizations/org-slug/stats_v2/',
  291. statusCode: 500,
  292. });
  293. const {organization, project} = getMockData();
  294. render(<GlobalModal />);
  295. openModal(modalProps => (
  296. <UniformRateModal
  297. {...modalProps}
  298. organization={organization}
  299. project={project}
  300. projectStats={outcomesWithoutClientDiscarded}
  301. rules={[]}
  302. onSubmit={jest.fn()}
  303. onReadDocs={jest.fn()}
  304. />
  305. ));
  306. expect(
  307. await screen.findByText(/There was an error loading data/)
  308. ).toBeInTheDocument();
  309. });
  310. });