projectStabilityScoreCard.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import round from 'lodash/round';
  2. import {
  3. getDiffInMinutes,
  4. shouldFetchPreviousPeriod,
  5. } from 'sentry/components/charts/utils';
  6. import LoadingError from 'sentry/components/loadingError';
  7. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  8. import ScoreCard from 'sentry/components/scoreCard';
  9. import {DEFAULT_STATS_PERIOD} from 'sentry/constants';
  10. import {IconArrow} from 'sentry/icons';
  11. import {t} from 'sentry/locale';
  12. import {PageFilters, SessionApiResponse, SessionFieldWithOperation} from 'sentry/types';
  13. import {defined} from 'sentry/utils';
  14. import {formatAbbreviatedNumber} from 'sentry/utils/formatters';
  15. import {getPeriod} from 'sentry/utils/getPeriod';
  16. import {useApiQuery} from 'sentry/utils/queryClient';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import {displayCrashFreePercent} from 'sentry/views/releases/utils';
  19. import {
  20. getSessionTermDescription,
  21. SessionTerm,
  22. } from 'sentry/views/releases/utils/sessionTerm';
  23. import MissingReleasesButtons from '../missingFeatureButtons/missingReleasesButtons';
  24. type Props = {
  25. field:
  26. | SessionFieldWithOperation.CRASH_FREE_RATE_SESSIONS
  27. | SessionFieldWithOperation.CRASH_FREE_RATE_USERS;
  28. hasSessions: boolean | null;
  29. isProjectStabilized: boolean;
  30. selection: PageFilters;
  31. query?: string;
  32. };
  33. const useCrashFreeRate = (props: Props) => {
  34. const organization = useOrganization();
  35. const {selection, isProjectStabilized, hasSessions, query, field} = props;
  36. const isEnabled = !!(isProjectStabilized && hasSessions);
  37. const {projects, environments: environment, datetime} = selection;
  38. const {period} = datetime;
  39. const doubledPeriod = getPeriod(
  40. {period, start: undefined, end: undefined},
  41. {shouldDoublePeriod: true}
  42. ).statsPeriod;
  43. const commonQuery = {
  44. environment,
  45. project: projects[0],
  46. interval: getDiffInMinutes(datetime) > 24 * 60 ? '1d' : '1h',
  47. query,
  48. field,
  49. };
  50. // Unfortunately we can't do something like statsPeriod=28d&interval=14d to get scores for this and previous interval with the single request
  51. // https://github.com/getsentry/sentry/pull/22770#issuecomment-758595553
  52. const currentQuery = useApiQuery<SessionApiResponse>(
  53. [
  54. `/organizations/${organization.slug}/sessions/`,
  55. {
  56. query: {
  57. ...commonQuery,
  58. ...normalizeDateTimeParams(datetime),
  59. },
  60. },
  61. ],
  62. {staleTime: 0, enabled: isEnabled}
  63. );
  64. const previousQuery = useApiQuery<SessionApiResponse>(
  65. [
  66. `/organizations/${organization.slug}/sessions/`,
  67. {
  68. query: {
  69. ...commonQuery,
  70. statsPeriodStart: doubledPeriod,
  71. statsPeriodEnd: period ?? DEFAULT_STATS_PERIOD,
  72. },
  73. },
  74. ],
  75. {
  76. staleTime: 0,
  77. enabled:
  78. isEnabled &&
  79. shouldFetchPreviousPeriod({
  80. start: datetime.start,
  81. end: datetime.end,
  82. period: datetime.period,
  83. }),
  84. }
  85. );
  86. return {
  87. crashFreeRate: currentQuery.data,
  88. previousCrashFreeRate: previousQuery.data,
  89. isLoading: currentQuery.isLoading || previousQuery.isLoading,
  90. error: currentQuery.error || previousQuery.error,
  91. refetch: () => {
  92. currentQuery.refetch();
  93. previousQuery.refetch();
  94. },
  95. };
  96. };
  97. // shouldRenderBadRequests = true;
  98. function ProjectStabilityScoreCard(props: Props) {
  99. const {hasSessions} = props;
  100. const organization = useOrganization();
  101. const cardTitle =
  102. props.field === SessionFieldWithOperation.CRASH_FREE_RATE_SESSIONS
  103. ? t('Crash Free Sessions')
  104. : t('Crash Free Users');
  105. const cardHelp = getSessionTermDescription(
  106. props.field === SessionFieldWithOperation.CRASH_FREE_RATE_SESSIONS
  107. ? SessionTerm.CRASH_FREE_SESSIONS
  108. : SessionTerm.CRASH_FREE_USERS,
  109. null
  110. );
  111. const {crashFreeRate, previousCrashFreeRate, isLoading, error, refetch} =
  112. useCrashFreeRate(props);
  113. const score = !crashFreeRate
  114. ? undefined
  115. : crashFreeRate?.groups[0]?.totals[props.field] * 100;
  116. const previousScore = !previousCrashFreeRate
  117. ? undefined
  118. : previousCrashFreeRate?.groups[0]?.totals[props.field] * 100;
  119. const trend =
  120. defined(score) && defined(previousScore)
  121. ? round(score - previousScore, 3)
  122. : undefined;
  123. const shouldRenderTrend = !isLoading && defined(score) && defined(trend);
  124. if (hasSessions === false) {
  125. return (
  126. <ScoreCard
  127. title={cardTitle}
  128. help={cardHelp}
  129. score={<MissingReleasesButtons organization={organization} health />}
  130. />
  131. );
  132. }
  133. if (error) {
  134. return (
  135. <LoadingError
  136. message={error.responseJSON?.detail || t('There was an error loading data.')}
  137. onRetry={refetch}
  138. />
  139. );
  140. }
  141. return (
  142. <ScoreCard
  143. title={cardTitle}
  144. help={cardHelp}
  145. score={isLoading || !defined(score) ? '\u2014' : displayCrashFreePercent(score)}
  146. trend={
  147. shouldRenderTrend ? (
  148. <div>
  149. {trend >= 0 ? (
  150. <IconArrow direction="up" size="xs" />
  151. ) : (
  152. <IconArrow direction="down" size="xs" />
  153. )}
  154. {`${formatAbbreviatedNumber(Math.abs(trend))}\u0025`}
  155. </div>
  156. ) : null
  157. }
  158. trendStatus={!trend ? undefined : trend > 0 ? 'good' : 'bad'}
  159. />
  160. );
  161. }
  162. export default ProjectStabilityScoreCard;