diff --git a/redash/destinations/__init__.py b/redash/destinations/__init__.py index 0d05a3470..4b14e52b0 100644 --- a/redash/destinations/__init__.py +++ b/redash/destinations/__init__.py @@ -31,7 +31,7 @@ class BaseDestination(object): def configuration_schema(cls): 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() @classmethod diff --git a/redash/destinations/chatwork.py b/redash/destinations/chatwork.py index ed021e676..46904750c 100644 --- a/redash/destinations/chatwork.py +++ b/redash/destinations/chatwork.py @@ -29,7 +29,7 @@ class ChatWork(BaseDestination): def icon(cls): 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: # 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")) diff --git a/redash/destinations/discord.py b/redash/destinations/discord.py index abb732491..b14084ab0 100644 --- a/redash/destinations/discord.py +++ b/redash/destinations/discord.py @@ -28,7 +28,7 @@ class Discord(BaseDestination): def icon(cls): 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 fields = [ { diff --git a/redash/destinations/email.py b/redash/destinations/email.py index 8f10737c6..cb835212c 100644 --- a/redash/destinations/email.py +++ b/redash/destinations/email.py @@ -27,7 +27,7 @@ class Email(BaseDestination): def icon(cls): 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] if not recipients: diff --git a/redash/destinations/hangoutschat.py b/redash/destinations/hangoutschat.py index 845aa7d4e..f090662d5 100644 --- a/redash/destinations/hangoutschat.py +++ b/redash/destinations/hangoutschat.py @@ -37,7 +37,7 @@ class HangoutsChat(BaseDestination): def icon(cls): 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: if new_state == "triggered": message = 'Triggered' diff --git a/redash/destinations/mattermost.py b/redash/destinations/mattermost.py index e46437623..6254ecf71 100644 --- a/redash/destinations/mattermost.py +++ b/redash/destinations/mattermost.py @@ -24,7 +24,7 @@ class Mattermost(BaseDestination): def icon(cls): 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: text = alert.custom_subject elif new_state == "triggered": diff --git a/redash/destinations/microsoft_teams_webhook.py b/redash/destinations/microsoft_teams_webhook.py index 178b98718..8c64d9353 100644 --- a/redash/destinations/microsoft_teams_webhook.py +++ b/redash/destinations/microsoft_teams_webhook.py @@ -74,7 +74,7 @@ class MicrosoftTeamsWebhook(BaseDestination): def icon(cls): 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 """ diff --git a/redash/destinations/pagerduty.py b/redash/destinations/pagerduty.py index 9d48aa9ed..570c876da 100644 --- a/redash/destinations/pagerduty.py +++ b/redash/destinations/pagerduty.py @@ -40,7 +40,7 @@ class PagerDuty(BaseDestination): def icon(cls): 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: default_desc = alert.custom_subject elif options.get("description"): diff --git a/redash/destinations/slack.py b/redash/destinations/slack.py index d6ecb10e3..699fb5921 100644 --- a/redash/destinations/slack.py +++ b/redash/destinations/slack.py @@ -21,7 +21,7 @@ class Slack(BaseDestination): def icon(cls): 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 fields = [ { diff --git a/redash/destinations/webhook.py b/redash/destinations/webhook.py index 0540f2750..b98b6f18a 100644 --- a/redash/destinations/webhook.py +++ b/redash/destinations/webhook.py @@ -26,12 +26,13 @@ class Webhook(BaseDestination): def icon(cls): 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: data = { "event": "alert_state_change", "alert": serialize_alert(alert, full=False), "url_base": host, + "metadata": metadata, } data["alert"]["description"] = alert.custom_body diff --git a/redash/models/__init__.py b/redash/models/__init__.py index 49821662a..eeb874669 100644 --- a/redash/models/__init__.py +++ b/redash/models/__init__.py @@ -1318,10 +1318,10 @@ class NotificationDestination(BelongsToOrgMixin, db.Model): 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) 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") @@ -1358,16 +1358,16 @@ class AlertSubscription(TimestampMixin, db.Model): def all(cls, 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: - return self.destination.notify(alert, query, user, new_state, app, host) + return self.destination.notify(alert, query, user, new_state, app, host, metadata) else: # User email subscription, so create an email destination object config = {"addresses": self.user.email} schema = get_configuration_schema_for_destination_type("email") options = ConfigurationContainer(config, schema) 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") diff --git a/redash/tasks/alerts.py b/redash/tasks/alerts.py index e441899c1..2e8093a0a 100644 --- a/redash/tasks/alerts.py +++ b/redash/tasks/alerts.py @@ -8,11 +8,11 @@ from redash.worker import get_job_logger, job 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) for subscription in alert.subscriptions: 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: logger.exception("Error with processing destination") @@ -26,7 +26,7 @@ def should_notify(alert, new_state): @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) query = models.Query.query.get(query_id) @@ -51,4 +51,4 @@ def check_alerts_for_query(query_id): logger.debug("Skipping notification (alert muted).") continue - notify_subscriptions(alert, new_state) + notify_subscriptions(alert, new_state, metadata) diff --git a/redash/tasks/queries/execution.py b/redash/tasks/queries/execution.py index e2432069b..8d790fdf5 100644 --- a/redash/tasks/queries/execution.py +++ b/redash/tasks/queries/execution.py @@ -227,7 +227,7 @@ class QueryExecutor(object): models.db.session.commit() # make sure that alert sees the latest query result self._log_progress("checking_alerts") 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") result = query_result.id diff --git a/tests/handlers/test_destinations.py b/tests/handlers/test_destinations.py index 86feb4a26..50315514c 100644 --- a/tests/handlers/test_destinations.py +++ b/tests/handlers/test_destinations.py @@ -108,7 +108,7 @@ def test_discord_notify_calls_requests_post(): app = mock.Mock() host = "https://localhost:5000" options = {"url": "https://discordapp.com/api/webhooks/test"} - + metadata = {"Scheduled": False} new_state = Alert.TRIGGERED_STATE destination = Discord(options) @@ -117,7 +117,7 @@ def test_discord_notify_calls_requests_post(): mock_response.status_code = 204 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 = { "content": "Test custom subject", diff --git a/tests/tasks/test_alerts.py b/tests/tasks/test_alerts.py index 83927b0dd..37eeed24c 100644 --- a/tests/tasks/test_alerts.py +++ b/tests/tasks/test_alerts.py @@ -12,7 +12,7 @@ class TestCheckAlertsForQuery(BaseTestCase): Alert.evaluate = MagicMock(return_value=Alert.TRIGGERED_STATE) 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) @@ -21,7 +21,7 @@ class TestCheckAlertsForQuery(BaseTestCase): Alert.evaluate = MagicMock(return_value=Alert.OK_STATE) 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) @@ -30,7 +30,7 @@ class TestCheckAlertsForQuery(BaseTestCase): Alert.evaluate = MagicMock(return_value=Alert.TRIGGERED_STATE) 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) @@ -39,7 +39,7 @@ class TestNotifySubscriptions(BaseTestCase): def test_calls_notify_for_subscribers(self): subscription = self.factory.create_alert_subscription() 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.alert, subscription.alert.query_rel, @@ -47,4 +47,5 @@ class TestNotifySubscriptions(BaseTestCase): Alert.OK_STATE, ANY, ANY, + ANY, )