import round from 'lodash/round'; import { getDiffInMinutes, shouldFetchPreviousPeriod, } from 'sentry/components/charts/utils'; import LoadingError from 'sentry/components/loadingError'; import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse'; import ScoreCard from 'sentry/components/scoreCard'; import {DEFAULT_STATS_PERIOD} from 'sentry/constants'; import {IconArrow} from 'sentry/icons'; import {t} from 'sentry/locale'; import type {PageFilters, SessionApiResponse} from 'sentry/types'; import {SessionFieldWithOperation} from 'sentry/types'; import {defined} from 'sentry/utils'; import {formatAbbreviatedNumber} from 'sentry/utils/formatters'; import {getPeriod} from 'sentry/utils/getPeriod'; import {useApiQuery} from 'sentry/utils/queryClient'; import useOrganization from 'sentry/utils/useOrganization'; import {displayCrashFreePercent} from 'sentry/views/releases/utils'; import { getSessionTermDescription, SessionTerm, } from 'sentry/views/releases/utils/sessionTerm'; import MissingReleasesButtons from '../missingFeatureButtons/missingReleasesButtons'; type Props = { field: | SessionFieldWithOperation.CRASH_FREE_RATE_SESSIONS | SessionFieldWithOperation.CRASH_FREE_RATE_USERS; hasSessions: boolean | null; isProjectStabilized: boolean; selection: PageFilters; query?: string; }; const useCrashFreeRate = (props: Props) => { const organization = useOrganization(); const {selection, isProjectStabilized, hasSessions, query, field} = props; const isEnabled = !!(isProjectStabilized && hasSessions); const {projects, environments: environment, datetime} = selection; const {period} = datetime; const doubledPeriod = getPeriod( {period, start: undefined, end: undefined}, {shouldDoublePeriod: true} ).statsPeriod; const commonQuery = { environment, project: projects[0], interval: getDiffInMinutes(datetime) > 24 * 60 ? '1d' : '1h', query, field, }; // Unfortunately we can't do something like statsPeriod=28d&interval=14d to get scores for this and previous interval with the single request // https://github.com/getsentry/sentry/pull/22770#issuecomment-758595553 const currentQuery = useApiQuery( [ `/organizations/${organization.slug}/sessions/`, { query: { ...commonQuery, ...normalizeDateTimeParams(datetime), }, }, ], {staleTime: 0, enabled: isEnabled} ); const isPreviousPeriodEnabled = shouldFetchPreviousPeriod({ start: datetime.start, end: datetime.end, period: datetime.period, }); const previousQuery = useApiQuery( [ `/organizations/${organization.slug}/sessions/`, { query: { ...commonQuery, statsPeriodStart: doubledPeriod, statsPeriodEnd: period ?? DEFAULT_STATS_PERIOD, }, }, ], { staleTime: 0, enabled: isEnabled && isPreviousPeriodEnabled, } ); return { crashFreeRate: currentQuery.data, previousCrashFreeRate: previousQuery.data, isLoading: currentQuery.isLoading || (previousQuery.isLoading && isPreviousPeriodEnabled), error: currentQuery.error || previousQuery.error, refetch: () => { currentQuery.refetch(); previousQuery.refetch(); }, }; }; // shouldRenderBadRequests = true; function ProjectStabilityScoreCard(props: Props) { const {hasSessions} = props; const organization = useOrganization(); const cardTitle = props.field === SessionFieldWithOperation.CRASH_FREE_RATE_SESSIONS ? t('Crash Free Sessions') : t('Crash Free Users'); const cardHelp = getSessionTermDescription( props.field === SessionFieldWithOperation.CRASH_FREE_RATE_SESSIONS ? SessionTerm.CRASH_FREE_SESSIONS : SessionTerm.CRASH_FREE_USERS, null ); const {crashFreeRate, previousCrashFreeRate, isLoading, error, refetch} = useCrashFreeRate(props); const score = !crashFreeRate ? undefined : crashFreeRate?.groups[0]?.totals[props.field] * 100; const previousScore = !previousCrashFreeRate ? undefined : previousCrashFreeRate?.groups[0]?.totals[props.field] * 100; const trend = defined(score) && defined(previousScore) ? round(score - previousScore, 3) : undefined; const shouldRenderTrend = !isLoading && defined(score) && defined(trend); if (hasSessions === false) { return ( } /> ); } if (error) { return ( ); } return ( {trend >= 0 ? ( ) : ( )} {`${formatAbbreviatedNumber(Math.abs(trend))}\u0025`} ) : null } trendStatus={!trend ? undefined : trend > 0 ? 'good' : 'bad'} /> ); } export default ProjectStabilityScoreCard;