Merge lp:~thumper/launchpad/new-reviewer-email-job into lp:launchpad
- new-reviewer-email-job
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Tim Penhey |
Approved revision: | no longer in the source branch. |
Merge reported by: | Tim Penhey |
Merged at revision: | not available |
Proposed branch: | lp:~thumper/launchpad/new-reviewer-email-job |
Merge into: | lp:launchpad |
Prerequisite: | lp:~thumper/launchpad/code-review-comment-email-job |
Diff against target: |
415 lines (+201/-34) 8 files modified
lib/lp/code/configure.zcml (+10/-0) lib/lp/code/interfaces/branchmergeproposal.py (+19/-0) lib/lp/code/mail/branch.py (+6/-7) lib/lp/code/mail/branchmergeproposal.py (+3/-16) lib/lp/code/mail/tests/test_branch.py (+7/-3) lib/lp/code/mail/tests/test_branchmergeproposal.py (+79/-5) lib/lp/code/model/branchmergeproposal.py (+2/-1) lib/lp/code/model/branchmergeproposaljob.py (+75/-2) |
To merge this branch: | bzr merge lp:~thumper/launchpad/new-reviewer-email-job |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brad Crittenden (community) | code | Approve | |
Review via email: mp+21610@code.launchpad.net |
Commit message
Description of the change
This branch adds the job to send the notification email for review requests.
lib/lp/
- simple registering of the job and the job source
lib/lp/
- define the interface for the job itself and the utility to create them
lib/lp/
- since the details are now stored in the job, we pass through a few
more details into the BMPMailer.
lib/lp/
- vastly simplify the event listner function.
NOTE: this method is renamed in a later pipe.
lib/lp/
lib/lp/
- since the static constructor function changed the parameters, the tests
also had to be updated.
lib/lp/
- some of the existing tests had to be augmented with job.run in order
to send the email that they were checking before
- other tests are checking the job creation.
lib/lp/
- the implementation of the new job
- just defers to the existing BMPMailer functionality
Preview Diff
1 | === modified file 'lib/lp/code/configure.zcml' | |||
2 | --- lib/lp/code/configure.zcml 2010-03-29 00:59:29 +0000 | |||
3 | +++ lib/lp/code/configure.zcml 2010-03-29 00:59:31 +0000 | |||
4 | @@ -933,6 +933,16 @@ | |||
5 | 933 | </class> | 933 | </class> |
6 | 934 | 934 | ||
7 | 935 | <securedutility | 935 | <securedutility |
8 | 936 | component="lp.code.model.branchmergeproposaljob.ReviewRequestedEmailJob" | ||
9 | 937 | provides="lp.code.interfaces.branchmergeproposal.IReviewRequestedEmailJobSource"> | ||
10 | 938 | <allow interface="lp.code.interfaces.branchmergeproposal.IReviewRequestedEmailJobSource"/> | ||
11 | 939 | </securedutility> | ||
12 | 940 | <class class="lp.code.model.branchmergeproposaljob.ReviewRequestedEmailJob"> | ||
13 | 941 | <allow interface="lp.services.job.interfaces.job.IRunnableJob" /> | ||
14 | 942 | <allow interface="lp.code.interfaces.branchmergeproposal.IBranchMergeProposalJob" /> | ||
15 | 943 | </class> | ||
16 | 944 | |||
17 | 945 | <securedutility | ||
18 | 936 | component="lp.code.model.branchjob.BranchUpgradeJob" | 946 | component="lp.code.model.branchjob.BranchUpgradeJob" |
19 | 937 | provides="lp.code.interfaces.branchjob.IBranchUpgradeJobSource"> | 947 | provides="lp.code.interfaces.branchjob.IBranchUpgradeJobSource"> |
20 | 938 | <allow interface="lp.code.interfaces.branchjob.IBranchUpgradeJobSource"/> | 948 | <allow interface="lp.code.interfaces.branchjob.IBranchUpgradeJobSource"/> |
21 | 939 | 949 | ||
22 | === modified file 'lib/lp/code/interfaces/branchmergeproposal.py' | |||
23 | --- lib/lp/code/interfaces/branchmergeproposal.py 2010-03-29 00:59:29 +0000 | |||
24 | +++ lib/lp/code/interfaces/branchmergeproposal.py 2010-03-29 00:59:31 +0000 | |||
25 | @@ -18,6 +18,8 @@ | |||
26 | 18 | 'ICreateMergeProposalJobSource', | 18 | 'ICreateMergeProposalJobSource', |
27 | 19 | 'IMergeProposalCreatedJob', | 19 | 'IMergeProposalCreatedJob', |
28 | 20 | 'IMergeProposalCreatedJobSource', | 20 | 'IMergeProposalCreatedJobSource', |
29 | 21 | 'IReviewRequestedEmailJob', | ||
30 | 22 | 'IReviewRequestedEmailJobSource', | ||
31 | 21 | 'IUpdatePreviewDiffJobSource', | 23 | 'IUpdatePreviewDiffJobSource', |
32 | 22 | 'notify_modified', | 24 | 'notify_modified', |
33 | 23 | ] | 25 | ] |
34 | @@ -613,6 +615,23 @@ | |||
35 | 613 | """Create a job to email subscribers about the comment.""" | 615 | """Create a job to email subscribers about the comment.""" |
36 | 614 | 616 | ||
37 | 615 | 617 | ||
38 | 618 | class IReviewRequestedEmailJob(IRunnableJob): | ||
39 | 619 | """Interface for the job to sends review request emails.""" | ||
40 | 620 | |||
41 | 621 | reviewer = Attribute('The person or team asked to do the review.') | ||
42 | 622 | requester = Attribute('The person who as asked for the review.') | ||
43 | 623 | |||
44 | 624 | |||
45 | 625 | class IReviewRequestedEmailJobSource(IJobSource): | ||
46 | 626 | """Create or retrieve jobs that email review requests.""" | ||
47 | 627 | |||
48 | 628 | def create(review_request): | ||
49 | 629 | """Create a job to email a review request. | ||
50 | 630 | |||
51 | 631 | :param review_request: A vote reference for the requested review. | ||
52 | 632 | """ | ||
53 | 633 | |||
54 | 634 | |||
55 | 616 | # XXX: JonathanLange 2010-01-06: This is only used in the scanner, perhaps it | 635 | # XXX: JonathanLange 2010-01-06: This is only used in the scanner, perhaps it |
56 | 617 | # should be moved there. | 636 | # should be moved there. |
57 | 618 | def notify_modified(proposal, func, *args, **kwargs): | 637 | def notify_modified(proposal, func, *args, **kwargs): |
58 | 619 | 638 | ||
59 | === modified file 'lib/lp/code/mail/branch.py' | |||
60 | --- lib/lp/code/mail/branch.py 2010-03-09 16:58:30 +0000 | |||
61 | +++ lib/lp/code/mail/branch.py 2010-03-29 00:59:31 +0000 | |||
62 | @@ -69,23 +69,22 @@ | |||
63 | 69 | review_level=subscription.review_level) | 69 | review_level=subscription.review_level) |
64 | 70 | 70 | ||
65 | 71 | @classmethod | 71 | @classmethod |
67 | 72 | def forReviewer(cls, vote_reference, recipient, | 72 | def forReviewer(cls, branch_merge_proposal, pending_review, reviewer, |
68 | 73 | branch_identity_cache=None): | 73 | branch_identity_cache=None): |
69 | 74 | """Construct RecipientReason for a reviewer. | 74 | """Construct RecipientReason for a reviewer. |
70 | 75 | 75 | ||
71 | 76 | The reviewer will be the sole recipient. | 76 | The reviewer will be the sole recipient. |
72 | 77 | """ | 77 | """ |
76 | 78 | merge_proposal = vote_reference.branch_merge_proposal | 78 | branch = branch_merge_proposal.source_branch |
77 | 79 | branch = merge_proposal.source_branch | 79 | if pending_review: |
75 | 80 | if vote_reference.comment is None: | ||
78 | 81 | reason_template = ( | 80 | reason_template = ( |
79 | 82 | '%(entity_is)s requested to review %(merge_proposal)s.') | 81 | '%(entity_is)s requested to review %(merge_proposal)s.') |
80 | 83 | else: | 82 | else: |
81 | 84 | reason_template = ( | 83 | reason_template = ( |
82 | 85 | '%(entity_is)s reviewing %(merge_proposal)s.') | 84 | '%(entity_is)s reviewing %(merge_proposal)s.') |
86 | 86 | return cls(vote_reference.reviewer, recipient, branch, | 85 | return cls(reviewer, reviewer, branch, 'Reviewer', |
87 | 87 | 'Reviewer', reason_template, merge_proposal, | 86 | reason_template, branch_merge_proposal, |
88 | 88 | branch_identity_cache=branch_identity_cache) | 87 | branch_identity_cache=branch_identity_cache) |
89 | 89 | 88 | ||
90 | 90 | @classmethod | 89 | @classmethod |
91 | 91 | def forRegistrant(cls, merge_proposal, branch_identity_cache=None): | 90 | def forRegistrant(cls, merge_proposal, branch_identity_cache=None): |
92 | 92 | 91 | ||
93 | === modified file 'lib/lp/code/mail/branchmergeproposal.py' | |||
94 | --- lib/lp/code/mail/branchmergeproposal.py 2010-02-17 08:57:04 +0000 | |||
95 | +++ lib/lp/code/mail/branchmergeproposal.py 2010-03-29 00:59:31 +0000 | |||
96 | @@ -15,8 +15,8 @@ | |||
97 | 15 | from lp.code.adapters.branch import BranchMergeProposalDelta | 15 | from lp.code.adapters.branch import BranchMergeProposalDelta |
98 | 16 | from lp.code.enums import CodeReviewNotificationLevel | 16 | from lp.code.enums import CodeReviewNotificationLevel |
99 | 17 | from lp.code.interfaces.branchmergeproposal import ( | 17 | from lp.code.interfaces.branchmergeproposal import ( |
102 | 18 | IMergeProposalCreatedJobSource) | 18 | IMergeProposalCreatedJobSource, IReviewRequestedEmailJobSource) |
103 | 19 | from lp.code.mail.branch import BranchMailer, RecipientReason | 19 | from lp.code.mail.branch import BranchMailer |
104 | 20 | from lp.registry.interfaces.person import IPerson | 20 | from lp.registry.interfaces.person import IPerson |
105 | 21 | from lp.services.mail.basemailer import BaseMailer | 21 | from lp.services.mail.basemailer import BaseMailer |
106 | 22 | 22 | ||
107 | @@ -46,20 +46,7 @@ | |||
108 | 46 | 46 | ||
109 | 47 | def send_review_requested_notifications(vote_reference, event): | 47 | def send_review_requested_notifications(vote_reference, event): |
110 | 48 | """Notify the reviewer that they have been requested to review.""" | 48 | """Notify the reviewer that they have been requested to review.""" |
125 | 49 | # XXX: rockstar - 9 Oct 2008 - If the reviewer is a team, don't send | 49 | getUtility(IReviewRequestedEmailJobSource).create(vote_reference) |
112 | 50 | # email. This is to stop the abuse of a user spamming all members of | ||
113 | 51 | # a team by requesting them to review a (possibly unrelated) branch. | ||
114 | 52 | # Ideally we'd come up with a better solution, but I can't think of | ||
115 | 53 | # one yet. In all other places we are emailing subscribers directly | ||
116 | 54 | # rather than people that haven't subscribed. | ||
117 | 55 | # See bug #281056. (affects IBranchMergeProposal) | ||
118 | 56 | if not vote_reference.reviewer.is_team: | ||
119 | 57 | reason = RecipientReason.forReviewer( | ||
120 | 58 | vote_reference, vote_reference.reviewer) | ||
121 | 59 | mailer = BMPMailer.forReviewRequest( | ||
122 | 60 | reason, vote_reference.branch_merge_proposal, | ||
123 | 61 | vote_reference.registrant) | ||
124 | 62 | mailer.sendAll() | ||
126 | 63 | 50 | ||
127 | 64 | 51 | ||
128 | 65 | class BMPMailer(BranchMailer): | 52 | class BMPMailer(BranchMailer): |
129 | 66 | 53 | ||
130 | === modified file 'lib/lp/code/mail/tests/test_branch.py' | |||
131 | --- lib/lp/code/mail/tests/test_branch.py 2009-11-12 09:00:35 +0000 | |||
132 | +++ lib/lp/code/mail/tests/test_branch.py 2010-03-29 00:59:31 +0000 | |||
133 | @@ -59,8 +59,11 @@ | |||
134 | 59 | 59 | ||
135 | 60 | def test_forReviewer(self): | 60 | def test_forReviewer(self): |
136 | 61 | """Test values when created from a branch subscription.""" | 61 | """Test values when created from a branch subscription.""" |
139 | 62 | ignored, vote_reference, subscriber = self.makeReviewerAndSubscriber() | 62 | merge_proposal, vote_reference, subscriber = ( |
140 | 63 | reason = RecipientReason.forReviewer(vote_reference, subscriber) | 63 | self.makeReviewerAndSubscriber()) |
141 | 64 | pending_review = vote_reference.comment is None | ||
142 | 65 | reason = RecipientReason.forReviewer( | ||
143 | 66 | merge_proposal, pending_review, subscriber) | ||
144 | 64 | self.assertEqual(subscriber, reason.subscriber) | 67 | self.assertEqual(subscriber, reason.subscriber) |
145 | 65 | self.assertEqual(subscriber, reason.recipient) | 68 | self.assertEqual(subscriber, reason.recipient) |
146 | 66 | self.assertEqual( | 69 | self.assertEqual( |
147 | @@ -68,7 +71,8 @@ | |||
148 | 68 | 71 | ||
149 | 69 | def test_getReasonReviewer(self): | 72 | def test_getReasonReviewer(self): |
150 | 70 | bmp, vote_reference, subscriber = self.makeReviewerAndSubscriber() | 73 | bmp, vote_reference, subscriber = self.makeReviewerAndSubscriber() |
152 | 71 | reason = RecipientReason.forReviewer(vote_reference, subscriber) | 74 | pending_review = vote_reference.comment is None |
153 | 75 | reason = RecipientReason.forReviewer(bmp, pending_review, subscriber) | ||
154 | 72 | self.assertEqual( | 76 | self.assertEqual( |
155 | 73 | 'You are requested to review the proposed merge of %s into %s.' | 77 | 'You are requested to review the proposed merge of %s into %s.' |
156 | 74 | % (bmp.source_branch.bzr_identity, | 78 | % (bmp.source_branch.bzr_identity, |
157 | 75 | 79 | ||
158 | === modified file 'lib/lp/code/mail/tests/test_branchmergeproposal.py' | |||
159 | --- lib/lp/code/mail/tests/test_branchmergeproposal.py 2010-03-05 03:35:10 +0000 | |||
160 | +++ lib/lp/code/mail/tests/test_branchmergeproposal.py 2010-03-29 00:59:31 +0000 | |||
161 | @@ -5,10 +5,12 @@ | |||
162 | 5 | 5 | ||
163 | 6 | from difflib import unified_diff | 6 | from difflib import unified_diff |
164 | 7 | from unittest import TestLoader | 7 | from unittest import TestLoader |
165 | 8 | from textwrap import dedent | ||
166 | 8 | import transaction | 9 | import transaction |
167 | 9 | 10 | ||
168 | 10 | from zope.security.proxy import removeSecurityProxy | 11 | from zope.security.proxy import removeSecurityProxy |
169 | 11 | 12 | ||
170 | 13 | from canonical.launchpad.interfaces import IStore | ||
171 | 12 | from canonical.testing import ( | 14 | from canonical.testing import ( |
172 | 13 | DatabaseFunctionalLayer, LaunchpadFunctionalLayer) | 15 | DatabaseFunctionalLayer, LaunchpadFunctionalLayer) |
173 | 14 | 16 | ||
174 | @@ -21,6 +23,9 @@ | |||
175 | 21 | from lp.code.mail.branchmergeproposal import ( | 23 | from lp.code.mail.branchmergeproposal import ( |
176 | 22 | BMPMailer, send_merge_proposal_modified_notifications) | 24 | BMPMailer, send_merge_proposal_modified_notifications) |
177 | 23 | from lp.code.model.branch import update_trigger_modified_fields | 25 | from lp.code.model.branch import update_trigger_modified_fields |
178 | 26 | from lp.code.model.branchmergeproposaljob import ( | ||
179 | 27 | BranchMergeProposalJob, BranchMergeProposalJobType, | ||
180 | 28 | ReviewRequestedEmailJob) | ||
181 | 24 | from lp.code.model.codereviewvote import CodeReviewVoteReference | 29 | from lp.code.model.codereviewvote import CodeReviewVoteReference |
182 | 25 | from canonical.launchpad.webapp import canonical_url | 30 | from canonical.launchpad.webapp import canonical_url |
183 | 26 | from lp.testing import login_person, TestCaseWithFactory | 31 | from lp.testing import login_person, TestCaseWithFactory |
184 | @@ -381,7 +386,8 @@ | |||
185 | 381 | request = CodeReviewVoteReference( | 386 | request = CodeReviewVoteReference( |
186 | 382 | branch_merge_proposal=merge_proposal, reviewer=candidate, | 387 | branch_merge_proposal=merge_proposal, reviewer=candidate, |
187 | 383 | registrant=requester) | 388 | registrant=requester) |
189 | 384 | return RecipientReason.forReviewer(request, candidate), requester | 389 | reason = RecipientReason.forReviewer(merge_proposal, True, candidate) |
190 | 390 | return reason, requester | ||
191 | 385 | 391 | ||
192 | 386 | def test_forReviewRequest(self): | 392 | def test_forReviewRequest(self): |
193 | 387 | """Test creating a mailer for a review request.""" | 393 | """Test creating a mailer for a review request.""" |
194 | @@ -431,6 +437,76 @@ | |||
195 | 431 | login_person(self.owner) | 437 | login_person(self.owner) |
196 | 432 | return person | 438 | return person |
197 | 433 | 439 | ||
198 | 440 | def getReviewNotificationEmail(self): | ||
199 | 441 | """Return the review requested email job for the test's proposal.""" | ||
200 | 442 | [job] = list( | ||
201 | 443 | IStore(BranchMergeProposalJob).find( | ||
202 | 444 | BranchMergeProposalJob, | ||
203 | 445 | BranchMergeProposalJob.branch_merge_proposal == self.bmp, | ||
204 | 446 | BranchMergeProposalJob.job_type == | ||
205 | 447 | BranchMergeProposalJobType.REVIEW_REQUEST_EMAIL)) | ||
206 | 448 | return ReviewRequestedEmailJob(job) | ||
207 | 449 | |||
208 | 450 | def test_nominateReview_creates_job(self): | ||
209 | 451 | # When a reviewer is nominated, a job is created to send out the | ||
210 | 452 | # review request email. | ||
211 | 453 | reviewer = self.factory.makePerson() | ||
212 | 454 | pop_notifications() | ||
213 | 455 | vote_reference = self.bmp.nominateReviewer(reviewer, self.owner, None) | ||
214 | 456 | # No email is sent. | ||
215 | 457 | sent_mail = pop_notifications() | ||
216 | 458 | self.assertEqual([], sent_mail) | ||
217 | 459 | # A job is created. | ||
218 | 460 | review_request_job = self.getReviewNotificationEmail() | ||
219 | 461 | self.assertEqual(self.bmp, review_request_job.branch_merge_proposal) | ||
220 | 462 | self.assertEqual(reviewer, review_request_job.reviewer) | ||
221 | 463 | self.assertEqual(self.bmp.registrant, review_request_job.requester) | ||
222 | 464 | |||
223 | 465 | def test_nominateReview_email_content(self): | ||
224 | 466 | # The email that is sent contains the description of the proposal, and | ||
225 | 467 | # a link to the proposal. | ||
226 | 468 | reviewer = self.factory.makePerson() | ||
227 | 469 | self.bmp.description = 'This branch is awesome.' | ||
228 | 470 | vote_reference = self.bmp.nominateReviewer(reviewer, self.owner, None) | ||
229 | 471 | review_request_job = self.getReviewNotificationEmail() | ||
230 | 472 | review_request_job.run() | ||
231 | 473 | [sent_mail] = pop_notifications() | ||
232 | 474 | self.assertEqual( | ||
233 | 475 | dedent("""\ | ||
234 | 476 | You have been requested to review the proposed merge of %(source)s into %(target)s. | ||
235 | 477 | |||
236 | 478 | This branch is awesome. | ||
237 | 479 | |||
238 | 480 | -- | ||
239 | 481 | %(bmp)s | ||
240 | 482 | You are requested to review the proposed merge of %(source)s into %(target)s. | ||
241 | 483 | """ % { | ||
242 | 484 | 'source': self.bmp.source_branch.bzr_identity, | ||
243 | 485 | 'target': self.bmp.target_branch.bzr_identity, | ||
244 | 486 | 'bmp': canonical_url(self.bmp)}), | ||
245 | 487 | sent_mail.get_payload(decode=True)) | ||
246 | 488 | |||
247 | 489 | def test_nominateReview_emails_team_address(self): | ||
248 | 490 | # If a review request is made for a team, the members of the team are | ||
249 | 491 | # sent an email. | ||
250 | 492 | eric = self.factory.makePerson( | ||
251 | 493 | displayname='Eric the Viking', email='eric@vikings.example.com') | ||
252 | 494 | black_beard = self.factory.makePerson( | ||
253 | 495 | displayname='Black Beard', email='black@pirates.example.com') | ||
254 | 496 | review_team = self.factory.makeTeam(owner=eric) | ||
255 | 497 | login_person(eric) | ||
256 | 498 | review_team.addMember(black_beard, eric) | ||
257 | 499 | pop_notifications() | ||
258 | 500 | login_person(self.owner) | ||
259 | 501 | vote_reference = self.bmp.nominateReviewer(review_team, self.owner, None) | ||
260 | 502 | review_request_job = self.getReviewNotificationEmail() | ||
261 | 503 | review_request_job.run() | ||
262 | 504 | sent_mail = pop_notifications() | ||
263 | 505 | self.assertEqual( | ||
264 | 506 | ['Black Beard <black@pirates.example.com>', | ||
265 | 507 | 'Eric the Viking <eric@vikings.example.com>'], | ||
266 | 508 | sorted(mail['to'] for mail in sent_mail)) | ||
267 | 509 | |||
268 | 434 | def test_requestReviewWithPrivateEmail(self): | 510 | def test_requestReviewWithPrivateEmail(self): |
269 | 435 | # We can request a review, even when one of the parties involved has a | 511 | # We can request a review, even when one of the parties involved has a |
270 | 436 | # private email address. | 512 | # private email address. |
271 | @@ -438,11 +514,9 @@ | |||
272 | 438 | # Request a review and prepare the mailer. | 514 | # Request a review and prepare the mailer. |
273 | 439 | vote_reference = self.bmp.nominateReviewer( | 515 | vote_reference = self.bmp.nominateReviewer( |
274 | 440 | candidate, self.owner, None) | 516 | candidate, self.owner, None) |
275 | 441 | reason = RecipientReason.forReviewer(vote_reference, candidate) | ||
276 | 442 | mailer = BMPMailer.forReviewRequest(reason, self.bmp, self.owner) | ||
277 | 443 | # Send the mail. | 517 | # Send the mail. |
280 | 444 | pop_notifications() | 518 | review_request_job = self.getReviewNotificationEmail() |
281 | 445 | mailer.sendAll() | 519 | review_request_job.run() |
282 | 446 | mails = pop_notifications() | 520 | mails = pop_notifications() |
283 | 447 | self.assertEqual(1, len(mails)) | 521 | self.assertEqual(1, len(mails)) |
284 | 448 | candidate = removeSecurityProxy(candidate) | 522 | candidate = removeSecurityProxy(candidate) |
285 | 449 | 523 | ||
286 | === modified file 'lib/lp/code/model/branchmergeproposal.py' | |||
287 | --- lib/lp/code/model/branchmergeproposal.py 2010-02-26 09:54:20 +0000 | |||
288 | +++ lib/lp/code/model/branchmergeproposal.py 2010-03-29 00:59:31 +0000 | |||
289 | @@ -281,8 +281,9 @@ | |||
290 | 281 | for review in self.votes: | 281 | for review in self.votes: |
291 | 282 | reviewer = review.reviewer | 282 | reviewer = review.reviewer |
292 | 283 | if not reviewer.is_team: | 283 | if not reviewer.is_team: |
293 | 284 | pending = review.comment is None | ||
294 | 284 | recipients[reviewer] = RecipientReason.forReviewer( | 285 | recipients[reviewer] = RecipientReason.forReviewer( |
296 | 285 | review, reviewer, | 286 | self, pending, reviewer, |
297 | 286 | branch_identity_cache=branch_identity_cache) | 287 | branch_identity_cache=branch_identity_cache) |
298 | 287 | # If the registrant of the proposal is getting emails, update the | 288 | # If the registrant of the proposal is getting emails, update the |
299 | 288 | # rationale to say that they registered it. Don't however send them | 289 | # rationale to say that they registered it. Don't however send them |
300 | 289 | 290 | ||
301 | === modified file 'lib/lp/code/model/branchmergeproposaljob.py' | |||
302 | --- lib/lp/code/model/branchmergeproposaljob.py 2010-03-29 00:59:29 +0000 | |||
303 | +++ lib/lp/code/model/branchmergeproposaljob.py 2010-03-29 00:59:31 +0000 | |||
304 | @@ -1,4 +1,4 @@ | |||
306 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009, 2010 Canonical Ltd. This software is licensed under the |
307 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
308 | 3 | 3 | ||
309 | 4 | 4 | ||
310 | @@ -20,6 +20,7 @@ | |||
311 | 20 | 'CodeReviewCommentEmailJob', | 20 | 'CodeReviewCommentEmailJob', |
312 | 21 | 'CreateMergeProposalJob', | 21 | 'CreateMergeProposalJob', |
313 | 22 | 'MergeProposalCreatedJob', | 22 | 'MergeProposalCreatedJob', |
314 | 23 | 'ReviewRequestedEmailJob', | ||
315 | 23 | 'UpdatePreviewDiffJob', | 24 | 'UpdatePreviewDiffJob', |
316 | 24 | ] | 25 | ] |
317 | 25 | 26 | ||
318 | @@ -51,13 +52,16 @@ | |||
319 | 51 | IBranchMergeProposalJob, ICodeReviewCommentEmailJob, | 52 | IBranchMergeProposalJob, ICodeReviewCommentEmailJob, |
320 | 52 | ICodeReviewCommentEmailJobSource, ICreateMergeProposalJob, | 53 | ICodeReviewCommentEmailJobSource, ICreateMergeProposalJob, |
321 | 53 | ICreateMergeProposalJobSource, IMergeProposalCreatedJob, | 54 | ICreateMergeProposalJobSource, IMergeProposalCreatedJob, |
323 | 54 | IMergeProposalCreatedJobSource, IUpdatePreviewDiffJobSource, | 55 | IMergeProposalCreatedJobSource, IReviewRequestedEmailJob, |
324 | 56 | IReviewRequestedEmailJobSource, IUpdatePreviewDiffJobSource, | ||
325 | 55 | ) | 57 | ) |
326 | 58 | from lp.code.mail.branch import RecipientReason | ||
327 | 56 | from lp.code.mail.branchmergeproposal import BMPMailer | 59 | from lp.code.mail.branchmergeproposal import BMPMailer |
328 | 57 | from lp.code.mail.codereviewcomment import CodeReviewCommentMailer | 60 | from lp.code.mail.codereviewcomment import CodeReviewCommentMailer |
329 | 58 | from lp.code.model.branchmergeproposal import BranchMergeProposal | 61 | from lp.code.model.branchmergeproposal import BranchMergeProposal |
330 | 59 | from lp.code.model.diff import PreviewDiff | 62 | from lp.code.model.diff import PreviewDiff |
331 | 60 | from lp.codehosting.vfs import get_multi_server, get_scanner_server | 63 | from lp.codehosting.vfs import get_multi_server, get_scanner_server |
332 | 64 | from lp.registry.interfaces.person import IPersonSet | ||
333 | 61 | from lp.services.job.model.job import Job | 65 | from lp.services.job.model.job import Job |
334 | 62 | from lp.services.job.interfaces.job import IRunnableJob | 66 | from lp.services.job.interfaces.job import IRunnableJob |
335 | 63 | from lp.services.job.runner import BaseRunnableJob | 67 | from lp.services.job.runner import BaseRunnableJob |
336 | @@ -86,6 +90,13 @@ | |||
337 | 86 | reviewers. | 90 | reviewers. |
338 | 87 | """) | 91 | """) |
339 | 88 | 92 | ||
340 | 93 | REVIEW_REQUEST_EMAIL = DBItem(3, """ | ||
341 | 94 | Send the review request email to the requested reviewer. | ||
342 | 95 | |||
343 | 96 | This job sends an email to the requested reviewer, or members of the | ||
344 | 97 | requested reviewer team asking them to review the proposal. | ||
345 | 98 | """) | ||
346 | 99 | |||
347 | 89 | 100 | ||
348 | 90 | class BranchMergeProposalJob(Storm): | 101 | class BranchMergeProposalJob(Storm): |
349 | 91 | """Base class for jobs related to branch merge proposals.""" | 102 | """Base class for jobs related to branch merge proposals.""" |
350 | @@ -418,3 +429,65 @@ | |||
351 | 418 | """Return a list of email-ids to notify about user errors.""" | 429 | """Return a list of email-ids to notify about user errors.""" |
352 | 419 | commenter = self.code_review_comment.message.owner | 430 | commenter = self.code_review_comment.message.owner |
353 | 420 | return [commenter.preferredemail] | 431 | return [commenter.preferredemail] |
354 | 432 | |||
355 | 433 | |||
356 | 434 | class ReviewRequestedEmailJob(BranchMergeProposalJobDerived): | ||
357 | 435 | """Send email to the reviewer telling them to review the proposal. | ||
358 | 436 | |||
359 | 437 | Provides class methods to create and retrieve such jobs. | ||
360 | 438 | """ | ||
361 | 439 | |||
362 | 440 | implements(IReviewRequestedEmailJob) | ||
363 | 441 | |||
364 | 442 | classProvides(IReviewRequestedEmailJobSource) | ||
365 | 443 | |||
366 | 444 | class_job_type = BranchMergeProposalJobType.REVIEW_REQUEST_EMAIL | ||
367 | 445 | |||
368 | 446 | def run(self): | ||
369 | 447 | """See `IRunnableJob`.""" | ||
370 | 448 | reason = RecipientReason.forReviewer( | ||
371 | 449 | self.branch_merge_proposal, True, self.reviewer) | ||
372 | 450 | mailer = BMPMailer.forReviewRequest( | ||
373 | 451 | reason, self.branch_merge_proposal, self.requester) | ||
374 | 452 | mailer.sendAll() | ||
375 | 453 | |||
376 | 454 | @classmethod | ||
377 | 455 | def create(cls, review_request): | ||
378 | 456 | """See `IReviewRequestedEmailJobSource`.""" | ||
379 | 457 | metadata = cls.getMetadata(review_request) | ||
380 | 458 | bmp = review_request.branch_merge_proposal | ||
381 | 459 | job = BranchMergeProposalJob(bmp, cls.class_job_type, metadata) | ||
382 | 460 | return cls(job) | ||
383 | 461 | |||
384 | 462 | @staticmethod | ||
385 | 463 | def getMetadata(review_request): | ||
386 | 464 | return { | ||
387 | 465 | 'reviewer': review_request.reviewer.name, | ||
388 | 466 | 'requester': review_request.registrant.name, | ||
389 | 467 | } | ||
390 | 468 | |||
391 | 469 | @property | ||
392 | 470 | def reviewer(self): | ||
393 | 471 | """The person or team who has been asked to review.""" | ||
394 | 472 | return getUtility(IPersonSet).getByName(self.metadata['reviewer']) | ||
395 | 473 | |||
396 | 474 | @property | ||
397 | 475 | def requester(self): | ||
398 | 476 | """The person who requested the review to be done.""" | ||
399 | 477 | return getUtility(IPersonSet).getByName(self.metadata['requester']) | ||
400 | 478 | |||
401 | 479 | def getOopsVars(self): | ||
402 | 480 | """See `IRunnableJob`.""" | ||
403 | 481 | vars = BranchMergeProposalJobDerived.getOopsVars(self) | ||
404 | 482 | vars.extend([ | ||
405 | 483 | ('reviewer', self.metadata['reviewer']), | ||
406 | 484 | ('requester', self.metadata['requester']), | ||
407 | 485 | ]) | ||
408 | 486 | return vars | ||
409 | 487 | |||
410 | 488 | def getErrorRecipients(self): | ||
411 | 489 | """Return a list of email-ids to notify about user errors.""" | ||
412 | 490 | recipients = [] | ||
413 | 491 | if self.requester is not None: | ||
414 | 492 | recipients.append(self.requester.preferredemail) | ||
415 | 493 | return recipients |
Hi Tim,
There are some real issues that 'make lint' exposes. Good luck
finding them amongst all of the noise. :)
Otherwise this is a fine branch with the exception of a few items marked below.
Thanks.
--Brad
> === modified file 'lib/lp/ code/interfaces /branchmergepro posal.py' code/interfaces /branchmergepro posal.py 2010-03-29 00:59:29 +0000 code/interfaces /branchmergepro posal.py 2010-03-29 00:59:31 +0000 dEmailJob( IRunnableJob) :
> --- lib/lp/
> +++ lib/lp/
> @@ -613,6 +615,23 @@
> """Create a job to email subscribers about the comment."""
>
>
> +class IReviewRequeste
> + """Interface for the job to sends review request emails."""
> +
> + reviewer = Attribute('The person or team asked to do the review.')
> + requester = Attribute('The person who as asked for the review.')
s/as/has/
> === modified file 'lib/lp/ code/mail/ tests/test_ branchmergeprop osal.py' code/mail/ tests/test_ branchmergeprop osal.py 2010-03-05 03:35:10 +0000 code/mail/ tests/test_ branchmergeprop osal.py 2010-03-29 00:59:31 +0000 self.owner) cationEmail( self): BranchMergeProp osalJob) .find( osalJob, osalJob. branch_ merge_proposal == self.bmp, osalJob. job_type == osalJobType. REVIEW_ REQUEST_ EMAIL)) EmailJob( job) view_creates_ job(self) : makePerson( ) nominateReviewe r(reviewer, self.owner, None) l([], sent_mail) otificationEmai l() l(self. bmp, review_ request_ job.branch_ merge_proposal) l(reviewer, review_ request_ job.reviewer) l(self. bmp.registrant, review_ request_ job.requester) view_email_ content( self): makePerson( ) description = 'This branch is awesome.' nominateReviewe r(reviewer, self.owner, None) otificationEmai l() request_ job.run( )
> --- lib/lp/
> +++ lib/lp/
> @@ -431,6 +437,76 @@
> login_person(
> return person
>
> + def getReviewNotifi
> + """Return the review requested email job for the test's proposal."""
> + [job] = list(
> + IStore(
> + BranchMergeProp
> + BranchMergeProp
> + BranchMergeProp
> + BranchMergeProp
> + return ReviewRequested
> +
> + def test_nominateRe
> + # When a reviewer is nominated, a job is created to send out the
> + # review request email.
> + reviewer = self.factory.
> + pop_notifications()
> + vote_reference = self.bmp.
> + # No email is sent.
> + sent_mail = pop_notifications()
> + self.assertEqua
> + # A job is created.
> + review_request_job = self.getReviewN
> + self.assertEqua
> + self.assertEqua
> + self.assertEqua
> +
> + def test_nominateRe
> + # The email that is sent contains the description of the proposal, and
> + # a link to the proposal.
> + reviewer = self.factory.
> + self.bmp.
> + vote_reference = self.bmp.
> + review_request_job = self.getReviewN
> + review_
> + [sent_mail] = pop_notifications()
> + self.assertEqual(
> + dedent("""\
> + You have been requested to review the proposed merge of %(source)s into %(target)s.
> +
> + This branch is awesome.
> +
> + --
> + %(bmp)s
> + You ...