fix: preserve branch follower stage intervals

This commit is contained in:
Codex
2026-07-04 16:13:12 +00:00
parent 1cd7846daf
commit d9dab2da89
7 changed files with 224 additions and 128 deletions
+38 -22
View File
@@ -73,14 +73,7 @@ export function compactTimings(timings: FollowerState["timings"]): FollowerState
startedAt: timings.startedAt,
finishedAt: timings.finishedAt,
overBudget: timings.overBudget,
stages: timings.stages.slice(0, 24).map((stage) => ({
stage: stage.stage,
status: stage.status,
seconds: stage.seconds,
budgetSeconds: stage.budgetSeconds,
source: stage.source,
object: stage.object,
})),
stages: timings.stages.slice(0, 24).map((stage) => compactStageTiming(stage, true)),
};
}
@@ -94,11 +87,7 @@ export function compactListTimings(timings: FollowerState["timings"]): Record<st
startedAt: timings.startedAt,
finishedAt: timings.finishedAt,
overBudget: timings.overBudget,
stages: timings.stages.slice(0, 4).map((stage) => ({
stage: stage.stage,
status: stage.status,
seconds: stage.seconds,
})),
stages: timings.stages.slice(0, 4).map((stage) => compactListStageTiming(stage)),
};
}
@@ -141,6 +130,31 @@ function noopStoredTotalFinishOverride(
return new Date().toISOString();
}
function compactStageTiming(stage: StageTiming, includeDetail: boolean): StageTiming {
const row: StageTiming = {
stage: stage.stage,
status: stage.status,
seconds: stage.seconds,
budgetSeconds: includeDetail ? stage.budgetSeconds : null,
source: includeDetail ? stage.source : "",
object: includeDetail ? stage.object : null,
};
if (stage.startedAt !== null && stage.startedAt !== undefined) row.startedAt = stage.startedAt;
if (stage.finishedAt !== null && stage.finishedAt !== undefined) row.finishedAt = stage.finishedAt;
return row;
}
function compactListStageTiming(stage: StageTiming): Record<string, unknown> {
const row: Record<string, unknown> = {
stage: stage.stage,
status: stage.status,
seconds: stage.seconds,
};
if (stage.startedAt !== null && stage.startedAt !== undefined) row.startedAt = stage.startedAt;
if (stage.finishedAt !== null && stage.finishedAt !== undefined) row.finishedAt = stage.finishedAt;
return row;
}
function storedStageTimings(storedTimings: Record<string, unknown> | null): StageTiming[] {
if (storedTimings === null) return [];
return arrayRecords(storedTimings.stages)
@@ -151,6 +165,8 @@ function storedStageTimings(storedTimings: Record<string, unknown> | null): Stag
numberOrNull(stage.budgetSeconds),
stringOrNull(stage.source) ?? "stored-state",
stringOrNull(stage.object),
stringOrNull(stage.startedAt),
stringOrNull(stage.finishedAt),
))
.filter((stage) => stage.stage.length > 0)
.slice(0, 24);
@@ -222,7 +238,7 @@ function stageTimingsFromNativePayload(payload: Record<string, unknown> | null,
if (payload === null) return [];
const stages: StageTiming[] = [];
const statusRead = asOptionalRecord(asOptionalRecord(payload.timings)?.statusRead);
stages.push(stageTiming("status-read", "ok", secondsFromMs(numberOrNull(statusRead?.elapsedMs)), numberOrNull(statusRead?.budgetSeconds), "native-status", null));
stages.push(stageTiming("status-read", "ok", secondsFromMs(numberOrNull(statusRead?.elapsedMs)), numberOrNull(statusRead?.budgetSeconds), "native-status", null, stringOrNull(statusRead?.startedAt), stringOrNull(statusRead?.finishedAt)));
const sourceSyncStage = k8sJobTiming("git-mirror-sync", asOptionalRecord(payload.sourceSync));
if (sourceSyncStage !== null) stages.push(sourceSyncStage);
const reuseConfig = asOptionalRecord(payload.reuseConfig);
@@ -237,18 +253,18 @@ function stageTimingsFromNativePayload(payload: Record<string, unknown> | null,
const tekton = asOptionalRecord(payload.tekton);
if (tekton !== null) {
const status = tekton.succeeded === true ? "succeeded" : tekton.succeeded === false ? `failed:${stringOrNull(tekton.reason) ?? "unknown"}` : "running";
stages.push(stageTiming("pipelinerun", status, numberOrNull(tekton.durationSeconds), null, "tekton", stringOrNull(tekton.name)));
stages.push(stageTiming("pipelinerun", status, numberOrNull(tekton.durationSeconds), null, "tekton", stringOrNull(tekton.name), stringOrNull(tekton.startTime), stringOrNull(tekton.completionTime)));
}
const taskRuns = asOptionalRecord(payload.taskRuns);
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)));
stages.push(stageTiming(`task:${name}`, status, numberOrNull(record.durationSeconds), null, "tekton-taskrun", stringOrNull(record.name), stringOrNull(record.startTime), stringOrNull(record.completionTime)));
}
const argo = asOptionalRecord(payload.argo);
if (argo !== null) {
const sameWindow = total === null || timingOverlapsTotal(total, stringOrNull(argo.operationStartedAt), stringOrNull(argo.operationFinishedAt));
stages.push(stageTiming("argo", `${sameWindow ? "" : "current:"}${stringOrNull(argo.syncStatus) ?? "unknown"}/${stringOrNull(argo.healthStatus) ?? "unknown"}`, sameWindow ? numberOrNull(argo.operationDurationSeconds) : null, null, sameWindow ? "argocd" : "argocd-current", stringOrNull(argo.name)));
stages.push(stageTiming("argo", `${sameWindow ? "" : "current:"}${stringOrNull(argo.syncStatus) ?? "unknown"}/${stringOrNull(argo.healthStatus) ?? "unknown"}`, sameWindow ? numberOrNull(argo.operationDurationSeconds) : null, null, sameWindow ? "argocd" : "argocd-current", stringOrNull(argo.name), stringOrNull(argo.operationStartedAt), stringOrNull(argo.operationFinishedAt)));
}
const runtime = asOptionalRecord(payload.runtime);
if (runtime !== null) {
@@ -296,7 +312,7 @@ function stageTimingsFromCommand(command: Record<string, unknown> | undefined):
const tektonSeconds = secondsFromMs(numberOrNull(payload.elapsedMs));
if (tektonSeconds !== null) {
const status = payload.completed === true ? "completed" : payload.failed === true ? "failed" : payload.stillRunning === true ? "running" : "submitted";
stages.push(stageTiming("pipelinerun-wait", status, tektonSeconds, null, "tekton-submit", stringOrNull(command.pipelineRun)));
stages.push(stageTiming("pipelinerun-wait", status, tektonSeconds, null, "tekton-submit", stringOrNull(command.pipelineRun), stringOrNull(payload.startedAt), stringOrNull(payload.finishedAt)));
}
}
const closeout = asOptionalRecord(command.closeout);
@@ -305,7 +321,7 @@ function stageTimingsFromCommand(command: Record<string, unknown> | undefined):
const gitMirrorFlushStage = k8sJobTiming("git-mirror-flush", asOptionalRecord(gitMirrorFlush?.result), stringOrNull(gitMirrorFlush?.jobName));
if (gitMirrorFlushStage !== null) stages.push(gitMirrorFlushStage);
const status = closeout.completed === true ? "completed" : closeout.timedOut === true ? "over-budget" : "pending";
stages.push(stageTiming("closeout", status, secondsFromMs(numberOrNull(closeout.elapsedMs)), null, "k8s-native-closeout", stringOrNull(command.pipelineRun)));
stages.push(stageTiming("closeout", status, secondsFromMs(numberOrNull(closeout.elapsedMs)), null, "k8s-native-closeout", stringOrNull(command.pipelineRun), stringOrNull(closeout.startedAt), stringOrNull(closeout.finishedAt)));
}
return stages;
}
@@ -313,11 +329,11 @@ function stageTimingsFromCommand(command: Record<string, unknown> | undefined):
function k8sJobTiming(stage: string, job: Record<string, unknown> | null, objectOverride?: string | null): StageTiming | null {
if (job === null) return null;
const status = job.completed === true ? job.reused === true ? "reused" : "completed" : job.failed === true ? "failed" : job.timedOut === true ? "over-budget" : "running";
return stageTiming(stage, status, secondsFromMs(numberOrNull(job.elapsedMs)), null, "kubernetes-job", objectOverride ?? stringOrNull(job.jobName));
return stageTiming(stage, status, secondsFromMs(numberOrNull(job.elapsedMs)), null, "kubernetes-job", objectOverride ?? stringOrNull(job.jobName), stringOrNull(job.startedAt), stringOrNull(job.finishedAt));
}
function stageTiming(stage: string, status: string, seconds: number | null, budgetSeconds: number | null, source: string, object: string | null): StageTiming {
return { stage, status, seconds, budgetSeconds, source, object };
function stageTiming(stage: string, status: string, seconds: number | null, budgetSeconds: number | null, source: string, object: string | null, startedAt?: string | null, finishedAt?: string | null): StageTiming {
return { stage, status, seconds, budgetSeconds, source, object, startedAt: startedAt ?? null, finishedAt: finishedAt ?? null };
}
function dedupeTimingStages(stages: StageTiming[]): StageTiming[] {