import { Suspense, useMemo, useState } from "react"; import { Trans, useLingui } from "@lingui/react/macro"; import { Divider, Group, SegmentedControl, SimpleGrid, Stack, Text, Title, } from "@mantine/core"; import { ListSkeleton, StatTileSkeleton } from "@/components/skeletons"; import { useRollupRange } from "../queries"; import type { RollupRow } from "../types"; import { DatePickerInput } from "@mantine/dates"; import { fillDailySeries, lastNDays, latestValue, listDays, sumByDim } from "../range"; import { BarList, Sparkline, StatTile } from "./charts"; import { DetailSheet } from "./shared"; import { useSheet } from "@/hooks/use-sheet"; export const USAGE_METRICS = [ "dau", "wau", "mau", "page_view.count", "page_view.sessions", "server_fn.count", "client_error.count", "vital.p75", "event.count", "event.detail.count", ]; const TOP_LIST_SIZE = 8; type VitalName = "LCP" | "INP" | "CLS"; const VITAL_THRESHOLDS: Record = { LCP: { good: 2500, poor: 4000 }, INP: { good: 200, poor: 500 }, CLS: { good: 0.1, poor: 0.25 }, }; const vitalRating = (name: VitalName, value: number): "good" | "ok" | "poor" => { const { good, poor } = VITAL_THRESHOLDS[name]; if (value <= good) return "good"; if (value > poor) return "poor"; return "ok"; }; const vitalColor = (name: VitalName, value: number): string | undefined => { const rating = vitalRating(name, value); if (rating === "good") return undefined; return rating === "ok" ? "yellow.8" : "red"; }; const formatVital = (name: VitalName, value: number) => name === "CLS" ? value.toFixed(2) : `${Math.round(value)}ms`; const dayLabel = (i18n: { date: (d: Date, opts?: Intl.DateTimeFormatOptions) => string }, day: string) => i18n.date(new Date(`${day}T12:00:00Z`), { month: "short", day: "numeric" }); const topItems = (totals: Map, onClick?: (dim: string) => void) => [...totals.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, TOP_LIST_SIZE) .map(([dim, value]) => ({ label: dim, value, onClick: onClick ? () => onClick(dim) : undefined, })); const FnDurationDetail = ({ fn, from, to }: { fn: string; from: string; to: string }) => { const { i18n } = useLingui(); const { data: rows } = useRollupRange({ metrics: ["server_fn.duration.p50", "server_fn.duration.p95"], from, to, dim: fn, }); const days = listDays({ from, to }); const p50 = fillDailySeries(rows, "server_fn.duration.p50", fn, days); const p95 = fillDailySeries(rows, "server_fn.duration.p95", fn, days); const lastNonZero = (series: number[]) => [...series].reverse().find((value) => value > 0) ?? 0; return ( `${Math.round(value)}ms`, }} /> p50 · {Math.round(lastNonZero(p50))}ms p95 · {Math.round(lastNonZero(p95))}ms ); }; const EventBreakdown = ({ name, rows }: { name: string; rows: RollupRow[] }) => { const items = useMemo(() => { const totals = new Map(); const prefix = `${name}|`; for (const row of rows) { if (row.metric !== "event.detail.count" || !row.dim.startsWith(prefix)) continue; const detail = row.dim.slice(prefix.length); totals.set(detail, (totals.get(detail) ?? 0) + row.value); } return [...totals.entries()] .sort((a, b) => b[1] - a[1]) .map(([label, value]) => ({ label, value })); }, [name, rows]); if (items.length === 0) { return ( No breakdown recorded ); } return ; }; const UsageSections = ({ from, to }: { from: string; to: string }) => { const { t, i18n } = useLingui(); const [selectedFn, setSelectedFn] = useState(null); const { isOpen: fnDetailOpened, open: openFnDetail, close: closeFnDetail } = useSheet(); const [selectedEvent, setSelectedEvent] = useState(null); const { isOpen: eventDetailOpened, open: openEventDetail, close: closeEventDetail } = useSheet(); const { data: rows } = useRollupRange({ metrics: USAGE_METRICS, from, to }); const days = useMemo(() => listDays({ from, to }), [from, to]); const dauSeries = fillDailySeries(rows, "dau", "", days); const errorSeries = fillDailySeries(rows, "client_error.count", "", days); const sessions = fillDailySeries(rows, "page_view.sessions", "", days).reduce( (sum, value) => sum + value, 0 ); const pageViewItems = topItems(sumByDim(rows, "page_view.count")); const serverFnItems = topItems(sumByDim(rows, "server_fn.count"), (dim) => { setSelectedFn(dim); openFnDetail(); }); const eventItems = topItems(sumByDim(rows, "event.count"), (dim) => { setSelectedEvent(dim); openEventDetail(); }); const vitals = (["LCP", "INP", "CLS"] as VitalName[]).map((name) => ({ name, value: latestValue(rows, "vital.p75", name), })); const handleCloseFnDetail = () => { setSelectedFn(null); closeFnDetail(); }; return ( <Trans>Engagement</Trans> Daily active players} value={(dauSeries[dauSeries.length - 1] ?? 0).toLocaleString()} spark={dauSeries} sparkScale={{ start: dayLabel(i18n, from), end: dayLabel(i18n, to) }} /> WAU} value={(latestValue(rows, "wau", "") ?? 0).toLocaleString()} /> MAU} value={(latestValue(rows, "mau", "") ?? 0).toLocaleString()} /> Sessions} value={sessions.toLocaleString()} /> Page views by route <Trans>Events</Trans> {eventItems.length > 0 ? ( Top interactions, tap for breakdown ) : ( No events recorded in this range )} <Trans>Server functions</Trans> Top functions by call count <Trans>Errors</Trans> Client errors} value={errorSeries.reduce((sum, value) => sum + value, 0).toLocaleString()} spark={errorSeries} sparkScale={{ start: dayLabel(i18n, from), end: dayLabel(i18n, to) }} /> <Trans>Vitals</Trans> {vitals.map(({ name, value }) => ( ))} { setSelectedEvent(null); closeEventDetail(); }} fields={ selectedEvent ? [ { label: Breakdown, value: , }, ] : [] } /> Duration p50 / p95, value: , }, ] : [] } /> ); }; export const UsageScreen = () => { const { t } = useLingui(); const [preset, setPreset] = useState<"7" | "30" | "180" | "custom">("7"); const [customRange, setCustomRange] = useState<[string | null, string | null]>([null, null]); const range = useMemo(() => { if (preset !== "custom") return lastNDays(Number(preset)); const [start, end] = customRange; if (!start || !end) return null; return { from: start, to: end }; }, [preset, customRange]); return ( setPreset(value as typeof preset)} data={[ { value: "7", label: t`7d` }, { value: "30", label: t`30d` }, { value: "180", label: t`180d` }, { value: "custom", label: t`Custom` }, ]} fullWidth /> {preset === "custom" && ( )} {range ? ( } > ) : ( Pick a date range to see usage )} ); }; export default UsageScreen;