reservedUsageChart.spec.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {
  3. Am3DsEnterpriseSubscriptionFixture,
  4. SubscriptionFixture,
  5. } from 'getsentry-test/fixtures/subscription';
  6. import {ChartDataTransform} from 'sentry/views/organizationStats/usageChart';
  7. import {type BillingStats, PlanTier} from 'getsentry/types';
  8. import {
  9. getCategoryOptions,
  10. mapCostStatsToChart,
  11. mapReservedBudgetStatsToChart,
  12. mapStatsToChart,
  13. } from './reservedUsageChart';
  14. describe('mapStatsToChart', () => {
  15. it('should map stats to chart data', () => {
  16. const stats: BillingStats = [
  17. {
  18. date: '2019-01-01',
  19. ts: '',
  20. accepted: 1,
  21. filtered: 0,
  22. total: 1,
  23. dropped: {total: 0},
  24. onDemandCostRunningTotal: 0,
  25. isProjected: false,
  26. },
  27. ];
  28. const result = mapStatsToChart({
  29. stats,
  30. transform: ChartDataTransform.CUMULATIVE,
  31. });
  32. expect(result).toEqual({
  33. accepted: [
  34. {
  35. value: ['Jan 1', 1],
  36. },
  37. ],
  38. dropped: [
  39. {
  40. dropped: {
  41. other: 0,
  42. overQuota: 0,
  43. spikeProtection: 0,
  44. },
  45. value: ['Jan 1', 0],
  46. },
  47. ],
  48. projected: [],
  49. onDemand: [],
  50. reserved: [],
  51. });
  52. });
  53. });
  54. describe('mapCostStatsToChart', () => {
  55. const organization = OrganizationFixture();
  56. const subscription = SubscriptionFixture({
  57. organization,
  58. plan: 'am2_business',
  59. });
  60. it('should map cost stats to chart data', () => {
  61. const stats: BillingStats = [
  62. {
  63. date: '2019-01-01',
  64. ts: '',
  65. accepted: 1,
  66. filtered: 0,
  67. total: 1,
  68. dropped: {total: 0},
  69. onDemandCostRunningTotal: 100,
  70. isProjected: false,
  71. },
  72. {
  73. date: '2019-01-02',
  74. ts: '',
  75. accepted: 2,
  76. filtered: 0,
  77. total: 2,
  78. dropped: {total: 0},
  79. onDemandCostRunningTotal: 200,
  80. isProjected: false,
  81. },
  82. ];
  83. const result = mapCostStatsToChart({
  84. category: 'errors',
  85. stats,
  86. transform: ChartDataTransform.CUMULATIVE,
  87. subscription,
  88. });
  89. expect(result).toEqual({
  90. accepted: [],
  91. dropped: [],
  92. projected: [],
  93. onDemand: [
  94. {
  95. value: ['Jan 1', 100],
  96. },
  97. {
  98. value: ['Jan 2', 200],
  99. },
  100. ],
  101. reserved: [
  102. {
  103. value: ['Jan 1', 0],
  104. },
  105. {
  106. value: ['Jan 2', 0],
  107. },
  108. ],
  109. });
  110. });
  111. it('should subtract previous onDemandCostRunningTotal to get periodic', () => {
  112. const stats: BillingStats = [
  113. {
  114. date: '2019-01-01',
  115. ts: '',
  116. accepted: 1,
  117. filtered: 0,
  118. total: 1,
  119. dropped: {total: 0},
  120. onDemandCostRunningTotal: 100,
  121. isProjected: false,
  122. },
  123. {
  124. date: '2019-01-02',
  125. ts: '',
  126. accepted: 2,
  127. filtered: 0,
  128. total: 2,
  129. dropped: {total: 0},
  130. onDemandCostRunningTotal: 200,
  131. isProjected: false,
  132. },
  133. ];
  134. const result = mapCostStatsToChart({
  135. category: 'errors',
  136. stats,
  137. transform: ChartDataTransform.PERIODIC,
  138. subscription,
  139. });
  140. expect(result).toEqual({
  141. accepted: [],
  142. dropped: [],
  143. projected: [],
  144. onDemand: [
  145. {
  146. value: ['Jan 1', 100],
  147. },
  148. {
  149. value: ['Jan 2', 100],
  150. },
  151. ],
  152. reserved: [
  153. {
  154. value: ['Jan 1', 0],
  155. },
  156. {
  157. value: ['Jan 2', 0],
  158. },
  159. ],
  160. });
  161. });
  162. it('should ignore projected days from periodic onDemandCost', () => {
  163. const stats: BillingStats = [
  164. {
  165. date: '2019-01-01',
  166. ts: '',
  167. accepted: 0,
  168. filtered: 0,
  169. total: 0,
  170. dropped: {total: 0},
  171. onDemandCostRunningTotal: 100,
  172. isProjected: false,
  173. },
  174. {
  175. date: '2019-01-02',
  176. ts: '',
  177. accepted: 2,
  178. filtered: 0,
  179. total: 2,
  180. dropped: {total: 0},
  181. onDemandCostRunningTotal: 200,
  182. isProjected: true,
  183. },
  184. ];
  185. const result = mapCostStatsToChart({
  186. category: 'errors',
  187. stats,
  188. transform: ChartDataTransform.PERIODIC,
  189. subscription,
  190. });
  191. expect(result).toEqual({
  192. accepted: [],
  193. dropped: [],
  194. projected: [],
  195. onDemand: [
  196. {
  197. value: ['Jan 1', 100],
  198. },
  199. ],
  200. reserved: expect.any(Array),
  201. });
  202. });
  203. it('should subtract previous onDemandCostRunningTotal for periodic', () => {
  204. const stats: BillingStats = [100, 200, 200, 200].map(
  205. (onDemandCostRunningTotal, i) => ({
  206. date: `2019-01-0${i + 1}`,
  207. ts: '',
  208. accepted: 0,
  209. filtered: 0,
  210. total: 0,
  211. dropped: {total: 0},
  212. onDemandCostRunningTotal,
  213. isProjected: false,
  214. })
  215. );
  216. const result = mapCostStatsToChart({
  217. category: 'errors',
  218. stats,
  219. transform: ChartDataTransform.PERIODIC,
  220. subscription,
  221. });
  222. expect(result.onDemand?.map(item => (item as any).value?.[1])).toEqual([
  223. 100, 100, 0, 0,
  224. ]);
  225. });
  226. });
  227. describe('mapReservedBudgetStatsToChart', () => {
  228. const organization = OrganizationFixture();
  229. const subscription = Am3DsEnterpriseSubscriptionFixture({organization});
  230. it('should map cumulative individual reserved budget stats to chart data', () => {
  231. const statsByDateAndCategory = {
  232. '2019-01-01': {
  233. spans: [
  234. {
  235. date: '2019-01-01',
  236. ts: '',
  237. accepted: 500,
  238. filtered: 0,
  239. total: 500,
  240. dropped: {total: 0},
  241. onDemandCostRunningTotal: 0,
  242. isProjected: false,
  243. },
  244. ],
  245. },
  246. '2019-01-02': {
  247. spans: [
  248. {
  249. date: '2019-01-02',
  250. ts: '',
  251. accepted: 2000,
  252. filtered: 0,
  253. total: 2000,
  254. dropped: {total: 0},
  255. onDemandCostRunningTotal: 500,
  256. isProjected: false,
  257. },
  258. ],
  259. },
  260. '2019-01-03': {
  261. spans: [
  262. {
  263. date: '2019-01-03',
  264. ts: '',
  265. accepted: 2000,
  266. filtered: 0,
  267. total: 2000,
  268. dropped: {total: 0},
  269. onDemandCostRunningTotal: 2500,
  270. isProjected: false,
  271. },
  272. ],
  273. },
  274. };
  275. const reservedBudgetCategoryInfo = {
  276. spans: {
  277. freeBudget: 0,
  278. prepaidBudget: 2000_00,
  279. reservedCpe: 1_00,
  280. reservedSpend: 2000_00,
  281. totalReservedBudget: 2000_00,
  282. },
  283. };
  284. const result = mapReservedBudgetStatsToChart({
  285. statsByDateAndCategory,
  286. transform: ChartDataTransform.CUMULATIVE,
  287. subscription,
  288. reservedBudgetCategoryInfo,
  289. });
  290. expect(result).toEqual({
  291. accepted: [],
  292. dropped: [],
  293. projected: [],
  294. onDemand: [
  295. {
  296. value: ['Jan 1', 0],
  297. },
  298. {
  299. value: ['Jan 2', 500],
  300. },
  301. {
  302. value: ['Jan 3', 2500],
  303. },
  304. ],
  305. reserved: [
  306. {
  307. value: ['Jan 1', 500_00],
  308. },
  309. {
  310. value: ['Jan 2', 2000_00],
  311. },
  312. {
  313. value: ['Jan 3', 2000_00],
  314. },
  315. ],
  316. });
  317. });
  318. it('should map cumulative combined reserved budget stats to chart data', () => {
  319. const statsByDateAndCategory = {
  320. '2019-01-01': {
  321. spans: [
  322. {
  323. date: '2019-01-01',
  324. ts: '',
  325. accepted: 500,
  326. filtered: 0,
  327. total: 500,
  328. dropped: {total: 0},
  329. onDemandCostRunningTotal: 0,
  330. isProjected: false,
  331. },
  332. ],
  333. spansIndexed: [
  334. {
  335. date: '2019-01-01',
  336. ts: '',
  337. accepted: 250,
  338. filtered: 0,
  339. total: 250,
  340. dropped: {total: 0},
  341. onDemandCostRunningTotal: 0,
  342. isProjected: false,
  343. },
  344. ],
  345. },
  346. '2019-01-02': {
  347. spans: [
  348. {
  349. date: '2019-01-02',
  350. ts: '',
  351. accepted: 1000,
  352. filtered: 0,
  353. total: 1000,
  354. dropped: {total: 0},
  355. onDemandCostRunningTotal: 1000,
  356. isProjected: false,
  357. },
  358. ],
  359. spansIndexed: [
  360. {
  361. date: '2019-01-02',
  362. ts: '',
  363. accepted: 1000,
  364. filtered: 0,
  365. total: 1000,
  366. dropped: {total: 0},
  367. onDemandCostRunningTotal: 2000,
  368. isProjected: false,
  369. },
  370. ],
  371. },
  372. '2019-01-03': {
  373. spans: [
  374. {
  375. date: '2019-01-03',
  376. ts: '',
  377. accepted: 2000,
  378. filtered: 0,
  379. total: 2000,
  380. dropped: {total: 0},
  381. onDemandCostRunningTotal: 2000,
  382. isProjected: false,
  383. },
  384. ],
  385. spansIndexed: [
  386. {
  387. date: '2019-01-03',
  388. ts: '',
  389. accepted: 1000,
  390. filtered: 0,
  391. total: 1000,
  392. dropped: {total: 0},
  393. onDemandCostRunningTotal: 4000,
  394. isProjected: false,
  395. },
  396. ],
  397. },
  398. };
  399. const reservedBudgetCategoryInfo = {
  400. spans: {
  401. freeBudget: 0,
  402. prepaidBudget: 2000_00,
  403. reservedCpe: 1_00,
  404. reservedSpend: 1500_00,
  405. totalReservedBudget: 2000_00,
  406. },
  407. spansIndexed: {
  408. freeBudget: 0,
  409. prepaidBudget: 2000_00,
  410. reservedCpe: 2_00,
  411. reservedSpend: 500_00,
  412. totalReservedBudget: 2000_00,
  413. },
  414. };
  415. const result = mapReservedBudgetStatsToChart({
  416. statsByDateAndCategory,
  417. transform: ChartDataTransform.CUMULATIVE,
  418. subscription,
  419. reservedBudgetCategoryInfo,
  420. });
  421. expect(result).toEqual({
  422. accepted: [],
  423. dropped: [],
  424. projected: [],
  425. onDemand: [
  426. {
  427. value: ['Jan 1', 0],
  428. },
  429. {
  430. value: ['Jan 2', 3000],
  431. },
  432. {
  433. value: ['Jan 3', 6000],
  434. },
  435. ],
  436. reserved: [
  437. {
  438. value: ['Jan 1', 1000_00],
  439. },
  440. {
  441. value: ['Jan 2', 2000_00],
  442. },
  443. {
  444. value: ['Jan 3', 2000_00],
  445. },
  446. ],
  447. });
  448. });
  449. it('should map periodic individual reserved budget stats to chart data', () => {
  450. const statsByDateAndCategory = {
  451. '2019-01-01': {
  452. spans: [
  453. {
  454. date: '2019-01-01',
  455. ts: '',
  456. accepted: 500,
  457. filtered: 0,
  458. total: 500,
  459. dropped: {total: 0},
  460. onDemandCostRunningTotal: 0,
  461. isProjected: false,
  462. },
  463. ],
  464. },
  465. '2019-01-02': {
  466. spans: [
  467. {
  468. date: '2019-01-02',
  469. ts: '',
  470. accepted: 1500,
  471. filtered: 0,
  472. total: 1500,
  473. dropped: {total: 0},
  474. onDemandCostRunningTotal: 0,
  475. isProjected: false,
  476. },
  477. ],
  478. },
  479. };
  480. const reservedBudgetCategoryInfo = {
  481. spans: {
  482. freeBudget: 0,
  483. prepaidBudget: 2000_00,
  484. reservedCpe: 1_00,
  485. reservedSpend: 2000_00,
  486. totalReservedBudget: 2000_00,
  487. },
  488. };
  489. const result = mapReservedBudgetStatsToChart({
  490. statsByDateAndCategory,
  491. transform: ChartDataTransform.PERIODIC,
  492. subscription,
  493. reservedBudgetCategoryInfo,
  494. });
  495. expect(result).toEqual({
  496. accepted: [],
  497. dropped: [],
  498. projected: [],
  499. onDemand: [
  500. {
  501. value: ['Jan 1', 0],
  502. },
  503. {
  504. value: ['Jan 2', 0],
  505. },
  506. ],
  507. reserved: [
  508. {
  509. value: ['Jan 1', 500_00],
  510. },
  511. {
  512. value: ['Jan 2', 1500_00],
  513. },
  514. ],
  515. });
  516. });
  517. it('should map periodic combined reserved budget stats to chart data', () => {
  518. const statsByDateAndCategory = {
  519. '2019-01-01': {
  520. spans: [
  521. {
  522. date: '2019-01-01',
  523. ts: '',
  524. accepted: 500,
  525. filtered: 0,
  526. total: 500,
  527. dropped: {total: 0},
  528. onDemandCostRunningTotal: 0,
  529. isProjected: false,
  530. },
  531. ],
  532. spansIndexed: [
  533. {
  534. date: '2019-01-01',
  535. ts: '',
  536. accepted: 250,
  537. filtered: 0,
  538. total: 250,
  539. dropped: {total: 0},
  540. onDemandCostRunningTotal: 0,
  541. isProjected: false,
  542. },
  543. ],
  544. },
  545. '2019-01-02': {
  546. spans: [
  547. {
  548. date: '2019-01-02',
  549. ts: '',
  550. accepted: 750,
  551. filtered: 0,
  552. total: 750,
  553. dropped: {total: 0},
  554. onDemandCostRunningTotal: 0,
  555. isProjected: false,
  556. },
  557. ],
  558. spansIndexed: [
  559. {
  560. date: '2019-01-02',
  561. ts: '',
  562. accepted: 125,
  563. filtered: 0,
  564. total: 125,
  565. dropped: {total: 0},
  566. onDemandCostRunningTotal: 0,
  567. isProjected: false,
  568. },
  569. ],
  570. },
  571. };
  572. const reservedBudgetCategoryInfo = {
  573. spans: {
  574. freeBudget: 0,
  575. prepaidBudget: 2000_00,
  576. reservedCpe: 1_00,
  577. reservedSpend: 1250_00,
  578. totalReservedBudget: 2000_00,
  579. },
  580. spansIndexed: {
  581. freeBudget: 0,
  582. prepaidBudget: 2000_00,
  583. reservedCpe: 2_00,
  584. reservedSpend: 750_00,
  585. totalReservedBudget: 2000_00,
  586. },
  587. };
  588. const result = mapReservedBudgetStatsToChart({
  589. statsByDateAndCategory,
  590. transform: ChartDataTransform.PERIODIC,
  591. subscription,
  592. reservedBudgetCategoryInfo,
  593. });
  594. expect(result).toEqual({
  595. accepted: [],
  596. dropped: [],
  597. projected: [],
  598. onDemand: [
  599. {
  600. value: ['Jan 1', 0],
  601. },
  602. {
  603. value: ['Jan 2', 0],
  604. },
  605. ],
  606. reserved: [
  607. {
  608. value: ['Jan 1', 1000_00],
  609. },
  610. {
  611. value: ['Jan 2', 1000_00],
  612. },
  613. ],
  614. });
  615. });
  616. });
  617. describe('getCategoryOptions', () => {
  618. const organization = OrganizationFixture({access: ['org:billing']});
  619. it('should return am3 categories', () => {
  620. const subscription = SubscriptionFixture({
  621. plan: 'am3_f',
  622. planTier: PlanTier.AM3,
  623. organization,
  624. });
  625. const result = getCategoryOptions({
  626. plan: subscription.planDetails,
  627. hadCustomDynamicSampling: subscription.hadCustomDynamicSampling,
  628. });
  629. result.forEach(option => {
  630. expect(subscription.planDetails.checkoutCategories).toContain(option.value);
  631. });
  632. });
  633. it('should return am3 categories with stored spans for custom dynamic sampling', () => {
  634. const subscription = SubscriptionFixture({
  635. plan: 'am3_f',
  636. planTier: PlanTier.AM3,
  637. organization,
  638. hadCustomDynamicSampling: true,
  639. });
  640. const result = getCategoryOptions({
  641. plan: subscription.planDetails,
  642. hadCustomDynamicSampling: subscription.hadCustomDynamicSampling,
  643. });
  644. result.forEach(option => {
  645. expect(subscription.planDetails.categories).toContain(option.value);
  646. });
  647. });
  648. it('should return am2 categories', () => {
  649. const subscription = SubscriptionFixture({
  650. plan: 'am2_f',
  651. planTier: PlanTier.AM2,
  652. organization,
  653. });
  654. const result = getCategoryOptions({
  655. plan: subscription.planDetails,
  656. hadCustomDynamicSampling: subscription.hadCustomDynamicSampling,
  657. });
  658. result.forEach(option => {
  659. expect(subscription.planDetails.categories).toContain(option.value);
  660. });
  661. });
  662. it('should return am1 categories', () => {
  663. const subscription = SubscriptionFixture({
  664. plan: 'am1_f',
  665. planTier: PlanTier.AM1,
  666. organization,
  667. });
  668. const result = getCategoryOptions({
  669. plan: subscription.planDetails,
  670. hadCustomDynamicSampling: subscription.hadCustomDynamicSampling,
  671. });
  672. result.forEach(option => {
  673. expect(subscription.planDetails.categories).toContain(option.value);
  674. });
  675. });
  676. it('should return mm2 categories', () => {
  677. const subscription = SubscriptionFixture({
  678. plan: 'mm2_f',
  679. planTier: PlanTier.MM2,
  680. organization,
  681. });
  682. const result = getCategoryOptions({
  683. plan: subscription.planDetails,
  684. hadCustomDynamicSampling: subscription.hadCustomDynamicSampling,
  685. });
  686. result.forEach(option => {
  687. expect(subscription.planDetails.categories).toContain(option.value);
  688. });
  689. });
  690. });