fix(awooop): preserve recurrence repair fields
This commit is contained in:
@@ -11,7 +11,7 @@ from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Query
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from src.services.channel_event_dossier_service import (
|
||||
fetch_channel_event_dossier,
|
||||
@@ -123,6 +123,10 @@ class ChannelEventRecurrenceSummary(BaseModel):
|
||||
duplicate_event_total: int
|
||||
linked_run_total: int
|
||||
unlinked_event_total: int
|
||||
auto_repair_linked_total: int = 0
|
||||
verified_repair_group_total: int = 0
|
||||
open_work_item_group_total: int = 0
|
||||
manual_gate_group_total: int = 0
|
||||
latest_received_at: datetime | None
|
||||
|
||||
|
||||
@@ -140,6 +144,10 @@ class ChannelEventRecurrenceItem(BaseModel):
|
||||
latest_run_id: UUID | None
|
||||
latest_run_state: str | None
|
||||
latest_agent_id: str | None
|
||||
latest_incident_id: str | None = None
|
||||
incident_ids: list[str] = Field(default_factory=list)
|
||||
repair_summary: dict[str, Any] | None = None
|
||||
work_item: dict[str, Any] | None = None
|
||||
occurrence_total: int
|
||||
duplicate_total: int
|
||||
linked_run_total: int
|
||||
@@ -172,7 +180,9 @@ class ChannelEventRecurrenceResponse(BaseModel):
|
||||
async def get_event_dossier(
|
||||
project_id: str | None = Query(None, description="租戶 ID(可選)"),
|
||||
run_id: UUID | None = Query(None, description="Run ID(可選)"),
|
||||
provider_event_id: str | None = Query(None, description="provider_event_id(可選)"),
|
||||
provider_event_id: str | None = Query(
|
||||
None, description="provider_event_id(可選)"
|
||||
),
|
||||
limit: int = Query(20, ge=1, le=50, description="最多返回筆數"),
|
||||
) -> dict[str, Any]:
|
||||
return await fetch_channel_event_dossier(
|
||||
@@ -194,7 +204,9 @@ async def get_event_dossier(
|
||||
)
|
||||
async def get_event_dossier_coverage(
|
||||
project_id: str | None = Query(None, description="租戶 ID(可選)"),
|
||||
provider: str | None = Query(None, description="provider(可選,如 sentry / signoz)"),
|
||||
provider: str | None = Query(
|
||||
None, description="provider(可選,如 sentry / signoz)"
|
||||
),
|
||||
limit: int = Query(100, ge=1, le=200, description="最多納入統計筆數"),
|
||||
) -> dict[str, Any]:
|
||||
return await fetch_channel_event_dossier_coverage(
|
||||
@@ -215,7 +227,9 @@ async def get_event_dossier_coverage(
|
||||
)
|
||||
async def get_event_dossier_recurrence(
|
||||
project_id: str | None = Query(None, description="租戶 ID(可選)"),
|
||||
provider: str | None = Query(None, description="provider(可選,如 alertmanager / sentry / signoz)"),
|
||||
provider: str | None = Query(
|
||||
None, description="provider(可選,如 alertmanager / sentry / signoz)"
|
||||
),
|
||||
limit: int = Query(100, ge=1, le=300, description="最多納入統計筆數"),
|
||||
) -> dict[str, Any]:
|
||||
return await fetch_channel_event_dossier_recurrence(
|
||||
@@ -237,7 +251,9 @@ async def get_event_dossier_recurrence(
|
||||
async def list_recent_events(
|
||||
project_id: str | None = Query(None, description="租戶 ID(可選)"),
|
||||
channel_type: str | None = Query(None, description="通道類型(可選)"),
|
||||
provider_prefix: str | None = Query(None, description="provider_event_id 前綴(可選)"),
|
||||
provider_prefix: str | None = Query(
|
||||
None, description="provider_event_id 前綴(可選)"
|
||||
),
|
||||
limit: int = Query(20, ge=1, le=100, description="最多返回筆數"),
|
||||
) -> dict[str, Any]:
|
||||
return await list_recent_channel_events(
|
||||
|
||||
@@ -5,6 +5,7 @@ from uuid import UUID
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
from src.api.v1.platform.events import ChannelEventRecurrenceResponse
|
||||
from src.services import channel_event_dossier_service
|
||||
from src.services.channel_event_dossier_service import (
|
||||
build_dossier_coverage,
|
||||
@@ -284,6 +285,72 @@ def test_build_dossier_recurrence_groups_events_and_run_state() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_recurrence_response_model_preserves_repair_work_item_fields() -> None:
|
||||
response = ChannelEventRecurrenceResponse.model_validate(
|
||||
{
|
||||
"project_id": "awoooi",
|
||||
"limit": 20,
|
||||
"summary": {
|
||||
"source_event_total": 1,
|
||||
"recurrence_group_total": 1,
|
||||
"recurrent_group_total": 1,
|
||||
"duplicate_event_total": 0,
|
||||
"linked_run_total": 1,
|
||||
"unlinked_event_total": 0,
|
||||
"auto_repair_linked_total": 1,
|
||||
"verified_repair_group_total": 1,
|
||||
"open_work_item_group_total": 0,
|
||||
"manual_gate_group_total": 0,
|
||||
"latest_received_at": "2026-05-13T13:47:00",
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"recurrence_key": "fingerprint:fp-host-disk",
|
||||
"provider": "alertmanager",
|
||||
"alertname": "HostDiskUsageHigh",
|
||||
"severity": "warning",
|
||||
"namespace": "node",
|
||||
"target_resource": "host-110",
|
||||
"fingerprint": "fp-host-disk",
|
||||
"latest_event_id": "11111111-1111-4111-8111-111111111111",
|
||||
"latest_provider_event_id": "alertmanager:received:1",
|
||||
"latest_content_preview": "Host disk pressure",
|
||||
"latest_run_id": "22222222-2222-4222-8222-222222222222",
|
||||
"latest_run_state": "completed",
|
||||
"latest_agent_id": "openclaw",
|
||||
"latest_incident_id": "INC-20260513-ABCD",
|
||||
"incident_ids": ["INC-20260513-ABCD"],
|
||||
"repair_summary": {
|
||||
"status": "auto_repair_verified",
|
||||
"latest_auto_repair_id": "repair-1",
|
||||
},
|
||||
"work_item": {
|
||||
"work_item_id": "verification:INC-20260513-ABCD:repair-1",
|
||||
"status": "closed",
|
||||
},
|
||||
"occurrence_total": 1,
|
||||
"duplicate_total": 0,
|
||||
"linked_run_total": 1,
|
||||
"source_ref_total": 3,
|
||||
"missing_source_refs_total": 0,
|
||||
"sentry_ref_total": 0,
|
||||
"signoz_ref_total": 0,
|
||||
"alert_ref_total": 1,
|
||||
"run_state_counts": {"completed": 1},
|
||||
"first_received_at": "2026-05-13T13:47:00",
|
||||
"latest_received_at": "2026-05-13T13:47:00",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
payload = response.model_dump()
|
||||
assert payload["summary"]["auto_repair_linked_total"] == 1
|
||||
assert payload["items"][0]["latest_incident_id"] == "INC-20260513-ABCD"
|
||||
assert payload["items"][0]["repair_summary"]["status"] == "auto_repair_verified"
|
||||
assert payload["items"][0]["work_item"]["status"] == "closed"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_channel_event_dossier_requires_source() -> None:
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
|
||||
Reference in New Issue
Block a user