diff --git a/apps/web/src/components/data-table/single-region/columns.tsx b/apps/web/src/components/data-table/single-region/columns.tsx new file mode 100644 index 00000000..75161934 --- /dev/null +++ b/apps/web/src/components/data-table/single-region/columns.tsx @@ -0,0 +1,96 @@ +"use client"; + +import { SimpleChart } from "@/components/monitor-charts/simple-chart"; +import { formatNumber } from "@/components/monitor-dashboard/metrics-card"; +import type { Region, ResponseTimeMetricsByRegion } from "@openstatus/tinybird"; +import { flyRegionsDict } from "@openstatus/utils"; +import type { ColumnDef } from "@tanstack/react-table"; +import { DataTableColumnHeader } from "./data-table-column-header"; + +export interface RegionWithMetrics { + data: (Partial> & { timestamp: string })[]; + metrics?: ResponseTimeMetricsByRegion; + region: Region; +} + +export const columns: ColumnDef[] = [ + { + accessorKey: "region", + header: "Region", + cell: ({ row }) => { + const region = row.getValue("region") as Region; + const { code, flag, location } = flyRegionsDict[region]; + return ( +
+

{location}

+

+ {flag} {code} +

+
+ ); + }, + meta: { + headerClassName: "w-[100px]", + }, + }, + { + accessorKey: "data", + header: "Trend", + cell: ({ row }) => { + const data = row.getValue("data") as RegionWithMetrics["data"]; + const region = row.getValue("region") as Region; + return ; + }, + meta: { + headerClassName: "min-w-[300px] w-full", + }, + }, + { + accessorKey: "p50", + header: ({ column }) => ( + + ), + accessorFn: (row) => row.metrics?.p50Latency, + cell: ({ row }) => { + const p50 = row.getValue("p50") as number; + return ( +
+ {formatNumber(p50)}{" "} + ms +
+ ); + }, + }, + { + accessorKey: "p95", + accessorFn: (row) => row.metrics?.p95Latency, + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const p95 = row.getValue("p95") as number; + return ( +
+ {formatNumber(p95)}{" "} + ms +
+ ); + }, + }, + { + accessorKey: "p99", + header: ({ column }) => ( + + ), + accessorFn: (row) => row.metrics?.p99Latency, + cell: ({ row }) => { + const p99 = row.getValue("p99") as number; + return ( +
+ {formatNumber(p99)}{" "} + ms +
+ ); + }, + }, +]; diff --git a/apps/web/src/components/data-table/single-region/data-table-column-header.tsx b/apps/web/src/components/data-table/single-region/data-table-column-header.tsx new file mode 100644 index 00000000..68b1fea9 --- /dev/null +++ b/apps/web/src/components/data-table/single-region/data-table-column-header.tsx @@ -0,0 +1,66 @@ +import type { Column } from "@tanstack/react-table"; +import { ArrowDown, ArrowUp, ChevronsUpDown } from "lucide-react"; + +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@openstatus/ui"; + +import { cn } from "@/lib/utils"; + +interface DataTableColumnHeaderProps + extends React.HTMLAttributes { + column: Column; + title: string; +} + +export function DataTableColumnHeader({ + column, + title, + className, +}: DataTableColumnHeaderProps) { + if (!column.getCanSort()) { + return
{title}
; + } + + return ( +
+ + + + + + column.toggleSorting(false)}> + + Asc + + column.toggleSorting(true)}> + + Desc + + {/* + column.toggleVisibility(false)}> + + Hide + */} + + +
+ ); +} diff --git a/apps/web/src/components/data-table/single-region/data-table.tsx b/apps/web/src/components/data-table/single-region/data-table.tsx new file mode 100644 index 00000000..bc542013 --- /dev/null +++ b/apps/web/src/components/data-table/single-region/data-table.tsx @@ -0,0 +1,92 @@ +"use client"; + +import type { ColumnDef, SortingState } from "@tanstack/react-table"; +import { + flexRender, + getCoreRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import * as React from "react"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@openstatus/ui"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ + columns, + data, +}: DataTableProps) { + const [sorting, setSorting] = React.useState([]); + const table = useReactTable({ + data, + columns, + state: { sorting }, + getCoreRowModel: getCoreRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+ ); +} diff --git a/apps/web/src/components/monitor-charts/combined-chart-wrapper.tsx b/apps/web/src/components/monitor-charts/combined-chart-wrapper.tsx index 174554ff..750bfe43 100644 --- a/apps/web/src/components/monitor-charts/combined-chart-wrapper.tsx +++ b/apps/web/src/components/monitor-charts/combined-chart-wrapper.tsx @@ -11,6 +11,8 @@ import type { } from "@openstatus/tinybird"; import { Toggle } from "@openstatus/ui"; +import { columns } from "@/components/data-table/single-region/columns"; +import { DataTable } from "@/components/data-table/single-region/data-table"; import { IntervalPreset } from "@/components/monitor-dashboard/interval-preset"; import { QuantilePreset } from "@/components/monitor-dashboard/quantile-preset"; import { RegionsPreset } from "@/components/monitor-dashboard/region-preset"; @@ -18,7 +20,6 @@ import type { Interval, Period, Quantile } from "@/lib/monitor/utils"; import { usePreferredSettings } from "@/lib/preferred-settings/client"; import type { PreferredSettings } from "@/lib/preferred-settings/server"; import { Chart } from "./chart"; -import { RegionTable } from "./region-table"; import { groupDataByTimestamp } from "./utils"; export function CombinedChartWrapper({ @@ -53,6 +54,18 @@ export function CombinedChartWrapper({ const combinedRegions = preferredSettings?.combinedRegions ?? false; + const tableData = useMemo( + () => + regions + .map((region) => ({ + region, + data: chartData.data, + metrics: metricsByRegion.find((metrics) => metrics.region === region), + })) + .filter((row) => !!row.metrics), + [regions, chartData, metricsByRegion], + ); + return ( <>
@@ -82,15 +95,11 @@ export function CombinedChartWrapper({
-
+
{combinedRegions ? ( ) : ( - + )}
diff --git a/apps/web/src/components/monitor-charts/region-table.tsx b/apps/web/src/components/monitor-charts/region-table.tsx index b585d9bb..638eb526 100644 --- a/apps/web/src/components/monitor-charts/region-table.tsx +++ b/apps/web/src/components/monitor-charts/region-table.tsx @@ -23,13 +23,15 @@ export interface RegionTableProps { caption?: string; } +/** + * @deprecated use the /region/data-table.tsx component instead, this is only used for the content blog posts + */ export function RegionTable({ regions, data, metricsByRegion, caption = "A list of all the selected regions.", }: RegionTableProps) { - // console.log(JSON.stringify({ regions, data, metricsByRegion }, null, 2)); return ( {caption} @@ -81,12 +83,6 @@ export function RegionTable({ ); })} - {/* - - Total - 0 - - */}
); } diff --git a/apps/web/src/react-table.d.ts b/apps/web/src/react-table.d.ts new file mode 100644 index 00000000..3e6ce4ab --- /dev/null +++ b/apps/web/src/react-table.d.ts @@ -0,0 +1,8 @@ +import "@tanstack/react-table"; + +declare module "@tanstack/react-table" { + interface ColumnMeta { + headerClassName?: string; + cellClassName?: string; + } +}