import {Component} from 'react'; import * as Sentry from '@sentry/react'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import type {Organization} from 'sentry/types/organization'; import getDisplayName from 'sentry/utils/getDisplayName'; import useOrganization from 'sentry/utils/useOrganization'; import SubscriptionStore from 'getsentry/stores/subscriptionStore'; import type {Subscription} from 'getsentry/types'; type InjectedSubscriptionProps = { subscription: Subscription; }; type DependentProps = { organization?: Organization; // Generalized type for routing parameters. params?: Record; subscription?: Subscription; }; type Options = { /** * Disable displaying the loading indicator while waiting for the * subscription to load. */ noLoader?: boolean; }; type State = { subscription?: Subscription; }; /** * HoC to inject the subscription object into the wrapped component. The * subscription will be loaded using the current org context, either through * params, a passed organization prop, or finally through organization context. * * If no organization ID can be determined, the subscription will be passed as * a `null` value. */ function withSubscription

( WrappedComponent: React.ComponentType

, {noLoader}: Options = {} ) { class WithSubscription extends Component< Omit & DependentProps, State > { static displayName = `withSubscription(${getDisplayName(WrappedComponent)})`; state: State = { subscription: this.props.subscription, }; componentDidMount() { this.mounted = true; const orgSlug = this.getOrgSlug(); if (orgSlug === null) { this.setState({subscription: this.props.subscription}); } else { SubscriptionStore.get(orgSlug, (subscription: Subscription) => { if (!this.mounted) { return; } this.setState({subscription}); this.configureScopeWithSubscriptionData(subscription); }); } } componentWillUnmount() { this.mounted = false; this.unsubscribe(); } unsubscribe = SubscriptionStore.listen( (subscription: Subscription) => this.onSubscriptionChange(subscription), undefined ); private mounted = false; configureScopeWithSubscriptionData(subscription: Subscription) { const {plan, planTier, totalMembers, planDetails} = subscription; Sentry.setTag('plan', plan); Sentry.setTag('plan.name', planDetails?.name); Sentry.setTag('plan.max_members', `${planDetails?.maxMembers}`); Sentry.setTag('plan.total_members', `${totalMembers}`); Sentry.setTag('plan.tier', planTier); } onSubscriptionChange(subscription: Subscription) { if (subscription && this.mounted) { this.setState({subscription}); this.configureScopeWithSubscriptionData(subscription); } } getOrgSlug() { if (this.props.params?.orgId) { return this.props.params.orgId; } if (this.props.organization) { return this.props.organization.slug; } return null; } render() { const {subscription} = this.state as State; const {organization, ...otherProps} = this.props; if (subscription === undefined) { return !noLoader && ; } // Needed to solve type errors with DisabledDateRange hook. if (organization === undefined) { // TODO(any): HoC prop types not working w/ emotion https://github.com/emotion-js/emotion/issues/3261 return ( ); } return ( ); } } // XXX(epurkhiser): Until we convert this over to a FC we need the // intermediate functional component to access the organization context function WithSubscriptionWrapper( props: Omit & DependentProps ) { const organization = useOrganization({allowNull: true}); // TODO(any): HoC prop types not working w/ emotion https://github.com/emotion-js/emotion/issues/3261 return ( ); } return WithSubscriptionWrapper; } export default withSubscription;