Skip to content

Commit d1bb388

Browse files
ref(billing): Convert Reservations to dynamic type (#103855)
Closes https://linear.app/getsentry/issue/BIL-956/dynamic-reservations Converts the hardcoded `Reservations` type to a dynamically-generated mapped type using the same pattern as `ReservedInvoiceItemType`. Pattern: `reserved${Capitalize<plural>}` from `DATA_CATEGORY_INFO` where `isBilledCategory: true` Automatically includes all billed categories that can have reservations across any plan type (AM1, AM2, and AM3+). --------- Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
1 parent ab653b4 commit d1bb388

File tree

7 files changed

+77
-13
lines changed

7 files changed

+77
-13
lines changed

static/gsApp/components/productSelectionAvailability.spec.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,11 @@ describe('ProductSelectionAvailability', () => {
346346
reservedProfileDuration: 0,
347347
reservedProfileDurationUI: 0,
348348
reservedLogBytes: 0,
349+
reservedSpans: undefined,
350+
reservedSeerAutofix: undefined,
351+
reservedSeerScanner: undefined,
352+
reservedSeerUsers: undefined,
353+
reservedPreventUsers: undefined,
349354
};
350355
const mockPlan = PlanFixture({});
351356
const mockPreview = PreviewDataFixture({});

static/gsApp/components/productUnavailableCTA.spec.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ describe('ProductUnavailableCTA', () => {
214214
reservedProfileDuration: 0,
215215
reservedProfileDurationUI: 0,
216216
reservedLogBytes: 0,
217+
reservedSpans: undefined,
218+
reservedSeerAutofix: undefined,
219+
reservedSeerScanner: undefined,
220+
reservedSeerUsers: undefined,
221+
reservedPreventUsers: undefined,
217222
};
218223
const mockPlan = PlanFixture({});
219224
const mockPreview = PreviewDataFixture({});

static/gsApp/components/upgradeNowModal/planTable.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function PlanTable({organization, previewData, reservations, subscription}: Prop
4646
abbr
4747
)}
4848
next={formatReservedWithUnits(
49-
reservations.reservedErrors,
49+
reservations.reservedErrors ?? null,
5050
DataCategory.ERRORS,
5151
abbr
5252
)}
@@ -61,7 +61,7 @@ function PlanTable({organization, previewData, reservations, subscription}: Prop
6161
abbr
6262
)}
6363
next={formatReservedWithUnits(
64-
reservations.reservedTransactions,
64+
reservations.reservedTransactions ?? null,
6565
DataCategory.TRANSACTIONS,
6666
abbr
6767
)}
@@ -83,7 +83,7 @@ function PlanTable({organization, previewData, reservations, subscription}: Prop
8383
abbr
8484
)}
8585
next={formatReservedWithUnits(
86-
reservations.reservedAttachments,
86+
reservations.reservedAttachments ?? null,
8787
DataCategory.ATTACHMENTS,
8888
abbr
8989
)}
Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,50 @@
1+
import type {DATA_CATEGORY_INFO} from 'sentry/constants';
2+
3+
/**
4+
* Dynamically generate Reservations type from DATA_CATEGORY_INFO.
5+
*
6+
* Generates reservation property names by prefixing 'reserved' to the capitalized plural form
7+
* of each billed category. This follows the same pattern as ReservedInvoiceItemType but produces
8+
* camelCase property names for object keys.
9+
*
10+
* Includes all categories where isBilledCategory: true, covering reservations across
11+
* all plan types (AM1, AM2, AM3+).
12+
*
13+
* Pattern: `reserved${Capitalize<plural>}`
14+
* - DATA_CATEGORY_INFO.ERROR (plural: 'errors') -> 'reservedErrors'
15+
* - DATA_CATEGORY_INFO.LOG_BYTE (plural: 'logBytes') -> 'reservedLogBytes'
16+
* - DATA_CATEGORY_INFO.SPAN (plural: 'spans') -> 'reservedSpans'
17+
*
18+
* Values are `number | undefined` to handle:
19+
* - Categories that may not be present in a customer's plan
20+
* - Newer categories that haven't been fully rolled out yet
21+
* - Optional reservation data
22+
* - Different plan types having different category subsets
23+
*
24+
* @example
25+
* // Generates from DATA_CATEGORY_INFO where isBilledCategory: true:
26+
* {
27+
* reservedErrors: number | undefined;
28+
* reservedTransactions: number | undefined;
29+
* reservedAttachments: number | undefined;
30+
* reservedReplays: number | undefined;
31+
* reservedSpans: number | undefined;
32+
* reservedMonitorSeats: number | undefined;
33+
* reservedProfileDuration: number | undefined;
34+
* reservedProfileDurationUI: number | undefined;
35+
* reservedUptime: number | undefined;
36+
* reservedLogBytes: number | undefined;
37+
* reservedSeerAutofix: number | undefined;
38+
* reservedSeerScanner: number | undefined;
39+
* reservedPreventUsers: number | undefined;
40+
* }
41+
*
42+
* @see DATA_CATEGORY_INFO in static/app/constants/index.tsx
43+
* @see ReservedInvoiceItemType in static/gsApp/types/index.tsx for snake_case equivalent
44+
* @see getsentry/billing/plans/ for plan definitions
45+
*/
146
export type Reservations = {
2-
reservedAttachments: number;
3-
reservedErrors: number;
4-
reservedLogBytes: number | undefined;
5-
reservedMonitorSeats: number;
6-
reservedProfileDuration: number | undefined;
7-
reservedProfileDurationUI: number | undefined;
8-
reservedReplays: number;
9-
reservedTransactions: number;
10-
reservedUptime: number | undefined;
11-
}; // TODO(data categories): BIL-956
47+
[K in keyof typeof DATA_CATEGORY_INFO as (typeof DATA_CATEGORY_INFO)[K]['isBilledCategory'] extends true
48+
? `reserved${Capitalize<(typeof DATA_CATEGORY_INFO)[K]['plural']>}`
49+
: never]: number | undefined;
50+
};

static/gsApp/components/upgradeNowModal/usePreviewData.spec.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ const mockReservations: Reservations = {
2121
reservedProfileDuration: 0,
2222
reservedProfileDurationUI: 0,
2323
reservedLogBytes: 5,
24+
reservedSpans: undefined,
25+
reservedSeerAutofix: 0,
26+
reservedSeerScanner: 0,
27+
reservedSeerUsers: undefined,
28+
reservedPreventUsers: undefined,
2429
};
2530

2631
const mockPreview = PreviewDataFixture({});

static/gsApp/components/upgradeNowModal/useUpgradeNowParams.spec.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ describe('useUpgradeNowParams', () => {
5454
reservedProfileDuration: 0,
5555
reservedProfileDurationUI: 0,
5656
reservedLogBytes: 5,
57+
reservedSpans: undefined,
58+
reservedSeerAutofix: 0,
59+
reservedSeerScanner: 0,
60+
reservedSeerUsers: undefined,
61+
reservedPreventUsers: undefined,
5762
},
5863
})
5964
);

static/gsApp/components/upgradeNowModal/useUpgradeNowParams.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ function useUpgradeNowParams({organization, subscription, enabled = true}: Opts)
100100
reservedProfileDuration: reserved.profileDuration,
101101
reservedProfileDurationUI: reserved.profileDurationUI,
102102
reservedLogBytes: reserved.logBytes,
103+
reservedSpans: reserved.spans,
104+
reservedSeerAutofix: reserved.seerAutofix,
105+
reservedSeerScanner: reserved.seerScanner,
106+
reservedSeerUsers: reserved.seerUsers,
107+
reservedPreventUsers: reserved.preventUsers,
103108
},
104109
};
105110
}, [billingConfig, isPending, subscription, enabled]);

0 commit comments

Comments
 (0)