mirror of
https://github.com/getredash/redash.git
synced 2025-12-19 17:37:19 -05:00
Passing Request metadata to Alert destinations (#5230)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|||||||
@@ -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 = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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>'
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
@@ -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
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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"):
|
||||||
|
|||||||
@@ -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 = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user