Skip to content

Commit

Permalink
Merge branch 'master' into metrica-retries
Browse files Browse the repository at this point in the history
  • Loading branch information
Vladislav Denisov authored Oct 3, 2023
2 parents 3b2505f + 09ec299 commit 64bbc10
Show file tree
Hide file tree
Showing 11 changed files with 500 additions and 305 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.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"bootstrap": "^3.3.7",
"classnames": "^2.2.6",
"d3": "^3.5.17",
"debug": "^3.1.0",
"debug": "^3.2.7",
"dompurify": "^2.0.17",
"font-awesome": "^4.7.0",
"history": "^4.10.1",
Expand Down
587 changes: 327 additions & 260 deletions poetry.lock

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ authlib = "0.15.5"
backoff = "2.2.1"
blinker = "1.6.2"
click = "8.1.3"
cryptography = "41.0.3"
cryptography = "41.0.4"
disposable-email-domains = ">=0.0.52"
flask = "2.3.2"
flask-limiter = "3.3.1"
Expand All @@ -41,8 +41,8 @@ flask-sqlalchemy = "2.5.1"
flask-talisman = "0.7.0"
flask-wtf = "1.1.1"
funcy = "1.13"
gevent = "22.10.1"
greenlet = "1.1.3"
gevent = "23.9.1"
greenlet = "2.0.2"
gunicorn = "20.0.4"
httplib2 = "0.19.0"
itsdangerous = "2.1.2"
Expand Down Expand Up @@ -73,14 +73,14 @@ sentry-sdk = "1.28.1"
simplejson = "3.16.0"
sqlalchemy = "1.3.24"
sqlalchemy-searchable = "1.2.0"
sqlalchemy-utils = "0.36.5"
sqlalchemy-utils = "0.34.2"
sqlparse = "0.4.4"
sshtunnel = "0.1.5"
statsd = "3.3.0"
supervisor = "4.1.0"
supervisor-checks = "0.8.1"
ua-parser = "0.18.0"
urllib3 = "1.26.16"
urllib3 = "1.26.17"
user-agents = "2.0"
werkzeug = "2.3.6"
wtforms = "2.2.1"
Expand All @@ -100,7 +100,7 @@ cmem-cmempy = "21.2.3"
databend-py = "0.4.6"
databend-sqlalchemy = "0.2.4"
google-api-python-client = "1.7.11"
gspread = "3.1.0"
gspread = "5.11.2"
impyla = "0.16.0"
influxdb = "5.2.3"
memsql = "3.2.0"
Expand Down Expand Up @@ -142,7 +142,7 @@ xlrd = "2.0.1"
optional = true

[tool.poetry.group.ldap3.dependencies]
ldap3 = "2.2.4"
ldap3 = "2.9.1"

[tool.poetry.group.dev]
optional = true
Expand Down
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/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,7 @@ def update_latest_result(cls, query_result):
queries = Query.query.filter(
Query.query_hash == query_result.query_hash,
Query.data_source == query_result.data_source,
Query.is_archived.is_(False),
)

for q in queries:
Expand Down
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
4 changes: 2 additions & 2 deletions tests/models/test_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ def setUp(self):
def test_updates_existing_queries(self):
query1 = self.factory.create_query(query_text=self.query)
query2 = self.factory.create_query(query_text=self.query)
query3 = self.factory.create_query(query_text=self.query)
query3 = self.factory.create_query(query_text=self.query, is_archived=True)

query_result = QueryResult.store_result(
self.data_source.org_id,
Expand All @@ -478,7 +478,7 @@ def test_updates_existing_queries(self):

self.assertEqual(query1.latest_query_data, query_result)
self.assertEqual(query2.latest_query_data, query_result)
self.assertEqual(query3.latest_query_data, query_result)
self.assertEqual(query3.latest_query_data, None)

def test_doesnt_update_queries_with_different_hash(self):
query1 = self.factory.create_query(query_text=self.query)
Expand Down
18 changes: 2 additions & 16 deletions viz-lib/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3720,28 +3720,14 @@ debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"

debug@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"

debug@^3.2.6:
debug@^3.1.0, debug@^3.2.6:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
dependencies:
ms "^2.1.1"

debug@^4.0.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies:
ms "2.1.2"

debug@^4.1.0, debug@^4.1.1:
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
Expand Down
24 changes: 5 additions & 19 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4956,31 +4956,17 @@ [email protected], debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
dependencies:
ms "2.0.0"

debug@4:
version "4.2.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
dependencies:
ms "2.1.2"

[email protected], debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
debug@4, [email protected], debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"

debug@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"

debug@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
debug@^3.1.0, debug@^3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
dependencies:
ms "^2.1.1"

Expand Down

0 comments on commit 64bbc10

Please sign in to comment.