mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2025-09-02 10:41:04 +00:00
New workflow table (#661)
This commit is contained in:
parent
3c81e4269a
commit
dadb3724b7
7 changed files with 257 additions and 14 deletions
7
skyvern-frontend/src/components/BadgeLoading.tsx
Normal file
7
skyvern-frontend/src/components/BadgeLoading.tsx
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { Skeleton } from "./ui/skeleton";
|
||||||
|
|
||||||
|
function BadgeLoading() {
|
||||||
|
return <Skeleton className="h-7 w-24" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BadgeLoading };
|
|
@ -69,6 +69,8 @@
|
||||||
--border: 215.3 25% 26.7%;
|
--border: 215.3 25% 26.7%;
|
||||||
--input: 215.3 25% 26.7%;
|
--input: 215.3 25% 26.7%;
|
||||||
--ring: 212.7 26.8% 83.9%;
|
--ring: 212.7 26.8% 83.9%;
|
||||||
|
|
||||||
|
--slate-elevation-2: 228 37% 11%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,5 @@
|
||||||
import { getClient } from "@/api/AxiosClient";
|
import { getClient } from "@/api/AxiosClient";
|
||||||
import { WorkflowApiResponse, WorkflowRunApiResponse } from "@/api/types";
|
import { WorkflowApiResponse, WorkflowRunApiResponse } from "@/api/types";
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
|
||||||
import { WorkflowsBetaAlertCard } from "./WorkflowsBetaAlertCard";
|
|
||||||
import { StatusBadge } from "@/components/StatusBadge";
|
import { StatusBadge } from "@/components/StatusBadge";
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
|
@ -21,9 +9,21 @@ import {
|
||||||
PaginationNext,
|
PaginationNext,
|
||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
} from "@/components/ui/pagination";
|
} from "@/components/ui/pagination";
|
||||||
import { cn } from "@/util/utils";
|
import {
|
||||||
import { WorkflowTitle } from "./WorkflowTitle";
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
import { basicTimeFormat } from "@/util/timeFormat";
|
import { basicTimeFormat } from "@/util/timeFormat";
|
||||||
|
import { cn } from "@/util/utils";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
import { WorkflowsBetaAlertCard } from "./WorkflowsBetaAlertCard";
|
||||||
|
import { WorkflowTitle } from "./WorkflowTitle";
|
||||||
|
|
||||||
function Workflows() {
|
function Workflows() {
|
||||||
const credentialGetter = useCredentialGetter();
|
const credentialGetter = useCredentialGetter();
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { useWorkflowLastRunQuery } from "../hooks/useWorkflowLastRunQuery";
|
||||||
|
import { basicTimeFormat } from "@/util/timeFormat";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
workflowId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function LastRunAtTime({ workflowId }: Props) {
|
||||||
|
const { data, isLoading } = useWorkflowLastRunQuery({ workflowId });
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Skeleton className="h-full w-full" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.status === "N/A") {
|
||||||
|
return <span>N/A</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span>{basicTimeFormat(data.time)}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { LastRunAtTime };
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { BadgeLoading } from "@/components/BadgeLoading";
|
||||||
|
import { StatusBadge } from "@/components/StatusBadge";
|
||||||
|
import { useWorkflowLastRunQuery } from "../hooks/useWorkflowLastRunQuery";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
workflowId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function LastRunStatus({ workflowId }: Props) {
|
||||||
|
const { data, isLoading } = useWorkflowLastRunQuery({ workflowId });
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <BadgeLoading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.status === "N/A") {
|
||||||
|
return <span>N/A</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <StatusBadge status={data.status} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { LastRunStatus };
|
|
@ -0,0 +1,140 @@
|
||||||
|
import { getClient } from "@/api/AxiosClient";
|
||||||
|
import { WorkflowApiResponse } from "@/api/types";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { LastRunStatus } from "./LastRunStatus";
|
||||||
|
import { LastRunAtTime } from "./LastRunAtTime";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious,
|
||||||
|
} from "@/components/ui/pagination";
|
||||||
|
import { cn } from "@/util/utils";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
function WorkflowsTable() {
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const credentialGetter = useCredentialGetter();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { data: workflows, isLoading } = useQuery<Array<WorkflowApiResponse>>({
|
||||||
|
queryKey: ["workflows", page],
|
||||||
|
queryFn: async () => {
|
||||||
|
const client = await getClient(credentialGetter);
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("page", String(page));
|
||||||
|
return client
|
||||||
|
.get("/workflows", {
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
.then((response) => response.data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const skeleton = Array.from({ length: 5 }).map((_, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell>
|
||||||
|
<Skeleton className="h-6 w-full" />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Skeleton className="h-6 w-full" />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Skeleton className="h-6 w-full" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Table>
|
||||||
|
<TableHeader className="bg-slate-elevation2 text-slate-400 [&_tr]:border-b-0">
|
||||||
|
<TableRow className="rounded-lg px-6 [&_th:first-child]:pl-6 [&_th]:py-4">
|
||||||
|
<TableHead className="text-sm text-slate-400">Title</TableHead>
|
||||||
|
<TableHead className="text-sm text-slate-400">
|
||||||
|
Last Run Status
|
||||||
|
</TableHead>
|
||||||
|
<TableHead className="text-sm text-slate-400">
|
||||||
|
Last Run Time
|
||||||
|
</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell className="h-5"></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
<TableBody>
|
||||||
|
{isLoading && skeleton}
|
||||||
|
{workflows?.map((workflow) => {
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={workflow.workflow_permanent_id}
|
||||||
|
className="cursor-pointer [&_td:first-child]:pl-6 [&_td:last-child]:pr-6 [&_td]:py-4"
|
||||||
|
onClick={(event) => {
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
window.open(
|
||||||
|
window.location.origin +
|
||||||
|
`/workflows/${workflow.workflow_permanent_id}`,
|
||||||
|
"_blank",
|
||||||
|
"noopener,noreferrer",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigate(`${workflow.workflow_permanent_id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell>
|
||||||
|
<span className="text-sm leading-5">{workflow.title}</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<LastRunStatus workflowId={workflow.workflow_permanent_id} />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<LastRunAtTime workflowId={workflow.workflow_permanent_id} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<Pagination>
|
||||||
|
<PaginationContent>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationPrevious
|
||||||
|
className={cn({ "cursor-not-allowed": page === 1 })}
|
||||||
|
onClick={() => {
|
||||||
|
setPage((prev) => Math.max(1, prev - 1));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationLink>{page}</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationNext
|
||||||
|
onClick={() => {
|
||||||
|
setPage((prev) => prev + 1);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { WorkflowsTable };
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { getClient } from "@/api/AxiosClient";
|
||||||
|
import { Status, WorkflowRunApiResponse } from "@/api/types";
|
||||||
|
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
workflowId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type LastRunInfo = {
|
||||||
|
status: Status | "N/A";
|
||||||
|
time: string | "N/A";
|
||||||
|
};
|
||||||
|
|
||||||
|
function useWorkflowLastRunQuery({ workflowId }: Props) {
|
||||||
|
const credentialGetter = useCredentialGetter();
|
||||||
|
const queryResult = useQuery<LastRunInfo | null>({
|
||||||
|
queryKey: ["lastRunInfo", workflowId],
|
||||||
|
queryFn: async () => {
|
||||||
|
const client = await getClient(credentialGetter);
|
||||||
|
const data = (await client
|
||||||
|
.get(`/workflows/${workflowId}/runs?page_size=1`)
|
||||||
|
.then((response) => response.data)) as Array<WorkflowRunApiResponse>;
|
||||||
|
if (data.length === 0) {
|
||||||
|
return {
|
||||||
|
status: "N/A",
|
||||||
|
time: "N/A",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: data[0]!.status,
|
||||||
|
time: data[0]!.created_at,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return queryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useWorkflowLastRunQuery };
|
Loading…
Add table
Reference in a new issue