Skip to content
Closed
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: Falsy check hides valid retry_jitter_factor value of 0

The expression taskRun.empirical_policy?.retry_jitter_factor ? ... : "None" uses a truthy check. If retry_jitter_factor is 0 (a valid numeric value), it will be treated as falsy and display "None" instead of "0". The original code used explicit !== null && !== undefined checks to avoid this issue.

This should use nullish coalescing or explicit null/undefined checks instead:

{taskRun.empirical_policy?.retry_jitter_factor != null
    ? taskRun.empirical_policy.retry_jitter_factor.toString()
    : "None"}

Was this helpful? React with 👍 / 👎

Suggested change
{taskRun.empirical_policy?.retry_jitter_factor
{taskRun.empirical_policy?.retry_jitter_factor != null
? taskRun.empirical_policy.retry_jitter_factor.toString()
: "None"}
  • Apply suggested fix

? 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) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug: Missing SVG favicons for CANCELLING and PAUSED states

The StateType union in use-state-favicon.ts includes "CANCELLING" and "PAUSED", but no corresponding SVG files (cancelling.svg, paused.svg) were added to ui-v2/public/. When a task run is in either of these states, the hook will set the favicon href to /cancelling.svg or /paused.svg, resulting in a broken favicon (404).

Either add the missing SVG files, or map these states to existing icons (e.g., CANCELLING → cancelled.svg, PAUSED → pending.svg).

Was this helpful? React with 👍 / 👎

  • Apply suggested fix

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