import {Fragment, useEffect, useState} from 'react'; import {useTheme} from '@emotion/react'; import type {LineSeriesOption} from 'echarts'; import LineSeries from 'sentry/components/charts/series/lineSeries'; import {shouldFetchPreviousPeriod} from 'sentry/components/charts/utils'; import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse'; import {t} from 'sentry/locale'; import type {SessionApiResponse} from 'sentry/types'; import type {Series} from 'sentry/types/echarts'; import {defined} from 'sentry/utils'; import {getPeriod} from 'sentry/utils/getPeriod'; import {filterSessionsInTimeWindow, getSessionsInterval} from 'sentry/utils/sessions'; import useApiRequests from 'sentry/utils/useApiRequests'; import {DisplayModes} from '../projectCharts'; import type {ProjectSessionsChartRequestProps} from './projectSessionsChartRequest'; const BAD_BEHAVIOUR_THRESHOLD = 0.47; type State = { sessionsData: SessionApiResponse; }; function ProjectSessionsAnrRequest({ children, organization, disablePrevious, selection, displayMode, query, onTotalValuesChange, }: Omit) { const {datetime, projects, environments: environment} = selection; const theme = useTheme(); const yAxis = displayMode === DisplayModes.ANR_RATE ? 'anr_rate()' : 'foreground_anr_rate()'; const [timeseriesData, setTimeseriesData] = useState(null); const [badBehaviourSeries, setBadBehaviourSeries] = useState( null ); const [previousTimeseriesData, setPreviousTimeseriesData] = useState( null ); const [totalSessions, setTotalSessions] = useState(null); const shouldFetchWithPrevious = !disablePrevious && shouldFetchPreviousPeriod({ start: datetime.start, end: datetime.end, period: datetime.period, }); function getParams(): Record { const baseParams = { field: [yAxis, 'count_unique(user)'], interval: getSessionsInterval(datetime, { highFidelity: organization.features.includes('minute-resolution-sessions'), dailyInterval: true, }), project: projects[0], environment, query, }; if (!shouldFetchWithPrevious) { return { ...baseParams, ...normalizeDateTimeParams(datetime), }; } const {period} = selection.datetime; const doubledPeriod = getPeriod( {period, start: undefined, end: undefined}, {shouldDoublePeriod: true} ).statsPeriod; return { ...baseParams, statsPeriod: doubledPeriod, }; } const queryParams = getParams(); const {data, isReloading, hasError} = useApiRequests({ endpoints: [ [ 'sessionsData', `/organizations/${organization.slug}/sessions/`, {query: queryParams}, ], ], }); useEffect(() => { if (defined(data.sessionsData)) { const filteredResponse = filterSessionsInTimeWindow( data.sessionsData, queryParams.start, queryParams.end ); const dataMiddleIndex = Math.floor(filteredResponse.intervals.length / 2); const totalUsers = filteredResponse.groups.reduce( (acc, group) => acc + group.series['count_unique(user)'] .slice(shouldFetchWithPrevious ? dataMiddleIndex : 0) .reduce((value, groupAcc) => groupAcc + value, 0), 0 ); setTotalSessions(totalUsers); onTotalValuesChange(totalUsers); const previousPeriodTotalUsers = filteredResponse ? filteredResponse.groups.reduce( (acc, group) => acc + group.series['count_unique(user)'] .slice(0, dataMiddleIndex) .reduce((value, groupAcc) => groupAcc + value, 0), 0 ) : 0; const timeseriesData_ = [ { seriesName: t('This Period'), data: filteredResponse.intervals .slice(shouldFetchWithPrevious ? dataMiddleIndex : 0) .map((interval, i) => { const anrRate = filteredResponse.groups.reduce( (acc, group) => acc + group.series[yAxis]?.slice( shouldFetchWithPrevious ? dataMiddleIndex : 0 )[i], 0 ); return { name: interval, value: totalUsers === 0 && previousPeriodTotalUsers === 0 ? 0 : anrRate === null ? null : anrRate * 100, }; }), }, ] as Series[]; const badBehaviourSeries_ = yAxis === 'foreground_anr_rate()' ? [ LineSeries({ name: t('Overall Bad Behaviour Threshold'), data: filteredResponse.intervals .slice(shouldFetchWithPrevious ? dataMiddleIndex : 0) .map(interval => [interval, BAD_BEHAVIOUR_THRESHOLD]), lineStyle: {color: theme.red200, width: 2, type: 'dotted'}, itemStyle: {color: theme.red200}, animation: false, stack: 'bad_behaviour_threshold', }), ] : null; const previousTimeseriesData_ = shouldFetchWithPrevious ? ({ seriesName: t('Previous Period'), data: filteredResponse.intervals .slice(0, dataMiddleIndex) .map((_interval, i) => { const previousAnrRate = filteredResponse.groups.reduce( (acc, group) => acc + group.series[yAxis]?.slice(0, dataMiddleIndex)[i], 0 ); return { name: filteredResponse.intervals[i + dataMiddleIndex], value: totalUsers === 0 && previousPeriodTotalUsers === 0 ? 0 : previousAnrRate === null ? null : previousAnrRate * 100, }; }), } as Series) // TODO(project-detail): Change SeriesDataUnit value to support null : null; setTimeseriesData(timeseriesData_); setPreviousTimeseriesData(previousTimeseriesData_); setBadBehaviourSeries(badBehaviourSeries_); } }, [ data.sessionsData, onTotalValuesChange, queryParams.end, queryParams.start, shouldFetchWithPrevious, theme.red200, yAxis, ]); return ( {children({ loading: timeseriesData === null, reloading: isReloading, errored: hasError, totalSessions, previousTimeseriesData, timeseriesData: timeseriesData ?? [], additionalSeries: badBehaviourSeries ?? undefined, })} ); } export default ProjectSessionsAnrRequest;