feat(awooop): surface source evidence flow
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / tests (push) Successful in 3m54s
CD Pipeline / build-and-deploy (push) Successful in 4m29s
CD Pipeline / post-deploy-checks (push) Successful in 1m50s

This commit is contained in:
Your Name
2026-05-21 13:00:59 +08:00
parent 1ae8f0d179
commit 53a3c846e5
5 changed files with 223 additions and 37 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { Activity, CheckCircle2, Route, ShieldAlert, TriangleAlert } from "lucide-react";
import { Activity, CheckCircle2, Link2, RadioTower, Route, ShieldAlert, TriangleAlert } from "lucide-react";
import { useTranslations } from "next-intl";
import { cn } from "@/lib/utils";
@@ -176,6 +176,15 @@ function valueOrEmpty(value: unknown, emptyLabel: string) {
return String(value);
}
type SourceFlowTone = "success" | "warning" | "blocked" | "neutral";
function sourceFlowToneClass(tone: SourceFlowTone) {
if (tone === "success") return "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]";
if (tone === "blocked") return "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]";
if (tone === "warning") return "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]";
return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]";
}
export function AwoooPStatusChainPanel({
chain,
compact = false,
@@ -251,6 +260,62 @@ export function AwoooPStatusChainPanel({
const providerItem = sourceCorrelation?.providers?.[provider];
return `${provider} ${providerItem?.direct_ref_total ?? 0}/${providerItem?.candidate_total ?? 0}/${providerItem?.applied_link_total ?? 0}`;
}).join(" · ");
const sourceProviderEntries = Object.entries(sourceCorrelation?.providers ?? {});
const sourceProviderReadyTotal = sourceProviderEntries.filter(([, provider]) => (
Boolean(provider.latest_event_at)
|| Boolean(provider.latest_heartbeat_at)
|| (provider.direct_ref_total ?? 0) > 0
|| (provider.candidate_total ?? 0) > 0
|| (provider.applied_link_total ?? 0) > 0
)).length;
const providerEventTotal = sourceCorrelation?.provider_event_total ?? 0;
const directRefTotal = sourceCorrelation?.direct_ref_total ?? 0;
const candidateTotal = sourceCorrelation?.candidate_total ?? 0;
const appliedLinkTotal = sourceCorrelation?.applied_link_total ?? 0;
const providerIngressReady = providerEventTotal > 0 || sourceProviderReadyTotal > 0;
const sourceEvidenceFound = directRefTotal > 0 || candidateTotal > 0 || appliedLinkTotal > 0;
const sourceLinkVerified = sourceVerificationStatus === "applied_link_verified"
|| sourceVerificationStatus === "direct_ref_verified";
const sourceVerificationBlocked = sourceVerificationStatus === "missing"
|| sourceVerificationStatus === "fetch_failed"
|| sourceVerificationStatus === "no_incident_context";
const sourceEvidenceFlow = sourceCorrelation ? [
{
key: "provider-ingress",
Icon: RadioTower,
tone: (providerIngressReady ? "success" : "warning") as SourceFlowTone,
label: t("source.flow.providerIngress"),
status: providerIngressReady ? t("source.flow.status.ready") : t("source.flow.status.waiting"),
detail: t("source.flow.providerDetail", {
providerEvents: providerEventTotal,
readyProviders: sourceProviderReadyTotal,
}),
},
{
key: "source-evidence",
Icon: Link2,
tone: (appliedLinkTotal > 0 ? "success" : (sourceEvidenceFound ? "warning" : "blocked")) as SourceFlowTone,
label: t("source.flow.sourceEvidence"),
status: appliedLinkTotal > 0
? t("source.flow.status.applied")
: (sourceEvidenceFound ? t("source.flow.status.needsReview") : t("source.flow.status.waiting")),
detail: t("source.directCandidateValue", {
direct: directRefTotal,
candidate: candidateTotal,
applied: appliedLinkTotal,
}),
},
{
key: "applied-verification",
Icon: CheckCircle2,
tone: (sourceLinkVerified ? "success" : (sourceVerificationBlocked ? "blocked" : "warning")) as SourceFlowTone,
label: t("source.flow.appliedVerification"),
status: sourceVerificationLabels[sourceVerificationStatus] ?? valueOrEmpty(sourceVerificationStatus, emptyLabel),
detail: t("source.flow.verificationDetail", {
latest: latestAppliedLabel,
}),
},
] : [];
return (
<section className={cn("border border-[#e0ddd4] bg-white", className)}>
@@ -324,43 +389,68 @@ export function AwoooPStatusChainPanel({
</div>
{sourceCorrelation && (
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-5">
<div className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("source.status")}</p>
<p className="mt-2 truncate font-mono text-sm text-[#141413]" title={sourceStatusLabels[sourceStatus] ?? sourceStatus}>
{sourceStatusLabels[sourceStatus] ?? valueOrEmpty(sourceCorrelation.status, emptyLabel)}
</p>
<div>
<div className={cn("grid gap-px bg-[#e0ddd4]", compact ? "grid-cols-1" : "md:grid-cols-3")}>
{sourceEvidenceFlow.map((item) => (
<div key={item.key} className="min-w-0 bg-white px-4 py-3">
<div className="flex min-w-0 items-start gap-3">
<span className={cn(
"flex h-8 w-8 shrink-0 items-center justify-center border",
sourceFlowToneClass(item.tone)
)}>
<item.Icon className="h-4 w-4" aria-hidden="true" />
</span>
<div className="min-w-0">
<p className="text-xs font-semibold text-[#77736a]">{item.label}</p>
<p className="mt-1 truncate font-mono text-sm font-semibold text-[#141413]" title={item.status}>
{item.status}
</p>
</div>
</div>
<p className="mt-2 truncate font-mono text-xs text-[#5f5b52]" title={item.detail}>
{item.detail}
</p>
</div>
))}
</div>
<div className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("source.verification")}</p>
<p
className="mt-2 truncate font-mono text-sm text-[#141413]"
title={sourceVerificationLabels[sourceVerificationStatus] ?? sourceVerificationStatus}
>
{sourceVerificationLabels[sourceVerificationStatus] ?? valueOrEmpty(sourceVerificationStatus, emptyLabel)}
</p>
</div>
<div className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("source.directCandidate")}</p>
<p className="mt-2 font-mono text-sm text-[#141413]">
{t("source.directCandidateValue", {
direct: sourceCorrelation.direct_ref_total ?? 0,
candidate: sourceCorrelation.candidate_total ?? 0,
applied: sourceCorrelation.applied_link_total ?? 0,
})}
</p>
</div>
<div className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("source.latestApplied")}</p>
<p className="mt-2 truncate font-mono text-sm text-[#141413]" title={latestAppliedLabel}>
{latestAppliedLabel}
</p>
</div>
<div className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("source.providers")}</p>
<p className="mt-2 truncate font-mono text-sm text-[#141413]" title={sourceProviderSummary}>
{sourceProviderSummary}
</p>
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-5">
<div className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("source.status")}</p>
<p className="mt-2 truncate font-mono text-sm text-[#141413]" title={sourceStatusLabels[sourceStatus] ?? sourceStatus}>
{sourceStatusLabels[sourceStatus] ?? valueOrEmpty(sourceCorrelation.status, emptyLabel)}
</p>
</div>
<div className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("source.verification")}</p>
<p
className="mt-2 truncate font-mono text-sm text-[#141413]"
title={sourceVerificationLabels[sourceVerificationStatus] ?? sourceVerificationStatus}
>
{sourceVerificationLabels[sourceVerificationStatus] ?? valueOrEmpty(sourceVerificationStatus, emptyLabel)}
</p>
</div>
<div className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("source.directCandidate")}</p>
<p className="mt-2 font-mono text-sm text-[#141413]">
{t("source.directCandidateValue", {
direct: directRefTotal,
candidate: candidateTotal,
applied: appliedLinkTotal,
})}
</p>
</div>
<div className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("source.latestApplied")}</p>
<p className="mt-2 truncate font-mono text-sm text-[#141413]" title={latestAppliedLabel}>
{latestAppliedLabel}
</p>
</div>
<div className="min-w-0 bg-white px-4 py-3">
<p className="text-xs font-semibold text-[#77736a]">{t("source.providers")}</p>
<p className="mt-2 truncate font-mono text-sm text-[#141413]" title={sourceProviderSummary}>
{sourceProviderSummary}
</p>
</div>
</div>
</div>
)}