| # Copyright 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Send alert summary emails to sheriffs on duty.""" |
| |
| import datetime |
| import sys |
| |
| from google.appengine.api import mail |
| |
| from dashboard import email_template |
| from dashboard import request_handler |
| from dashboard.models import anomaly |
| from dashboard.models import sheriff |
| |
| # The string to use as the header in an alert summary email. |
| _EMAIL_HTML_TOTAL_ANOMALIES = """ |
| <br><b><u>%d Total Anomalies</b></u><br> |
| <b>+++++++++++++++++++++++++++++++</b><br> |
| """ |
| |
| _EMAIL_SUBJECT = '%s: %d anomalies found at %d:%d.' |
| |
| |
| class EmailSummaryHandler(request_handler.RequestHandler): |
| """Summarizes alerts and sends e-mail to sheriff on duty. |
| |
| Identifies sheriffs who have the "summarize" property set to True, and gets |
| anomalies related to that sheriff that were triggered in the past 24 hours. |
| """ |
| |
| def get(self): |
| """Emails sheriffs with anomalies identified in most-recent 24 hours.""" |
| |
| # Get all Sheriffs that have requested an e-mail summary. |
| sheriffs_to_email_query = sheriff.Sheriff.query( |
| sheriff.Sheriff.summarize == True) |
| |
| # Start time after which to get anomalies. |
| start_time = datetime.datetime.now() - datetime.timedelta(hours=24) |
| |
| for sheriff_entity in sheriffs_to_email_query.fetch(): |
| _SendSummaryEmail(sheriff_entity, start_time) |
| |
| |
| def _SendSummaryEmail(sheriff_entity, start_time): |
| """Sends a summary email for the given sheriff rotation. |
| |
| Args: |
| sheriff_entity: A Sheriff entity. |
| start_time: A starting datetime for anomalies to fetch. |
| """ |
| receivers = email_template.GetSheriffEmails(sheriff_entity) |
| anomalies = _RecentUntriagedAnomalies(sheriff_entity, start_time) |
| if not anomalies: |
| return |
| subject = _EmailSubject(sheriff_entity, anomalies) |
| html, text = _EmailBody(anomalies) |
| mail.send_mail( |
| sender='gasper-alerts@google.com', to=receivers, |
| subject=subject, body=text, html=html) |
| |
| |
| def _RecentUntriagedAnomalies(sheriff_entity, start_time): |
| """Returns untriaged anomalies for |sheriff| after |start_time|.""" |
| recent_anomalies = anomaly.Anomaly.query( |
| anomaly.Anomaly.sheriff == sheriff_entity.key, |
| anomaly.Anomaly.timestamp > start_time).fetch() |
| return [a for a in recent_anomalies |
| if not a.is_improvement and a.bug_id is None] |
| |
| |
| def _EmailSubject(sheriff_entity, anomalies): |
| """Returns the email subject string for a summary email.""" |
| lowest_revision, highest_revision = _MaximalRevisionRange(anomalies) |
| return _EMAIL_SUBJECT % (sheriff_entity.key.string_id(), len(anomalies), |
| lowest_revision, highest_revision) |
| |
| |
| def _MaximalRevisionRange(anomalies): |
| """Gets the lowest start and highest end revision for |anomalies|.""" |
| lowest_revision = sys.maxint |
| highest_revision = 1 |
| for anomaly_entity in anomalies: |
| if anomaly_entity.start_revision < lowest_revision: |
| lowest_revision = anomaly_entity.start_revision |
| if anomaly_entity.end_revision > highest_revision: |
| highest_revision = anomaly_entity.end_revision |
| return lowest_revision, highest_revision |
| |
| |
| def _EmailBody(anomalies): |
| """Returns the html and text versions of the email body.""" |
| assert anomalies |
| html_body = [] |
| text_body = [] |
| html_body.append(_EMAIL_HTML_TOTAL_ANOMALIES % len(anomalies)) |
| |
| anomaly_info = {} |
| for anomaly_entity in anomalies: |
| test = anomaly_entity.test.get() |
| anomaly_info = email_template.GetAlertInfo(anomaly_entity, test) |
| html_body.append(anomaly_info['email_html']) |
| text_body.append(anomaly_info['email_text']) |
| |
| assert anomaly_info |
| html_body.append(anomaly_info['alerts_link']) |
| |
| # Join details for all anomalies to generate e-mail body. |
| html = ''.join(html_body) |
| text = ''.join(text_body) |
| return html, text |