mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
136 lines
3.6 KiB
TypeScript
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>
|
|
);
|
|
}
|