11
"construct_email_notifications",
12
"get_email_notifications",
15
from itertools import groupby
16
from operator import attrgetter, itemgetter
10
20
from zope.component import getUtility
12
22
from canonical.config import config
13
from canonical.database.sqlbase import rollback, begin
14
23
from canonical.launchpad.helpers import emailPeople, get_email_template
15
from lp.bugs.interfaces.bugmessage import IBugMessageSet
16
24
from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
17
from lp.registry.interfaces.person import IPersonSet
18
25
from canonical.launchpad.mailnotification import (
19
generate_bug_add_email, MailWrapper)
26
MailWrapper, generate_bug_add_email)
27
from canonical.launchpad.scripts.logger import log
28
from canonical.launchpad.webapp import canonical_url
30
from lp.bugs.interfaces.bugmessage import IBugMessageSet
20
31
from lp.bugs.mail.bugnotificationbuilder import (
21
32
BugNotificationBuilder, get_bugmail_from_address)
22
from canonical.launchpad.scripts.logger import log
23
from canonical.launchpad.webapp import canonical_url
33
from lp.registry.interfaces.person import IPersonSet
26
36
def construct_email_notifications(bug_notifications):
163
173
return bug_notifications, messages
165
def _log_exception_and_restart_transaction():
166
"""Log an exception and restart the current transaction.
168
It's important to restart the transaction if an exception occurs,
169
since if it's a DB exception, the transaction isn't usable anymore.
176
def notification_comment_batches(notifications):
177
"""Search `notification` for continuous spans with only one comment.
179
Generates `comment_group, notification` tuples.
181
The notifications are searched in order for continuous spans containing
182
only one comment. Each continous span is given a unique number. Each
183
notification is yielded along with its span number.
172
"An exception was raised while building the email notification.")
177
def get_email_notifications(bug_notifications, date_emailed=None):
186
for notification in notifications:
187
if notification.is_comment:
189
# Everything before the 2nd comment is in the first comment group.
190
yield comment_count or 1, notification
193
def notification_batches(notifications):
194
"""Batch notifications for `get_email_notifications`."""
195
notifications_grouped = groupby(
196
notifications, attrgetter("bug", "message.owner"))
197
for (bug, person), notification_group in notifications_grouped:
198
batches = notification_comment_batches(notification_group)
199
for comment_group, batch in groupby(batches, itemgetter(0)):
200
yield [notification for (comment_group, notification) in batch]
203
def get_email_notifications(bug_notifications):
178
204
"""Return the email notifications pending to be sent.
180
206
The intention of this code is to ensure that as many notifications
184
210
- Must be related to the same bug.
185
211
- Must contain at most one comment.
187
# Avoid spurious lint about possibly undefined loop variables.
189
# Copy bug_notifications because we will modify it as we go.
190
bug_notifications = list(bug_notifications)
191
while bug_notifications:
192
found_comment = False
193
notification_batch = []
194
bug = bug_notifications[0].bug
195
person = bug_notifications[0].message.owner
196
# What the loop below does is find the largest contiguous set of
197
# bug notifications as specified above.
199
# Note that we iterate over a copy of the notifications here
200
# because we are modifying bug_modifications as we go.
201
for notification in list(bug_notifications):
202
if notification.is_comment and found_comment:
203
# Oops, found a second comment, stop batching.
205
if notification.bug != bug:
206
# Found a different change, stop batching.
208
if notification.message.owner != person:
209
# Ah, we've found a change made by somebody else; time
210
# to stop batching too.
212
notification_batch.append(notification)
213
bug_notifications.remove(notification)
214
if notification.is_comment:
217
if date_emailed is not None:
218
notification.date_emailed = date_emailed
213
for batch in notification_batches(bug_notifications):
219
214
# We don't want bugs preventing all bug notifications from
220
215
# being sent, so catch and log all exceptions.
222
yield construct_email_notifications(notification_batch)
217
yield construct_email_notifications(batch)
223
218
except (KeyboardInterrupt, SystemExit):
226
_log_exception_and_restart_transaction()
221
log.exception("Error while building email notifications.")