From 3cd51e3f9a0c03fc7e1ac58461b2e86a91084be0 Mon Sep 17 00:00:00 2001 From: Alberto Leal Date: Mon, 24 Nov 2025 21:42:28 -0500 Subject: [PATCH] feat(billing): Migrate chart functions to use DATA_CATEGORY_INFO formatting - Update mapReservedToChart to use formatting.reservedMultiplier instead of isByteCategory() check with GIGABYTE constant - Update formatProjected to use formatting.projectedAbbreviated instead of hardcoded DataCategory.ATTACHMENTS check - Export mapReservedToChart and add unit tests for multiplier logic Part of BIL-952: Move formatting functions to DATA_CATEGORY_INFO --- .../reservedUsageChart.spec.tsx | 45 +++++++++++++++++++ .../subscriptionPage/reservedUsageChart.tsx | 12 +++-- .../views/subscriptionPage/usageAlert.tsx | 17 ++++--- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/static/gsApp/views/subscriptionPage/reservedUsageChart.spec.tsx b/static/gsApp/views/subscriptionPage/reservedUsageChart.spec.tsx index b22d12c892e53c..7f7d2638f51855 100644 --- a/static/gsApp/views/subscriptionPage/reservedUsageChart.spec.tsx +++ b/static/gsApp/views/subscriptionPage/reservedUsageChart.spec.tsx @@ -10,12 +10,15 @@ import {act, render, screen} from 'sentry-test/reactTestingLibrary'; import {DataCategory} from 'sentry/types/core'; import {ChartDataTransform} from 'sentry/views/organizationStats/usageChart'; +import {GIGABYTE} from 'getsentry/constants'; import {PlanTier, type BillingStats} from 'getsentry/types'; +import {MILLISECONDS_IN_HOUR} from 'getsentry/utils/billing'; import ReservedUsageChart, { getCategoryOptions, mapCostStatsToChart, mapReservedBudgetStatsToChart, + mapReservedToChart, mapStatsToChart, } from './reservedUsageChart'; @@ -62,6 +65,48 @@ describe('mapStatsToChart', () => { }); }); +describe('mapReservedToChart', () => { + it('should apply GIGABYTE multiplier for byte categories (attachments)', () => { + const reserved = 5; // 5 GB + const result = mapReservedToChart(reserved, DataCategory.ATTACHMENTS); + expect(result).toBe(reserved * GIGABYTE); + }); + + it('should apply GIGABYTE multiplier for byte categories (log bytes)', () => { + const reserved = 10; // 10 GB + const result = mapReservedToChart(reserved, DataCategory.LOG_BYTE); + expect(result).toBe(reserved * GIGABYTE); + }); + + it('should apply MILLISECONDS_IN_HOUR multiplier for duration categories', () => { + const reserved = 100; // 100 hours + const result = mapReservedToChart(reserved, DataCategory.PROFILE_DURATION); + expect(result).toBe(reserved * MILLISECONDS_IN_HOUR); + }); + + it('should apply multiplier of 1 for count categories (errors)', () => { + const reserved = 50000; + const result = mapReservedToChart(reserved, DataCategory.ERRORS); + expect(result).toBe(reserved); + }); + + it('should apply multiplier of 1 for count categories (transactions)', () => { + const reserved = 100000; + const result = mapReservedToChart(reserved, DataCategory.TRANSACTIONS); + expect(result).toBe(reserved); + }); + + it('should return 0 for unlimited reserved (-1)', () => { + const result = mapReservedToChart(-1, DataCategory.ERRORS); + expect(result).toBe(0); + }); + + it('should return 0 for null reserved', () => { + const result = mapReservedToChart(null, DataCategory.ERRORS); + expect(result).toBe(0); + }); +}); + describe('mapCostStatsToChart', () => { const organization = OrganizationFixture(); const subscription = SubscriptionFixture({ diff --git a/static/gsApp/views/subscriptionPage/reservedUsageChart.tsx b/static/gsApp/views/subscriptionPage/reservedUsageChart.tsx index 100a03a8547459..8d325b81de5b5d 100644 --- a/static/gsApp/views/subscriptionPage/reservedUsageChart.tsx +++ b/static/gsApp/views/subscriptionPage/reservedUsageChart.tsx @@ -33,7 +33,6 @@ import { getTooltipFormatter, } from 'sentry/views/organizationStats/usageChart/utils'; -import {GIGABYTE} from 'getsentry/constants'; import { ReservedBudgetCategoryType, type BillingMetricHistory, @@ -50,9 +49,9 @@ import { isUnlimitedReserved, } from 'getsentry/utils/billing'; import { + getCategoryInfoFromPlural, getPlanCategoryName, hasCategoryFeature, - isByteCategory, isPartOfReservedBudget, } from 'getsentry/utils/dataCategory'; import formatCurrency from 'getsentry/utils/formatCurrency'; @@ -181,15 +180,14 @@ function chartTooltip(category: DataCategory, displayMode: 'usage' | 'cost') { }); } -function mapReservedToChart(reserved: number | null, category: DataCategory) { +export function mapReservedToChart(reserved: number | null, category: DataCategory) { if (isUnlimitedReserved(reserved)) { return 0; } - if (isByteCategory(category)) { - return typeof reserved === 'number' ? reserved * GIGABYTE : 0; - } - return reserved || 0; + const categoryInfo = getCategoryInfoFromPlural(category); + const multiplier = categoryInfo?.formatting.reservedMultiplier ?? 1; + return typeof reserved === 'number' ? reserved * multiplier : 0; } function defaultChartData(): ChartStats { diff --git a/static/gsApp/views/subscriptionPage/usageAlert.tsx b/static/gsApp/views/subscriptionPage/usageAlert.tsx index 78e83b60bb63a8..292ecee2161347 100644 --- a/static/gsApp/views/subscriptionPage/usageAlert.tsx +++ b/static/gsApp/views/subscriptionPage/usageAlert.tsx @@ -24,7 +24,11 @@ import { isUnlimitedReserved, UsageAction, } from 'getsentry/utils/billing'; -import {getPlanCategoryName, sortCategoriesWithKeys} from 'getsentry/utils/dataCategory'; +import { + getCategoryInfoFromPlural, + getPlanCategoryName, + sortCategoriesWithKeys, +} from 'getsentry/utils/dataCategory'; import {ButtonWrapper, SubscriptionBody} from './styles'; @@ -64,13 +68,16 @@ function UsageAlert({subscription, usage}: Props) { hadCustomDynamicSampling: subscription.hadCustomDynamicSampling, }); + const categoryInfo = getCategoryInfoFromPlural(category); + const isAbbreviated = categoryInfo?.formatting.projectedAbbreviated ?? true; + const formattedAmount = formatReservedWithUnits(projected, category, { - isAbbreviated: category !== DataCategory.ATTACHMENTS, + isAbbreviated, }); - return category === DataCategory.ATTACHMENTS - ? `${formattedAmount} of attachments` - : `${formattedAmount} ${displayName}`; + return isAbbreviated + ? `${formattedAmount} ${displayName}` + : `${formattedAmount} of ${displayName}`; } function projectedCategoryOverages() {