Files
triggerdotdev--trigger.dev/apps/webapp/app/components/runs/v3/TaskRunStatus.tsx
T
2026-07-13 13:32:57 +08:00

331 lines
11 KiB
TypeScript

import {
ArrowPathIcon,
BoltSlashIcon,
BugAntIcon,
CheckCircleIcon,
ClockIcon,
FireIcon,
NoSymbolIcon,
PauseCircleIcon,
RectangleStackIcon,
TrashIcon,
XCircleIcon,
} from "@heroicons/react/20/solid";
import type { TaskRunStatus } from "@trigger.dev/database";
import { runFriendlyStatus, type RunFriendlyStatus } from "@trigger.dev/core/v3";
import assertNever from "assert-never";
import { HourglassIcon } from "lucide-react";
import { TimedOutIcon } from "~/assets/icons/TimedOutIcon";
import { Callout } from "~/components/primitives/Callout";
import { Spinner } from "~/components/primitives/Spinner";
import { cn } from "~/utils/cn";
export const allTaskRunStatuses = [
"DELAYED",
"WAITING_FOR_DEPLOY",
"PENDING_VERSION",
"PENDING",
"DEQUEUED",
"EXECUTING",
"RETRYING_AFTER_FAILURE",
"WAITING_TO_RESUME",
"COMPLETED_SUCCESSFULLY",
"COMPLETED_WITH_ERRORS",
"CANCELED",
"TIMED_OUT",
"CRASHED",
"PAUSED",
"INTERRUPTED",
"SYSTEM_FAILURE",
"EXPIRED",
] as const satisfies Readonly<Array<TaskRunStatus>>;
export const filterableTaskRunStatuses = [
"PENDING_VERSION",
"DELAYED",
"PENDING",
"DEQUEUED",
"EXECUTING",
"WAITING_TO_RESUME",
"COMPLETED_SUCCESSFULLY",
"COMPLETED_WITH_ERRORS",
"TIMED_OUT",
"CRASHED",
"SYSTEM_FAILURE",
"CANCELED",
"EXPIRED",
] as const satisfies Readonly<Array<TaskRunStatus>>;
const taskRunStatusDescriptions: Record<TaskRunStatus, string> = {
DELAYED: "Task has been delayed and is waiting to be executed.",
PENDING: "Task is waiting to be executed.",
PENDING_VERSION: "Run cannot execute until a version includes the task and queue.",
WAITING_FOR_DEPLOY: "Run cannot execute until a version includes the task and queue.",
DEQUEUED: "Task has been dequeued from the queue but is not yet executing.",
EXECUTING: "Task is currently being executed.",
RETRYING_AFTER_FAILURE: "Task is being reattempted after a failure.",
WAITING_TO_RESUME: `You have used a "wait" function. When the wait is complete, the task will resume execution.`,
COMPLETED_SUCCESSFULLY: "Task has been successfully completed.",
CANCELED: "Task has been canceled.",
COMPLETED_WITH_ERRORS: "Task has failed with errors.",
INTERRUPTED: "Task has failed because it was interrupted.",
SYSTEM_FAILURE: "Task has failed due to a system failure.",
PAUSED: "Task has been paused by the user.",
CRASHED: "Task has crashed and won't be retried.",
EXPIRED: "Task has surpassed its ttl and won't be executed.",
TIMED_OUT: "Task has failed because it exceeded its maxDuration.",
};
export const QUEUED_STATUSES = [
"PENDING",
"PENDING_VERSION",
"WAITING_FOR_DEPLOY",
"DELAYED",
] satisfies TaskRunStatus[];
export const RUNNING_STATUSES = [
"DEQUEUED",
"EXECUTING",
"RETRYING_AFTER_FAILURE",
"WAITING_TO_RESUME",
] satisfies TaskRunStatus[];
export function descriptionForTaskRunStatus(status: TaskRunStatus): string {
return taskRunStatusDescriptions[status];
}
export function TaskRunStatusCombo({
status,
className,
iconClassName,
}: {
status: TaskRunStatus;
className?: string;
iconClassName?: string;
}) {
return (
<span className={cn("flex items-center gap-1", className)}>
<TaskRunStatusIcon status={status} className={cn("h-4 w-4", iconClassName)} />
<TaskRunStatusLabel status={status} />
</span>
);
}
const statusReasonsToDescription: Record<string, string> = {
NO_DEPLOYMENT: "No deployment or deployment image reference found for deployed run",
NO_WORKER: "No worker found for run",
TASK_NEVER_REGISTERED: "Task never registered",
QUEUE_NOT_FOUND: "Queue not found",
TASK_NOT_IN_LATEST: "Task not in latest version",
BACKGROUND_WORKER_MISMATCH: "Background worker mismatch",
};
export function TaskRunStatusReason({
status,
statusReason,
}: {
status: TaskRunStatus;
statusReason?: string;
}) {
if (status !== "PENDING_VERSION") {
return null;
}
if (!statusReason) {
return null;
}
const description = statusReasonsToDescription[statusReason];
if (!description) {
return null;
}
return (
<Callout to="https://trigger.dev/docs" variant="warning" className="text-sm">
{description}
</Callout>
);
}
export function TaskRunStatusLabel({ status }: { status: TaskRunStatus }) {
return <span className={runStatusClassNameColor(status)}>{runStatusTitle(status)}</span>;
}
export function TaskRunStatusIcon({
status,
className,
}: {
status: TaskRunStatus;
className: string;
}) {
switch (status) {
case "DELAYED":
return <ClockIcon className={cn(runStatusClassNameColor(status), className)} />;
case "PENDING":
return <RectangleStackIcon className={cn(runStatusClassNameColor(status), className)} />;
case "PENDING_VERSION":
case "WAITING_FOR_DEPLOY":
return <RectangleStackIcon className={cn(runStatusClassNameColor(status), className)} />;
case "DEQUEUED":
return <RectangleStackIcon className={cn(runStatusClassNameColor(status), className)} />;
case "EXECUTING":
return <Spinner className={cn(runStatusClassNameColor(status), className)} />;
case "WAITING_TO_RESUME":
return <HourglassIcon className={cn(runStatusClassNameColor(status), className)} />;
case "RETRYING_AFTER_FAILURE":
return <ArrowPathIcon className={cn(runStatusClassNameColor(status), className)} />;
case "PAUSED":
return <PauseCircleIcon className={cn(runStatusClassNameColor(status), className)} />;
case "CANCELED":
return <NoSymbolIcon className={cn(runStatusClassNameColor(status), className)} />;
case "INTERRUPTED":
return <BoltSlashIcon className={cn(runStatusClassNameColor(status), className)} />;
case "COMPLETED_SUCCESSFULLY":
return <CheckCircleIcon className={cn(runStatusClassNameColor(status), className)} />;
case "COMPLETED_WITH_ERRORS":
return <XCircleIcon className={cn(runStatusClassNameColor(status), className)} />;
case "SYSTEM_FAILURE":
return <BugAntIcon className={cn(runStatusClassNameColor(status), className)} />;
case "CRASHED":
return <FireIcon className={cn(runStatusClassNameColor(status), className)} />;
case "EXPIRED":
return <TrashIcon className={cn(runStatusClassNameColor(status), className)} />;
case "TIMED_OUT":
return <TimedOutIcon className={cn(runStatusClassNameColor(status), className)} />;
default: {
assertNever(status);
}
}
}
export function runStatusClassNameColor(status: TaskRunStatus): string {
switch (status) {
case "PENDING":
case "DELAYED":
return "text-text-faint";
case "PENDING_VERSION":
case "WAITING_FOR_DEPLOY":
return "text-amber-500";
case "EXECUTING":
case "RETRYING_AFTER_FAILURE":
case "DEQUEUED":
return "text-pending";
case "WAITING_TO_RESUME":
return "text-text-faint";
case "PAUSED":
return "text-amber-300";
case "CANCELED":
case "EXPIRED":
return "text-text-faint";
case "INTERRUPTED":
return "text-error";
case "COMPLETED_SUCCESSFULLY":
return "text-success";
case "COMPLETED_WITH_ERRORS":
return "text-error";
case "SYSTEM_FAILURE":
return "text-error";
case "CRASHED":
return "text-error";
case "TIMED_OUT":
return "text-error";
default: {
assertNever(status);
}
}
}
export function runStatusTitle(status: TaskRunStatus): RunFriendlyStatus {
return runStatusTitleFromStatus[status];
}
export function runStatusFromFriendlyTitle(friendly: RunFriendlyStatus): TaskRunStatus {
const result = titlesStatusesArray.find(([_, f]) => f === friendly);
if (!result) {
throw new Error(`Unknown friendly status: ${friendly}`);
}
return result[0] as TaskRunStatus;
}
// runFriendlyStatus and RunFriendlyStatus are imported from @trigger.dev/core/v3
// and re-exported here for backward compatibility.
export { runFriendlyStatus, type RunFriendlyStatus } from "@trigger.dev/core/v3";
/**
* Check if a value is a valid TaskRunStatus
*/
export function isTaskRunStatus(value: unknown): value is TaskRunStatus {
return typeof value === "string" && allTaskRunStatuses.includes(value as TaskRunStatus);
}
/**
* Check if a value is a valid RunFriendlyStatus
*/
export function isRunFriendlyStatus(value: unknown): value is RunFriendlyStatus {
return typeof value === "string" && runFriendlyStatus.includes(value as RunFriendlyStatus);
}
export const runStatusTitleFromStatus: Record<TaskRunStatus, RunFriendlyStatus> = {
DELAYED: "Delayed",
PENDING: "Queued",
PENDING_VERSION: "Pending version",
WAITING_FOR_DEPLOY: "Pending version",
DEQUEUED: "Dequeued",
EXECUTING: "Executing",
WAITING_TO_RESUME: "Waiting",
RETRYING_AFTER_FAILURE: "Reattempting",
PAUSED: "Paused",
CANCELED: "Canceled",
INTERRUPTED: "Interrupted",
COMPLETED_SUCCESSFULLY: "Completed",
COMPLETED_WITH_ERRORS: "Failed",
SYSTEM_FAILURE: "System failure",
CRASHED: "Crashed",
EXPIRED: "Expired",
TIMED_OUT: "Timed out",
};
const titlesStatusesArray = Object.entries(runStatusTitleFromStatus);
/**
* Chart series color for each TaskRunStatus, mirroring `runStatusClassNameColor`.
* Values are CSS variables (defined in tailwind.css) so they follow the theme;
* only usable in CSS contexts (SVG attributes, chart config styles).
*/
const RUN_STATUS_CHART_COLORS: Record<TaskRunStatus, string> = {
PENDING: "var(--color-run-pending)",
DELAYED: "var(--color-run-delayed)",
PENDING_VERSION: "var(--color-run-pending-version)",
WAITING_FOR_DEPLOY: "var(--color-run-waiting-for-deploy)",
EXECUTING: "var(--color-run-executing)",
RETRYING_AFTER_FAILURE: "var(--color-run-retrying-after-failure)",
DEQUEUED: "var(--color-run-dequeued)",
WAITING_TO_RESUME: "var(--color-run-waiting-to-resume)",
PAUSED: "var(--color-run-paused)",
CANCELED: "var(--color-run-canceled)",
EXPIRED: "var(--color-run-expired)",
INTERRUPTED: "var(--color-run-interrupted)",
COMPLETED_SUCCESSFULLY: "var(--color-run-completed-successfully)",
COMPLETED_WITH_ERRORS: "var(--color-run-completed-with-errors)",
SYSTEM_FAILURE: "var(--color-run-system-failure)",
CRASHED: "var(--color-run-crashed)",
TIMED_OUT: "var(--color-run-timed-out)",
};
/**
* Get the chart color for a run status value. Accepts either a raw TaskRunStatus
* (e.g. "COMPLETED_SUCCESSFULLY") or a friendly name (e.g. "Completed").
* Returns `undefined` when the value is not a recognised status.
*/
export function getRunStatusChartColor(value: string): string | undefined {
if (isTaskRunStatus(value)) {
return RUN_STATUS_CHART_COLORS[value];
}
if (isRunFriendlyStatus(value)) {
return RUN_STATUS_CHART_COLORS[runStatusFromFriendlyTitle(value)];
}
return undefined;
}