withSubscription.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import {Component} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import LoadingIndicator from 'sentry/components/loadingIndicator';
  4. import type {Organization} from 'sentry/types/organization';
  5. import getDisplayName from 'sentry/utils/getDisplayName';
  6. import useOrganization from 'sentry/utils/useOrganization';
  7. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  8. import type {Subscription} from 'getsentry/types';
  9. type InjectedSubscriptionProps = {
  10. subscription: Subscription;
  11. };
  12. type DependentProps = {
  13. organization?: Organization;
  14. // Generalized type for routing parameters.
  15. params?: Record<string, any | undefined>;
  16. subscription?: Subscription;
  17. };
  18. type Options = {
  19. /**
  20. * Disable displaying the loading indicator while waiting for the
  21. * subscription to load.
  22. */
  23. noLoader?: boolean;
  24. };
  25. type State = {
  26. subscription?: Subscription;
  27. };
  28. /**
  29. * HoC to inject the subscription object into the wrapped component. The
  30. * subscription will be loaded using the current org context, either through
  31. * params, a passed organization prop, or finally through organization context.
  32. *
  33. * If no organization ID can be determined, the subscription will be passed as
  34. * a `null` value.
  35. */
  36. function withSubscription<P extends InjectedSubscriptionProps>(
  37. WrappedComponent: React.ComponentType<P>,
  38. {noLoader}: Options = {}
  39. ) {
  40. class WithSubscription extends Component<
  41. Omit<P, keyof InjectedSubscriptionProps> & DependentProps,
  42. State
  43. > {
  44. static displayName = `withSubscription(${getDisplayName(WrappedComponent)})`;
  45. state: State = {
  46. subscription: this.props.subscription,
  47. };
  48. componentDidMount() {
  49. this.mounted = true;
  50. const orgSlug = this.getOrgSlug();
  51. if (orgSlug === null) {
  52. this.setState({subscription: this.props.subscription});
  53. } else {
  54. SubscriptionStore.get(orgSlug, (subscription: Subscription) => {
  55. if (!this.mounted) {
  56. return;
  57. }
  58. this.setState({subscription});
  59. this.configureScopeWithSubscriptionData(subscription);
  60. });
  61. }
  62. }
  63. componentWillUnmount() {
  64. this.mounted = false;
  65. this.unsubscribe();
  66. }
  67. unsubscribe = SubscriptionStore.listen(
  68. (subscription: Subscription) => this.onSubscriptionChange(subscription),
  69. undefined
  70. );
  71. private mounted = false;
  72. configureScopeWithSubscriptionData(subscription: Subscription) {
  73. const {plan, planTier, totalMembers, planDetails} = subscription;
  74. Sentry.setTag('plan', plan);
  75. Sentry.setTag('plan.name', planDetails?.name);
  76. Sentry.setTag('plan.max_members', `${planDetails?.maxMembers}`);
  77. Sentry.setTag('plan.total_members', `${totalMembers}`);
  78. Sentry.setTag('plan.tier', planTier);
  79. }
  80. onSubscriptionChange(subscription: Subscription) {
  81. if (subscription && this.mounted) {
  82. this.setState({subscription});
  83. this.configureScopeWithSubscriptionData(subscription);
  84. }
  85. }
  86. getOrgSlug() {
  87. if (this.props.params?.orgId) {
  88. return this.props.params.orgId;
  89. }
  90. if (this.props.organization) {
  91. return this.props.organization.slug;
  92. }
  93. return null;
  94. }
  95. render() {
  96. const {subscription} = this.state as State;
  97. const {organization, ...otherProps} = this.props;
  98. if (subscription === undefined) {
  99. return !noLoader && <LoadingIndicator />;
  100. }
  101. // Needed to solve type errors with DisabledDateRange hook.
  102. if (organization === undefined) {
  103. // TODO(any): HoC prop types not working w/ emotion https://github.com/emotion-js/emotion/issues/3261
  104. return (
  105. <WrappedComponent {...(otherProps as P as any)} subscription={subscription} />
  106. );
  107. }
  108. return (
  109. <WrappedComponent
  110. // TODO(any): HoC prop types not working w/ emotion https://github.com/emotion-js/emotion/issues/3261
  111. {...(this.props as P as any)}
  112. organization={organization}
  113. subscription={subscription}
  114. />
  115. );
  116. }
  117. }
  118. // XXX(epurkhiser): Until we convert this over to a FC we need the
  119. // intermediate functional component to access the organization context
  120. function WithSubscriptionWrapper(
  121. props: Omit<P, keyof InjectedSubscriptionProps> & DependentProps
  122. ) {
  123. const organization = useOrganization({allowNull: true});
  124. // TODO(any): HoC prop types not working w/ emotion https://github.com/emotion-js/emotion/issues/3261
  125. return (
  126. <WithSubscription organization={organization ?? undefined} {...(props as any)} />
  127. );
  128. }
  129. return WithSubscriptionWrapper;
  130. }
  131. export default withSubscription;