Skip to content

Commit 03d1672

Browse files
fix(sub v3): Rework usage and reserved columns (#101514)
<img width="789" height="670" alt="Screenshot 2025-10-15 at 10 09 47 AM" src="https://github.com/user-attachments/assets/a3d79877-2011-495d-abef-73c56d580137" />
1 parent e24b4f5 commit 03d1672

File tree

2 files changed

+78
-76
lines changed

2 files changed

+78
-76
lines changed

static/gsApp/views/subscriptionPage/usageOverview.spec.tsx

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,8 @@ describe('UsageOverview', () => {
4949
/>
5050
);
5151
expect(screen.getByRole('columnheader', {name: 'Product'})).toBeInTheDocument();
52-
expect(screen.getByRole('columnheader', {name: 'Current usage'})).toBeInTheDocument();
53-
expect(
54-
screen.getByRole('columnheader', {name: 'Reserved usage'})
55-
).toBeInTheDocument();
52+
expect(screen.getByRole('columnheader', {name: 'Total usage'})).toBeInTheDocument();
53+
expect(screen.getByRole('columnheader', {name: 'Reserved'})).toBeInTheDocument();
5654
expect(
5755
screen.getByRole('columnheader', {name: 'Reserved spend'})
5856
).toBeInTheDocument();
@@ -76,10 +74,8 @@ describe('UsageOverview', () => {
7674
/>
7775
);
7876
expect(screen.getByRole('columnheader', {name: 'Product'})).toBeInTheDocument();
79-
expect(screen.getByRole('columnheader', {name: 'Current usage'})).toBeInTheDocument();
80-
expect(
81-
screen.getByRole('columnheader', {name: 'Reserved usage'})
82-
).toBeInTheDocument();
77+
expect(screen.getByRole('columnheader', {name: 'Total usage'})).toBeInTheDocument();
78+
expect(screen.getByRole('columnheader', {name: 'Reserved'})).toBeInTheDocument();
8379
expect(screen.queryByText('Reserved spend')).not.toBeInTheDocument();
8480
expect(screen.queryByText('Pay-as-you-go spend')).not.toBeInTheDocument();
8581
expect(
@@ -120,6 +116,7 @@ describe('UsageOverview', () => {
120116
subscription.categories.attachments = {
121117
...subscription.categories.attachments!,
122118
reserved: 25, // 25 GB
119+
prepaid: 25, // 25 GB
123120
free: 0,
124121
usage: GIGABYTE / 2,
125122
};
@@ -145,21 +142,21 @@ describe('UsageOverview', () => {
145142
expect(screen.queryByText('Trial available')).not.toBeInTheDocument();
146143

147144
// Replays product trial active
148-
expect(screen.getByText('20 days left')).toBeInTheDocument();
145+
expect(screen.getByRole('cell', {name: '20 days left'})).toBeInTheDocument();
149146

150147
// Errors usage and gifted units
151-
expect(screen.getByText('6,000 / 51,000')).toBeInTheDocument();
152-
expect(screen.getByText('$10.00')).toBeInTheDocument();
153-
expect(screen.getByText('12%')).toBeInTheDocument();
148+
expect(screen.getByRole('cell', {name: '6,000'})).toBeInTheDocument();
149+
expect(screen.getByRole('cell', {name: '$10.00'})).toBeInTheDocument();
150+
expect(screen.getByRole('cell', {name: '12% of 51,000'})).toBeInTheDocument();
154151

155152
// Attachments usage should be in the correct unit + above platform volume
156-
expect(screen.getByText('500 MB / 25 GB')).toBeInTheDocument();
157-
expect(screen.getByText('50%')).toBeInTheDocument();
158-
expect(screen.getByText('$6.00')).toBeInTheDocument();
153+
expect(screen.getByRole('cell', {name: '500 MB'})).toBeInTheDocument();
154+
expect(screen.getByRole('cell', {name: '2% of 25 GB'})).toBeInTheDocument();
155+
expect(screen.getByRole('cell', {name: '$6.00'})).toBeInTheDocument();
159156

160157
// Reserved spans above platform volume
161-
expect(screen.getByText('0 / 20,000,000')).toBeInTheDocument();
162-
expect(screen.getByText('$32.00')).toBeInTheDocument();
158+
expect(screen.getAllByRole('cell', {name: '0'}).length).toBeGreaterThan(0);
159+
expect(screen.getByRole('cell', {name: '$32.00'})).toBeInTheDocument();
163160
});
164161

165162
it('renders table based on add-on state', () => {

static/gsApp/views/subscriptionPage/usageOverview.tsx

Lines changed: 64 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,8 @@ function UsageOverviewTable({subscription, organization, usageData}: UsageOvervi
226226
);
227227
return [
228228
{key: 'product', name: t('Product'), width: 250},
229-
{key: 'currentUsage', name: t('Current usage'), width: 200},
230-
{key: 'reservedUsage', name: t('Reserved usage'), width: 200},
229+
{key: 'totalUsage', name: t('Total usage'), width: 200},
230+
{key: 'reservedUsage', name: t('Reserved'), width: 300},
231231
{key: 'reservedSpend', name: t('Reserved spend'), width: isXlScreen ? 200 : 150},
232232
{
233233
key: 'budgetSpend',
@@ -265,22 +265,22 @@ function UsageOverviewTable({subscription, organization, usageData}: UsageOvervi
265265
// TODO(isabella): refactor this to have better types
266266
const productData: Array<{
267267
budgetSpend: number;
268-
currentUsage: number;
269268
hasAccess: boolean;
270269
isClickable: boolean;
271270
isPaygOnly: boolean;
272271
isUnlimited: boolean;
273272
product: string;
273+
totalUsage: number;
274274
addOnCategory?: AddOnCategory;
275275
ariaLabel?: string;
276276
dataCategory?: DataCategory;
277277
free?: number;
278278
isChildProduct?: boolean;
279279
isOpen?: boolean;
280+
percentUsed?: number;
280281
productTrialCategory?: DataCategory;
281282
reserved?: number;
282283
reservedSpend?: number;
283-
reservedUsage?: number;
284284
softCapType?: 'ON_DEMAND' | 'TRUE_FORWARD';
285285
toggleKey?: DataCategory | AddOnCategory;
286286
}> = useMemo(() => {
@@ -342,8 +342,8 @@ function UsageOverviewTable({subscription, organization, usageData}: UsageOvervi
342342
isUnlimited: !!activeProductTrial || reserved === UNLIMITED_RESERVED,
343343
softCapType: softCapType ?? undefined,
344344
product: productName,
345-
currentUsage: total,
346-
reservedUsage: percentUsed,
345+
totalUsage: total,
346+
percentUsed,
347347
reservedSpend: recurringReservedSpend,
348348
budgetSpend: paygTotal,
349349
productTrialCategory: category,
@@ -431,7 +431,7 @@ function UsageOverviewTable({subscription, organization, usageData}: UsageOvervi
431431
isUnlimited: !!activeProductTrial,
432432
softCapType: softCapType ?? undefined,
433433
budgetSpend: childPaygTotal,
434-
currentUsage: (childSpend ?? 0) + childPaygTotal,
434+
totalUsage: (childSpend ?? 0) + childPaygTotal,
435435
product: childProductName,
436436
isClickable: categoryInfo?.tallyType === 'usage',
437437
};
@@ -450,8 +450,8 @@ function UsageOverviewTable({subscription, organization, usageData}: UsageOvervi
450450
isUnlimited: !!activeProductTrial,
451451
productTrialCategory: addOnDataCategories[0] as DataCategory,
452452
product: addOnName,
453-
currentUsage: (reservedBudget?.totalReservedSpend ?? 0) + paygTotal,
454-
reservedUsage: percentUsed,
453+
totalUsage: (reservedBudget?.totalReservedSpend ?? 0) + paygTotal,
454+
percentUsed,
455455
reservedSpend: recurringReservedSpend,
456456
budgetSpend: paygTotal,
457457
isClickable: hasAccess,
@@ -482,7 +482,7 @@ function UsageOverviewTable({subscription, organization, usageData}: UsageOvervi
482482
},
483483
renderBodyCell: (column, row) => {
484484
const {
485-
currentUsage,
485+
totalUsage,
486486
hasAccess,
487487
isPaygOnly,
488488
isUnlimited,
@@ -493,7 +493,7 @@ function UsageOverviewTable({subscription, organization, usageData}: UsageOvervi
493493
isChildProduct,
494494
isOpen,
495495
reserved,
496-
reservedUsage,
496+
percentUsed,
497497
softCapType,
498498
toggleKey,
499499
productTrialCategory,
@@ -543,60 +543,19 @@ function UsageOverviewTable({subscription, organization, usageData}: UsageOvervi
543543
</Flex>
544544
);
545545
}
546-
case 'currentUsage': {
546+
case 'totalUsage': {
547547
const formattedTotal = addOnCategory
548-
? displayPriceWithCents({cents: currentUsage})
548+
? displayPriceWithCents({cents: totalUsage})
549549
: dataCategory
550-
? formatUsageWithUnits(currentUsage, dataCategory, {
550+
? formatUsageWithUnits(totalUsage, dataCategory, {
551551
useUnitScaling: true,
552552
})
553-
: currentUsage;
554-
const formattedReserved = addOnCategory
555-
? displayPriceWithCents({cents: reserved ?? 0})
556-
: dataCategory
557-
? formatReservedWithUnits(reserved ?? 0, dataCategory, {
558-
useUnitScaling: true,
559-
})
560-
: (reserved ?? 0);
561-
const formattedFree = addOnCategory
562-
? displayPriceWithCents({cents: free ?? 0})
563-
: dataCategory
564-
? formatReservedWithUnits(free ?? 0, dataCategory, {
565-
useUnitScaling: true,
566-
})
567-
: (free ?? 0);
568-
const formattedReservedTotal = addOnCategory
569-
? displayPriceWithCents({cents: (reserved ?? 0) + (free ?? 0)})
570-
: dataCategory
571-
? formatReservedWithUnits((reserved ?? 0) + (free ?? 0), dataCategory, {
572-
useUnitScaling: true,
573-
})
574-
: (reserved ?? 0) + (free ?? 0);
575-
const formattedCurrentUsage =
576-
isPaygOnly || isChildProduct
577-
? formattedTotal
578-
: `${formattedTotal} / ${formattedReservedTotal}`;
553+
: totalUsage;
579554

580555
return (
581556
<Flex align="center" gap="sm" width="max-content">
582557
<Text as="div" textWrap="balance">
583-
{isUnlimited ? UNLIMITED : formattedCurrentUsage}{' '}
584-
{!(isPaygOnly || isChildProduct) && (
585-
<QuestionTooltip
586-
size="xs"
587-
position="top"
588-
title={
589-
isUnlimited
590-
? t('Unlimited usage during your product trial')
591-
: tct('[formattedReserved] reserved[freeString]', {
592-
formattedReserved,
593-
freeString: free
594-
? tct(' + [formattedFree] gifted', {formattedFree})
595-
: '',
596-
})
597-
}
598-
/>
599-
)}
558+
{isUnlimited ? UNLIMITED : formattedTotal}{' '}
600559
</Text>
601560
</Flex>
602561
);
@@ -620,12 +579,58 @@ function UsageOverviewTable({subscription, organization, usageData}: UsageOvervi
620579
return <Text>{UNLIMITED}</Text>;
621580
}
622581

623-
const percentUsed = reservedUsage;
624582
if (defined(percentUsed)) {
583+
const formattedReserved = addOnCategory
584+
? displayPriceWithCents({cents: reserved ?? 0})
585+
: dataCategory
586+
? formatReservedWithUnits(reserved ?? 0, dataCategory, {
587+
useUnitScaling: true,
588+
})
589+
: (reserved ?? 0);
590+
const formattedFree = addOnCategory
591+
? displayPriceWithCents({cents: free ?? 0})
592+
: dataCategory
593+
? formatReservedWithUnits(free ?? 0, dataCategory, {
594+
useUnitScaling: true,
595+
})
596+
: (free ?? 0);
597+
const formattedReservedTotal = addOnCategory
598+
? displayPriceWithCents({cents: (reserved ?? 0) + (free ?? 0)})
599+
: dataCategory
600+
? formatReservedWithUnits(
601+
(reserved ?? 0) + (free ?? 0),
602+
dataCategory,
603+
{
604+
useUnitScaling: true,
605+
}
606+
)
607+
: (reserved ?? 0) + (free ?? 0);
608+
625609
return (
626610
<Flex gap="sm" align="center">
627611
<ReservedUsageBar percentUsed={percentUsed / 100} />
628-
<Text>{percentUsed.toFixed(0) + '%'}</Text>
612+
<Text>
613+
{tct('[percent]% of [formattedReservedTotal]', {
614+
percent: percentUsed.toFixed(0),
615+
formattedReservedTotal,
616+
})}
617+
</Text>
618+
{
619+
<QuestionTooltip
620+
size="xs"
621+
position="top"
622+
title={
623+
isUnlimited
624+
? t('Unlimited usage during your product trial')
625+
: tct('[formattedReserved] reserved[freeString]', {
626+
formattedReserved,
627+
freeString: free
628+
? tct(' + [formattedFree] gifted', {formattedFree})
629+
: '',
630+
})
631+
}
632+
/>
633+
}
629634
</Flex>
630635
);
631636
}

0 commit comments

Comments
 (0)