Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 99 additions & 22 deletions src/daft-dashboard/frontend/src/app/queries/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ import {
import LoadingPage from "@/components/loading";

import { QuerySummary, useQueries } from "@/hooks/use-queries";
import { toHumanReadableDate } from "@/lib/utils";
import Status from "./status";
import { toHumanReadableDate, getEngineName } from "@/lib/utils";
import Status, { Duration } from "./status";
import Link from "next/link";
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty";
import { ClipboardIcon, Database } from "lucide-react";
import { ClipboardIcon, Database, ExternalLinkIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from "@/components/ui/tooltip";
import { dashboardUrl } from "@/components/server-provider";
import { TooltipArrow } from "@radix-ui/react-tooltip";

Expand All @@ -42,7 +42,7 @@ const STATUS: string = "state";
// Define status priority for sorting (lower number = higher priority)
const statusPriority: Record<string, number> = {
Pending: 1,
Planning: 2,
Optimizing: 2,
Setup: 3,
Executing: 4,
Finalizing: 5,
Expand All @@ -63,18 +63,87 @@ const columnHelper = createColumnHelper<QuerySummary>();
const columns = [
columnHelper.accessor("id", {
header: "Name",
cell: info => info.getValue(),
cell: info => {
const id = info.getValue();
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="max-w-[150px] truncate">{id}</div>
</TooltipTrigger>
<TooltipContent>
<p>{id}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
},
sortingFn: "alphanumeric",
}),
columnHelper.accessor("status", {
header: "Status",
cell: info => <Status state={info.getValue()} />,
cell: info => <Status state={info.getValue()} showDuration={false} />,
sortingFn: statusSortingFn,
}),
columnHelper.display({
id: "duration",
header: "Duration",
cell: info => <Duration state={info.row.original.status} />,
}),
columnHelper.accessor("entrypoint", {
header: "Entrypoint",
cell: info => {
const entry = info.getValue();
if (!entry) return "-";
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="max-w-[200px] truncate">{entry}</div>
</TooltipTrigger>
<TooltipContent className="max-w-none text-sm">
<p className="break-all font-mono">{entry}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
},
sortingFn: "alphanumeric",
}),
columnHelper.accessor("start_sec", {
header: "Start Time",
cell: info => toHumanReadableDate(info.getValue()),
sortingFn: "basic",
}),
columnHelper.accessor("runner", {
header: "Engine",
cell: info => getEngineName(info.getValue()),
sortingFn: "alphanumeric",
}),
// @ts-ignore
columnHelper.accessor("ray_dashboard_url", {
header: "Ray UI",
cell: info => {
let url = info.getValue();
if (url) {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
return (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-blue-500 hover:underline"
onClick={(e) => e.stopPropagation()}
>
Open Ray UI <ExternalLinkIcon className="h-4 w-4" />
</a>
);
}
return null;
},
enableSorting: false,
})
];

Expand Down Expand Up @@ -184,6 +253,7 @@ export default function QueryList() {
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
columnResizeMode: "onChange",
initialState: {
sorting: [
{ id: "status", desc: false },
Expand All @@ -193,7 +263,7 @@ export default function QueryList() {
});

const spacing = (obj: any) =>
`px-[20px] ${obj.column.columnDef.accessorKey === STATUS ? "w-[60%]" : undefined}`;
`px-[20px]`;

const handleRowClick = (queryId: string) => {
router.push(`/query?id=${queryId}`);
Expand All @@ -208,23 +278,31 @@ export default function QueryList() {
}

return (
<div className="space-y-4 max-w-6xl mx-auto">
<div className="space-y-4 max-w-full px-6 mx-auto">
<Header />

<div className="border">
<div className="border rounded-md overflow-hidden">
<Table>
<TableHeader>
<TableHeader className="bg-white">
{table.getHeaderGroups().map(headerGroup => (
<TableRow key={headerGroup.id} className="">
{headerGroup.headers.map(header => (
<TableRow key={headerGroup.id} className="hover:bg-transparent border-b border-zinc-200">
{headerGroup.headers.map((header, index) => (
<TableHead
key={header.id}
className={`text-xs font-mono ${spacing(header)}`}
className={`relative text-xs font-mono font-bold text-black ${spacing(header)} ${index !== headerGroup.headers.length - 1 ? "border-r border-zinc-200" : ""}`}
style={{ width: header.getSize() }}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
<div
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className={`absolute right-0 top-0 h-full w-1 cursor-col-resize touch-none select-none bg-border/50 hover:bg-primary/50 ${
header.column.getIsResizing() ? "bg-primary" : ""
}`}
/>
</TableHead>
))}
</TableRow>
Expand All @@ -238,17 +316,16 @@ export default function QueryList() {
className="hover:bg-zinc-800 transition-colors duration-50 cursor-pointer"
onClick={() => handleRowClick(row.original.id)}
>
{row.getAllCells().map(cell => (
{row.getAllCells().map((cell, index) => (
<TableCell
key={cell.id}
className={`py-[15px] ${spacing(cell)}`}
className={`py-[15px] ${spacing(cell)} ${index !== row.getAllCells().length - 1 ? "border-r border-zinc-800" : ""}`}
style={{ width: cell.column.getSize() }}
>
<div className="truncate">
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</div>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
Expand Down
50 changes: 39 additions & 11 deletions src/daft-dashboard/frontend/src/app/queries/status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { AnimatedFish, Naruto } from "@/components/icons";

interface StatusBadgeProps {
state: QueryStatus;
showDuration?: boolean;
}

const StatusBadgeInner = ({
Expand All @@ -44,7 +45,7 @@ const StatusBadgeInner = ({
<span className={`${main.className} ${textColor} font-bold text-sm`}>
{label}
</span>
<span className={`${main.className} font-semibold text-sm`}>{text}</span>
{text && <span className={`${main.className} font-semibold text-sm`}>{text}</span>}
</div>
);

Expand Down Expand Up @@ -77,7 +78,7 @@ const Pending = () => (
/>
);

const Planning = ({ state }: { state: PlanningStatus }) => {
const Planning = ({ state, showDuration }: { state: PlanningStatus, showDuration: boolean }) => {
const [currentTime, setCurrentTime] = useState(() => Date.now());

useEffect(() => {
Expand All @@ -95,7 +96,7 @@ const Planning = ({ state }: { state: PlanningStatus }) => {
<LoaderCircle size={16} strokeWidth={3} className="text-orange-500" />
}
label="Optimizing"
text={` for ${toHumanReadableDuration(duration)}`}
text={showDuration ? ` for ${toHumanReadableDuration(duration)}` : undefined}
textColor="text-orange-500"
/>
);
Expand All @@ -111,7 +112,7 @@ const Setup = () => (
/>
);

const Running = ({ state }: { state: ExecutingStatus }) => {
const Running = ({ state, showDuration }: { state: ExecutingStatus, showDuration: boolean }) => {
const [currentTime, setCurrentTime] = useState(() => Date.now());

useEffect(() => {
Expand All @@ -127,7 +128,7 @@ const Running = ({ state }: { state: ExecutingStatus }) => {
<StatusBadgeInner
icon={<AnimatedFish />}
label="Running"
text={` for ${toHumanReadableDuration(duration)}`}
text={showDuration ? ` for ${toHumanReadableDuration(duration)}` : undefined}
textColor="text-(--daft-accent)"
/>
);
Expand All @@ -141,11 +142,11 @@ const Finalizing = () => (
/>
);

const Finished = ({ state }: { state: FinishedStatus }) => (
const Finished = ({ state, showDuration }: { state: FinishedStatus, showDuration: boolean }) => (
<StatusBadgeInner
icon={<Naruto />}
label="Finished"
text={`in ${toHumanReadableDuration(state.duration_sec)}`}
text={showDuration ? `in ${toHumanReadableDuration(state.duration_sec)}` : undefined}
textColor="text-green-500"
/>
);
Expand Down Expand Up @@ -184,20 +185,47 @@ const Unknown = () => (
/>
);

export default function Status({ state }: StatusBadgeProps) {
export function Duration({ state }: { state: QueryStatus }) {
const [currentTime, setCurrentTime] = useState(() => Date.now());

useEffect(() => {
// Only set interval if state is running
if (state.status === "Optimizing" || state.status === "Executing") {
const interval = setInterval(() => {
setCurrentTime(Date.now());
}, 1000);
return () => clearInterval(interval);
}
}, [state.status]);

if (state.status === "Finished") {
return <>{toHumanReadableDuration(state.duration_sec)}</>;
}
if (state.status === "Optimizing") {
const duration = Math.round(currentTime / 1000 - state.plan_start_sec);
return <>{toHumanReadableDuration(duration)}</>;
}
if (state.status === "Executing") {
const duration = Math.round(currentTime / 1000 - state.exec_start_sec);
return <>{toHumanReadableDuration(duration)}</>;
}
return <>-</>;
}

export default function Status({ state, showDuration = true }: StatusBadgeProps) {
switch (state.status) {
case "Pending":
return <Pending />;
case "Optimizing":
return <Planning state={state} />;
return <Planning state={state} showDuration={showDuration} />;
case "Setup":
return <Setup />;
case "Executing":
return <Running state={state} />;
return <Running state={state} showDuration={showDuration} />;
case "Finalizing":
return <Finalizing />;
case "Finished":
return <Finished state={state} />;
return <Finished state={state} showDuration={showDuration} />;
case "Failed":
return <Failed state={state} />;
case "Canceled":
Expand Down
Loading
Loading