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
4 changes: 4 additions & 0 deletions ui-v2/public/cancelled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui-v2/public/completed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui-v2/public/crashed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui-v2/public/failed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui-v2/public/pending.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui-v2/public/running.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui-v2/public/scheduled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 18 additions & 2 deletions ui-v2/src/components/task-runs/task-run-details-page/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { usePageTitle } from "@/hooks/use-page-title";
import { useStateFavicon } from "@/hooks/use-state-favicon";

type TaskRunDetailsPageProps = {
id: string;
Expand All @@ -67,6 +69,12 @@ export const TaskRunDetailsPage = ({
const { deleteTaskRun } = useDeleteTaskRun();
const { navigate } = useRouter();

// Set page title based on task run name
usePageTitle(taskRun?.name ? `Task Run: ${taskRun.name}` : "Task Run");

// Set favicon based on task run state
useStateFavicon(taskRun?.state_type);

useEffect(() => {
if (taskRun.state_type === "RUNNING" || taskRun.state_type === "PENDING") {
setRefetchInterval(5000);
Expand Down Expand Up @@ -183,7 +191,11 @@ const Header = ({
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink to="/runs" className="text-xl font-semibold">
<BreadcrumbLink
to="/runs"
search={{ tab: "task-runs" }}
className="text-xl font-semibold"
>
Runs
</BreadcrumbLink>
</BreadcrumbItem>
Expand Down Expand Up @@ -365,6 +377,10 @@ const ArtifactsSkeleton = () => {

const TaskInputs = ({ taskRun }: { taskRun: TaskRun }) => {
return (
<JsonInput value={JSON.stringify(taskRun.task_inputs, null, 2)} disabled />
<JsonInput
value={JSON.stringify(taskRun.task_inputs, null, 2)}
disabled
copy
/>
);
};
174 changes: 86 additions & 88 deletions ui-v2/src/components/task-runs/task-run-details/task-run-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const TaskRunDetails = ({ taskRun }: TaskRunDetailsProps) => {

return (
<div className="flex flex-col gap-2 p-2 text-xs">
{taskRun.flow_run_name && taskRun.flow_run_id && (
{taskRun.flow_run_id ? (
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Flow Run</dt>
<dd>
Expand All @@ -56,85 +56,84 @@ export const TaskRunDetails = ({ taskRun }: TaskRunDetailsProps) => {
</Link>
</dd>
</dl>
)}

{taskRun.start_time && (
) : (
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Start Time</dt>
<dd className="font-mono">{formatTaskDate(taskRun.start_time)}</dd>
<dt className="text-gray-500">Flow Run</dt>
<dd>None</dd>
</dl>
)}

{taskRun.estimated_run_time !== null &&
taskRun.estimated_run_time !== undefined && (
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Duration</dt>
<dd className="">
<span className="flex items-center">
<Icon id="Clock" className="mr-1 size-4" />
{formatTaskDuration(taskRun.total_run_time)}
</span>
</dd>
</dl>
)}

{taskRun.run_count !== null && taskRun.run_count !== undefined && (
<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Run Count</dt>
<dd className="">{taskRun.run_count.toString()}</dd>
</dl>
)}
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Start Time</dt>
<dd className="font-mono">
{taskRun.start_time ? formatTaskDate(taskRun.start_time) : "None"}
</dd>
</dl>

{taskRun.estimated_run_time !== null &&
taskRun.estimated_run_time !== undefined && (
<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Estimated Run Time</dt>
<dd className="">
{formatTaskDuration(taskRun.estimated_run_time)}
</dd>
</dl>
)}

{taskRun.created && (
<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Created</dt>
<dd className="font-mono ">{formatTaskDate(taskRun.created)}</dd>
</dl>
)}
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Duration</dt>
<dd className="">
<span className="flex items-center">
<Icon id="Clock" className="mr-1 size-4" />
{taskRun.total_run_time !== null &&
taskRun.total_run_time !== undefined
? formatTaskDuration(taskRun.total_run_time)
: "None"}
</span>
</dd>
</dl>

{taskRun.updated && (
<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Last Updated</dt>
<dd className="font-mono ">{formatTaskDate(taskRun.updated)}</dd>
</dl>
)}
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Run Count</dt>
<dd className="">{taskRun.run_count || 0}</dd>
</dl>

{taskRun.cache_key && (
<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Cache Key</dt>
<dd className="font-mono ">{taskRun.cache_key}</dd>
</dl>
)}
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Estimated Run Time</dt>
<dd className="">
{taskRun.estimated_run_time !== null &&
taskRun.estimated_run_time !== undefined
? formatTaskDuration(taskRun.estimated_run_time)
: "None"}
</dd>
</dl>

{taskRun.cache_expiration && (
<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Cache Expiration</dt>
<dd className="font-mono ">
{formatTaskDate(taskRun.cache_expiration)}
</dd>
</dl>
)}
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Created</dt>
<dd className="font-mono">
{taskRun.created ? formatTaskDate(taskRun.created) : "None"}
</dd>
</dl>

{taskRun.dynamic_key && (
<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Dynamic Key</dt>
<dd className="font-mono ">{taskRun.dynamic_key}</dd>
</dl>
)}
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Last Updated</dt>
<dd className="font-mono">
{taskRun.updated ? formatTaskDate(taskRun.updated) : "None"}
</dd>
</dl>

<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Cache Key</dt>
<dd className="font-mono">{taskRun.cache_key || "None"}</dd>
</dl>

<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Cache Expiration</dt>
<dd className="font-mono">
{taskRun.cache_expiration
? formatTaskDate(taskRun.cache_expiration)
: "None"}
</dd>
</dl>

<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Task Run ID</dt>
<dd className="font-mono ">{taskRun.id}</dd>
<dt className="text-gray-500">Dynamic Key</dt>
<dd className="font-mono">{taskRun.dynamic_key || "None"}</dd>
</dl>

<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Task Run ID</dt>
<dd className="font-mono">{taskRun.id}</dd>
</dl>

{resultArtifact?.description && (
Expand All @@ -159,30 +158,29 @@ export const TaskRunDetails = ({ taskRun }: TaskRunDetailsProps) => {
</dl>

<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Retries</dt>
<dt className="text-gray-500">Retries</dt>
<dd className="">
{taskRun.empirical_policy?.retries?.toString() || "0"}
{taskRun.empirical_policy?.retries ?? "0"}
</dd>
</dl>

{typeof taskRun.empirical_policy?.retry_delay === "number" && (
<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Retry Delay</dt>
<dd className="">
{formatTaskDuration(taskRun.empirical_policy.retry_delay)}
</dd>
</dl>
)}
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Retry Delay</dt>
<dd className="">
{typeof taskRun.empirical_policy?.retry_delay === "number"
? formatTaskDuration(taskRun.empirical_policy.retry_delay)
: "None"}
</dd>
</dl>

{taskRun.empirical_policy?.retry_jitter_factor !== null &&
taskRun.empirical_policy?.retry_jitter_factor !== undefined && (
<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Retry Jitter Factor</dt>
<dd className="">
{taskRun.empirical_policy.retry_jitter_factor.toString()}
</dd>
</dl>
)}
<dl className="flex flex-col gap-1 mb-2">
<dt className="text-gray-500">Retry Jitter Factor</dt>
<dd className="">
{taskRun.empirical_policy?.retry_jitter_factor
? taskRun.empirical_policy.retry_jitter_factor.toString()
: "None"}
</dd>
</dl>

<dl className="flex flex-col gap-1 mb-2">
<dt className=" text-gray-500">Tags</dt>
Expand Down
66 changes: 66 additions & 0 deletions ui-v2/src/hooks/use-state-favicon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useEffect } from "react";

type StateType =
| "SCHEDULED"
| "PENDING"
| "RUNNING"
| "COMPLETED"
| "FAILED"
| "CANCELLED"
| "CANCELLING"
| "CRASHED"
| "PAUSED";

function getPreferredColorScheme(): "dark" | "light" | "no-preference" {
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
return "dark";
}
if (window.matchMedia("(prefers-color-scheme: light)").matches) {
return "light";
}
return "no-preference";
}

/**
* A hook that sets the browser favicon based on the provided state type.
* Resets the favicon to the default when the component unmounts.
*
* @param stateType - The state type to display in the favicon (e.g., "COMPLETED", "FAILED")
* @returns void
*
* @example
* ```tsx
* // Set favicon based on task run state
* useStateFavicon(taskRun.state_type);
* ```
*/
export function useStateFavicon(stateType: StateType | null | undefined): void {
useEffect(() => {
const colorScheme = getPreferredColorScheme();
const favicon16 =
colorScheme === "dark"
? document.getElementById("favicon-16-dark")
: document.getElementById("favicon-16");
const favicon32 =
colorScheme === "dark"
? document.getElementById("favicon-32-dark")
: document.getElementById("favicon-32");

if (stateType) {
const faviconPath = `/${stateType.toLowerCase()}.svg`;
favicon16?.setAttribute("href", faviconPath);
favicon32?.setAttribute("href", faviconPath);
}

return () => {
// Reset to default favicon on unmount
if (colorScheme === "dark") {
favicon16?.setAttribute("href", "/favicon-16x16-dark.png");
favicon32?.setAttribute("href", "/favicon-32x32-dark.png");
} else {
favicon16?.setAttribute("href", "/favicon-16x16.png");
favicon32?.setAttribute("href", "/favicon-32x32.png");
}
};
}, [stateType]);
}
Loading