blocked"],
chat_id="chat",
failure_context="test_history",
+ reply_markup=reply_markup,
)
assert len(sent_requests) == 2
assert sent_requests[0][1]["parse_mode"] == "HTML"
+ assert sent_requests[0][1]["reply_markup"] == reply_markup
assert "parse_mode" not in sent_requests[1][1]
+ assert sent_requests[1][1]["reply_markup"] == reply_markup
assert "" not in sent_requests[1][1]["text"]
assert "blocked" in sent_requests[1][1]["text"]
+@pytest.mark.asyncio
+async def test_send_html_line_message_attaches_awooop_markup_to_first_chunk(monkeypatch):
+ """詳情/歷史這類 HTML reply 要能帶 AwoooP evidence URL,且長訊息只掛第一段。"""
+ sent_requests = []
+ gateway = TelegramGateway()
+ reply_markup = telegram_gateway_module._awooop_runs_reply_markup("INC-20260514-F85F21")
+
+ async def fake_send_request(method, payload):
+ sent_requests.append((method, payload))
+ return {"ok": True}
+
+ monkeypatch.setattr(gateway, "_send_request", fake_send_request)
+
+ await gateway._send_html_line_message(
+ ["📊 事件歷史統計"] + [
+ f"INC-20260514-F85F21 trace line {index:03d}"
+ for index in range(80)
+ ],
+ chat_id="chat",
+ failure_context="test_history",
+ reply_markup=reply_markup,
+ )
+
+ assert len(sent_requests) > 1
+ assert sent_requests[0][1]["reply_markup"] == reply_markup
+ assert all("reply_markup" not in payload for _, payload in sent_requests[1:])
+
+
class TestTelegramMessageFormat:
"""測試現有 TelegramMessage 格式化"""
diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json
index a199846d..234e9e6d 100644
--- a/apps/web/messages/en.json
+++ b/apps/web/messages/en.json
@@ -1808,6 +1808,12 @@
"incidentLabel": "Incident ID filter",
"incidentPlaceholder": "Enter Incident ID"
},
+ "incident": {
+ "column": "Incident",
+ "empty": "Not linked",
+ "filterTitle": "Show only {incidentId}",
+ "more": "+{count} more"
+ },
"statuses": {
"noEvidence": "No dry-run yet",
"readOnlyDryRun": "AI dry-run: read-only",
diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json
index 4cb3b57d..0e763cbf 100644
--- a/apps/web/messages/zh-TW.json
+++ b/apps/web/messages/zh-TW.json
@@ -1809,6 +1809,12 @@
"incidentLabel": "Incident ID 篩選",
"incidentPlaceholder": "輸入 Incident ID"
},
+ "incident": {
+ "column": "Incident",
+ "empty": "尚未關聯",
+ "filterTitle": "只看 {incidentId}",
+ "more": "+{count} 筆"
+ },
"statuses": {
"noEvidence": "尚無試跑",
"readOnlyDryRun": "AI 已試跑:只讀",
diff --git a/apps/web/src/app/[locale]/awooop/runs/page.tsx b/apps/web/src/app/[locale]/awooop/runs/page.tsx
index 4016d768..35795d67 100644
--- a/apps/web/src/app/[locale]/awooop/runs/page.tsx
+++ b/apps/web/src/app/[locale]/awooop/runs/page.tsx
@@ -379,6 +379,55 @@ function RemediationEvidenceCell({ summary }: { summary?: RemediationSummary | n
);
}
+function linkedIncidentIds(summary?: RemediationSummary | null): string[] {
+ const rawIds = summary?.incident_ids ?? [];
+ return Array.from(
+ new Set(
+ rawIds
+ .map((incidentId) => String(incidentId || "").trim().toUpperCase())
+ .filter((incidentId) => INCIDENT_ID_FILTER_RE.test(incidentId))
+ )
+ );
+}
+
+function IncidentIdsCell({ run }: { run: Run }) {
+ const t = useTranslations("awooop.listEvidence");
+ const incidentIds = linkedIncidentIds(run.remediation_summary);
+ const visibleIds = incidentIds.slice(0, 2);
+ const hiddenCount = Math.max(incidentIds.length - visibleIds.length, 0);
+
+ if (visibleIds.length === 0) {
+ return (
+
+ {t("incident.empty")}
+
+ );
+ }
+
+ return (
+
+ {visibleIds.map((incidentId) => (
+
+ {incidentId}
+
+ ))}
+ {hiddenCount > 0 && (
+
+ {t("incident.more", { count: hiddenCount })}
+
+ )}
+
+ );
+}
+
function RunRow({ run }: { run: Run }) {
const formattedDate = run.created_at
? new Date(run.created_at).toLocaleDateString("zh-TW", {
@@ -406,6 +455,9 @@ function RunRow({ run }: { run: Run }) {
{run.project_id || "--"}
+
+
+
{run.agent_id || "--"}
@@ -843,6 +895,9 @@ export default function RunsPage() {
Project ID
+
+ {tEvidence("incident.column")}
+
Agent
@@ -870,7 +925,7 @@ export default function RunsPage() {
{loading ? (
Array.from({ length: 8 }).map((_, i) => (
- {Array.from({ length: 9 }).map((_, j) => (
+ {Array.from({ length: 10 }).map((_, j) => (
@@ -879,7 +934,7 @@ export default function RunsPage() {
))
) : runs.length === 0 && !error ? (
-
+
尚無 Run 資料