addDataVolume.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {BillingConfigFixture} from 'getsentry-test/fixtures/billingConfig';
  3. import {PlanDetailsLookupFixture} from 'getsentry-test/fixtures/planDetailsLookup';
  4. import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription';
  5. import {initializeOrg} from 'sentry-test/initializeOrg';
  6. import {fireEvent, render, screen, within} from 'sentry-test/reactTestingLibrary';
  7. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  8. import {PlanTier} from 'getsentry/types';
  9. import AMCheckout from 'getsentry/views/amCheckout/';
  10. import AddDataVolume from 'getsentry/views/amCheckout/steps/addDataVolume';
  11. describe('AddDataVolume', function () {
  12. const api = new MockApiClient();
  13. const {organization, router, routerProps} = initializeOrg();
  14. const subscription = SubscriptionFixture({organization});
  15. const params = {};
  16. const billingConfig = BillingConfigFixture(PlanTier.AM2);
  17. const bizPlan = PlanDetailsLookupFixture('am1_business')!;
  18. const teamPlanAnnual = PlanDetailsLookupFixture('am1_team_auf')!;
  19. const am2TeamPlanAnnual = PlanDetailsLookupFixture('am2_team_auf')!;
  20. const stepProps = {
  21. checkoutTier: PlanTier.AM2,
  22. subscription,
  23. isActive: true,
  24. stepNumber: 2,
  25. onUpdate: jest.fn(),
  26. onCompleteStep: jest.fn(),
  27. onEdit: jest.fn(),
  28. billingConfig,
  29. formData: {
  30. plan: billingConfig.defaultPlan,
  31. reserved: billingConfig.defaultReserved,
  32. },
  33. activePlan: bizPlan,
  34. isCompleted: false,
  35. organization,
  36. prevStepCompleted: true,
  37. };
  38. beforeEach(function () {
  39. SubscriptionStore.set(organization.slug, subscription);
  40. MockApiClient.addMockResponse({
  41. url: `/customers/${organization.slug}/plan-migrations/?applied=0`,
  42. method: 'GET',
  43. body: {},
  44. });
  45. MockApiClient.addMockResponse({
  46. url: `/subscriptions/${organization.slug}/`,
  47. method: 'GET',
  48. body: {},
  49. });
  50. MockApiClient.addMockResponse({
  51. url: `/customers/${organization.slug}/billing-config/`,
  52. method: 'GET',
  53. body: BillingConfigFixture(PlanTier.AM2),
  54. });
  55. });
  56. it('renders a heading', async function () {
  57. MockApiClient.addMockResponse({
  58. url: `/organizations/${organization.slug}/promotions/trigger-check/`,
  59. method: 'POST',
  60. });
  61. render(
  62. <AMCheckout
  63. {...routerProps}
  64. api={api}
  65. checkoutTier={PlanTier.AM2}
  66. onToggleLegacy={jest.fn()}
  67. params={params}
  68. />,
  69. {
  70. router,
  71. }
  72. );
  73. const heading = await screen.findByText('Reserved Volumes');
  74. expect(heading).toBeInTheDocument();
  75. });
  76. it('renders with default event volumes', async function () {
  77. MockApiClient.addMockResponse({
  78. url: `/organizations/${organization.slug}/promotions/trigger-check/`,
  79. method: 'POST',
  80. });
  81. render(
  82. <AMCheckout
  83. {...routerProps}
  84. api={api}
  85. checkoutTier={PlanTier.AM2}
  86. onToggleLegacy={jest.fn()}
  87. params={params}
  88. />,
  89. {
  90. router,
  91. }
  92. );
  93. // Open section by clicking on heading.
  94. const heading = await screen.findByText('Reserved Volumes');
  95. fireEvent.click(heading);
  96. const errors = screen.getByTestId('errors-volume-item').textContent;
  97. expect(errors).toContain('50,000');
  98. expect(errors).toContain('included');
  99. const transactions = screen.getByTestId('transactions-volume-item').textContent;
  100. expect(transactions).toContain('100,000');
  101. expect(transactions).toContain('included');
  102. const attachments = screen.getByTestId('attachments-volume-item').textContent;
  103. expect(attachments).toContain('1 GB');
  104. expect(attachments).toContain('included');
  105. const replays = screen.getByTestId('replays-volume-item').textContent;
  106. expect(replays).toContain('500');
  107. expect(replays).toContain('included');
  108. const monitorSeats = screen.getByTestId('monitorSeats-volume-item').textContent;
  109. expect(monitorSeats).toContain('1');
  110. expect(monitorSeats).toContain('included');
  111. const uptime = screen.getByTestId('uptime-volume-item').textContent;
  112. expect(uptime).toContain('1');
  113. expect(uptime).toContain('included');
  114. });
  115. it('renders additional event volume prices, business plan', function () {
  116. const props = {
  117. ...stepProps,
  118. formData: {
  119. plan: 'am2',
  120. reserved: {
  121. errors: 100_000,
  122. transactions: 500_000,
  123. attachments: 50,
  124. replays: 10_000,
  125. monitorSeats: 1,
  126. },
  127. },
  128. };
  129. render(<AddDataVolume {...props} />);
  130. const errors = screen.getByTestId('errors-volume-item').textContent;
  131. expect(errors).toContain('100,000');
  132. expect(errors).toContain('$45/mo');
  133. expect(errors).toContain('$0.00050 per event');
  134. expect(errors).toContain('50K');
  135. expect(errors).toContain('50M');
  136. const transactions = screen.getByTestId('transactions-volume-item').textContent;
  137. expect(transactions).toContain('500,000');
  138. expect(transactions).toContain('$90/mo');
  139. expect(transactions).toContain('$0.00013 per event');
  140. expect(transactions).toContain('100K');
  141. expect(transactions).toContain('30M');
  142. const attachments = screen.getByTestId('attachments-volume-item').textContent;
  143. expect(attachments).toContain('50 GB');
  144. expect(attachments).toContain('$12/mo');
  145. expect(attachments).toContain('$0.25');
  146. expect(attachments).toContain('1GB');
  147. expect(attachments).toContain('1,000GB');
  148. const replays = screen.getByTestId('replays-volume-item').textContent;
  149. expect(replays).toContain('10,000');
  150. expect(replays).toContain('$29/mo');
  151. expect(replays).toContain('$0.00288 per event');
  152. expect(replays).toContain('500');
  153. expect(replays).toContain('10M');
  154. const monitorSeats = screen.getByTestId('monitorSeats-volume-item').textContent;
  155. expect(monitorSeats).toContain('1');
  156. expect(monitorSeats).toContain('included');
  157. });
  158. it('renders event volume prices, team annual plan', function () {
  159. const props = {
  160. ...stepProps,
  161. activePlan: teamPlanAnnual,
  162. formData: {
  163. plan: 'am2',
  164. reserved: {
  165. errors: 200_000,
  166. transactions: 500_000,
  167. attachments: 25,
  168. replays: 25_000,
  169. monitorSeats: 1,
  170. },
  171. },
  172. };
  173. render(<AddDataVolume {...props} />);
  174. const errors = screen.getByTestId('errors-volume-item').textContent;
  175. expect(errors).toContain('200,000');
  176. expect(errors).toContain('$352/yr');
  177. expect(errors).not.toContain('dynamically sample');
  178. const transactions = screen.getByTestId('transactions-volume-item').textContent;
  179. expect(transactions).toContain('500,000');
  180. expect(transactions).toContain('$324/yr');
  181. expect(errors).not.toContain('dynamically sample');
  182. const attachments = screen.getByTestId('attachments-volume-item').textContent;
  183. expect(attachments).toContain('25 GB');
  184. expect(attachments).toContain('$63/yr');
  185. expect(errors).not.toContain('dynamically sample');
  186. const replays = screen.getByTestId('replays-volume-item').textContent;
  187. expect(replays).toContain('25,000');
  188. expect(replays).toContain('$780/yr');
  189. expect(errors).not.toContain('dynamically sample');
  190. const monitorSeats = screen.getByTestId('monitorSeats-volume-item').textContent;
  191. expect(monitorSeats).toContain('1');
  192. expect(monitorSeats).toContain('included');
  193. expect(errors).not.toContain('dynamically sample');
  194. });
  195. it('can complete step', async function () {
  196. MockApiClient.addMockResponse({
  197. url: `/organizations/${organization.slug}/promotions/trigger-check/`,
  198. method: 'POST',
  199. });
  200. render(
  201. <AMCheckout
  202. {...routerProps}
  203. api={api}
  204. checkoutTier={PlanTier.AM2}
  205. onToggleLegacy={jest.fn()}
  206. params={params}
  207. />,
  208. {
  209. router,
  210. }
  211. );
  212. const panel = await screen.findByTestId('step-add-data-volume');
  213. const heading = within(panel).getByText('Reserved Volumes');
  214. fireEvent.click(heading);
  215. // Click continue to collapse the panel again
  216. const button = within(panel).getByLabelText('Continue');
  217. fireEvent.click(button);
  218. // The button should be gone now.
  219. expect(within(panel).queryByLabelText('Continue')).not.toBeInTheDocument();
  220. });
  221. it('am2 checkout displays dynamic sampling alert', function () {
  222. const props = {
  223. ...stepProps,
  224. activePlan: am2TeamPlanAnnual,
  225. formData: {
  226. plan: 'am2',
  227. reserved: {
  228. errors: 200_000,
  229. transactions: 500_000,
  230. attachments: 25,
  231. replays: 10_000,
  232. monitorSeats: 1,
  233. },
  234. },
  235. };
  236. render(<AddDataVolume {...props} />);
  237. const errors = screen.getByTestId('errors-volume-item').textContent;
  238. expect(errors).toContain('200,000');
  239. expect(errors).toContain('$352/yr');
  240. expect(errors).not.toContain('dynamically sample');
  241. const transactions = screen.getByTestId('transactions-volume-item').textContent;
  242. expect(transactions).toContain('500,000');
  243. expect(transactions).toContain('$324/yr');
  244. expect(transactions).toContain('dynamically sample');
  245. const attachments = screen.getByTestId('attachments-volume-item').textContent;
  246. expect(attachments).toContain('25 GB');
  247. expect(attachments).toContain('$63/yr');
  248. expect(attachments).not.toContain('dynamically sample');
  249. const replays = screen.getByTestId('replays-volume-item').textContent;
  250. expect(replays).toContain('10,000');
  251. expect(replays).toContain('$312/yr');
  252. expect(replays).not.toContain('dynamically sample');
  253. const monitorSeats = screen.getByTestId('monitorSeats-volume-item').textContent;
  254. expect(monitorSeats).toContain('1');
  255. expect(monitorSeats).toContain('included');
  256. expect(monitorSeats).not.toContain('dynamically sample');
  257. });
  258. it('displays performance unit types with feature', function () {
  259. const org = OrganizationFixture({features: ['profiling-billing']});
  260. const props = {
  261. ...stepProps,
  262. organization: org,
  263. activePlan: am2TeamPlanAnnual,
  264. formData: {
  265. plan: 'am2',
  266. reserved: {
  267. errors: 200_000,
  268. transactions: 500_000,
  269. attachments: 25,
  270. replays: 500,
  271. monitorSeats: 1,
  272. },
  273. },
  274. };
  275. render(<AddDataVolume {...props} />);
  276. const transactions = screen.getByTestId('transactions-volume-item').textContent;
  277. expect(transactions).toContain('Total Units');
  278. expect(transactions).toContain('Sentry Performance');
  279. expect(transactions).toContain('per unit');
  280. expect(transactions).toContain('200M');
  281. });
  282. it('does not display performance unit types without feature', function () {
  283. const props = {
  284. ...stepProps,
  285. organization,
  286. activePlan: am2TeamPlanAnnual,
  287. formData: {
  288. plan: 'am2',
  289. reserved: {
  290. errors: 200_000,
  291. transactions: 500_000,
  292. attachments: 25,
  293. replays: 500,
  294. monitorSeats: 1,
  295. },
  296. },
  297. };
  298. render(<AddDataVolume {...props} />);
  299. const transactions = screen.getByTestId('transactions-volume-item').textContent;
  300. expect(transactions).not.toContain('Total Units');
  301. expect(transactions).not.toContain('Sentry Performance');
  302. expect(transactions).toContain('per event');
  303. });
  304. });