-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
ref(billing): Convert CreditType to dynamic type union #103815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ad5e57e
85ce9d0
4f505b5
576b233
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -865,19 +865,39 @@ export type PreviewInvoiceItem = BaseInvoiceItem & { | |
| period_start: string; | ||
| }; | ||
|
|
||
| // TODO(data categories): BIL-970 | ||
| export enum CreditType { | ||
| ERROR = 'error', | ||
| TRANSACTION = 'transaction', | ||
| SPAN = 'span', | ||
| PROFILE_DURATION = 'profile_duration', | ||
| PROFILE_DURATION_UI = 'profile_duration_ui', | ||
| ATTACHMENT = 'attachment', | ||
| REPLAY = 'replay', | ||
| DISCOUNT = 'discount', | ||
| PERCENT = 'percent', | ||
| LOG_BYTE = 'log_byte', | ||
| } | ||
| /** | ||
| * Dynamically generate credit types from DATA_CATEGORY_INFO. | ||
| * Uses SINGULAR form (unlike InvoiceItemType which uses plural). | ||
| * This automatically includes new billing categories without manual enum updates. | ||
| * | ||
| * Follows the pattern: snake_case of singular | ||
| * Example: DATA_CATEGORY_INFO.ERROR (singular: "error") -> "error" | ||
| * Example: DATA_CATEGORY_INFO.LOG_BYTE (singular: "logByte") -> "log_byte" | ||
| */ | ||
| type DynamicCreditType = { | ||
| [K in keyof typeof DATA_CATEGORY_INFO]: (typeof DATA_CATEGORY_INFO)[K]['isBilledCategory'] extends true | ||
| ? CamelToSnake<(typeof DATA_CATEGORY_INFO)[K]['singular']> | ||
| : never; | ||
| }[keyof typeof DATA_CATEGORY_INFO]; | ||
|
|
||
| /** | ||
| * Static credit types not tied to data categories. | ||
| * These must be manually maintained but change infrequently. | ||
| */ | ||
| type StaticCreditType = | ||
| | 'discount' // Dollar-based recurring discount | ||
| | 'percent' // Percentage-based recurring discount | ||
| | 'seer_user'; // Special: maps to PREVENT_USER category (temporary until category renamed) | ||
|
|
||
| /** | ||
| * Complete credit type union. | ||
| * Automatically stays in sync with backend when new billing categories are added. | ||
| * | ||
| * Migration from enum: Use string literals instead of enum members. | ||
| * Before: CreditType.ERROR | ||
| * After: 'error' | ||
| */ | ||
| export type CreditType = DynamicCreditType | StaticCreditType; | ||
|
|
||
| type BaseRecurringCredit = { | ||
| amount: number; | ||
|
|
@@ -888,26 +908,27 @@ type BaseRecurringCredit = { | |
|
|
||
| interface RecurringDiscount extends BaseRecurringCredit { | ||
| totalAmountRemaining: number; | ||
| type: CreditType.DISCOUNT; | ||
| type: 'discount'; | ||
| } | ||
|
|
||
| interface RecurringPercentDiscount extends BaseRecurringCredit { | ||
| percentPoints: number; | ||
| totalAmountRemaining: number; | ||
| type: CreditType.PERCENT; | ||
| type: 'percent'; | ||
| } | ||
|
|
||
| interface RecurringEventCredit extends BaseRecurringCredit { | ||
| totalAmountRemaining: null; | ||
| type: | ||
| | CreditType.ERROR | ||
| | CreditType.TRANSACTION | ||
| | CreditType.SPAN | ||
| | CreditType.PROFILE_DURATION | ||
| | CreditType.PROFILE_DURATION_UI | ||
| | CreditType.ATTACHMENT | ||
| | CreditType.REPLAY | ||
| | CreditType.LOG_BYTE; | ||
| | 'error' | ||
| | 'transaction' | ||
| | 'span' | ||
| | 'profile_duration' | ||
| | 'profile_duration_ui' | ||
| | 'attachment' | ||
| | 'replay' | ||
| | 'log_byte' | ||
| | 'seer_user'; | ||
| } | ||
|
|
||
| export type RecurringCredit = | ||
|
Comment on lines
+924
to
934
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The new 🔍 Detailed AnalysisThe pull request introduces 💡 Suggested FixUpdate 🤖 Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews. |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,8 +10,7 @@ import {space} from 'sentry/styles/space'; | |
| import type {DataCategory} from 'sentry/types/core'; | ||
|
|
||
| import {useRecurringCredits} from 'getsentry/hooks/useRecurringCredits'; | ||
| import type {Plan, RecurringCredit} from 'getsentry/types'; | ||
| import {CreditType} from 'getsentry/types'; | ||
| import type {CreditType, Plan, RecurringCredit} from 'getsentry/types'; | ||
| import {formatReservedWithUnits} from 'getsentry/utils/billing'; | ||
| import {getCreditDataCategory, getPlanCategoryName} from 'getsentry/utils/dataCategory'; | ||
| import {displayPrice} from 'getsentry/views/amCheckout/utils'; | ||
|
|
@@ -25,7 +24,7 @@ const isExpired = (date: moment.MomentInput) => { | |
| const getActiveDiscounts = (recurringCredits: RecurringCredit[]) => | ||
| recurringCredits.filter( | ||
| credit => | ||
| (credit.type === CreditType.DISCOUNT || credit.type === CreditType.PERCENT) && | ||
| (credit.type === 'discount' || credit.type === 'percent') && | ||
| credit.totalAmountRemaining > 0 && | ||
| !isExpired(credit.periodEnd) | ||
| ); | ||
|
|
@@ -58,7 +57,7 @@ function RecurringCredits({displayType, planDetails}: Props) { | |
| } | ||
|
|
||
| const getTooltipTitle = (credit: RecurringCredit) => { | ||
| return credit.type === CreditType.DISCOUNT || credit.type === CreditType.PERCENT | ||
| return credit.type === 'discount' || credit.type === 'percent' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Component silently filters out seer_user creditsThe component filters recurring credits to only show those where |
||
| ? tct('[amount] per month or [annualAmount] remaining towards an annual plan.', { | ||
| amount: displayPrice({cents: credit.amount}), | ||
| annualAmount: displayPrice({ | ||
|
|
@@ -69,7 +68,7 @@ function RecurringCredits({displayType, planDetails}: Props) { | |
| }; | ||
|
|
||
| const getAmount = (credit: RecurringCredit, category: DataCategory | CreditType) => { | ||
| if (credit.type === CreditType.DISCOUNT || credit.type === CreditType.PERCENT) { | ||
| if (credit.type === 'discount' || credit.type === 'percent') { | ||
| return ( | ||
| <Fragment> | ||
| {tct('[amount]/mo', { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Mismatch between dynamic and manual credit types for PREVENT_USER
The
DynamicCreditTypeautomatically includes'prevent_user'(fromPREVENT_USERcategory's singular'preventUser'converted to snake_case), but theRecurringEventCredit.typeunion at line 923-931 lists'seer_user'instead. This creates a type inconsistency where the dynamically generated type allows'prevent_user'but the concrete interface doesn't, potentially causing type errors if the backend sends credits withtype: 'prevent_user'.