serverSideSampling.spec.tsx 11 KB

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