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
2 changes: 1 addition & 1 deletion static/gsApp/views/onDemandBudgets/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ class OnDemandBudgets extends Component<Props> {
return this.renderNotEnabled();
}

if (!hasPaymentSource && !subscription.onDemandInvoicedManual) {
if (!hasPaymentSource) {
return this.renderNeedsPaymentSource();
}

Expand Down
39 changes: 0 additions & 39 deletions static/gsApp/views/onDemandBudgets/onDemandBudgets.spec.tsx
Copy link
Member Author

Choose a reason for hiding this comment

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

the hasPaymentSource logic is done in the parent so it doesn't make sense to test it here

Original file line number Diff line number Diff line change
Expand Up @@ -116,45 +116,6 @@ describe('OnDemandBudgets', () => {
expect(await screen.findByText('Stripe')).toBeInTheDocument();
});

it('allows VC partner accounts to set up on-demand budget without credit card', () => {
const subscription = SubscriptionFixture({
plan: 'am3_business',
planTier: PlanTier.AM3,
isFree: false,
isTrial: false,
supportsOnDemand: true,
organization,
partner: {
externalId: 'x123x',
name: 'VC Org',
partnership: {
id: 'VC',
displayName: 'VC',
supportNote: '',
},
isActive: true,
},
onDemandBudgets: {
enabled: false,
budgetMode: OnDemandBudgetMode.SHARED,
sharedMaxBudget: 0,
onDemandSpendUsed: 0,
},
});
SubscriptionStore.set(organization.slug, subscription);

const isVCPartner = subscription.partner?.partnership?.id === 'VC';
createWrapper({
subscription,
onDemandEnabled: true,
hasPaymentSource: isVCPartner,
});

// Should show Set Up Pay-as-you-go button instead of Add Credit Card
expect(screen.getByText('Set Up Pay-as-you-go')).toBeInTheDocument();
expect(screen.queryByText('Add Credit Card')).not.toBeInTheDocument();
});

it('renders initial on-demand budget setup state', () => {
const subscription = SubscriptionFixture({
plan: 'am1_business',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@ function getCards(organization: Organization, subscription: Subscription) {
const cards: React.ReactNode[] = [];
const isTrialOrFreePlan =
isTrialPlan(subscription.plan) || isDeveloperPlan(subscription.planDetails);

// the organization can use PAYG
const canUsePayg = supportsPayg(subscription);

// the user can update the PAYG budget
const canUpdatePayg = canUsePayg && hasBillingPerms;

if (subscription.canSelfServe && !isTrialOrFreePlan && hasBillingPerms) {
cards.push(
<NextBillCard
Expand All @@ -41,9 +46,7 @@ function getCards(organization: Organization, subscription: Subscription) {
);
}

const canUpdatePayg = canUsePayg && hasBillingPerms;

if (canUpdatePayg) {
if (canUsePayg) {
cards.push(
<PaygCard key="payg" subscription={subscription} organization={organization} />
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {OrganizationFixture} from 'sentry-fixture/organization';

import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription';
import {
InvoicedSubscriptionFixture,
SubscriptionFixture,
} from 'getsentry-test/fixtures/subscription';
import {
render,
renderGlobalModal,
Expand All @@ -26,6 +29,28 @@ describe('PaygCard', () => {
resetMockDate();
});

it('renders set/edit button for users with billing perms', () => {
const subscription = SubscriptionFixture({
organization,
plan: 'am3_team',
});
render(<PaygCard organization={organization} subscription={subscription} />);
expect(screen.getByRole('button', {name: 'Set limit'})).toBeInTheDocument();
});

it('does not render set/edit button for users without billing perms', () => {
const diffOrg = OrganizationFixture({
access: [],
});
const subscription = SubscriptionFixture({
organization: diffOrg,
plan: 'am3_team',
});
render(<PaygCard organization={diffOrg} subscription={subscription} />);
expect(screen.queryByRole('button', {name: 'Set limit'})).not.toBeInTheDocument();
expect(screen.queryByRole('button', {name: 'Edit limit'})).not.toBeInTheDocument();
});

it('renders for plan with no budget modes', async () => {
const subscription = SubscriptionFixture({
organization,
Expand Down Expand Up @@ -148,4 +173,45 @@ describe('PaygCard', () => {
// closes inline edit
expect(screen.getByRole('heading', {name: 'Pay-as-you-go'})).toBeInTheDocument();
});

it('enables edit button for present payment source', () => {
const subscription = SubscriptionFixture({
organization,
plan: 'am3_team',
});
render(<PaygCard organization={organization} subscription={subscription} />);
expect(screen.getByRole('button', {name: 'Set limit'})).toBeEnabled();
});

it('disables edit button if no payment source', () => {
const subscription = SubscriptionFixture({
organization,
plan: 'am3_team', // we should never have a paid plan without a payment source IRL, but for testing purposes
paymentSource: null,
});
render(<PaygCard organization={organization} subscription={subscription} />);
expect(screen.getByRole('button', {name: 'Set limit'})).toBeDisabled();
});

it('enables edit button for self-serve partner accounts', () => {
const subscription = SubscriptionFixture({
organization,
plan: 'am3_team',
paymentSource: null,
isSelfServePartner: true,
});
render(<PaygCard organization={organization} subscription={subscription} />);
expect(screen.getByRole('button', {name: 'Set limit'})).toBeEnabled();
});

it('enables edit button for manually invoiced PAYG', () => {
const subscription = InvoicedSubscriptionFixture({
organization,
plan: 'am3_business_ent',
paymentSource: null,
onDemandInvoicedManual: true,
});
render(<PaygCard organization={organization} subscription={subscription} />);
expect(screen.getByRole('button', {name: 'Set limit'})).toBeEnabled();
});
});
37 changes: 27 additions & 10 deletions static/gsApp/views/subscriptionPage/headerCards/paygCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
type OnDemandBudgets,
type Subscription,
} from 'getsentry/types';
import {displayBudgetName} from 'getsentry/utils/billing';
import {displayBudgetName, hasBillingAccess} from 'getsentry/utils/billing';
import {displayPrice} from 'getsentry/views/amCheckout/utils';
import {openOnDemandBudgetEditModal} from 'getsentry/views/onDemandBudgets/editOnDemandButton';
import {
Expand All @@ -40,6 +40,12 @@ function PaygCard({
organization: Organization;
subscription: Subscription;
}) {
const hasBillingPerms = hasBillingAccess(organization);
const hasPaymentSource = !!(
subscription.paymentSource ||
subscription.isSelfServePartner ||
subscription.onDemandInvoicedManual
);
const api = useApi();
const theme = useTheme();
const paygBudget = parseOnDemandBudgetsFromSubscription(subscription);
Expand Down Expand Up @@ -88,6 +94,9 @@ function PaygCard({

const handleEditPayg = useCallback(
(shouldHighlight = false) => {
if (!hasBillingPerms) {
return;
}
if (hasBudgetModes) {
openOnDemandBudgetEditModal({organization, subscription, theme});
} else {
Expand All @@ -97,7 +106,7 @@ function PaygCard({
setIsEditing(true);
}
},
[hasBudgetModes, organization, subscription, theme]
[hasBudgetModes, organization, subscription, theme, hasBillingPerms]
);

useEffect(() => {
Expand Down Expand Up @@ -193,14 +202,22 @@ function PaygCard({
<Heading as="h2" size="lg">
{displayBudgetName(subscription.planDetails, {title: true})}
</Heading>
<Button
size="xs"
onClick={() => {
handleEditPayg(false);
}}
>
{totalBudget > 0 ? t('Edit limit') : t('Set limit')}
</Button>
{hasBillingPerms && (
<Button
size="xs"
disabled={!hasPaymentSource}
title={
hasPaymentSource
? undefined
: t('You must add a payment method to edit the limit')
}
onClick={() => {
handleEditPayg(false);
}}
>
{totalBudget > 0 ? t('Edit limit') : t('Set limit')}
</Button>
)}
</Flex>,
<Container key="payg-budget">
<Text size="xl" bold>
Expand Down
13 changes: 2 additions & 11 deletions static/gsApp/views/subscriptionPage/onDemandSettings.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('edit on-demand budget', () => {
});
});

it('allows VC partner accounts to edit on-demand budget without payment source', () => {
it('allows self-serve partner accounts to edit on-demand budget without payment source', () => {
const subscription = SubscriptionFixture({
plan: 'am3_business',
planTier: PlanTier.AM3,
Expand All @@ -41,16 +41,7 @@ describe('edit on-demand budget', () => {
supportsOnDemand: true,
organization: onDemandOrg,
paymentSource: null,
partner: {
externalId: 'x123x',
name: 'VC Org',
partnership: {
id: 'VC',
displayName: 'VC',
supportNote: '',
},
isActive: true,
},
isSelfServePartner: true,
onDemandBudgets: {
enabled: true,
budgetMode: OnDemandBudgetMode.SHARED,
Expand Down
8 changes: 5 additions & 3 deletions static/gsApp/views/subscriptionPage/onDemandSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,11 @@ export function OnDemandSettings({subscription, organization}: OnDemandSettingsP
}

const onDemandEnabled = subscription.planDetails.allowOnDemand;
// VC partner accounts don't require a payment source (i.e. credit card) since they make all payments via VC
const isVCPartner = subscription.partner?.partnership?.id === 'VC';
const hasPaymentSource = !!subscription.paymentSource || isVCPartner;
const hasPaymentSource = !!(
subscription.paymentSource ||
subscription.isSelfServePartner ||
subscription.onDemandInvoicedManual
);
const hasOndemandBudgets =
hasOnDemandBudgetsFeature(organization, subscription) &&
Boolean(subscription.onDemandBudgets);
Expand Down
2 changes: 1 addition & 1 deletion static/gsApp/views/subscriptionPage/onDemandSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ class OnDemandSummary extends Component<Props, State> {
return this.renderNotEnabled();
}

if (!hasPaymentSource && !subscription.onDemandInvoicedManual) {
if (!hasPaymentSource) {
return this.renderNeedsPaymentSource();
}

Expand Down
35 changes: 31 additions & 4 deletions static/gsApp/views/subscriptionPage/subscriptionHeader.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe('SubscriptionHeader', () => {
hasPaygCard: boolean;
organization: Organization;
}) {
const hasBillingPerms = organization.access?.includes('org:billing');
await screen.findByRole('heading', {name: 'Subscription'});

if (hasNextBillCard) {
Expand All @@ -83,16 +84,22 @@ describe('SubscriptionHeader', () => {

if (hasPaygCard) {
await screen.findByRole('heading', {name: 'Pay-as-you-go'});
screen.getByRole('button', {name: 'Set limit'});

if (hasBillingPerms) {
expect(screen.getByRole('button', {name: 'Set limit'})).toBeInTheDocument();
} else {
expect(screen.queryByRole('button', {name: 'Set limit'})).not.toBeInTheDocument();
expect(
screen.queryByRole('button', {name: 'Edit limit'})
).not.toBeInTheDocument();
}
} else {
expect(
screen.queryByRole('heading', {name: 'Pay-as-you-go'})
).not.toBeInTheDocument();
expect(screen.queryByRole('button', {name: 'Set limit'})).not.toBeInTheDocument();
}

const hasBillingPerms = organization.access?.includes('org:billing');

// all subscriptions have links card
if (hasBillingPerms) {
expect(
Expand Down Expand Up @@ -240,7 +247,7 @@ describe('SubscriptionHeader', () => {
});
});

it('renders new header cards for self-serve customers and user without billing perms', async () => {
it('renders new header cards for self-serve free customers and user without billing perms', async () => {
const organization = OrganizationFixture({
features: ['subscriptions-v3'],
});
Expand All @@ -260,6 +267,26 @@ describe('SubscriptionHeader', () => {
});
});

it('renders new header cards for self-serve paid customers and user without billing perms', async () => {
const organization = OrganizationFixture({
features: ['subscriptions-v3'],
});
const subscription = SubscriptionFixture({
organization,
plan: 'am3_team',
});
SubscriptionStore.set(organization.slug, subscription);
render(
<SubscriptionHeader organization={organization} subscription={subscription} />
);
await assertNewHeaderCards({
organization,
hasNextBillCard: false,
hasBillingInfoCard: false,
hasPaygCard: true,
});
});

it('renders new header cards for self-serve customers on subscription trial', async () => {
const organization = OrganizationFixture({
features: ['subscriptions-v3'],
Expand Down
8 changes: 6 additions & 2 deletions static/gsApp/views/subscriptionPage/usageOverview.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,12 @@ describe('UsageOverview', () => {
expect(screen.getByRole('columnheader', {name: 'Product'})).toBeInTheDocument();
expect(screen.getByRole('columnheader', {name: 'Total usage'})).toBeInTheDocument();
expect(screen.getByRole('columnheader', {name: 'Reserved'})).toBeInTheDocument();
expect(screen.queryByText('Reserved spend')).not.toBeInTheDocument();
expect(screen.queryByText('Pay-as-you-go spend')).not.toBeInTheDocument();
expect(
screen.getByRole('columnheader', {name: 'Reserved spend'})
).toBeInTheDocument();
expect(
screen.getByRole('columnheader', {name: 'Pay-as-you-go spend'})
).toBeInTheDocument();
expect(
screen.queryByRole('button', {name: 'View usage history'})
).not.toBeInTheDocument();
Expand Down
3 changes: 0 additions & 3 deletions static/gsApp/views/subscriptionPage/usageOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ function ReservedUsageBar({percentUsed}: {percentUsed: number}) {
}

function UsageOverviewTable({subscription, organization, usageData}: UsageOverviewProps) {
const hasBillingPerms = organization.access.includes('org:billing');
const navigate = useNavigate();
const location = useLocation();
const [openState, setOpenState] = useState<Partial<Record<AddOnCategory, boolean>>>({});
Expand Down Expand Up @@ -273,15 +272,13 @@ function UsageOverviewTable({subscription, organization, usageData}: UsageOvervi
},
].filter(
column =>
(hasBillingPerms || !column.key.endsWith('Spend')) &&
(subscription.canSelfServe ||
!column.key.endsWith('Spend') ||
((subscription.onDemandInvoiced || subscription.onDemandInvoicedManual) &&
column.key === 'budgetSpend')) &&
(hasAnyPotentialOrActiveProductTrial || column.key !== 'trialInfo')
);
}, [
hasBillingPerms,
subscription.planDetails,
subscription.productTrials,
subscription.canSelfServe,
Expand Down
Loading