Merge lp:~gmb/launchpad/heat-garbo-hourly-bug-509195 into lp:launchpad/db-devel
- heat-garbo-hourly-bug-509195
- Merge into db-devel
Proposed by
Graham Binns
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~gmb/launchpad/heat-garbo-hourly-bug-509195 |
Merge into: | lp:launchpad/db-devel |
Diff against target: |
410 lines (+172/-52) 7 files modified
database/schema/security.cfg (+2/-1) lib/canonical/config/schema-lazr.conf (+1/-0) lib/canonical/launchpad/scripts/garbo.py (+19/-16) lib/lp/bugs/configure.zcml (+1/-0) lib/lp/bugs/doc/bug-heat.txt (+117/-32) lib/lp/bugs/interfaces/bug.py (+11/-1) lib/lp/bugs/model/bug.py (+21/-2) |
To merge this branch: | bzr merge lp:~gmb/launchpad/heat-garbo-hourly-bug-509195 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abel Deuring (community) | code | Approve | |
Review via email: mp+20729@code.launchpad.net |
Commit message
Bugs with out-of-date heat will now have CalculateBugHea
Description of the change
This branch adds a job to the hourly garbage collector to mop up bugs with out-of-date heat.
In this branch I've added Bug.heat_
I've also added permissions to the garbo db user so that it can add new BugJobs.
To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) wrote : | # |
Revision history for this message
Abel Deuring (adeuring) : | # |
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-03-01 21:59:32 +0000 | |||
3 | +++ database/schema/security.cfg 2010-03-05 11:49:30 +0000 | |||
4 | @@ -1842,8 +1842,9 @@ | |||
5 | 1842 | public.mailinglistsubscription = SELECT, DELETE | 1842 | public.mailinglistsubscription = SELECT, DELETE |
6 | 1843 | public.teamparticipation = SELECT, DELETE | 1843 | public.teamparticipation = SELECT, DELETE |
7 | 1844 | public.emailaddress = SELECT, UPDATE | 1844 | public.emailaddress = SELECT, UPDATE |
9 | 1845 | public.job = SELECT, DELETE | 1845 | public.job = SELECT, INSERT, DELETE |
10 | 1846 | public.branchjob = SELECT, DELETE | 1846 | public.branchjob = SELECT, DELETE |
11 | 1847 | public.bugjob = SELECT, INSERT | ||
12 | 1847 | 1848 | ||
13 | 1848 | [garbo_daily] | 1849 | [garbo_daily] |
14 | 1849 | type=user | 1850 | type=user |
15 | 1850 | 1851 | ||
16 | === modified file 'lib/canonical/config/schema-lazr.conf' | |||
17 | --- lib/canonical/config/schema-lazr.conf 2010-02-24 10:18:16 +0000 | |||
18 | +++ lib/canonical/config/schema-lazr.conf 2010-03-05 11:49:30 +0000 | |||
19 | @@ -36,6 +36,7 @@ | |||
20 | 36 | oops_prefix: none | 36 | oops_prefix: none |
21 | 37 | error_dir: none | 37 | error_dir: none |
22 | 38 | copy_to_zlog: false | 38 | copy_to_zlog: false |
23 | 39 | max_heat_age: 7 | ||
24 | 39 | 40 | ||
25 | 40 | [process_apport_blobs] | 41 | [process_apport_blobs] |
26 | 41 | # The database user which will be used by this process. | 42 | # The database user which will be used by this process. |
27 | 42 | 43 | ||
28 | === modified file 'lib/canonical/launchpad/scripts/garbo.py' | |||
29 | --- lib/canonical/launchpad/scripts/garbo.py 2010-02-02 17:44:24 +0000 | |||
30 | +++ lib/canonical/launchpad/scripts/garbo.py 2010-03-05 11:49:30 +0000 | |||
31 | @@ -31,8 +31,8 @@ | |||
32 | 31 | from canonical.launchpad.webapp.interfaces import ( | 31 | from canonical.launchpad.webapp.interfaces import ( |
33 | 32 | IStoreSelector, AUTH_STORE, MAIN_STORE, MASTER_FLAVOR) | 32 | IStoreSelector, AUTH_STORE, MAIN_STORE, MASTER_FLAVOR) |
34 | 33 | from lp.bugs.interfaces.bug import IBugSet | 33 | from lp.bugs.interfaces.bug import IBugSet |
35 | 34 | from lp.bugs.interfaces.bugjob import ICalculateBugHeatJobSource | ||
36 | 34 | from lp.bugs.model.bugnotification import BugNotification | 35 | from lp.bugs.model.bugnotification import BugNotification |
37 | 35 | from lp.bugs.scripts.bugheat import BugHeatCalculator | ||
38 | 36 | from lp.code.interfaces.revision import IRevisionSet | 36 | from lp.code.interfaces.revision import IRevisionSet |
39 | 37 | from lp.code.model.branchjob import BranchJob | 37 | from lp.code.model.branchjob import BranchJob |
40 | 38 | from lp.code.model.codeimportresult import CodeImportResult | 38 | from lp.code.model.codeimportresult import CodeImportResult |
41 | @@ -698,18 +698,22 @@ | |||
42 | 698 | 698 | ||
43 | 699 | maximum_chunk_size = 1000 | 699 | maximum_chunk_size = 1000 |
44 | 700 | 700 | ||
46 | 701 | def __init__(self, log, abort_time=None): | 701 | def __init__(self, log, abort_time=None, max_heat_age=None): |
47 | 702 | super(BugHeatUpdater, self).__init__(log, abort_time) | 702 | super(BugHeatUpdater, self).__init__(log, abort_time) |
48 | 703 | self.transaction = transaction | 703 | self.transaction = transaction |
49 | 704 | self.total_processed = 0 | ||
50 | 705 | self.is_done = False | ||
51 | 704 | self.offset = 0 | 706 | self.offset = 0 |
53 | 705 | self.total_updated = 0 | 707 | if max_heat_age is None: |
54 | 708 | max_heat_age = config.calculate_bug_heat.max_heat_age | ||
55 | 709 | self.max_heat_age = max_heat_age | ||
56 | 706 | 710 | ||
57 | 707 | def isDone(self): | 711 | def isDone(self): |
58 | 708 | """See `ITunableLoop`.""" | 712 | """See `ITunableLoop`.""" |
59 | 709 | # When the main loop has no more Bugs to process it sets | 713 | # When the main loop has no more Bugs to process it sets |
60 | 710 | # offset to None. Until then, it always has a numerical | 714 | # offset to None. Until then, it always has a numerical |
61 | 711 | # value. | 715 | # value. |
63 | 712 | return self.offset is None | 716 | return self.is_done |
64 | 713 | 717 | ||
65 | 714 | def __call__(self, chunk_size): | 718 | def __call__(self, chunk_size): |
66 | 715 | """Retrieve a batch of Bugs and update their heat. | 719 | """Retrieve a batch of Bugs and update their heat. |
67 | @@ -721,23 +725,23 @@ | |||
68 | 721 | # trying to slice using floats or anything similarly | 725 | # trying to slice using floats or anything similarly |
69 | 722 | # foolish. We shouldn't have to do this. | 726 | # foolish. We shouldn't have to do this. |
70 | 723 | chunk_size = int(chunk_size) | 727 | chunk_size = int(chunk_size) |
71 | 724 | |||
72 | 725 | start = self.offset | 728 | start = self.offset |
73 | 726 | end = self.offset + chunk_size | 729 | end = self.offset + chunk_size |
74 | 727 | 730 | ||
75 | 728 | transaction.begin() | 731 | transaction.begin() |
80 | 729 | # XXX 2010-01-08 gmb bug=505850: | 732 | bugs = getUtility(IBugSet).getBugsWithOutdatedHeat( |
81 | 730 | # This method call should be taken out and shot as soon as | 733 | self.max_heat_age)[start:end] |
78 | 731 | # we have a proper permissions system for scripts. | ||
79 | 732 | bugs = getUtility(IBugSet).dangerousGetAllBugs()[start:end] | ||
82 | 733 | 734 | ||
83 | 734 | self.offset = None | ||
84 | 735 | bug_count = bugs.count() | 735 | bug_count = bugs.count() |
85 | 736 | if bug_count > 0: | 736 | if bug_count > 0: |
86 | 737 | starting_id = bugs.first().id | 737 | starting_id = bugs.first().id |
88 | 738 | self.log.debug("Updating %i Bugs (starting id: %i)" % | 738 | self.log.debug( |
89 | 739 | "Adding CalculateBugHeatJobs for %i Bugs (starting id: %i)" % | ||
90 | 739 | (bug_count, starting_id)) | 740 | (bug_count, starting_id)) |
91 | 741 | else: | ||
92 | 742 | self.is_done = True | ||
93 | 740 | 743 | ||
94 | 744 | self.offset = None | ||
95 | 741 | for bug in bugs: | 745 | for bug in bugs: |
96 | 742 | # We set the starting point of the next batch to the Bug | 746 | # We set the starting point of the next batch to the Bug |
97 | 743 | # id after the one we're looking at now. If there aren't any | 747 | # id after the one we're looking at now. If there aren't any |
98 | @@ -745,11 +749,9 @@ | |||
99 | 745 | # will remain set to None. | 749 | # will remain set to None. |
100 | 746 | start += 1 | 750 | start += 1 |
101 | 747 | self.offset = start | 751 | self.offset = start |
107 | 748 | self.log.debug("Updating heat for bug %s" % bug.id) | 752 | self.log.debug("Adding CalculateBugHeatJob for bug %s" % bug.id) |
108 | 749 | bug_heat_calculator = BugHeatCalculator(bug) | 753 | getUtility(ICalculateBugHeatJobSource).create(bug) |
109 | 750 | heat = bug_heat_calculator.getBugHeat() | 754 | self.total_processed += 1 |
105 | 751 | bug.setHeat(heat) | ||
106 | 752 | self.total_updated += 1 | ||
110 | 753 | transaction.commit() | 755 | transaction.commit() |
111 | 754 | 756 | ||
112 | 755 | 757 | ||
113 | @@ -836,6 +838,7 @@ | |||
114 | 836 | OpenIDAssociationPruner, | 838 | OpenIDAssociationPruner, |
115 | 837 | OpenIDConsumerAssociationPruner, | 839 | OpenIDConsumerAssociationPruner, |
116 | 838 | RevisionCachePruner, | 840 | RevisionCachePruner, |
117 | 841 | BugHeatUpdater, | ||
118 | 839 | ] | 842 | ] |
119 | 840 | experimental_tunable_loops = [] | 843 | experimental_tunable_loops = [] |
120 | 841 | 844 | ||
121 | 842 | 845 | ||
122 | === modified file 'lib/lp/bugs/configure.zcml' | |||
123 | --- lib/lp/bugs/configure.zcml 2010-02-19 12:31:43 +0000 | |||
124 | +++ lib/lp/bugs/configure.zcml 2010-03-05 11:49:30 +0000 | |||
125 | @@ -575,6 +575,7 @@ | |||
126 | 575 | personIsAlsoNotifiedSubscriber | 575 | personIsAlsoNotifiedSubscriber |
127 | 576 | personIsSubscribedToDuplicate | 576 | personIsSubscribedToDuplicate |
128 | 577 | heat | 577 | heat |
129 | 578 | heat_last_updated | ||
130 | 578 | has_patches | 579 | has_patches |
131 | 579 | latest_patch | 580 | latest_patch |
132 | 580 | latest_patch_uploaded"/> | 581 | latest_patch_uploaded"/> |
133 | 581 | 582 | ||
134 | === modified file 'lib/lp/bugs/doc/bug-heat.txt' | |||
135 | --- lib/lp/bugs/doc/bug-heat.txt 2010-03-03 13:10:17 +0000 | |||
136 | +++ lib/lp/bugs/doc/bug-heat.txt 2010-03-05 11:49:30 +0000 | |||
137 | @@ -12,54 +12,139 @@ | |||
138 | 12 | >>> bug.heat | 12 | >>> bug.heat |
139 | 13 | 0 | 13 | 0 |
140 | 14 | 14 | ||
141 | 15 | It will also have a heat_last_updated of None. | ||
142 | 16 | |||
143 | 17 | >>> print bug.heat_last_updated | ||
144 | 18 | None | ||
145 | 19 | |||
146 | 15 | The bug's heat can be set by calling its setHeat() method. | 20 | The bug's heat can be set by calling its setHeat() method. |
147 | 16 | 21 | ||
148 | 17 | >>> bug.setHeat(42) | 22 | >>> bug.setHeat(42) |
149 | 18 | >>> bug.heat | 23 | >>> bug.heat |
150 | 19 | 42 | 24 | 42 |
151 | 20 | 25 | ||
152 | 26 | Its heat_last_updated will also have been set. | ||
153 | 27 | |||
154 | 28 | >>> bug.heat_last_updated | ||
155 | 29 | datetime.datetime(..., tzinfo=<UTC>) | ||
156 | 30 | |||
157 | 31 | |||
158 | 32 | Getting bugs whose heat is outdated | ||
159 | 33 | ----------------------------------- | ||
160 | 34 | |||
161 | 35 | It's possible to get the set of bugs whose heat hasn't been updated for | ||
162 | 36 | a given amount of time by calling IBugSet's getBugsWithOutdatedHeat() | ||
163 | 37 | method. | ||
164 | 38 | |||
165 | 39 | First, we'll set the heat of all bugs so that none of them are out of | ||
166 | 40 | date. | ||
167 | 41 | |||
168 | 42 | >>> from lp.bugs.interfaces.bug import IBugSet | ||
169 | 43 | >>> for bug in getUtility(IBugSet).dangerousGetAllBugs(): | ||
170 | 44 | ... bug.setHeat(0) | ||
171 | 45 | |||
172 | 46 | If we call getBugsWithOutdatedHeat() now, the set that is returned will | ||
173 | 47 | be empty because all the bugs have been recently updated. | ||
174 | 48 | getBugsWithOutdatedHeat() takes a single parameter, max_heat_age, which | ||
175 | 49 | is the maximum age, in days, that a bug's heat can be before it gets | ||
176 | 50 | included in the returned set. | ||
177 | 51 | |||
178 | 52 | >>> getUtility(IBugSet).getBugsWithOutdatedHeat(1).count() | ||
179 | 53 | 0 | ||
180 | 54 | |||
181 | 55 | IBug.setHeat() takes a timestamp parameter so that we can set the | ||
182 | 56 | heat_last_updated date manually for the purposes of testing. If we make | ||
183 | 57 | a bug's heat older than the max_heat_age that we pass to | ||
184 | 58 | getBugsWithOutdatedHeat() it will appear in the set returned by | ||
185 | 59 | getBugsWithOutdatedHeat(). | ||
186 | 60 | |||
187 | 61 | >>> from datetime import datetime, timedelta | ||
188 | 62 | >>> from pytz import timezone | ||
189 | 63 | >>> old_heat_bug = factory.makeBug() | ||
190 | 64 | >>> old_heat_bug.setHeat( | ||
191 | 65 | ... 0, datetime.now(timezone('UTC')) - timedelta(days=2)) | ||
192 | 66 | |||
193 | 67 | >>> outdated_bugs = getUtility(IBugSet).getBugsWithOutdatedHeat(1) | ||
194 | 68 | >>> outdated_bugs.count() | ||
195 | 69 | 1 | ||
196 | 70 | |||
197 | 71 | >>> outdated_bugs[0] == old_heat_bug | ||
198 | 72 | True | ||
199 | 73 | |||
200 | 74 | getBugsWithOutdatedHeat() also returns bugs whose heat has never been | ||
201 | 75 | updated. | ||
202 | 76 | |||
203 | 77 | >>> new_bug = factory.makeBug() | ||
204 | 78 | >>> outdated_bugs = getUtility(IBugSet).getBugsWithOutdatedHeat(1) | ||
205 | 79 | >>> outdated_bugs.count() | ||
206 | 80 | 2 | ||
207 | 81 | |||
208 | 82 | >>> new_bug in outdated_bugs | ||
209 | 83 | True | ||
210 | 84 | |||
211 | 21 | 85 | ||
212 | 22 | The BugHeatUpdater class | 86 | The BugHeatUpdater class |
213 | 23 | --------------------------- | 87 | --------------------------- |
214 | 24 | 88 | ||
218 | 25 | In order to calculate bug heat we need to use the BugHeatUpdater | 89 | The BugHeatUpdater class is used to create bug heat calculation jobs for |
219 | 26 | class, which is designed precisely for that task. It's part of the garbo | 90 | bugs with out-of-date heat. |
217 | 27 | module and runs as part of the garbo-daily cronjob. | ||
220 | 28 | 91 | ||
221 | 29 | >>> from canonical.launchpad.scripts.garbo import BugHeatUpdater | 92 | >>> from canonical.launchpad.scripts.garbo import BugHeatUpdater |
222 | 30 | >>> from canonical.launchpad.scripts import FakeLogger | 93 | >>> from canonical.launchpad.scripts import FakeLogger |
223 | 31 | 94 | ||
225 | 32 | >>> update_bug_heat = BugHeatUpdater(FakeLogger()) | 95 | >>> update_bug_heat = BugHeatUpdater(FakeLogger(), max_heat_age=1) |
226 | 33 | 96 | ||
227 | 34 | BugHeatUpdater implements ITunableLoop and as such is callable. Calling | 97 | BugHeatUpdater implements ITunableLoop and as such is callable. Calling |
256 | 35 | it as a method will update the heat for all the bugs currently held in | 98 | it as a method will add jobs to calculate the heat of for all the bugs |
257 | 36 | Launchpad. | 99 | whose heat is more than seven days old. |
258 | 37 | 100 | ||
259 | 38 | Before update_bug_heat is called, bug 1 will have no heat. | 101 | Before update_bug_heat is called, we'll ensure that there are no waiting |
260 | 39 | 102 | jobs in the bug heat calculation queue. | |
261 | 40 | >>> from zope.component import getUtility | 103 | |
262 | 41 | >>> from lp.bugs.interfaces.bug import IBugSet | 104 | >>> from lp.bugs.interfaces.bugjob import ICalculateBugHeatJobSource |
263 | 42 | >>> bug_1 = getUtility(IBugSet).get(1) | 105 | >>> for calc_job in getUtility(ICalculateBugHeatJobSource).iterReady(): |
264 | 43 | 106 | ... calc_job.job.start() | |
265 | 44 | >>> bug_1.heat | 107 | ... calc_job.job.complete() |
266 | 45 | 0 | 108 | |
267 | 46 | 109 | >>> ready_jobs = list(getUtility(ICalculateBugHeatJobSource).iterReady()) | |
268 | 47 | We touch bug 1 to make sure its date_last_updated is recent enough (bug heat | 110 | >>> len(ready_jobs) |
269 | 48 | decays over time). | 111 | 0 |
270 | 49 | 112 | ||
271 | 50 | >>> new_comment = bug_1.newMessage( | 113 | We need to commit here to ensure that the bugs we've created are |
272 | 51 | ... owner=bug_1.owner, subject="...", content="...") | 114 | available to the update_bug_heat script. |
273 | 52 | >>> import transaction ; transaction.commit() | 115 | |
274 | 53 | >>> bug_1 = getUtility(IBugSet).get(1) | 116 | >>> import transaction |
275 | 54 | 117 | >>> transaction.commit() | |
276 | 55 | >>> update_bug_heat(chunk_size=1) | 118 | |
277 | 56 | DEBUG Updating 1 Bugs (starting id: ...) | 119 | >>> getUtility(IBugSet).getBugsWithOutdatedHeat(1).count() |
278 | 57 | ... | 120 | 2 |
279 | 58 | 121 | ||
280 | 59 | Bug 1's heat will now be greater than 0. | 122 | We need to run update_bug_heat() twice to ensure that both the bugs are |
281 | 60 | 123 | updated. | |
282 | 61 | >>> bug_1.heat > 0 | 124 | |
283 | 62 | True | 125 | >>> update_bug_heat(chunk_size=2) |
284 | 126 | DEBUG Adding CalculateBugHeatJobs for 2 Bugs (starting id: ...) | ||
285 | 127 | DEBUG Adding CalculateBugHeatJob for bug ... | ||
286 | 128 | DEBUG Adding CalculateBugHeatJob for bug ... | ||
287 | 129 | |||
288 | 130 | There will now be two CalculateBugHeatJobs in the queue. | ||
289 | 131 | |||
290 | 132 | >>> ready_jobs = list(getUtility(ICalculateBugHeatJobSource).iterReady()) | ||
291 | 133 | >>> len(ready_jobs) | ||
292 | 134 | 2 | ||
293 | 135 | |||
294 | 136 | Running them will update the bugs' heat. | ||
295 | 137 | |||
296 | 138 | >>> for calc_job in getUtility(ICalculateBugHeatJobSource).iterReady(): | ||
297 | 139 | ... calc_job.job.start() | ||
298 | 140 | ... calc_job.run() | ||
299 | 141 | ... calc_job.job.complete() | ||
300 | 142 | |||
301 | 143 | IBugSet.getBugsWithOutdatedHeat() will now return an empty set since all | ||
302 | 144 | the bugs have been updated. | ||
303 | 145 | |||
304 | 146 | >>> getUtility(IBugSet).getBugsWithOutdatedHeat(1).count() | ||
305 | 147 | 0 | ||
306 | 63 | 148 | ||
307 | 64 | 149 | ||
308 | 65 | Caculating the maximum heat for a target | 150 | Caculating the maximum heat for a target |
309 | 66 | 151 | ||
310 | === modified file 'lib/lp/bugs/interfaces/bug.py' | |||
311 | --- lib/lp/bugs/interfaces/bug.py 2010-02-27 20:20:03 +0000 | |||
312 | +++ lib/lp/bugs/interfaces/bug.py 2010-03-05 11:49:30 +0000 | |||
313 | @@ -320,6 +320,8 @@ | |||
314 | 320 | heat = exported( | 320 | heat = exported( |
315 | 321 | Int(title=_("The 'heat' of the bug"), | 321 | Int(title=_("The 'heat' of the bug"), |
316 | 322 | required=False, readonly=True)) | 322 | required=False, readonly=True)) |
317 | 323 | heat_last_updated = Datetime( | ||
318 | 324 | title=_('Heat Last Updated'), required=False, readonly=True) | ||
319 | 323 | 325 | ||
320 | 324 | # Adding related BugMessages provides a hook for getting at | 326 | # Adding related BugMessages provides a hook for getting at |
321 | 325 | # BugMessage.visible when building bug comments. | 327 | # BugMessage.visible when building bug comments. |
322 | @@ -778,7 +780,7 @@ | |||
323 | 778 | if the user is the owner or an admin. | 780 | if the user is the owner or an admin. |
324 | 779 | """ | 781 | """ |
325 | 780 | 782 | ||
327 | 781 | def setHeat(heat): | 783 | def setHeat(heat, timestamp=None): |
328 | 782 | """Set the heat for the bug.""" | 784 | """Set the heat for the bug.""" |
329 | 783 | 785 | ||
330 | 784 | class InvalidDuplicateValue(Exception): | 786 | class InvalidDuplicateValue(Exception): |
331 | @@ -1032,6 +1034,14 @@ | |||
332 | 1032 | # Note, this method should go away when we have a proper | 1034 | # Note, this method should go away when we have a proper |
333 | 1033 | # permissions system for scripts. | 1035 | # permissions system for scripts. |
334 | 1034 | 1036 | ||
335 | 1037 | def getBugsWithOutdatedHeat(max_heat_age): | ||
336 | 1038 | """Return the set of bugs whose heat is out of date. | ||
337 | 1039 | |||
338 | 1040 | :param max_heat_age: The maximum age, in days, that a bug's heat | ||
339 | 1041 | can be before it is included in the | ||
340 | 1042 | returned set. | ||
341 | 1043 | """ | ||
342 | 1044 | |||
343 | 1035 | 1045 | ||
344 | 1036 | class IFileBugData(Interface): | 1046 | class IFileBugData(Interface): |
345 | 1037 | """A class containing extra data to be used when filing a bug.""" | 1047 | """A class containing extra data to be used when filing a bug.""" |
346 | 1038 | 1048 | ||
347 | === modified file 'lib/lp/bugs/model/bug.py' | |||
348 | --- lib/lp/bugs/model/bug.py 2010-02-28 13:41:18 +0000 | |||
349 | +++ lib/lp/bugs/model/bug.py 2010-03-05 11:49:30 +0000 | |||
350 | @@ -22,7 +22,9 @@ | |||
351 | 22 | import operator | 22 | import operator |
352 | 23 | import re | 23 | import re |
353 | 24 | from cStringIO import StringIO | 24 | from cStringIO import StringIO |
354 | 25 | from datetime import datetime, timedelta | ||
355 | 25 | from email.Utils import make_msgid | 26 | from email.Utils import make_msgid |
356 | 27 | from pytz import timezone | ||
357 | 26 | 28 | ||
358 | 27 | from zope.contenttype import guess_content_type | 29 | from zope.contenttype import guess_content_type |
359 | 28 | from zope.component import getUtility | 30 | from zope.component import getUtility |
360 | @@ -33,7 +35,7 @@ | |||
361 | 33 | from sqlobject import SQLMultipleJoin, SQLRelatedJoin | 35 | from sqlobject import SQLMultipleJoin, SQLRelatedJoin |
362 | 34 | from sqlobject import SQLObjectNotFound | 36 | from sqlobject import SQLObjectNotFound |
363 | 35 | from storm.expr import ( | 37 | from storm.expr import ( |
365 | 36 | And, Count, Func, In, LeftJoin, Max, Not, Select, SQLRaw, Union) | 38 | And, Count, Func, In, LeftJoin, Max, Not, Or, Select, SQLRaw, Union) |
366 | 37 | from storm.store import EmptyResultSet, Store | 39 | from storm.store import EmptyResultSet, Store |
367 | 38 | 40 | ||
368 | 39 | from lazr.lifecycle.event import ( | 41 | from lazr.lifecycle.event import ( |
369 | @@ -260,6 +262,7 @@ | |||
370 | 260 | users_affected_count = IntCol(notNull=True, default=0) | 262 | users_affected_count = IntCol(notNull=True, default=0) |
371 | 261 | users_unaffected_count = IntCol(notNull=True, default=0) | 263 | users_unaffected_count = IntCol(notNull=True, default=0) |
372 | 262 | heat = IntCol(notNull=True, default=0) | 264 | heat = IntCol(notNull=True, default=0) |
373 | 265 | heat_last_updated = UtcDateTimeCol(default=None) | ||
374 | 263 | latest_patch_uploaded = UtcDateTimeCol(default=None) | 266 | latest_patch_uploaded = UtcDateTimeCol(default=None) |
375 | 264 | 267 | ||
376 | 265 | @property | 268 | @property |
377 | @@ -1531,9 +1534,13 @@ | |||
378 | 1531 | 1534 | ||
379 | 1532 | return not subscriptions_from_dupes.is_empty() | 1535 | return not subscriptions_from_dupes.is_empty() |
380 | 1533 | 1536 | ||
382 | 1534 | def setHeat(self, heat): | 1537 | def setHeat(self, heat, timestamp=None): |
383 | 1535 | """See `IBug`.""" | 1538 | """See `IBug`.""" |
384 | 1539 | if timestamp is None: | ||
385 | 1540 | timestamp = UTC_NOW | ||
386 | 1541 | |||
387 | 1536 | self.heat = heat | 1542 | self.heat = heat |
388 | 1543 | self.heat_last_updated = timestamp | ||
389 | 1537 | for task in self.bugtasks: | 1544 | for task in self.bugtasks: |
390 | 1538 | task.target.recalculateMaxBugHeat() | 1545 | task.target.recalculateMaxBugHeat() |
391 | 1539 | 1546 | ||
392 | @@ -1790,6 +1797,18 @@ | |||
393 | 1790 | result_set = store.find(Bug) | 1797 | result_set = store.find(Bug) |
394 | 1791 | return result_set.order_by('id') | 1798 | return result_set.order_by('id') |
395 | 1792 | 1799 | ||
396 | 1800 | def getBugsWithOutdatedHeat(self, max_heat_age): | ||
397 | 1801 | """See `IBugSet`.""" | ||
398 | 1802 | store = IStore(Bug) | ||
399 | 1803 | last_updated_cutoff = ( | ||
400 | 1804 | datetime.now(timezone('UTC')) - | ||
401 | 1805 | timedelta(days=max_heat_age)) | ||
402 | 1806 | last_updated_clause = Or( | ||
403 | 1807 | Bug.heat_last_updated < last_updated_cutoff, | ||
404 | 1808 | Bug.heat_last_updated == None) | ||
405 | 1809 | |||
406 | 1810 | return store.find(Bug, last_updated_clause).order_by('id') | ||
407 | 1811 | |||
408 | 1793 | 1812 | ||
409 | 1794 | class BugAffectsPerson(SQLBase): | 1813 | class BugAffectsPerson(SQLBase): |
410 | 1795 | """A bug is marked as affecting a user.""" | 1814 | """A bug is marked as affecting a user.""" |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Hi Graham,
nice banch! I have just two nitpicks, see below.
Abel
> === modified file 'lib/lp/ bugs/doc/ bug-heat. txt' bugs/doc/ bug-heat. txt 2010-03-03 13:10:17 +0000 bugs/doc/ bug-heat. txt 2010-03-05 10:13:32 +0000
> --- lib/lp/
> +++ lib/lp/
[...]
> +it as a method will add jobs to calculate the heat of for all the bugs
> +whose heat is more than seven days old.
I think one of ('of', 'for') is sufficient ;)
> + interfaces. bugjob import ICalculateBugHe atJobSource ICalculateBugHe atJobSource) .iterReady( ): job.start( ) job.complete( ) (ICalculateBugH eatJobSource) .iterReady( )) commit( ) IBugSet) .getBugsWithOut datedHeat( 1).count( )
> +Before update_bug_heat is called, we'll ensure that there are no waiting
> +jobs in the bug heat calculation queue.
> +
> + >>> from lp.bugs.
> + >>> for calc_job in
getUtility(
> + ... calc_job.
> + ... calc_job.
> +
> + >>> ready_jobs =
list(getUtility
> + >>> len(ready_jobs)
> + 0
> +
> +We need to commit here to ensure that the bugs we've created are
> +available to the update_bug_heat script.
> +
> + >>> import transaction
> + >>> transaction.
> +
> + >>> getUtility(
> + 2
> +
> +We need to run update_bug_heat() twice to ensure that both the bugs are
> +updated.
I think you call update_bug_heat() in this doc test only once. But it
seems it should be called with chunksize=2 to ensure both bugs are
processed.
> + bug_heat( chunk_size= 2) tJobs for 2 Bugs (starting id: ...)
> + >>> update_
> + DEBUG Adding CalculateBugHea
> + DEBUG Adding CalculateBugHeatJob for bug ...
> + DEBUG Adding CalculateBugHeatJob for bug ...
-----BEGIN PGP SIGNATURE----- enigmail. mozdev. org
Phm8NrtARApukAJ oCrVwFFnMkn4bR+ JHA6lbUGQaZigCg kHXF qmb/8X7o=
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://
iD8DBQFLkODxekB
ARR5bSLClYT7Elx
=+2Ba
-----END PGP SIGNATURE-----