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'; import type {Theme} from 'sentry/utils/theme'; /** * Stats are provided indexed by statsPeriod strings. */ type StatsGroup = Record; 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 [ '
', `
${data.name}
`, '
', `
${time}
`, '', '
', ].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 ( {title} ); } const EventNumber = styled('div')` line-height: 1; font-size: ${p => p.theme.fontSizeExtraLarge}; `; export default GroupReleaseChart;