import {useTheme} from '@emotion/react'; import type {LineSeriesOption} from 'echarts'; import type {AreaChartProps} from 'sentry/components/charts/areaChart'; import { transformComparisonTimeseriesData, transformTimeseriesData, } from 'sentry/components/charts/eventsRequest'; import LineSeries from 'sentry/components/charts/series/lineSeries'; import type {Series} from 'sentry/types/echarts'; import type {EventsStats, SessionApiResponse} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import type {UseApiQueryOptions, UseApiQueryResult} from 'sentry/utils/queryClient'; import type RequestError from 'sentry/utils/requestError/requestError'; import {MINUTES_THRESHOLD_TO_DISPLAY_SECONDS} from 'sentry/utils/sessions'; import {capitalize} from 'sentry/utils/string/capitalize'; import {COMPARISON_DELTA_OPTIONS} from 'sentry/views/alerts/rules/metric/constants'; import type {TimePeriodType} from 'sentry/views/alerts/rules/metric/details/constants'; import { getMetricChartTooltipFormatter, getRuleChangeSeries, } from 'sentry/views/alerts/rules/metric/details/metricChart'; import { getMetricAlertChartOption, transformSessionResponseToSeries, } from 'sentry/views/alerts/rules/metric/details/metricChartOption'; import {getPeriodInterval} from 'sentry/views/alerts/rules/metric/details/utils'; import type {MetricRule} from 'sentry/views/alerts/rules/metric/types'; import {isCrashFreeAlert} from 'sentry/views/alerts/rules/metric/utils/isCrashFreeAlert'; import type {Anomaly, Incident} from 'sentry/views/alerts/types'; import {useMetricEventStats} from 'sentry/views/issueDetails/metricIssues/useMetricEventStats'; import {useMetricSessionStats} from 'sentry/views/issueDetails/metricIssues/useMetricSessionStats'; interface MetricStatsParams { project: Project; referrer: string; rule: MetricRule; timePeriod: TimePeriodType; anomalies?: Anomaly[]; incidents?: Incident[]; } type MetricStatsResponse = SessionApiResponse | EventsStats | undefined; interface MetricStatsResult { chartProps: Partial; data: UseApiQueryResult['data']; resultType: 'sessions' | 'events'; queryResult?: Partial>; } /** * Helper hook to coerce any rule into returning a series data response, whether it is a session or event rule. * Returns a similar response to a useApiQuery hook, */ export function useMetricStatsChart( { project, rule, timePeriod, referrer, anomalies = [], incidents = [], }: MetricStatsParams, options: Partial> = {} ): MetricStatsResult { const theme = useTheme(); const shouldUseSessionsStats = isCrashFreeAlert(rule.dataset); const interval = getPeriodInterval(timePeriod, rule); const {data: sessionStats, ...sessionStatsResults} = useMetricSessionStats( {project, rule, timePeriod}, { enabled: shouldUseSessionsStats, ...(options as Partial>), } ); const {data: eventStats, ...eventStatsResults} = useMetricEventStats( {project, rule, timePeriod, referrer}, { enabled: !shouldUseSessionsStats, ...(options as Partial>), } ); let stats: Series[] = []; if (shouldUseSessionsStats && sessionStats) { stats = transformSessionResponseToSeries(sessionStats, rule); } else if (eventStats) { stats = transformTimeseriesData(eventStats.data, eventStats?.meta, rule.aggregate); } let comparisonData: Series[] = []; if (rule.comparisonDelta) { comparisonData = transformComparisonTimeseriesData(eventStats?.data ?? []); } let chartProps: Partial = {}; if (stats.length > 0) { const {chartOption} = getMetricAlertChartOption({ timeseriesData: stats, rule, anomalies, incidents, seriesName: rule.aggregate, }); chartProps = {...chartOption}; } const comparisonSeriesName = capitalize( COMPARISON_DELTA_OPTIONS.find(({value}) => value === rule.comparisonDelta)?.label || '' ); const additionalSeries: LineSeriesOption[] = [ ...(comparisonData || []).map(({data: _data, ...otherSeriesProps}) => LineSeries({ name: comparisonSeriesName, data: _data.map(({name, value}) => [name, value]), lineStyle: {color: theme.gray200, type: 'dashed', width: 1}, itemStyle: {color: theme.gray200}, animation: false, animationThreshold: 1, animationDuration: 0, ...otherSeriesProps, }) ), ...getRuleChangeSeries(rule, stats, theme), ]; return { chartProps: { minutesThresholdToDisplaySeconds: shouldUseSessionsStats ? MINUTES_THRESHOLD_TO_DISPLAY_SECONDS : undefined, additionalSeries, tooltip: getMetricChartTooltipFormatter({ formattedAggregate: rule.aggregate, rule, interval, comparisonSeriesName, theme, }), ...chartProps, }, resultType: shouldUseSessionsStats ? 'sessions' : 'events', data: shouldUseSessionsStats ? sessionStats : eventStats, queryResult: shouldUseSessionsStats ? sessionStatsResults : eventStatsResults, }; }