diff --git a/static/gsAdmin/components/changePlanAction.spec.tsx b/static/gsAdmin/components/changePlanAction.spec.tsx index 288dea2e837f8f..1c06f52dfea5c8 100644 --- a/static/gsAdmin/components/changePlanAction.spec.tsx +++ b/static/gsAdmin/components/changePlanAction.spec.tsx @@ -4,8 +4,10 @@ import {UserFixture} from 'sentry-fixture/user'; import {BillingConfigFixture} from 'getsentry-test/fixtures/billingConfig'; import {MetricHistoryFixture} from 'getsentry-test/fixtures/metricHistory'; import {PlanDetailsLookupFixture} from 'getsentry-test/fixtures/planDetailsLookup'; -import {SeerReservedBudgetFixture} from 'getsentry-test/fixtures/reservedBudget'; -import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription'; +import { + SubscriptionFixture, + SubscriptionWithSeerFixture, +} from 'getsentry-test/fixtures/subscription'; import { renderGlobalModal, screen, @@ -239,7 +241,7 @@ describe('ChangePlanAction', () => { }); it('completes form with addOns', async () => { - mockOrg.features = ['seer-billing', 'seer-user-billing']; + mockOrg.features = ['seer-billing', 'seer-user-billing']; // this won't happen IRL, but doing this for testing multiple addons const putMock = MockApiClient.addMockResponse({ url: `/customers/${mockOrg.slug}/subscription/`, method: 'PUT', @@ -378,7 +380,7 @@ describe('ChangePlanAction', () => { expect(requestData).toHaveProperty('reservedTransactions', 25000); }); - describe('Seer Budget', () => { + describe('Legacy Seer', () => { beforeEach(() => { mockOrg.features = ['seer-billing']; jest.clearAllMocks(); @@ -445,21 +447,10 @@ describe('ChangePlanAction', () => { it('initializes Seer budget checkbox based on current subscription', async () => { // Create subscription with Seer budget - const subscriptionWithSeer = SubscriptionFixture({ + const subscriptionWithSeer = SubscriptionWithSeerFixture({ organization: mockOrg, planTier: PlanTier.AM3, plan: 'am3_business', - billingInterval: 'monthly', - contractInterval: 'monthly', - reservedBudgets: [SeerReservedBudgetFixture({})], - categories: { - errors: MetricHistoryFixture({ - category: DataCategory.ERRORS, - reserved: 1000000, - prepaid: 1000000, - order: 1, - }), - }, }); SubscriptionStore.set(mockOrg.slug, subscriptionWithSeer); @@ -555,7 +546,7 @@ describe('ChangePlanAction', () => { expect(requestData).toHaveProperty('addOnSeer', true); }); - it('does not include seer parameter in form submission when checkbox is unchecked', async () => { + it('does not include add-on parameter in form submission when checkbox is unchecked', async () => { // Mock the PUT endpoint response const putMock = MockApiClient.addMockResponse({ url: `/customers/${mockOrg.slug}/subscription/`, diff --git a/static/gsAdmin/components/changePlanAction.tsx b/static/gsAdmin/components/changePlanAction.tsx index 74ed2a66fde8cd..fe19630c459eb0 100644 --- a/static/gsAdmin/components/changePlanAction.tsx +++ b/static/gsAdmin/components/changePlanAction.tsx @@ -24,7 +24,7 @@ import useApi from 'sentry/utils/useApi'; import PlanList from 'admin/components/planList'; import {ANNUAL, MONTHLY} from 'getsentry/constants'; import type {BillingConfig, Plan, Subscription} from 'getsentry/types'; -import {CheckoutType, PlanTier, ReservedBudgetCategoryType} from 'getsentry/types'; +import {CheckoutType, PlanTier} from 'getsentry/types'; const ALLOWED_TIERS = [PlanTier.MM2, PlanTier.AM1, PlanTier.AM2, PlanTier.AM3]; @@ -49,23 +49,6 @@ function ChangePlanAction({ const [formModel] = useState(() => new FormModel()); const orgId = organization.slug; - /** - * Check if the current subscription has Seer budget enabled - */ - const hasCurrentSeerBudget = useMemo(() => { - return ( - subscription.reservedBudgets?.some( - budget => - budget.apiName === ReservedBudgetCategoryType.SEER && budget.reservedBudget > 0 - ) ?? false - ); - }, [subscription.reservedBudgets]); - - // Initialize Seer budget value in form model - React.useEffect(() => { - formModel.setValue('addOnSeer', hasCurrentSeerBudget); - }, [formModel, hasCurrentSeerBudget]); - const api = useApi({persistInFlight: true}); const { data: configs, @@ -214,17 +197,11 @@ function ChangePlanAction({ return; } - // Add Seer budget parameter for AM plans and TEST tier - const submitData = { - ...data, - addOnSeer: formModel.getValue('addOnSeer'), - }; - if (activeTier === PlanTier.MM2) { try { await api.requestPromise(`/customers/${orgId}/`, { method: 'PUT', - data: submitData, + data, }); onSubmitSuccess(data); } catch (error) { @@ -237,7 +214,7 @@ function ChangePlanAction({ try { await api.requestPromise(`/customers/${orgId}/subscription/`, { method: 'PUT', - data: submitData, + data, }); onSubmitSuccess(data); onSuccess?.(); @@ -281,7 +258,6 @@ function ChangePlanAction({ setActiveTier(tab); setBillingInterval(MONTHLY); setContractInterval(MONTHLY); - formModel.setValue('addOnSeer', hasCurrentSeerBudget); }} > diff --git a/static/gsAdmin/components/planList.tsx b/static/gsAdmin/components/planList.tsx index e21c719f3442e9..3bdf4a79a59b3a 100644 --- a/static/gsAdmin/components/planList.tsx +++ b/static/gsAdmin/components/planList.tsx @@ -1,3 +1,4 @@ +import {useEffect, useMemo} from 'react'; import styled from '@emotion/styled'; import CheckboxField from 'sentry/components/forms/fields/checkboxField'; @@ -78,14 +79,27 @@ function PlanList({ 100000: '100K', }; - const availableProducts = Object.values(activePlan?.addOnCategories || {}) - .filter( - productInfo => - productInfo.billingFlag && organization.features.includes(productInfo.billingFlag) - ) - .map(productInfo => { - return productInfo; + const availableProducts = useMemo( + () => + Object.values(activePlan?.addOnCategories || {}) + .filter( + productInfo => + productInfo.billingFlag && + organization.features.includes(productInfo.billingFlag) + ) + .map(productInfo => { + return productInfo; + }), + [activePlan?.addOnCategories, organization.features] + ); + + useEffect(() => { + availableProducts.forEach(productInfo => { + const addOnKey = `addOn${toTitleCase(productInfo.apiName, {allowInnerUpperCase: true})}`; + const enabled = subscription.addOns?.[productInfo.apiName]?.enabled; + formModel.setValue(addOnKey, enabled); }); + }, [availableProducts, subscription.addOns, formModel]); return (

Available Products

{availableProducts.map(productInfo => { - const addOnKey = `addOn${toTitleCase(productInfo.apiName)}`; + const addOnKey = `addOn${toTitleCase(productInfo.apiName, {allowInnerUpperCase: true})}`; return ( - getProductCheckoutDescription({ - product: AddOnCategory.SEER, - isNewCheckout: !!isNewCheckout, - withPunctuation: false, - includedBudget, - }), - categoryInfo: { - [DataCategory.SEER_AUTOFIX]: { - description: t( - 'Uses the latest AI models with Sentry data to find root causes & proposes PRs' - ), - maxEventPriceDigits: 0, - }, - [DataCategory.SEER_SCANNER]: { - description: t( - 'Triages issues as they happen, automatically flagging highly-fixable ones for followup' - ), - maxEventPriceDigits: 3, - }, + const SEER_CHECKOUT_INFO = { + color: theme.pink400 as Color, + gradientEndColor: theme.pink100 as Color, + buttonBorderColor: theme.pink200 as Color, + getProductDescription: (includedBudget: string) => + getProductCheckoutDescription({ + product: AddOnCategory.SEER, + isNewCheckout: !!isNewCheckout, + withPunctuation: false, + includedBudget, + }), + categoryInfo: { + [DataCategory.SEER_AUTOFIX]: { + description: t( + 'Uses the latest AI models with Sentry data to find root causes & proposes PRs' + ), + maxEventPriceDigits: 0, + }, + [DataCategory.SEER_SCANNER]: { + description: t( + 'Triages issues as they happen, automatically flagging highly-fixable ones for followup' + ), + maxEventPriceDigits: 3, }, }, + }; + const PRODUCT_CHECKOUT_INFO = { + [AddOnCategory.SEER]: SEER_CHECKOUT_INFO, + [AddOnCategory.LEGACY_SEER]: SEER_CHECKOUT_INFO, [AddOnCategory.PREVENT]: { getProductDescription: (includedBudget: string) => getProductCheckoutDescription({ diff --git a/tests/js/getsentry-test/fixtures/constants.ts b/tests/js/getsentry-test/fixtures/constants.ts index eecf96949456e6..f3e196111f23d1 100644 --- a/tests/js/getsentry-test/fixtures/constants.ts +++ b/tests/js/getsentry-test/fixtures/constants.ts @@ -21,4 +21,4 @@ export const AM_ADD_ON_CATEGORIES = { order: 2, productName: 'prevent', }, -} satisfies Record; +} satisfies Record, AddOnCategoryInfo>; // TODO(seer): Add LEGACY_SEER once the backend is updated to use the new value and the rest of the frontend can be updated