def send_bug_details_to_new_bug_subscribers( bug, previous_subscribers, current_subscribers, subscribed_by=None, event_creator=None): """Send an email containing full bug details to new bug subscribers. This function is designed to handle situations where bugtasks get reassigned to new products or sourcepackages, and the new bug subscribers need to be notified of the bug. A boolean is returned indicating whether any emails were sent. """ prev_subs_set = set(previous_subscribers) cur_subs_set = set(current_subscribers) new_subs = cur_subs_set.difference(prev_subs_set) if (event_creator is not None and not event_creator.selfgenerated_bugnotifications): new_subs.discard(event_creator) to_addrs = set() for new_sub in new_subs: to_addrs.update(get_contact_email_addresses(new_sub)) if not to_addrs: return False from_addr = format_address( 'Launchpad Bug Tracker', "%s@%s" % (bug.id, config.launchpad.bugs_domain)) # Now's a good a time as any for this email; don't use the original # reported date for the bug as it will just confuse mailer and # recipient. email_date = datetime.datetime.now() # The new subscriber email is effectively the initial message regarding # a new bug. The bug's initial message is used in the References # header to establish the message's context in the email client. references = [bug.initial_message.rfc822msgid] recipients = bug.getBugNotificationRecipients() bug_notification_builder = BugNotificationBuilder(bug, event_creator) for to_addr in sorted(to_addrs): reason, rationale = recipients.getReason(to_addr) subject, contents = generate_bug_add_email( bug, new_recipients=True, subscribed_by=subscribed_by, reason=reason, event_creator=event_creator) msg = bug_notification_builder.build( from_addr, to_addr, contents, subject, email_date, rationale=rationale, references=references) sendmail(msg) return True
def construct_email_notifications(bug_notifications): """Construct an email from a list of related bug notifications. The person and bug has to be the same for all notifications, and there can be only one comment. """ first_notification = bug_notifications[0] bug = first_notification.bug actor = first_notification.message.owner subject = first_notification.message.subject comment = None references = [] text_notifications = [] old_values = {} new_values = {} for notification in bug_notifications: assert notification.bug == bug, bug.id assert notification.message.owner == actor, actor.id if notification.is_comment: assert comment is None, ( "Only one of the notifications is allowed to be a comment.") comment = notification.message else: key = get_activity_key(notification) if key is not None: if key not in old_values: old_values[key] = notification.activity.oldvalue new_values[key] = notification.activity.newvalue recipients = {} filtered_notifications = [] omitted_notifications = [] for notification in bug_notifications: key = get_activity_key(notification) if (notification.is_comment or key is None or key == 'removed_subscriber' or old_values[key] != new_values[key]): # We will report this notification. filtered_notifications.append(notification) for subscription_source in notification.recipients: for recipient in get_recipients(subscription_source.person): # The subscription_source.person may be a person or a # team. The get_recipients function gives us everyone # who should actually get an email for that person. # If subscription_source.person is a person or a team # with a preferred email address, then the people to # be emailed will only be subscription_source.person. # However, if it is a team without a preferred email # address, then this list will be the people and teams # that comprise the team, transitively, stopping the walk # at each person and at each team with a preferred email # address. sources_for_person = recipients.get(recipient) if sources_for_person is None: sources_for_person = [] recipients[recipient] = sources_for_person sources_for_person.append(subscription_source) else: omitted_notifications.append(notification) # If the actor does not want self-generated bug notifications, remove the # actor now. if not actor.selfgenerated_bugnotifications: recipients.pop(actor, None) if bug.duplicateof is not None: text_notifications.append( '*** This bug is a duplicate of bug %d ***\n %s' % (bug.duplicateof.id, canonical_url(bug.duplicateof))) if comment is not None: if comment == bug.initial_message: subject, text = generate_bug_add_email(bug) else: text = comment.text_contents text_notifications.append(text) msgid = comment.rfc822msgid email_date = comment.datecreated reference = comment.parent while reference is not None: references.insert(0, reference.rfc822msgid) reference = reference.parent else: msgid = first_notification.message.rfc822msgid email_date = first_notification.message.datecreated for notification in filtered_notifications: if notification.message == comment: # Comments were just handled in the previous if block. continue text = notification.message.text_contents.rstrip() text_notifications.append(text) if bug.initial_message.rfc822msgid not in references: # Ensure that references contain the initial message ID references.insert(0, bug.initial_message.rfc822msgid) # At this point we've got the data we need to construct the # messages. Now go ahead and actually do that. messages = [] mail_wrapper = MailWrapper(width=72) content = '\n\n'.join(text_notifications) from_address = get_bugmail_from_address(actor, bug) bug_notification_builder = BugNotificationBuilder(bug, actor) recipients = getUtility(IBugNotificationSet).getRecipientFilterData( bug, recipients, filtered_notifications) sorted_recipients = sorted(recipients.items(), key=lambda t: t[0].preferredemail.email) for email_person, data in sorted_recipients: address = str(email_person.preferredemail.email) # Choosing the first source is a bit arbitrary, but it # is simple for the user to understand. We may want to reconsider # this in the future. reason = data['sources'][0].reason_body rationale = data['sources'][0].reason_header if data['filter descriptions']: # There are some filter descriptions as well. Add them to # the email body. filters_text = u"\nMatching subscriptions: %s" % ", ".join( data['filter descriptions']) else: filters_text = u"" # In the rare case of a bug with no bugtasks, we can't generate the # subscription management URL so just leave off the subscription # management message entirely. if len(bug.bugtasks): bug_url = canonical_url(bug.bugtasks[0]) notification_url = bug_url + '/+subscriptions' subscriptions_message = ( "To manage notifications about this bug go to:\n%s" % notification_url) else: subscriptions_message = '' data_wrapper = MailWrapper(width=72, indent=' ') body_data = { 'content': mail_wrapper.format(content), 'bug_title': data_wrapper.format(bug.title), 'bug_url': canonical_url(bug), 'notification_rationale': mail_wrapper.format(reason), 'subscription_filters': filters_text, 'subscriptions_message': subscriptions_message, } # If the person we're sending to receives verbose notifications # we include the description and status of the bug in the email # footer. if email_person.verbose_bugnotifications: email_template = 'bug-notification-verbose.txt' body_data['bug_description'] = data_wrapper.format(bug.description) status_base = "Status in %s:\n %s" status_strings = [] for bug_task in bug.bugtasks: status_strings.append( status_base % (bug_task.target.title, bug_task.status.title)) body_data['bug_statuses'] = "\n".join(status_strings) else: email_template = 'bug-notification.txt' body_template = get_email_template(email_template, 'bugs') body = (body_template % body_data).strip() msg = bug_notification_builder.build( from_address, address, body, subject, email_date, rationale, references, msgid, filters=data['filter descriptions']) messages.append(msg) return filtered_notifications, omitted_notifications, messages
def construct_email_notifications(bug_notifications): """Construct an email from a list of related bug notifications. The person and bug has to be the same for all notifications, and there can be only one comment. """ first_notification = bug_notifications[0] bug = first_notification.bug actor = first_notification.message.owner subject = first_notification.message.subject comment = None references = [] text_notifications = [] old_values = {} new_values = {} for notification in bug_notifications: assert notification.bug == bug, bug.id assert notification.message.owner == actor, actor.id if notification.is_comment: assert comment is None, "Only one of the notifications is allowed to be a comment." comment = notification.message else: key = get_activity_key(notification) if key is not None: if key not in old_values: old_values[key] = notification.activity.oldvalue new_values[key] = notification.activity.newvalue recipients = {} filtered_notifications = [] omitted_notifications = [] for notification in bug_notifications: key = get_activity_key(notification) if notification.is_comment or key is None or key == "removed_subscriber" or old_values[key] != new_values[key]: # We will report this notification. filtered_notifications.append(notification) for subscription_source in notification.recipients: for recipient in get_recipients(subscription_source.person): # The subscription_source.person may be a person or a # team. The get_recipients function gives us everyone # who should actually get an email for that person. # If subscription_source.person is a person or a team # with a preferred email address, then the people to # be emailed will only be subscription_source.person. # However, if it is a team without a preferred email # address, then this list will be the people and teams # that comprise the team, transitively, stopping the walk # at each person and at each team with a preferred email # address. sources_for_person = recipients.get(recipient) if sources_for_person is None: sources_for_person = [] recipients[recipient] = sources_for_person sources_for_person.append(subscription_source) else: omitted_notifications.append(notification) # If the actor does not want self-generated bug notifications, remove the # actor now. if not actor.selfgenerated_bugnotifications: recipients.pop(actor, None) if bug.duplicateof is not None: text_notifications.append( "*** This bug is a duplicate of bug %d ***\n %s" % (bug.duplicateof.id, canonical_url(bug.duplicateof)) ) if comment is not None: if comment == bug.initial_message: subject, text = generate_bug_add_email(bug) else: text = comment.text_contents text_notifications.append(text) msgid = comment.rfc822msgid email_date = comment.datecreated reference = comment.parent while reference is not None: references.insert(0, reference.rfc822msgid) reference = reference.parent else: msgid = first_notification.message.rfc822msgid email_date = first_notification.message.datecreated for notification in filtered_notifications: if notification.message == comment: # Comments were just handled in the previous if block. continue text = notification.message.text_contents.rstrip() text_notifications.append(text) if bug.initial_message.rfc822msgid not in references: # Ensure that references contain the initial message ID references.insert(0, bug.initial_message.rfc822msgid) # At this point we've got the data we need to construct the # messages. Now go ahead and actually do that. messages = [] mail_wrapper = MailWrapper(width=72) content = "\n\n".join(text_notifications) from_address = get_bugmail_from_address(actor, bug) bug_notification_builder = BugNotificationBuilder(bug, actor) recipients = getUtility(IBugNotificationSet).getRecipientFilterData(bug, recipients, filtered_notifications) sorted_recipients = sorted(recipients.items(), key=lambda t: t[0].preferredemail.email) for email_person, data in sorted_recipients: address = str(email_person.preferredemail.email) # Choosing the first source is a bit arbitrary, but it # is simple for the user to understand. We may want to reconsider # this in the future. reason = data["sources"][0].reason_body rationale = data["sources"][0].reason_header if data["filter descriptions"]: # There are some filter descriptions as well. Add them to # the email body. filters_text = u"\nMatching subscriptions: %s" % ", ".join(data["filter descriptions"]) else: filters_text = u"" # In the rare case of a bug with no bugtasks, we can't generate the # subscription management URL so just leave off the subscription # management message entirely. if len(bug.bugtasks): bug_url = canonical_url(bug.bugtasks[0]) notification_url = bug_url + "/+subscriptions" subscriptions_message = "To manage notifications about this bug go to:\n%s" % notification_url else: subscriptions_message = "" data_wrapper = MailWrapper(width=72, indent=" ") body_data = { "content": mail_wrapper.format(content), "bug_title": data_wrapper.format(bug.title), "bug_url": canonical_url(bug), "notification_rationale": mail_wrapper.format(reason), "subscription_filters": filters_text, "subscriptions_message": subscriptions_message, } # If the person we're sending to receives verbose notifications # we include the description and status of the bug in the email # footer. if email_person.verbose_bugnotifications: email_template = "bug-notification-verbose.txt" body_data["bug_description"] = data_wrapper.format(bug.description) status_base = "Status in %s:\n %s" status_strings = [] for bug_task in bug.bugtasks: status_strings.append(status_base % (bug_task.target.title, bug_task.status.title)) body_data["bug_statuses"] = "\n".join(status_strings) else: email_template = "bug-notification.txt" body_template = get_email_template(email_template, "bugs") body = (body_template % body_data).strip() msg = bug_notification_builder.build( from_address, address, body, subject, email_date, rationale, references, msgid, filters=data["filter descriptions"], ) messages.append(msg) return filtered_notifications, omitted_notifications, messages
def send_bug_details_to_new_bug_subscribers(bug, previous_subscribers, current_subscribers, subscribed_by=None, event_creator=None): """Send an email containing full bug details to new bug subscribers. This function is designed to handle situations where bugtasks get reassigned to new products or sourcepackages, and the new bug subscribers need to be notified of the bug. A boolean is returned indicating whether any emails were sent. """ prev_subs_set = set(previous_subscribers) cur_subs_set = set(current_subscribers) new_subs = cur_subs_set.difference(prev_subs_set) if (event_creator is not None and not event_creator.selfgenerated_bugnotifications): new_subs.discard(event_creator) to_persons = set() for new_sub in new_subs: to_persons.update(get_recipients(new_sub)) if not to_persons: return False from_addr = format_address( 'Launchpad Bug Tracker', "%s@%s" % (bug.id, config.launchpad.bugs_domain)) # Now's a good a time as any for this email; don't use the original # reported date for the bug as it will just confuse mailer and # recipient. email_date = datetime.datetime.now() # The new subscriber email is effectively the initial message regarding # a new bug. The bug's initial message is used in the References # header to establish the message's context in the email client. references = [bug.initial_message.rfc822msgid] recipients = bug.getBugNotificationRecipients() bug_notification_builder = BugNotificationBuilder(bug, event_creator) for to_person in sorted(to_persons): reason, rationale = recipients.getReason( str(removeSecurityProxy(to_person).preferredemail.email)) subject, contents = generate_bug_add_email(bug, new_recipients=True, subscribed_by=subscribed_by, reason=reason, event_creator=event_creator) msg = bug_notification_builder.build(from_addr, to_person, contents, subject, email_date, rationale=rationale, references=references) sendmail(msg) return True