|
1 | 1 | <!-- search-meta |
2 | 2 | tags: [KPI-monitor, webhook, Slack, Python, FastAPI, alerts, notifications] |
3 | | -apis: [KPIMonitorWebhook, SlackWebhookAPI, FastAPI] |
| 3 | +apis: [KPIMonitorWebhookPayload, SlackWebhookAPI, FastAPI, notificationType, scheduledMetricUpdateWebhookNotification] |
4 | 4 | questions: |
5 | 5 | - How do I send ThoughtSpot KPI monitor alerts to Slack using Python? |
6 | 6 | - How do I build a Python FastAPI webhook for ThoughtSpot KPI alerts? |
7 | 7 | - How do I handle ThoughtSpot KPI webhook payloads in Python? |
8 | 8 | - How do I integrate ThoughtSpot KPI monitor with Slack using FastAPI? |
| 9 | + - How do I filter ThoughtSpot webhook notification types in Python? |
| 10 | + - How do I send a rich Slack block kit message from a ThoughtSpot KPI alert? |
9 | 11 | --> |
10 | 12 |
|
11 | 13 | # Slack webhook (Python) |
12 | 14 |
|
13 | | -A simple webhook made with Python and FastAPI that forwards a KPI monitor alert through webhooks to a Slack channel. The message would look like this: |
| 15 | +A webhook built with Python and FastAPI that forwards ThoughtSpot KPI monitor alerts to a Slack channel as a rich block kit message. The message would look like this: |
14 | 16 |
|
15 | 17 |  |
16 | 18 |
|
17 | | -This app exposes a single endpoint `/send-to-slack` that accepts POST requests from KPI Monitor. It then builds a Slack message and sends it to a Slack channel configured in the `.env` file. |
| 19 | +This app exposes a single endpoint `/send-to-slack` that accepts POST requests from KPI Monitor. It filters by notification type, extracts metric details from the payload, and sends a formatted Slack message with metric info and action buttons. |
18 | 20 |
|
19 | 21 | ## Key Usage |
20 | 22 |
|
21 | 23 | ```python |
22 | | -from fastapi import FastAPI |
| 24 | +from fastapi import FastAPI, HTTPException |
23 | 25 | from slack_sdk import WebClient |
| 26 | +from pydantic import BaseModel |
| 27 | +from typing import Dict, Any |
| 28 | +import os |
24 | 29 |
|
25 | 30 | app = FastAPI() |
26 | | -slack = WebClient(token=os.environ["SLACK_TOKEN"]) |
| 31 | +slack_client = WebClient(token=os.getenv("SLACK_TOKEN")) |
| 32 | +slack_channel = os.getenv("SLACK_CHANNEL") |
| 33 | + |
| 34 | +# Only forward real alert updates — ignore subscription/test events |
| 35 | +UPDATION_NOTIFICATION_TYPES = { |
| 36 | + "SCHEDULE_METRIC_UPDATE", |
| 37 | + "THRESHOLD_METRIC_UPDATE", |
| 38 | + "AUTOMATIC_SELF_SUBSCRIPTION", |
| 39 | + "THRESHOLD_BY_ATTRIBUTE", |
| 40 | + "SCHEDULED_BY_ATTRIBUTE", |
| 41 | + "ANOMALY_METRIC_UPDATE", |
| 42 | +} |
| 43 | + |
| 44 | +class WebhookPayload(BaseModel): |
| 45 | + data: Dict[str, Any] |
27 | 46 |
|
28 | | -# ThoughtSpot KPI Monitor calls this endpoint when an alert fires |
29 | 47 | @app.post("/send-to-slack") |
30 | | -async def send_to_slack(payload: dict): |
31 | | - data = payload["data"]["scheduledMetricUpdateWebhookNotification"] |
32 | | - rule_name = data["monitorRuleForWebhook"]["ruleName"] |
33 | | - change = data["ruleExecutionDetails"]["percentageChange"] |
34 | | - |
35 | | - slack.chat_postMessage( |
36 | | - channel=os.environ["SLACK_CHANNEL"], |
37 | | - text=f"📊 Alert: {rule_name} — {change}% change detected", |
| 48 | +async def send_to_slack(payload: WebhookPayload): |
| 49 | + data = payload.data |
| 50 | + notification_type = data.get("notificationType") |
| 51 | + |
| 52 | + if notification_type not in UPDATION_NOTIFICATION_TYPES: |
| 53 | + return {"message": "Not forwarding — not an alert update notification"} |
| 54 | + |
| 55 | + notification = data.get("scheduledMetricUpdateWebhookNotification", {}) |
| 56 | + monitor_rule = notification.get("monitorRuleForWebhook") |
| 57 | + rule_execution_details = notification.get("ruleExecutionDetails") |
| 58 | + current_user = data.get("currentUser") |
| 59 | + modify_url = notification.get("modifyUrl") |
| 60 | + unsubscribe_url = notification.get("unsubscribeUrl") |
| 61 | + |
| 62 | + if not all([monitor_rule, rule_execution_details, current_user, modify_url, unsubscribe_url]): |
| 63 | + raise HTTPException(status_code=400, detail="Invalid payload structure") |
| 64 | + |
| 65 | + # Rich Slack block kit message with metric details and action buttons |
| 66 | + slack_client.chat_postMessage( |
| 67 | + channel=slack_channel, |
| 68 | + text=f"Alert: {monitor_rule['ruleName']} - {rule_execution_details['percentageChange']} change detected", |
| 69 | + blocks=[ |
| 70 | + {"type": "header", "text": {"type": "plain_text", "text": f"📊 {monitor_rule['ruleName']}", "emoji": True}}, |
| 71 | + { |
| 72 | + "type": "section", |
| 73 | + "fields": [ |
| 74 | + {"type": "mrkdwn", "text": f"*Metric:*\n<{monitor_rule['metricUrl']}|{monitor_rule['metricName']}>"}, |
| 75 | + {"type": "mrkdwn", "text": f"*Change:*\n{rule_execution_details['percentageChange']}"}, |
| 76 | + {"type": "mrkdwn", "text": f"*New Value:*\n{rule_execution_details['currentMetricValue']}"}, |
| 77 | + {"type": "mrkdwn", "text": f"*Period:*\n{rule_execution_details['executionTimestamp']}"}, |
| 78 | + {"type": "mrkdwn", "text": f"*Schedule:*\n{monitor_rule['scheduleString']}"}, |
| 79 | + {"type": "mrkdwn", "text": f"*Triggered By:*\n{current_user['displayName']} ({current_user['email']})"}, |
| 80 | + ], |
| 81 | + }, |
| 82 | + { |
| 83 | + "type": "actions", |
| 84 | + "elements": [ |
| 85 | + {"type": "button", "text": {"type": "plain_text", "text": "View Metric", "emoji": True}, "url": monitor_rule["metricUrl"]}, |
| 86 | + {"type": "button", "text": {"type": "plain_text", "text": "Modify Alert", "emoji": True}, "url": modify_url}, |
| 87 | + {"type": "button", "text": {"type": "plain_text", "text": "Unsubscribe", "emoji": True}, "url": unsubscribe_url, "style": "danger"}, |
| 88 | + ], |
| 89 | + }, |
| 90 | + ], |
38 | 91 | ) |
39 | | - return {"ok": True} |
| 92 | + return {"message": f"Message forwarded to Slack channel: {slack_channel}"} |
40 | 93 | ``` |
41 | 94 |
|
42 | 95 | File structure: |
@@ -112,4 +165,4 @@ The above steps will start the server on port 3000 on your local machine. Now we |
112 | 165 | - Python |
113 | 166 | - FastAPI |
114 | 167 | - Slack |
115 | | -- Webhook |
| 168 | +- Webhook |
0 commit comments