123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- 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 type {Project} from 'sentry/types/project';
- import {defined} from 'sentry/utils';
- import {getPeriod} from 'sentry/utils/duration/getPeriod';
- import {formatAbbreviatedNumber} from 'sentry/utils/formatters';
- 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;
- project?: Project;
- 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<SessionApiResponse>(
- [
- `/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<SessionApiResponse>(
- [
- `/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 (
- <ScoreCard
- title={cardTitle}
- help={cardHelp}
- score={
- <MissingReleasesButtons
- organization={organization}
- health
- platform={props.project?.platform}
- />
- }
- />
- );
- }
- if (error) {
- return (
- <LoadingError
- message={
- (error.responseJSON?.detail as React.ReactNode) ||
- t('There was an error loading data.')
- }
- onRetry={refetch}
- />
- );
- }
- return (
- <ScoreCard
- title={cardTitle}
- help={cardHelp}
- score={isLoading || !defined(score) ? '\u2014' : displayCrashFreePercent(score)}
- trend={
- shouldRenderTrend ? (
- <div>
- {trend >= 0 ? (
- <IconArrow direction="up" size="xs" />
- ) : (
- <IconArrow direction="down" size="xs" />
- )}
- {`${formatAbbreviatedNumber(Math.abs(trend))}\u0025`}
- </div>
- ) : null
- }
- trendStatus={!trend ? undefined : trend > 0 ? 'good' : 'bad'}
- />
- );
- }
- export default ProjectStabilityScoreCard;
|