import type { FC, ReactNode } from 'react'; import { useMemo } from 'react'; import { Alert, Box, Typography } from '@mui/material'; import { LineChart, NotEnoughData, } from '../insights/components/LineChart/LineChart.tsx'; import { useImpactMetricsData } from 'hooks/api/getters/useImpactMetricsData/useImpactMetricsData'; import { usePlaceholderData } from '../insights/hooks/usePlaceholderData.js'; import { getDisplayFormat, getTimeUnit, formatLargeNumbers } from './utils.ts'; import { fromUnixTime } from 'date-fns'; import { useChartData } from './hooks/useChartData.ts'; import type { AggregationMode } from './types.ts'; type ImpactMetricsChartProps = { metricName: string; timeRange: 'hour' | 'day' | 'week' | 'month'; labelSelectors: Record; yAxisMin: 'auto' | 'zero'; aggregationMode?: AggregationMode; aspectRatio?: number; overrideOptions?: Record; errorTitle?: string; emptyDataDescription?: string; noSeriesPlaceholder?: ReactNode; isPreview?: boolean; }; export const ImpactMetricsChart: FC = ({ metricName, timeRange, labelSelectors, yAxisMin, aggregationMode, aspectRatio, overrideOptions = {}, errorTitle = 'Failed to load impact metrics.', emptyDataDescription = 'Send impact metrics using Unleash SDK and select data series to view the chart.', noSeriesPlaceholder, isPreview, }) => { const { data: { start, end, series: timeSeriesData, debug }, loading: dataLoading, error: dataError, } = useImpactMetricsData( metricName ? { series: metricName, range: timeRange, aggregationMode, labels: Object.keys(labelSelectors).length > 0 ? labelSelectors : undefined, } : undefined, ); const placeholderData = usePlaceholderData({ fill: true, type: 'constant', }); const data = useChartData(timeSeriesData, debug?.query); const hasError = !!dataError; const isLoading = dataLoading; const shouldShowPlaceholder = !metricName || isLoading || hasError; const notEnoughData = useMemo( () => !isLoading && (!timeSeriesData || timeSeriesData.length === 0 || !data.datasets.some((d) => d.data.length > 1)), [data, isLoading, timeSeriesData], ); const minTime = start ? fromUnixTime(Number.parseInt(start, 10)) : undefined; const maxTime = end ? fromUnixTime(Number.parseInt(end, 10)) : undefined; const placeholder = metricName ? ( ) : noSeriesPlaceholder ? ( noSeriesPlaceholder ) : ( ); const hasManyLabels = Object.keys(labelSelectors).length > 0; const cover = notEnoughData ? placeholder : isLoading; const chartOptions = shouldShowPlaceholder ? overrideOptions : { ...overrideOptions, scales: { x: { type: 'time', min: minTime?.getTime(), max: maxTime?.getTime(), time: { unit: getTimeUnit(timeRange), displayFormats: { [getTimeUnit(timeRange)]: getDisplayFormat(timeRange), }, tooltipFormat: 'PPpp', }, ticks: { maxRotation: 45, minRotation: 45, maxTicksLimit: 8, }, }, y: { beginAtZero: yAxisMin === 'zero', title: { display: aggregationMode === 'rps', text: aggregationMode === 'rps' ? 'Rate per second' : '', }, ticks: { precision: 0, callback: (value: unknown): string | number => typeof value === 'number' ? `${formatLargeNumbers(value)}${aggregationMode === 'rps' ? '/s' : ''}` : (value as number), }, }, }, plugins: { legend: { display: timeSeriesData && (hasManyLabels || timeSeriesData.length > 1), position: 'bottom' as const, labels: { usePointStyle: true, boxWidth: 8, padding: 12, }, }, }, animations: { x: { duration: 0 }, y: { duration: 0 }, }, }; return ( <> div': { height: '100% !important', width: '100% !important', }, } : {} } > {errorTitle} ) : ( cover ) } /> {isPreview && debug?.query ? ( ({ margin: theme.spacing(2), padding: theme.spacing(2), background: theme.palette.background.elevation1, })} > {debug.query} ) : null} {isPreview && debug?.isTruncated ? ( ({ padding: theme.spacing(0, 2), })} > Showing only {timeSeriesData.length} series due to performance. Please change filters for more accurate results. ) : null} ); };