Skip to content

Commit 9f6b924

Browse files
committed
unify status
1 parent be4d79e commit 9f6b924

File tree

7 files changed

+234
-37
lines changed

7 files changed

+234
-37
lines changed

src/components/shared/ReactFlow/FlowCanvas/TaskNode/StatusIndicator.tsx

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
CheckCircleIcon,
33
CircleDashedIcon,
4+
CircleMinusIcon,
45
ClockIcon,
56
Loader2Icon,
67
XCircleIcon,
@@ -10,18 +11,19 @@ import type { ContainerExecutionStatus } from "@/api/types.gen";
1011
import { Icon } from "@/components/ui/icon";
1112
import { QuickTooltip } from "@/components/ui/tooltip";
1213
import { cn } from "@/lib/utils";
13-
import type { RunStatus } from "@/types/pipelineRun";
14+
import { getExecutionStatusLabel } from "@/utils/executionStatus";
1415

1516
type StatusIndicatorProps = {
16-
status: ContainerExecutionStatus | RunStatus;
17+
status: ContainerExecutionStatus;
1718
disabledCache?: boolean;
1819
};
1920

2021
export const StatusIndicator = ({
2122
status,
2223
disabledCache = false,
2324
}: StatusIndicatorProps) => {
24-
const { style, text, icon } = getStatusMetadata(status);
25+
const { style, icon } = getStatusMetadata(status);
26+
const text = getExecutionStatusLabel(status);
2527

2628
return (
2729
<div className="absolute -z-1 -top-5 left-0 flex items-start">
@@ -46,70 +48,55 @@ export const StatusIndicator = ({
4648
);
4749
};
4850

49-
const getStatusMetadata = (status: ContainerExecutionStatus | RunStatus) => {
51+
const getStatusMetadata = (status: ContainerExecutionStatus) => {
5052
switch (status) {
5153
case "SUCCEEDED":
5254
return {
5355
style: "bg-emerald-500",
54-
text: "Succeeded",
5556
icon: <CheckCircleIcon className="w-2 h-2" />,
5657
};
5758
case "FAILED":
5859
case "SYSTEM_ERROR":
5960
case "INVALID":
6061
return {
6162
style: "bg-red-700",
62-
text: "Failed",
6363
icon: <XCircleIcon className="w-2 h-2" />,
6464
};
6565
case "RUNNING":
6666
return {
6767
style: "bg-sky-500",
68-
text: "Running",
6968
icon: <Loader2Icon className="w-2 h-2 animate-spin" />,
7069
};
7170
case "PENDING":
71+
case "UNINITIALIZED":
7272
return {
7373
style: "bg-yellow-500",
74-
text: "Pending",
7574
icon: <ClockIcon className="w-2 h-2 animate-spin duration-2000" />,
7675
};
7776
case "CANCELLING":
7877
case "CANCELLED":
7978
return {
8079
style: "bg-gray-800",
81-
text: status === "CANCELLING" ? "Cancelling" : "Cancelled",
8280
icon: <XCircleIcon className="w-2 h-2" />,
8381
};
8482
case "SKIPPED":
8583
return {
8684
style: "bg-slate-400",
87-
text: "Skipped",
88-
icon: <XCircleIcon className="w-2 h-2" />,
85+
icon: <CircleMinusIcon className="w-2 h-2" />,
8986
};
9087
case "QUEUED":
9188
return {
9289
style: "bg-yellow-500",
93-
text: "Queued",
9490
icon: <ClockIcon className="w-2 h-2 animate-spin duration-2000" />,
9591
};
9692
case "WAITING_FOR_UPSTREAM":
9793
return {
9894
style: "bg-slate-500",
99-
text: "Waiting for upstream",
100-
icon: <ClockIcon className="w-2 h-2 animate-spin duration-2000" />,
101-
};
102-
case "WAITING":
103-
case "UNINITIALIZED":
104-
return {
105-
style: "bg-yellow-500",
106-
text: "Pending",
10795
icon: <ClockIcon className="w-2 h-2 animate-spin duration-2000" />,
10896
};
10997
default:
11098
return {
11199
style: "bg-slate-300",
112-
text: "Unknown",
113100
icon: <CircleDashedIcon className="w-2 h-2" />,
114101
};
115102
}

src/components/shared/ReactFlow/FlowCanvas/TaskNode/TaskNode.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { type NodeProps } from "@xyflow/react";
22
import { memo, useMemo } from "react";
33

4+
import type { ContainerExecutionStatus } from "@/api/types.gen";
45
import { useExecutionDataOptional } from "@/providers/ExecutionDataProvider";
56
import { TaskNodeProvider } from "@/providers/TaskNodeProvider";
6-
import { getRunStatus } from "@/services/executionService";
77
import type { TaskNodeData } from "@/types/taskNode";
8+
import { deriveExecutionStatusFromStats } from "@/utils/executionStatus";
89
import { isCacheDisabled } from "@/utils/cache";
910

1011
import { StatusIndicator } from "./StatusIndicator";
@@ -15,16 +16,20 @@ const TaskNode = ({ data, selected }: NodeProps) => {
1516

1617
const typedData = useMemo(() => data as TaskNodeData, [data]);
1718

18-
const status = useMemo(() => {
19+
const status: ContainerExecutionStatus | undefined = useMemo(() => {
1920
const taskId = typedData.taskId ?? "";
20-
const statusCounts = executionData?.taskStatusCountsMap.get(taskId);
21-
22-
if (!statusCounts) {
23-
return undefined;
24-
}
25-
26-
return getRunStatus(statusCounts);
27-
}, [executionData?.taskStatusCountsMap, typedData.taskId]);
21+
const executionId = executionData?.details?.child_task_execution_ids?.[taskId];
22+
if (!executionId) return undefined;
23+
24+
const statusStats =
25+
executionData?.state?.child_execution_status_stats?.[executionId];
26+
27+
return deriveExecutionStatusFromStats(statusStats);
28+
}, [
29+
executionData?.details?.child_task_execution_ids,
30+
executionData?.state?.child_execution_status_stats,
31+
typedData.taskId,
32+
]);
2833

2934
const disabledCache = isCacheDisabled(typedData.taskSpec);
3035

src/components/shared/Status/StatusIcon.tsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
TooltipContent,
1414
TooltipTrigger,
1515
} from "@/components/ui/tooltip";
16+
import { getExecutionStatusLabel, isContainerExecutionStatus } from "@/utils/executionStatus";
1617

1718
const StatusIcon = ({
1819
status,
@@ -23,9 +24,11 @@ const StatusIcon = ({
2324
tooltip?: boolean;
2425
label?: "run" | "task" | "pipeline";
2526
}) => {
27+
const statusLabel = getStatusLabel(status);
28+
2629
if (tooltip) {
2730
const capitalizedLabel = label.charAt(0).toUpperCase() + label.slice(1);
28-
const tooltipText = `${capitalizedLabel} ${status?.toLowerCase() ?? "unknown"}`;
31+
const tooltipText = `${capitalizedLabel} ${statusLabel.toLowerCase()}`;
2932
return (
3033
<Tooltip>
3134
<TooltipTrigger asChild>
@@ -43,6 +46,34 @@ const StatusIcon = ({
4346
return <Icon status={status} />;
4447
};
4548

49+
const getStatusLabel = (status: string | undefined) => {
50+
if (!status) return "Unknown";
51+
52+
if (isContainerExecutionStatus(status)) {
53+
return getExecutionStatusLabel(status);
54+
}
55+
56+
// Aggregate / run-level statuses (derived via getRunStatus)
57+
switch (status) {
58+
case "SUCCEEDED":
59+
return "Succeeded";
60+
case "FAILED":
61+
return "Failed";
62+
case "RUNNING":
63+
return "Running";
64+
case "WAITING":
65+
return "Waiting";
66+
case "CANCELLED":
67+
return "Cancelled";
68+
case "SKIPPED":
69+
return "Skipped";
70+
case "UNKNOWN":
71+
return "Unknown";
72+
default:
73+
return status;
74+
}
75+
};
76+
4677
const Icon = ({ status }: { status?: string }) => {
4778
switch (status) {
4879
case "SUCCEEDED":

src/components/shared/TaskDetails/Details.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
isGithubUrl,
3030
} from "@/utils/URL";
3131
import copyToYaml from "@/utils/yaml";
32+
import { getExecutionStatusLabel, isContainerExecutionStatus } from "@/utils/executionStatus";
3233

3334
import { ExecutionDetails } from "../ExecutionDetails/ExecutionDetails";
3435

@@ -157,10 +158,10 @@ const TaskDetails = ({
157158
{status && (
158159
<div className="flex flex-col px-3 py-2">
159160
<div className="shrink-0 font-medium text-sm text-gray-700 mb-1">
160-
Run Status
161+
Execution Status
161162
</div>
162163
<div className="text-xs text-gray-600 wrap-break-word whitespace-pre-wrap">
163-
{status}
164+
{isContainerExecutionStatus(status) ? getExecutionStatusLabel(status) : status}
164165
</div>
165166
</div>
166167
)}

src/providers/TaskNodeProvider.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import useComponentFromUrl from "@/hooks/useComponentFromUrl";
66
import { useTaskNodeDimensions } from "@/hooks/useTaskNodeDimensions";
77
import useToastNotification from "@/hooks/useToastNotification";
88
import type { Annotations } from "@/types/annotations";
9-
import type { RunStatus } from "@/types/pipelineRun";
109
import {
1110
DEFAULT_TASK_NODE_CALLBACKS,
1211
type TaskNodeData,
@@ -37,7 +36,7 @@ type TaskNodeState = Readonly<{
3736
readOnly: boolean;
3837
disabled: boolean;
3938
connectable: boolean;
40-
status?: ContainerExecutionStatus | RunStatus;
39+
status?: ContainerExecutionStatus;
4140
isCustomComponent: boolean;
4241
dimensions: TaskNodeDimensions;
4342
}>;
@@ -55,7 +54,7 @@ type TaskNodeProviderProps = {
5554
children: ReactNode;
5655
data: TaskNodeData;
5756
selected: boolean;
58-
status?: ContainerExecutionStatus | RunStatus;
57+
status?: ContainerExecutionStatus;
5958
};
6059

6160
export type TaskNodeContextType = {

src/utils/executionStatus.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import {
4+
deriveExecutionStatusFromStats,
5+
getExecutionStatusLabel,
6+
} from "@/utils/executionStatus";
7+
8+
describe("getExecutionStatusLabel", () => {
9+
it("maps WAITING_FOR_UPSTREAM to the real name", () => {
10+
expect(getExecutionStatusLabel("WAITING_FOR_UPSTREAM")).toBe(
11+
"Waiting for upstream",
12+
);
13+
});
14+
15+
it("maps QUEUED to the real name", () => {
16+
expect(getExecutionStatusLabel("QUEUED")).toBe("Queued");
17+
});
18+
19+
it("maps SYSTEM_ERROR to Failed (per current decision)", () => {
20+
expect(getExecutionStatusLabel("SYSTEM_ERROR")).toBe("Failed");
21+
});
22+
});
23+
24+
describe("deriveExecutionStatusFromStats", () => {
25+
it("returns undefined when no stats are present", () => {
26+
expect(deriveExecutionStatusFromStats(undefined)).toBeUndefined();
27+
});
28+
29+
it("returns RUNNING if any child is running", () => {
30+
expect(deriveExecutionStatusFromStats({ RUNNING: 1, SUCCEEDED: 3 })).toBe(
31+
"RUNNING",
32+
);
33+
});
34+
35+
it("returns WAITING_FOR_UPSTREAM when present and nothing higher priority exists", () => {
36+
expect(deriveExecutionStatusFromStats({ WAITING_FOR_UPSTREAM: 2 })).toBe(
37+
"WAITING_FOR_UPSTREAM",
38+
);
39+
});
40+
41+
it("returns FAILED when SYSTEM_ERROR is present", () => {
42+
expect(deriveExecutionStatusFromStats({ SYSTEM_ERROR: 1 })).toBe("FAILED");
43+
});
44+
45+
it("returns SUCCEEDED when all known nodes are succeeded", () => {
46+
expect(deriveExecutionStatusFromStats({ SUCCEEDED: 4 })).toBe("SUCCEEDED");
47+
});
48+
});
49+
50+

0 commit comments

Comments
 (0)