serverSideSampling.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. import {
  2. render,
  3. screen,
  4. userEvent,
  5. waitFor,
  6. within,
  7. } from 'sentry-test/reactTestingLibrary';
  8. import {SERVER_SIDE_SAMPLING_DOC_LINK} from 'sentry/views/settings/project/server-side-sampling/utils';
  9. import {samplingBreakdownTitle} from './samplingBreakdown.spec';
  10. import {
  11. getMockData,
  12. mockedProjects,
  13. mockedSamplingDistribution,
  14. mockedSamplingSdkVersions,
  15. specificRule,
  16. TestComponent,
  17. uniformRule,
  18. } from './testUtils';
  19. describe('Server-Side Sampling', function () {
  20. let distributionMock: ReturnType<typeof MockApiClient.addMockResponse> | undefined =
  21. undefined;
  22. let sdkVersionsMock: ReturnType<typeof MockApiClient.addMockResponse> | undefined =
  23. undefined;
  24. beforeEach(function () {
  25. distributionMock = MockApiClient.addMockResponse({
  26. url: '/projects/org-slug/project-slug/dynamic-sampling/distribution/',
  27. method: 'GET',
  28. body: mockedSamplingDistribution,
  29. });
  30. sdkVersionsMock = MockApiClient.addMockResponse({
  31. url: '/organizations/org-slug/dynamic-sampling/sdk-versions/',
  32. method: 'GET',
  33. body: mockedSamplingSdkVersions,
  34. });
  35. MockApiClient.addMockResponse({
  36. url: '/organizations/org-slug/projects/',
  37. method: 'GET',
  38. body: mockedSamplingDistribution.project_breakdown!.map(p =>
  39. TestStubs.Project({id: p.project_id, slug: p.project})
  40. ),
  41. });
  42. MockApiClient.addMockResponse({
  43. url: '/organizations/org-slug/stats_v2/',
  44. method: 'GET',
  45. body: TestStubs.Outcomes(),
  46. });
  47. });
  48. afterEach(() => {
  49. MockApiClient.clearMockResponses();
  50. });
  51. it('renders onboarding promo', async function () {
  52. const {router, organization, project} = getMockData();
  53. const {container} = render(
  54. <TestComponent router={router} organization={organization} project={project} />
  55. );
  56. expect(
  57. screen.getByRole('heading', {name: /Server-Side Sampling/})
  58. ).toBeInTheDocument();
  59. expect(screen.getByText(/enhance the performance monitoring/i)).toBeInTheDocument();
  60. // Assert that project breakdown is there
  61. expect(await screen.findByText(samplingBreakdownTitle)).toBeInTheDocument();
  62. expect(
  63. screen.getByRole('heading', {name: 'Sample for relevancy'})
  64. ).toBeInTheDocument();
  65. expect(
  66. screen.getByText(
  67. 'Create rules to sample transactions under specific conditions, keeping what you need and dropping what you don’t.'
  68. )
  69. ).toBeInTheDocument();
  70. expect(screen.getByRole('button', {name: 'Read Docs'})).toHaveAttribute(
  71. 'href',
  72. SERVER_SIDE_SAMPLING_DOC_LINK
  73. );
  74. expect(screen.getByRole('button', {name: 'Start Setup'})).toBeInTheDocument();
  75. expect(container).toSnapshot();
  76. });
  77. it('renders rules panel', async function () {
  78. const {router, organization, project} = getMockData({
  79. projects: [
  80. TestStubs.Project({
  81. dynamicSampling: {
  82. rules: [{...uniformRule, sampleRate: 1}],
  83. },
  84. }),
  85. ],
  86. });
  87. const {container} = render(
  88. <TestComponent router={router} organization={organization} project={project} />
  89. );
  90. // Assert that project breakdown is there
  91. expect(await screen.findByText(samplingBreakdownTitle)).toBeInTheDocument();
  92. // Rule Panel Header
  93. expect(screen.getByText('Operator')).toBeInTheDocument();
  94. expect(screen.getByText('Condition')).toBeInTheDocument();
  95. expect(screen.getByText('Rate')).toBeInTheDocument();
  96. expect(screen.getByText('Active')).toBeInTheDocument();
  97. // Rule Panel Content
  98. expect(screen.getAllByTestId('sampling-rule').length).toBe(1);
  99. expect(screen.queryByLabelText('Drag Rule')).not.toBeInTheDocument();
  100. expect(screen.getByTestId('sampling-rule')).toHaveTextContent('If');
  101. expect(screen.getByTestId('sampling-rule')).toHaveTextContent('All');
  102. expect(screen.getByTestId('sampling-rule')).toHaveTextContent('100%');
  103. expect(screen.getByLabelText('Activate Rule')).toBeInTheDocument();
  104. expect(screen.getByLabelText('Actions')).toBeInTheDocument();
  105. // Rule Panel Footer
  106. expect(screen.getByText('Add Rule')).toBeInTheDocument();
  107. expect(screen.getByRole('button', {name: 'Read Docs'})).toHaveAttribute(
  108. 'href',
  109. SERVER_SIDE_SAMPLING_DOC_LINK
  110. );
  111. expect(container).toSnapshot();
  112. });
  113. it('does not let you delete the base rule', async function () {
  114. const {router, organization, project} = getMockData({
  115. projects: [
  116. TestStubs.Project({
  117. dynamicSampling: {
  118. rules: [
  119. {
  120. sampleRate: 0.2,
  121. type: 'trace',
  122. active: false,
  123. condition: {
  124. op: 'and',
  125. inner: [
  126. {
  127. op: 'glob',
  128. name: 'trace.release',
  129. value: ['1.2.3'],
  130. },
  131. ],
  132. },
  133. id: 2,
  134. },
  135. {
  136. sampleRate: 0.2,
  137. type: 'trace',
  138. active: false,
  139. condition: {
  140. op: 'and',
  141. inner: [],
  142. },
  143. id: 1,
  144. },
  145. ],
  146. next_id: 3,
  147. },
  148. }),
  149. ],
  150. });
  151. render(
  152. <TestComponent router={router} organization={organization} project={project} />
  153. );
  154. // Assert that project breakdown is there (avoids 'act' warnings)
  155. expect(await screen.findByText(samplingBreakdownTitle)).toBeInTheDocument();
  156. const deleteButtons = screen.getAllByLabelText('Delete');
  157. expect(deleteButtons[0]).not.toHaveAttribute('disabled'); // eslint-disable-line jest-dom/prefer-enabled-disabled
  158. expect(deleteButtons[1]).toHaveAttribute('disabled'); // eslint-disable-line jest-dom/prefer-enabled-disabled
  159. });
  160. it('display "update sdk versions" alert and open "recommended next step" modal', async function () {
  161. const {organization, projects, router} = getMockData({
  162. projects: mockedProjects,
  163. });
  164. render(
  165. <TestComponent
  166. organization={organization}
  167. project={projects[2]}
  168. router={router}
  169. withModal
  170. />
  171. );
  172. expect(distributionMock).toHaveBeenCalled();
  173. await waitFor(() => {
  174. expect(sdkVersionsMock).toHaveBeenCalled();
  175. });
  176. const recommendedSdkUpgradesAlert = await screen.findByTestId(
  177. 'recommended-sdk-upgrades-alert'
  178. );
  179. expect(
  180. within(recommendedSdkUpgradesAlert).getByText(
  181. 'To activate server-side sampling rules, it’s a requirement to update the following project SDK(s):'
  182. )
  183. ).toBeInTheDocument();
  184. expect(
  185. within(recommendedSdkUpgradesAlert).getByRole('link', {
  186. name: mockedProjects[1].slug,
  187. })
  188. ).toHaveAttribute(
  189. 'href',
  190. `/organizations/org-slug/projects/sentry/?project=${mockedProjects[1].id}`
  191. );
  192. // Open Modal
  193. userEvent.click(
  194. within(recommendedSdkUpgradesAlert).getByRole('button', {
  195. name: 'Learn More',
  196. })
  197. );
  198. expect(await screen.findByRole('heading', {name: 'Next steps'})).toBeInTheDocument();
  199. });
  200. it('Open specific conditions modal', async function () {
  201. const {router, project, organization} = getMockData({
  202. projects: [
  203. TestStubs.Project({
  204. dynamicSampling: {
  205. rules: [
  206. {
  207. sampleRate: 1,
  208. type: 'trace',
  209. active: false,
  210. condition: {
  211. op: 'and',
  212. inner: [],
  213. },
  214. id: 1,
  215. },
  216. ],
  217. },
  218. }),
  219. ],
  220. });
  221. render(
  222. <TestComponent
  223. organization={organization}
  224. project={project}
  225. router={router}
  226. withModal
  227. />
  228. );
  229. // Open Modal
  230. userEvent.click(screen.getByLabelText('Add Rule'));
  231. expect(await screen.findByRole('heading', {name: 'Add Rule'})).toBeInTheDocument();
  232. });
  233. it('does not let user add without permissions', async function () {
  234. const {organization, router, project} = getMockData({
  235. projects: [
  236. TestStubs.Project({
  237. dynamicSampling: {
  238. rules: [uniformRule],
  239. },
  240. }),
  241. ],
  242. access: [],
  243. });
  244. render(
  245. <TestComponent organization={organization} project={project} router={router} />
  246. );
  247. expect(screen.getByRole('button', {name: 'Add Rule'})).toBeDisabled();
  248. userEvent.hover(screen.getByText('Add Rule'));
  249. expect(
  250. await screen.findByText("You don't have permission to add a rule")
  251. ).toBeInTheDocument();
  252. expect(distributionMock).not.toHaveBeenCalled();
  253. expect(sdkVersionsMock).not.toHaveBeenCalled();
  254. });
  255. it('does not let the user activate a rule if sdk updates exists', async function () {
  256. const {organization, router, project} = getMockData({
  257. projects: [
  258. TestStubs.Project({
  259. dynamicSampling: {
  260. rules: [uniformRule],
  261. },
  262. }),
  263. ],
  264. });
  265. render(
  266. <TestComponent organization={organization} project={project} router={router} />
  267. );
  268. await screen.findByTestId('recommended-sdk-upgrades-alert');
  269. expect(screen.getByRole('checkbox', {name: 'Activate Rule'})).toBeDisabled();
  270. userEvent.hover(screen.getByLabelText('Activate Rule'));
  271. expect(
  272. await screen.findByText(
  273. 'To enable the rule, the recommended sdk version have to be updated'
  274. )
  275. ).toBeInTheDocument();
  276. });
  277. it('open uniform rate modal when editing a uniform rule', async function () {
  278. const {organization, router, project} = getMockData({
  279. projects: [
  280. TestStubs.Project({
  281. dynamicSampling: {
  282. rules: [uniformRule],
  283. },
  284. }),
  285. ],
  286. });
  287. render(
  288. <TestComponent
  289. organization={organization}
  290. project={project}
  291. router={router}
  292. withModal
  293. />
  294. );
  295. userEvent.click(screen.getByLabelText('Actions'));
  296. // Open Modal
  297. userEvent.click(screen.getByLabelText('Edit'));
  298. expect(
  299. await screen.findByRole('heading', {
  300. name: 'Set a global sample rate',
  301. })
  302. ).toBeInTheDocument();
  303. });
  304. it('does not let user reorder uniform rule', async function () {
  305. const {organization, router, project} = getMockData({
  306. projects: [
  307. TestStubs.Project({
  308. dynamicSampling: {
  309. rules: [specificRule, uniformRule],
  310. },
  311. }),
  312. ],
  313. });
  314. render(
  315. <TestComponent
  316. organization={organization}
  317. project={project}
  318. router={router}
  319. withModal
  320. />
  321. );
  322. const samplingUniformRule = screen.getAllByTestId('sampling-rule')[1];
  323. expect(
  324. within(samplingUniformRule).getByRole('button', {name: 'Drag Rule'})
  325. ).toHaveAttribute('aria-disabled', 'true');
  326. userEvent.hover(within(samplingUniformRule).getByLabelText('Drag Rule'));
  327. expect(
  328. await screen.findByText('Uniform rules cannot be reordered')
  329. ).toBeInTheDocument();
  330. });
  331. });