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

210 lines
5.5 KiB
TypeScript

import type {Theme} from '@emotion/react';
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';
import type {BarChartSeries} from 'sentry/components/charts/barChart';
import MiniBarChart from 'sentry/components/charts/miniBarChart';
import Count from 'sentry/components/count';
import * as SidebarSection from 'sentry/components/sidebarSection';
import {t} from 'sentry/locale';
import type {Group, Release, TimeseriesValue} from 'sentry/types';
import {getFormattedDate} from 'sentry/utils/dates';
import {formatVersion} from 'sentry/utils/formatters';
/**
* Stats are provided indexed by statsPeriod strings.
*/
type StatsGroup = Record<string, TimeseriesValue[]>;
interface Props {
group: Group;
statsPeriod: string;
title: string;
className?: string;
environment?: string;
environmentLabel?: string;
environmentStats?: StatsGroup;
firstSeen?: string;
lastSeen?: string;
release?: Release;
releaseStats?: StatsGroup;
}
type Marker = {
color: string;
displayValue: string | number | Date;
name: string;
value: string | number | Date;
};
export function getGroupReleaseChartMarkers(
theme: Theme,
stats: TimeseriesValue[],
firstSeen?: string,
lastSeen?: string
): BarChartSeries['markPoint'] {
const markers: Marker[] = [];
// Get the timestamp of the first point.
const firstGraphTime = stats[0][0] * 1000;
const firstSeenX = new Date(firstSeen ?? 0).getTime();
const lastSeenX = new Date(lastSeen ?? 0).getTime();
const difference = lastSeenX - firstSeenX;
const oneHourMs = 1000 * 60 * 60;
if (
firstSeen &&
stats.length > 2 &&
firstSeenX >= firstGraphTime &&
// Don't show first seen if the markers are too close together
difference > oneHourMs
) {
// Find the first bucket that would contain our first seen event
const firstBucket = stats.findIndex(([time]) => time * 1000 > firstSeenX);
let bucketStart: number | undefined;
if (firstBucket > 0) {
// The size of the data interval in ms
const halfBucketSize = ((stats[1][0] - stats[0][0]) * 1000) / 2;
// Display the marker in front of the first bucket
bucketStart = stats[firstBucket - 1][0] * 1000 - halfBucketSize;
}
markers.push({
name: t('First seen'),
value: bucketStart ?? firstSeenX,
displayValue: firstSeenX,
color: theme.pink300,
});
}
if (lastSeen && lastSeenX >= firstGraphTime) {
markers.push({
name: t('Last seen'),
value: lastSeenX,
displayValue: lastSeenX,
color: theme.green300,
});
}
const markerTooltip = {
show: true,
trigger: 'item',
formatter: ({data}) => {
const time = getFormattedDate(data.displayValue, 'MMM D, YYYY LT', {
local: true,
});
return [
'<div class="tooltip-series">',
`<div><span class="tooltip-label"><strong>${data.name}</strong></span></div>`,
'</div>',
`<div class="tooltip-footer">${time}</div>`,
'</div>',
'<div class="tooltip-arrow"></div>',
].join('');
},
};
return {
data: markers.map(marker => ({
name: marker.name,
coord: [marker.value, 0],
tooltip: markerTooltip,
displayValue: marker.displayValue,
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: marker.color,
borderColor: theme.background,
},
})),
};
}
function GroupReleaseChart(props: Props) {
const {
group,
lastSeen,
firstSeen,
statsPeriod,
release,
releaseStats,
environment,
environmentLabel,
environmentStats,
title,
} = props;
const theme = useTheme();
const stats = group.stats[statsPeriod];
const environmentPeriodStats = environmentStats?.[statsPeriod];
if (!stats || !stats.length || !environmentPeriodStats) {
return null;
}
const series: BarChartSeries[] = [];
if (environment) {
// Add all events.
series.push({
seriesName: t('Events'),
data: stats.map(point => ({name: point[0] * 1000, value: point[1]})),
});
}
series.push({
seriesName: t('Events in %s', environmentLabel),
data: environmentStats[statsPeriod].map(point => ({
name: point[0] * 1000,
value: point[1],
})),
});
if (release && releaseStats) {
series.push({
seriesName: t('Events in release %s', formatVersion(release.version)),
data: releaseStats[statsPeriod].map(point => ({
name: point[0] * 1000,
value: point[1],
})),
});
}
const totalSeries =
environment && environmentStats ? environmentStats[statsPeriod] : stats;
const totalEvents = totalSeries.reduce((acc, current) => acc + current[1], 0);
series[0].markPoint = getGroupReleaseChartMarkers(theme, stats, firstSeen, lastSeen);
return (
<SidebarSection.Wrap>
<SidebarSection.Title>{title}</SidebarSection.Title>
<SidebarSection.Content>
<EventNumber>
<Count value={totalEvents} />
</EventNumber>
<MiniBarChart
isGroupedByDate
showTimeInTooltip
showMarkLineLabel
height={42}
colors={environment ? undefined : [theme.purple300, theme.purple300]}
series={series}
grid={{
top: 6,
bottom: 4,
left: 4,
right: 4,
}}
/>
</SidebarSection.Content>
</SidebarSection.Wrap>
);
}
const EventNumber = styled('div')`
line-height: 1;
font-size: ${p => p.theme.fontSizeExtraLarge};
`;
export default GroupReleaseChart;