import {useMemo} from 'react';
import {type Theme, useTheme} from '@emotion/react';
import {
LineChart,
LineChartProps,
LineChartSeries,
} from 'sentry/components/charts/lineChart';
import {t} from 'sentry/locale';
import {tooltipFormatter} from 'sentry/utils/discover/charts';
import {makeFormatter} from 'sentry/utils/profiling/units/units';
const durationFormatter = makeFormatter('nanoseconds', 0);
function asSeries(
seriesName: string,
color: string | undefined,
data: {timestamp: number; value: number}[]
) {
return {
data: data.map(p => ({
name: p.timestamp * 1e3,
value: p.value / 1e6 ?? 0,
})),
color,
seriesName,
};
}
function getTooltipFormatter(label: string, baseline: number) {
return [
'
',
'',
].join('');
}
interface BaseSparklineChartProps {
name: string;
points: {timestamp: number; value: number}[];
chartProps?: Partial;
color?: string;
}
interface ProfilingSparklineChartPropsWithBreakpoint extends BaseSparklineChartProps {
aggregate_range_1: number;
aggregate_range_2: number;
breakpoint: number;
end: number;
start: number;
}
interface ProfilingSparklineChartPropsWithoutBreakpoint extends BaseSparklineChartProps {}
function isBreakPointProps(
props: ProfilingSparklineChartProps
): props is ProfilingSparklineChartPropsWithBreakpoint {
return typeof (props as any).breakpoint === 'number';
}
type ProfilingSparklineChartProps =
| ProfilingSparklineChartPropsWithBreakpoint
| ProfilingSparklineChartPropsWithoutBreakpoint;
function makeSeriesBeforeAfterLines(
start: number,
breakpoint: number,
end: number,
aggregate_range_1: number,
aggregate_range_2: number,
theme: Theme
): LineChartSeries[] {
const dividingLine = {
data: [],
color: theme.textColor,
seriesName: 'dividing line',
markLine: {},
};
dividingLine.markLine = {
data: [{xAxis: breakpoint * 1e3}],
label: {show: false},
lineStyle: {
color: theme.textColor,
type: 'solid',
width: 2,
},
symbol: ['none', 'none'],
tooltip: {
show: false,
},
silent: true,
};
const beforeLine = {
data: [],
color: theme.textColor,
seriesName: 'before line',
markLine: {},
};
beforeLine.markLine = {
data: [
[
{value: 'Past', coord: [start * 1e3, aggregate_range_1 / 1e6]},
{coord: [breakpoint * 1e3, aggregate_range_1 / 1e6]},
],
],
label: {
show: false,
},
lineStyle: {
color: theme.textColor,
type: 'dashed',
width: 1,
},
symbol: ['none', 'none'],
tooltip: {
formatter: getTooltipFormatter(t('Past Baseline'), aggregate_range_1),
},
};
const afterLine = {
data: [],
color: theme.textColor,
seriesName: 'after line',
markLine: {},
};
afterLine.markLine = {
data: [
[
{
value: 'Present',
coord: [breakpoint * 1e3, aggregate_range_2 / 1e6],
},
{coord: [end * 1e3, aggregate_range_2 / 1e6]},
],
],
label: {
show: false,
},
lineStyle: {
color: theme.textColor,
type: 'dashed',
width: 1,
},
symbol: ['none', 'none'],
tooltip: {
formatter: getTooltipFormatter(t('Present Baseline'), aggregate_range_2),
},
};
return [dividingLine, beforeLine, afterLine];
}
export function ProfilingSparklineChart(props: ProfilingSparklineChartProps) {
const theme = useTheme();
const chartProps: LineChartProps = useMemo(() => {
const additionalSeries: LineChartSeries[] = [];
if (isBreakPointProps(props)) {
additionalSeries.push(
...makeSeriesBeforeAfterLines(
props.start,
props.breakpoint,
props.end,
props.aggregate_range_1,
props.aggregate_range_2,
theme
)
);
}
const baseProps: LineChartProps = {
height: 26,
width: 'auto',
series: [asSeries(props.name, props.color, props.points), ...additionalSeries],
grid: [
{
containLabel: false,
top: '2px',
left: '2px',
right: '2px',
bottom: '2px',
},
{
containLabel: false,
top: '2px',
left: '2px',
right: '2px',
bottom: '2px',
},
],
tooltip: {
valueFormatter: (v: number) => durationFormatter(v),
},
axisPointer: {},
xAxes: [
{
gridIndex: 0,
type: 'time' as const,
show: false,
},
],
yAxes: [
{
scale: true,
show: false,
},
],
...(props.chartProps ? props.chartProps : {}),
};
return baseProps;
}, [props, theme]);
return ;
}