From 226f551e77498f20c4a6bb5a36730d157f779d07 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 15 May 2026 03:29:55 +0800 Subject: [PATCH] fix(awooop): sort mixed run timeline timestamps --- .../src/services/platform_operator_service.py | 10 ++++++++- .../test_awooop_operator_timeline_labels.py | 22 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/api/src/services/platform_operator_service.py b/apps/api/src/services/platform_operator_service.py index e8ab8830..d52deaae 100644 --- a/apps/api/src/services/platform_operator_service.py +++ b/apps/api/src/services/platform_operator_service.py @@ -341,6 +341,14 @@ def _remediation_timeline_summary(item: dict[str, Any]) -> str: )[:500] +def _timeline_sort_key(item: dict[str, Any], fallback_ts: Any) -> str: + """Normalize mixed DB datetime / ISO string timestamps for timeline sorting.""" + value = item.get("ts") or fallback_ts + if hasattr(value, "isoformat"): + return value.isoformat() + return str(value or "") + + def _summarize_run_remediation_by_work_item( items: list[dict[str, Any]], ) -> list[dict[str, Any]]: @@ -700,7 +708,7 @@ async def get_run_detail( timeline = sorted( timeline, - key=lambda item: item["ts"] or run.created_at, + key=lambda item: _timeline_sort_key(item, run.created_at), )[:_MAX_TIMELINE_ITEMS] return { diff --git a/apps/api/tests/test_awooop_operator_timeline_labels.py b/apps/api/tests/test_awooop_operator_timeline_labels.py index ac78d9c1..180aad90 100644 --- a/apps/api/tests/test_awooop_operator_timeline_labels.py +++ b/apps/api/tests/test_awooop_operator_timeline_labels.py @@ -1,9 +1,11 @@ +from datetime import datetime from types import SimpleNamespace from src.services.platform_operator_service import ( _collect_run_incident_ids, _outbound_timeline_title, _remediation_timeline_summary, + _timeline_sort_key, ) @@ -101,3 +103,23 @@ def test_remediation_timeline_summary_surfaces_route_and_write_flags() -> None: assert "route=auto_repair_executor/ssh_diagnose/read" in summary assert "writes_incident=False" in summary assert "writes_auto_repair=False" in summary + + +def test_timeline_sort_key_normalizes_datetime_and_iso_string() -> None: + fallback = datetime(2026, 5, 14, 10, 0, 0) + keys = [ + _timeline_sort_key({"ts": datetime(2026, 5, 14, 10, 0, 1)}, fallback), + _timeline_sort_key({"ts": "2026-05-14T10:00:02+00:00"}, fallback), + _timeline_sort_key({"ts": None}, fallback), + ] + + assert keys == [ + "2026-05-14T10:00:01", + "2026-05-14T10:00:02+00:00", + "2026-05-14T10:00:00", + ] + assert sorted(keys) == [ + "2026-05-14T10:00:00", + "2026-05-14T10:00:01", + "2026-05-14T10:00:02+00:00", + ]