~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/bugs/scripts/bugnotification.py

[r=bac][ui=none][bug=621140] Refactor get_email_notifications().

Show diffs side-by-side

added added

removed removed

Lines of Context:
7
7
 
8
8
__metaclass__ = type
9
9
 
 
10
__all__ = [
 
11
    "construct_email_notifications",
 
12
    "get_email_notifications",
 
13
    ]
 
14
 
 
15
from itertools import groupby
 
16
from operator import attrgetter, itemgetter
 
17
 
 
18
import transaction
 
19
 
10
20
from zope.component import getUtility
11
21
 
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
 
29
 
 
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
24
34
 
25
35
 
26
36
def construct_email_notifications(bug_notifications):
162
172
 
163
173
    return bug_notifications, messages
164
174
 
165
 
def _log_exception_and_restart_transaction():
166
 
    """Log an exception and restart the current transaction.
167
 
 
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.
 
175
 
 
176
def notification_comment_batches(notifications):
 
177
    """Search `notification` for continuous spans with only one comment.
 
178
 
 
179
    Generates `comment_group, notification` tuples.
 
180
 
 
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.
170
184
    """
171
 
    log.exception(
172
 
        "An exception was raised while building the email notification.")
173
 
    rollback()
174
 
    begin()
175
 
 
176
 
 
177
 
def get_email_notifications(bug_notifications, date_emailed=None):
 
185
    comment_count = 0
 
186
    for notification in notifications:
 
187
        if notification.is_comment:
 
188
            comment_count += 1
 
189
        # Everything before the 2nd comment is in the first comment group.
 
190
        yield comment_count or 1, notification
 
191
 
 
192
 
 
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]
 
201
 
 
202
 
 
203
def get_email_notifications(bug_notifications):
178
204
    """Return the email notifications pending to be sent.
179
205
 
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.
186
212
    """
187
 
    # Avoid spurious lint about possibly undefined loop variables.
188
 
    notification = None
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.
198
 
        #
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.
204
 
                break
205
 
            if notification.bug != bug:
206
 
                # Found a different change, stop batching.
207
 
                break
208
 
            if notification.message.owner != person:
209
 
                # Ah, we've found a change made by somebody else; time
210
 
                # to stop batching too.
211
 
                break
212
 
            notification_batch.append(notification)
213
 
            bug_notifications.remove(notification)
214
 
            if notification.is_comment:
215
 
                found_comment = True
216
 
 
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.
221
216
        try:
222
 
            yield construct_email_notifications(notification_batch)
 
217
            yield construct_email_notifications(batch)
223
218
        except (KeyboardInterrupt, SystemExit):
224
219
            raise
225
220
        except:
226
 
            _log_exception_and_restart_transaction()
227
 
 
 
221
            log.exception("Error while building email notifications.")
 
222
            transaction.abort()
 
223
            transaction.begin()