Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 119 additions & 2 deletions static/gsApp/views/subscriptionPage/usageHistory.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
Am3DsEnterpriseSubscriptionFixture,
SubscriptionFixture,
} from 'getsentry-test/fixtures/subscription';
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';

import {ProjectsStore} from 'sentry/stores/projectsStore';
import {DataCategory} from 'sentry/types/core';
Expand All @@ -23,7 +23,9 @@ import {
} from 'getsentry/constants';
import {SubscriptionStore} from 'getsentry/stores/subscriptionStore';
import {OnDemandBudgetMode, PlanTier} from 'getsentry/types';
import UsageHistory from 'getsentry/views/subscriptionPage/usageHistory';
import UsageHistory, {
usagePercentage,
} from 'getsentry/views/subscriptionPage/usageHistory';

describe('Subscription > UsageHistory', () => {
const organization = OrganizationFixture({access: ['org:billing']});
Expand Down Expand Up @@ -166,6 +168,88 @@ describe('Subscription > UsageHistory', () => {
expect(mockCall).toHaveBeenCalled();
});

it('renders ∞ when reserved and prepaid are UNLIMITED_RESERVED', async () => {
MockApiClient.addMockResponse({
url: `/customers/${organization.slug}/history/`,
method: 'GET',
body: [
BillingHistoryFixture({
categories: {
errors: MetricHistoryFixture({
usage: 12345,
reserved: UNLIMITED_RESERVED,
prepaid: UNLIMITED_RESERVED,
}),
transactions: MetricHistoryFixture({
category: DataCategory.TRANSACTIONS,
reserved: 1000,
prepaid: 1000,
}),
attachments: MetricHistoryFixture({
category: DataCategory.ATTACHMENTS,
reserved: 1,
prepaid: 1,
}),
},
}),
],
});

const subscription = SubscriptionFixture({
plan: 'am1_f',
planTier: PlanTier.AM1,
organization,
});
SubscriptionStore.set(organization.slug, subscription);

render(<UsageHistory />, {organization});

const errorsRow = await screen.findByRole('row', {name: /^Errors/});
expect(within(errorsRow).getAllByRole('cell', {name: UNLIMITED})).toHaveLength(2);
expect(screen.queryByText('>100%')).not.toBeInTheDocument();
});

it('renders ∞ when prepaid is UNLIMITED_RESERVED but reserved is positive', async () => {
MockApiClient.addMockResponse({
url: `/customers/${organization.slug}/history/`,
method: 'GET',
body: [
BillingHistoryFixture({
categories: {
errors: MetricHistoryFixture({
usage: 9999,
reserved: 1000,
prepaid: UNLIMITED_RESERVED,
}),
transactions: MetricHistoryFixture({
category: DataCategory.TRANSACTIONS,
reserved: 1000,
prepaid: 1000,
}),
attachments: MetricHistoryFixture({
category: DataCategory.ATTACHMENTS,
reserved: 1,
prepaid: 1,
}),
},
}),
],
});

const subscription = SubscriptionFixture({
plan: 'am1_f',
planTier: PlanTier.AM1,
organization,
});
SubscriptionStore.set(organization.slug, subscription);

render(<UsageHistory />, {organization});

const errorsRow = await screen.findByRole('row', {name: /^Errors/});
expect(within(errorsRow).getByRole('cell', {name: UNLIMITED})).toBeInTheDocument();
expect(screen.queryByText('>100%')).not.toBeInTheDocument();
});

it('overage is shown as >100%', async () => {
MockApiClient.addMockResponse({
url: `/customers/${organization.slug}/history/`,
Expand Down Expand Up @@ -913,3 +997,36 @@ describe('Subscription > UsageHistory', () => {
expect(await screen.findByText('>100%')).toBeInTheDocument();
});
});

describe('usagePercentage', () => {
it('returns 0% when prepaid is null', () => {
expect(usagePercentage(0, null)).toBe('0%');
expect(usagePercentage(100, null)).toBe('0%');
});

it('returns 0% when prepaid is 0', () => {
expect(usagePercentage(0, 0)).toBe('0%');
expect(usagePercentage(50, 0)).toBe('0%');
});

it('returns UNLIMITED when prepaid equals UNLIMITED_RESERVED', () => {
expect(usagePercentage(0, UNLIMITED_RESERVED)).toBe(UNLIMITED);
expect(usagePercentage(1_000_000, UNLIMITED_RESERVED)).toBe(UNLIMITED);
});

it('returns >100% when usage exceeds prepaid', () => {
expect(usagePercentage(1001, 1000)).toBe('>100%');
});

it('returns formatted percentage when usage is below prepaid', () => {
expect(usagePercentage(500, 1000)).toBe('50%');
});

it('returns 0% when usage is 0 and prepaid is positive', () => {
expect(usagePercentage(0, 1000)).toBe('0%');
});

it('returns 100% when usage equals prepaid', () => {
expect(usagePercentage(1000, 1000)).toBe('100%');
});
});
22 changes: 14 additions & 8 deletions static/gsApp/views/subscriptionPage/usageHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
formatReservedWithUnits,
formatUsageWithUnits,
getSoftCapType,
isUnlimitedReserved,
} from 'getsentry/utils/billing';
import {getPlanCategoryName, sortCategories} from 'getsentry/utils/dataCategory';
import {trackGetsentryAnalytics} from 'getsentry/utils/trackGetsentryAnalytics';
Expand All @@ -53,7 +54,10 @@ interface Props {
subscription: Subscription;
}

function usagePercentage(usage: number, prepaid: number | null): string {
export function usagePercentage(usage: number, prepaid: number | null): string {
if (isUnlimitedReserved(prepaid)) {
return UNLIMITED;
}
if (prepaid === null || prepaid === 0) {
return t('0%');
}
Expand Down Expand Up @@ -345,13 +349,15 @@ function UsageHistoryRow({history}: RowProps) {
<td>
{metricHistory.reserved === RESERVED_BUDGET_QUOTA
? 'N/A'
: usagePercentage(
convertUsageToReservedUnit(
metricHistory.usage,
metricHistory.category
),
metricHistory.prepaid
)}
: isUnlimitedReserved(metricHistory.reserved)
? UNLIMITED
Copy link
Copy Markdown
Member

@brendanhsentry brendanhsentry Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes more sense to make this 'N/A' imo. Infinite percent used could be misleading

: usagePercentage(
convertUsageToReservedUnit(
metricHistory.usage,
metricHistory.category
),
metricHistory.prepaid
)}
</td>
</tr>
))}
Expand Down
Loading