import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; import { z } from "zod"; import { approveRun, cancelRun, getRun, rerunRun } from "~/fns/actions"; import { useAction } from "~/hooks/use-action"; import { usePollWhile } from "~/hooks/use-poll-while"; import { JobLog } from "~/components/JobLog"; import { DeploymentGate } from "~/components/DeploymentGate"; import { Badge } from "~/ui/Badge"; import { formatBytes, formatDuration } from "~/lib/format"; import { Panel } from "~/ui/Panel"; import { RelativeTime } from "~/ui/RelativeTime"; import { StatusDot, runState } from "~/ui/StatusDot"; import { Button } from "~/ui/Button"; import { ErrorBanner } from "~/ui/Callout"; import { cn } from "~/utils/cn"; import { Callout } from "~/ui/Callout"; import { ExternalLink, TextLink } from "~/ui/TextLink"; import { Heading } from "~/ui/Heading"; /** A waiting job flips to running within seconds; once its log streams, only the run's final state needs a poll. */ const WAITING_POLL_MS = 2_000; const RUNNING_POLL_MS = 10_000; export const Route = createFileRoute("/$owner/$repo/actions/runs/$number")({ validateSearch: z.object({ job: z.string().optional() }), loader: ({ params }) => getRun({ data: { owner: params.owner, repo: params.repo, number: Number(params.number) } }), component: RunPage, }); function RunPage() { const data = Route.useLoaderData(); const params = Route.useParams(); const search = Route.useSearch(); const navigate = useNavigate(); const cancel = useAction(cancelRun); const rerun = useAction(rerunRun, { invalidate: false }); const approve = useAction(approveRun); const { run } = data; const selected = data.jobs.find((job) => job.id === search.job) ?? data.jobs.find((job) => job.conclusion === "failure") ?? data.jobs[0]; const number = Number(params.number); const waiting = !selected || selected.status === "pending" || selected.status === "queued"; usePollWhile(run.status !== "completed" && waiting, WAITING_POLL_MS); usePollWhile(run.status !== "completed" && !waiting, RUNNING_POLL_MS); const rerunAndOpen = async (failedOnly: boolean) => { const result = await rerun.run({ data: { ...params, number, failedOnly } }); if (result) await navigate({ to: "/$owner/$repo/actions/runs/$number", params: { ...params, number: String(result.number) }, }); }; return (
{run.title}
{selected.status === "queued" ? "Waiting for a runner to pick up this job…" : selected.waiting ? `Waiting for the ${selected.environment ?? ""} environment's protection rules…` : "Waiting for dependencies…"}