From 7b9ca10f4fcdd072a62eb233775b090110e3af28 Mon Sep 17 00:00:00 2001 From: Volo Kluev Date: Thu, 18 Jun 2026 22:55:51 +0000 Subject: [PATCH 1/3] ref: wrap BILLED_DATA_CATEGORY_INFO behind getBilledDataCategoryInfo() Prepare for moving billed data category info to a backend call by hiding the static constant behind a function. All consumers now call getBilledDataCategoryInfo() instead of importing the constant directly. --- .../gsAdmin/components/addGiftEventsAction.spec.tsx | 4 ++-- .../components/customers/customerOverview.tsx | 4 ++-- static/gsApp/components/gsBanner.tsx | 6 +++--- static/gsApp/constants.tsx | 13 ++++++++++++- static/gsApp/utils/dataCategory.tsx | 6 ++++-- .../spendAllocations/components/allocationForm.tsx | 6 +++--- 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/static/gsAdmin/components/addGiftEventsAction.spec.tsx b/static/gsAdmin/components/addGiftEventsAction.spec.tsx index 75958275b543..20401624986e 100644 --- a/static/gsAdmin/components/addGiftEventsAction.spec.tsx +++ b/static/gsAdmin/components/addGiftEventsAction.spec.tsx @@ -7,7 +7,7 @@ import {DataCategory, DataCategoryExact} from 'sentry/types/core'; import {AddGiftEventsAction} from 'admin/components/addGiftEventsAction'; import {openAdminConfirmModal} from 'admin/components/adminConfirmationModal'; -import {BILLED_DATA_CATEGORY_INFO} from 'getsentry/constants'; +import {getBilledDataCategoryInfo} from 'getsentry/constants'; import {getCategoryInfoFromPlural} from 'getsentry/utils/dataCategory'; describe('AddGiftEventsAction', () => { @@ -18,7 +18,7 @@ describe('AddGiftEventsAction', () => { const triggerGiftModal = () => { const billedCategoryInfo = categoryOverride ? getCategoryInfoFromPlural(categoryOverride) - : BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SPAN]; + : getBilledDataCategoryInfo()[DataCategoryExact.SPAN]; openAdminConfirmModal({ renderModalSpecificContent: deps => ( { // Category must be in the plan's categories if (!customer.planDetails?.categories.includes(categoryInfo.plural)) { diff --git a/static/gsApp/components/gsBanner.tsx b/static/gsApp/components/gsBanner.tsx index 5fedcb7be4bb..05407685cd21 100644 --- a/static/gsApp/components/gsBanner.tsx +++ b/static/gsApp/components/gsBanner.tsx @@ -44,7 +44,7 @@ import {ProductTrialAlert} from 'getsentry/components/productTrial/productTrialA import {getProductForPath} from 'getsentry/components/productTrial/productTrialPaths'; import {makeLinkToOwnersAndBillingMembers} from 'getsentry/components/profiling/alerts'; import {withSubscription} from 'getsentry/components/withSubscription'; -import {BILLED_DATA_CATEGORY_INFO} from 'getsentry/constants'; +import {getBilledDataCategoryInfo} from 'getsentry/constants'; import {SubscriptionStore} from 'getsentry/stores/subscriptionStore'; import { type BilledDataCategoryInfo, @@ -80,7 +80,7 @@ enum ModalType { const TRIAL_ENDING_DAY_WINDOW = 3; function objectFromBilledCategories(callback: (c: BilledDataCategoryInfo) => any) { - return Object.values(BILLED_DATA_CATEGORY_INFO).reduce( + return Object.values(getBilledDataCategoryInfo()).reduce( (acc, c) => { if (c.isBilledCategory) { acc[c.singular as EventType] = callback(c); @@ -634,7 +634,7 @@ class GSBanner extends Component { const category_warning_prompts: string[] = []; const category_product_trial_prompts: string[] = []; - Object.values(BILLED_DATA_CATEGORY_INFO) + Object.values(getBilledDataCategoryInfo()) .filter( categoryInfo => categoryInfo.isBilledCategory && diff --git a/static/gsApp/constants.tsx b/static/gsApp/constants.tsx index 76cf0c1174a8..a365725bc5bb 100644 --- a/static/gsApp/constants.tsx +++ b/static/gsApp/constants.tsx @@ -77,7 +77,7 @@ Object.entries(DEFAULT_BILLED_DATA_CATEGORY_INFO).forEach( * All categories with isBilledCategory: true, should be explicitly * added to this object with billing info. */ -export const BILLED_DATA_CATEGORY_INFO = { +const BILLED_DATA_CATEGORY_INFO = { ...DEFAULT_BILLED_DATA_CATEGORY_INFO, [DataCategoryExact.ERROR]: { ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.ERROR], @@ -234,3 +234,14 @@ export const BILLED_DATA_CATEGORY_INFO = { adminOnlyProductTrialFeature: 'expose-category-installable-build', }, } as const satisfies Record; + +/** + * Returns billing-enriched data category info for all categories. + * This wraps the static constant so it can later be replaced with a backend call. + */ +export function getBilledDataCategoryInfo(): Record< + DataCategoryExact, + BilledDataCategoryInfo +> { + return BILLED_DATA_CATEGORY_INFO; +} diff --git a/static/gsApp/utils/dataCategory.tsx b/static/gsApp/utils/dataCategory.tsx index 157f8b8b4219..faa63fc1b04a 100644 --- a/static/gsApp/utils/dataCategory.tsx +++ b/static/gsApp/utils/dataCategory.tsx @@ -6,7 +6,7 @@ import {DataCategory, DataCategoryExact} from 'sentry/types/core'; import {oxfordizeArray} from 'sentry/utils/oxfordizeArray'; import {toTitleCase} from 'sentry/utils/string/toTitleCase'; -import {BILLED_DATA_CATEGORY_INFO, UNLIMITED_RESERVED} from 'getsentry/constants'; +import {getBilledDataCategoryInfo, UNLIMITED_RESERVED} from 'getsentry/constants'; import type { BilledDataCategoryInfo, BillingMetricHistory, @@ -28,7 +28,9 @@ import {MILLISECONDS_IN_HOUR} from 'getsentry/utils/billing'; export function getCategoryInfoFromPlural( category: DataCategory ): BilledDataCategoryInfo | null { - const info = Object.values(BILLED_DATA_CATEGORY_INFO).find(c => c.plural === category); + const info = Object.values(getBilledDataCategoryInfo()).find( + c => c.plural === category + ); if (!info) { return null; } diff --git a/static/gsApp/views/spendAllocations/components/allocationForm.tsx b/static/gsApp/views/spendAllocations/components/allocationForm.tsx index 03b5cb254366..774c0f1c82ae 100644 --- a/static/gsApp/views/spendAllocations/components/allocationForm.tsx +++ b/static/gsApp/views/spendAllocations/components/allocationForm.tsx @@ -20,7 +20,7 @@ import type {RequestMethod} from 'sentry/utils/api/apiQueryKey'; import {useApi} from 'sentry/utils/useApi'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {AllocationTargetTypes, BILLED_DATA_CATEGORY_INFO} from 'getsentry/constants'; +import {AllocationTargetTypes, getBilledDataCategoryInfo} from 'getsentry/constants'; import type {Subscription} from 'getsentry/types'; import { getCategoryInfoFromPlural, @@ -541,10 +541,10 @@ const Select = styled(SelectField)` } `; -// Normalizes singular billingMetric values to match DataCategory enum using BILLED_DATA_CATEGORY_INFO +// Normalizes singular billingMetric values to match DataCategory enum using getBilledDataCategoryInfo() function normalizeBillingMetric(metric: string): DataCategory { return ( - Object.values(BILLED_DATA_CATEGORY_INFO) + Object.values(getBilledDataCategoryInfo()) .filter(info => info.canAllocate) .find(c => c.name === metric)?.plural ?? (metric as DataCategory) ); From 4c99390876da6b63d0bd89ff2d67bfdd73b62e30 Mon Sep 17 00:00:00 2001 From: Volo Kluev Date: Thu, 18 Jun 2026 23:06:17 +0000 Subject: [PATCH 2/3] ref: move getBilledDataCategoryInfo() to utils/dataCategory Address review feedback: the function is not a constant, so move it out of constants.tsx into utils/dataCategory.tsx where related helpers already live. The constant remains exported from constants.tsx and the function imports it from there. --- .../components/addGiftEventsAction.spec.tsx | 6 ++++-- .../components/customers/customerOverview.tsx | 3 ++- static/gsApp/components/gsBanner.tsx | 6 ++++-- static/gsApp/constants.tsx | 13 +------------ static/gsApp/utils/dataCategory.tsx | 17 +++++++++++++---- .../components/allocationForm.tsx | 3 ++- 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/static/gsAdmin/components/addGiftEventsAction.spec.tsx b/static/gsAdmin/components/addGiftEventsAction.spec.tsx index 20401624986e..522eee186d42 100644 --- a/static/gsAdmin/components/addGiftEventsAction.spec.tsx +++ b/static/gsAdmin/components/addGiftEventsAction.spec.tsx @@ -7,8 +7,10 @@ import {DataCategory, DataCategoryExact} from 'sentry/types/core'; import {AddGiftEventsAction} from 'admin/components/addGiftEventsAction'; import {openAdminConfirmModal} from 'admin/components/adminConfirmationModal'; -import {getBilledDataCategoryInfo} from 'getsentry/constants'; -import {getCategoryInfoFromPlural} from 'getsentry/utils/dataCategory'; +import { + getBilledDataCategoryInfo, + getCategoryInfoFromPlural, +} from 'getsentry/utils/dataCategory'; describe('AddGiftEventsAction', () => { const mockOrg = OrganizationFixture(); diff --git a/static/gsAdmin/components/customers/customerOverview.tsx b/static/gsAdmin/components/customers/customerOverview.tsx index 6ea8edc1b25d..fbcb25cd702b 100644 --- a/static/gsAdmin/components/customers/customerOverview.tsx +++ b/static/gsAdmin/components/customers/customerOverview.tsx @@ -28,7 +28,7 @@ import {DetailList} from 'admin/components/detailList'; import {DetailsContainer} from 'admin/components/detailsContainer'; import {ExtendProductTrialAction} from 'admin/components/extendProductTrialAction'; import {getLogQuery} from 'admin/utils'; -import {getBilledDataCategoryInfo, UNLIMITED} from 'getsentry/constants'; +import {UNLIMITED} from 'getsentry/constants'; import type { Plan, ReservedBudget, @@ -46,6 +46,7 @@ import { RETENTION_SETTINGS_CATEGORIES, } from 'getsentry/utils/billing'; import { + getBilledDataCategoryInfo, getPlanCategoryName, getReservedBudgetDisplayName, sortCategories, diff --git a/static/gsApp/components/gsBanner.tsx b/static/gsApp/components/gsBanner.tsx index 05407685cd21..420dd9beb43f 100644 --- a/static/gsApp/components/gsBanner.tsx +++ b/static/gsApp/components/gsBanner.tsx @@ -44,7 +44,6 @@ import {ProductTrialAlert} from 'getsentry/components/productTrial/productTrialA import {getProductForPath} from 'getsentry/components/productTrial/productTrialPaths'; import {makeLinkToOwnersAndBillingMembers} from 'getsentry/components/profiling/alerts'; import {withSubscription} from 'getsentry/components/withSubscription'; -import {getBilledDataCategoryInfo} from 'getsentry/constants'; import {SubscriptionStore} from 'getsentry/stores/subscriptionStore'; import { type BilledDataCategoryInfo, @@ -62,7 +61,10 @@ import { partnerPlanEndingModalIsDismissed, trialPromptIsDismissed, } from 'getsentry/utils/billing'; -import {getCategoryInfoFromPlural} from 'getsentry/utils/dataCategory'; +import { + getBilledDataCategoryInfo, + getCategoryInfoFromPlural, +} from 'getsentry/utils/dataCategory'; import {getPendoAccountFields} from 'getsentry/utils/pendo'; import {claimAvailablePromotion} from 'getsentry/utils/promotionUtils'; import {trackGetsentryAnalytics} from 'getsentry/utils/trackGetsentryAnalytics'; diff --git a/static/gsApp/constants.tsx b/static/gsApp/constants.tsx index a365725bc5bb..76cf0c1174a8 100644 --- a/static/gsApp/constants.tsx +++ b/static/gsApp/constants.tsx @@ -77,7 +77,7 @@ Object.entries(DEFAULT_BILLED_DATA_CATEGORY_INFO).forEach( * All categories with isBilledCategory: true, should be explicitly * added to this object with billing info. */ -const BILLED_DATA_CATEGORY_INFO = { +export const BILLED_DATA_CATEGORY_INFO = { ...DEFAULT_BILLED_DATA_CATEGORY_INFO, [DataCategoryExact.ERROR]: { ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.ERROR], @@ -234,14 +234,3 @@ const BILLED_DATA_CATEGORY_INFO = { adminOnlyProductTrialFeature: 'expose-category-installable-build', }, } as const satisfies Record; - -/** - * Returns billing-enriched data category info for all categories. - * This wraps the static constant so it can later be replaced with a backend call. - */ -export function getBilledDataCategoryInfo(): Record< - DataCategoryExact, - BilledDataCategoryInfo -> { - return BILLED_DATA_CATEGORY_INFO; -} diff --git a/static/gsApp/utils/dataCategory.tsx b/static/gsApp/utils/dataCategory.tsx index faa63fc1b04a..9af7535c3428 100644 --- a/static/gsApp/utils/dataCategory.tsx +++ b/static/gsApp/utils/dataCategory.tsx @@ -6,7 +6,7 @@ import {DataCategory, DataCategoryExact} from 'sentry/types/core'; import {oxfordizeArray} from 'sentry/utils/oxfordizeArray'; import {toTitleCase} from 'sentry/utils/string/toTitleCase'; -import {getBilledDataCategoryInfo, UNLIMITED_RESERVED} from 'getsentry/constants'; +import {BILLED_DATA_CATEGORY_INFO, UNLIMITED_RESERVED} from 'getsentry/constants'; import type { BilledDataCategoryInfo, BillingMetricHistory, @@ -19,6 +19,17 @@ import type { } from 'getsentry/types'; import {MILLISECONDS_IN_HOUR} from 'getsentry/utils/billing'; +/** + * Returns billing-enriched data category info for all categories. + * This wraps the static constant so it can later be replaced with a backend call. + */ +export function getBilledDataCategoryInfo(): Record< + DataCategoryExact, + BilledDataCategoryInfo +> { + return BILLED_DATA_CATEGORY_INFO; +} + /** * Returns the data category info defined in DATA_CATEGORY_INFO for the given category, * with billing context defined in BILLED_DATA_CATEGORY_INFO. @@ -28,9 +39,7 @@ import {MILLISECONDS_IN_HOUR} from 'getsentry/utils/billing'; export function getCategoryInfoFromPlural( category: DataCategory ): BilledDataCategoryInfo | null { - const info = Object.values(getBilledDataCategoryInfo()).find( - c => c.plural === category - ); + const info = Object.values(BILLED_DATA_CATEGORY_INFO).find(c => c.plural === category); if (!info) { return null; } diff --git a/static/gsApp/views/spendAllocations/components/allocationForm.tsx b/static/gsApp/views/spendAllocations/components/allocationForm.tsx index 774c0f1c82ae..f20748247ac7 100644 --- a/static/gsApp/views/spendAllocations/components/allocationForm.tsx +++ b/static/gsApp/views/spendAllocations/components/allocationForm.tsx @@ -20,9 +20,10 @@ import type {RequestMethod} from 'sentry/utils/api/apiQueryKey'; import {useApi} from 'sentry/utils/useApi'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {AllocationTargetTypes, getBilledDataCategoryInfo} from 'getsentry/constants'; +import {AllocationTargetTypes} from 'getsentry/constants'; import type {Subscription} from 'getsentry/types'; import { + getBilledDataCategoryInfo, getCategoryInfoFromPlural, getPlanCategoryName, } from 'getsentry/utils/dataCategory'; From 0dac2236c1ab80c83d4aabf7aacf6ce791e01092 Mon Sep 17 00:00:00 2001 From: Volo Kluev Date: Thu, 18 Jun 2026 23:10:04 +0000 Subject: [PATCH 3/3] ref: move BILLED_DATA_CATEGORY_INFO constant into utils/dataCategory Move the entire constant definition out of constants.tsx into utils/dataCategory.tsx alongside getBilledDataCategoryInfo(). The constant is now private to dataCategory.tsx and only accessible through the function. --- static/gsApp/constants.tsx | 192 ---------------------------- static/gsApp/utils/dataCategory.tsx | 188 ++++++++++++++++++++++++++- 2 files changed, 187 insertions(+), 193 deletions(-) diff --git a/static/gsApp/constants.tsx b/static/gsApp/constants.tsx index 76cf0c1174a8..e6bddc4f6648 100644 --- a/static/gsApp/constants.tsx +++ b/static/gsApp/constants.tsx @@ -1,9 +1,3 @@ -import {DATA_CATEGORY_INFO} from 'sentry/constants'; -import {t} from 'sentry/locale'; -import {DataCategoryExact} from 'sentry/types/core'; - -import type {BilledDataCategoryInfo} from 'getsentry/types'; - export const MONTHLY = 'monthly'; export const ANNUAL = 'annual'; @@ -48,189 +42,3 @@ export enum AllocationTargetTypes { PROJECT = 'Project', ORGANIZATION = 'Organization', } - -// XXX: initialize the BilledDataCategoryInfo-specific field for all non-billed -// `categories and make TS happy so we can access the BilledDataCategoryInfo -// fields directly without needing to check that they exist on the object -const DEFAULT_BILLED_DATA_CATEGORY_INFO = { - ...DATA_CATEGORY_INFO, -} as Record; -Object.entries(DEFAULT_BILLED_DATA_CATEGORY_INFO).forEach( - ([categoryExact, categoryInfo]) => { - DEFAULT_BILLED_DATA_CATEGORY_INFO[categoryExact as DataCategoryExact] = { - ...categoryInfo, - canAllocate: false, - canProductTrial: false, - freeEventsMultiple: 0, - feature: null, - hasSpikeProtection: false, - checkoutTooltip: null, - tallyType: 'usage', - hasPerCategory: false, - adminOnlyProductTrialFeature: null, - }; - } -); - -/** - * Extension of DATA_CATEGORY_INFO with billing info for billed categories. - * All categories with isBilledCategory: true, should be explicitly - * added to this object with billing info. - */ -export const BILLED_DATA_CATEGORY_INFO = { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO, - [DataCategoryExact.ERROR]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.ERROR], - canAllocate: true, - freeEventsMultiple: 1_000, - hasSpikeProtection: true, - checkoutTooltip: t( - 'Errors are sent every time an SDK catches a bug. You can send them manually too, if you want.' - ), - hasPerCategory: true, - }, - [DataCategoryExact.TRANSACTION]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.TRANSACTION], - canAllocate: true, - canProductTrial: true, - freeEventsMultiple: 1_000, - feature: 'performance-view', - hasSpikeProtection: true, - checkoutTooltip: t( - 'Transactions are sent when your service receives a request and sends a response.' - ), - hasPerCategory: true, - shortenedUnitName: t('unit'), - }, - [DataCategoryExact.ATTACHMENT]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.ATTACHMENT], - canAllocate: true, - freeEventsMultiple: 1, - feature: 'event-attachments', - hasSpikeProtection: true, - checkoutTooltip: t('Attachments are files attached to errors, such as minidumps.'), - hasPerCategory: true, - shortenedUnitName: 'GB', - }, - [DataCategoryExact.REPLAY]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.REPLAY], - canProductTrial: true, - freeEventsMultiple: 1, - feature: 'session-replay', - checkoutTooltip: t( - 'Session Replays are video-like reproductions of your users’ sessions navigating your app or website.' - ), - hasPerCategory: true, - }, - [DataCategoryExact.SPAN]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SPAN], - canProductTrial: true, - freeEventsMultiple: 100_000, - feature: 'spans-usage-tracking', - hasSpikeProtection: true, - checkoutTooltip: t( - 'Tracing is enabled by spans. A span represents a single operation of work within a trace.' - ), - }, - [DataCategoryExact.SPAN_INDEXED]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SPAN_INDEXED], - canProductTrial: true, - freeEventsMultiple: 100_000, - feature: 'spans-usage-tracking', - }, - [DataCategoryExact.MONITOR_SEAT]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.MONITOR_SEAT], - freeEventsMultiple: 1, - feature: 'monitor-seat-billing', - tallyType: 'seat', - hasPerCategory: true, - checkoutTooltip: t( - 'Crons monitors scheduled jobs to confirm they run on time and alert you when they fail or misfire.' - ), - shortenedUnitName: t('monitor'), - }, - [DataCategoryExact.UPTIME]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.UPTIME], - freeEventsMultiple: 1, - feature: 'uptime-billing', - tallyType: 'seat', - hasPerCategory: true, - checkoutTooltip: t( - 'Uptime monitoring checks your application’s availability and alerts you when services go down so you can respond quickly.' - ), - shortenedUnitName: t('monitor'), - }, - [DataCategoryExact.PROFILE_DURATION]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.PROFILE_DURATION], - canProductTrial: true, - freeEventsMultiple: 1, // in hours - hasPerCategory: true, - checkoutTooltip: t( - 'Continuous profiling tracks how code runs while your service is active, helping you find bottlenecks and improve efficiency.' - ), - shortenedUnitName: t('hour'), - }, - [DataCategoryExact.PROFILE_DURATION_UI]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.PROFILE_DURATION_UI], - canProductTrial: true, - freeEventsMultiple: 1, // in hours - hasPerCategory: true, - checkoutTooltip: t( - 'UI profiling tracks code performance during user sessions in frontend or mobile apps, helping you spot slowdowns and improve experience.' - ), - shortenedUnitName: t('hour'), - }, - // Seer categories have product trials through ReservedBudgetCategoryType.SEER, not as individual categories - [DataCategoryExact.SEER_AUTOFIX]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SEER_AUTOFIX], - feature: 'seer-billing', - shortenedUnitName: t('fix'), - }, - [DataCategoryExact.SEER_SCANNER]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SEER_SCANNER], - feature: 'seer-billing', - shortenedUnitName: t('scan'), - }, - [DataCategoryExact.LOG_BYTE]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.LOG_BYTE], - canAllocate: false, - canProductTrial: true, - freeEventsMultiple: 1, - hasSpikeProtection: false, - feature: 'logs-billing', - checkoutTooltip: t( - 'A log records events from your application, giving you the context to debug issues and understand system behavior.' - ), - shortenedUnitName: 'GB', - }, - [DataCategoryExact.TRACE_METRIC_BYTE]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.TRACE_METRIC_BYTE], - canProductTrial: true, - freeEventsMultiple: 1, - feature: 'expose-category-trace-metric-byte', - shortenedUnitName: 'GB', - checkoutTooltip: t( - 'Application Metrics capture key signals from your application using counters, gauges, and distributions.' - ), - }, - [DataCategoryExact.SEER_USER]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SEER_USER], - feature: 'seer-user-billing-launch', - canProductTrial: false, - freeEventsMultiple: 1, - tallyType: 'seat', - shortenedUnitName: t('contributor'), - }, - [DataCategoryExact.SIZE_ANALYSIS]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SIZE_ANALYSIS], - freeEventsMultiple: 1, - shortenedUnitName: t('build'), - adminOnlyProductTrialFeature: true, - }, - [DataCategoryExact.INSTALLABLE_BUILD]: { - ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.INSTALLABLE_BUILD], - freeEventsMultiple: 1, - shortenedUnitName: t('install'), - adminOnlyProductTrialFeature: 'expose-category-installable-build', - }, -} as const satisfies Record; diff --git a/static/gsApp/utils/dataCategory.tsx b/static/gsApp/utils/dataCategory.tsx index 9af7535c3428..75cd71e3fca9 100644 --- a/static/gsApp/utils/dataCategory.tsx +++ b/static/gsApp/utils/dataCategory.tsx @@ -6,7 +6,7 @@ import {DataCategory, DataCategoryExact} from 'sentry/types/core'; import {oxfordizeArray} from 'sentry/utils/oxfordizeArray'; import {toTitleCase} from 'sentry/utils/string/toTitleCase'; -import {BILLED_DATA_CATEGORY_INFO, UNLIMITED_RESERVED} from 'getsentry/constants'; +import {UNLIMITED_RESERVED} from 'getsentry/constants'; import type { BilledDataCategoryInfo, BillingMetricHistory, @@ -19,6 +19,192 @@ import type { } from 'getsentry/types'; import {MILLISECONDS_IN_HOUR} from 'getsentry/utils/billing'; +// XXX: initialize the BilledDataCategoryInfo-specific field for all non-billed +// `categories and make TS happy so we can access the BilledDataCategoryInfo +// fields directly without needing to check that they exist on the object +const DEFAULT_BILLED_DATA_CATEGORY_INFO = { + ...DATA_CATEGORY_INFO, +} as Record; +Object.entries(DEFAULT_BILLED_DATA_CATEGORY_INFO).forEach( + ([categoryExact, categoryInfo]) => { + DEFAULT_BILLED_DATA_CATEGORY_INFO[categoryExact as DataCategoryExact] = { + ...categoryInfo, + canAllocate: false, + canProductTrial: false, + freeEventsMultiple: 0, + feature: null, + hasSpikeProtection: false, + checkoutTooltip: null, + tallyType: 'usage', + hasPerCategory: false, + adminOnlyProductTrialFeature: null, + }; + } +); + +/** + * Extension of DATA_CATEGORY_INFO with billing info for billed categories. + * All categories with isBilledCategory: true, should be explicitly + * added to this object with billing info. + */ +const BILLED_DATA_CATEGORY_INFO = { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO, + [DataCategoryExact.ERROR]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.ERROR], + canAllocate: true, + freeEventsMultiple: 1_000, + hasSpikeProtection: true, + checkoutTooltip: t( + 'Errors are sent every time an SDK catches a bug. You can send them manually too, if you want.' + ), + hasPerCategory: true, + }, + [DataCategoryExact.TRANSACTION]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.TRANSACTION], + canAllocate: true, + canProductTrial: true, + freeEventsMultiple: 1_000, + feature: 'performance-view', + hasSpikeProtection: true, + checkoutTooltip: t( + 'Transactions are sent when your service receives a request and sends a response.' + ), + hasPerCategory: true, + shortenedUnitName: t('unit'), + }, + [DataCategoryExact.ATTACHMENT]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.ATTACHMENT], + canAllocate: true, + freeEventsMultiple: 1, + feature: 'event-attachments', + hasSpikeProtection: true, + checkoutTooltip: t('Attachments are files attached to errors, such as minidumps.'), + hasPerCategory: true, + shortenedUnitName: 'GB', + }, + [DataCategoryExact.REPLAY]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.REPLAY], + canProductTrial: true, + freeEventsMultiple: 1, + feature: 'session-replay', + checkoutTooltip: t( + 'Session Replays are video-like reproductions of your users\u2019 sessions navigating your app or website.' + ), + hasPerCategory: true, + }, + [DataCategoryExact.SPAN]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SPAN], + canProductTrial: true, + freeEventsMultiple: 100_000, + feature: 'spans-usage-tracking', + hasSpikeProtection: true, + checkoutTooltip: t( + 'Tracing is enabled by spans. A span represents a single operation of work within a trace.' + ), + }, + [DataCategoryExact.SPAN_INDEXED]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SPAN_INDEXED], + canProductTrial: true, + freeEventsMultiple: 100_000, + feature: 'spans-usage-tracking', + }, + [DataCategoryExact.MONITOR_SEAT]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.MONITOR_SEAT], + freeEventsMultiple: 1, + feature: 'monitor-seat-billing', + tallyType: 'seat', + hasPerCategory: true, + checkoutTooltip: t( + 'Crons monitors scheduled jobs to confirm they run on time and alert you when they fail or misfire.' + ), + shortenedUnitName: t('monitor'), + }, + [DataCategoryExact.UPTIME]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.UPTIME], + freeEventsMultiple: 1, + feature: 'uptime-billing', + tallyType: 'seat', + hasPerCategory: true, + checkoutTooltip: t( + 'Uptime monitoring checks your application\u2019s availability and alerts you when services go down so you can respond quickly.' + ), + shortenedUnitName: t('monitor'), + }, + [DataCategoryExact.PROFILE_DURATION]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.PROFILE_DURATION], + canProductTrial: true, + freeEventsMultiple: 1, // in hours + hasPerCategory: true, + checkoutTooltip: t( + 'Continuous profiling tracks how code runs while your service is active, helping you find bottlenecks and improve efficiency.' + ), + shortenedUnitName: t('hour'), + }, + [DataCategoryExact.PROFILE_DURATION_UI]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.PROFILE_DURATION_UI], + canProductTrial: true, + freeEventsMultiple: 1, // in hours + hasPerCategory: true, + checkoutTooltip: t( + 'UI profiling tracks code performance during user sessions in frontend or mobile apps, helping you spot slowdowns and improve experience.' + ), + shortenedUnitName: t('hour'), + }, + // Seer categories have product trials through ReservedBudgetCategoryType.SEER, not as individual categories + [DataCategoryExact.SEER_AUTOFIX]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SEER_AUTOFIX], + feature: 'seer-billing', + shortenedUnitName: t('fix'), + }, + [DataCategoryExact.SEER_SCANNER]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SEER_SCANNER], + feature: 'seer-billing', + shortenedUnitName: t('scan'), + }, + [DataCategoryExact.LOG_BYTE]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.LOG_BYTE], + canAllocate: false, + canProductTrial: true, + freeEventsMultiple: 1, + hasSpikeProtection: false, + feature: 'logs-billing', + checkoutTooltip: t( + 'A log records events from your application, giving you the context to debug issues and understand system behavior.' + ), + shortenedUnitName: 'GB', + }, + [DataCategoryExact.TRACE_METRIC_BYTE]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.TRACE_METRIC_BYTE], + canProductTrial: true, + freeEventsMultiple: 1, + feature: 'expose-category-trace-metric-byte', + shortenedUnitName: 'GB', + checkoutTooltip: t( + 'Application Metrics capture key signals from your application using counters, gauges, and distributions.' + ), + }, + [DataCategoryExact.SEER_USER]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SEER_USER], + feature: 'seer-user-billing-launch', + canProductTrial: false, + freeEventsMultiple: 1, + tallyType: 'seat', + shortenedUnitName: t('contributor'), + }, + [DataCategoryExact.SIZE_ANALYSIS]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.SIZE_ANALYSIS], + freeEventsMultiple: 1, + shortenedUnitName: t('build'), + adminOnlyProductTrialFeature: true, + }, + [DataCategoryExact.INSTALLABLE_BUILD]: { + ...DEFAULT_BILLED_DATA_CATEGORY_INFO[DataCategoryExact.INSTALLABLE_BUILD], + freeEventsMultiple: 1, + shortenedUnitName: t('install'), + adminOnlyProductTrialFeature: 'expose-category-installable-build', + }, +} as const satisfies Record; + /** * Returns billing-enriched data category info for all categories. * This wraps the static constant so it can later be replaced with a backend call.