diff --git a/ui-v2/public/cancelled.svg b/ui-v2/public/cancelled.svg new file mode 100644 index 000000000000..ee3ca39fdb8c --- /dev/null +++ b/ui-v2/public/cancelled.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-v2/public/completed.svg b/ui-v2/public/completed.svg new file mode 100644 index 000000000000..e777b55eaa58 --- /dev/null +++ b/ui-v2/public/completed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-v2/public/crashed.svg b/ui-v2/public/crashed.svg new file mode 100644 index 000000000000..0ea5a9c140c1 --- /dev/null +++ b/ui-v2/public/crashed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-v2/public/failed.svg b/ui-v2/public/failed.svg new file mode 100644 index 000000000000..38a5b0a7fb24 --- /dev/null +++ b/ui-v2/public/failed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-v2/public/pending.svg b/ui-v2/public/pending.svg new file mode 100644 index 000000000000..fa3b0227c7e1 --- /dev/null +++ b/ui-v2/public/pending.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-v2/public/running.svg b/ui-v2/public/running.svg new file mode 100644 index 000000000000..a4b6ecffa860 --- /dev/null +++ b/ui-v2/public/running.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-v2/public/scheduled.svg b/ui-v2/public/scheduled.svg new file mode 100644 index 000000000000..da7aa3034d66 --- /dev/null +++ b/ui-v2/public/scheduled.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui-v2/src/components/task-runs/task-run-details-page/index.tsx b/ui-v2/src/components/task-runs/task-run-details-page/index.tsx index e1f316227fd3..ebf9c01509d4 100644 --- a/ui-v2/src/components/task-runs/task-run-details-page/index.tsx +++ b/ui-v2/src/components/task-runs/task-run-details-page/index.tsx @@ -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; @@ -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); @@ -183,7 +191,11 @@ const Header = ({ - + Runs @@ -365,6 +377,10 @@ const ArtifactsSkeleton = () => { const TaskInputs = ({ taskRun }: { taskRun: TaskRun }) => { return ( - + ); }; diff --git a/ui-v2/src/components/task-runs/task-run-details/task-run-details.tsx b/ui-v2/src/components/task-runs/task-run-details/task-run-details.tsx index d58668d00883..646fcc4e01b6 100644 --- a/ui-v2/src/components/task-runs/task-run-details/task-run-details.tsx +++ b/ui-v2/src/components/task-runs/task-run-details/task-run-details.tsx @@ -42,7 +42,7 @@ export const TaskRunDetails = ({ taskRun }: TaskRunDetailsProps) => { return (
- {taskRun.flow_run_name && taskRun.flow_run_id && ( + {taskRun.flow_run_id ? (
Flow Run
@@ -56,85 +56,84 @@ export const TaskRunDetails = ({ taskRun }: TaskRunDetailsProps) => {
- )} - - {taskRun.start_time && ( + ) : (
-
Start Time
-
{formatTaskDate(taskRun.start_time)}
+
Flow Run
+
None
)} - {taskRun.estimated_run_time !== null && - taskRun.estimated_run_time !== undefined && ( -
-
Duration
-
- - - {formatTaskDuration(taskRun.total_run_time)} - -
-
- )} - - {taskRun.run_count !== null && taskRun.run_count !== undefined && ( -
-
Run Count
-
{taskRun.run_count.toString()}
-
- )} +
+
Start Time
+
+ {taskRun.start_time ? formatTaskDate(taskRun.start_time) : "None"} +
+
- {taskRun.estimated_run_time !== null && - taskRun.estimated_run_time !== undefined && ( -
-
Estimated Run Time
-
- {formatTaskDuration(taskRun.estimated_run_time)} -
-
- )} - - {taskRun.created && ( -
-
Created
-
{formatTaskDate(taskRun.created)}
-
- )} +
+
Duration
+
+ + + {taskRun.total_run_time !== null && + taskRun.total_run_time !== undefined + ? formatTaskDuration(taskRun.total_run_time) + : "None"} + +
+
- {taskRun.updated && ( -
-
Last Updated
-
{formatTaskDate(taskRun.updated)}
-
- )} +
+
Run Count
+
{taskRun.run_count || 0}
+
- {taskRun.cache_key && ( -
-
Cache Key
-
{taskRun.cache_key}
-
- )} +
+
Estimated Run Time
+
+ {taskRun.estimated_run_time !== null && + taskRun.estimated_run_time !== undefined + ? formatTaskDuration(taskRun.estimated_run_time) + : "None"} +
+
- {taskRun.cache_expiration && ( -
-
Cache Expiration
-
- {formatTaskDate(taskRun.cache_expiration)} -
-
- )} +
+
Created
+
+ {taskRun.created ? formatTaskDate(taskRun.created) : "None"} +
+
- {taskRun.dynamic_key && ( -
-
Dynamic Key
-
{taskRun.dynamic_key}
-
- )} +
+
Last Updated
+
+ {taskRun.updated ? formatTaskDate(taskRun.updated) : "None"} +
+
+ +
+
Cache Key
+
{taskRun.cache_key || "None"}
+
+ +
+
Cache Expiration
+
+ {taskRun.cache_expiration + ? formatTaskDate(taskRun.cache_expiration) + : "None"} +
+
-
Task Run ID
-
{taskRun.id}
+
Dynamic Key
+
{taskRun.dynamic_key || "None"}
+
+ +
+
Task Run ID
+
{taskRun.id}
{resultArtifact?.description && ( @@ -159,30 +158,29 @@ export const TaskRunDetails = ({ taskRun }: TaskRunDetailsProps) => {
-
Retries
+
Retries
- {taskRun.empirical_policy?.retries?.toString() || "0"} + {taskRun.empirical_policy?.retries ?? "0"}
- {typeof taskRun.empirical_policy?.retry_delay === "number" && ( -
-
Retry Delay
-
- {formatTaskDuration(taskRun.empirical_policy.retry_delay)} -
-
- )} +
+
Retry Delay
+
+ {typeof taskRun.empirical_policy?.retry_delay === "number" + ? formatTaskDuration(taskRun.empirical_policy.retry_delay) + : "None"} +
+
- {taskRun.empirical_policy?.retry_jitter_factor !== null && - taskRun.empirical_policy?.retry_jitter_factor !== undefined && ( -
-
Retry Jitter Factor
-
- {taskRun.empirical_policy.retry_jitter_factor.toString()} -
-
- )} +
+
Retry Jitter Factor
+
+ {taskRun.empirical_policy?.retry_jitter_factor + ? taskRun.empirical_policy.retry_jitter_factor.toString() + : "None"} +
+
Tags
diff --git a/ui-v2/src/hooks/use-state-favicon.ts b/ui-v2/src/hooks/use-state-favicon.ts new file mode 100644 index 000000000000..0047dd5d1db4 --- /dev/null +++ b/ui-v2/src/hooks/use-state-favicon.ts @@ -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]); +}