From ad9d9483ec1a167cf4773c13501c8547619d6906 Mon Sep 17 00:00:00 2001 From: Yue Chao Qin Date: Tue, 17 Feb 2026 19:37:08 -0800 Subject: [PATCH] refactor: Use new flag in API to check pipeline run has ended --- src/api/types.gen.ts | 13 ++++ .../PipelineRun/RunDetails.test.tsx | 1 + .../PipelineRun/RunToolbar.test.tsx | 3 + src/components/PipelineRun/RunToolbar.tsx | 13 +--- src/hooks/usePipelineRunData.ts | 9 +-- src/routes/PipelineRun/PipelineRun.test.tsx | 1 + src/utils/executionStatus.test.ts | 70 ------------------- src/utils/executionStatus.ts | 31 -------- 8 files changed, 21 insertions(+), 120 deletions(-) diff --git a/src/api/types.gen.ts b/src/api/types.gen.ts index f86f2697f..913454cf2 100644 --- a/src/api/types.gen.ts +++ b/src/api/types.gen.ts @@ -582,6 +582,19 @@ export type GetGraphExecutionStateResponse = { [key: string]: number; }; }; + /** + * Summary + */ + summary: ExecutionStatusSummary; +}; + +/** + * ExecutionStatusSummary + */ +export type ExecutionStatusSummary = { + total_nodes: number; + ended_nodes: number; + has_ended: boolean; }; /** diff --git a/src/components/PipelineRun/RunDetails.test.tsx b/src/components/PipelineRun/RunDetails.test.tsx index b3206f15b..321f32778 100644 --- a/src/components/PipelineRun/RunDetails.test.tsx +++ b/src/components/PipelineRun/RunDetails.test.tsx @@ -91,6 +91,7 @@ describe("", () => { execution1: { SUCCEEDED: 1 }, execution2: { RUNNING: 1 }, }, + summary: { total_nodes: 2, ended_nodes: 1, has_ended: false }, }; const mockComponentSpec: ComponentSpec = { diff --git a/src/components/PipelineRun/RunToolbar.test.tsx b/src/components/PipelineRun/RunToolbar.test.tsx index d3c603686..11472b44b 100644 --- a/src/components/PipelineRun/RunToolbar.test.tsx +++ b/src/components/PipelineRun/RunToolbar.test.tsx @@ -73,6 +73,7 @@ describe("", () => { execution1: { SUCCEEDED: 1 }, execution2: { RUNNING: 1 }, }, + summary: { total_nodes: 2, ended_nodes: 1, has_ended: false }, }; const mockComponentSpec: ComponentSpec = { @@ -205,6 +206,7 @@ describe("", () => { execution1: { SUCCEEDED: 1 }, execution2: { CANCELLED: 1 }, }, + summary: { total_nodes: 2, ended_nodes: 2, has_ended: true }, }, }, rootExecutionId: "456", @@ -250,6 +252,7 @@ describe("", () => { execution1: { SUCCEEDED: 1 }, execution2: { CANCELLED: 1 }, }, + summary: { total_nodes: 2, ended_nodes: 2, has_ended: true }, }, }, rootExecutionId: "456", diff --git a/src/components/PipelineRun/RunToolbar.tsx b/src/components/PipelineRun/RunToolbar.tsx index 2f3eef50d..615ba899f 100644 --- a/src/components/PipelineRun/RunToolbar.tsx +++ b/src/components/PipelineRun/RunToolbar.tsx @@ -5,11 +5,6 @@ import { cn } from "@/lib/utils"; import { useComponentSpec } from "@/providers/ComponentSpecProvider"; import { useExecutionData } from "@/providers/ExecutionDataProvider"; import { extractCanonicalName } from "@/utils/canonicalPipelineName"; -import { - countInProgressFromStats, - flattenExecutionStatusStats, - isExecutionComplete, -} from "@/utils/executionStatus"; import { ViewYamlButton } from "../shared/Buttons/ViewYamlButton"; import { buildTakSpecShape } from "../shared/PipelineRunNameTemplate/types"; @@ -44,12 +39,8 @@ export const RunToolbar = () => { return null; } - const executionStatusStats = - metadata?.execution_status_stats ?? - flattenExecutionStatusStats(state.child_execution_status_stats); - - const isInProgress = countInProgressFromStats(executionStatusStats) > 0; - const isComplete = isExecutionComplete(executionStatusStats); + const isComplete = state.summary.has_ended; + const isInProgress = !isComplete; const isViewingSubgraph = currentSubgraphPath.length > 1; diff --git a/src/hooks/usePipelineRunData.ts b/src/hooks/usePipelineRunData.ts index 510c292ab..be4159354 100644 --- a/src/hooks/usePipelineRunData.ts +++ b/src/hooks/usePipelineRunData.ts @@ -7,10 +7,6 @@ import { fetchExecutionState, fetchPipelineRun, } from "@/services/executionService"; -import { - flattenExecutionStatusStats, - isExecutionComplete, -} from "@/utils/executionStatus"; const useRootExecutionId = (id: string) => { const { backendUrl } = useBackend(); @@ -79,10 +75,7 @@ export const usePipelineRunData = (id: string) => { if (!state) { return false; } - const stats = flattenExecutionStatusStats( - state.child_execution_status_stats, - ); - return isExecutionComplete(stats) ? false : 5000; + return state.summary?.has_ended ? false : 5000; } return false; }, diff --git a/src/routes/PipelineRun/PipelineRun.test.tsx b/src/routes/PipelineRun/PipelineRun.test.tsx index 14cb26f4f..b029d68e8 100644 --- a/src/routes/PipelineRun/PipelineRun.test.tsx +++ b/src/routes/PipelineRun/PipelineRun.test.tsx @@ -167,6 +167,7 @@ describe("", () => { "execution-1": { SUCCEEDED: 1 }, "execution-2": { RUNNING: 1 }, }, + summary: { total_nodes: 2, ended_nodes: 1, has_ended: false }, }; beforeEach(() => { diff --git a/src/utils/executionStatus.test.ts b/src/utils/executionStatus.test.ts index f4def9d8d..cfa3875de 100644 --- a/src/utils/executionStatus.test.ts +++ b/src/utils/executionStatus.test.ts @@ -2,11 +2,9 @@ import { describe, expect, test } from "vitest"; import type { GetGraphExecutionStateResponse } from "@/api/types.gen"; import { - countInProgressFromStats, flattenExecutionStatusStats, getExecutionStatusLabel, getOverallExecutionStatusFromStats, - isExecutionComplete, } from "@/utils/executionStatus"; type ChildExecutionStatusStats = @@ -142,71 +140,3 @@ describe("getOverallExecutionStatusFromStats()", () => { ).toBe("UNINITIALIZED"); }); }); - -describe("countInProgressFromStats()", () => { - test("counts all in-progress statuses", () => { - expect( - countInProgressFromStats({ - RUNNING: 2, - PENDING: 1, - QUEUED: 3, - SUCCEEDED: 10, - }), - ).toBe(6); - }); - - test("returns 0 when no in-progress statuses", () => { - expect( - countInProgressFromStats({ - SUCCEEDED: 5, - FAILED: 2, - }), - ).toBe(0); - }); - - test("counts all in-progress status types", () => { - expect( - countInProgressFromStats({ - RUNNING: 1, - PENDING: 1, - QUEUED: 1, - WAITING_FOR_UPSTREAM: 1, - CANCELLING: 1, - UNINITIALIZED: 1, - }), - ).toBe(6); - }); -}); - -describe("isExecutionComplete()", () => { - test("returns true when all tasks are in terminal states", () => { - expect( - isExecutionComplete({ - SUCCEEDED: 5, - FAILED: 2, - }), - ).toBe(true); - }); - - test("returns false when any tasks are in progress", () => { - expect( - isExecutionComplete({ - SUCCEEDED: 5, - RUNNING: 1, - }), - ).toBe(false); - }); - - test("returns false for empty stats", () => { - expect(isExecutionComplete({})).toBe(false); - }); - - test("returns true for cancelled/skipped executions", () => { - expect( - isExecutionComplete({ - CANCELLED: 3, - SKIPPED: 2, - }), - ).toBe(true); - }); -}); diff --git a/src/utils/executionStatus.ts b/src/utils/executionStatus.ts index 9f4c41ad4..5e80cf49e 100644 --- a/src/utils/executionStatus.ts +++ b/src/utils/executionStatus.ts @@ -42,18 +42,6 @@ export const EXECUTION_STATUS_BG_COLORS: Record = { UNINITIALIZED: "bg-yellow-400", }; -/** - * Statuses considered "in progress" (not terminal). - */ -const IN_PROGRESS_STATUSES = new Set([ - "RUNNING", - "PENDING", - "QUEUED", - "WAITING_FOR_UPSTREAM", - "CANCELLING", - "UNINITIALIZED", -]); - /** * Priority order for determining overall/aggregate execution status. * Higher priority statuses appear first — if any task has SYSTEM_ERROR, @@ -134,22 +122,3 @@ export function getOverallExecutionStatusFromStats( const firstNonZero = Object.entries(stats).find(([, c]) => (c ?? 0) > 0); return firstNonZero?.[0]; } - -/** - * Count the number of in-progress tasks from execution stats. - */ -export function countInProgressFromStats(stats: ExecutionStatusStats): number { - let count = 0; - for (const status of IN_PROGRESS_STATUSES) { - count += stats[status] ?? 0; - } - return count; -} - -/** - * Check if execution is complete based on stats (no in-progress tasks and at least one task). - */ -export function isExecutionComplete(stats: ExecutionStatusStats): boolean { - const total = Object.values(stats).reduce((sum, c) => sum + (c ?? 0), 0); - return total > 0 && countInProgressFromStats(stats) === 0; -}