uniformRateModal.spec.tsx 13 KB

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