Passing Request metadata to Alert destinations (#5230)

This commit is contained in:
Ivan Torgashov
2023-08-24 08:08:52 +05:00
committed by GitHub
parent 5eeeb5c62e
commit e18cd8f248
15 changed files with 28 additions and 26 deletions

View File

@@ -31,7 +31,7 @@ class BaseDestination(object):
def configuration_schema(cls): def configuration_schema(cls):
return {} return {}
def notify(self, alert, query, user, new_state, app, host, options): def notify(self, alert, query, user, new_state, app, host, metadata, options):
raise NotImplementedError() raise NotImplementedError()
@classmethod @classmethod

View File

@@ -29,7 +29,7 @@ class ChatWork(BaseDestination):
def icon(cls): def icon(cls):
return "fa-comment" return "fa-comment"
def notify(self, alert, query, user, new_state, app, host, options): def notify(self, alert, query, user, new_state, app, host, metadata, options):
try: try:
# Documentation: http://developer.chatwork.com/ja/endpoint_rooms.html#POST-rooms-room_id-messages # Documentation: http://developer.chatwork.com/ja/endpoint_rooms.html#POST-rooms-room_id-messages
url = "https://api.chatwork.com/v2/rooms/{room_id}/messages".format(room_id=options.get("room_id")) url = "https://api.chatwork.com/v2/rooms/{room_id}/messages".format(room_id=options.get("room_id"))

View File

@@ -28,7 +28,7 @@ class Discord(BaseDestination):
def icon(cls): def icon(cls):
return "fa-discord" return "fa-discord"
def notify(self, alert, query, user, new_state, app, host, options): def notify(self, alert, query, user, new_state, app, host, metadata, options):
# Documentation: https://birdie0.github.io/discord-webhooks-guide/discord_webhook.html # Documentation: https://birdie0.github.io/discord-webhooks-guide/discord_webhook.html
fields = [ fields = [
{ {

View File

@@ -27,7 +27,7 @@ class Email(BaseDestination):
def icon(cls): def icon(cls):
return "fa-envelope" return "fa-envelope"
def notify(self, alert, query, user, new_state, app, host, options): def notify(self, alert, query, user, new_state, app, host, metadata, options):
recipients = [email for email in options.get("addresses", "").split(",") if email] recipients = [email for email in options.get("addresses", "").split(",") if email]
if not recipients: if not recipients:

View File

@@ -37,7 +37,7 @@ class HangoutsChat(BaseDestination):
def icon(cls): def icon(cls):
return "fa-bolt" return "fa-bolt"
def notify(self, alert, query, user, new_state, app, host, options): def notify(self, alert, query, user, new_state, app, host, metadata, options):
try: try:
if new_state == "triggered": if new_state == "triggered":
message = '<b><font color="#c0392b">Triggered</font></b>' message = '<b><font color="#c0392b">Triggered</font></b>'

View File

@@ -24,7 +24,7 @@ class Mattermost(BaseDestination):
def icon(cls): def icon(cls):
return "fa-bolt" return "fa-bolt"
def notify(self, alert, query, user, new_state, app, host, options): def notify(self, alert, query, user, new_state, app, host, metadata, options):
if alert.custom_subject: if alert.custom_subject:
text = alert.custom_subject text = alert.custom_subject
elif new_state == "triggered": elif new_state == "triggered":

View File

@@ -74,7 +74,7 @@ class MicrosoftTeamsWebhook(BaseDestination):
def icon(cls): def icon(cls):
return "fa-bolt" return "fa-bolt"
def notify(self, alert, query, user, new_state, app, host, options): def notify(self, alert, query, user, new_state, app, host, metadata, options):
""" """
:type app: redash.Redash :type app: redash.Redash
""" """

View File

@@ -40,7 +40,7 @@ class PagerDuty(BaseDestination):
def icon(cls): def icon(cls):
return "creative-commons-pd-alt" return "creative-commons-pd-alt"
def notify(self, alert, query, user, new_state, app, host, options): def notify(self, alert, query, user, new_state, app, host, metadata, options):
if alert.custom_subject: if alert.custom_subject:
default_desc = alert.custom_subject default_desc = alert.custom_subject
elif options.get("description"): elif options.get("description"):

View File

@@ -21,7 +21,7 @@ class Slack(BaseDestination):
def icon(cls): def icon(cls):
return "fa-slack" return "fa-slack"
def notify(self, alert, query, user, new_state, app, host, options): def notify(self, alert, query, user, new_state, app, host, metadata, options):
# Documentation: https://api.slack.com/docs/attachments # Documentation: https://api.slack.com/docs/attachments
fields = [ fields = [
{ {

View File

@@ -26,12 +26,13 @@ class Webhook(BaseDestination):
def icon(cls): def icon(cls):
return "fa-bolt" return "fa-bolt"
def notify(self, alert, query, user, new_state, app, host, options): def notify(self, alert, query, user, new_state, app, host, metadata, options):
try: try:
data = { data = {
"event": "alert_state_change", "event": "alert_state_change",
"alert": serialize_alert(alert, full=False), "alert": serialize_alert(alert, full=False),
"url_base": host, "url_base": host,
"metadata": metadata,
} }
data["alert"]["description"] = alert.custom_body data["alert"]["description"] = alert.custom_body

View File

@@ -1318,10 +1318,10 @@ class NotificationDestination(BelongsToOrgMixin, db.Model):
return notification_destinations return notification_destinations
def notify(self, alert, query, user, new_state, app, host): def notify(self, alert, query, user, new_state, app, host, metadata):
schema = get_configuration_schema_for_destination_type(self.type) schema = get_configuration_schema_for_destination_type(self.type)
self.options.set_schema(schema) self.options.set_schema(schema)
return self.destination.notify(alert, query, user, new_state, app, host, self.options) return self.destination.notify(alert, query, user, new_state, app, host, metadata, self.options)
@generic_repr("id", "user_id", "destination_id", "alert_id") @generic_repr("id", "user_id", "destination_id", "alert_id")
@@ -1358,16 +1358,16 @@ class AlertSubscription(TimestampMixin, db.Model):
def all(cls, alert_id): def all(cls, alert_id):
return AlertSubscription.query.join(User).filter(AlertSubscription.alert_id == alert_id) return AlertSubscription.query.join(User).filter(AlertSubscription.alert_id == alert_id)
def notify(self, alert, query, user, new_state, app, host): def notify(self, alert, query, user, new_state, app, host, metadata):
if self.destination: if self.destination:
return self.destination.notify(alert, query, user, new_state, app, host) return self.destination.notify(alert, query, user, new_state, app, host, metadata)
else: else:
# User email subscription, so create an email destination object # User email subscription, so create an email destination object
config = {"addresses": self.user.email} config = {"addresses": self.user.email}
schema = get_configuration_schema_for_destination_type("email") schema = get_configuration_schema_for_destination_type("email")
options = ConfigurationContainer(config, schema) options = ConfigurationContainer(config, schema)
destination = get_destination("email", options) destination = get_destination("email", options)
return destination.notify(alert, query, user, new_state, app, host, options) return destination.notify(alert, query, user, new_state, app, host, metadata, options)
@generic_repr("id", "trigger", "user_id", "org_id") @generic_repr("id", "trigger", "user_id", "org_id")

View File

@@ -8,11 +8,11 @@ from redash.worker import get_job_logger, job
logger = get_job_logger(__name__) logger = get_job_logger(__name__)
def notify_subscriptions(alert, new_state): def notify_subscriptions(alert, new_state, metadata):
host = utils.base_url(alert.query_rel.org) host = utils.base_url(alert.query_rel.org)
for subscription in alert.subscriptions: for subscription in alert.subscriptions:
try: try:
subscription.notify(alert, alert.query_rel, subscription.user, new_state, current_app, host) subscription.notify(alert, alert.query_rel, subscription.user, new_state, current_app, host, metadata)
except Exception: except Exception:
logger.exception("Error with processing destination") logger.exception("Error with processing destination")
@@ -26,7 +26,7 @@ def should_notify(alert, new_state):
@job("default", timeout=300) @job("default", timeout=300)
def check_alerts_for_query(query_id): def check_alerts_for_query(query_id, metadata):
logger.debug("Checking query %d for alerts", query_id) logger.debug("Checking query %d for alerts", query_id)
query = models.Query.query.get(query_id) query = models.Query.query.get(query_id)
@@ -51,4 +51,4 @@ def check_alerts_for_query(query_id):
logger.debug("Skipping notification (alert muted).") logger.debug("Skipping notification (alert muted).")
continue continue
notify_subscriptions(alert, new_state) notify_subscriptions(alert, new_state, metadata)

View File

@@ -227,7 +227,7 @@ class QueryExecutor(object):
models.db.session.commit() # make sure that alert sees the latest query result models.db.session.commit() # make sure that alert sees the latest query result
self._log_progress("checking_alerts") self._log_progress("checking_alerts")
for query_id in updated_query_ids: for query_id in updated_query_ids:
check_alerts_for_query.delay(query_id) check_alerts_for_query.delay(query_id, self.metadata)
self._log_progress("finished") self._log_progress("finished")
result = query_result.id result = query_result.id

View File

@@ -108,7 +108,7 @@ def test_discord_notify_calls_requests_post():
app = mock.Mock() app = mock.Mock()
host = "https://localhost:5000" host = "https://localhost:5000"
options = {"url": "https://discordapp.com/api/webhooks/test"} options = {"url": "https://discordapp.com/api/webhooks/test"}
metadata = {"Scheduled": False}
new_state = Alert.TRIGGERED_STATE new_state = Alert.TRIGGERED_STATE
destination = Discord(options) destination = Discord(options)
@@ -117,7 +117,7 @@ def test_discord_notify_calls_requests_post():
mock_response.status_code = 204 mock_response.status_code = 204
mock_post.return_value = mock_response mock_post.return_value = mock_response
destination.notify(alert, query, user, new_state, app, host, options) destination.notify(alert, query, user, new_state, app, host, metadata, options)
expected_payload = { expected_payload = {
"content": "Test custom subject", "content": "Test custom subject",

View File

@@ -12,7 +12,7 @@ class TestCheckAlertsForQuery(BaseTestCase):
Alert.evaluate = MagicMock(return_value=Alert.TRIGGERED_STATE) Alert.evaluate = MagicMock(return_value=Alert.TRIGGERED_STATE)
alert = self.factory.create_alert() alert = self.factory.create_alert()
check_alerts_for_query(alert.query_id) check_alerts_for_query(alert.query_id, metadata={"Scheduled": False})
self.assertTrue(redash.tasks.alerts.notify_subscriptions.called) self.assertTrue(redash.tasks.alerts.notify_subscriptions.called)
@@ -21,7 +21,7 @@ class TestCheckAlertsForQuery(BaseTestCase):
Alert.evaluate = MagicMock(return_value=Alert.OK_STATE) Alert.evaluate = MagicMock(return_value=Alert.OK_STATE)
alert = self.factory.create_alert() alert = self.factory.create_alert()
check_alerts_for_query(alert.query_id) check_alerts_for_query(alert.query_id, metadata={"Scheduled": False})
self.assertFalse(redash.tasks.alerts.notify_subscriptions.called) self.assertFalse(redash.tasks.alerts.notify_subscriptions.called)
@@ -30,7 +30,7 @@ class TestCheckAlertsForQuery(BaseTestCase):
Alert.evaluate = MagicMock(return_value=Alert.TRIGGERED_STATE) Alert.evaluate = MagicMock(return_value=Alert.TRIGGERED_STATE)
alert = self.factory.create_alert(options={"muted": True}) alert = self.factory.create_alert(options={"muted": True})
check_alerts_for_query(alert.query_id) check_alerts_for_query(alert.query_id, metadata={"Scheduled": False})
self.assertFalse(redash.tasks.alerts.notify_subscriptions.called) self.assertFalse(redash.tasks.alerts.notify_subscriptions.called)
@@ -39,7 +39,7 @@ class TestNotifySubscriptions(BaseTestCase):
def test_calls_notify_for_subscribers(self): def test_calls_notify_for_subscribers(self):
subscription = self.factory.create_alert_subscription() subscription = self.factory.create_alert_subscription()
subscription.notify = MagicMock() subscription.notify = MagicMock()
notify_subscriptions(subscription.alert, Alert.OK_STATE) notify_subscriptions(subscription.alert, Alert.OK_STATE, metadata={"Scheduled": False})
subscription.notify.assert_called_with( subscription.notify.assert_called_with(
subscription.alert, subscription.alert,
subscription.alert.query_rel, subscription.alert.query_rel,
@@ -47,4 +47,5 @@ class TestNotifySubscriptions(BaseTestCase):
Alert.OK_STATE, Alert.OK_STATE,
ANY, ANY,
ANY, ANY,
ANY,
) )