Skip to content

Commit

Permalink
Add Datadog alert destination (#6476)
Browse files Browse the repository at this point in the history
* Add Datadog alert destination

* Fix Datadog Event API response code: 200 -> 202

* Add datadog alert dest test

* Sort test_destinations.py imports

* Fix test_datadog_notify_calls_requests_post

* Fix datadog alert dest: Add aggregation_key
  • Loading branch information
winebarrel authored Sep 28, 2023
1 parent 3ebf163 commit 4a36abc
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 0 deletions.
Binary file added client/app/assets/images/destinations/datadog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
93 changes: 93 additions & 0 deletions redash/destinations/datadog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import logging
import os

import requests

from redash.destinations import BaseDestination, register
from redash.utils import json_dumps


class Datadog(BaseDestination):
@classmethod
def configuration_schema(cls):
return {
"type": "object",
"properties": {
"api_key": {"type": "string", "title": "API Key"},
"tags": {"type": "string", "title": "Tags"},
"priority": {"type": "string", "default": "normal", "title": "Priority"},
# https://docs.datadoghq.com/integrations/faq/list-of-api-source-attribute-value/
"source_type_name": {"type": "string", "default": "my_apps", "title": "Source Type Name"},
},
"secret": ["api_key"],
"required": ["api_key"],
}

@classmethod
def icon(cls):
return "fa-datadog"

def notify(self, alert, query, user, new_state, app, host, metadata, options):
# Documentation: https://docs.datadoghq.com/api/latest/events/#post-an-event
if new_state == "triggered":
alert_type = "error"
if alert.custom_subject:
title = alert.custom_subject
else:
title = f"{alert.name} just triggered"
else:
alert_type = "success"
if alert.custom_subject:
title = alert.custom_subject
else:
title = f"{alert.name} went back to normal"

if alert.custom_body:
text = alert.custom_body
else:
text = f"{alert.name} changed state to {new_state}."

query_url = f"{host}/queries/{query.id}"
alert_url = f"{host}/alerts/{alert.id}"
text += f"\nQuery: {query_url}\nAlert: {alert_url}"

headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"DD-API-KEY": options.get("api_key"),
}

body = {
"title": title,
"text": text,
"alert_type": alert_type,
"priority": options.get("priority"),
"source_type_name": options.get("source_type_name"),
"aggregation_key": f"redash:{alert_url}",
"tags": [],
}

tags = options.get("tags")
if tags:
body["tags"] = tags.split(",")
body["tags"].extend(
[
"redash",
f"query_id:{query.id}",
f"alert_id:{alert.id}",
]
)

dd_host = os.getenv("DATADOG_HOST", "api.datadoghq.com")
url = f"https://{dd_host}/api/v1/events"

try:
resp = requests.post(url, headers=headers, data=json_dumps(body), timeout=5.0)
logging.warning(resp.text)
if resp.status_code != 202:
logging.error(f"Datadog send ERROR. status_code => {resp.status_code}")
except Exception as e:
logging.exception("Datadog send ERROR: %s", e)


register(Datadog)
1 change: 1 addition & 0 deletions redash/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ def email_server_is_configured():
"redash.destinations.microsoft_teams_webhook",
"redash.destinations.asana",
"redash.destinations.webex",
"redash.destinations.datadog",
]

enabled_destinations = array_from_string(os.environ.get("REDASH_ENABLED_DESTINATIONS", ",".join(default_destinations)))
Expand Down
61 changes: 61 additions & 0 deletions tests/handlers/test_destinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from unittest import mock

from redash.destinations.asana import Asana
from redash.destinations.datadog import Datadog
from redash.destinations.discord import Discord
from redash.destinations.webex import Webex
from redash.models import Alert, NotificationDestination
Expand Down Expand Up @@ -247,3 +248,63 @@ def test_webex_notify_calls_requests_post():
)

assert mock_response.status_code == 204


def test_datadog_notify_calls_requests_post():
alert = mock.Mock(spec_set=["id", "name", "custom_subject", "custom_body", "render_template"])
alert.id = 1
alert.name = "Test Alert"
alert.custom_subject = "Test custom subject"
alert.custom_body = "Test custom body"
alert.render_template = mock.Mock(return_value={"Rendered": "template"})
query = mock.Mock()
query.id = 1

user = mock.Mock()
app = mock.Mock()
host = "https://localhost:5000"
options = {
"api_key": "my-api-key",
"tags": "foo:bar,zoo:baz",
"priority": "normal",
"source_type_name": "postgres",
}
metadata = {"Scheduled": False}
new_state = Alert.TRIGGERED_STATE
destination = Datadog(options)

with mock.patch("redash.destinations.datadog.requests.post") as mock_post:
mock_response = mock.Mock()
mock_response.status_code = 202
mock_post.return_value = mock_response

destination.notify(alert, query, user, new_state, app, host, metadata, options)

expected_payload = {
"title": "Test custom subject",
"text": "Test custom body\nQuery: https://localhost:5000/queries/1\nAlert: https://localhost:5000/alerts/1",
"alert_type": "error",
"priority": "normal",
"source_type_name": "postgres",
"aggregation_key": "redash:https://localhost:5000/alerts/1",
"tags": [
"foo:bar",
"zoo:baz",
"redash",
"query_id:1",
"alert_id:1",
],
}

mock_post.assert_called_once_with(
"https://api.datadoghq.com/api/v1/events",
data=json.dumps(expected_payload),
headers={
"Accept": "application/json",
"Content-Type": "application/json",
"DD-API-KEY": "my-api-key",
},
timeout=5.0,
)

assert mock_response.status_code == 202

0 comments on commit 4a36abc

Please sign in to comment.