fix: show branch follower drilldown gates

This commit is contained in:
Codex
2026-07-03 18:42:13 +00:00
parent 9c61bf7315
commit 668d8317a3
3 changed files with 186 additions and 22 deletions
@@ -97,6 +97,94 @@ function compactStateText(text) {
timings: compactTimings(state.timings),
warnings: arrayStrings(state.warnings).slice(0, 6),
stateFormat: stringOrNull(state.stateFormat),
command: compactCommand(state.command),
};
}
function compactCommand(command) {
const value = recordOrNull(command);
if (value === null) return null;
return {
mode: stringOrNull(value.mode) ?? stringOrNull(value.status),
namespace: stringOrNull(value.namespace),
pipelineRun: stringOrNull(value.pipelineRun),
sourceCommit: stringOrNull(value.sourceCommit),
sourceStageRef: stringOrNull(value.sourceStageRef),
wait: value.wait === true ? true : null,
pipelineRunCompleted: value.pipelineRunCompleted === true ? true : null,
stillRunning: value.stillRunning === true ? true : null,
closeout: compactCloseout(value.closeout),
payload: compactNativePayload(value.payload),
exitCode: numberOrNull(value.exitCode),
timedOut: value.timedOut === true,
statusAuthority: stringOrNull(value.statusAuthority),
parsedDownstreamCliOutput: false,
};
}
function compactCloseout(closeout) {
const value = recordOrNull(closeout);
if (value === null) return null;
return {
ok: value.ok === true,
completed: value.completed === true,
timedOut: value.timedOut === true,
polls: numberOrNull(value.polls),
elapsedMs: numberOrNull(value.elapsedMs),
summary: compactNativePayload(value.summary),
statusAuthority: stringOrNull(value.statusAuthority),
parsedDownstreamCliOutput: false,
};
}
function compactNativePayload(payload) {
const value = recordOrNull(payload);
if (value === null) return null;
return {
source: recordOrNull(value.source),
sourceSync: recordOrNull(value.sourceSync),
gitMirror: recordOrNull(value.gitMirror),
tekton: recordOrNull(value.tekton),
taskRuns: compactTaskRuns(value.taskRuns),
planArtifacts: compactPlanArtifacts(value.planArtifacts),
argo: recordOrNull(value.argo),
runtime: recordOrNull(value.runtime),
errors: arrayStrings(value.errors).slice(0, 5),
statusAuthority: stringOrNull(value.statusAuthority),
parsedDownstreamCliOutput: false,
};
}
function compactTaskRuns(taskRuns) {
const value = recordOrNull(taskRuns);
if (value === null) return null;
return {
ok: value.ok === true,
count: numberOrNull(value.count),
succeededCount: numberOrNull(value.succeededCount),
failedCount: numberOrNull(value.failedCount),
activeCount: numberOrNull(value.activeCount),
performance: recordOrNull(value.performance),
items: arrayRecords(value.items).slice(0, 16),
};
}
function compactPlanArtifacts(planArtifacts) {
const value = recordOrNull(planArtifacts);
if (value === null) return null;
return {
ok: value.ok === true,
pipelineRun: stringOrNull(value.pipelineRun),
eventFound: value.eventFound === true,
degradedReason: stringOrNull(value.degradedReason),
sourceCommitId: stringOrNull(value.sourceCommitId),
affectedServices: arrayStrings(value.affectedServices).slice(0, 40),
rolloutServices: arrayStrings(value.rolloutServices).slice(0, 40),
buildServices: arrayStrings(value.buildServices).slice(0, 40),
reusedServices: arrayStrings(value.reusedServices).slice(0, 40),
buildSkippedCount: numberOrNull(value.buildSkippedCount),
summary: stringOrNull(value.summary),
disclosure: stringOrNull(value.disclosure),
};
}
+97
View File
@@ -0,0 +1,97 @@
// SPEC: PJ2026-01060703 CI/CD branch follower drill-down rendering.
// Responsibility: bounded human summaries for branch-follower events/logs gates.
export function renderDrillDownHuman(payload: Record<string, unknown>): string {
if (payload.follower === undefined) {
const followers = arrayRecords(payload.followers);
return [
`CI/CD BRANCH-FOLLOWER ${String(payload.action ?? "drill-down").toUpperCase()}`,
"",
table(["FOLLOWER", "ADAPTER", "STATUS_AUTHORITY"], followers.map((item) => [item.id, item.adapter, item.statusAuthority ?? "k8s-native"])),
"",
].join("\n");
}
const summary = asOptionalRecord(payload.summary);
const native = asOptionalRecord(payload.native);
const gateRows = nativeGateRows(native);
return [
`CI/CD BRANCH-FOLLOWER ${String(payload.action ?? "drill-down").toUpperCase()} (${payload.ok === false ? "failed" : "ok"})`,
"",
table(
["FOLLOWER", "ADAPTER", "AUTHORITY", "PHASE", "OBSERVED", "TARGET", "PIPELINERUN", "MESSAGE"],
[[payload.follower, payload.adapter ?? "-", payload.statusAuthority ?? "k8s-native", summary?.phase ?? "-", shortSha(stringOrNull(summary?.observedSha)), shortSha(stringOrNull(summary?.targetSha)), summary?.pipelineRun ?? "-", summary?.message ?? "-"]],
),
gateRows.length === 0 ? "" : `\nGATES\n${table(["GATE", "STATUS", "DETAIL", "OBJECT"], gateRows)}`,
"",
].filter((line) => line !== "").join("\n");
}
function nativeGateRows(native: Record<string, unknown> | null): unknown[][] {
if (native === null) return [];
const rows: unknown[][] = [];
const gitMirror = asOptionalRecord(native.gitMirror);
if (gitMirror !== null) {
const hasGitops = stringOrNull(gitMirror.gitopsBranch) !== null;
const status = gitMirror.pendingFlush === true
? "pending-flush"
: hasGitops
? gitMirror.githubInSync === true && gitMirror.sourceSnapshotReady === true ? "ready" : "not-ready"
: gitMirror.sourceSnapshotReady === true ? "source-ready" : "source-not-ready";
rows.push(["git-mirror", status, `${shortSha(stringOrNull(gitMirror.localSource))}/${shortSha(stringOrNull(gitMirror.githubSource))}`, stringOrNull(gitMirror.gitopsBranch) ?? stringOrNull(gitMirror.sourceBranch) ?? "-"]);
}
const tekton = asOptionalRecord(native.tekton);
if (tekton !== null) {
const status = tekton.succeeded === true ? "succeeded" : tekton.succeeded === false ? `failed:${stringOrNull(tekton.reason) ?? "unknown"}` : "running";
const duration = numberOrNull(tekton.durationSeconds);
rows.push(["tekton", status, duration === null ? stringOrNull(tekton.reason) ?? "-" : `${duration}s`, stringOrNull(tekton.name) ?? "-"]);
}
const argo = asOptionalRecord(native.argo);
if (argo !== null) {
rows.push(["argo", `${stringOrNull(argo.syncStatus) ?? "unknown"}/${stringOrNull(argo.healthStatus) ?? "unknown"}`, shortSha(stringOrNull(argo.revision)), stringOrNull(argo.name) ?? "-"]);
}
const runtime = asOptionalRecord(native.runtime);
if (runtime !== null) {
const status = runtime.ready === true ? (runtime.aligned === true ? "ready/aligned" : "ready/stale") : "not-ready";
rows.push(["runtime", status, `${shortSha(stringOrNull(runtime.targetSha))}/${shortSha(stringOrNull(runtime.expectedSha))}`, stringOrNull(runtime.namespace) ?? "-"]);
}
for (const error of arrayTextItems(native.errors).slice(0, 5)) rows.push(["error", "present", error, "-"]);
return rows;
}
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 arrayTextItems(value: unknown): string[] {
return Array.isArray(value) ? value.map(String) : [];
}
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;
}
function shortSha(value: string | null): string {
if (value === null) return "-";
return value.length > 12 ? value.slice(0, 12) : value;
}
function table(headers: readonly string[], rows: readonly (readonly unknown[])[]): string {
const normalized = rows.map((row) => headers.map((_, index) => cell(row[index])));
const widths = headers.map((header, index) => Math.max(header.length, ...normalized.map((row) => row[index]?.length ?? 0)));
const format = (row: readonly string[]) => row.map((value, index) => value.padEnd(widths[index] ?? 0)).join(" ").trimEnd();
return [format(headers), format(headers.map((header) => "-".repeat(header.length))), ...normalized.map(format)].join("\n");
}
function cell(value: unknown): string {
if (value === null || value === undefined || value === "") return "-";
const text = String(value).replace(/\s+/gu, " ");
return text.length > 96 ? `${text.slice(0, 93)}...` : text;
}
+1 -22
View File
@@ -22,6 +22,7 @@ import { transPath } from "./hwlab-node/runtime-common";
import { configRefGraph, resolveConfigRefString } from "./ops/config-refs";
import { renderControllerManifests, renderControllerReconcileJob, waitForJobShell } from "./cicd-controller-render";
import { buildDebugStep, renderDebugStepHuman } from "./cicd-debug";
import { renderDrillDownHuman } from "./cicd-drilldown-render";
import { runNativeHwlabControlPlaneRefresh } from "./cicd-hwlab-refresh";
import { nativeCicdScriptLoadShell, readNativeObjectBundle } from "./cicd-native-bundle";
import { runNativeK8sJob, runNativeTektonPipelineRun } from "./cicd-native";
@@ -2951,28 +2952,6 @@ function renderCleanupStateHuman(payload: Record<string, unknown>): string {
].filter((line) => line !== "").join("\n");
}
function renderDrillDownHuman(payload: Record<string, unknown>): string {
if (payload.follower === undefined) {
const followers = arrayRecords(payload.followers);
return [
`CI/CD BRANCH-FOLLOWER ${String(payload.action ?? "drill-down").toUpperCase()}`,
"",
table(["FOLLOWER", "ADAPTER", "STATUS_AUTHORITY"], followers.map((item) => [item.id, item.adapter, item.statusAuthority ?? "k8s-native"])),
"",
].join("\n");
}
const summary = asOptionalRecord(payload.summary);
return [
`CI/CD BRANCH-FOLLOWER ${String(payload.action ?? "drill-down").toUpperCase()} (${payload.ok === false ? "failed" : "ok"})`,
"",
table(
["FOLLOWER", "ADAPTER", "AUTHORITY", "PHASE", "OBSERVED", "TARGET", "PIPELINERUN", "MESSAGE"],
[[payload.follower, payload.adapter ?? "-", payload.statusAuthority ?? "k8s-native", summary?.phase ?? "-", shortSha(stringOrNull(summary?.observedSha)), shortSha(stringOrNull(summary?.targetSha)), summary?.pipelineRun ?? "-", summary?.message ?? "-"]],
),
"",
].filter((line) => line !== "").join("\n");
}
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)) : [];
}