From 22df52eb0dbdafbad2c3fa41269f7a3a3cbf044b Mon Sep 17 00:00:00 2001 From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com> Date: Fri, 21 Aug 2026 14:49:11 +0200 Subject: [PATCH] chore: bring global speed checker chart back (#2597) * chore: bring global speed checker chart back * fix: remove transfer from the list --- .../(landing)/play/checker/[slug]/chart.tsx | 206 +++++++++++ .../(landing)/play/checker/[slug]/client.tsx | 327 ++++++++++-------- .../(landing)/play/checker/[slug]/page.tsx | 4 +- .../src/content/pages/tools/checker-slug.mdx | 4 - 4 files changed, 384 insertions(+), 157 deletions(-) create mode 100644 apps/web/src/app/(landing)/play/checker/[slug]/chart.tsx diff --git a/apps/web/src/app/(landing)/play/checker/[slug]/chart.tsx b/apps/web/src/app/(landing)/play/checker/[slug]/chart.tsx new file mode 100644 index 000000000..005c7eec6 --- /dev/null +++ b/apps/web/src/app/(landing)/play/checker/[slug]/chart.tsx @@ -0,0 +1,206 @@ +"use client"; + +import { formatRegionCode, getRegionInfo } from "@openstatus/regions"; +import { Button } from "@openstatus/ui/components/ui/button"; +import { + type ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@openstatus/ui/components/ui/chart"; +import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; + +import { + type CachedRegionChecker, + getTimingPhases, +} from "../../../../../lib/checker/utils"; + +const chartConfig = { + dns: { label: "DNS", color: "var(--chart-1)" }, + connection: { label: "Connection", color: "var(--chart-2)" }, + tls: { label: "TLS", color: "var(--chart-3)" }, + ttfb: { label: "TTFB", color: "var(--chart-4)" }, +} satisfies ChartConfig; + +const PHASES = Object.keys(chartConfig) as (keyof typeof chartConfig)[]; + +interface ChartProps { + checks: CachedRegionChecker["checks"]; + desc: boolean; + onToggleSort: () => void; +} + +export function Chart({ checks, desc, onToggleSort }: ChartProps) { + // `transfer` is left out: the checker stops its latency clock when the + // response headers land, before the body is read. sorts on the stacked + // total rather than `check.latency` — the two disagree whenever a checker + // reports phases that don't add up to its round-trip + const chartData = checks + .map((check) => { + const { dns, connection, tls, ttfb } = getTimingPhases(check.timing); + return { + region: check.region, + total: dns + connection + tls + ttfb, + dns, + connection, + tls, + ttfb, + }; + }) + .sort((a, b) => (desc ? b.total - a.total : a.total - b.total)); + + return ( +
+
+
+ latency by region{" "} + + — {checks.length} regions + +
+ +
+
+
+ + + + } + /> + `${value}ms`} + /> + { + const region = getRegionInfo(String(label)); + return ( +
+ {region.location} + + {formatRegionCode(label)} + +
+ ); + }} + formatter={(value, name, item, index) => ( + <> +
+ {chartConfig[name as keyof typeof chartConfig]?.label || + name} + + {index === PHASES.length - 1 ? ( +
+ latency + +
+ ) : null} + + )} + /> + } + /> + {PHASES.map((phase) => ( + + ))} + + +
+
+
+ {PHASES.map((phase) => ( +
+
+ + {chartConfig[phase].label} + +
+ ))} +
+
+ ); +} + +function TooltipValue({ value }: { value: number }) { + return ( +
+ {Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(value)} + ms +
+ ); +} + +/** Region codes are rotated to fit ~30 regions on the axis. */ +function RegionTick({ + x, + y, + payload, +}: { + x?: number; + y?: number; + payload?: { value: string }; +}) { + if (!payload) return null; + const code = formatRegionCode(payload.value); + return ( + + + {code.length > 8 ? `${code.slice(0, 7)}…` : code} + + + ); +} diff --git a/apps/web/src/app/(landing)/play/checker/[slug]/client.tsx b/apps/web/src/app/(landing)/play/checker/[slug]/client.tsx index 6b979138b..44fe7b432 100644 --- a/apps/web/src/app/(landing)/play/checker/[slug]/client.tsx +++ b/apps/web/src/app/(landing)/play/checker/[slug]/client.tsx @@ -28,6 +28,10 @@ import { } from "../../../../../lib/checker/utils"; import { cn } from "../../../../../lib/utils"; import { handleExportCSV } from "../utils"; +import { Chart } from "./chart"; + +const views = ["table", "chart"] as const; +type View = (typeof views)[number]; const STATUS_CODES = { "1": "text-muted-foreground", @@ -37,16 +41,17 @@ const STATUS_CODES = { "5": "text-destructive", }; -interface TableProps { +interface ResultsProps { data: CachedRegionChecker; } -export function Table({ data }: TableProps) { +export function Results({ data }: ResultsProps) { + const [view, setView] = useState("table"); const [input, setInput] = useState(""); const [sort, setSort] = useState<{ value: "latency" | "status" | "region"; desc: boolean; - }>({ value: "latency", desc: false }); + }>({ value: "latency", desc: true }); // Filter successful checks and calculate timing phases const checks = data.checks @@ -59,160 +64,180 @@ export function Table({ data }: TableProps) { }; }); - const filteredAndSorted = checks - .filter((check) => { - const regionInfo = regionDict[check.region as Region]; - if (!regionInfo) return false; - return [ - regionInfo.code, - regionInfo.location, - regionInfo.flag, - regionInfo.continent, - regionInfo.provider, - ].some((value) => value?.toLowerCase().includes(input.toLowerCase())); - }) - .sort((a, b) => { - if (sort.value === "status") { - return sort.desc ? b.status - a.status : a.status - b.status; - } - if (sort.value === "latency") { - return sort.desc ? b.latency - a.latency : a.latency - b.latency; - } - return sort.desc - ? b.region.localeCompare(a.region) - : a.region.localeCompare(b.region); - }); + const sorted = checks.sort((a, b) => { + if (sort.value === "status") { + return sort.desc ? b.status - a.status : a.status - b.status; + } + if (sort.value === "latency") { + return sort.desc ? b.latency - a.latency : a.latency - b.latency; + } + return sort.desc + ? b.region.localeCompare(a.region) + : a.region.localeCompare(b.region); + }); + + const filteredAndSorted = sorted.filter((check) => { + const regionInfo = regionDict[check.region as Region]; + if (!regionInfo) return false; + return [ + regionInfo.code, + regionInfo.location, + regionInfo.flag, + regionInfo.continent, + regionInfo.provider, + ].some((value) => value?.toLowerCase().includes(input.toLowerCase())); + }); return ( -
-
- setInput(e.target.value)} - placeholder="Search by region, flag, location code, cloud provider or continent" - className="h-auto! flex-1 rounded-none p-4 text-base md:text-base" - /> - -
-
- - - - - - - - - - - - - - {filteredAndSorted.length === 0 ? ( + setView(value as View)}> + + {views.map((value) => ( + + {value} + + ))} + + +
+ setInput(e.target.value)} + placeholder="Search by region, flag, location code, cloud provider or continent" + className="h-auto! flex-1 rounded-none p-4 text-base md:text-base" + /> + +
+
+
- RegionStatusDNSConnectTLSTTFB - - setSort({ value: "latency", desc: !sort.desc }) - } - direction={ - sort.value === "latency" - ? sort.desc - ? "desc" - : "asc" - : undefined - } - > - Latency - -
+ - + + + + + + + - ) : ( - filteredAndSorted.map((check) => { - const regionInfo = regionDict[check.region as Region]; - if (!regionInfo) return null; + + + {filteredAndSorted.length === 0 ? ( + + + + ) : ( + filteredAndSorted.map((check) => { + const regionInfo = regionDict[check.region as Region]; + if (!regionInfo) return null; - const { dns, connection, tls, ttfb } = check.timingPhases; + const { dns, connection, tls, ttfb } = check.timingPhases; - return ( - - - - - - - - - - - - - ); - }) - )} - - -
- No data available - + RegionStatusDNSConnectTLSTTFB + + setSort({ value: "latency", desc: !sort.desc }) + } + direction={ + sort.value === "latency" + ? sort.desc + ? "desc" + : "asc" + : undefined + } + > + Latency + +
+ No data available +
- - - {regionInfo.flag} {regionInfo.code}{" "} - - {regionInfo.location} - - - {check.status} - - {Intl.NumberFormat("en-US", { - maximumFractionDigits: 0, - }).format(dns)} - ms - - {Intl.NumberFormat("en-US", { - maximumFractionDigits: 0, - }).format(connection)} - ms - - {Intl.NumberFormat("en-US", { - maximumFractionDigits: 0, - }).format(tls)} - ms - - {Intl.NumberFormat("en-US", { - maximumFractionDigits: 0, - }).format(ttfb)} - ms - - {Intl.NumberFormat("en-US", { - maximumFractionDigits: 0, - }).format(check.latency)} - ms -
- Results of your check ({filteredAndSorted.length} / {checks.length}{" "} - regions) -
-
-
+ return ( + + + + + + + {regionInfo.flag} {regionInfo.code}{" "} + + {regionInfo.location} + + + + {check.status} + + + {Intl.NumberFormat("en-US", { + maximumFractionDigits: 0, + }).format(dns)} + ms + + + {Intl.NumberFormat("en-US", { + maximumFractionDigits: 0, + }).format(connection)} + ms + + + {Intl.NumberFormat("en-US", { + maximumFractionDigits: 0, + }).format(tls)} + ms + + + {Intl.NumberFormat("en-US", { + maximumFractionDigits: 0, + }).format(ttfb)} + ms + + + {Intl.NumberFormat("en-US", { + maximumFractionDigits: 0, + }).format(check.latency)} + ms + + + + ); + }) + )} + + + Results of your check ({filteredAndSorted.length} /{" "} + {checks.length} regions) + + +
+ + + setSort({ ...sort, desc: !sort.desc })} + /> + + ); } diff --git a/apps/web/src/app/(landing)/play/checker/[slug]/page.tsx b/apps/web/src/app/(landing)/play/checker/[slug]/page.tsx index f07f70f0c..65c06f078 100644 --- a/apps/web/src/app/(landing)/play/checker/[slug]/page.tsx +++ b/apps/web/src/app/(landing)/play/checker/[slug]/page.tsx @@ -19,7 +19,7 @@ import { getJsonLDBreadcrumbList, getJsonLDWebPage, } from "../../../../../lib/metadata/structured-data"; -import { Table } from "./client"; +import { Results } from "./client"; function formatDate(date: Date) { return date.toLocaleDateString("en-US", { @@ -116,7 +116,7 @@ export default async function Page({

{data.url}

{formatDate(new Date(data.timestamp))}

- + ); diff --git a/apps/web/src/content/pages/tools/checker-slug.mdx b/apps/web/src/content/pages/tools/checker-slug.mdx index 31765b12e..aa0dca7ca 100644 --- a/apps/web/src/content/pages/tools/checker-slug.mdx +++ b/apps/web/src/content/pages/tools/checker-slug.mdx @@ -11,10 +11,6 @@ faq: The data is getting stored for **7 days**. If you want to keep it longer, consider [creating an account](https://app.openstatus.dev) and use our cloud solution. ---- - -> **We have reworked the checker experience ([go back to v1](https://v1.openstatus.dev/play/checker))**. Please let us know if you are missing a feature from the older version. Contact us directly or send us a message to [ping@openstatus.dev](mailto:ping@openstatus.dev). Happy to bring stuff back! - ## Frequently asked questions
-- 2.51.2