Skip to content
Merged
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
90 changes: 90 additions & 0 deletions static/gsApp/utils/billing.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,96 @@ describe('productIsEnabled', () => {
};
expect(productIsEnabled(subscription, DataCategory.PROFILE_DURATION)).toBe(true);
});
it('returns true for gifted-only data categories (reserved=0, free>0)', () => {
subscription.categories.monitorSeats = {
...subscription.categories.monitorSeats!,
reserved: 0,
free: 1,
prepaid: 1,
};
expect(productIsEnabled(subscription, DataCategory.MONITOR_SEATS)).toBe(true);

subscription.categories.uptime = {
...subscription.categories.uptime!,
reserved: 0,
free: 1,
prepaid: 1,
};
expect(productIsEnabled(subscription, DataCategory.UPTIME)).toBe(true);
});

it('returns false for categories with no quota at all', () => {
subscription.categories.monitorSeats = {
...subscription.categories.monitorSeats!,
reserved: 0,
free: 0,
prepaid: 0,
};
expect(productIsEnabled(subscription, DataCategory.MONITOR_SEATS)).toBe(false);
});

it('returns true for categories with both reserved and gifted quota', () => {
subscription.categories.monitorSeats = {
...subscription.categories.monitorSeats!,
reserved: 1,
free: 1,
prepaid: 2,
};
expect(productIsEnabled(subscription, DataCategory.MONITOR_SEATS)).toBe(true);
});

it('returns true for categories with unlimited prepaid (UNLIMITED_RESERVED sentinel)', () => {
subscription.categories.monitorSeats = {
...subscription.categories.monitorSeats!,
reserved: UNLIMITED_RESERVED,
free: 0,
prepaid: UNLIMITED_RESERVED,
};
expect(productIsEnabled(subscription, DataCategory.MONITOR_SEATS)).toBe(true);
});

it('returns true for categories with softCapType TRUE_FORWARD even with no prepaid quota', () => {
subscription.categories.monitorSeats = {
...subscription.categories.monitorSeats!,
reserved: 0,
free: 0,
prepaid: 0,
softCapType: 'TRUE_FORWARD',
};
expect(productIsEnabled(subscription, DataCategory.MONITOR_SEATS)).toBe(true);
});

it('returns true for categories with softCapType ON_DEMAND even with no prepaid quota', () => {
subscription.categories.monitorSeats = {
...subscription.categories.monitorSeats!,
reserved: 0,
free: 0,
prepaid: 0,
softCapType: 'ON_DEMAND',
};
expect(productIsEnabled(subscription, DataCategory.MONITOR_SEATS)).toBe(true);
});

it('returns true for subscriptions with hasSoftCap=true even when softCapType is null and no prepaid quota', () => {
subscription.hasSoftCap = true;
subscription.categories.monitorSeats = {
...subscription.categories.monitorSeats!,
reserved: 0,
free: 0,
prepaid: 0,
softCapType: null,
};
expect(productIsEnabled(subscription, DataCategory.MONITOR_SEATS)).toBe(true);

subscription.categories.uptime = {
...subscription.categories.uptime!,
reserved: 0,
free: 0,
prepaid: 0,
softCapType: null,
};
expect(productIsEnabled(subscription, DataCategory.UPTIME)).toBe(true);
});
});

describe('getSeerTrialCategory', () => {
Expand Down
12 changes: 7 additions & 5 deletions static/gsApp/utils/billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1040,13 +1040,15 @@ export function productIsEnabled(
if (!metricHistory) {
return false;
}
const isPaygOnly = metricHistory.reserved === 0;
return (
!isPaygOnly ||
const hasNonPaygAccess =
(metricHistory.prepaid ?? 0) !== 0 ||
!!metricHistory.softCapType ||
!!subscription.hasSoftCap;
const hasPaygBudget =
metricHistory.onDemandBudget > 0 ||
(subscription.onDemandBudgets?.budgetMode === OnDemandBudgetMode.SHARED &&
subscription.onDemandBudgets.sharedMaxBudget > 0)
);
subscription.onDemandBudgets.sharedMaxBudget > 0);
return hasNonPaygAccess || hasPaygBudget;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,96 @@ describe('ProductBreakdownPanel', () => {
await screen.findByText('Active Contributors (0)'); // wait for billed seats to be loaded
});

it('does not show Upgrade required for gifted-only monitors', async () => {
subscription.categories.monitorSeats = {
...subscription.categories.monitorSeats!,
reserved: 0,
free: 1,
prepaid: 1,
};
render(
<ProductBreakdownPanel
subscription={subscription}
organization={organization}
usageData={usageData}
selectedProduct={DataCategory.MONITOR_SEATS}
/>
);

await screen.findByRole('heading', {name: 'Cron Monitors'});
expect(screen.queryByText('Upgrade required')).not.toBeInTheDocument();
});

it('does not show Upgrade required for gifted-only uptime monitors', async () => {
subscription.categories.uptime = {
...subscription.categories.uptime!,
reserved: 0,
free: 1,
prepaid: 1,
};
render(
<ProductBreakdownPanel
subscription={subscription}
organization={organization}
usageData={usageData}
selectedProduct={DataCategory.UPTIME}
/>
);

await screen.findByRole('heading', {name: 'Uptime Monitors'});
expect(screen.queryByText('Upgrade required')).not.toBeInTheDocument();
});

it('does not show Upgrade required for soft cap monitors with no prepaid quota', async () => {
subscription.categories.monitorSeats = {
...subscription.categories.monitorSeats!,
reserved: 0,
free: 0,
prepaid: 0,
softCapType: 'TRUE_FORWARD',
};
subscription.hasSoftCap = true;
SubscriptionStore.set(organization.slug, subscription);
render(
<ProductBreakdownPanel
subscription={subscription}
organization={organization}
usageData={usageData}
selectedProduct={DataCategory.MONITOR_SEATS}
/>
);

await screen.findByRole('heading', {name: 'Cron Monitors'});
expect(screen.queryByText('Upgrade required')).not.toBeInTheDocument();
});

it('does not show Upgrade required when hasSoftCap=true but category softCapType is null', async () => {
// Legacy soft cap orgs can have hasSoftCap=true on the subscription but
// softCapType=null on newer categories (e.g. MONITOR_SEAT, UPTIME)
// because create_new_category_histories does not inherit soft_cap_type
// from siblings or from the subscription-level soft_cap flag.
subscription.hasSoftCap = true;
subscription.categories.monitorSeats = {
...subscription.categories.monitorSeats!,
reserved: 0,
free: 0,
prepaid: 0,
softCapType: null,
};
SubscriptionStore.set(organization.slug, subscription);
render(
<ProductBreakdownPanel
subscription={subscription}
organization={organization}
usageData={usageData}
selectedProduct={DataCategory.MONITOR_SEATS}
/>
);

await screen.findByRole('heading', {name: 'Cron Monitors'});
expect(screen.queryByText('Upgrade required')).not.toBeInTheDocument();
});

it('renders for data category with missing metric history', async () => {
// NOTE(isabella): currently, we would never have this case IRL
// since we would not allow a data category without a metric history to be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,129 @@ describe('UsageOverviewTable', () => {
expect(screen.queryByRole('cell', {name: 'Errors'})).not.toBeInTheDocument();
});

it('renders gifted-only monitors as enabled, not disabled', async () => {
const sub = SubscriptionFixture({organization, plan: 'am3_business'});
sub.categories.monitorSeats = {
...sub.categories.monitorSeats!,
reserved: 0,
free: 1,
prepaid: 1,
};
sub.categories.uptime = {
...sub.categories.uptime!,
reserved: 0,
free: 1,
prepaid: 1,
};
SubscriptionStore.set(organization.slug, sub);

render(
<UsageOverviewTable
subscription={sub}
organization={organization}
usageData={usageData}
onRowClick={jest.fn()}
selectedProduct={DataCategory.ERRORS}
/>
);

await screen.findByRole('columnheader', {name: 'Feature'});

// Gifted-only monitors should appear as enabled rows
expect(screen.getByTestId('product-row-monitorSeats')).toBeInTheDocument();
expect(
screen.queryByTestId('product-row-disabled-monitorSeats')
).not.toBeInTheDocument();

expect(screen.getByTestId('product-row-uptime')).toBeInTheDocument();
expect(screen.queryByTestId('product-row-disabled-uptime')).not.toBeInTheDocument();
});

it('renders soft cap monitors as enabled, not disabled', async () => {
const sub = SubscriptionFixture({organization, plan: 'am3_business'});
sub.categories.monitorSeats = {
...sub.categories.monitorSeats!,
reserved: 0,
free: 0,
prepaid: 0,
softCapType: 'TRUE_FORWARD',
};
sub.categories.uptime = {
...sub.categories.uptime!,
reserved: 0,
free: 0,
prepaid: 0,
softCapType: 'TRUE_FORWARD',
};
sub.hasSoftCap = true;
SubscriptionStore.set(organization.slug, sub);

render(
<UsageOverviewTable
subscription={sub}
organization={organization}
usageData={usageData}
onRowClick={jest.fn()}
selectedProduct={DataCategory.ERRORS}
/>
);

await screen.findByRole('columnheader', {name: 'Feature'});

// Soft cap monitors should appear as enabled rows
expect(screen.getByTestId('product-row-monitorSeats')).toBeInTheDocument();
expect(
screen.queryByTestId('product-row-disabled-monitorSeats')
).not.toBeInTheDocument();

expect(screen.getByTestId('product-row-uptime')).toBeInTheDocument();
expect(screen.queryByTestId('product-row-disabled-uptime')).not.toBeInTheDocument();
});

it('renders hasSoftCap monitors as enabled even when category softCapType is null', async () => {
// Legacy soft cap orgs can have hasSoftCap=true on the subscription but
// softCapType=null on newer categories (e.g. MONITOR_SEAT, UPTIME)
// because create_new_category_histories does not inherit soft_cap_type
// from siblings or from the subscription-level soft_cap flag.
const sub = SubscriptionFixture({organization, plan: 'am3_business'});
sub.hasSoftCap = true;
sub.categories.monitorSeats = {
...sub.categories.monitorSeats!,
reserved: 0,
free: 0,
prepaid: 0,
softCapType: null,
};
sub.categories.uptime = {
...sub.categories.uptime!,
reserved: 0,
free: 0,
prepaid: 0,
softCapType: null,
};
SubscriptionStore.set(organization.slug, sub);

render(
<UsageOverviewTable
subscription={sub}
organization={organization}
usageData={usageData}
onRowClick={jest.fn()}
selectedProduct={DataCategory.ERRORS}
/>
);

await screen.findByRole('columnheader', {name: 'Feature'});

expect(screen.getByTestId('product-row-monitorSeats')).toBeInTheDocument();
expect(
screen.queryByTestId('product-row-disabled-monitorSeats')
).not.toBeInTheDocument();

expect(screen.getByTestId('product-row-uptime')).toBeInTheDocument();
expect(screen.queryByTestId('product-row-disabled-uptime')).not.toBeInTheDocument();
});

it('renders disabled product rows', async () => {
// both profiling categories are disabled because there is no PAYG
subscription.onDemandBudgets = {
Expand Down
Loading
Loading