""" Slack Action für Alerts Agent. Sendet Slack-Nachrichten für Alerts via Incoming Webhooks. """ import logging from typing import Dict, Any, List import httpx from .base import ActionHandler, ActionResult, ActionType, AlertContext logger = logging.getLogger(__name__) class SlackAction(ActionHandler): """ Slack-Benachrichtigungen für Alerts via Incoming Webhooks. Konfiguration: - webhook_url: Slack Incoming Webhook URL - channel: Optional - Channel überschreiben - username: Optional - Bot-Username (default: BreakPilot Alerts) - icon_emoji: Optional - Bot-Icon (default: :bell:) """ @property def action_type(self) -> ActionType: return ActionType.SLACK def get_required_config_fields(self) -> List[str]: return ["webhook_url"] def validate_config(self, config: Dict[str, Any]) -> bool: url = config.get("webhook_url", "") return "hooks.slack.com" in url or url.startswith("https://") async def execute( self, context: AlertContext, config: Dict[str, Any], ) -> ActionResult: """ Sendet eine Slack-Nachricht. Args: context: Alert-Kontext config: Slack-Konfiguration (webhook_url, channel, etc.) Returns: ActionResult """ try: webhook_url = config.get("webhook_url") # Slack-Payload mit Block Kit payload = self._build_slack_payload(context, config) # Request senden async with httpx.AsyncClient(timeout=30) as client: response = await client.post( webhook_url, json=payload, headers={"Content-Type": "application/json"}, ) # Slack gibt "ok" als Text zurück bei Erfolg success = response.status_code == 200 and response.text == "ok" return ActionResult( success=success, action_type=self.action_type, message="Slack-Nachricht gesendet" if success else "Slack-Fehler", details={ "status_code": response.status_code, "response": response.text[:100], }, error=None if success else response.text, ) except Exception as e: logger.error(f"Slack action error: {e}") return ActionResult( success=False, action_type=self.action_type, message="Slack-Fehler", error=str(e), ) def _build_slack_payload( self, context: AlertContext, config: Dict[str, Any], ) -> Dict[str, Any]: """ Erstellt den Slack-Payload mit Block Kit. Verwendet Rich-Formatting für bessere Darstellung. """ # Basis-Payload payload = { "username": config.get("username", "BreakPilot Alerts"), "icon_emoji": config.get("icon_emoji", ":bell:"), } # Channel überschreiben wenn angegeben if config.get("channel"): payload["channel"] = config["channel"] # Block Kit Blocks blocks = [ # Header { "type": "header", "text": { "type": "plain_text", "text": f"📰 {context.topic_name}", "emoji": True, } }, # Alert-Titel als Link { "type": "section", "text": { "type": "mrkdwn", "text": f"*<{context.url}|{context.title}>*", } }, ] # Snippet wenn vorhanden if context.snippet: snippet = context.snippet[:200] if len(context.snippet) > 200: snippet += "..." blocks.append({ "type": "section", "text": { "type": "plain_text", "text": snippet, "emoji": False, } }) # Kontext-Felder (Score, Decision, Rule) fields = [] if context.relevance_score is not None: score_emoji = "🟢" if context.relevance_score >= 0.7 else "🟡" if context.relevance_score >= 0.4 else "🔴" fields.append({ "type": "mrkdwn", "text": f"*Score:* {score_emoji} {context.relevance_score:.0%}", }) if context.relevance_decision: decision_emoji = {"KEEP": "✅", "DROP": "❌", "REVIEW": "👀"}.get(context.relevance_decision, "") fields.append({ "type": "mrkdwn", "text": f"*Decision:* {decision_emoji} {context.relevance_decision}", }) if context.matched_rule: fields.append({ "type": "mrkdwn", "text": f"*Regel:* {context.matched_rule}", }) if context.tags: fields.append({ "type": "mrkdwn", "text": f"*Tags:* {', '.join(context.tags)}", }) if fields: blocks.append({ "type": "section", "fields": fields[:10], # Max 10 Felder }) # Divider blocks.append({"type": "divider"}) # Actions (Link zur Inbox) blocks.append({ "type": "context", "elements": [ { "type": "mrkdwn", "text": f"<{config.get('dashboard_url', 'http://localhost:8000/studio#alerts')}|Zur Alerts Inbox> | Gesendet von BreakPilot", } ] }) payload["blocks"] = blocks # Fallback-Text für Notifications payload["text"] = f"Neuer Alert: {context.title}" return payload