fix(telegram): link incident truth chain from alerts
This commit is contained in:
@@ -27,7 +27,6 @@ import asyncio
|
||||
import html
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import quote
|
||||
from uuid import UUID
|
||||
|
||||
import structlog
|
||||
@@ -40,9 +39,10 @@ from src.plugins.mcp.gateway import GatewayContext, McpGateway, McpGatewayError
|
||||
from src.plugins.mcp.interfaces import MCPToolResult
|
||||
from src.services.approval_action_classifier import is_no_action_approval_action
|
||||
from src.services.approval_db import get_approval_service, get_timeline_service
|
||||
from src.services.awooop_deeplinks import incident_truth_chain_button_row
|
||||
from src.services.executor import ExecutionResult, OperationType, get_executor
|
||||
from src.services.operator_outcome import build_operator_outcome
|
||||
from src.services.operation_parser import parse_operation_from_action
|
||||
from src.services.operator_outcome import build_operator_outcome
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.services.notifications import ExecutionStatus
|
||||
@@ -1046,24 +1046,19 @@ class ApprovalExecutionService:
|
||||
km_info=km_info,
|
||||
outcome=outcome,
|
||||
)
|
||||
reply_markup = {
|
||||
"inline_keyboard": [[
|
||||
{
|
||||
"text": "🧭 AwoooP",
|
||||
"url": (
|
||||
"https://awoooi.wooo.work/zh-TW/awooop/runs"
|
||||
f"?project_id=awoooi&incident_id={quote(approval.incident_id or '')}"
|
||||
),
|
||||
}
|
||||
]]
|
||||
}
|
||||
truth_chain_row = incident_truth_chain_button_row(
|
||||
approval.incident_id or ""
|
||||
)
|
||||
payload: dict[str, Any] = {
|
||||
"chat_id": target_chat_id,
|
||||
"text": text,
|
||||
"parse_mode": "HTML",
|
||||
"reply_markup": reply_markup,
|
||||
"disable_web_page_preview": True,
|
||||
}
|
||||
if truth_chain_row:
|
||||
payload["reply_markup"] = {
|
||||
"inline_keyboard": [truth_chain_row],
|
||||
}
|
||||
if orig_msg_id is not None:
|
||||
payload["reply_to_message_id"] = orig_msg_id
|
||||
|
||||
|
||||
63
apps/api/src/services/awooop_deeplinks.py
Normal file
63
apps/api/src/services/awooop_deeplinks.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Shared AwoooP operator deeplinks used by Telegram-facing services."""
|
||||
|
||||
from urllib.parse import quote
|
||||
|
||||
AWOOOP_WEB_BASE_URL = "https://awoooi.wooo.work"
|
||||
|
||||
|
||||
def incident_runs_url(
|
||||
incident_id: str,
|
||||
*,
|
||||
project_id: str = "awoooi",
|
||||
locale: str = "zh-TW",
|
||||
) -> str:
|
||||
safe_project_id = quote(str(project_id or "awoooi"), safe="")
|
||||
safe_incident_id = quote(str(incident_id or ""), safe="")
|
||||
return (
|
||||
f"{AWOOOP_WEB_BASE_URL}/{locale}/awooop/runs"
|
||||
f"?project_id={safe_project_id}&incident_id={safe_incident_id}"
|
||||
)
|
||||
|
||||
|
||||
def incident_alerts_url(
|
||||
incident_id: str,
|
||||
*,
|
||||
project_id: str = "awoooi",
|
||||
locale: str = "zh-TW",
|
||||
) -> str:
|
||||
safe_project_id = quote(str(project_id or "awoooi"), safe="")
|
||||
safe_incident_id = quote(str(incident_id or ""), safe="")
|
||||
return (
|
||||
f"{AWOOOP_WEB_BASE_URL}/{locale}/alerts"
|
||||
f"?project_id={safe_project_id}&incident_id={safe_incident_id}"
|
||||
)
|
||||
|
||||
|
||||
def incident_truth_chain_button_row(
|
||||
incident_id: str,
|
||||
*,
|
||||
project_id: str = "awoooi",
|
||||
) -> list[dict[str, str]]:
|
||||
if not incident_id:
|
||||
return []
|
||||
return [
|
||||
{
|
||||
"text": "🔎 真相鏈",
|
||||
"url": incident_alerts_url(incident_id, project_id=project_id),
|
||||
},
|
||||
{
|
||||
"text": "🧭 Runs",
|
||||
"url": incident_runs_url(incident_id, project_id=project_id),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def incident_truth_chain_reply_markup(
|
||||
incident_id: str,
|
||||
*,
|
||||
project_id: str = "awoooi",
|
||||
) -> dict | None:
|
||||
row = incident_truth_chain_button_row(incident_id, project_id=project_id)
|
||||
if not row:
|
||||
return None
|
||||
return {"inline_keyboard": [row]}
|
||||
@@ -30,7 +30,6 @@ import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from urllib.parse import quote
|
||||
from uuid import NAMESPACE_URL, UUID, uuid5
|
||||
|
||||
import httpx
|
||||
@@ -40,13 +39,19 @@ from opentelemetry import trace
|
||||
from src.core.config import settings
|
||||
from src.core.redis_client import get_redis
|
||||
from src.services.awooop_ansible_audit_service import summarize_ansible_execution
|
||||
from src.services.awooop_deeplinks import (
|
||||
incident_alerts_url,
|
||||
incident_runs_url,
|
||||
incident_truth_chain_button_row,
|
||||
incident_truth_chain_reply_markup,
|
||||
)
|
||||
from src.services.chat_manager import get_chat_manager
|
||||
from src.services.operator_outcome import build_operator_outcome
|
||||
from src.services.security_interceptor import (
|
||||
NonceReplayError,
|
||||
UserNotWhitelistedError,
|
||||
get_security_interceptor,
|
||||
)
|
||||
from src.services.chat_manager import get_chat_manager
|
||||
|
||||
# =============================================================================
|
||||
# Snooze/Silence Redis Keys (2026-03-27 P1 優化)
|
||||
@@ -78,7 +83,6 @@ _AI_ADVISORY_CALLBACK_RE = re.compile(
|
||||
)
|
||||
_CODE_REF_RE = re.compile(r"<code>([0-9a-f]{7,12})</code>", re.IGNORECASE)
|
||||
_TELEGRAM_HTML_CHUNK_LIMIT = 3600
|
||||
_AWOOOP_WEB_BASE_URL = "https://awoooi.wooo.work"
|
||||
_AWOOOP_SOURCE_ENVELOPE_EXTRA_KEY = "_awooop_source_envelope_extra"
|
||||
|
||||
|
||||
@@ -249,27 +253,32 @@ def _format_remediation_history_lines(history: dict[str, object] | None) -> list
|
||||
|
||||
|
||||
def _awooop_runs_url_for_incident(incident_id: str) -> str:
|
||||
safe_incident_id = quote(str(incident_id or ""), safe="")
|
||||
return (
|
||||
f"{_AWOOOP_WEB_BASE_URL}/zh-TW/awooop/runs"
|
||||
f"?project_id=awoooi&incident_id={safe_incident_id}"
|
||||
)
|
||||
return incident_runs_url(incident_id)
|
||||
|
||||
|
||||
def _awooop_alerts_url_for_incident(incident_id: str) -> str:
|
||||
return incident_alerts_url(incident_id)
|
||||
|
||||
|
||||
def _awooop_runs_button_row(incident_id: str) -> list[dict[str, str]]:
|
||||
if not incident_id:
|
||||
return []
|
||||
return [{
|
||||
"text": "🧭 AwoooP",
|
||||
"text": "🧭 Runs",
|
||||
"url": _awooop_runs_url_for_incident(incident_id),
|
||||
}]
|
||||
|
||||
|
||||
def _awooop_truth_chain_button_row(incident_id: str) -> list[dict[str, str]]:
|
||||
return incident_truth_chain_button_row(incident_id)
|
||||
|
||||
|
||||
def _awooop_truth_chain_reply_markup(incident_id: str) -> dict | None:
|
||||
return incident_truth_chain_reply_markup(incident_id)
|
||||
|
||||
|
||||
def _awooop_runs_reply_markup(incident_id: str) -> dict | None:
|
||||
row = _awooop_runs_button_row(incident_id)
|
||||
if not row:
|
||||
return None
|
||||
return {"inline_keyboard": [row]}
|
||||
return _awooop_truth_chain_reply_markup(incident_id)
|
||||
|
||||
|
||||
def _latest_remediation_history_item(history: dict[str, object] | None) -> dict[str, object]:
|
||||
@@ -3578,7 +3587,7 @@ class TelegramGateway:
|
||||
tuning_nonce = self._security.generate_callback_nonce(approval_id, "tune")
|
||||
buttons.append([{"text": "⚡ 執行自動調優", "callback_data": tuning_nonce}])
|
||||
|
||||
awooop_row = _awooop_runs_button_row(incident_id)
|
||||
awooop_row = _awooop_truth_chain_button_row(incident_id)
|
||||
if awooop_row:
|
||||
buttons.append(awooop_row)
|
||||
|
||||
@@ -3627,7 +3636,7 @@ class TelegramGateway:
|
||||
{"text": "📋 詳情", "callback_data": f"detail:{incident_id}"},
|
||||
{"text": "🔕 忽略", "callback_data": silence_nonce},
|
||||
])
|
||||
awooop_row = _awooop_runs_button_row(incident_id)
|
||||
awooop_row = _awooop_truth_chain_button_row(incident_id)
|
||||
if awooop_row:
|
||||
rows.append(awooop_row)
|
||||
buttons = rows
|
||||
@@ -3647,7 +3656,7 @@ class TelegramGateway:
|
||||
{"text": "🔄 重診", "callback_data": f"reanalyze:{incident_id}"},
|
||||
{"text": "📊 歷史", "callback_data": f"history:{incident_id}"},
|
||||
])
|
||||
awooop_row = _awooop_runs_button_row(incident_id)
|
||||
awooop_row = _awooop_truth_chain_button_row(incident_id)
|
||||
if awooop_row:
|
||||
buttons.append(awooop_row)
|
||||
|
||||
@@ -4242,7 +4251,7 @@ class TelegramGateway:
|
||||
{"text": "📋 詳情", "callback_data": f"detail:{incident_id}"},
|
||||
{"text": "📊 歷史", "callback_data": f"history:{incident_id}"},
|
||||
]]
|
||||
awooop_row = _awooop_runs_button_row(incident_id)
|
||||
awooop_row = _awooop_truth_chain_button_row(incident_id)
|
||||
if awooop_row:
|
||||
inline_keyboard.append(awooop_row)
|
||||
keyboard = {"inline_keyboard": inline_keyboard}
|
||||
@@ -6628,7 +6637,7 @@ class TelegramGateway:
|
||||
{"text": "📊 歷史", "callback_data": f"history:{incident_id}"},
|
||||
],
|
||||
]
|
||||
awooop_row = _awooop_runs_button_row(incident_id)
|
||||
awooop_row = _awooop_truth_chain_button_row(incident_id)
|
||||
if awooop_row:
|
||||
inline_keyboard.append(awooop_row)
|
||||
new_keyboard = {"inline_keyboard": inline_keyboard}
|
||||
@@ -6997,7 +7006,7 @@ class TelegramGateway:
|
||||
await self._send_html_line_message(
|
||||
lines,
|
||||
failure_context="incident_detail",
|
||||
reply_markup=_awooop_runs_reply_markup(incident_id),
|
||||
reply_markup=_awooop_truth_chain_reply_markup(incident_id),
|
||||
incident_id=incident_id,
|
||||
callback_action="detail",
|
||||
km_stale_completion_summary=km_completion_summary,
|
||||
@@ -7182,7 +7191,7 @@ class TelegramGateway:
|
||||
await self._send_html_line_message(
|
||||
lines,
|
||||
failure_context="incident_history",
|
||||
reply_markup=_awooop_runs_reply_markup(incident_id),
|
||||
reply_markup=_awooop_truth_chain_reply_markup(incident_id),
|
||||
incident_id=incident_id,
|
||||
callback_action="history",
|
||||
km_stale_completion_summary=km_completion_summary,
|
||||
@@ -8580,7 +8589,7 @@ class TelegramGateway:
|
||||
{"text": "📋 詳情", "callback_data": f"detail:{incident_id}"},
|
||||
{"text": "📊 歷史", "callback_data": f"history:{incident_id}"},
|
||||
]]
|
||||
awooop_row = _awooop_runs_button_row(incident_id)
|
||||
awooop_row = _awooop_truth_chain_button_row(incident_id)
|
||||
if awooop_row:
|
||||
info_buttons.append(awooop_row)
|
||||
await self._send_request(
|
||||
|
||||
Reference in New Issue
Block a user