fix: prioritize follower taskrun visibility

This commit is contained in:
Codex
2026-07-03 20:17:21 +00:00
parent b3c039c8ca
commit 19d270b44b
5 changed files with 111 additions and 25 deletions
+32 -1
View File
@@ -308,10 +308,41 @@ function compactTimings(timings) {
startedAt: stringOrNull(value.startedAt),
finishedAt: stringOrNull(value.finishedAt),
overBudget: typeof value.overBudget === "boolean" ? value.overBudget : null,
stages: arrayRecords(value.stages).slice(0, maxTimingStages).map(compactStageTiming),
stages: prioritizedStageTimings(arrayRecords(value.stages)).slice(0, maxTimingStages).map(compactStageTiming),
};
}
function prioritizedStageTimings(stages) {
const priority = [];
const rest = [];
for (const stage of stages) {
if (isPriorityTaskStage(stage)) priority.push(stage);
else rest.push(stage);
}
const seen = new Set();
const out = [];
for (const stage of [...priority, ...rest]) {
const key = [
stringOrNull(stage.stage),
stringOrNull(stage.status),
stringOrNull(stage.source),
stringOrNull(stage.object),
].filter((item) => item !== null).join("|");
if (seen.has(key)) continue;
seen.add(key);
out.push(stage);
}
return out;
}
function isPriorityTaskStage(stage) {
const name = stringOrNull(stage.stage) || "";
if (!name.startsWith("task:")) return false;
const status = stringOrNull(stage.status) || "";
const seconds = numberOrNull(stage.seconds);
return status.startsWith("failed") || status === "running" || (seconds !== null && seconds > 60);
}
function compactStageTiming(stage) {
return {
stage: stringOrNull(stage.stage),
+2 -4
View File
@@ -27,6 +27,7 @@ import { runNativeHwlabControlPlaneRefresh } from "./cicd-hwlab-refresh";
import { nativeCicdScriptLoadShell, readNativeObjectBundle } from "./cicd-native-bundle";
import { runNativeK8sJob, runNativeTektonPipelineRun } from "./cicd-native";
import { argoApplicationReady, nativeArgoSummary, nativeGitMirrorReady, nativeGitMirrorRequired, nativeGitMirrorSummary, nativePipelineRunSummary, nativeRuntimeSummary, pipelineRunSucceeded, runtimeTargetShaFromWorkloads, runtimeWorkloadsReady } from "./cicd-native-summary";
import { prioritizedTaskRunItems } from "./cicd-taskruns";
import type { AdapterSummary, BranchFollowerAction, BranchFollowerDebugStep, BranchFollowerPhase, BranchFollowerRegistry, ControllerSpec, FollowerSpec, FollowerState, K8sFollowerStateRead, K8sStateRead, NativeCloseoutWaitResult, NativeK8sJobResult, NativeStatusSpec, NativeWorkloadSpec, OutputMode, ParsedOptions, StageTiming, TriggerResult } from "./cicd-types";
import {
arrayField,
@@ -2279,10 +2280,7 @@ function stageTimingsFromNativePayload(payload: Record<string, unknown> | null):
stages.push(stageTiming("pipelinerun", status, numberOrNull(tekton.durationSeconds), null, "tekton", stringOrNull(tekton.name)));
}
const taskRuns = asOptionalRecord(payload.taskRuns);
const taskRunItems = taskRuns !== null && Array.isArray(taskRuns.items) ? taskRuns.items : [];
for (const item of taskRunItems) {
const record = asOptionalRecord(item);
if (record === null) continue;
for (const record of taskRuns === null ? [] : prioritizedTaskRunItems(taskRuns)) {
const name = stringOrNull(record.pipelineTask) ?? stringOrNull(record.name) ?? "unknown";
const status = record.status === "True" ? "succeeded" : record.status === "False" ? `failed:${stringOrNull(record.reason) ?? "unknown"}` : "running";
stages.push(stageTiming(`task:${name}`, status, numberOrNull(record.durationSeconds), null, "tekton-taskrun", stringOrNull(record.name)));
+1 -20
View File
@@ -6,6 +6,7 @@ import { runCommand, type CommandResult } from "./command";
import { repoRoot, rootPath } from "./config";
import type { AdapterSummary, BranchFollowerDebugStep, BranchFollowerRegistry, FollowerSpec, FollowerState, K8sStateRead, ParsedOptions } from "./cicd-types";
import { renderControllerDebugJob, waitForJobShell } from "./cicd-controller-render";
import { taskRunItems } from "./cicd-taskruns";
import { redactText, shQuote } from "./platform-infra-ops-library";
type KubeScriptRunner = (registry: BranchFollowerRegistry, options: ParsedOptions, script: string, input: string, timeoutMs: number) => CommandResult;
@@ -423,26 +424,6 @@ function compactStatusGates(payload: Record<string, unknown> | null): Record<str
};
}
function taskRunItems(taskRuns: Record<string, unknown>, mode: "failed" | "active" | "slow"): Record<string, unknown>[] {
const explicit = mode === "failed" ? taskRuns.failedItems : mode === "active" ? taskRuns.activeItems : taskRuns.slowItems;
const explicitItems = arrayRecords(explicit);
if (explicitItems.length > 0) return explicitItems.slice(0, 5).map(compactTaskRunItem);
const items = arrayRecords(taskRuns.items);
if (mode === "failed") return items.filter((item) => item.status === "False").slice(0, 5).map(compactTaskRunItem);
if (mode === "active") return items.filter((item) => item.status !== "True" && item.status !== "False").slice(0, 5).map(compactTaskRunItem);
return arrayRecords(asOptionalRecord(taskRuns.performance)?.slowTaskRuns).slice(0, 5).map(compactTaskRunItem);
}
function compactTaskRunItem(item: Record<string, unknown>): Record<string, unknown> {
return {
name: stringOrNull(item.name),
pipelineTask: stringOrNull(item.pipelineTask),
status: stringOrNull(item.status),
reason: stringOrNull(item.reason),
durationSeconds: numberOrNull(item.durationSeconds),
};
}
function arrayRecords(value: unknown): Record<string, unknown>[] {
return Array.isArray(value) ? value.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null && !Array.isArray(item)) : [];
}
+74
View File
@@ -0,0 +1,74 @@
// SPEC: PJ2026-01060703 CI/CD TaskRun summaries.
// Responsibility: shared bounded TaskRun prioritization for branch-follower status/debug visibility.
export type TaskRunMode = "failed" | "active" | "slow";
export function taskRunItems(taskRuns: Record<string, unknown>, mode: TaskRunMode, limit = 5): Record<string, unknown>[] {
const explicit = mode === "failed" ? taskRuns.failedItems : mode === "active" ? taskRuns.activeItems : taskRuns.slowItems;
const explicitItems = arrayRecords(explicit);
if (explicitItems.length > 0) return explicitItems.slice(0, limit).map(compactTaskRunItem);
const items = arrayRecords(taskRuns.items);
if (mode === "failed") return items.filter((item) => item.status === "False").slice(0, limit).map(compactTaskRunItem);
if (mode === "active") return items.filter((item) => item.status !== "True" && item.status !== "False").slice(0, limit).map(compactTaskRunItem);
const slowItems = arrayRecords(asOptionalRecord(taskRuns.performance)?.slowTaskRuns);
if (slowItems.length > 0) return slowItems.slice(0, limit).map(compactTaskRunItem);
return items.filter((item) => {
const seconds = numberOrNull(item.durationSeconds);
return seconds !== null && seconds > 60;
}).slice(0, limit).map(compactTaskRunItem);
}
export function prioritizedTaskRunItems(taskRuns: Record<string, unknown>, limit = 16): Record<string, unknown>[] {
const prioritized = [
...taskRunItems(taskRuns, "failed", limit),
...taskRunItems(taskRuns, "active", limit),
...taskRunItems(taskRuns, "slow", limit),
...arrayRecords(taskRuns.items).map(compactTaskRunItem),
];
const seen = new Set<string>();
const out: Record<string, unknown>[] = [];
for (const item of prioritized) {
const key = taskRunKey(item);
if (seen.has(key)) continue;
seen.add(key);
out.push(item);
if (out.length >= limit) break;
}
return out;
}
export function compactTaskRunItem(item: Record<string, unknown>): Record<string, unknown> {
return {
name: stringOrNull(item.name),
pipelineTask: stringOrNull(item.pipelineTask),
status: stringOrNull(item.status),
reason: stringOrNull(item.reason),
durationSeconds: numberOrNull(item.durationSeconds),
};
}
function taskRunKey(item: Record<string, unknown>): string {
const key = [
stringOrNull(item.name),
stringOrNull(item.pipelineTask),
stringOrNull(item.status),
stringOrNull(item.reason),
].filter((value) => value !== null).join("|");
return key.length > 0 ? key : JSON.stringify(item);
}
function asOptionalRecord(value: unknown): Record<string, unknown> | null {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
}
function arrayRecords(value: unknown): Record<string, unknown>[] {
return Array.isArray(value) ? value.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null && !Array.isArray(item)) : [];
}
function stringOrNull(value: unknown): string | null {
return typeof value === "string" && value.length > 0 ? value : null;
}
function numberOrNull(value: unknown): number | null {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}