From 974d33b3dea55118b52c5ea032bbb9411e319dbe Mon Sep 17 00:00:00 2001 From: mannilakash Date: Fri, 13 Feb 2026 10:32:52 -0500 Subject: [PATCH 1/3] enhancement: add average and max response time for histogram --- .../common/charts/HistogramResponseTime.tsx | 209 ++++++++++++------ client/src/locales/en.json | 4 + 2 files changed, 148 insertions(+), 65 deletions(-) diff --git a/client/src/Components/v2/common/charts/HistogramResponseTime.tsx b/client/src/Components/v2/common/charts/HistogramResponseTime.tsx index e3b82cd50..c3c68643f 100644 --- a/client/src/Components/v2/common/charts/HistogramResponseTime.tsx +++ b/client/src/Components/v2/common/charts/HistogramResponseTime.tsx @@ -1,102 +1,181 @@ import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; import { useTheme } from "@mui/material/styles"; import type { ResponsiveStyleValue } from "@mui/system"; import type { CheckSnapshot } from "@/Types/Check"; import { HeatmapResponseTimeTooltip } from "./HeatmapResponseTimeTooltip"; +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; interface HistogramResponseTimeProps { checks: CheckSnapshot[]; height?: ResponsiveStyleValue; gap?: ResponsiveStyleValue; + showStats?: boolean; + statsPosition?: "left" | "right"; +} + +interface ResponseTimeStats { + max: number; + avg: number; } const DEFAULT_HEIGHT = 50; +const calculateResponseTimeStats = (checks: CheckSnapshot[]): ResponseTimeStats => { + if (!Array.isArray(checks) || checks.length === 0) { + return { max: 0, avg: 0 }; + } + + const validChecks = checks.filter( + (check) => + (check as any).status !== "placeholder" && + (check.originalResponseTime != null || check.responseTime != null) + ); + + if (validChecks.length === 0) { + return { max: 0, avg: 0 }; + } + + const responseTimes = validChecks.map( + (check) => check.originalResponseTime ?? check.responseTime ?? 0 + ); + + const max = Math.max(...responseTimes); + const avg = Math.round( + responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length + ); + + return { max, avg }; +}; + export const HistogramResponseTime = ({ checks, height = DEFAULT_HEIGHT, gap, + showStats = true, + statsPosition = "right", }: HistogramResponseTimeProps) => { const theme = useTheme(); + const { t } = useTranslation(); + + const stats = useMemo(() => calculateResponseTimeStats(checks), [checks]); if (!Array.isArray(checks) || checks.length === 0) return null; - let data = Array(); + const data = + checks.length !== 25 + ? [...checks, ...Array(25 - checks.length).fill({ status: "placeholder" })] + : checks; - if (!checks || checks.length === 0) { - data = []; - } - if (checks.length !== 25) { - const placeholders = Array(25 - checks.length).fill({ - status: "placeholder", - }); - data = [...checks, ...placeholders]; - } else { - data = checks; - } const chartHeight = typeof height === "number" ? `${height}px` : height; const gridGap = gap ?? theme.spacing(0.5); - return ( - - 0 || stats.avg > 0) && ( + + - {data.map((check, index) => { - const isPlaceholder = (check as any).status === "placeholder"; - const heightPct = `${Math.max(0, Math.min(100, (check as any).responseTime ?? 0))}%`; - const barColor = - check.status === true - ? theme.palette.success.light - : theme.palette.error.light; - const bar = ( - + {t("common.charts.histogram.avg", { value: stats.avg })} + + + {t("common.charts.histogram.max", { value: stats.max })} + + + ); + + return ( + + {statsPosition === "left" && statsContent} + + + {data.map((check, index) => { + const isPlaceholder = (check as any).status === "placeholder"; + const heightPct = `${Math.max(0, Math.min(100, (check as any).responseTime ?? 0))}%`; + const barColor = + check.status === true + ? theme.palette.success.light + : theme.palette.error.light; + const bar = ( - - ); + > + + + ); - return isPlaceholder ? ( - - {bar} - - ) : ( - - {bar} - - ); - })} + return isPlaceholder ? ( + + {bar} + + ) : ( + + {bar} + + ); + })} + - + {statsPosition === "right" && statsContent} + ); }; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index d2a066c11..3b58827ca 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -231,6 +231,10 @@ "high": "High", "low": "Low", "uptime": "Uptime" + }, + "histogram": { + "avg": "Avg: {{value}} ms", + "max": "Max: {{value}} ms" } }, "dialogs": { From 39a449e5948dc65ce0d29216433499c4e10aaf0a Mon Sep 17 00:00:00 2001 From: mannilakash Date: Fri, 13 Feb 2026 11:14:25 -0500 Subject: [PATCH 2/3] fix: padding fix for stats text --- .../Components/v2/common/charts/HistogramResponseTime.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/Components/v2/common/charts/HistogramResponseTime.tsx b/client/src/Components/v2/common/charts/HistogramResponseTime.tsx index c3c68643f..75ae25001 100644 --- a/client/src/Components/v2/common/charts/HistogramResponseTime.tsx +++ b/client/src/Components/v2/common/charts/HistogramResponseTime.tsx @@ -78,8 +78,8 @@ export const HistogramResponseTime = ({ alignItems={statsPosition === "left" ? "flex-end" : "flex-start"} sx={{ minWidth: 70, - pr: statsPosition === "left" ? theme.spacing(2) : 0, - pl: statsPosition === "right" ? theme.spacing(2) : 0, + pr: statsPosition === "left" ? theme.spacing(8) : 0, + pl: statsPosition === "right" ? theme.spacing(8) : 0, }} > {t("common.charts.histogram.avg", { value: stats.avg })} @@ -99,7 +99,7 @@ export const HistogramResponseTime = ({ color: theme.palette.text.secondary, fontSize: "0.65rem", fontWeight: 500, - lineHeight: 1.4, + pt: theme.spacing(2), }} > {t("common.charts.histogram.max", { value: stats.max })} From 7d66bb5668688a8702c44719c79af02737bc8985 Mon Sep 17 00:00:00 2001 From: mannilakash Date: Tue, 17 Feb 2026 10:27:22 -0500 Subject: [PATCH 3/3] fix: unnecessary custom styling and originalResponseTime usage --- .../common/charts/HistogramResponseTime.tsx | 74 ++++++------------- 1 file changed, 24 insertions(+), 50 deletions(-) diff --git a/client/src/Components/v2/common/charts/HistogramResponseTime.tsx b/client/src/Components/v2/common/charts/HistogramResponseTime.tsx index 75ae25001..1ef400daf 100644 --- a/client/src/Components/v2/common/charts/HistogramResponseTime.tsx +++ b/client/src/Components/v2/common/charts/HistogramResponseTime.tsx @@ -17,31 +17,23 @@ interface HistogramResponseTimeProps { } interface ResponseTimeStats { - max: number; - avg: number; + max: number | string; + avg: number | string; } const DEFAULT_HEIGHT = 50; const calculateResponseTimeStats = (checks: CheckSnapshot[]): ResponseTimeStats => { if (!Array.isArray(checks) || checks.length === 0) { - return { max: 0, avg: 0 }; + return { max: "-", avg: "-" }; } - const validChecks = checks.filter( - (check) => - (check as any).status !== "placeholder" && - (check.originalResponseTime != null || check.responseTime != null) - ); - + const validChecks = checks.filter((check) => check.originalResponseTime != null); if (validChecks.length === 0) { - return { max: 0, avg: 0 }; + return { max: "-", avg: "-" }; } - const responseTimes = validChecks.map( - (check) => check.originalResponseTime ?? check.responseTime ?? 0 - ); - + const responseTimes = validChecks.map((check) => check.originalResponseTime); const max = Math.max(...responseTimes); const avg = Math.round( responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length @@ -72,48 +64,30 @@ export const HistogramResponseTime = ({ const chartHeight = typeof height === "number" ? `${height}px` : height; const gridGap = gap ?? theme.spacing(0.5); - const statsContent = showStats && (stats.max > 0 || stats.avg > 0) && ( - - - {t("common.charts.histogram.avg", { value: stats.avg })} - - - {t("common.charts.histogram.max", { value: stats.max })} - - - ); + + {t("common.charts.histogram.avg", { value: stats.avg })} + + + {t("common.charts.histogram.max", { value: stats.max })} + + + ); return ( - {statsPosition === "left" && statsContent} - {statsPosition === "right" && statsContent} + {statsContent} ); };