quotaExceededAlert.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import {useEffect} from 'react';
  2. import moment from 'moment-timezone';
  3. import {Alert} from 'sentry/components/core/alert';
  4. import Link from 'sentry/components/links/link';
  5. import {tct} from 'sentry/locale';
  6. import type {PageFilters} from 'sentry/types/core';
  7. import type {Organization} from 'sentry/types/organization';
  8. import {getFormattedDate} from 'sentry/utils/dates';
  9. import {parsePeriodToHours} from 'sentry/utils/duration/parsePeriodToHours';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import usePageFilters from 'sentry/utils/usePageFilters';
  12. import withSubscription from 'getsentry/components/withSubscription';
  13. import {usePerformanceUsageStats} from 'getsentry/hooks/performance/usePerformanceUsageStats';
  14. import type {Subscription} from 'getsentry/types';
  15. import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics';
  16. const DATE_FORMAT = 'MMM DD, YYYY';
  17. // Returns the beggining of statsPeriod formatted like "Jan 1, 2022"
  18. // or absolute date range formatted like "Jan 1, 2022 - Jan 2, 2022"
  19. function getFormattedDateTime(dateTime: PageFilters['datetime']): string | null {
  20. const {start, end, period} = dateTime;
  21. if (period) {
  22. const periodToHours = parsePeriodToHours(period);
  23. const periodStartDate = new Date(Date.now() - periodToHours * 60 * 60 * 1000);
  24. return getFormattedDate(periodStartDate, DATE_FORMAT);
  25. }
  26. if (start && end) {
  27. return `${getFormattedDate(new Date(start), DATE_FORMAT)} - ${getFormattedDate(new Date(end), DATE_FORMAT)}`;
  28. }
  29. return null;
  30. }
  31. function useQuotaExceededAlertMessage(
  32. subscription: Subscription,
  33. organization: Organization
  34. ) {
  35. const {selection} = usePageFilters();
  36. let hasExceededPerformanceUsageLimit: boolean | null = null;
  37. const dataCategories = subscription?.categories;
  38. if (dataCategories) {
  39. if ('transactions' in dataCategories) {
  40. hasExceededPerformanceUsageLimit =
  41. dataCategories.transactions?.usageExceeded || false;
  42. } else if ('spans' in dataCategories) {
  43. hasExceededPerformanceUsageLimit = dataCategories.spans?.usageExceeded || false;
  44. }
  45. }
  46. const {data: performanceUsageStats} = usePerformanceUsageStats({
  47. organization,
  48. dateRange: selection.datetime,
  49. projectIds: selection.projects,
  50. });
  51. // Check if events were dropped due to exceeding the transaction/spans quota
  52. const droppedEventsCount = performanceUsageStats?.totals['sum(quantity)'] || 0;
  53. if (droppedEventsCount === 0 || !hasExceededPerformanceUsageLimit || !subscription) {
  54. return null;
  55. }
  56. const formattedDateRange: string | null = getFormattedDateTime(selection.datetime);
  57. const billingPageLink = (
  58. <Link
  59. to={{
  60. pathname: `/settings/billing/checkout/?referrer=trace-view`,
  61. query: {
  62. skipBundles: true,
  63. },
  64. }}
  65. />
  66. );
  67. const periodRenewalDate = moment(subscription.onDemandPeriodEnd)
  68. .add(1, 'days')
  69. .format('ll');
  70. if (!formattedDateRange) {
  71. return subscription?.onDemandBudgets?.enabled
  72. ? ['am1', 'am2'].includes(subscription.planTier)
  73. ? tct(
  74. 'You’ve exceeded your on-demand budget during this date range and results will be skewed. We can’t collect more spans until [subscriptionRenewalDate]. If you need more, [billingPageLink:increase your on-demand budget].',
  75. {
  76. periodRenewalDate,
  77. billingPageLink,
  78. }
  79. )
  80. : tct(
  81. 'You’ve exceeded your pay-as-you-go budget during this date range and results will be skewed. We can’t collect more spans until [periodRenewalDate]. If you need more, [billingPageLink:increase your pay-as-you-go budget].',
  82. {
  83. periodRenewalDate,
  84. billingPageLink,
  85. }
  86. )
  87. : tct(
  88. 'You’ve exceeded your reserved volumes during this date range and results will be skewed. We can’t collect more spans until [periodRenewalDate]. If you need more, [billingPageLink:increase your reserved volumes].',
  89. {
  90. periodRenewalDate,
  91. billingPageLink,
  92. }
  93. );
  94. }
  95. const {period} = selection.datetime;
  96. return subscription?.onDemandBudgets?.enabled
  97. ? ['am1', 'am2'].includes(subscription.planTier)
  98. ? tct(
  99. 'You’ve exceeded your on-demand budget during this date range and results will be skewed. We can’t collect more spans until [periodRenewalDate]. [rest]',
  100. {
  101. periodRenewalDate,
  102. rest: period
  103. ? tct(
  104. 'If you need more, [billingPageLink: increase your on-demand budget] or adjust your date range prior to [formattedDateRange].',
  105. {
  106. billingPageLink,
  107. formattedDateRange,
  108. }
  109. )
  110. : tct(
  111. 'If you need more, [billingPageLink: increase your on-demand budget] or adjust your date range before or after [formattedDateRange].',
  112. {
  113. billingPageLink,
  114. formattedDateRange,
  115. }
  116. ),
  117. }
  118. )
  119. : tct(
  120. 'You’ve exceeded your pay-as-you-go budget during this date range and results will be skewed. We can’t collect more spans until [periodRenewalDate]. [rest]',
  121. {
  122. periodRenewalDate,
  123. rest: period
  124. ? tct(
  125. 'If you need more, [billingPageLink: increase your pay-as-you-go budget] or adjust your date range prior to [formattedDateRange].',
  126. {
  127. billingPageLink,
  128. formattedDateRange,
  129. }
  130. )
  131. : tct(
  132. 'If you need more, [billingPageLink: increase your pay-as-you-go budget] or adjust your date range before or after [formattedDateRange].',
  133. {
  134. billingPageLink,
  135. formattedDateRange,
  136. }
  137. ),
  138. }
  139. )
  140. : tct(
  141. 'You’ve exceeded your reserved volumes during this date range and results will be skewed. We can’t collect more spans until [periodRenewalDate]. [rest]',
  142. {
  143. periodRenewalDate,
  144. rest: period
  145. ? tct(
  146. 'If you need more, [billingPageLink: increase your reserved volumes] or adjust your date range prior to [formattedDateRange].',
  147. {
  148. billingPageLink,
  149. formattedDateRange,
  150. }
  151. )
  152. : tct(
  153. 'If you need more, [billingPageLink: increase your reserved volumes] or adjust your date range before or after [formattedDateRange].',
  154. {
  155. billingPageLink,
  156. formattedDateRange,
  157. }
  158. ),
  159. }
  160. );
  161. }
  162. type Props = {
  163. referrer: string;
  164. subscription: Subscription;
  165. };
  166. export function QuotaExceededAlert(props: Props) {
  167. const organization = useOrganization();
  168. const message = useQuotaExceededAlertMessage(props.subscription, organization);
  169. useEffect(() => {
  170. if (!message) {
  171. return;
  172. }
  173. trackGetsentryAnalytics('performance.quota_exceeded_alert.displayed', {
  174. organization,
  175. referrer: props.referrer,
  176. });
  177. }, [message, organization, props.referrer]);
  178. if (!message) {
  179. return null;
  180. }
  181. return (
  182. <Alert.Container>
  183. <Alert type="warning" showIcon>
  184. {message}
  185. </Alert>
  186. </Alert.Container>
  187. );
  188. }
  189. export default withSubscription(QuotaExceededAlert, {noLoader: true});