From e72fce29e82e4206be9aeb4061c7f5971261638a Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 10 Oct 2025 14:34:45 -0400 Subject: [PATCH 1/4] feat(checkout v3): Auto-open sliders for customers with reserved volumes --- .../reserveAdditionalVolume.spec.tsx | 23 +++++++++---------- .../amCheckout/reserveAdditionalVolume.tsx | 13 +++++++++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/static/gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx b/static/gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx index ea7fdcab3a49a4..d43d65b96b1da6 100644 --- a/static/gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx +++ b/static/gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx @@ -8,7 +8,7 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import {MONTHLY} from 'getsentry/constants'; import SubscriptionStore from 'getsentry/stores/subscriptionStore'; -import {PlanTier} from 'getsentry/types'; +import {PlanTier, type Subscription} from 'getsentry/types'; import ReserveAdditionalVolume from 'getsentry/views/amCheckout/reserveAdditionalVolume'; type SliderInfo = { @@ -201,26 +201,17 @@ describe('ReserveAdditionalVolume', () => { const transactions = screen.getByTestId('transactions-volume-item').textContent; expect(transactions).not.toContain('Sentry Performance'); }); - - it('can hide sliders', async () => { - render(); - await openSection(); - expect(screen.getByTestId('errors-volume-item')).toBeInTheDocument(); - await closeSection(); - expect(screen.queryByTestId('errors-volume-item')).not.toBeInTheDocument(); - }); }); describe('Modern Plans', () => { const {organization} = initializeOrg(); - const subscription = SubscriptionFixture({organization}); + let subscription: Subscription; const billingConfig = BillingConfigFixture(PlanTier.AM3); const bizPlanMonthly = PlanDetailsLookupFixture('am3_business')!; - const stepProps = { + const stepProps: any = { checkoutTier: PlanTier.AM3, - subscription, isActive: true, stepNumber: 2, onUpdate: jest.fn(), @@ -238,6 +229,8 @@ describe('ReserveAdditionalVolume', () => { }; beforeEach(() => { + subscription = SubscriptionFixture({organization}); + stepProps.subscription = subscription; SubscriptionStore.set(organization.slug, subscription); MockApiClient.addMockResponse({ url: `/customers/${organization.slug}/plan-migrations/?applied=0`, @@ -304,5 +297,11 @@ describe('ReserveAdditionalVolume', () => { await closeSection(); expect(screen.queryByTestId('errors-volume-item')).not.toBeInTheDocument(); }); + + it('auto-shows sliders if customer has reserved volume above platform', () => { + subscription.categories.errors!.reserved = 100_000; + render(); + expect(screen.getByTestId('errors-volume-item')).toBeInTheDocument(); + }); }); }); diff --git a/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx b/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx index 8b1d65277b9a44..aa21b5e89658b6 100644 --- a/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx +++ b/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx @@ -15,7 +15,7 @@ import {isAmPlan} from 'getsentry/utils/billing'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; import VolumeSliders from 'getsentry/views/amCheckout/steps/volumeSliders'; import type {StepProps} from 'getsentry/views/amCheckout/types'; -import {formatPrice, getShortInterval} from 'getsentry/views/amCheckout/utils'; +import {formatPrice, getBucket, getShortInterval} from 'getsentry/views/amCheckout/utils'; function ReserveAdditionalVolume({ organization, @@ -33,7 +33,16 @@ function ReserveAdditionalVolume({ | 'formData' | 'onUpdate' >) { - const [showSliders, setShowSliders] = useState(false); + // if the customer has any reserved volume above platform already, auto-show the sliders + const [showSliders, setShowSliders] = useState( + Object.values(subscription.categories) + .filter(({category}) => activePlan.checkoutCategories.includes(category)) + .some( + ({category, reserved}) => + getBucket({buckets: activePlan.planCategories[category], events: reserved ?? 0}) + .price > 0 + ) + ); const reservedVolumeTotal = useMemo(() => { return Object.entries(formData.reserved).reduce((acc, [category, value]) => { const bucket = activePlan.planCategories?.[category as DataCategory]?.find( From e05ea27613e153033730deb884653f12aebad4c5 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 10 Oct 2025 14:39:15 -0400 Subject: [PATCH 2/4] some safety --- .../amCheckout/reserveAdditionalVolume.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx b/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx index aa21b5e89658b6..3062b1e2451f9f 100644 --- a/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx +++ b/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx @@ -11,7 +11,7 @@ import {t} from 'sentry/locale'; import type {DataCategory} from 'sentry/types/core'; import {PlanTier} from 'getsentry/types'; -import {isAmPlan} from 'getsentry/utils/billing'; +import {isAmPlan, isDeveloperPlan} from 'getsentry/utils/billing'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; import VolumeSliders from 'getsentry/views/amCheckout/steps/volumeSliders'; import type {StepProps} from 'getsentry/views/amCheckout/types'; @@ -35,13 +35,17 @@ function ReserveAdditionalVolume({ >) { // if the customer has any reserved volume above platform already, auto-show the sliders const [showSliders, setShowSliders] = useState( - Object.values(subscription.categories) - .filter(({category}) => activePlan.checkoutCategories.includes(category)) - .some( - ({category, reserved}) => - getBucket({buckets: activePlan.planCategories[category], events: reserved ?? 0}) - .price > 0 - ) + isDeveloperPlan(subscription.planDetails) + ? false + : Object.values(subscription.categories ?? {}) + .filter(({category}) => activePlan.checkoutCategories.includes(category)) + .some( + ({category, reserved}) => + getBucket({ + buckets: activePlan.planCategories[category], + events: reserved ?? 0, + }).price > 0 + ) ); const reservedVolumeTotal = useMemo(() => { return Object.entries(formData.reserved).reduce((acc, [category, value]) => { From 58e94a09fb2e2c96ef628272baae921d44598e22 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 10 Oct 2025 14:40:18 -0400 Subject: [PATCH 3/4] fix test --- .../gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/static/gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx b/static/gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx index d43d65b96b1da6..19208f037099a6 100644 --- a/static/gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx +++ b/static/gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx @@ -299,8 +299,9 @@ describe('ReserveAdditionalVolume', () => { }); it('auto-shows sliders if customer has reserved volume above platform', () => { - subscription.categories.errors!.reserved = 100_000; - render(); + const paidSub = SubscriptionFixture({organization, plan: 'am3_business'}); + paidSub.categories.errors!.reserved = 100_000; + render(); expect(screen.getByTestId('errors-volume-item')).toBeInTheDocument(); }); }); From e39ed8273bc638675acbaf6d8da96f91eef3e201 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 10 Oct 2025 14:41:39 -0400 Subject: [PATCH 4/4] more safety --- static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx b/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx index 3062b1e2451f9f..8c0c1b5a8d075c 100644 --- a/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx +++ b/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx @@ -38,7 +38,11 @@ function ReserveAdditionalVolume({ isDeveloperPlan(subscription.planDetails) ? false : Object.values(subscription.categories ?? {}) - .filter(({category}) => activePlan.checkoutCategories.includes(category)) + .filter( + ({category}) => + activePlan.checkoutCategories.includes(category) && + category in activePlan.planCategories + ) .some( ({category, reserved}) => getBucket({