Skip to content

Commit

Permalink
New data with pagination (#20)
Browse files Browse the repository at this point in the history
* Adapt to new data model

* Remove row size selection

* Hide column toggle if no filters

* Adapt to pagination and new model

* Adapt tests
  • Loading branch information
abefernan authored Oct 21, 2024
1 parent 10314d3 commit 53327df
Show file tree
Hide file tree
Showing 10 changed files with 1,663 additions and 675 deletions.
33 changes: 1 addition & 32 deletions components/data-table/data-table-pagination.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
ChevronLeftIcon,
ChevronRightIcon,
Expand All @@ -22,32 +15,8 @@ export function DataTablePagination<TData>({
table,
}: DataTablePaginationProps<TData>) {
return (
<div className="flex items-center justify-between px-2">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="flex items-center justify-end px-2">
<div className="flex items-center space-x-6 lg:space-x-8">
<div className="flex items-center space-x-2">
<p className="text-sm font-medium">Rows per page</p>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
}}
>
<SelectTrigger className="h-8 w-[70px]">
<SelectValue placeholder={table.getState().pagination.pageSize} />
</SelectTrigger>
<SelectContent side="top">
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={`${pageSize}`}>
{pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
Page {table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
Expand Down
41 changes: 23 additions & 18 deletions components/data-table/data-table-view-options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ interface DataTableViewOptionsProps<TData> {
export function DataTableViewOptions<TData>({
table,
}: DataTableViewOptionsProps<TData>) {
const hideableColumns = table
.getAllColumns()
.filter(
(column) =>
typeof column.accessorFn !== "undefined" && column.getCanHide(),
);

if (!hideableColumns.length) {
return null;
}

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
Expand All @@ -32,24 +43,18 @@ export function DataTableViewOptions<TData>({
<DropdownMenuContent align="end" className="w-[150px]">
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter(
(column) =>
typeof column.accessorFn !== "undefined" && column.getCanHide(),
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
{hideableColumns.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
8 changes: 4 additions & 4 deletions components/tx-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { useTx } from "@/hooks/api";
import { sequenceDiagramFromSpans } from "@/lib/mermaid";
import { Tx } from "@/types/txs";
import Mermaid from "./mermaid";

type TxDataProps = {
Expand All @@ -11,14 +10,15 @@ type TxDataProps = {
};

export default function TxData({ txId, spanId }: TxDataProps) {
const { isPending, isFetching, error, data: spans } = useTx(txId);
const { isPending, isFetching, error, data: tx } = useTx(txId);

if (isPending) return "Loading...";
if (error) return "An error has occurred: " + error.message;
if (!tx) return "Couldn't find a Tx with id: " + txId;

const mermaidChart = sequenceDiagramFromSpans(spans);
const mermaidChart = sequenceDiagramFromSpans(tx.spans);

const span = spans.find((span: Tx) => span.spanId === spanId);
const span = tx.spans.find((span) => span.spanId === spanId);

return (
<div>
Expand Down
41 changes: 34 additions & 7 deletions components/txs-table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useTxs } from "@/hooks/api";
import { useDebounce } from "@/hooks/use-debounce";
import { Tx } from "@/types/txs";
import { Span, Tx } from "@/types/txs";
import {
ColumnDef,
ColumnFiltersState,
Expand All @@ -16,10 +16,11 @@ import {
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { DataTable } from "../data-table";
import { DataTableColumnHeader } from "../data-table/data-table-column-header";
import { DataTablePagination } from "../data-table/data-table-pagination";
import { Button } from "../ui/button";
import { txsColumns } from "./txs-columns";
import { DataTableToolbar } from "./txs-table-toolbar";

Expand All @@ -36,7 +37,7 @@ export function TxsTable() {
const operationName = useDebounce(
typeof operationNameValue === "string" && operationNameValue.length
? operationNameValue
: "execute_tx",
: "",
);

const tags =
Expand All @@ -52,18 +53,31 @@ export function TxsTable() {
),
cell: ({ row }) => (
<div className="max-w-[500px] truncate font-medium">
{(row.getValue("tags") as Map<string, string>).get(tagKey)}
{(row.getValue("spans") as readonly Span[])
.map((span) => span.tags.get(tagKey))
.filter((span) => !!span)
.join(" | ")}
</div>
),
//NOTE - Don't UI filter, query API
filterFn: () => true,
enableSorting: false,
}),
);

const columns = [...txsColumns, ...tagsColumns];

const { isPending, error, data: txs } = useTxs(operationName, tags);
const data = (txs ?? []) as Tx[];
const {
isPending,
error,
data: queriedData,
fetchNextPage,
} = useTxs(operationName, tags);

const data = useMemo(
() => (queriedData?.pages.flatMap((v) => v) ?? []) as Tx[],
[queriedData?.pages],
);

const table = useReactTable({
columns,
Expand All @@ -80,6 +94,10 @@ export function TxsTable() {
getSortedRowModel: getSortedRowModel(),
});

useEffect(() => {
table.setPageSize(15);
}, [table]);

return (
<div className="space-y-4">
<DataTableToolbar table={table} />
Expand All @@ -90,7 +108,16 @@ export function TxsTable() {
rowLink={{ url: "/", field: "traceId" }}
/>
</div>
<DataTablePagination table={table} />
<div className="flex items-center justify-between flex-wrap">
{/* NOTE - this is a hack to center the "Load more" Button below */}
<div className="invisible">
<DataTablePagination table={table} />
</div>
<Button variant="outline" onClick={() => fetchNextPage()}>
Load more
</Button>
<DataTablePagination table={table} />
</div>
</div>
);
}
58 changes: 31 additions & 27 deletions components/txs-table/txs-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,56 @@ import { DataTableColumnHeader } from "../data-table/data-table-column-header";

export const txsColumns: ColumnDef<Tx>[] = [
{
accessorKey: "traceId",
accessorKey: "startTime",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Trace" />
),
cell: ({ row }) => (
<div className="max-w-[500px] font-medium">{row.getValue("traceId")}</div>
),
<DataTableColumnHeader column={column} title="Start time" />
),
cell: ({ row }) => {
const startDate = new Date(Number(row.getValue("startTime")) / 1000);
return (
<div className="max-w-[500px] font-mono text-xs">
{startDate.toLocaleTimeString() +
" - " +
startDate.toLocaleDateString()}
</div>
);
},
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "spanId",
accessorKey: "traceId",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Span" />
<DataTableColumnHeader column={column} title="Trace" />
),
cell: ({ row }) => (
<div className="max-w-[500px] font-medium">{row.getValue("spanId")}</div>
<div className="max-w-[500px] font-mono font-bold">
{row.getValue("traceId")}
</div>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "spans",
header: () => null,
cell: () => null,
//NOTE - Don't UI filter, query API
filterFn: () => true,
enableHiding: false,
},
{
accessorKey: "operationName",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Operation" />
),
cell: ({ row }) => (
<div className="max-w-[500px] font-medium">
{row.getValue("operationName")}
</div>
),
header: () => null,
cell: () => null,
//NOTE - Don't UI filter, query API
filterFn: () => true,
enableHiding: false,
},
{
accessorKey: "tags",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Tags" />
),
cell: ({ row }) => (
<div className="max-w-[500px] truncate font-medium">
{Array.from((row.getValue("tags") as Map<string, string>).keys()).join(
", ",
)}
</div>
),
header: () => null,
cell: () => null,
//NOTE - Don't UI filter, query API
filterFn: () => true,
enableHiding: false,
Expand Down
46 changes: 29 additions & 17 deletions hooks/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { env } from "@/lib/env";
import { Tx, TxSchema } from "@/types/txs";
import {
keepPreviousData,
useInfiniteQuery,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
Expand All @@ -10,40 +11,51 @@ import { z } from "zod";
export const useTxs = (operationName?: string, tags?: Map<string, string>) => {
const tagsObj = tags ? Object.fromEntries(tags.entries()) : undefined;

const params = new URLSearchParams();
if (operationName) {
params.append("operationName", operationName);
}
if (tagsObj) {
params.append("tags", JSON.stringify(tagsObj));
}

return useQuery<Readonly<Array<Tx>>>({
return useInfiniteQuery<Readonly<Array<Tx>>>({
queryKey: ["txs", { operationName, tags: tagsObj }],
queryFn: () =>
fetch(
`${env.NEXT_PUBLIC_API_URL}/txs${params.size ? `?${params.toString()}` : ""}`,
)
initialPageParam: 0,
getNextPageParam: (lastPage) => {
const nextPageParam = Math.min(
...lastPage.map((trace) => trace.startTime),
);

return Number.isSafeInteger(nextPageParam) ? nextPageParam : undefined;
},
queryFn: async ({ pageParam }) => {
const queryUrl = new URL(`${env.NEXT_PUBLIC_API_URL}/txs`);

if (pageParam && Number.isSafeInteger(pageParam)) {
queryUrl.searchParams.append("searchAfter", String(pageParam));
}
if (operationName) {
queryUrl.searchParams.append("operationName", operationName);
}
if (tagsObj) {
queryUrl.searchParams.append("tags", JSON.stringify(tagsObj));
}

return fetch(queryUrl)
.then((res) => res.json())
.then(({ txs }) => z.array(TxSchema).parse(txs)),
.then(({ txs }) => z.array(TxSchema).parse(txs));
},
placeholderData: keepPreviousData,
});
};

export const useTx = (id: string) => {
const queryClient = useQueryClient();

return useQuery<Readonly<Array<Tx>>>({
return useQuery<Readonly<Tx | undefined>>({
queryKey: ["tx", id],
queryFn: () =>
fetch(`${env.NEXT_PUBLIC_API_URL}/txs?traceID=${id}`)
.then((res) => res.json())
.then(({ txs }) => z.array(TxSchema).parse(txs)),
.then(({ txs }) => z.array(TxSchema).parse(txs).at(0)),
placeholderData: () => {
const cachedTxs =
queryClient.getQueryData<Readonly<Array<Tx>>>(["txs"]) ?? [];

const foundTx = cachedTxs.filter((tx) => tx.traceId === id);
const foundTx = cachedTxs.find((tx) => tx.traceId === id);
return foundTx;
},
});
Expand Down
Loading

0 comments on commit 53327df

Please sign in to comment.