|
@@ -3,15 +3,20 @@ from __future__ import absolute_import
|
|
|
import abc
|
|
|
|
|
|
import six
|
|
|
+from django.core.urlresolvers import reverse
|
|
|
|
|
|
-from sentry.incidents.models import AlertRuleTriggerAction
|
|
|
+from sentry.incidents.models import AlertRuleTriggerAction, QueryAggregations, TriggerStatus
|
|
|
+from sentry.utils.email import MessageBuilder
|
|
|
+from sentry.utils.http import absolute_uri
|
|
|
+from sentry.utils.linksign import generate_signed_link
|
|
|
|
|
|
|
|
|
@six.add_metaclass(abc.ABCMeta)
|
|
|
class ActionHandler(object):
|
|
|
- def __init__(self, action, incident):
|
|
|
+ def __init__(self, action, incident, project):
|
|
|
self.action = action
|
|
|
self.incident = incident
|
|
|
+ self.project = project
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
def fire(self):
|
|
@@ -24,8 +29,96 @@ class ActionHandler(object):
|
|
|
|
|
|
@AlertRuleTriggerAction.register_type_handler(AlertRuleTriggerAction.Type.EMAIL)
|
|
|
class EmailActionHandler(ActionHandler):
|
|
|
+ query_aggregations_display = {
|
|
|
+ QueryAggregations.TOTAL: "Total Events",
|
|
|
+ QueryAggregations.UNIQUE_USERS: "Total Unique Users",
|
|
|
+ }
|
|
|
+ status_display = {TriggerStatus.ACTIVE: "Fired", TriggerStatus.RESOLVED: "Resolved"}
|
|
|
+
|
|
|
+ def get_targets(self):
|
|
|
+ target = self.action.target
|
|
|
+ if not target:
|
|
|
+ return []
|
|
|
+ targets = []
|
|
|
+ if self.action.target_type in (
|
|
|
+ AlertRuleTriggerAction.TargetType.USER.value,
|
|
|
+ AlertRuleTriggerAction.TargetType.TEAM.value,
|
|
|
+ ):
|
|
|
+ alert_settings = self.project.get_member_alert_settings("mail:alert")
|
|
|
+ disabled_users = set(
|
|
|
+ user_id for user_id, setting in alert_settings.items() if setting == 0
|
|
|
+ )
|
|
|
+ if self.action.target_type == AlertRuleTriggerAction.TargetType.USER.value:
|
|
|
+ if target.id not in disabled_users:
|
|
|
+ targets = [(target.id, target.email)]
|
|
|
+ elif self.action.target_type == AlertRuleTriggerAction.TargetType.TEAM.value:
|
|
|
+ targets = target.member_set.values_list("user_id", "user__email")
|
|
|
+ targets = [
|
|
|
+ (user_id, email) for user_id, email in targets if user_id not in disabled_users
|
|
|
+ ]
|
|
|
+ # TODO: We need some sort of verification system to make sure we're not being
|
|
|
+ # used as an email relay.
|
|
|
+ # elif self.action.target_type == AlertRuleTriggerAction.TargetType.SPECIFIC.value:
|
|
|
+ # emails = [target]
|
|
|
+ return targets
|
|
|
+
|
|
|
def fire(self):
|
|
|
- pass
|
|
|
+ self.email_users(TriggerStatus.ACTIVE)
|
|
|
|
|
|
def resolve(self):
|
|
|
- pass
|
|
|
+ self.email_users(TriggerStatus.RESOLVED)
|
|
|
+
|
|
|
+ def email_users(self, status):
|
|
|
+ email_context = self.generate_email_context(status)
|
|
|
+ for user_id, email in self.get_targets():
|
|
|
+ email_context["unsubscribe_link"] = self.generate_unsubscribe_link(user_id)
|
|
|
+ self.build_message(email_context, status, user_id).send_async(to=[email])
|
|
|
+
|
|
|
+ def build_message(self, context, status, user_id):
|
|
|
+ context["unsubscribe_link"] = self.generate_unsubscribe_link(user_id)
|
|
|
+ display = self.status_display[status]
|
|
|
+ return MessageBuilder(
|
|
|
+ subject=u"Incident Alert Rule {} for Project {}".format(display, self.project.slug),
|
|
|
+ template=u"sentry/emails/incidents/trigger.txt",
|
|
|
+ html_template=u"sentry/emails/incidents/trigger.html",
|
|
|
+ type="incident.alert_rule_{}".format(display.lower()),
|
|
|
+ context=context,
|
|
|
+ )
|
|
|
+
|
|
|
+ def generate_unsubscribe_link(self, user_id):
|
|
|
+ return generate_signed_link(
|
|
|
+ user_id,
|
|
|
+ "sentry-account-email-unsubscribe-project",
|
|
|
+ kwargs={"project_id": self.project.id},
|
|
|
+ )
|
|
|
+
|
|
|
+ def generate_email_context(self, status):
|
|
|
+ trigger = self.action.alert_rule_trigger
|
|
|
+ alert_rule = trigger.alert_rule
|
|
|
+ return {
|
|
|
+ "link": absolute_uri(
|
|
|
+ reverse(
|
|
|
+ "sentry-incident",
|
|
|
+ kwargs={
|
|
|
+ "organization_slug": self.incident.organization.slug,
|
|
|
+ "incident_id": self.incident.identifier,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ "rule_link": absolute_uri(
|
|
|
+ reverse(
|
|
|
+ "sentry-alert-rule",
|
|
|
+ kwargs={
|
|
|
+ "organization_slug": self.incident.organization.slug,
|
|
|
+ "alert_rule_id": self.action.alert_rule_trigger.alert_rule_id,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ "incident_name": self.incident.title,
|
|
|
+ "aggregate": self.query_aggregations_display[QueryAggregations(alert_rule.aggregation)],
|
|
|
+ "query": alert_rule.query,
|
|
|
+ "threshold": trigger.alert_threshold
|
|
|
+ if status == TriggerStatus.ACTIVE
|
|
|
+ else trigger.resolve_threshold,
|
|
|
+ "status": self.status_display[status],
|
|
|
+ }
|