Skip to content

Commit

Permalink
feat: add tasks table with sortable columns (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
notnitsuj committed Feb 10, 2024
1 parent 32482c3 commit 38e2f71
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 18 deletions.
9 changes: 5 additions & 4 deletions src/backend/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,12 @@ class JobCreate(JobBase):

class JobRead(JobBase):
id: int
best: Optional[int]


class TaskBase(SQLModel):
status: int = Field(default=0, nullable=False)
job_id: int = Field(foreign_key="job.id")
execute_order: Optional[int] = Field(default=0, nullable=False)
checkpoint: Optional[str] = Field(default=None)
logs: Optional[str] = Field(default=None)
execute_order: Optional[int] = Field(default=1, nullable=False)
use_gpu: Optional[bool] = Field(default=False, nullable=False)
lr: Optional[float] = Field(default=1e-3, nullable=False)
train_batch_size: Optional[int] = Field(default=64, nullable=False)
Expand All @@ -48,9 +45,12 @@ class TaskBase(SQLModel):

class Task(TaskBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
checkpoint: Optional[str] = Field(default=None)
logs: Optional[str] = Field(default=None)
accuracy: Optional[float] = Field(default=None)
avg_precision: Optional[float] = Field(default=None)
avg_recall: Optional[float] = Field(default=None)
runtime: Optional[int] = Field(default=0)

job: Job = Relationship(back_populates="tasks")

Expand All @@ -64,6 +64,7 @@ class TaskRead(TaskBase):
accuracy: Optional[float]
avg_precision: Optional[float]
avg_recall: Optional[float]
runtime: Optional[int]


class TaskReadWithJob(TaskRead):
Expand Down
1 change: 0 additions & 1 deletion src/backend/database/schema.dbml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ Table job {
backlog_order integer
type integer [not null]
strategy integer [not null]
best integer
}

Table task {
Expand Down
10 changes: 5 additions & 5 deletions src/frontend/app/hook/use-navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ const useNavigation = () => {
const pathname = usePathname();
const [IsHomeActive, setIsHomeActive] = useState(false);
const [isAddActive, setIsAddActive] = useState(false);
const [isJobsActive, setIsJobsActive] = useState(false);
const [isTasksActive, setIsTasksActive] = useState(false);
const [isResultsActive, setIsResultsActive] = useState(false);

useEffect(() => {
setIsHomeActive(false);
setIsAddActive(false);
setIsJobsActive(false);
setIsTasksActive(false);
setIsResultsActive(false);

switch (pathname) {
Expand All @@ -24,8 +24,8 @@ const useNavigation = () => {
case "/add":
setIsAddActive(true);
break;
case "/jobs":
setIsJobsActive(true);
case "/tasks":
setIsTasksActive(true);
break;
case "/results":
setIsResultsActive(true);
Expand All @@ -38,7 +38,7 @@ const useNavigation = () => {
return {
IsHomeActive,
isAddActive,
isJobsActive,
isTasksActive,
isResultsActive,
};
};
Expand Down
1 change: 0 additions & 1 deletion src/frontend/app/interface/jobs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export default interface JobInterface {
backlog_order: number;
type: number;
strategy: number;
best: number | null;
tasks: {
id: number;
status: number;
Expand Down
31 changes: 31 additions & 0 deletions src/frontend/app/interface/tasks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export default interface TaskInterface {
id: number;
status: number;
job_id: number;
execute_order: number;
checkpoint: string;
logs: string;
use_gpu: boolean;
lr: number;
train_batch_size: number;
test_batch_size: number;
epoch: number;
dropout_rate: number;
transform: string;
optimizer: number;
optimizer_args: string;
scheduler: number;
scheduler_args: string;
cleanlab: boolean;
accuracy: number;
avg_precision: number;
avg_recall: number;
runtime: number;
job: {
id: number;
queue_order: number;
backlog_order: number;
type: number;
strategy: number;
};
}
3 changes: 0 additions & 3 deletions src/frontend/app/jobs/page.tsx

This file was deleted.

21 changes: 21 additions & 0 deletions src/frontend/app/tasks/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client";

import useSWR from "swr";

import { GET_ALL_TASKS_URL } from "../constants/backend";
import SortableTable from "@/components/sortable-table";

export default function Tasks() {
const fetcher = (url: string) => fetch(url).then((res) => res.json());

const { data, error, isLoading } = useSWR(GET_ALL_TASKS_URL, fetcher);

if (error) return "An error has occurred.";
if (isLoading) return "Loading...";

return (
<div className="ml-52">
<SortableTable tasks={data} />
</div>
);
}
8 changes: 4 additions & 4 deletions src/frontend/components/side-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import useNavigation from "@/app/hook/use-navigation";
import { Icon } from "@iconify/react";

const SideNav = () => {
const { IsHomeActive, isAddActive, isJobsActive, isResultsActive } =
const { IsHomeActive, isAddActive, isTasksActive, isResultsActive } =
useNavigation();

return (
Expand Down Expand Up @@ -46,16 +46,16 @@ const SideNav = () => {
</Link>

<Link
href="/jobs"
href="/tasks"
className="flex flex-row space-x-4 items-center px-4 py-3 rounded-full duration-200 hover:bg-black/10"
>
<Icon icon="carbon:batch-job" width="38" height="38" />
<span
className={`text-2xl hidden md:flex ${
isJobsActive ? "font-bold" : ""
isTasksActive ? "font-bold" : ""
}`}
>
Jobs
Tasks
</span>
</Link>

Expand Down
151 changes: 151 additions & 0 deletions src/frontend/components/sortable-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"use client";

import { useState, useCallback, MouseEventHandler } from "react";
import TaskInterface from "@/app/interface/tasks";

type SortOrder = "ascn" | "desc";

type SortKeys = keyof TaskInterface;

function sortData({
tableData,
sortKey,
reverse,
}: {
tableData: TaskInterface[];
sortKey: SortKeys;
reverse: boolean;
}) {
if (!sortKey) return tableData;

const sortedData = tableData.sort((a: TaskInterface, b: TaskInterface) => {
return a[sortKey] > b[sortKey] ? 1 : -1;
});

if (reverse) {
return sortedData.reverse();
}

return sortedData;
}

function SortButton({
sortOrder,
columnKey,
sortKey,
onClick,
}: {
sortOrder: SortOrder;
columnKey: SortKeys;
sortKey: SortKeys;
onClick: MouseEventHandler<HTMLButtonElement>;
}) {
return (
<button
onClick={onClick}
className={`${
sortKey === columnKey && sortOrder === "desc" ? " rotate-180" : ""
}`}
>
</button>
);
}

export default function SortableTable({ tasks }: { tasks: TaskInterface[] }) {
const [sortKey, setSortKey] = useState<SortKeys>("id");
const [sortOrder, setSortOrder] = useState<SortOrder>("ascn");

const sortedData = useCallback(
() =>
sortData({ tableData: tasks, sortKey, reverse: sortOrder === "desc" }),
[tasks, sortKey, sortOrder]
);

const headers: { key: SortKeys; label: string }[] = [
{ key: "id", label: "ID" },
{ key: "status", label: "Task Status" },
{ key: "job_id", label: "Job" },
{ key: "lr", label: "Learning rate" },
{ key: "train_batch_size", label: "Batch size (train)" },
{ key: "test_batch_size", label: "Batch size (test)" },
{ key: "epoch", label: "epoch" },
{ key: "dropout_rate", label: "Dropout rate" },
{ key: "optimizer", label: "Optimizer" },
{ key: "scheduler", label: "Scheduler" },
{ key: "accuracy", label: "Accuracy" },
{ key: "avg_precision", label: "Average Precision" },
{ key: "avg_recall", label: "Average Recall" },
{ key: "runtime", label: "Runtime" },
];

const sortableHeaders = [
"id",
"accuracy",
"avg_precision",
"avg_recall",
"runtime",
];

function changeSort(key: SortKeys) {
setSortOrder(sortOrder === "ascn" ? "desc" : "ascn");

setSortKey(key);
}

return (
<table className="w-full text-sm text-left rtl:text-right text-black">
<caption className="p-5 text-xl font-semibold text-left rtl:text-right text-gray-900 bg-white">
ALL TASKS
<p className="mt-1 text-sm font-normal text-black">
Browse the list of tasks that have been created. Tasks can be sorted
by ID, accuracy, average precision, average recall and runtime.
</p>
</caption>
<thead className="text-sm text-black uppercase bg-white">
<tr>
{headers.map((row) => {
return (
<td key={row.key}>
{row.label}{" "}
{sortableHeaders.includes(row.key) && (
<SortButton
columnKey={row.key}
onClick={() => changeSort(row.key)}
{...{
sortOrder,
sortKey,
}}
/>
)}
</td>
);
})}
</tr>
</thead>

<tbody>
{sortedData().map((task) => {
return (
<tr key={task.id} className="bg-white border-b hover:bg-gray-50 ">
<td>{task.id}</td>
<td>{task.status}</td>
<td>{task.job_id}</td>
<td>{task.lr}</td>
<td>{task.train_batch_size}</td>
<td>{task.test_batch_size}</td>
<td>{task.epoch}</td>
<td>{task.dropout_rate}</td>
<td>{task.optimizer}</td>
<td>{task.scheduler}</td>
<td>{task.accuracy}</td>
<td>{task.avg_precision}</td>
<td>{task.avg_recall}</td>
<td>{task.runtime}</td>
</tr>
);
})}
</tbody>
</table>
);
}

0 comments on commit 38e2f71

Please sign in to comment.