feat(awooop): expose source refs on incidents
This commit is contained in:
@@ -1300,6 +1300,76 @@ def _status_chain_execution_section(truth_chain: dict[str, Any] | None) -> dict[
|
||||
}
|
||||
|
||||
|
||||
def _source_ref_values(envelope: Any, key: str) -> list[str]:
|
||||
if not isinstance(envelope, dict):
|
||||
return []
|
||||
source_refs = envelope.get("source_refs")
|
||||
if not isinstance(source_refs, dict):
|
||||
return []
|
||||
raw_values = source_refs.get(key)
|
||||
if isinstance(raw_values, list):
|
||||
return [str(item) for item in raw_values if str(item or "").strip()]
|
||||
if raw_values not in (None, ""):
|
||||
return [str(raw_values)]
|
||||
return []
|
||||
|
||||
|
||||
def _status_chain_source_section(truth_chain: dict[str, Any] | None) -> dict[str, Any]:
|
||||
channel = truth_chain.get("channel") if isinstance(truth_chain, dict) else {}
|
||||
if not isinstance(channel, dict):
|
||||
channel = {}
|
||||
inbound_events = channel.get("inbound_events")
|
||||
outbound_messages = channel.get("outbound_messages")
|
||||
if not isinstance(inbound_events, list):
|
||||
inbound_events = []
|
||||
if not isinstance(outbound_messages, list):
|
||||
outbound_messages = []
|
||||
|
||||
source_refs: dict[str, list[str]] = {
|
||||
"alert_ids": [],
|
||||
"sentry_issue_ids": [],
|
||||
"signoz_alerts": [],
|
||||
"fingerprints": [],
|
||||
"incident_ids": [],
|
||||
}
|
||||
inbound_channels: list[str] = []
|
||||
for row in inbound_events:
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
_append_unique(inbound_channels, row.get("channel_type"))
|
||||
envelope = row.get("source_envelope")
|
||||
for key in source_refs:
|
||||
for value in _source_ref_values(envelope, key):
|
||||
_append_unique(source_refs[key], value)
|
||||
|
||||
latest_inbound = inbound_events[0] if inbound_events and isinstance(inbound_events[0], dict) else {}
|
||||
latest_outbound = (
|
||||
outbound_messages[0]
|
||||
if outbound_messages and isinstance(outbound_messages[0], dict)
|
||||
else {}
|
||||
)
|
||||
|
||||
return {
|
||||
"inbound_total": len(inbound_events),
|
||||
"outbound_total": len(outbound_messages),
|
||||
"inbound_channels": inbound_channels[:5],
|
||||
"refs": {key: values[:5] for key, values in source_refs.items()},
|
||||
"latest_inbound": {
|
||||
"channel_type": latest_inbound.get("channel_type"),
|
||||
"provider_event_id": latest_inbound.get("provider_event_id"),
|
||||
"content_type": latest_inbound.get("content_type"),
|
||||
"is_duplicate": latest_inbound.get("is_duplicate"),
|
||||
"received_at": latest_inbound.get("received_at"),
|
||||
},
|
||||
"latest_outbound": {
|
||||
"channel_type": latest_outbound.get("channel_type"),
|
||||
"message_type": latest_outbound.get("message_type"),
|
||||
"send_status": latest_outbound.get("send_status"),
|
||||
"sent_at": latest_outbound.get("sent_at"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _build_awooop_status_chain(
|
||||
*,
|
||||
incident_ids: list[str],
|
||||
@@ -1379,6 +1449,7 @@ def _build_awooop_status_chain(
|
||||
|
||||
mcp_section = _status_chain_mcp_section(truth_chain)
|
||||
execution_section = _status_chain_execution_section(truth_chain)
|
||||
source_section = _status_chain_source_section(truth_chain)
|
||||
blockers = [
|
||||
str(item)
|
||||
for item in [
|
||||
@@ -1422,6 +1493,7 @@ def _build_awooop_status_chain(
|
||||
},
|
||||
"mcp": mcp_section,
|
||||
"execution": execution_section,
|
||||
"source_refs": source_section,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -539,6 +539,33 @@ def test_awooop_status_chain_marks_verified_repair() -> None:
|
||||
"not_used_reason": None,
|
||||
},
|
||||
},
|
||||
"channel": {
|
||||
"inbound_events": [
|
||||
{
|
||||
"channel_type": "alertmanager",
|
||||
"provider_event_id": "alert-1",
|
||||
"content_type": "application/json",
|
||||
"is_duplicate": False,
|
||||
"received_at": "2026-05-20T00:00:00Z",
|
||||
"source_envelope": {
|
||||
"source_refs": {
|
||||
"alert_ids": ["alert-1"],
|
||||
"sentry_issue_ids": ["SENTRY-1"],
|
||||
"signoz_alerts": ["signoz:abc"],
|
||||
"fingerprints": ["fp-1"],
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
"outbound_messages": [
|
||||
{
|
||||
"channel_type": "telegram",
|
||||
"message_type": "incident_detail",
|
||||
"send_status": "sent",
|
||||
"sent_at": "2026-05-20T00:01:00Z",
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
remediation_history={
|
||||
"total": 1,
|
||||
@@ -571,6 +598,10 @@ def test_awooop_status_chain_marks_verified_repair() -> None:
|
||||
assert chain["execution"]["playbook_ids"] == ["pb-host-restart"]
|
||||
assert chain["execution"]["ansible"]["considered"] is True
|
||||
assert chain["execution"]["ansible"]["candidate_count"] == 1
|
||||
assert chain["source_refs"]["inbound_total"] == 1
|
||||
assert chain["source_refs"]["outbound_total"] == 1
|
||||
assert chain["source_refs"]["refs"]["sentry_issue_ids"] == ["SENTRY-1"]
|
||||
assert chain["source_refs"]["refs"]["signoz_alerts"] == ["signoz:abc"]
|
||||
|
||||
|
||||
def test_awooop_status_chain_marks_read_only_manual_gate() -> None:
|
||||
|
||||
@@ -436,6 +436,7 @@
|
||||
"flowExecutionAnsibleConsidered": "considered ({records} records / {candidates} candidates)",
|
||||
"flowExecutionAnsibleNotUsed": "not used: {reason}",
|
||||
"flowExecutionAnsibleEmpty": "--",
|
||||
"flowSourceRefsDetail": "Source detail: Inbound {inbound} / Outbound {outbound}; Alert {alert}; Sentry {sentry}; SigNoz {signoz}; latest {latest}",
|
||||
"flowTruthChainCurrent": "{stage} / {status}",
|
||||
"flowComplete": "Complete",
|
||||
"flowStages": {
|
||||
|
||||
@@ -437,6 +437,7 @@
|
||||
"flowExecutionAnsibleConsidered": "已納入 ({records} records / {candidates} candidates)",
|
||||
"flowExecutionAnsibleNotUsed": "未使用:{reason}",
|
||||
"flowExecutionAnsibleEmpty": "--",
|
||||
"flowSourceRefsDetail": "來源明細:Inbound {inbound} / Outbound {outbound};Alert {alert};Sentry {sentry};SigNoz {signoz};最新 {latest}",
|
||||
"flowTruthChainCurrent": "{stage} / {status}",
|
||||
"flowComplete": "已完成",
|
||||
"flowStages": {
|
||||
|
||||
@@ -88,6 +88,31 @@ export interface AwoooPStatusChain {
|
||||
}>;
|
||||
};
|
||||
};
|
||||
source_refs?: {
|
||||
inbound_total?: number | null;
|
||||
outbound_total?: number | null;
|
||||
inbound_channels?: string[];
|
||||
refs?: {
|
||||
alert_ids?: string[];
|
||||
sentry_issue_ids?: string[];
|
||||
signoz_alerts?: string[];
|
||||
fingerprints?: string[];
|
||||
incident_ids?: string[];
|
||||
};
|
||||
latest_inbound?: {
|
||||
channel_type?: string | null;
|
||||
provider_event_id?: string | null;
|
||||
content_type?: string | null;
|
||||
is_duplicate?: boolean | null;
|
||||
received_at?: string | null;
|
||||
};
|
||||
latest_outbound?: {
|
||||
channel_type?: string | null;
|
||||
message_type?: string | null;
|
||||
send_status?: string | null;
|
||||
sent_at?: string | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function toneClass(chain?: AwoooPStatusChain | null) {
|
||||
|
||||
@@ -380,6 +380,22 @@ export function IncidentCard({ incident, decision, statusChain, onApprovalChange
|
||||
playbook: executionPlaybook,
|
||||
})
|
||||
: null
|
||||
const sourceRefs = statusChain?.source_refs
|
||||
const latestSource = sourceRefs?.latest_inbound?.channel_type
|
||||
? `${sourceRefs.latest_inbound.channel_type}/${chainValue(sourceRefs.latest_inbound.provider_event_id)}`
|
||||
: sourceRefs?.latest_outbound?.channel_type
|
||||
? `${sourceRefs.latest_outbound.channel_type}/${chainValue(sourceRefs.latest_outbound.message_type)}`
|
||||
: '--'
|
||||
const statusChainSourceRefDetail = hasTruthChain
|
||||
? t('flowSourceRefsDetail', {
|
||||
inbound: sourceRefs?.inbound_total ?? 0,
|
||||
outbound: sourceRefs?.outbound_total ?? 0,
|
||||
alert: sourceRefs?.refs?.alert_ids?.length ?? 0,
|
||||
sentry: sourceRefs?.refs?.sentry_issue_ids?.length ?? 0,
|
||||
signoz: sourceRefs?.refs?.signoz_alerts?.length ?? 0,
|
||||
latest: latestSource,
|
||||
})
|
||||
: null
|
||||
|
||||
const serviceName = incident.affected_services?.[0] ?? '--'
|
||||
const duration = formatDuration(incident.created_at)
|
||||
@@ -659,6 +675,10 @@ export function IncidentCard({ incident, decision, statusChain, onApprovalChange
|
||||
<span data-testid="incident-execution-evidence" style={{ color: '#555550' }}>
|
||||
{statusChainExecutionDetail}
|
||||
</span>
|
||||
<span style={{ color: '#b0ad9f' }}>/</span>
|
||||
<span data-testid="incident-source-ref-evidence" style={{ color: '#555550' }}>
|
||||
{statusChainSourceRefDetail}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user