Merge lp:~gmb/launchpad/add-calculatebugheatjobs-bug-510876 into lp:launchpad/db-devel

Proposed by Graham Binns
Status: Merged
Approved by: Deryck Hodge
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~gmb/launchpad/add-calculatebugheatjobs-bug-510876
Merge into: lp:launchpad/db-devel
Diff against target: 352 lines (+158/-11)
5 files modified
database/schema/security.cfg (+10/-0)
lib/lp/bugs/configure.zcml (+1/-0)
lib/lp/bugs/model/bug.py (+20/-0)
lib/lp/bugs/scripts/bugheat.py (+0/-7)
lib/lp/bugs/tests/test_bugheat.py (+127/-4)
To merge this branch: bzr merge lp:~gmb/launchpad/add-calculatebugheatjobs-bug-510876
Reviewer Review Type Date Requested Status
Deryck Hodge (community) code Approve
Review via email: mp+17895@code.launchpad.net

Commit message

Relevant changes to a bug will now add CalculateBugHeatJobs to the job queue.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

This branch updates the Bug model so that changes to a Bug add
CalculateBugHeatJobs to the job queue.

We've made changes in the following files:

=== lib/lp/bugs/configure.zcml ===

 - We've added IBugJob as an allowed interface for CalculateBugHeatJob
   so that we can access it as a BugJob.

=== lib/lp/bugs/interfaces/bugjob.py ===

 - We've fixed some lint by adding IBugJobSource to the modules __all__.

=== lib/lp/bugs/model/bug.py ===

 - We've added job creation code for the following events:
   - Subscriptions and unsubscriptions
   - Marking a user as affected or unaffected
   - Marking or unmarking a bug as a duplicate
   - Generic changes to the bug's attributes (this includes
     security_related and private, and uses the BugChange API, which is
     tested elsewhere).

=== lib/lp/bugs/scripts/bugheat.py ===

 - We've removed some unused imports.

=== lib/lp/bugs/tests/test_bugheat.py ===

 - We've added unit tests for the above event-driven job creation code.

Revision history for this message
Deryck Hodge (deryck) wrote :

Looks good to me. As mentioned in person, I would remove the comments that say something to the effect of "updating bug heat" since the method object makes this obvious. Otherwise, r=me.

Cheers,
deryck

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/security.cfg'
2--- database/schema/security.cfg 2010-01-29 17:15:31 +0000
3+++ database/schema/security.cfg 2010-02-01 08:45:27 +0000
4@@ -546,6 +546,7 @@
5 public.bugactivity = SELECT, INSERT
6 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
7 public.bugcve = SELECT, INSERT
8+public.bugjob = SELECT, INSERT
9 public.bugmessage = SELECT, INSERT, UPDATE
10 public.bugnomination = SELECT
11 public.bugnotification = SELECT, INSERT
12@@ -561,6 +562,7 @@
13 public.distribution = SELECT
14 public.distroseries = SELECT
15 public.emailaddress = SELECT, INSERT
16+public.job = SELECT, INSERT, UPDATE
17 public.language = SELECT
18 public.libraryfilealias = SELECT, INSERT
19 public.libraryfilecontent = SELECT, INSERT
20@@ -925,6 +927,7 @@
21 public.bug = SELECT, INSERT, UPDATE
22 public.bugactivity = SELECT, INSERT, UPDATE
23 public.bugattachment = SELECT, INSERT, UPDATE
24+public.bugjob = SELECT, INSERT
25 public.bugmessage = SELECT, INSERT, UPDATE
26 public.bugnomination = SELECT, INSERT, UPDATE
27 public.bugpackageinfestation = SELECT, INSERT, UPDATE
28@@ -1147,6 +1150,7 @@
29 public.bug = SELECT, UPDATE
30 public.bugactivity = SELECT, INSERT
31 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
32+public.bugjob = SELECT, INSERT
33 public.bugsubscription = SELECT
34 public.bugnotification = SELECT, INSERT
35 public.bugnotificationrecipient = SELECT, INSERT
36@@ -1246,6 +1250,7 @@
37 public.bug = SELECT, UPDATE
38 public.bugactivity = SELECT, INSERT
39 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
40+public.bugjob = SELECT, INSERT
41 public.bugsubscription = SELECT
42 public.bugnotification = SELECT, INSERT
43 public.bugnotificationrecipient = SELECT, INSERT
44@@ -1315,10 +1320,12 @@
45 public.bug = SELECT, INSERT, UPDATE
46 public.bugactivity = SELECT, INSERT
47 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
48+public.bugjob = SELECT, INSERT
49 public.bugmessage = SELECT, INSERT
50 public.bugtag = SELECT
51 public.bugtask = SELECT, INSERT, UPDATE
52 public.bugwatch = SELECT
53+public.job = SELECT, INSERT, UPDATE
54 public.component = SELECT
55 public.packagebugsupervisor = SELECT
56 public.person = SELECT
57@@ -1515,6 +1522,7 @@
58 public.bug = SELECT, INSERT, UPDATE
59 public.bugactivity = SELECT, INSERT
60 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
61+public.bugjob = SELECT, INSERT
62 public.bugsubscription = SELECT, INSERT
63 public.bugnotification = SELECT, INSERT
64 public.bugnotificationattachment = SELECT
65@@ -1783,6 +1791,7 @@
66 public.bugtracker = SELECT, INSERT
67 public.bugwatch = SELECT, INSERT
68 public.bug = SELECT, INSERT, UPDATE
69+public.bugjob = SELECT, INSERT
70 public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE
71 public.bugtask = SELECT, INSERT, UPDATE
72 public.accountpassword = SELECT, INSERT
73@@ -1796,6 +1805,7 @@
74 public.bugsubscription = SELECT, INSERT
75 public.bugmessage = SELECT, INSERT
76 public.sourcepackagename = SELECT
77+public.job = SELECT, INSERT, UPDATE
78
79 [updatesourceforgeremoteproduct]
80 # Updates Product.remote_product using SourceForge project data.
81
82=== modified file 'lib/lp/bugs/configure.zcml'
83--- lib/lp/bugs/configure.zcml 2010-01-24 12:40:12 +0000
84+++ lib/lp/bugs/configure.zcml 2010-02-01 08:45:27 +0000
85@@ -903,6 +903,7 @@
86
87 <!-- CalculateBugHeatJobs -->
88 <class class="lp.bugs.model.bugheat.CalculateBugHeatJob">
89+ <allow interface="lp.bugs.interfaces.bugjob.IBugJob" />
90 <allow interface="lp.bugs.interfaces.bugjob.ICalculateBugHeatJob"/>
91 </class>
92 <securedutility
93
94=== modified file 'lib/lp/bugs/model/bug.py'
95--- lib/lp/bugs/model/bug.py 2010-01-25 11:41:18 +0000
96+++ lib/lp/bugs/model/bug.py 2010-02-01 08:45:27 +0000
97@@ -69,6 +69,7 @@
98 from lp.bugs.interfaces.bugactivity import IBugActivitySet
99 from lp.bugs.interfaces.bugattachment import (
100 BugAttachmentType, IBugAttachmentSet)
101+from lp.bugs.interfaces.bugjob import ICalculateBugHeatJobSource
102 from lp.bugs.interfaces.bugmessage import IBugMessageSet
103 from lp.bugs.interfaces.bugnomination import (
104 NominationError, NominationSeriesObsoleteError)
105@@ -464,6 +465,9 @@
106
107 sub = BugSubscription(
108 bug=self, person=person, subscribed_by=subscribed_by)
109+
110+ getUtility(ICalculateBugHeatJobSource).create(self)
111+
112 # Ensure that the subscription has been flushed.
113 Store.of(sub).flush()
114 return sub
115@@ -771,6 +775,8 @@
116 notification_data['text'], change.person, recipients,
117 when)
118
119+ getUtility(ICalculateBugHeatJobSource).create(self)
120+
121 def expireNotifications(self):
122 """See `IBug`."""
123 for notification in BugNotification.selectBy(
124@@ -1393,11 +1399,14 @@
125 if bap.affected != affected:
126 bap.affected = affected
127 self._flushAndInvalidate()
128+
129 # Loop over dupes.
130 for dupe in self.duplicates:
131 if dupe._getAffectedUser(user) is not None:
132 dupe.markUserAffected(user, affected)
133
134+ getUtility(ICalculateBugHeatJobSource).create(self)
135+
136 @property
137 def readonly_duplicateof(self):
138 """See `IBug`."""
139@@ -1414,6 +1423,17 @@
140 except LaunchpadValidationError, validation_error:
141 raise InvalidDuplicateValue(validation_error)
142
143+ if duplicate_of is not None:
144+ # Create a job to update the heat of the master bug and set
145+ # this bug's heat to 0 (since it's a duplicate, it shouldn't
146+ # have any heat at all).
147+ getUtility(ICalculateBugHeatJobSource).create(duplicate_of)
148+ self.setHeat(0)
149+ else:
150+ # Otherwise, create a job to recalculate this bug's heat,
151+ # since it will be 0 from having been a duplicate.
152+ getUtility(ICalculateBugHeatJobSource).create(self)
153+
154 def setCommentVisibility(self, user, comment_number, visible):
155 """See `IBug`."""
156 bug_message_set = getUtility(IBugMessageSet)
157
158=== modified file 'lib/lp/bugs/scripts/bugheat.py'
159--- lib/lp/bugs/scripts/bugheat.py 2010-01-21 16:56:10 +0000
160+++ lib/lp/bugs/scripts/bugheat.py 2010-02-01 08:45:27 +0000
161@@ -9,13 +9,6 @@
162 ]
163
164
165-from zope.component import getUtility
166-from zope.interface import implements
167-
168-from canonical.launchpad.interfaces.looptuner import ITunableLoop
169-from canonical.launchpad.utilities.looptuner import DBLoopTuner
170-
171-
172 class BugHeatConstants:
173
174 PRIVACY = 150
175
176=== modified file 'lib/lp/bugs/tests/test_bugheat.py'
177--- lib/lp/bugs/tests/test_bugheat.py 2010-01-21 17:47:38 +0000
178+++ lib/lp/bugs/tests/test_bugheat.py 2010-02-01 08:45:27 +0000
179@@ -5,14 +5,17 @@
180
181 __metaclass__ = type
182
183+import pytz
184 import transaction
185 import unittest
186+from datetime import datetime
187
188 from zope.component import getUtility
189
190 from canonical.launchpad.scripts.tests import run_script
191 from canonical.testing import LaunchpadZopelessLayer
192
193+from lp.bugs.adapters.bugchange import BugDescriptionChange
194 from lp.bugs.interfaces.bugjob import ICalculateBugHeatJobSource
195 from lp.bugs.model.bugheat import CalculateBugHeatJob
196 from lp.bugs.scripts.bugheat import BugHeatCalculator
197@@ -28,6 +31,26 @@
198 super(CalculateBugHeatJobTestCase, self).setUp()
199 self.bug = self.factory.makeBug()
200
201+ # NB: This looks like it should go in the teardown, however
202+ # creating the bug causes a job to be added for it. We clear
203+ # this out so that our tests are consistent.
204+ self._completeJobsAndAssertQueueEmpty()
205+
206+ def _completeJobsAndAssertQueueEmpty(self):
207+ """Make sure that all the CalculateBugHeatJobs are completed."""
208+ for bug_job in getUtility(ICalculateBugHeatJobSource).iterReady():
209+ bug_job.job.start()
210+ bug_job.job.complete()
211+ self.assertEqual(0, self._getJobCount())
212+
213+ def _getJobCount(self):
214+ """Return the number of CalculateBugHeatJobs in the queue."""
215+ return len(self._getJobs())
216+
217+ def _getJobs(self):
218+ """Return the pending CalculateBugHeatJobs as a list."""
219+ return list(CalculateBugHeatJob.iterReady())
220+
221 def test_run(self):
222 # CalculateBugHeatJob.run() sets calculates and sets the heat
223 # for a bug.
224@@ -51,8 +74,7 @@
225 job = CalculateBugHeatJob.create(self.bug)
226
227 # There will now be one job in the queue.
228- self.assertEqual(
229- 1, len(list(CalculateBugHeatJob.iterReady())))
230+ self.assertEqual(1, self._getJobCount())
231
232 new_job = CalculateBugHeatJob.create(self.bug)
233
234@@ -60,8 +82,7 @@
235 self.assertEqual(job, new_job)
236
237 # And the queue will still have a length of 1.
238- self.assertEqual(
239- 1, len(list((CalculateBugHeatJob.iterReady()))))
240+ self.assertEqual(1, self._getJobCount())
241
242 def test_cronscript_succeeds(self):
243 # The calculate-bug-heat cronscript will run all pending
244@@ -90,6 +111,108 @@
245 self.assertIn(('bug_job_id', job.context.id), vars)
246 self.assertIn(('bug_job_type', job.context.job_type.title), vars)
247
248+ def test_bug_changes_adds_job(self):
249+ # Calling addChange() on a Bug will add a CalculateBugHeatJob
250+ # for that bug to the queue.
251+ self.assertEqual(0, self._getJobCount())
252+
253+ change = BugDescriptionChange(
254+ when=datetime.now().replace(tzinfo=pytz.timezone('UTC')),
255+ person=self.bug.owner, what_changed='description',
256+ old_value=self.bug.description, new_value='Some text')
257+ self.bug.addChange(change)
258+
259+ # There will now be a job in the queue.
260+ self.assertEqual(1, self._getJobCount())
261+
262+ def test_subscribing_adds_job(self):
263+ # Calling Bug.subscribe() will add a CalculateBugHeatJob for the
264+ # Bug.
265+ self.assertEqual(0, self._getJobCount())
266+
267+ person = self.factory.makePerson()
268+ self.bug.subscribe(person, person)
269+ transaction.commit()
270+
271+ # There will now be a job in the queue.
272+ self.assertEqual(1, self._getJobCount())
273+
274+ def test_unsubscribing_adds_job(self):
275+ # Calling Bug.unsubscribe() will add a CalculateBugHeatJob for the
276+ # Bug.
277+ self.assertEqual(0, self._getJobCount())
278+
279+ self.bug.unsubscribe(self.bug.owner, self.bug.owner)
280+ transaction.commit()
281+
282+ # There will now be a job in the queue.
283+ self.assertEqual(1, self._getJobCount())
284+
285+ def test_marking_affected_adds_job(self):
286+ # Marking a user as affected by a bug adds a CalculateBugHeatJob
287+ # for the bug.
288+ self.assertEqual(0, self._getJobCount())
289+
290+ person = self.factory.makePerson()
291+ self.bug.markUserAffected(person)
292+
293+ # There will now be a job in the queue.
294+ self.assertEqual(1, self._getJobCount())
295+
296+ def test_marking_unaffected_adds_job(self):
297+ # Marking a user as unaffected by a bug adds a CalculateBugHeatJob
298+ # for the bug.
299+ self.assertEqual(0, self._getJobCount())
300+
301+ self.bug.markUserAffected(self.bug.owner, False)
302+
303+ # There will now be a job in the queue.
304+ self.assertEqual(1, self._getJobCount())
305+
306+ def test_bug_creation_creates_job(self):
307+ # Creating a bug adds a CalculateBugHeatJob for the new bug.
308+ self.assertEqual(0, self._getJobCount())
309+
310+ new_bug = self.factory.makeBug()
311+
312+ # There will now be a job in the queue.
313+ self.assertEqual(1, self._getJobCount())
314+
315+ def test_marking_dupe_creates_job(self):
316+ # Marking a bug as a duplicate of another bug creates a job to
317+ # update the master bug.
318+ new_bug = self.factory.makeBug()
319+ new_bug.setHeat(42)
320+ self._completeJobsAndAssertQueueEmpty()
321+
322+ new_bug.markAsDuplicate(self.bug)
323+
324+ # There will now be a job in the queue.
325+ self.assertEqual(1, self._getJobCount())
326+
327+ # And the job will be for the master bug.
328+ bug_job = self._getJobs()[0]
329+ self.assertEqual(bug_job.bug, self.bug)
330+
331+ # Also, the duplicate bug's heat will have been set to zero.
332+ self.assertEqual(0, new_bug.heat)
333+
334+ def test_unmarking_dupe_creates_job(self):
335+ # Unmarking a bug as a duplicate will create a
336+ # CalculateBugHeatJob for the bug, since its heat will be 0 from
337+ # having been marked as a duplicate.
338+ new_bug = self.factory.makeBug()
339+ new_bug.markAsDuplicate(self.bug)
340+ self._completeJobsAndAssertQueueEmpty()
341+ new_bug.markAsDuplicate(None)
342+
343+ # There will now be a job in the queue.
344+ self.assertEqual(1, self._getJobCount())
345+
346+ # And the job will be for the master bug.
347+ bug_job = self._getJobs()[0]
348+ self.assertEqual(bug_job.bug, new_bug)
349+
350
351 def test_suite():
352 return unittest.TestLoader().loadTestsFromName(__name__)

Subscribers

People subscribed via source and target branches

to status/vote changes: