pendingChanges.spec.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {PlanDetailsLookupFixture} from 'getsentry-test/fixtures/planDetailsLookup';
  3. import {PlanMigrationFixture} from 'getsentry-test/fixtures/planMigration';
  4. import {
  5. Am3DsEnterpriseSubscriptionFixture,
  6. SubscriptionFixture,
  7. } from 'getsentry-test/fixtures/subscription';
  8. import {render} from 'sentry-test/reactTestingLibrary';
  9. import PendingChanges from 'admin/components/customers/pendingChanges';
  10. import {PendingChangesFixture} from 'getsentry/__fixtures__/pendingChanges';
  11. import {PlanFixture} from 'getsentry/__fixtures__/plan';
  12. import {ANNUAL, RESERVED_BUDGET_QUOTA} from 'getsentry/constants';
  13. import * as usePlanMigrations from 'getsentry/hooks/usePlanMigrations';
  14. import {CohortId, OnDemandBudgetMode} from 'getsentry/types';
  15. describe('PendingChanges', function () {
  16. it('renders null pendingChanges)', function () {
  17. const subscription = SubscriptionFixture({
  18. organization: OrganizationFixture(),
  19. });
  20. const {container} = render(<PendingChanges subscription={subscription} />);
  21. expect(container).toBeEmptyDOMElement();
  22. });
  23. it('renders empty pendingChanges', function () {
  24. const subscription = SubscriptionFixture({
  25. organization: OrganizationFixture(),
  26. pendingChanges: null,
  27. });
  28. const {container} = render(<PendingChanges subscription={subscription} />);
  29. expect(container).toBeEmptyDOMElement();
  30. });
  31. it('renders pending changes', function () {
  32. const subscription = SubscriptionFixture({
  33. organization: OrganizationFixture(),
  34. customPrice: 0,
  35. customPricePcss: 0,
  36. pendingChanges: PendingChangesFixture({
  37. planDetails: PlanFixture({
  38. name: 'Team (Enterprise)',
  39. contractInterval: 'annual',
  40. billingInterval: 'annual',
  41. }),
  42. plan: 'am1_team_ent',
  43. planName: 'Team (Enterprise)',
  44. reservedEvents: 15000000,
  45. reservedErrors: 15000000,
  46. reservedTransactions: 20000000,
  47. reservedAttachments: 25,
  48. reserved: {errors: 15000000, transactions: 20000000, attachments: 25},
  49. customPrice: 5000000,
  50. customPriceErrors: 2000000,
  51. customPriceTransactions: 2900000,
  52. customPriceAttachments: 50000,
  53. customPrices: {errors: 2000000, transactions: 2900000, attachments: 50000},
  54. customPricePcss: 50000,
  55. onDemandMaxSpend: 50000,
  56. effectiveDate: '2022-03-16',
  57. onDemandEffectiveDate: '2022-02-16',
  58. }),
  59. });
  60. const {container} = render(<PendingChanges subscription={subscription} />);
  61. // expected copy for plan changes
  62. expect(container).toHaveTextContent(
  63. 'This account has pending changes to the subscription'
  64. );
  65. expect(container).toHaveTextContent(
  66. 'The following changes will take effect on Mar 16, 2022'
  67. );
  68. expect(container).toHaveTextContent('Plan changes — Developer → Team (Enterprise)');
  69. expect(container).toHaveTextContent('Contract period — monthly → annual');
  70. expect(container).toHaveTextContent('Billing period — monthly → annual');
  71. expect(container).toHaveTextContent('Reserved errors — 5,000 → 15,000,000 errors');
  72. expect(container).toHaveTextContent(
  73. 'Reserved transactions — 10,000 → 20,000,000 transactions'
  74. );
  75. expect(container).toHaveTextContent('Reserved attachments — 1 GB → 25 GB');
  76. expect(container).toHaveTextContent('Custom price (ACV) — $0.00 → $50,000.00');
  77. expect(container).toHaveTextContent('Custom price for errors — $0.00 → $20,000.00');
  78. expect(container).toHaveTextContent(
  79. 'Custom price for transactions — $0.00 → $29,000.00'
  80. );
  81. expect(container).toHaveTextContent('Custom price for attachments — $0.00 → $500.00');
  82. expect(container).toHaveTextContent('Custom price for PCSS — $0.00 → $500.00');
  83. // expected copy for on-demand changes
  84. expect(container).toHaveTextContent(
  85. 'The following changes will take effect on Feb 16, 2022'
  86. );
  87. expect(container).toHaveTextContent('On-demand maximum — $0.00 → $500.00');
  88. });
  89. it('renders pending changes with all categories', function () {
  90. const subscription = SubscriptionFixture({
  91. organization: OrganizationFixture(),
  92. customPrice: 0,
  93. customPricePcss: 0,
  94. pendingChanges: PendingChangesFixture({
  95. planDetails: PlanFixture({
  96. name: 'Team (Enterprise)',
  97. contractInterval: 'annual',
  98. billingInterval: 'annual',
  99. }),
  100. plan: 'am3_team_ent',
  101. planName: 'Team (Enterprise)',
  102. reservedEvents: 15000000,
  103. reservedErrors: 15000000,
  104. reservedTransactions: 0,
  105. reservedAttachments: 25,
  106. reserved: {errors: 15000000, spans: 20000000, attachments: 25},
  107. customPrice: 5000000,
  108. customPriceErrors: 2000000,
  109. customPriceTransactions: 0,
  110. customPriceAttachments: 50000,
  111. customPrices: {errors: 2000000, spans: 200000, attachments: 50000},
  112. customPricePcss: 50000,
  113. onDemandMaxSpend: 50000,
  114. effectiveDate: '2024-10-09',
  115. onDemandEffectiveDate: '2024-02-20',
  116. }),
  117. });
  118. const {container} = render(<PendingChanges subscription={subscription} />);
  119. // expected copy for plan changes
  120. expect(container).toHaveTextContent(
  121. 'This account has pending changes to the subscription'
  122. );
  123. expect(container).toHaveTextContent(
  124. 'The following changes will take effect on Oct 9, 2024'
  125. );
  126. expect(container).toHaveTextContent('Plan changes — Developer → Team (Enterprise)');
  127. expect(container).toHaveTextContent('Contract period — monthly → annual');
  128. expect(container).toHaveTextContent('Billing period — monthly → annual');
  129. expect(container).toHaveTextContent('Reserved errors — 5,000 → 15,000,000 errors');
  130. expect(container).toHaveTextContent(
  131. 'Reserved transactions — 10,000 → 0 transactions'
  132. );
  133. expect(container).toHaveTextContent('Reserved spans — 0 → 20,000,000 spans');
  134. expect(container).toHaveTextContent('Reserved attachments — 1 GB → 25 GB');
  135. expect(container).toHaveTextContent('Custom price (ACV) — $0.00 → $50,000.00');
  136. expect(container).toHaveTextContent('Custom price for errors — $0.00 → $20,000.00');
  137. expect(container).toHaveTextContent('Custom price for spans — $0.00 → $2,000.00');
  138. expect(container).toHaveTextContent('Custom price for attachments — $0.00 → $500.00');
  139. expect(container).toHaveTextContent('Custom price for PCSS — $0.00 → $500.00');
  140. // expected copy for on-demand changes
  141. expect(container).toHaveTextContent(
  142. 'The following changes will take effect on Feb 20, 2024'
  143. );
  144. expect(container).toHaveTextContent('On-demand maximum — $0.00 → $500.00');
  145. });
  146. it('renders on-demand budgets', function () {
  147. const subscription = SubscriptionFixture({
  148. organization: OrganizationFixture(),
  149. onDemandBudgets: {
  150. enabled: true,
  151. budgetMode: OnDemandBudgetMode.SHARED,
  152. sharedMaxBudget: 10000,
  153. onDemandSpendUsed: 0,
  154. },
  155. pendingChanges: PendingChangesFixture({
  156. planDetails: PlanFixture({
  157. name: 'Team (Enterprise)',
  158. contractInterval: 'annual',
  159. billingInterval: 'annual',
  160. onDemandCategories: ['errors', 'transactions', 'attachments'],
  161. }),
  162. onDemandBudgets: {
  163. enabled: true,
  164. budgetMode: OnDemandBudgetMode.PER_CATEGORY,
  165. errorsBudget: 300,
  166. transactionsBudget: 200,
  167. replaysBudget: 0,
  168. attachmentsBudget: 100,
  169. budgets: {errors: 300, transactions: 200, replays: 0, attachments: 100},
  170. },
  171. onDemandMaxSpend: 50000,
  172. effectiveDate: '2022-03-16',
  173. onDemandEffectiveDate: '2022-02-16',
  174. }),
  175. });
  176. const {container} = render(<PendingChanges subscription={subscription} />);
  177. // expected copy for plan changes
  178. expect(container).toHaveTextContent(
  179. 'This account has pending changes to the subscription'
  180. );
  181. expect(container).toHaveTextContent(
  182. 'The following changes will take effect on Mar 16, 2022'
  183. );
  184. // expected copy for on-demand changes
  185. expect(container).toHaveTextContent(
  186. 'The following changes will take effect on Feb 16, 2022'
  187. );
  188. expect(container).toHaveTextContent(
  189. 'On-demand budget — shared on-demand of $100 → per-category on-demand (errors at $3, transactions at $2, and attachments at $1)'
  190. );
  191. });
  192. it('renders pending changes for plan migration', function () {
  193. const organization = OrganizationFixture();
  194. const am2BusinessPlan = PlanDetailsLookupFixture('am2_business_auf');
  195. const subscription = SubscriptionFixture({
  196. planDetails: am2BusinessPlan,
  197. plan: 'am2_business_auf',
  198. contractInterval: ANNUAL,
  199. organization,
  200. onDemandPeriodEnd: '2018-10-24',
  201. contractPeriodEnd: '2019-09-24',
  202. pendingChanges: PendingChangesFixture({
  203. planDetails: PlanFixture({
  204. id: 'am3_business_auf',
  205. name: 'Business',
  206. contractInterval: 'annual',
  207. billingInterval: 'annual',
  208. onDemandCategories: [
  209. 'errors',
  210. 'attachments',
  211. 'spans',
  212. 'replays',
  213. 'monitorSeats',
  214. ],
  215. }),
  216. reservedEvents: 50_000,
  217. reserved: {
  218. errors: 50_000,
  219. spans: 10_000_000,
  220. replays: 50,
  221. monitorSeats: 1,
  222. attachments: 1,
  223. },
  224. effectiveDate: '2019-09-25',
  225. onDemandEffectiveDate: '2018-10-25',
  226. }),
  227. });
  228. const migrationDate = '2018-10-25';
  229. const migration = PlanMigrationFixture({
  230. cohortId: CohortId.TENTH,
  231. effectiveAt: migrationDate,
  232. });
  233. jest
  234. .spyOn(usePlanMigrations, 'usePlanMigrations')
  235. .mockReturnValue({planMigrations: [migration], isLoading: false});
  236. const {container} = render(<PendingChanges subscription={subscription} />);
  237. // expected copy for plan changes
  238. expect(container).toHaveTextContent(
  239. 'This account has pending changes to the subscription'
  240. );
  241. expect(container).toHaveTextContent(
  242. 'The following changes will take effect on Oct 25, 2018'
  243. );
  244. expect(container).toHaveTextContent('Plan changes — Business → Business');
  245. expect(container).toHaveTextContent(
  246. 'Reserved performance units — 100,000 → 0 transactions'
  247. );
  248. expect(container).toHaveTextContent('Reserved replays — 500 → 50 replays');
  249. expect(container).toHaveTextContent('Reserved spans — 0 → 10,000,000 spans');
  250. // no actual changes
  251. expect(container).not.toHaveTextContent('Reserved errors — 50,000 → 50,000 errors');
  252. expect(container).not.toHaveTextContent(
  253. 'Reserved attachments — 1 GB → 1 GB attachments'
  254. );
  255. expect(container).not.toHaveTextContent(
  256. 'Reserved cron monitors — 1 → 1 cron monitor'
  257. );
  258. });
  259. it('renders reserved budgets with existing budgets', function () {
  260. const subscription = Am3DsEnterpriseSubscriptionFixture({
  261. organization: OrganizationFixture(),
  262. pendingChanges: PendingChangesFixture({
  263. planDetails: PlanDetailsLookupFixture('am3_business_ent_ds_auf'),
  264. plan: 'am3_business_ent_ds_auf',
  265. planName: 'Business',
  266. reserved: {
  267. spans: RESERVED_BUDGET_QUOTA,
  268. spansIndexed: RESERVED_BUDGET_QUOTA,
  269. },
  270. reservedCpe: {
  271. spans: 12.345678,
  272. spansIndexed: 87.654321,
  273. },
  274. reservedBudgets: [
  275. {
  276. reservedBudget: 50_000_00,
  277. categories: {spans: true, spansIndexed: true},
  278. },
  279. ],
  280. }),
  281. });
  282. const {container} = render(<PendingChanges subscription={subscription} />);
  283. expect(container).not.toHaveTextContent('Plan changes —');
  284. expect(container).not.toHaveTextContent('Reserved spans —');
  285. expect(container).not.toHaveTextContent('Reserved accepted spans —');
  286. expect(container).not.toHaveTextContent('Reserved spansIndexed —');
  287. expect(container).not.toHaveTextContent('Reserved stored spans —');
  288. expect(container).toHaveTextContent(
  289. 'Reserved cost-per-event for accepted spans — $0.01000000 → $0.12345678'
  290. );
  291. expect(container).toHaveTextContent(
  292. 'Reserved cost-per-event for stored spans — $0.02000000 → $0.87654321'
  293. );
  294. expect(container).toHaveTextContent(
  295. 'Reserved budgets — $100,000.00 for accepted spans and stored spans → $50,000.00 for accepted spans and stored spans'
  296. );
  297. });
  298. it('renders reserved budgets without existing budgets', function () {
  299. const subscription = SubscriptionFixture({
  300. organization: OrganizationFixture(),
  301. plan: 'am3_business',
  302. pendingChanges: PendingChangesFixture({
  303. planDetails: PlanDetailsLookupFixture('am3_business_ent_ds_auf'),
  304. plan: 'am3_business_ent_ds_auf',
  305. planName: 'Business',
  306. reserved: {
  307. spans: RESERVED_BUDGET_QUOTA,
  308. spansIndexed: RESERVED_BUDGET_QUOTA,
  309. },
  310. reservedCpe: {
  311. spans: 12.345678,
  312. spansIndexed: 87.654321,
  313. },
  314. reservedBudgets: [
  315. {
  316. reservedBudget: 50_000_00,
  317. categories: {spans: true, spansIndexed: true},
  318. },
  319. ],
  320. }),
  321. });
  322. const {container} = render(<PendingChanges subscription={subscription} />);
  323. expect(container).toHaveTextContent('Plan changes — Business → Business');
  324. expect(container).toHaveTextContent(
  325. 'Reserved accepted spans — 10,000,000 → reserved budget'
  326. );
  327. expect(container).toHaveTextContent('Reserved stored spans — 0 → reserved budget');
  328. expect(container).not.toHaveTextContent('Reserved spans —');
  329. expect(container).not.toHaveTextContent('Reserved spansIndexed —');
  330. expect(container).toHaveTextContent(
  331. 'Reserved cost-per-event for accepted spans — None → $0.12345678'
  332. );
  333. expect(container).toHaveTextContent(
  334. 'Reserved cost-per-event for stored spans — None → $0.87654321'
  335. );
  336. expect(container).toHaveTextContent(
  337. 'Reserved budgets — None → $50,000.00 for accepted spans and stored spans'
  338. );
  339. });
  340. it('renders reserved budgets to reserved volume', function () {
  341. const subscription = Am3DsEnterpriseSubscriptionFixture({
  342. organization: OrganizationFixture(),
  343. pendingChanges: PendingChangesFixture({
  344. planDetails: PlanDetailsLookupFixture('am3_business_ent_auf'),
  345. plan: 'am3_business_ent_auf',
  346. planName: 'Business',
  347. reserved: {
  348. spans: 10_000_000,
  349. },
  350. }),
  351. });
  352. const {container} = render(<PendingChanges subscription={subscription} />);
  353. expect(container).toHaveTextContent('Plan changes — Business → Business');
  354. expect(container).toHaveTextContent(
  355. 'Reserved accepted spans — reserved budget → 10,000,000 spans'
  356. );
  357. expect(container).toHaveTextContent(
  358. 'Reserved stored spans — reserved budget → 0 spansIndexed'
  359. );
  360. expect(container).not.toHaveTextContent('Reserved spans —');
  361. expect(container).not.toHaveTextContent('Reserved spansIndexed —');
  362. expect(container).toHaveTextContent(
  363. 'Reserved cost-per-event for spans — $0.01000000 → None'
  364. );
  365. expect(container).toHaveTextContent(
  366. 'Reserved cost-per-event for spansIndexed — $0.02000000 → None'
  367. );
  368. expect(container).toHaveTextContent(
  369. 'Reserved budgets — $100,000.00 for accepted spans and stored spans → None'
  370. );
  371. });
  372. it('does not render reserved budgets if there are no changes', function () {
  373. const subscription = SubscriptionFixture({
  374. organization: OrganizationFixture(),
  375. });
  376. const {container} = render(<PendingChanges subscription={subscription} />);
  377. expect(container).not.toHaveTextContent('Reserved budgets —');
  378. });
  379. });