Files
2024-02-15 17:01:50 +00:00

231 lines
6.9 KiB
TypeScript

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<ProjectSessionsChartRequestProps, 'theme'>) {
const {datetime, projects, environments: environment} = selection;
const theme = useTheme();
const yAxis =
displayMode === DisplayModes.ANR_RATE ? 'anr_rate()' : 'foreground_anr_rate()';
const [timeseriesData, setTimeseriesData] = useState<Series[] | null>(null);
const [badBehaviourSeries, setBadBehaviourSeries] = useState<LineSeriesOption[] | null>(
null
);
const [previousTimeseriesData, setPreviousTimeseriesData] = useState<Series | null>(
null
);
const [totalSessions, setTotalSessions] = useState<number | null>(null);
const shouldFetchWithPrevious =
!disablePrevious &&
shouldFetchPreviousPeriod({
start: datetime.start,
end: datetime.end,
period: datetime.period,
});
function getParams(): Record<string, any> {
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<State>({
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 (
<Fragment>
{children({
loading: timeseriesData === null,
reloading: isReloading,
errored: hasError,
totalSessions,
previousTimeseriesData,
timeseriesData: timeseriesData ?? [],
additionalSeries: badBehaviourSeries ?? undefined,
})}
</Fragment>
);
}
export default ProjectSessionsAnrRequest;