diff --git a/client/src/Components/monitors/charts/HistogramDetails.tsx b/client/src/Components/monitors/charts/HistogramDetails.tsx index 5c3970d3c..0736abd6c 100644 --- a/client/src/Components/monitors/charts/HistogramDetails.tsx +++ b/client/src/Components/monitors/charts/HistogramDetails.tsx @@ -122,7 +122,10 @@ export const HistogramDetails = ({ const theme = useTheme(); const uiTimezone = useSelector((state: RootState) => state.ui.timezone); const yMax = useMemo(() => { - const totals = checks.map((check) => check.avgResponseTime || 0); + const totals = checks.map((check) => { + const phaseSum = PHASE_KEYS.reduce((sum, key) => sum + (check[key] ?? 0), 0); + return Math.max(check.avgResponseTime ?? 0, phaseSum); + }); return computeYAxisCap(totals); }, [checks]); diff --git a/client/src/Components/monitors/charts/HistogramDetailsStacked.tsx b/client/src/Components/monitors/charts/HistogramDetailsStacked.tsx new file mode 100644 index 000000000..2ef74f56b --- /dev/null +++ b/client/src/Components/monitors/charts/HistogramDetailsStacked.tsx @@ -0,0 +1,207 @@ +import type { GroupedUptimeCheck } from "@/Types/Check"; +import type { TooltipProps } from "recharts"; +import type { NameType, ValueType } from "recharts/types/component/DefaultTooltipContent"; +import type { Theme } from "@mui/material/styles"; +import type { RootState } from "@/Types/state"; +import { Clock } from "lucide-react"; +import prettyMilliseconds from "pretty-ms"; + +import Stack from "@mui/material/Stack"; +import { + Area, + AreaChart, + CartesianGrid, + ResponsiveContainer, + YAxis, + XAxis, + Tooltip, +} from "recharts"; +import { BaseBox, BaseChart, Dot } from "@/Components/design-elements"; +import Typography from "@mui/material/Typography"; +import { XTick } from "@/Components/monitors/charts/XTick"; + +import { formatDateWithTz, tooltipDateFormatLookup } from "@/Utils/TimeUtils"; +import { useSelector } from "react-redux"; +import { useMemo } from "react"; +import { useTheme } from "@mui/material"; +import { SPACING } from "@/Utils/Theme/constants"; +import { computeYAxisCap } from "@/Components/monitors/charts/ChartUtils"; + +export const PHASE_KEYS = [ + "avgDns", + "avgTcp", + "avgTls", + "avgRequest", + "avgFirstByte", + "avgDownload", +] as const; + +const PHASE_COLOR_KEYS = { + avgDns: "dns", + avgTcp: "tcp", + avgTls: "tls", + avgRequest: "request", + avgFirstByte: "firstByte", + avgDownload: "download", +} as const; + +const PHASE_LABELS: Record<(typeof PHASE_KEYS)[number], string> = { + avgDns: "DNS", + avgTcp: "TCP", + avgTls: "TLS", + avgRequest: "Request", + avgFirstByte: "First byte", + avgDownload: "Download", +}; + +type TimingPhaseToolTipProps = TooltipProps & { + range: string; + theme: Theme; + uiTimezone: string; +}; + +const TimingPhasesToolTip = ({ + active, + payload, + range, + theme, + uiTimezone, +}: TimingPhaseToolTipProps) => { + if (!active || !payload?.length) return null; + const bucket = payload[0].payload as GroupedUptimeCheck; + const total = PHASE_KEYS.reduce((sum, key) => sum + bucket[key], 0); + const format = tooltipDateFormatLookup(range); + return ( + + {formatDateWithTz(bucket.bucketDate, format, uiTimezone)} + Total: {prettyMilliseconds(total)} + {PHASE_KEYS.map((key) => ( + + + + {PHASE_LABELS[key]}:{" "} + {prettyMilliseconds(bucket[key], { + formatSubMilliseconds: true, + compact: true, + })} + + + ))} + + ); +}; + +export const HistogramDetailsStacked = ({ + checks = [], + range, +}: { + checks: GroupedUptimeCheck[]; + range: string; +}) => { + const theme = useTheme(); + const uiTimezone = useSelector((state: RootState) => state.ui.timezone); + + // Cap the y domain at 2x the p95 of bucket totals, so typical stacks sit + // around mid-height with headroom above + const yMax = useMemo(() => { + const totals = checks.map((check) => + PHASE_KEYS.reduce((sum, key) => sum + check[key], 0) + ); + return computeYAxisCap(totals); + }, [checks]); + + return ( + + } + title="Timing phases" + > + + + + + {PHASE_KEYS.map((key) => ( + + + + + ))} + + + ( + + )} + /> + ( + + )} + /> + {PHASE_KEYS.map((key) => ( + + ))} + + + + ); +};