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

136 lines
3.6 KiB
TypeScript

import type {ReactNode} from 'react';
import {useMemo} from 'react';
import {useTheme} from '@emotion/react';
import {AreaChart} from 'sentry/components/charts/areaChart';
import ChartZoom from 'sentry/components/charts/chartZoom';
import {t} from 'sentry/locale';
import type {PageFilters} from 'sentry/types';
import type {Series} from 'sentry/types/echarts';
import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts';
import {useProfileEventsStats} from 'sentry/utils/profiling/hooks/useProfileEventsStats';
import useRouter from 'sentry/utils/useRouter';
import {
ContentContainer,
HeaderContainer,
HeaderTitleLegend,
WidgetContainer,
} from './styles';
interface ProfilesChartWidgetProps {
chartHeight: number;
referrer: string;
header?: ReactNode;
selection?: PageFilters;
userQuery?: string;
widgetHeight?: string;
}
const SERIES_ORDER = ['p99()', 'p95()', 'p75()', 'p50()'] as const;
export function ProfilesChartWidget({
chartHeight,
header,
referrer,
selection,
userQuery,
widgetHeight,
}: ProfilesChartWidgetProps) {
const router = useRouter();
const theme = useTheme();
const profileStats = useProfileEventsStats({
dataset: 'profiles',
query: userQuery,
referrer,
yAxes: SERIES_ORDER,
});
const series: Series[] = useMemo(() => {
if (profileStats.status !== 'success') {
return [];
}
// the timestamps in the response is in seconds but echarts expects
// a timestamp in milliseconds, so multiply by 1e3 to do the conversion
const timestamps = profileStats.data.timestamps.map(ts => ts * 1e3);
return profileStats.data.data
.map(rawData => {
if (timestamps.length !== rawData.values.length) {
throw new Error('Invalid stats response');
}
return {
data: rawData.values.map((value, i) => ({
name: timestamps[i]!,
// the response value contains nulls when no data
// is available, use 0 to represent it
value: value ?? 0,
})),
seriesName: rawData.axis,
};
})
.sort((a, b) => {
const idxA = SERIES_ORDER.indexOf(a.seriesName as any);
const idxB = SERIES_ORDER.indexOf(b.seriesName as any);
return idxA - idxB;
});
}, [profileStats]);
const chartOptions = useMemo(() => {
return {
height: chartHeight,
grid: {
top: '16px',
left: '24px',
right: '24px',
bottom: '16px',
},
xAxis: {
type: 'time' as const,
},
yAxis: {
scale: true,
axisLabel: {
color: theme.chartLabel,
formatter(value: number) {
return axisLabelFormatter(value, 'duration');
},
},
},
tooltip: {
valueFormatter: value => tooltipFormatter(value, 'duration'),
},
legend: {
right: 16,
top: 0,
data: SERIES_ORDER.slice(),
},
};
}, [chartHeight, theme.chartLabel]);
return (
<WidgetContainer height={widgetHeight}>
<HeaderContainer>
{header ?? <HeaderTitleLegend>{t('Profiles by Percentiles')}</HeaderTitleLegend>}
</HeaderContainer>
<ContentContainer>
<ChartZoom router={router} {...selection?.datetime}>
{zoomRenderProps => (
<AreaChart
{...zoomRenderProps}
{...chartOptions}
series={series}
isGroupedByDate
showTimeInTooltip
/>
)}
</ChartZoom>
</ContentContainer>
</WidgetContainer>
);
}