feat(awooop): surface status chain on work queues
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / tests (push) Successful in 1m30s
CD Pipeline / build-and-deploy (push) Successful in 3m29s
CD Pipeline / post-deploy-checks (push) Successful in 1m15s

This commit is contained in:
Your Name
2026-05-19 10:42:44 +08:00
parent a0f41658db
commit aa330339b8
6 changed files with 185 additions and 8 deletions

View File

@@ -22,6 +22,10 @@ import {
import { Link, useRouter } from "@/i18n/routing";
import { IncidentEvidenceHeader } from "@/components/awooop/incident-evidence-header";
import {
AwoooPStatusChainPanel,
type AwoooPStatusChain,
} from "@/components/awooop/status-chain";
import { cn } from "@/lib/utils";
interface RunDetail {
@@ -63,6 +67,7 @@ interface RunRemediationHistory {
interface RunDetailResponse {
run: RunDetail;
remediation_history?: RunRemediationHistory;
awooop_status_chain?: AwoooPStatusChain | null;
}
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "";
@@ -480,6 +485,8 @@ export default function ApprovalDecisionPage({
writesAutoRepairResult={latestRemediation?.writes_auto_repair_result}
/>
<AwoooPStatusChainPanel chain={detail?.awooop_status_chain} />
<ApprovalRemediationEvidence
history={detail?.remediation_history}
locale={locale}

View File

@@ -21,6 +21,10 @@ import {
} from "lucide-react";
import { cn } from "@/lib/utils";
import { Link } from "@/i18n/routing";
import {
AwoooPStatusChainPanel,
type AwoooPStatusChain,
} from "@/components/awooop/status-chain";
// =============================================================================
// Types
@@ -57,6 +61,7 @@ interface Approval {
created_at: string;
timeout_at: string | null;
remediation_summary?: RemediationSummary | null;
awooop_status_chain?: AwoooPStatusChain | null;
}
// =============================================================================
@@ -294,6 +299,9 @@ function ApprovalRow({ approval }: { approval: Approval }) {
<td className="px-4 py-3">
<RemediationEvidenceCell summary={approval.remediation_summary} />
</td>
<td className="min-w-[280px] px-4 py-3">
<AwoooPStatusChainPanel chain={approval.awooop_status_chain} compact />
</td>
<td className="px-4 py-3">
<span className="text-sm text-muted-foreground font-mono">
{formattedDate}
@@ -312,6 +320,7 @@ function ApprovalRow({ approval }: { approval: Approval }) {
export default function ApprovalsPage() {
const tEvidence = useTranslations("awooop.listEvidence");
const tStatusChain = useTranslations("awooop.statusChain");
const [approvals, setApprovals] = useState<Approval[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@@ -554,6 +563,9 @@ export default function ApprovalsPage() {
<th className="text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider">
{tEvidence("column")}
</th>
<th className="text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider">
{tStatusChain("title")}
</th>
<th className="text-left px-4 py-3 text-xs font-medium text-muted-foreground uppercase tracking-wider">
</th>
@@ -566,7 +578,7 @@ export default function ApprovalsPage() {
{loading ? (
Array.from({ length: 5 }).map((_, i) => (
<tr key={i} className="border-b border-border">
{Array.from({ length: 7 }).map((_, j) => (
{Array.from({ length: 8 }).map((_, j) => (
<td key={j} className="px-4 py-3">
<div className="h-5 bg-muted animate-pulse rounded w-20" />
</td>

View File

@@ -27,6 +27,10 @@ import {
import { Link } from "@/i18n/routing";
import { IncidentEvidenceHeader } from "@/components/awooop/incident-evidence-header";
import {
AwoooPStatusChainPanel,
type AwoooPStatusChain,
} from "@/components/awooop/status-chain";
import { cn } from "@/lib/utils";
type WorkStatus = "live" | "in_progress" | "blocked" | "watching";
@@ -278,6 +282,7 @@ type Telemetry = {
slo: SloResponse | null;
remediationHistory: RemediationHistoryResponse | null;
driftFingerprintState: DriftFingerprintState | null;
statusChain: AwoooPStatusChain | null;
};
type WorkItem = {
@@ -371,6 +376,33 @@ function recurrenceOpenItems(recurrence: RecurrenceResponse | null) {
return (recurrence?.items ?? []).filter((item) => item.work_item?.status === "open");
}
function firstIncidentId(...candidates: Array<string | null | undefined>) {
return candidates.find((candidate) => Boolean(candidate?.trim()))?.trim() ?? null;
}
function selectStatusChainIncidentId(
focusedIncidentId: string | null,
remediationHistory: RemediationHistoryResponse | null,
recurrence: RecurrenceResponse | null
) {
const latestRemediationIncident = remediationHistory?.items?.find(
(item) => Boolean(item.incident_id?.trim())
)?.incident_id;
const latestOpenRecurrence = recurrenceOpenItems(recurrence)[0] ?? null;
const latestRecurrenceIncident = recurrence?.items?.find(
(item) => Boolean(item.latest_incident_id?.trim() || item.work_item?.incident_id?.trim())
);
return firstIncidentId(
focusedIncidentId,
latestRemediationIncident,
latestOpenRecurrence?.latest_incident_id,
latestOpenRecurrence?.work_item?.incident_id,
latestRecurrenceIncident?.latest_incident_id,
latestRecurrenceIncident?.work_item?.incident_id
);
}
function recurrenceRepairStatusKey(status?: string | null) {
if (
status === "auto_repair_verified" ||
@@ -1382,6 +1414,7 @@ export default function AwoooPWorkItemsPage() {
slo: null,
remediationHistory: null,
driftFingerprintState: null,
statusChain: null,
});
const [loading, setLoading] = useState(true);
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
@@ -1418,6 +1451,21 @@ export default function AwoooPWorkItemsPage() {
fetchJson<DriftFingerprintState>(driftFingerprintUrl, 12000),
]);
const statusChainIncidentId = selectStatusChainIncidentId(
focusedIncidentId,
remediationHistory,
eventRecurrence
);
let statusChain: AwoooPStatusChain | null = null;
if (statusChainIncidentId) {
const statusChainParams = new URLSearchParams({ project_id: projectId });
statusChainParams.append("incident_id", statusChainIncidentId);
statusChain = await fetchJson<AwoooPStatusChain>(
`${API_BASE}/api/v1/platform/status-chain?${statusChainParams.toString()}`,
12000
);
}
setTelemetry({
quality,
governanceEvents,
@@ -1427,10 +1475,11 @@ export default function AwoooPWorkItemsPage() {
slo,
remediationHistory,
driftFingerprintState,
statusChain,
});
setLastUpdated(new Date());
setLoading(false);
}, [projectId]);
}, [focusedIncidentId, projectId]);
useEffect(() => {
fetchTelemetry();
@@ -1533,6 +1582,8 @@ export default function AwoooPWorkItemsPage() {
writesAutoRepairResult={latestRemediationHistory?.writes_auto_repair_result}
/>
<AwoooPStatusChainPanel chain={telemetry.statusChain} />
<RecurrenceWorkQueuePanel
recurrence={telemetry.eventRecurrence}
focusedWorkItemId={focusedWorkItemId}