Merge lp:~abentley/launchpad/notify-merge into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: Brad Crittenden
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~abentley/launchpad/notify-merge
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~abentley/launchpad/notify-merge
Reviewer Review Type Date Requested Status
Brad Crittenden (community) Approve
Review via email: mp+9847@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

= Summary =
fix bug #382527 by sending our normal kind of modification email.

== Proposed fix ==
emit an ObjectModified event when a BMP is marked as merged. Allow BMP
emails to be sent when event.user is an UnauthenticatedPrincipal, like
when scripts run.

== Pre-implementation notes ==
Discussed with thumper and flacoste

== Implementation details ==
Until now, we've always used from_user to generate the from_address.
Now, we allow from_user to be None, and use
config.canonical.noreply_from_address when it is.

We provide from_user=None when event.user is an UnauthenticatedPrincipal.

== Tests ==
bin/test -t TestNotifyModified -t test_mergeProposalMergeDetected

== Demo and Q/A ==
Push up source and target branches. Propose a merge. Perform a merge
of source into target. Push target. You should get an email that the
proposal was merged.

= Launchpad lint =

The ILaunchpadCelebrities import is fixed in another branch.

The too-long line is hard to fix, because it is a test name.

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/codehosting/scanner/tests/test_mergedetection.py
  lib/lp/code/interfaces/branchmergeproposal.py
  lib/lp/code/model/tests/test_branchmergeproposals.py
  lib/lp/code/mail/branchmergeproposal.py
  lib/lp/codehosting/scanner/mergedetection.py

== Pyflakes notices ==

lib/lp/code/model/tests/test_branchmergeproposals.py
    26: 'ILaunchpadCelebrities' imported but unused

== Pylint notices ==

lib/lp/codehosting/scanner/tests/test_mergedetection.py
    107: [C0301] Line too long (79/78)

lib/lp/code/model/tests/test_branchmergeproposals.py
    26: [W0611] Unused import ILaunchpadCelebrities
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkp8hhsACgkQ0F+nu1YWqI2MygCfaF/90QfTcLHiGBDQ3MaCd7Uj
Q3wAn0J4PjWp9KE304ZO6Dyk9ALLQPSW
=tfl8
-----END PGP SIGNATURE-----

Revision history for this message
Brad Crittenden (bac) wrote :

Nice branch Aaron. This change will make working with merge proposals much easier to track.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/interfaces/branchmergeproposal.py'
2--- lib/lp/code/interfaces/branchmergeproposal.py 2009-08-06 00:15:48 +0000
3+++ lib/lp/code/interfaces/branchmergeproposal.py 2009-08-07 15:41:43 +0000
4@@ -24,6 +24,8 @@
5 'WrongBranchMergeProposal',
6 ]
7
8+from lazr.lifecycle.event import ObjectModifiedEvent
9+from zope.event import notify
10 from zope.interface import Attribute, Interface
11 from zope.schema import (
12 Bytes, Choice, Datetime, Int, Object, Text, TextLine)
13@@ -595,3 +597,19 @@
14
15 def iterReady():
16 """Iterate through all ready MergeProposalCreatedJobs."""
17+
18+
19+def notify_modified(proposal, func, *args, **kwargs):
20+ """Call func, then notify about the changes it made.
21+
22+ :param proposal: the merge proposal to notify about.
23+ :param func: The callable that will modify the merge proposal.
24+ :param args: Additional arguments for the method.
25+ :param kwargs: Keyword arguments for the method.
26+ :return: The return value of the method.
27+ """
28+ from lp.code.adapters.branch import BranchMergeProposalDelta
29+ snapshot = BranchMergeProposalDelta.snapshot(proposal)
30+ result = func(*args, **kwargs)
31+ notify(ObjectModifiedEvent(proposal, snapshot, []))
32+ return result
33
34=== modified file 'lib/lp/code/mail/branchmergeproposal.py'
35--- lib/lp/code/mail/branchmergeproposal.py 2009-08-05 02:18:13 +0000
36+++ lib/lp/code/mail/branchmergeproposal.py 2009-08-07 19:39:48 +0000
37@@ -6,8 +6,10 @@
38
39 __metaclass__ = type
40
41+from zope.app.security.principalregistry import UnauthenticatedPrincipal
42 from zope.component import getUtility
43
44+from canonical.config import config
45 from canonical.launchpad.mail import get_msgid
46 from canonical.launchpad.webapp import canonical_url
47 from lp.code.adapters.branch import BranchMergeProposalDelta
48@@ -32,8 +34,12 @@
49 """Notify branch subscribers when merge proposals are updated."""
50 if event.user is None:
51 return
52+ if isinstance(event.user, UnauthenticatedPrincipal):
53+ from_person = None
54+ else:
55+ from_person = IPerson(event.user)
56 mailer = BMPMailer.forModification(
57- event.object_before_modification, merge_proposal, IPerson(event.user))
58+ event.object_before_modification, merge_proposal, from_person)
59 if mailer is not None:
60 mailer.sendAll()
61
62@@ -104,18 +110,22 @@
63 review_diff=merge_proposal.review_diff)
64
65 @classmethod
66- def forModification(cls, old_merge_proposal, merge_proposal, from_user):
67+ def forModification(cls, old_merge_proposal, merge_proposal,
68+ from_user=None):
69 """Return a mailer for BranchMergeProposal creation.
70
71 :param merge_proposal: The BranchMergeProposal that was created.
72 :param from_user: The user that the creation notification should
73- come from.
74+ come from. Optional.
75 """
76 recipients = merge_proposal.getNotificationRecipients(
77 CodeReviewNotificationLevel.STATUS)
78- assert from_user.preferredemail is not None, (
79- 'The sender must have an email address.')
80- from_address = cls._format_user_address(from_user)
81+ if from_user is not None:
82+ assert from_user.preferredemail is not None, (
83+ 'The sender must have an email address.')
84+ from_address = cls._format_user_address(from_user)
85+ else:
86+ from_address = config.canonical.noreply_from_address
87 delta = BranchMergeProposalDelta.construct(
88 old_merge_proposal, merge_proposal)
89 if delta is None:
90
91=== modified file 'lib/lp/code/model/tests/test_branchmergeproposals.py'
92--- lib/lp/code/model/tests/test_branchmergeproposals.py 2009-08-06 00:15:48 +0000
93+++ lib/lp/code/model/tests/test_branchmergeproposals.py 2009-08-07 15:41:43 +0000
94@@ -21,6 +21,7 @@
95 from canonical.database.constants import UTC_NOW
96 from canonical.testing import (
97 DatabaseFunctionalLayer, LaunchpadFunctionalLayer, LaunchpadZopelessLayer)
98+from lazr.lifecycle.event import ObjectModifiedEvent
99
100 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
101 from lp.code.model.branchmergeproposaljob import (
102@@ -42,7 +43,7 @@
103 BRANCH_MERGE_PROPOSAL_FINAL_STATES as FINAL_STATES,
104 IBranchMergeProposalGetter, IBranchMergeProposalJob,
105 ICreateMergeProposalJob, ICreateMergeProposalJobSource,
106- IMergeProposalCreatedJob, WrongBranchMergeProposal)
107+ IMergeProposalCreatedJob, notify_modified, WrongBranchMergeProposal)
108 from canonical.launchpad.interfaces.message import IMessageJob
109 from lp.registry.interfaces.person import IPersonSet
110 from lp.registry.interfaces.product import IProductSet
111@@ -1081,6 +1082,28 @@
112 SQLObjectNotFound, BranchMergeProposalJob.get, job_id)
113
114
115+class TestNotifyModified(TestCaseWithFactory):
116+
117+ layer = DatabaseFunctionalLayer
118+
119+ def test_notify_modified_generates_notification(self):
120+ """notify_modified generates an event.
121+
122+ notify_modified runs the callable with the specified args and kwargs,
123+ and generates a ObjectModifiedEvent.
124+ """
125+ bmp = self.factory.makeBranchMergeProposal()
126+ login_person(bmp.target_branch.owner)
127+ # Approve branch to prevent enqueue from approving it, which would
128+ # generate an undesired event.
129+ bmp.approveBranch(bmp.target_branch.owner, revision_id='abc')
130+ self.assertNotifies(
131+ ObjectModifiedEvent, notify_modified, bmp, bmp.enqueue,
132+ bmp.target_branch.owner, revision_id='abc')
133+ self.assertEqual(BranchMergeProposalStatus.QUEUED, bmp.queue_status)
134+ self.assertEqual('abc', bmp.queued_revision_id)
135+
136+
137 class TestBranchMergeProposalJob(TestCaseWithFactory):
138
139 layer = DatabaseFunctionalLayer
140
141=== modified file 'lib/lp/codehosting/scanner/mergedetection.py'
142--- lib/lp/codehosting/scanner/mergedetection.py 2009-06-30 16:56:07 +0000
143+++ lib/lp/codehosting/scanner/mergedetection.py 2009-08-06 19:07:32 +0000
144@@ -18,7 +18,7 @@
145 from lp.code.enums import BranchLifecycleStatus
146 from lp.code.interfaces.branchcollection import IAllBranches
147 from lp.code.interfaces.branchmergeproposal import (
148- BRANCH_MERGE_PROPOSAL_FINAL_STATES)
149+ BRANCH_MERGE_PROPOSAL_FINAL_STATES, notify_modified)
150
151
152 def is_series_branch(branch):
153@@ -62,7 +62,7 @@
154 if is_development_focus(target):
155 mark_branch_merged(logger, source)
156 else:
157- proposal.markAsMerged()
158+ notify_modified(proposal, proposal.markAsMerged)
159 # If there is an explicit merge proposal, change the branch's
160 # status when it's been merged into a development focus or any
161 # other series branch.
162
163=== modified file 'lib/lp/codehosting/scanner/tests/test_mergedetection.py'
164--- lib/lp/codehosting/scanner/tests/test_mergedetection.py 2009-06-30 16:56:07 +0000
165+++ lib/lp/codehosting/scanner/tests/test_mergedetection.py 2009-08-06 19:07:32 +0000
166@@ -22,6 +22,7 @@
167 from lp.code.enums import BranchLifecycleStatus, BranchMergeProposalStatus
168 from lp.code.interfaces.branchlookup import IBranchLookup
169 from lp.testing import TestCaseWithFactory
170+from lp.testing.mail_helpers import pop_notifications
171 from canonical.testing import LaunchpadZopelessLayer
172
173
174@@ -261,6 +262,16 @@
175 self.assertEqual(
176 BranchLifecycleStatus.MERGED,
177 proposal.source_branch.lifecycle_status)
178+ notifications = pop_notifications()
179+ self.assertIn('Work in progress => Merged',
180+ notifications[0].get_payload(decode=True))
181+ self.assertEqual(
182+ config.canonical.noreply_from_address, notifications[0]['From'])
183+ recipients = set(msg['x-envelope-to'] for msg in notifications)
184+ expected = set(
185+ [proposal.source_branch.registrant.preferredemail.email,
186+ proposal.target_branch.registrant.preferredemail.email])
187+ self.assertEqual(expected, recipients)
188
189 def test_mergeProposalMergeDetected_not_series(self):
190 # If the target branch is not a series branch, then the merge proposal