Merge lp:~thumper/launchpad/bmp-notification-email-job into lp:launchpad
- bmp-notification-email-job
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Michael Hudson-Doyle |
Approved revision: | no longer in the source branch. |
Merge reported by: | Tim Penhey |
Merged at revision: | not available |
Proposed branch: | lp:~thumper/launchpad/bmp-notification-email-job |
Merge into: | lp:launchpad |
Prerequisite: | lp:~thumper/launchpad/new-reviewer-email-job |
Diff against target: |
423 lines (+187/-76) 5 files modified
lib/lp/code/configure.zcml (+10/-0) lib/lp/code/interfaces/branchmergeproposal.py (+21/-0) lib/lp/code/mail/branchmergeproposal.py (+22/-13) lib/lp/code/mail/tests/test_branchmergeproposal.py (+60/-62) lib/lp/code/model/branchmergeproposaljob.py (+74/-1) |
To merge this branch: | bzr merge lp:~thumper/launchpad/bmp-notification-email-job |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Hudson-Doyle | Approve | ||
Review via email: mp+21703@code.launchpad.net |
Commit message
Description of the change
Pipe 3 of (approximately 7).
This branch changes the email being sent from update events to be sent using a job.
Not a lot to see here, just more of the same.
tests:
mail/
This branch is not going to be landed, but the branch at the end of the
pipeline that will be.
BTW the tests pass for this branch on ec2.
lib/lp/
- just registering the job
lib/lp/
- the interface for the job and the job creation utility
lib/lp/
- Some of the delta checking logic has moved into the event subscriber
function as we don't want to create a job for something that isn't
even going to send out some mail.
- If there are changes to see, then a job is created.
- The job contains the text_delta that would be included in the email.
lib/lp/
- tests for the job creation and the resulting email.
lib/lp/
- the implementation of the job itself.
- again this just defers most of the processing to the BMPMailer
Preview Diff
1 | === modified file 'lib/lp/code/configure.zcml' | |||
2 | --- lib/lp/code/configure.zcml 2010-03-29 01:14:29 +0000 | |||
3 | +++ lib/lp/code/configure.zcml 2010-03-29 01:14:30 +0000 | |||
4 | @@ -943,6 +943,16 @@ | |||
5 | 943 | </class> | 943 | </class> |
6 | 944 | 944 | ||
7 | 945 | <securedutility | 945 | <securedutility |
8 | 946 | component="lp.code.model.branchmergeproposaljob.MergeProposalUpdatedEmailJob" | ||
9 | 947 | provides="lp.code.interfaces.branchmergeproposal.IMergeProposalUpdatedEmailJobSource"> | ||
10 | 948 | <allow interface="lp.code.interfaces.branchmergeproposal.IMergeProposalUpdatedEmailJobSource"/> | ||
11 | 949 | </securedutility> | ||
12 | 950 | <class class="lp.code.model.branchmergeproposaljob.MergeProposalUpdatedEmailJob"> | ||
13 | 951 | <allow interface="lp.services.job.interfaces.job.IRunnableJob" /> | ||
14 | 952 | <allow interface="lp.code.interfaces.branchmergeproposal.IBranchMergeProposalJob" /> | ||
15 | 953 | </class> | ||
16 | 954 | |||
17 | 955 | <securedutility | ||
18 | 946 | component="lp.code.model.branchjob.BranchUpgradeJob" | 956 | component="lp.code.model.branchjob.BranchUpgradeJob" |
19 | 947 | provides="lp.code.interfaces.branchjob.IBranchUpgradeJobSource"> | 957 | provides="lp.code.interfaces.branchjob.IBranchUpgradeJobSource"> |
20 | 948 | <allow interface="lp.code.interfaces.branchjob.IBranchUpgradeJobSource"/> | 958 | <allow interface="lp.code.interfaces.branchjob.IBranchUpgradeJobSource"/> |
21 | 949 | 959 | ||
22 | === modified file 'lib/lp/code/interfaces/branchmergeproposal.py' | |||
23 | --- lib/lp/code/interfaces/branchmergeproposal.py 2010-03-29 01:14:29 +0000 | |||
24 | +++ lib/lp/code/interfaces/branchmergeproposal.py 2010-03-29 01:14:30 +0000 | |||
25 | @@ -18,6 +18,8 @@ | |||
26 | 18 | 'ICreateMergeProposalJobSource', | 18 | 'ICreateMergeProposalJobSource', |
27 | 19 | 'IMergeProposalCreatedJob', | 19 | 'IMergeProposalCreatedJob', |
28 | 20 | 'IMergeProposalCreatedJobSource', | 20 | 'IMergeProposalCreatedJobSource', |
29 | 21 | 'IMergeProposalUpdatedEmailJob', | ||
30 | 22 | 'IMergeProposalUpdatedEmailJobSource', | ||
31 | 21 | 'IReviewRequestedEmailJob', | 23 | 'IReviewRequestedEmailJob', |
32 | 22 | 'IReviewRequestedEmailJobSource', | 24 | 'IReviewRequestedEmailJobSource', |
33 | 23 | 'IUpdatePreviewDiffJobSource', | 25 | 'IUpdatePreviewDiffJobSource', |
34 | @@ -632,6 +634,25 @@ | |||
35 | 632 | """ | 634 | """ |
36 | 633 | 635 | ||
37 | 634 | 636 | ||
38 | 637 | class IMergeProposalUpdatedEmailJob(IRunnableJob): | ||
39 | 638 | """Interface for the job to sends email about merge proposal updates.""" | ||
40 | 639 | |||
41 | 640 | editor = Attribute('The person that did the editing.') | ||
42 | 641 | delta_text = Attribute('The textual representation of the changed fields.') | ||
43 | 642 | |||
44 | 643 | |||
45 | 644 | class IMergeProposalUpdatedEmailJobSource(IJobSource): | ||
46 | 645 | """Create or retrieve jobs that email about merge proposal updates.""" | ||
47 | 646 | |||
48 | 647 | def create(merge_proposal, delta_text, editor): | ||
49 | 648 | """Create a job to email merge proposal updates to subscribers. | ||
50 | 649 | |||
51 | 650 | :param merge_proposal: The merge proposal that has been edited. | ||
52 | 651 | :param delta_text: The text representation of the changed fields. | ||
53 | 652 | :param editor: The person who did the editing. | ||
54 | 653 | """ | ||
55 | 654 | |||
56 | 655 | |||
57 | 635 | # XXX: JonathanLange 2010-01-06: This is only used in the scanner, perhaps it | 656 | # XXX: JonathanLange 2010-01-06: This is only used in the scanner, perhaps it |
58 | 636 | # should be moved there. | 657 | # should be moved there. |
59 | 637 | def notify_modified(proposal, func, *args, **kwargs): | 658 | def notify_modified(proposal, func, *args, **kwargs): |
60 | 638 | 659 | ||
61 | === modified file 'lib/lp/code/mail/branchmergeproposal.py' | |||
62 | --- lib/lp/code/mail/branchmergeproposal.py 2010-03-29 01:14:29 +0000 | |||
63 | +++ lib/lp/code/mail/branchmergeproposal.py 2010-03-29 01:14:30 +0000 | |||
64 | @@ -15,10 +15,12 @@ | |||
65 | 15 | from lp.code.adapters.branch import BranchMergeProposalDelta | 15 | from lp.code.adapters.branch import BranchMergeProposalDelta |
66 | 16 | from lp.code.enums import CodeReviewNotificationLevel | 16 | from lp.code.enums import CodeReviewNotificationLevel |
67 | 17 | from lp.code.interfaces.branchmergeproposal import ( | 17 | from lp.code.interfaces.branchmergeproposal import ( |
69 | 18 | IMergeProposalCreatedJobSource, IReviewRequestedEmailJobSource) | 18 | IMergeProposalCreatedJobSource, IMergeProposalUpdatedEmailJobSource, |
70 | 19 | IReviewRequestedEmailJobSource) | ||
71 | 19 | from lp.code.mail.branch import BranchMailer | 20 | from lp.code.mail.branch import BranchMailer |
72 | 20 | from lp.registry.interfaces.person import IPerson | 21 | from lp.registry.interfaces.person import IPerson |
73 | 21 | from lp.services.mail.basemailer import BaseMailer | 22 | from lp.services.mail.basemailer import BaseMailer |
74 | 23 | from lp.services.utils import text_delta | ||
75 | 22 | 24 | ||
76 | 23 | 25 | ||
77 | 24 | def send_merge_proposal_created_notifications(merge_proposal, event): | 26 | def send_merge_proposal_created_notifications(merge_proposal, event): |
78 | @@ -32,16 +34,24 @@ | |||
79 | 32 | 34 | ||
80 | 33 | def send_merge_proposal_modified_notifications(merge_proposal, event): | 35 | def send_merge_proposal_modified_notifications(merge_proposal, event): |
81 | 34 | """Notify branch subscribers when merge proposals are updated.""" | 36 | """Notify branch subscribers when merge proposals are updated.""" |
82 | 37 | # Check the user. | ||
83 | 35 | if event.user is None: | 38 | if event.user is None: |
84 | 36 | return | 39 | return |
85 | 37 | if isinstance(event.user, UnauthenticatedPrincipal): | 40 | if isinstance(event.user, UnauthenticatedPrincipal): |
86 | 38 | from_person = None | 41 | from_person = None |
87 | 39 | else: | 42 | else: |
88 | 40 | from_person = IPerson(event.user) | 43 | from_person = IPerson(event.user) |
93 | 41 | mailer = BMPMailer.forModification( | 44 | # Create a delta of the changes. If there are no changes to report, then |
94 | 42 | event.object_before_modification, merge_proposal, from_person) | 45 | # we're done. |
95 | 43 | if mailer is not None: | 46 | delta = BranchMergeProposalDelta.construct( |
96 | 44 | mailer.sendAll() | 47 | event.object_before_modification, merge_proposal) |
97 | 48 | if delta is None: | ||
98 | 49 | return | ||
99 | 50 | changes = text_delta( | ||
100 | 51 | delta, delta.delta_values, delta.new_values, delta.interface) | ||
101 | 52 | # Now create the job to send the email. | ||
102 | 53 | getUtility(IMergeProposalUpdatedEmailJobSource).create( | ||
103 | 54 | merge_proposal, changes, from_person) | ||
104 | 45 | 55 | ||
105 | 46 | 56 | ||
106 | 47 | def send_review_requested_notifications(vote_reference, event): | 57 | def send_review_requested_notifications(vote_reference, event): |
107 | @@ -57,13 +67,14 @@ | |||
108 | 57 | requested_reviews=None, preview_diff=None, | 67 | requested_reviews=None, preview_diff=None, |
109 | 58 | direct_email=False): | 68 | direct_email=False): |
110 | 59 | BranchMailer.__init__( | 69 | BranchMailer.__init__( |
112 | 60 | self, subject, template_name, recipients, from_address, delta, | 70 | self, subject, template_name, recipients, from_address, |
113 | 61 | message_id=message_id, notification_type='code-review') | 71 | message_id=message_id, notification_type='code-review') |
114 | 62 | self.merge_proposal = merge_proposal | 72 | self.merge_proposal = merge_proposal |
115 | 63 | if requested_reviews is None: | 73 | if requested_reviews is None: |
116 | 64 | requested_reviews = [] | 74 | requested_reviews = [] |
117 | 65 | self.requested_reviews = requested_reviews | 75 | self.requested_reviews = requested_reviews |
118 | 66 | self.preview_diff = preview_diff | 76 | self.preview_diff = preview_diff |
119 | 77 | self.delta_text = delta | ||
120 | 67 | self.template_params = self._generateTemplateParams() | 78 | self.template_params = self._generateTemplateParams() |
121 | 68 | self.direct_email = direct_email | 79 | self.direct_email = direct_email |
122 | 69 | 80 | ||
123 | @@ -95,8 +106,7 @@ | |||
124 | 95 | preview_diff=merge_proposal.preview_diff) | 106 | preview_diff=merge_proposal.preview_diff) |
125 | 96 | 107 | ||
126 | 97 | @classmethod | 108 | @classmethod |
129 | 98 | def forModification(cls, old_merge_proposal, merge_proposal, | 109 | def forModification(cls, merge_proposal, delta_text, from_user): |
128 | 99 | from_user=None): | ||
130 | 100 | """Return a mailer for BranchMergeProposal creation. | 110 | """Return a mailer for BranchMergeProposal creation. |
131 | 101 | 111 | ||
132 | 102 | :param merge_proposal: The BranchMergeProposal that was created. | 112 | :param merge_proposal: The BranchMergeProposal that was created. |
133 | @@ -111,14 +121,11 @@ | |||
134 | 111 | from_address = cls._format_user_address(from_user) | 121 | from_address = cls._format_user_address(from_user) |
135 | 112 | else: | 122 | else: |
136 | 113 | from_address = config.canonical.noreply_from_address | 123 | from_address = config.canonical.noreply_from_address |
137 | 114 | delta = BranchMergeProposalDelta.construct( | ||
138 | 115 | old_merge_proposal, merge_proposal) | ||
139 | 116 | if delta is None: | ||
140 | 117 | return None | ||
141 | 118 | return cls( | 124 | return cls( |
142 | 119 | '%(proposal_title)s', | 125 | '%(proposal_title)s', |
143 | 120 | 'branch-merge-proposal-updated.txt', recipients, | 126 | 'branch-merge-proposal-updated.txt', recipients, |
145 | 121 | merge_proposal, from_address, delta, get_msgid()) | 127 | merge_proposal, from_address, delta=delta_text, |
146 | 128 | message_id=get_msgid()) | ||
147 | 122 | 129 | ||
148 | 123 | @classmethod | 130 | @classmethod |
149 | 124 | def forReviewRequest(cls, reason, merge_proposal, from_user): | 131 | def forReviewRequest(cls, reason, merge_proposal, from_user): |
150 | @@ -189,6 +196,8 @@ | |||
151 | 189 | 'whiteboard': '', # No more whiteboard. | 196 | 'whiteboard': '', # No more whiteboard. |
152 | 190 | 'diff_cutoff_warning': '', | 197 | 'diff_cutoff_warning': '', |
153 | 191 | } | 198 | } |
154 | 199 | if self.delta_text is not None: | ||
155 | 200 | params['delta'] = self.delta_text | ||
156 | 192 | 201 | ||
157 | 193 | if proposal.prerequisite_branch is not None: | 202 | if proposal.prerequisite_branch is not None: |
158 | 194 | prereq_url = proposal.prerequisite_branch.bzr_identity | 203 | prereq_url = proposal.prerequisite_branch.bzr_identity |
159 | 195 | 204 | ||
160 | === modified file 'lib/lp/code/mail/tests/test_branchmergeproposal.py' | |||
161 | --- lib/lp/code/mail/tests/test_branchmergeproposal.py 2010-03-29 01:14:29 +0000 | |||
162 | +++ lib/lp/code/mail/tests/test_branchmergeproposal.py 2010-03-29 01:14:30 +0000 | |||
163 | @@ -25,7 +25,7 @@ | |||
164 | 25 | from lp.code.model.branch import update_trigger_modified_fields | 25 | from lp.code.model.branch import update_trigger_modified_fields |
165 | 26 | from lp.code.model.branchmergeproposaljob import ( | 26 | from lp.code.model.branchmergeproposaljob import ( |
166 | 27 | BranchMergeProposalJob, BranchMergeProposalJobType, | 27 | BranchMergeProposalJob, BranchMergeProposalJobType, |
168 | 28 | ReviewRequestedEmailJob) | 28 | MergeProposalUpdatedEmailJob, ReviewRequestedEmailJob) |
169 | 29 | from lp.code.model.codereviewvote import CodeReviewVoteReference | 29 | from lp.code.model.codereviewvote import CodeReviewVoteReference |
170 | 30 | from canonical.launchpad.webapp import canonical_url | 30 | from canonical.launchpad.webapp import canonical_url |
171 | 31 | from lp.testing import login_person, TestCaseWithFactory | 31 | from lp.testing import login_person, TestCaseWithFactory |
172 | @@ -280,95 +280,93 @@ | |||
173 | 280 | warning_text = "The attached diff has been truncated due to its size." | 280 | warning_text = "The attached diff has been truncated due to its size." |
174 | 281 | self.assertTrue(warning_text in ctrl.body) | 281 | self.assertTrue(warning_text in ctrl.body) |
175 | 282 | 282 | ||
177 | 283 | def test_forModificationNoModification(self): | 283 | def getProposalUpdatedEmailJob(self, merge_proposal): |
178 | 284 | """Return the merge proposal updated email job.""" | ||
179 | 285 | jobs = list( | ||
180 | 286 | IStore(BranchMergeProposalJob).find( | ||
181 | 287 | BranchMergeProposalJob, | ||
182 | 288 | BranchMergeProposalJob.branch_merge_proposal == | ||
183 | 289 | merge_proposal, | ||
184 | 290 | BranchMergeProposalJob.job_type == | ||
185 | 291 | BranchMergeProposalJobType.MERGE_PROPOSAL_UPDATED)) | ||
186 | 292 | if len(jobs) == 0: | ||
187 | 293 | return None | ||
188 | 294 | elif len(jobs) == 1: | ||
189 | 295 | return MergeProposalUpdatedEmailJob(jobs[0]) | ||
190 | 296 | else: | ||
191 | 297 | self.fail('There are more than one jobs.') | ||
192 | 298 | |||
193 | 299 | def test_no_job_created_if_no_delta(self): | ||
194 | 284 | """Ensure None is returned if no change has been made.""" | 300 | """Ensure None is returned if no change has been made.""" |
195 | 285 | merge_proposal, person = self.makeProposalWithSubscriber() | 301 | merge_proposal, person = self.makeProposalWithSubscriber() |
196 | 286 | old_merge_proposal = BranchMergeProposalDelta.snapshot(merge_proposal) | 302 | old_merge_proposal = BranchMergeProposalDelta.snapshot(merge_proposal) |
199 | 287 | self.assertEqual(None, BMPMailer.forModification( | 303 | event = ObjectModifiedEvent( |
200 | 288 | old_merge_proposal, merge_proposal, merge_proposal.registrant)) | 304 | merge_proposal, old_merge_proposal, [], merge_proposal.registrant) |
201 | 305 | send_merge_proposal_modified_notifications(merge_proposal, event) | ||
202 | 306 | self.assertIs(None, self.getProposalUpdatedEmailJob(merge_proposal)) | ||
203 | 289 | 307 | ||
205 | 290 | def makeMergeProposalMailerModification(self): | 308 | def makeProposalUpdatedEmailJob(self): |
206 | 291 | """Fixture method providing a mailer for a modified merge proposal""" | 309 | """Fixture method providing a mailer for a modified merge proposal""" |
207 | 292 | merge_proposal, subscriber = self.makeProposalWithSubscriber() | 310 | merge_proposal, subscriber = self.makeProposalWithSubscriber() |
208 | 293 | old_merge_proposal = BranchMergeProposalDelta.snapshot(merge_proposal) | 311 | old_merge_proposal = BranchMergeProposalDelta.snapshot(merge_proposal) |
209 | 294 | merge_proposal.requestReview() | 312 | merge_proposal.requestReview() |
210 | 295 | merge_proposal.commit_message = 'new commit message' | 313 | merge_proposal.commit_message = 'new commit message' |
211 | 296 | merge_proposal.description = 'change description' | 314 | merge_proposal.description = 'change description' |
212 | 315 | event = ObjectModifiedEvent( | ||
213 | 316 | merge_proposal, old_merge_proposal, [], merge_proposal.registrant) | ||
214 | 317 | send_merge_proposal_modified_notifications(merge_proposal, event) | ||
215 | 318 | job = self.getProposalUpdatedEmailJob(merge_proposal) | ||
216 | 319 | self.assertIsNot(None, job, 'Job was not created.') | ||
217 | 320 | return job, subscriber | ||
218 | 321 | |||
219 | 322 | def test_forModificationHasMsgId(self): | ||
220 | 323 | """Ensure the right delta is filled out if there is a change.""" | ||
221 | 324 | merge_proposal = self.factory.makeBranchMergeProposal() | ||
222 | 297 | mailer = BMPMailer.forModification( | 325 | mailer = BMPMailer.forModification( |
236 | 298 | old_merge_proposal, merge_proposal, merge_proposal.registrant) | 326 | merge_proposal, 'the diff', merge_proposal.registrant) |
237 | 299 | return mailer, subscriber | 327 | self.assertIsNot(None, mailer.message_id, 'message_id not set') |
225 | 300 | |||
226 | 301 | def test_forModificationWithModificationDelta(self): | ||
227 | 302 | """Ensure the right delta is filled out if there is a change.""" | ||
228 | 303 | mailer, subscriber = self.makeMergeProposalMailerModification() | ||
229 | 304 | self.assertEqual('new commit message', | ||
230 | 305 | mailer.delta.commit_message) | ||
231 | 306 | |||
232 | 307 | def test_forModificationHasMsgId(self): | ||
233 | 308 | """Ensure the right delta is filled out if there is a change.""" | ||
234 | 309 | mailer, subscriber = self.makeMergeProposalMailerModification() | ||
235 | 310 | assert mailer.message_id is not None, 'message_id not set' | ||
238 | 311 | 328 | ||
239 | 312 | def test_forModificationWithModificationTextDelta(self): | 329 | def test_forModificationWithModificationTextDelta(self): |
240 | 313 | """Ensure the right delta is filled out if there is a change.""" | 330 | """Ensure the right delta is filled out if there is a change.""" |
242 | 314 | mailer, subscriber = self.makeMergeProposalMailerModification() | 331 | job, subscriber = self.makeProposalUpdatedEmailJob() |
243 | 315 | self.assertEqual( | 332 | self.assertEqual( |
244 | 316 | ' Status: Work in progress => Needs review\n\n' | 333 | ' Status: Work in progress => Needs review\n\n' |
245 | 317 | 'Commit Message changed to:\n\nnew commit message\n\n' | 334 | 'Commit Message changed to:\n\nnew commit message\n\n' |
246 | 318 | 'Description changed to:\n\nchange description', | 335 | 'Description changed to:\n\nchange description', |
275 | 319 | mailer.textDelta()) | 336 | job.delta_text) |
248 | 320 | |||
249 | 321 | def test_generateEmail(self): | ||
250 | 322 | """Ensure that contents of modification mails are right.""" | ||
251 | 323 | mailer, subscriber = self.makeMergeProposalMailerModification() | ||
252 | 324 | ctrl = mailer.generateEmail('baz.quxx@example.com', subscriber) | ||
253 | 325 | self.assertEqual('[Merge] ' | ||
254 | 326 | 'lp://dev/~bob/super-product/fix-foo-for-bar into ' | ||
255 | 327 | 'lp://dev/~mary/super-product/bar', ctrl.subject) | ||
256 | 328 | url = canonical_url(mailer.merge_proposal) | ||
257 | 329 | reason = mailer._recipients.getReason( | ||
258 | 330 | subscriber.preferredemail.email)[0].getReason() | ||
259 | 331 | self.assertEqual("""\ | ||
260 | 332 | The proposal to merge lp://dev/~bob/super-product/fix-foo-for-bar into lp://dev/~mary/super-product/bar has been updated. | ||
261 | 333 | |||
262 | 334 | Status: Work in progress => Needs review | ||
263 | 335 | |||
264 | 336 | Commit Message changed to: | ||
265 | 337 | |||
266 | 338 | new commit message | ||
267 | 339 | |||
268 | 340 | Description changed to: | ||
269 | 341 | |||
270 | 342 | change description | ||
271 | 343 | --\x20 | ||
272 | 344 | %s | ||
273 | 345 | %s | ||
274 | 346 | """ % (url, reason), ctrl.body) | ||
276 | 347 | 337 | ||
277 | 348 | def test_send_merge_proposal_modified_notifications(self): | 338 | def test_send_merge_proposal_modified_notifications(self): |
278 | 349 | """Should send emails when invoked with correct parameters.""" | 339 | """Should send emails when invoked with correct parameters.""" |
283 | 350 | merge_proposal, subscriber = self.makeProposalWithSubscriber() | 340 | job, subscriber = self.makeProposalUpdatedEmailJob() |
280 | 351 | snapshot = BranchMergeProposalDelta.snapshot(merge_proposal) | ||
281 | 352 | merge_proposal.commit_message = 'new message' | ||
282 | 353 | event = ObjectModifiedEvent(merge_proposal, snapshot, None) | ||
284 | 354 | pop_notifications() | 341 | pop_notifications() |
286 | 355 | send_merge_proposal_modified_notifications(merge_proposal, event) | 342 | job.run() |
287 | 356 | emails = pop_notifications() | 343 | emails = pop_notifications() |
288 | 357 | self.assertEqual(3, len(emails), | 344 | self.assertEqual(3, len(emails), |
289 | 358 | 'There should be three emails sent out. One to the ' | 345 | 'There should be three emails sent out. One to the ' |
290 | 359 | 'explicit subscriber above, and one each to the ' | 346 | 'explicit subscriber above, and one each to the ' |
291 | 360 | 'source branch owner and the target branch owner ' | 347 | 'source branch owner and the target branch owner ' |
292 | 361 | 'who were implicitly subscribed to their branches.') | 348 | 'who were implicitly subscribed to their branches.') |
303 | 362 | 349 | email = emails[0] | |
304 | 363 | def test_send_merge_proposal_modified_notifications_no_delta(self): | 350 | self.assertEqual('[Merge] ' |
305 | 364 | """Should not send emails if no delta.""" | 351 | 'lp://dev/~bob/super-product/fix-foo-for-bar into\n\t' |
306 | 365 | merge_proposal, subscriber = self.makeProposalWithSubscriber() | 352 | 'lp://dev/~mary/super-product/bar', email['subject']) |
307 | 366 | snapshot = BranchMergeProposalDelta.snapshot(merge_proposal) | 353 | self.assertEqual(dedent("""\ |
308 | 367 | event = ObjectModifiedEvent(merge_proposal, snapshot, None) | 354 | The proposal to merge lp://dev/~bob/super-product/fix-foo-for-bar into lp://dev/~mary/super-product/bar has been updated. |
309 | 368 | pop_notifications() | 355 | |
310 | 369 | send_merge_proposal_modified_notifications(merge_proposal, event) | 356 | Status: Work in progress => Needs review |
311 | 370 | emails = pop_notifications() | 357 | |
312 | 371 | self.assertEqual([], emails) | 358 | Commit Message changed to: |
313 | 359 | |||
314 | 360 | new commit message | ||
315 | 361 | |||
316 | 362 | Description changed to: | ||
317 | 363 | |||
318 | 364 | change description | ||
319 | 365 | --\x20 | ||
320 | 366 | %s | ||
321 | 367 | You are subscribed to branch lp://dev/~bob/super-product/fix-foo-for-bar. | ||
322 | 368 | """) % canonical_url(job.branch_merge_proposal), | ||
323 | 369 | email.get_payload(decode=True)) | ||
324 | 372 | 370 | ||
325 | 373 | def assertRecipientsMatches(self, recipients, mailer): | 371 | def assertRecipientsMatches(self, recipients, mailer): |
326 | 374 | """Assert that `mailer` will send to the people in `recipients`.""" | 372 | """Assert that `mailer` will send to the people in `recipients`.""" |
327 | 375 | 373 | ||
328 | === modified file 'lib/lp/code/model/branchmergeproposaljob.py' | |||
329 | --- lib/lp/code/model/branchmergeproposaljob.py 2010-03-29 01:14:29 +0000 | |||
330 | +++ lib/lp/code/model/branchmergeproposaljob.py 2010-03-29 01:14:30 +0000 | |||
331 | @@ -52,7 +52,8 @@ | |||
332 | 52 | IBranchMergeProposalJob, ICodeReviewCommentEmailJob, | 52 | IBranchMergeProposalJob, ICodeReviewCommentEmailJob, |
333 | 53 | ICodeReviewCommentEmailJobSource, ICreateMergeProposalJob, | 53 | ICodeReviewCommentEmailJobSource, ICreateMergeProposalJob, |
334 | 54 | ICreateMergeProposalJobSource, IMergeProposalCreatedJob, | 54 | ICreateMergeProposalJobSource, IMergeProposalCreatedJob, |
336 | 55 | IMergeProposalCreatedJobSource, IReviewRequestedEmailJob, | 55 | IMergeProposalCreatedJobSource, IMergeProposalUpdatedEmailJob, |
337 | 56 | IMergeProposalUpdatedEmailJobSource, IReviewRequestedEmailJob, | ||
338 | 56 | IReviewRequestedEmailJobSource, IUpdatePreviewDiffJobSource, | 57 | IReviewRequestedEmailJobSource, IUpdatePreviewDiffJobSource, |
339 | 57 | ) | 58 | ) |
340 | 58 | from lp.code.mail.branch import RecipientReason | 59 | from lp.code.mail.branch import RecipientReason |
341 | @@ -97,6 +98,13 @@ | |||
342 | 97 | requested reviewer team asking them to review the proposal. | 98 | requested reviewer team asking them to review the proposal. |
343 | 98 | """) | 99 | """) |
344 | 99 | 100 | ||
345 | 101 | MERGE_PROPOSAL_UPDATED = DBItem(4, """ | ||
346 | 102 | Merge proposal updated | ||
347 | 103 | |||
348 | 104 | This job sends an email to the subscribers informing them of fields | ||
349 | 105 | that have been changed on the merge proposal itself. | ||
350 | 106 | """) | ||
351 | 107 | |||
352 | 100 | 108 | ||
353 | 101 | class BranchMergeProposalJob(Storm): | 109 | class BranchMergeProposalJob(Storm): |
354 | 102 | """Base class for jobs related to branch merge proposals.""" | 110 | """Base class for jobs related to branch merge proposals.""" |
355 | @@ -491,3 +499,68 @@ | |||
356 | 491 | if self.requester is not None: | 499 | if self.requester is not None: |
357 | 492 | recipients.append(self.requester.preferredemail) | 500 | recipients.append(self.requester.preferredemail) |
358 | 493 | return recipients | 501 | return recipients |
359 | 502 | |||
360 | 503 | |||
361 | 504 | class MergeProposalUpdatedEmailJob(BranchMergeProposalJobDerived): | ||
362 | 505 | """Send email to the subscribers informing them of updated fields. | ||
363 | 506 | |||
364 | 507 | When attributes of the merge proposal are edited, we inform the | ||
365 | 508 | subscribers. | ||
366 | 509 | """ | ||
367 | 510 | |||
368 | 511 | implements(IMergeProposalUpdatedEmailJob) | ||
369 | 512 | |||
370 | 513 | classProvides(IMergeProposalUpdatedEmailJobSource) | ||
371 | 514 | |||
372 | 515 | class_job_type = BranchMergeProposalJobType.MERGE_PROPOSAL_UPDATED | ||
373 | 516 | |||
374 | 517 | def run(self): | ||
375 | 518 | """See `IRunnableJob`.""" | ||
376 | 519 | mailer = BMPMailer.forModification( | ||
377 | 520 | self.branch_merge_proposal, self.delta_text, self.editor) | ||
378 | 521 | mailer.sendAll() | ||
379 | 522 | |||
380 | 523 | @classmethod | ||
381 | 524 | def create(cls, merge_proposal, delta_text, editor): | ||
382 | 525 | """See `IReviewRequestedEmailJobSource`.""" | ||
383 | 526 | metadata = cls.getMetadata(delta_text, editor) | ||
384 | 527 | job = BranchMergeProposalJob( | ||
385 | 528 | merge_proposal, cls.class_job_type, metadata) | ||
386 | 529 | return cls(job) | ||
387 | 530 | |||
388 | 531 | @staticmethod | ||
389 | 532 | def getMetadata(delta_text, editor): | ||
390 | 533 | metadata = {'delta_text': delta_text} | ||
391 | 534 | if editor is not None: | ||
392 | 535 | metadata['editor'] = editor.name; | ||
393 | 536 | return metadata | ||
394 | 537 | |||
395 | 538 | @property | ||
396 | 539 | def editor(self): | ||
397 | 540 | """The person who updated the merge proposal.""" | ||
398 | 541 | editor_name = self.metadata.get('editor') | ||
399 | 542 | if editor_name is None: | ||
400 | 543 | return None | ||
401 | 544 | else: | ||
402 | 545 | return getUtility(IPersonSet).getByName(editor_name) | ||
403 | 546 | |||
404 | 547 | @property | ||
405 | 548 | def delta_text(self): | ||
406 | 549 | """The changes that were made to the merge proposal.""" | ||
407 | 550 | return self.metadata['delta_text'] | ||
408 | 551 | |||
409 | 552 | def getOopsVars(self): | ||
410 | 553 | """See `IRunnableJob`.""" | ||
411 | 554 | vars = BranchMergeProposalJobDerived.getOopsVars(self) | ||
412 | 555 | vars.extend([ | ||
413 | 556 | ('editor', self.metadata.get('editor', '(not set)')), | ||
414 | 557 | ('delta_text', self.metadata['delta_text']), | ||
415 | 558 | ]) | ||
416 | 559 | return vars | ||
417 | 560 | |||
418 | 561 | def getErrorRecipients(self): | ||
419 | 562 | """Return a list of email-ids to notify about user errors.""" | ||
420 | 563 | recipients = [] | ||
421 | 564 | if self.editor is not None: | ||
422 | 565 | recipients.append(self.editor.preferredemail) | ||
423 | 566 | return recipients |
I'm not sure that all the docstrings in lib/lp/ code/mail/ tests/test_ branchmergeprop osal.py make sense any more -- can you have a whip through and make sure they're up to date?
Otherwise, it's all good.