diff --git a/static/app/components/profiling/billing/alerts.tsx b/static/app/components/profiling/billing/alerts.tsx index b26b18b6d70f97..a14b2a7d9d5372 100644 --- a/static/app/components/profiling/billing/alerts.tsx +++ b/static/app/components/profiling/billing/alerts.tsx @@ -11,3 +11,7 @@ export const ContinuousProfilingBetaAlertBanner = HookOrDefault({ export const ContinuousProfilingBetaSDKAlertBanner = HookOrDefault({ hookName: 'component:continuous-profiling-beta-sdk-banner', }); + +export const ContinuousProfilingNoQuotaAlertBanner = HookOrDefault({ + hookName: 'component:continuous-profiling-no-quota-banner', +}); diff --git a/static/app/types/hooks.tsx b/static/app/types/hooks.tsx index 19c39bcbee586d..95745e9a385c04 100644 --- a/static/app/types/hooks.tsx +++ b/static/app/types/hooks.tsx @@ -102,6 +102,10 @@ type ContinuousProfilingBetaAlertBannerProps = { organization: Organization; }; +type ContinuousProfilingNoQuotaBannerProps = { + organization: Organization; +}; + type CronsBillingBannerProps = { organization: Organization; }; @@ -162,6 +166,7 @@ type ComponentHooks = { 'component:confirm-account-close': () => React.ComponentType; 'component:continuous-profiling-beta-banner': () => React.ComponentType; 'component:continuous-profiling-beta-sdk-banner': () => React.ComponentType; + 'component:continuous-profiling-no-quota-banner': () => React.ComponentType; 'component:crons-list-page-header': () => React.ComponentType; 'component:crons-onboarding-panel': () => React.ComponentType; 'component:dashboards-header': () => React.ComponentType; diff --git a/static/app/views/profiling/content.tsx b/static/app/views/profiling/content.tsx index 668aa7196c46fd..e4b1ee93cdc30f 100644 --- a/static/app/views/profiling/content.tsx +++ b/static/app/views/profiling/content.tsx @@ -18,6 +18,7 @@ import {TransactionSearchQueryBuilder} from 'sentry/components/performance/trans import { ContinuousProfilingBetaAlertBanner, ContinuousProfilingBetaSDKAlertBanner, + ContinuousProfilingNoQuotaAlertBanner, ProfilingBetaAlertBanner, } from 'sentry/components/profiling/billing/alerts'; import {ProfileEventsTable} from 'sentry/components/profiling/profileEventsTable'; @@ -146,6 +147,7 @@ export default function ProfilingContent({location}: ProfilingContentProps) { + diff --git a/static/gsApp/components/profiling/alerts.tsx b/static/gsApp/components/profiling/alerts.tsx index 0360b56dcc4b0f..767f3aa7fd6c4c 100644 --- a/static/gsApp/components/profiling/alerts.tsx +++ b/static/gsApp/components/profiling/alerts.tsx @@ -17,8 +17,8 @@ import {openAM2ProfilingUpsellModal} from 'getsentry/actionCreators/modal'; import AddEventsCTA, {type EventType} from 'getsentry/components/addEventsCTA'; import withSubscription from 'getsentry/components/withSubscription'; import type {Subscription} from 'getsentry/types'; -import {PlanTier} from 'getsentry/types'; -import {isAm2Plan, isEnterprise} from 'getsentry/utils/billing'; +import {OnDemandBudgetMode, PlanTier} from 'getsentry/types'; +import {isAm2Plan, isAm3Plan, isEnterprise} from 'getsentry/utils/billing'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; export function makeLinkToOwnersAndBillingMembers( @@ -372,6 +372,170 @@ export function ContinuousProfilingBetaSDKAlertBanner() { ); } +interface ContinuousProfilingNoQuotaAlertBannerProps { + organization: Organization; + subscription: Subscription; +} + +function ContinuousProfilingNoQuotaAlertAm2Banner({ + organization, + subscription, +}: ContinuousProfilingNoQuotaAlertBannerProps) { + const hasBudget = hasBudgetConfiguredForContinuousOrUiProfilesHours(subscription); + + // As long as they have a budget set, don't show this banner. + if (hasBudget) { + return null; + } + + // For AM2 plans, if they haven't tried to send any continuous/ui profiles, don't + // show this banner because they can still use transaction profiles. + if ( + !subscription.categories.profileDuration?.usageExceeded && + !subscription.categories.profileDurationUI?.usageExceeded + ) { + return null; + } + + const eventTypes: EventType[] = [ + DataCategoryExact.PROFILE_DURATION, + DataCategoryExact.PROFILE_DURATION_UI, + ]; + + return ( + + + } + > + {t( + 'Continuous Profiling and UI Profiling requires you to set your On-demand budget.' + )} + + + ); +} + +function ContinuousProfilingNoQuotaAlertAm3Banner({ + organization, + subscription, +}: ContinuousProfilingNoQuotaAlertBannerProps) { + const hasBudget = hasBudgetConfiguredForContinuousOrUiProfilesHours(subscription); + + // As long as they have a budget set, don't show this banner. + if (hasBudget) { + return null; + } + + const eventTypes: EventType[] = [ + DataCategoryExact.PROFILE_DURATION, + DataCategoryExact.PROFILE_DURATION_UI, + ]; + + return ( + + + } + > + {t( + 'Continuous Profiling and UI Profiling requires you to set your Pay-as-you-go budget.' + )} + + + ); +} + +function hasBudgetConfiguredForContinuousOrUiProfilesHours( + subscription: Subscription +): boolean { + if ( + subscription.categories.profileDuration?.reserved || + subscription.categories.profileDurationUI?.reserved + ) { + return true; + } + + if (subscription.onDemandBudgets) { + if (subscription.onDemandBudgets.budgetMode === OnDemandBudgetMode.SHARED) { + if (subscription.onDemandBudgets.sharedMaxBudget) { + return true; + } + } else if ( + subscription.onDemandBudgets.budgetMode === OnDemandBudgetMode.PER_CATEGORY + ) { + if ( + subscription.onDemandBudgets.profileDurationBudget || + subscription.onDemandBudgets.profileDurationUIBudget + ) { + return true; + } + } + } + + return false; +} + +function ContinuousProfilingNoQuotaAlertBannerComponent({ + organization, + subscription, +}: ContinuousProfilingNoQuotaAlertBannerProps) { + if (isAm2Plan(subscription.plan)) { + return ( + + ); + } + + if (isAm3Plan(subscription.plan)) { + return ( + + ); + } + + return null; +} + +export const ContinuousProfilingNoQuotaAlertBanner = withSubscription( + ContinuousProfilingNoQuotaAlertBannerComponent +); + interface SDKDeprecation { minimumVersion: string; projectId: string; diff --git a/static/gsApp/registerHooks.tsx b/static/gsApp/registerHooks.tsx index 616b1f44e79c9e..7626e87a1eb02b 100644 --- a/static/gsApp/registerHooks.tsx +++ b/static/gsApp/registerHooks.tsx @@ -75,6 +75,7 @@ import OpenInDiscoverBtn from './components/openInDiscoverBtn'; import { ContinuousProfilingBetaAlertBanner, ContinuousProfilingBetaSDKAlertBanner, + ContinuousProfilingNoQuotaAlertBanner, ProfilingBetaAlertBanner, } from './components/profiling/alerts'; import ReplayOnboardingAlert from './components/replayOnboardingAlert'; @@ -208,6 +209,8 @@ const GETSENTRY_HOOKS: Partial = { 'component:continuous-profiling-beta-banner': () => ContinuousProfilingBetaAlertBanner, 'component:continuous-profiling-beta-sdk-banner': () => ContinuousProfilingBetaSDKAlertBanner, + 'component:continuous-profiling-no-quota-banner': () => + ContinuousProfilingNoQuotaAlertBanner, 'component:explore-date-range-query-limit-footer': () => ExploreDateRangeQueryLimitFooter, /**