"""Slack interactivity Lambda: drill-down modals behind API Gateway. Fronted by the ``apm-wo.seahaven.com`` HTTP API (Slack interactivity request URL). Verifies the Slack request signature, then for a category/site drill button reads the day's ``details.json``, filters it, and opens a WO-list modal via ``views.open`` within Slack's 3-second ``trigger_id`` window. WO rows link to Grafana only — no APM deep-links (see Phase 4 decisions). """ from __future__ import annotations import base64 import json from datetime import datetime, timezone from urllib.parse import parse_qs import blockkit import slackio _FILTER_FIELD = {"drill_category": "category", "drill_site": "site"} def _raw_body(event: dict) -> str: body = event.get("body") or "" if event.get("isBase64Encoded"): body = base64.b64decode(body).decode("utf-8") return body def _headers(event: dict) -> dict: # API Gateway HTTP API lowercases header names; be defensive either way. return {k.lower(): v for k, v in (event.get("headers") or {}).items()} def handler(event, context): """Verify the Slack signature and open the requested drill-down modal.""" body = _raw_body(event) headers = _headers(event) timestamp = headers.get("x-slack-request-timestamp", "") signature = headers.get("x-slack-signature", "") if not slackio.verify_signature(body, timestamp, signature): print("Rejected: invalid Slack signature.") return {"statusCode": 401, "body": "invalid signature"} parsed = parse_qs(body) payload = json.loads(parsed.get("payload", ["{}"])[0]) if payload.get("type") != "block_actions": return {"statusCode": 200, "body": ""} action = (payload.get("actions") or [{}])[0] # action_id is qualified for Slack uniqueness, e.g. "drill_category:Report / # Docs Needed" — match on the prefix before ":"; the filter value is in `value`. action_kind = (action.get("action_id") or "").split(":", 1)[0] field = _FILTER_FIELD.get(action_kind) value = action.get("value") trigger_id = payload.get("trigger_id") if not field or not value or not trigger_id: return {"statusCode": 200, "body": ""} # The daily post is same-day; default the drill to today's snapshot. dt = datetime.now(timezone.utc).strftime("%Y-%m-%d") details = slackio.read_meta_json(dt, "details.json") or [] wos = [d for d in details if d.get(field) == value] view = blockkit.build_wo_modal( title=f"{value} ({len(wos)})", wos=wos, dashboard_url=slackio.get_dashboard_url(), ) slackio.web_client().views_open(trigger_id=trigger_id, view=view) return {"statusCode": 200, "body": ""}