fix(cicd): expose reconcile timeline state summary
This commit is contained in:
@@ -97,6 +97,7 @@ function compactStateText(text, includeCommand) {
|
||||
timings: compactTimings(state.timings),
|
||||
warnings: arrayStrings(state.warnings).slice(0, 6),
|
||||
stateFormat: stringOrNull(state.stateFormat),
|
||||
rawStateDiagnostic: rawStateDiagnostic(state, text),
|
||||
};
|
||||
if (includeCommand) compact.command = compactCommand(state.command);
|
||||
return compact;
|
||||
@@ -119,10 +120,73 @@ function compactCommand(command) {
|
||||
exitCode: numberOrNull(value.exitCode),
|
||||
timedOut: value.timedOut === true,
|
||||
statusAuthority: stringOrNull(value.statusAuthority),
|
||||
reconcileTimeline: compactReconcileTimeline(value.reconcileTimeline),
|
||||
parsedDownstreamCliOutput: false,
|
||||
};
|
||||
}
|
||||
|
||||
function compactReconcileTimeline(reconcileTimeline) {
|
||||
const value = recordOrNull(reconcileTimeline);
|
||||
if (value === null) return null;
|
||||
const steps = arrayRecords(value.steps).slice(-16).map((step) => ({
|
||||
follower: stringOrNull(step.follower),
|
||||
step: stringOrNull(step.step),
|
||||
status: stringOrNull(step.status),
|
||||
startedAt: stringOrNull(step.startedAt),
|
||||
finishedAt: stringOrNull(step.finishedAt),
|
||||
elapsedMs: numberOrNull(step.elapsedMs),
|
||||
observedSha: stringOrNull(step.observedSha),
|
||||
targetSha: stringOrNull(step.targetSha),
|
||||
phase: stringOrNull(step.phase),
|
||||
pipelineRun: stringOrNull(step.pipelineRun),
|
||||
object: stringOrNull(step.object),
|
||||
message: stringOrNull(step.message),
|
||||
reason: stringOrNull(step.reason),
|
||||
exitCode: numberOrNull(step.exitCode),
|
||||
}));
|
||||
return {
|
||||
startedAt: stringOrNull(value.startedAt),
|
||||
finishedAt: stringOrNull(value.finishedAt),
|
||||
elapsedMs: numberOrNull(value.elapsedMs),
|
||||
controller: value.controller === true,
|
||||
dryRun: value.dryRun === true,
|
||||
confirm: value.confirm === true,
|
||||
wait: value.wait === true,
|
||||
followerCount: numberOrNull(value.followerCount),
|
||||
followers: arrayStrings(value.followers).slice(0, 8),
|
||||
bounded: true,
|
||||
omittedStepCount: Math.max(0, arrayRecords(value.steps).length - steps.length),
|
||||
steps,
|
||||
};
|
||||
}
|
||||
|
||||
function rawStateDiagnostic(state, text) {
|
||||
const command = recordOrNull(state.command);
|
||||
const reconcileTimeline = recordOrNull(command?.reconcileTimeline);
|
||||
return {
|
||||
bounded: true,
|
||||
valueBytes: Buffer.byteLength(text, "utf8"),
|
||||
hasCommand: command !== null,
|
||||
commandBytes: jsonBytes(command),
|
||||
hasReconcileTimeline: reconcileTimeline !== null,
|
||||
reconcileTimelineBytes: jsonBytes(reconcileTimeline),
|
||||
reconcileTimelineStepCount: reconcileTimeline === null ? 0 : arrayRecords(reconcileTimeline.steps).length,
|
||||
reconcileTimelineStartedAt: stringOrNull(reconcileTimeline?.startedAt),
|
||||
reconcileTimelineFinishedAt: stringOrNull(reconcileTimeline?.finishedAt),
|
||||
reconcileTimelineElapsedMs: numberOrNull(reconcileTimeline?.elapsedMs),
|
||||
missingReason: reconcileTimeline === null ? command === null ? "command missing" : "command.reconcileTimeline missing" : null,
|
||||
};
|
||||
}
|
||||
|
||||
function jsonBytes(value) {
|
||||
if (value === null) return null;
|
||||
try {
|
||||
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function compactCloseout(closeout) {
|
||||
const value = recordOrNull(closeout);
|
||||
if (value === null) return null;
|
||||
|
||||
@@ -1982,6 +1982,7 @@ function mergeFollowerStatus(
|
||||
message: live?.message ?? stringOrNull(stored.decision) ?? "no controller state yet",
|
||||
timings: detailed ? timings : compactListTimings(timings),
|
||||
reconcileTimeline: detailed ? reconcileTimeline : null,
|
||||
rawStateDiagnostic: detailed ? asOptionalRecord(stored.rawStateDiagnostic) : null,
|
||||
drilldown: `bun scripts/cli.ts cicd branch-follower status --follower ${follower.id} --live`,
|
||||
};
|
||||
if (!detailed) return summary;
|
||||
|
||||
@@ -241,6 +241,7 @@ function compactStateLike(value: Record<string, unknown> | null): Record<string,
|
||||
totalSeconds: numberOrNull(value.totalSeconds),
|
||||
timingStatus: stringOrNull(value.timingStatus),
|
||||
resourceVersion: stringOrNull(metadata?.resourceVersion),
|
||||
rawStateDiagnostic: asOptionalRecord(value.rawStateDiagnostic),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -348,6 +349,7 @@ function stateSnapshot(read: K8sStateRead, followerId: string): Record<string, u
|
||||
totalSeconds: numberOrNull(timings?.totalSeconds),
|
||||
startedAt: stringOrNull(timings?.startedAt),
|
||||
updatedAt: stringOrNull(state.updatedAt),
|
||||
rawStateDiagnostic: asOptionalRecord(state.rawStateDiagnostic),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ function renderStatusHuman(payload: Record<string, unknown>, _options: ParsedOpt
|
||||
const errors = Array.isArray(payload.errors) ? payload.errors : [];
|
||||
const timingRows = followers.flatMap(timingRowsForFollower).slice(0, 48);
|
||||
const reconcileRows = followers.flatMap(reconcileRowsForFollower).slice(0, 48);
|
||||
const rawStateRows = followers.flatMap(rawStateRowsForFollower).slice(0, 24);
|
||||
return [
|
||||
`CI/CD BRANCH-FOLLOWER STATUS (${payload.ok === false ? "degraded" : "ok"})`,
|
||||
"",
|
||||
@@ -122,6 +123,7 @@ function renderStatusHuman(payload: Record<string, unknown>, _options: ParsedOpt
|
||||
table(["FOLLOWER", "PHASE", "ADAPTER", "OBSERVED", "TARGET", "TRIGGERED", "SUCCEEDED", "IN_FLIGHT", "BUDGET", "MESSAGE"], rows),
|
||||
timingRows.length === 0 ? "" : `\nSTAGE TIMINGS\n${table(["FOLLOWER", "STAGE", "STATUS", "SECONDS", "BUDGET", "OBJECT"], timingRows)}`,
|
||||
reconcileRows.length === 0 ? "" : `\nRECONCILE TIMELINE\n${table(["FOLLOWER", "STEP", "STATUS", "SECONDS", "STARTED", "OBJECT"], reconcileRows)}`,
|
||||
rawStateRows.length === 0 ? "" : `\nRAW STATE DIAGNOSTIC\n${table(["FOLLOWER", "STATE_BYTES", "COMMAND", "TIMELINE", "STEPS", "TIMELINE_BYTES", "REASON"], rawStateRows)}`,
|
||||
errors.length === 0 ? "" : `\nERRORS\n${errors.map((item) => `- ${item}`).join("\n")}`,
|
||||
"",
|
||||
"NEXT",
|
||||
@@ -253,6 +255,20 @@ function reconcileRowsForTimeline(timeline: Record<string, unknown> | null, fall
|
||||
]);
|
||||
}
|
||||
|
||||
function rawStateRowsForFollower(item: Record<string, unknown>): unknown[][] {
|
||||
const diagnostic = asOptionalRecord(item.rawStateDiagnostic);
|
||||
if (diagnostic === null) return [];
|
||||
return [[
|
||||
item.id ?? "-",
|
||||
diagnostic.valueBytes ?? "-",
|
||||
diagnostic.hasCommand === true ? "yes" : "no",
|
||||
diagnostic.hasReconcileTimeline === true ? "yes" : "no",
|
||||
diagnostic.reconcileTimelineStepCount ?? "-",
|
||||
diagnostic.reconcileTimelineBytes ?? "-",
|
||||
stringOrNull(diagnostic.missingReason) ?? "-",
|
||||
]];
|
||||
}
|
||||
|
||||
function secondsFromMs(value: number | null): number | null {
|
||||
return value === null ? null : Math.round(value / 100) / 10;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user