Skip to content

Commit

Permalink
feat: sortable table (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
gluonfield authored Jul 11, 2024
1 parent 392cd42 commit 80246e7
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 121 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@mui/material": "^5.14.5",
"@prisma/client": "^5.1.1",
"@tailwindcss/forms": "^0.5.4",
"@tanstack/react-table": "^8.19.2",
"@textea/json-viewer": "^3.1.1",
"@types/bytes": "^3.1.1",
"@types/moment": "^2.13.0",
Expand Down
265 changes: 144 additions & 121 deletions src/app/runs/[id]/components/TasksTable/TasksTable.tsx
Original file line number Diff line number Diff line change
@@ -1,143 +1,166 @@
import React, { useMemo } from "react"
import {
createColumnHelper,
flexRender,
getCoreRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table"
import { Container, StatusTag, TaskStatusTag } from "@/app/components"
import { formatDuration, fullDateTime } from "@/common"
import { Task } from "@prisma/client"
import bytes from "bytes"
import { FaCaretDown } from "react-icons/fa"

type TasksTableProps = {
tasks: Task[]
className?: string
onTaskClick: (task: Task) => void
}

const columnHelper = createColumnHelper<Task>()

const columns = [
columnHelper.accessor((row) => row.data.status, {
id: "status",
header: "Status",
cell: (info) => <TaskStatusTag status={info.getValue().toLowerCase()} />,
}),
columnHelper.accessor((row) => row.data.process, {
id: "process",
header: "Process",
}),
columnHelper.accessor((row) => row.data.duration, {
id: "duration",
header: "Duration",
cell: (info) => formatDuration(info.getValue(), "ms"),
}),
columnHelper.accessor((row) => row.data.realtime, {
id: "realtime",
header: "Realtime",
cell: (info) => formatDuration(info.getValue(), "ms"),
}),
columnHelper.accessor((row) => row.data.pcpu, {
id: "pcpu",
header: "% CPU",
}),
columnHelper.accessor((row) => row.data.pmem, {
id: "pmem",
header: "% Memory",
}),
columnHelper.accessor((row) => row.data.tag, {
id: "tag",
header: "Tag",
}),
columnHelper.accessor("id", {
header: "Task Id",
}),
columnHelper.accessor((row) => row.data.hash, {
id: "hash",
header: "Hash",
}),
columnHelper.accessor((row) => row.data.exit, {
id: "exit",
header: "Exit",
}),
columnHelper.accessor((row) => row.data.container, {
id: "container",
header: "Container",
}),
columnHelper.accessor((row) => row.data.nativeId, {
id: "nativeId",
header: "Native Id",
}),
columnHelper.accessor((row) => row.data.submit, {
id: "submit",
header: "Submitted",
cell: (info) => fullDateTime(info.getValue()),
}),
columnHelper.accessor((row) => row.data.peakRss, {
id: "peakRss",
header: "Peak RSS",
cell: (info) => bytes(info.getValue() ?? 0),
}),
columnHelper.accessor((row) => row.data.peakVmem, {
id: "peakVmem",
header: "Peak VMEM",
cell: (info) => bytes(info.getValue() ?? 0),
}),
columnHelper.accessor((row) => row.data.rchar, {
id: "rchar",
header: "rchar",
cell: (info) => bytes(info.getValue()),
}),
columnHelper.accessor((row) => row.data.wchar, {
id: "wchar",
header: "wchar",
cell: (info) => bytes(info.getValue()),
}),
columnHelper.accessor((row) => row.data.volCtxt, {
id: "volCtxt",
header: "vol_ctxt",
}),
columnHelper.accessor((row) => row.data.invCtxt, {
id: "invCtxt",
header: "inv_ctxt",
}),
]

export const TasksTable = ({ tasks, className, onTaskClick }: TasksTableProps) => {
const data = useMemo(() => tasks, [tasks])

const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
})

return (
<Container sectionName="Tasks" className={className}>
<div className="overflow-x-auto">
<table className="table-auto w-full text-black">
<thead className="text-xs font-semibold uppercase text-gray-400 bg-gray-50">
<tr>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">Status</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-left">Process</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">Duration</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">Realtime</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">% CPU</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">% Memory</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-left">Tag</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-left">Task Id</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">Hash</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">Exit</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">Container</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">Native Id</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">Submitted</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">Peak RSS</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">Peak VMEM</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">rchar</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">wchar</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">vol_ctxt</div>
</th>
<th className="p-2 whitespace-nowrap">
<div className="font-semibold text-center">inv_ctxt</div>
</th>
</tr>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id} className="p-2 whitespace-nowrap">
{header.isPlaceholder ? null : (
<div
className={header.column.getCanSort() ? "cursor-pointer select-none" : ""}
onClick={header.column.getToggleSortingHandler()}
title={
header.column.getCanSort()
? header.column.getNextSortingOrder() === "asc"
? "Sort ascending"
: header.column.getNextSortingOrder() === "desc"
? "Sort descending"
: "Clear sort"
: undefined
}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{{
asc: " ▲",
desc: " ▼",
}[header.column.getIsSorted() as string] ?? null}
</div>
)}
</th>
))}
</tr>
))}
</thead>
<tbody className="text-sm divide-y divide-gray-100">
{tasks.map((task) => (
<tr key={task.id} className="hover:bg-gray-50 cursor-pointer" onClick={() => onTaskClick(task)}>
<td className="p-2 whitespace-nowrap">
<div className="text-left">
<TaskStatusTag status={task.data.status.toLowerCase()} />
</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-left">{task.data.process}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-left">{formatDuration(task.data.duration, "ms")}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-left">{formatDuration(task.data.realtime, "ms")}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-left">{task.data.pcpu}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-center">{task.data.pmem}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-left">{task.data.tag}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-left">{task.id}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-left">{task.data.hash}</div>
</td>

<td className="p-2 whitespace-nowrap">
<div className="text-left">{task.data.exit}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-left">{task.data.container}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-left">{task.data.nativeId}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-left">{fullDateTime(task.data.submit)}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-center">{bytes(task.data.peakRss ?? 0)}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-center">{bytes(task.data.peakVmem ?? 0)}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-center">{bytes(task.data.rchar)}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-center">{bytes(task.data.wchar)}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-center">{task.data.volCtxt}</div>
</td>
<td className="p-2 whitespace-nowrap">
<div className="text-center">{task.data.invCtxt}</div>
</td>
{table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:bg-gray-50 cursor-pointer" onClick={() => onTaskClick(row.original)}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="p-2 whitespace-nowrap">
<div className={cell.column.id === "process" ? "text-left" : "text-center"}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</div>
</td>
))}
</tr>
))}
</tbody>
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,18 @@
dependencies:
mini-svg-data-uri "^1.2.3"

"@tanstack/react-table@^8.19.2":
version "8.19.2"
resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.19.2.tgz#25ee691d12b8e6e0fca32f8e8cf465ee172009af"
integrity sha512-itoSIAkA/Vsg+bjY23FSemcTyPhc5/1YjYyaMsr9QSH/cdbZnQxHVWrpWn0Sp2BWN71qkzR7e5ye8WuMmwyOjg==
dependencies:
"@tanstack/table-core" "8.19.2"

"@tanstack/[email protected]":
version "8.19.2"
resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.19.2.tgz#5fc8ede54f18867f74715ad93927f7df4d255209"
integrity sha512-KpRjhgehIhbfH78ARm/GJDXGnpdw4bCg3qas6yjWSi7czJhI/J6pWln7NHtmBkGE9ZbohiiNtLqwGzKmBfixig==

"@textea/json-viewer@^3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@textea/json-viewer/-/json-viewer-3.1.1.tgz#cdca583ef93f8c92697c067b41c49d9125aaf77e"
Expand Down

0 comments on commit 80246e7

Please sign in to comment.