def send_notification_as_email( notification: BaseNotification, users: Set[User], shared_context: Mapping[str, Any], extra_context_by_user_id: Optional[Mapping[int, Mapping[str, Any]]], ) -> None: headers = get_headers(notification) subject = get_subject_with_prefix(notification) type = get_email_type(notification) for user in users: extra_context = (extra_context_by_user_id or {}).get(user.id, {}) log_message(notification, user) msg = MessageBuilder( subject=subject, context=get_context(notification, user, shared_context, extra_context), template=notification.get_template(), html_template=notification.get_html_template(), headers=headers, reference=notification.get_reference(), reply_reference=notification.get_reply_reference(), type=type, ) msg.add_users([user.id], project=notification.project) msg.send_async()
def get_context( notification: BaseNotification, recipient: Union[User, Team], shared_context: Mapping[str, Any], extra_context: Mapping[str, Any], ) -> Mapping[str, Any]: """ Compose the various levels of context and add slack-specific fields. """ return { **shared_context, **notification.get_user_context(recipient, extra_context), }
def build_notification_attachment( notification: BaseNotification, context: Mapping[str, Any]) -> Mapping[str, str]: footer = build_notification_footer(notification) return { "title": notification.get_title(), "text": context["text_description"], "mrkdwn_in": ["text"], "footer_icon": get_sentry_avatar_url(), "footer": footer, "color": LEVEL_TO_COLOR["info"], }
def get_headers(notification: BaseNotification) -> Mapping[str, Any]: headers = { "X-Sentry-Project": notification.project.slug, "X-SMTPAPI": json.dumps({"category": notification.get_category()}), } if notification.group: headers.update({ "X-Sentry-Logger": notification.group.logger, "X-Sentry-Logger-Level": notification.group.get_level_display(), "X-Sentry-Reply-To": group_id_to_email(notification.group.id), }) return headers
def build_notification_attachment( notification: BaseNotification, context: Mapping[str, Any]) -> Mapping[str, str]: if isinstance(notification, AlertRuleNotification): return build_group_attachment( notification.group, notification.event, context["tags"], notification.rules, issue_alert=True, ) footer = build_notification_footer(notification) return { "title": notification.get_title(), "text": context["text_description"], "mrkdwn_in": ["text"], "footer_icon": get_sentry_avatar_url(), "footer": footer, "color": LEVEL_TO_COLOR["info"], }
def get_context( notification: BaseNotification, user: User, shared_context: Mapping[str, Any], extra_context: Mapping[str, Any], ) -> Mapping[str, Any]: """ Compose the various levels of context and add email-specific fields. The generic HTML/text templates only render the unsubscribe link if one is present in the context, so don't automatically add it to every message. """ context = { **shared_context, **notification.get_user_context(user, extra_context), } if can_users_unsubscribe(notification) and notification.group: context.update({ "unsubscribe_link": get_unsubscribe_link(user.id, notification.group.id) }) return context
def send_notification_as_slack( notification: BaseNotification, recipients: Union[Set[User], Set[Team]], shared_context: Mapping[str, Any], extra_context_by_user_id: Mapping[str, Any], ) -> None: """Send an "activity" or "alert rule" notification to a Slack user or team.""" client = SlackClient() data = get_channel_and_token_by_recipient(notification.organization, recipients) for recipient, tokens_by_channel in data.items(): is_multiple = True if len([token for token in tokens_by_channel ]) > 1 else False if is_multiple: logger.info( "notification.multiple.slack_post", extra={ "notification": notification, "recipient": recipient.id, }, ) extra_context = (extra_context_by_user_id or {}).get(recipient.id, {}) context = get_context(notification, recipient, shared_context, extra_context) attachment = [ build_notification_attachment(notification, context, recipient) ] for channel, token in tokens_by_channel.items(): # unfurl_links and unfurl_media are needed to preserve the intended message format # and prevent the app from replying with help text to the unfurl payload = { "token": token, "channel": channel, "link_names": 1, "unfurl_links": False, "unfurl_media": False, "text": notification.get_notification_title(), "attachments": json.dumps(attachment), } try: client.post("/chat.postMessage", data=payload, timeout=5) except ApiError as e: logger.info( "notification.fail.slack_post", extra={ "error": str(e), "notification": notification, "recipient": recipient.id, "channel_id": channel, "is_multiple": is_multiple, }, ) continue key = get_key(notification) metrics.incr( f"{key}.notifications.sent", instance=f"slack.{key}.notification", skip_internal=False, )