feat(awooop): show incident source correlation evidence
This commit is contained in:
@@ -99,6 +99,29 @@ export interface AwoooPStatusChain {
|
||||
fingerprints?: string[];
|
||||
incident_ids?: string[];
|
||||
};
|
||||
correlation?: {
|
||||
schema_version?: string;
|
||||
status?: string | null;
|
||||
missing_reason?: string | null;
|
||||
direct_ref_total?: number | null;
|
||||
candidate_total?: number | null;
|
||||
provider_event_total?: number | null;
|
||||
providers?: Record<string, {
|
||||
direct_ref_total?: number | null;
|
||||
candidate_total?: number | null;
|
||||
latest_event_at?: string | null;
|
||||
latest_heartbeat_at?: string | null;
|
||||
}>;
|
||||
top_candidates?: Array<{
|
||||
provider?: string | null;
|
||||
provider_event_id?: string | null;
|
||||
stage?: string | null;
|
||||
score?: number | null;
|
||||
match_type?: string | null;
|
||||
reasons?: string[];
|
||||
received_at?: string | null;
|
||||
}>;
|
||||
};
|
||||
latest_inbound?: {
|
||||
channel_type?: string | null;
|
||||
provider_event_id?: string | null;
|
||||
@@ -161,6 +184,7 @@ export function AwoooPStatusChainPanel({
|
||||
const emptyLabel = t("emptyValue");
|
||||
const evidence = chain?.evidence ?? {};
|
||||
const blockers = chain?.blockers ?? [];
|
||||
const sourceCorrelation = chain?.source_refs?.correlation;
|
||||
|
||||
if (!chain) {
|
||||
return (
|
||||
@@ -190,6 +214,19 @@ export function AwoooPStatusChainPanel({
|
||||
{ label: t("evidence.mcp"), value: evidence.mcp_gateway_total ?? 0 },
|
||||
{ label: t("evidence.km"), value: evidence.knowledge_entries ?? 0 },
|
||||
];
|
||||
const sourceStatusLabels: Record<string, string> = {
|
||||
linked: t("source.statuses.linked"),
|
||||
candidate_found: t("source.statuses.candidateFound"),
|
||||
provider_fresh_no_match: t("source.statuses.providerFreshNoMatch"),
|
||||
missing: t("source.statuses.missing"),
|
||||
no_incident_context: t("source.statuses.noIncidentContext"),
|
||||
fetch_failed: t("source.statuses.fetchFailed"),
|
||||
};
|
||||
const sourceStatus = String(sourceCorrelation?.status ?? "missing");
|
||||
const sourceProviderSummary = ["sentry", "signoz"].map((provider) => {
|
||||
const providerItem = sourceCorrelation?.providers?.[provider];
|
||||
return `${provider} ${providerItem?.direct_ref_total ?? 0}/${providerItem?.candidate_total ?? 0}`;
|
||||
}).join(" · ");
|
||||
|
||||
return (
|
||||
<section className={cn("border border-[#e0ddd4] bg-white", className)}>
|
||||
@@ -262,6 +299,32 @@ export function AwoooPStatusChainPanel({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{sourceCorrelation && (
|
||||
<div className="grid gap-px bg-[#e0ddd4] md:grid-cols-3">
|
||||
<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.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,
|
||||
})}
|
||||
</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>
|
||||
)}
|
||||
|
||||
{blockers.length > 0 && (
|
||||
<div className="border-t border-[#eee9dd] bg-[#fff7e8] px-4 py-3 text-xs leading-5 text-[#8a5a08]">
|
||||
<span className="font-semibold">{t("blockers")}</span>{" "}
|
||||
|
||||
@@ -58,6 +58,15 @@ const FLOW_STAGE_ORDER: FlowStage[] = [
|
||||
'resolved',
|
||||
]
|
||||
|
||||
const SOURCE_CORRELATION_STATUS_KEYS = {
|
||||
linked: 'linked',
|
||||
candidate_found: 'candidateFound',
|
||||
provider_fresh_no_match: 'providerFreshNoMatch',
|
||||
missing: 'missing',
|
||||
no_incident_context: 'noIncidentContext',
|
||||
fetch_failed: 'fetchFailed',
|
||||
} as const
|
||||
|
||||
/** 根據 incident + decision evidence 對應 FlowStage */
|
||||
function toFlowStage(status: string, severity: string, decision?: DecisionInfo | null): FlowStage {
|
||||
const normalizedStatus = status.toLowerCase()
|
||||
@@ -381,6 +390,21 @@ export function IncidentCard({ incident, decision, statusChain, onApprovalChange
|
||||
})
|
||||
: null
|
||||
const sourceRefs = statusChain?.source_refs
|
||||
const sourceCorrelation = sourceRefs?.correlation
|
||||
const sourceCorrelationStatus = String(sourceCorrelation?.status ?? 'missing')
|
||||
const sourceCorrelationKey =
|
||||
SOURCE_CORRELATION_STATUS_KEYS[
|
||||
sourceCorrelationStatus as keyof typeof SOURCE_CORRELATION_STATUS_KEYS
|
||||
] ?? 'missing'
|
||||
const sourceCorrelationStatusLabels: Record<typeof sourceCorrelationKey, string> = {
|
||||
linked: t('flowSourceCorrelationStatus.linked'),
|
||||
candidateFound: t('flowSourceCorrelationStatus.candidateFound'),
|
||||
providerFreshNoMatch: t('flowSourceCorrelationStatus.providerFreshNoMatch'),
|
||||
missing: t('flowSourceCorrelationStatus.missing'),
|
||||
noIncidentContext: t('flowSourceCorrelationStatus.noIncidentContext'),
|
||||
fetchFailed: t('flowSourceCorrelationStatus.fetchFailed'),
|
||||
}
|
||||
const sourceCorrelationLabel = sourceCorrelationStatusLabels[sourceCorrelationKey]
|
||||
const latestSource = sourceRefs?.latest_inbound?.channel_type
|
||||
? `${sourceRefs.latest_inbound.channel_type}/${chainValue(sourceRefs.latest_inbound.provider_event_id)}`
|
||||
: sourceRefs?.latest_outbound?.channel_type
|
||||
@@ -393,6 +417,9 @@ export function IncidentCard({ incident, decision, statusChain, onApprovalChange
|
||||
alert: sourceRefs?.refs?.alert_ids?.length ?? 0,
|
||||
sentry: sourceRefs?.refs?.sentry_issue_ids?.length ?? 0,
|
||||
signoz: sourceRefs?.refs?.signoz_alerts?.length ?? 0,
|
||||
linked: sourceCorrelation?.direct_ref_total ?? 0,
|
||||
candidate: sourceCorrelation?.candidate_total ?? 0,
|
||||
correlation: sourceCorrelationLabel,
|
||||
latest: latestSource,
|
||||
})
|
||||
: null
|
||||
|
||||
Reference in New Issue
Block a user