Merge lp:~deryck/launchpad/max-heat-by-target-511382 into lp:launchpad/db-devel

Proposed by Deryck Hodge
Status: Merged
Approved by: Eleanor Berger
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~deryck/launchpad/max-heat-by-target-511382
Merge into: lp:launchpad/db-devel
Diff against target: 650 lines (+241/-52)
19 files modified
database/schema/comments.sql (+4/-2)
database/schema/patch-2207-34-0.sql (+11/-0)
lib/lp/bugs/browser/bug.py (+0/-22)
lib/lp/bugs/browser/bugtask.py (+33/-8)
lib/lp/bugs/browser/configure.zcml (+0/-5)
lib/lp/bugs/browser/tests/bug-heat-view.txt (+44/-11)
lib/lp/bugs/interfaces/bug.py (+1/-0)
lib/lp/bugs/interfaces/bugtarget.py (+5/-0)
lib/lp/bugs/model/bug.py (+1/-0)
lib/lp/bugs/model/bugtarget.py (+14/-0)
lib/lp/bugs/tests/test_bugheat.py (+82/-0)
lib/lp/registry/configure.zcml (+2/-1)
lib/lp/registry/model/distribution.py (+2/-2)
lib/lp/registry/model/distributionsourcepackage.py (+23/-0)
lib/lp/registry/model/distroseries.py (+5/-0)
lib/lp/registry/model/product.py (+2/-1)
lib/lp/registry/model/productseries.py (+5/-0)
lib/lp/registry/model/project.py (+2/-0)
lib/lp/registry/model/sourcepackage.py (+5/-0)
To merge this branch: bzr merge lp:~deryck/launchpad/max-heat-by-target-511382
Reviewer Review Type Date Requested Status
Björn Tillenius (community) db Approve
Eleanor Berger (community) Approve
Stuart Bishop (community) db Approve
Review via email: mp+19998@code.launchpad.net

Commit message

Add a max_heat attribute to each bug target.

To post a comment you must log in.
Revision history for this message
Deryck Hodge (deryck) wrote :

This is the first step in fixing Bug #511382. This branch provides a max_heat attribute on bug targets and a setMaxHeat method. This branch includes the DB patch and a test to exercise the new attribute on each bug target.

I did a implementation call with Björn to make sure the approach was sane.

To run the test:

./bin/test -cvvt test_bugheat

The branch doesn't use max_heat yet. This branch is only to provide the DB changes. A later branch will need to be provided to update this value in an offline-run script and then convert the icon view to use a target's max_heat, rather than the constant MAX_HEAT.

Revision history for this message
Stuart Bishop (stub) wrote :

Will Launchpad have other heat in the future? If so, the column needs to be called max_bug_heat to avoid future name conflicts.

Is 0 a sane default? For heat calculations, I imagine this could lead to division by zero errors. If there is no sane default and the heat calculations explicitly checks for '0', we should just use NULL instead.

Otherwise fine. Allocated number is patch-2207-34-0.sql.

Rather than write a new script to update the max heat cache, I think it would be better to add a new task to garbo-daily.py. This provides infrastructure to do the updates in a database friendly manner (with DBLoopTuner), and test infrastructure to speed up writing your tests.

review: Needs Information (db)
Revision history for this message
Björn Tillenius (bjornt) wrote :

I have the same questions as Stuart. For now I think it would be better to call it max_bug_heat to avoid confusion. If we add heat to other things, we can rename it, if we want the heat to be shared.

I also think that NULL should be used to define that heat hasn't been calculated yet, unless using 0 makes things easier. Does it? (Using 0 to have a special value is a bit of a hack, when you are restricted to integer values only.)

I have another question as well, though. You have max_heat for Distribution, DistroSeries, and DistributionSourcePackage. Why not DistroSeriesSourcePackage? Is it necessary to go into the *Series level, or could they just delegate to their non-series counterparts?

review: Needs Information (db)
Revision history for this message
Deryck Hodge (deryck) wrote :

I'll update the patch and code to use max_bug_heat, that makes sense. I'm also fine to use NULL; again that makes sense. Thanks for the reviews!

I'll fix the branch now and follow up with Björn on IRC about the DistroSeriesSourcePackage question to make sure I understand what is being asked there.

Cheers,
deryck

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

Okay, I've updated based on the recommendations here.

I talked to Björn and after a long discussion on IRC came to the conclusion to have max_bug_heat on Product, Project, Distribution, and DistributionSourcePackage. The series versions of these will delegate to those objects.

Does this look good to you guys?

Cheers,
deryck

Revision history for this message
Stuart Bishop (stub) wrote :

db patch looks good.

review: Approve (db)
Revision history for this message
Eleanor Berger (intellectronica) wrote :

As we discussed on IRC, it will be safer to avoid having a setter for DSP.max_bug_heat, since it uses the distro's value. With that change, r=me.

review: Approve
Revision history for this message
Björn Tillenius (bjornt) :
review: Approve (db)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/comments.sql'
2--- database/schema/comments.sql 2010-02-23 15:43:49 +0000
3+++ database/schema/comments.sql 2010-02-26 05:16:22 +0000
4@@ -461,7 +461,7 @@
5
6 COMMENT ON TABLE DistributionSourcePackage IS 'Representing a sourcepackage in a distribution across all distribution series.';
7 COMMENT ON COLUMN DistributionSourcePackage.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on a particular a source package in a distribution.';
8-
9+COMMENT ON COLUMN DistributionSourcePackage.max_bug_heat IS 'The highest heat value across bugs for this source package.';
10
11 -- DistributionSourcePackageCache
12
13@@ -475,7 +475,6 @@
14 COMMENT ON COLUMN DistributionSourcePackageCache.changelog IS 'A concatenation of the source package release changelogs for this source package, where the status is not REMOVED.';
15 COMMENT ON COLUMN DistributionSourcePackageCache.archive IS 'The archive where the source is published.';
16
17-
18 -- DistroSeriesPackageCache
19
20 COMMENT ON TABLE DistroSeriesPackageCache IS 'A cache of the text associated with binary packages in the distroseries. This table allows for fast queries to find a binary packagename that matches a given text.';
21@@ -612,6 +611,7 @@
22 COMMENT ON COLUMN Product.reviewer_whiteboard IS 'A whiteboard for Launchpad admins, registry experts and the project owners to capture the state of current issues with the project.';
23 COMMENT ON COLUMN Product.license_approved IS 'The Other/Open Source license has been approved by an administrator.';
24 COMMENT ON COLUMN Product.remote_product IS 'The ID of this product on its remote bug tracker.';
25+COMMENT ON COLUMN Product.max_bug_heat IS 'The highest heat value across bugs for this product.';
26
27 -- ProductLicense
28 COMMENT ON TABLE ProductLicense IS 'The licenses that cover the software for a product.';
29@@ -703,6 +703,7 @@
30 COMMENT ON COLUMN Project.logo IS 'The library file alias of a smaller version of this product''s mugshot.';
31 COMMENT ON COLUMN Project.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on products in this project.';
32 COMMENT ON COLUMN Project.reviewer_whiteboard IS 'A whiteboard for Launchpad admins, registry experts and the project owners to capture the state of current issues with the project.';
33+COMMENT ON COLUMN Project.max_bug_heat IS 'The highest heat value across bugs for products in this project.';
34
35 -- ProjectRelationship
36 COMMENT ON TABLE ProjectRelationship IS 'Project Relationships. This table stores information about the way projects are related to one another in the open source world. The actual nature of the relationship is stored in the ''label'' field, and possible values are given by the ProjectRelationship enum in dbschema.py. Examples are AGGREGATES ("the Gnome Project AGGREGATES EOG and Evolution and Gnumeric and AbiWord") and SIMILAR ("the Evolution project is SIMILAR to the Mutt project").';
37@@ -966,6 +967,7 @@
38 COMMENT ON COLUMN Distribution.enable_bug_expiration IS 'Indicates whether automatic bug expiration is enabled.';
39 COMMENT ON COLUMN Distribution.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on this distribution.';
40 COMMENT ON COLUMN Distribution.reviewer_whiteboard IS 'A whiteboard for Launchpad admins, registry experts and the project owners to capture the state of current issues with the project.';
41+COMMENT ON COLUMN Distribution.max_bug_heat IS 'The highest heat value across bugs for this distribution.';
42
43 -- DistroSeries
44
45
46=== added file 'database/schema/patch-2207-34-0.sql'
47--- database/schema/patch-2207-34-0.sql 1970-01-01 00:00:00 +0000
48+++ database/schema/patch-2207-34-0.sql 2010-02-26 05:16:22 +0000
49@@ -0,0 +1,11 @@
50+-- Copyright 2010 Canonical Ltd. This software is licensed under the
51+-- GNU Affero General Public License version 3 (see the file LICENSE).
52+
53+SET client_min_messages=ERROR;
54+
55+ALTER TABLE product ADD COLUMN max_bug_heat integer;
56+ALTER TABLE project ADD COLUMN max_bug_heat integer;
57+ALTER TABLE distribution ADD COLUMN max_bug_heat integer;
58+ALTER TABLE distributionsourcepackage ADD COLUMN max_bug_heat integer;
59+
60+INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 34, 0);
61
62=== modified file 'lib/lp/bugs/browser/bug.py'
63--- lib/lp/bugs/browser/bug.py 2010-02-02 17:26:53 +0000
64+++ lib/lp/bugs/browser/bug.py 2010-02-26 05:16:22 +0000
65@@ -10,7 +10,6 @@
66 'BugContextMenu',
67 'BugEditView',
68 'BugFacets',
69- 'BugHeatView',
70 'BugMarkAsAffectingUserView',
71 'BugMarkAsDuplicateView',
72 'BugNavigation',
73@@ -23,13 +22,11 @@
74 'BugWithoutContextView',
75 'DeprecatedAssignedBugsView',
76 'MaloneView',
77- 'MAX_HEAT',
78 ]
79
80 from datetime import datetime, timedelta
81 from email.MIMEMultipart import MIMEMultipart
82 from email.MIMEText import MIMEText
83-from math import floor
84 import re
85
86 import pytz
87@@ -77,10 +74,6 @@
88 from canonical.widgets.bug import BugTagsWidget
89 from canonical.widgets.project import ProjectScopeWidget
90
91-# Constant for the maximum bug heat we'll use for converting
92-# IBug.heat to ratio. In the future this should come from the DB.
93-# The value must be a float
94-MAX_HEAT = 5000.0
95
96 class BugNavigation(Navigation):
97 """Navigation for the `IBug`."""
98@@ -969,18 +962,3 @@
99 return html.encode('utf-8')
100 return renderer
101
102-
103-class BugHeatView(LaunchpadView):
104- """View for rendering the graphical (HTML) representation of bug heat."""
105-
106- def __call__(self):
107- """Render the bug heat representation."""
108- heat_ratio = floor((self.context.heat / MAX_HEAT) * 4)
109- html = '<span>'
110- for flame in range(1, 5):
111- if flame <= heat_ratio:
112- html += '<img src="/@@/flame-icon" />'
113- else:
114- html += '<img src="/@@/flame-bw-icon" />'
115- html += '</span>'
116- return html
117
118=== modified file 'lib/lp/bugs/browser/bugtask.py'
119--- lib/lp/bugs/browser/bugtask.py 2010-02-25 22:00:26 +0000
120+++ lib/lp/bugs/browser/bugtask.py 2010-02-26 05:16:22 +0000
121@@ -33,7 +33,9 @@
122 'BugTaskTextView',
123 'BugTaskView',
124 'BugTasksAndNominationsView',
125+ 'bugtask_heat_html',
126 'BugsBugTaskSearchListingView',
127+ 'calculate_heat_display',
128 'NominationsReviewTableBatchNavigatorView',
129 'TextualBugTaskSearchListingView',
130 'get_buglisting_search_filter_url',
131@@ -49,6 +51,7 @@
132 from simplejson import dumps
133 import urllib
134 from operator import attrgetter, itemgetter
135+from math import floor, log
136
137 from zope import component
138 from zope.app.form import CustomWidgetFactory
139@@ -1080,10 +1083,35 @@
140 @property
141 def bug_heat_html(self):
142 """HTML representation of the bug heat."""
143- view = getMultiAdapter(
144- (self.context.bug, self.request),
145- name='+bug-heat')
146- return view()
147+ return bugtask_heat_html(self.context)
148+
149+
150+def calculate_heat_display(heat, max_bug_heat):
151+ """Calculate the number of heat 'flames' to display."""
152+ heat = float(heat)
153+ max_bug_heat = float(max_bug_heat)
154+ if heat / max_bug_heat < 0.33333:
155+ return 0
156+ if heat / max_bug_heat < 0.66666:
157+ return int(floor((heat / max_bug_heat) * 4))
158+ else:
159+ return int(floor((log(heat) / log(max_bug_heat)) * 4))
160+
161+
162+def bugtask_heat_html(bugtask):
163+ """Render the HTML representing bug heat for a given bugask."""
164+ max_bug_heat = bugtask.target.max_bug_heat
165+ if max_bug_heat is None:
166+ max_bug_heat = 5000
167+ heat_ratio = calculate_heat_display(bugtask.bug.heat, max_bug_heat)
168+ html = '<span>'
169+ for flame in range(1, 5):
170+ if flame <= heat_ratio:
171+ html += '<img src="/@@/flame-icon" />'
172+ else:
173+ html += '<img src="/@@/flame-bw-icon" />'
174+ html += '</span>'
175+ return html
176
177
178 class BugTaskPortletView:
179@@ -1967,10 +1995,7 @@
180 @property
181 def bug_heat_html(self):
182 """Returns the bug heat flames HTML."""
183- view = getMultiAdapter(
184- (self.bugtask.bug, self.request),
185- name='+bug-heat')
186- return view()
187+ return bugtask_heat_html(self.bugtask)
188
189
190 class BugListingBatchNavigator(TableBatchNavigator):
191
192=== modified file 'lib/lp/bugs/browser/configure.zcml'
193--- lib/lp/bugs/browser/configure.zcml 2010-02-20 13:16:38 +0000
194+++ lib/lp/bugs/browser/configure.zcml 2010-02-26 05:16:22 +0000
195@@ -1044,11 +1044,6 @@
196 name="+bug-portlet-subscribers-ids"
197 class="lp.bugs.browser.bugsubscription.BugPortletSubcribersIds"
198 permission="zope.Public"/>
199- <browser:page
200- for="lp.bugs.interfaces.bug.IBug"
201- class="lp.bugs.browser.bug.BugHeatView"
202- permission="zope.Public"
203- name="+bug-heat"/>
204 <browser:navigation
205 module="lp.bugs.browser.bug"
206 classes="
207
208=== modified file 'lib/lp/bugs/browser/tests/bug-heat-view.txt'
209--- lib/lp/bugs/browser/tests/bug-heat-view.txt 2010-01-18 22:11:00 +0000
210+++ lib/lp/bugs/browser/tests/bug-heat-view.txt 2010-02-26 05:16:22 +0000
211@@ -1,15 +1,16 @@
212 = Bug heat view =
213
214 Bug heat is represented as four flame icons. The quantity of flames that are
215-coloured is dependent on the value of the heat field. The view BugHeatView
216-is used to render the flames.
217+coloured is dependent on the value of the heat field. The function
218+bugtask_heat_html is used to render the flames.
219
220- >>> from lp.bugs.browser.bug import MAX_HEAT
221+ >>> MAX_HEAT = 5000.0
222 >>> from canonical.launchpad.ftests import login, logout
223- >>> from canonical.launchpad.webapp import canonical_url
224 >>> from zope.security.proxy import removeSecurityProxy
225 >>> from BeautifulSoup import BeautifulSoup
226- >>> def print_flames(html):
227+ >>> from lp.bugs.browser.bugtask import bugtask_heat_html
228+ >>> def print_flames(bugtask):
229+ ... html = bugtask_heat_html(bugtask)
230 ... soup = BeautifulSoup(html)
231 ... for img in soup.span.contents:
232 ... print img['src']
233@@ -20,9 +21,9 @@
234 a heat of half the maximum will result in a display of two coloured flames
235 and two black-and-white flames.
236
237+ >>> removeSecurityProxy(bug.default_bugtask.target).max_bug_heat = MAX_HEAT
238 >>> removeSecurityProxy(bug).heat = MAX_HEAT / 2
239- >>> bug_heat_view = create_initialized_view(bug, name='+bug-heat')
240- >>> print_flames(bug_heat_view())
241+ >>> print_flames(bug.default_bugtask)
242 /@@/flame-icon
243 /@@/flame-icon
244 /@@/flame-bw-icon
245@@ -31,8 +32,7 @@
246 A bug with a maximum heat will display all four flames coloured.
247
248 >>> removeSecurityProxy(bug).heat = MAX_HEAT
249- >>> bug_heat_view = create_initialized_view(bug, name='+bug-heat')
250- >>> print_flames(bug_heat_view())
251+ >>> print_flames(bug.default_bugtask)
252 /@@/flame-icon
253 /@@/flame-icon
254 /@@/flame-icon
255@@ -41,11 +41,44 @@
256 A heat of less than a quarter of the maximum will display no coloured flames.
257
258 >>> removeSecurityProxy(bug).heat = 0.1 * MAX_HEAT
259- >>> bug_heat_view = create_initialized_view(bug, name='+bug-heat')
260- >>> print_flames(bug_heat_view())
261+ >>> print_flames(bug.default_bugtask)
262 /@@/flame-bw-icon
263 /@@/flame-bw-icon
264 /@@/flame-bw-icon
265 /@@/flame-bw-icon
266
267 >>> logout()
268+
269+
270+== Scaling Bug Heat ==
271+
272+To ensure a reasonable proportion of cold and hot bugs, the number used to
273+calculate the number of flames to display is not a straight-forward ratio.
274+Instead, we transform it by forcing low heat bugs to produce no flames and
275+scaling the hottest bugs logarithmically.
276+
277+ >>> from lp.bugs.browser.bugtask import calculate_heat_display
278+ >>> from math import floor
279+
280+Heat values less than a third of the maximum heat don't produce any flames.
281+
282+ >>> print int(floor((300.0 / 1000.0) * 4))
283+ 1
284+ >>> print calculate_heat_display(300.0, 1000.0)
285+ 0
286+
287+Heat values higher than a third of the max but lower than two thirds are treated
288+as a straightforward ratio.
289+
290+ >>> print int(floor((500.0 / 1000.0) * 4))
291+ 2
292+ >>> print calculate_heat_display(500.0, 1000.0)
293+ 2
294+
295+Heat values higher than two thirds of the maximum heat are scaled upwards.
296+
297+ >>> print int(floor((700.0 / 1000.0) * 4))
298+ 2
299+ >>> print calculate_heat_display(800.0, 1000.0)
300+ 3
301+
302
303=== modified file 'lib/lp/bugs/interfaces/bug.py'
304--- lib/lp/bugs/interfaces/bug.py 2010-02-23 15:34:15 +0000
305+++ lib/lp/bugs/interfaces/bug.py 2010-02-26 05:16:22 +0000
306@@ -15,6 +15,7 @@
307 'IBugBecameQuestionEvent',
308 'IBugDelta',
309 'IBugSet',
310+ 'IFileBugData',
311 'IFrontPageBugAddForm',
312 'IProjectGroupBugAddForm',
313 'InvalidBugTargetType',
314
315=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
316--- lib/lp/bugs/interfaces/bugtarget.py 2010-02-25 22:00:26 +0000
317+++ lib/lp/bugs/interfaces/bugtarget.py 2010-02-26 05:16:22 +0000
318@@ -61,6 +61,8 @@
319 description=_("The list of bug tags defined as official."),
320 value_type=Tag(),
321 readonly=True))
322+ max_bug_heat = Attribute(
323+ "The current highest bug heat value for this target.")
324
325 @call_with(search_params=None, user=REQUEST_USER)
326 @operation_parameters(
327@@ -219,6 +221,9 @@
328 None, all statuses will be included.
329 """
330
331+ def setMaxBugHeat(heat):
332+ """Set the max_bug_heat for this context."""
333+
334
335 class IBugTarget(IHasBugs):
336 """An entity on which a bug can be reported.
337
338=== modified file 'lib/lp/bugs/model/bug.py'
339--- lib/lp/bugs/model/bug.py 2010-02-19 16:39:25 +0000
340+++ lib/lp/bugs/model/bug.py 2010-02-26 05:16:22 +0000
341@@ -13,6 +13,7 @@
342 'BugBecameQuestionEvent',
343 'BugSet',
344 'BugTag',
345+ 'FileBugData',
346 'get_bug_tags',
347 'get_bug_tags_open_count',
348 ]
349
350=== modified file 'lib/lp/bugs/model/bugtarget.py'
351--- lib/lp/bugs/model/bugtarget.py 2010-02-25 22:00:26 +0000
352+++ lib/lp/bugs/model/bugtarget.py 2010-02-26 05:16:22 +0000
353@@ -25,7 +25,10 @@
354 from canonical.launchpad.webapp.interfaces import ILaunchBag
355 from lp.bugs.interfaces.bugtarget import IOfficialBugTag
356 from lp.registry.interfaces.distribution import IDistribution
357+from lp.registry.interfaces.distributionsourcepackage import (
358+ IDistributionSourcePackage)
359 from lp.registry.interfaces.product import IProduct
360+from lp.registry.interfaces.projectgroup import IProjectGroup
361 from lp.bugs.interfaces.bugtask import (
362 BugTagsSearchCombinator, BugTaskImportance, BugTaskSearchParams,
363 BugTaskStatus, RESOLVED_BUGTASK_STATUSES, UNRESOLVED_BUGTASK_STATUSES)
364@@ -156,6 +159,17 @@
365
366 return self.searchTasks(all_tasks_query)
367
368+ def setMaxBugHeat(self, heat):
369+ """See `IHasBugs`."""
370+ if (IDistribution.providedBy(self)
371+ or IProduct.providedBy(self)
372+ or IProjectGroup.providedBy(self)
373+ or IDistributionSourcePackage.providedBy(self)):
374+ # Only objects that don't delegate have a setter.
375+ self.max_bug_heat = heat
376+ else:
377+ raise NotImplementedError
378+
379 def getBugCounts(self, user, statuses=None):
380 """See `IHasBugs`."""
381 if statuses is None:
382
383=== modified file 'lib/lp/bugs/tests/test_bugheat.py'
384--- lib/lp/bugs/tests/test_bugheat.py 2010-01-21 22:59:32 +0000
385+++ lib/lp/bugs/tests/test_bugheat.py 2010-02-26 05:16:22 +0000
386@@ -20,6 +20,7 @@
387 from lp.bugs.model.bugheat import CalculateBugHeatJob
388 from lp.bugs.scripts.bugheat import BugHeatCalculator
389 from lp.testing import TestCaseWithFactory
390+from lp.testing.factory import LaunchpadObjectFactory
391
392
393 class CalculateBugHeatJobTestCase(TestCaseWithFactory):
394@@ -214,5 +215,86 @@
395 self.assertEqual(bug_job.bug, new_bug)
396
397
398+class MaxHeatByTargetBase:
399+ """Base class for testing a bug target's max_bug_heat attribute."""
400+
401+ layer = LaunchpadZopelessLayer
402+
403+ factory = LaunchpadObjectFactory()
404+
405+ # The target to test.
406+ target = None
407+
408+ # Does the target have a set method?
409+ delegates_setter = False
410+
411+ def test_target_max_bug_heat_default(self):
412+ self.assertEqual(self.target.max_bug_heat, None)
413+
414+ def test_set_target_max_bug_heat(self):
415+ if self.delegates_setter:
416+ self.assertRaises(
417+ NotImplementedError, self.target.setMaxBugHeat, 1000)
418+ else:
419+ self.target.setMaxBugHeat(1000)
420+ self.assertEqual(self.target.max_bug_heat, 1000)
421+
422+
423+class ProjectMaxHeatByTargetTest(MaxHeatByTargetBase, unittest.TestCase):
424+ """Ensure a project has a max_bug_heat value that can be set."""
425+
426+ def setUp(self):
427+ self.target = self.factory.makeProduct()
428+
429+
430+class DistributionMaxHeatByTargetTest(MaxHeatByTargetBase, unittest.TestCase):
431+ """Ensure a distribution has a max_bug_heat value that can be set."""
432+
433+ def setUp(self):
434+ self.target = self.factory.makeDistribution()
435+
436+
437+class DistributionSourcePackageMaxHeatByTargetTest(
438+ MaxHeatByTargetBase, unittest.TestCase):
439+ """Ensure distro source package has max_bug_heat value that can be set."""
440+
441+ def setUp(self):
442+ self.target = self.factory.makeDistributionSourcePackage()
443+
444+
445+class SourcePackageMaxHeatByTargetTest(
446+ MaxHeatByTargetBase, unittest.TestCase):
447+ """Ensure a source package has a max_bug_heat value that can be set."""
448+
449+ def setUp(self):
450+ self.target = self.factory.makeSourcePackage()
451+ self.delegates_setter = True
452+
453+
454+class ProductSeriesMaxHeatByTargetTest(
455+ MaxHeatByTargetBase, unittest.TestCase):
456+ """Ensure a product series has a max_bug_heat value that can be set."""
457+
458+ def setUp(self):
459+ self.target = self.factory.makeProductSeries()
460+ self.delegates_setter = True
461+
462+
463+class DistroSeriesMaxHeatByTargetTest(
464+ MaxHeatByTargetBase, unittest.TestCase):
465+ """Ensure a distro series has a max_bug_heat value that can be set."""
466+
467+ def setUp(self):
468+ self.target = self.factory.makeDistroSeries()
469+ self.delegates_setter = True
470+
471+
472+class ProjectGroupMaxHeatByTargetTest(
473+ MaxHeatByTargetBase, unittest.TestCase):
474+ """Ensure a project group has a max_bug_heat value that can be set."""
475+
476+ def setUp(self):
477+ self.target = self.factory.makeProject()
478+
479 def test_suite():
480 return unittest.TestLoader().loadTestsFromName(__name__)
481
482=== modified file 'lib/lp/registry/configure.zcml'
483--- lib/lp/registry/configure.zcml 2010-02-18 09:04:51 +0000
484+++ lib/lp/registry/configure.zcml 2010-02-26 05:16:22 +0000
485@@ -407,7 +407,8 @@
486 official_bug_tags
487 findRelatedArchives
488 findRelatedArchivePublications
489- userHasBugSubscriptions"/>
490+ userHasBugSubscriptions
491+ max_bug_heat"/>
492 <require
493 permission="launchpad.AnyPerson"
494 attributes="
495
496=== modified file 'lib/lp/registry/model/distribution.py'
497--- lib/lp/registry/model/distribution.py 2010-02-16 15:08:03 +0000
498+++ lib/lp/registry/model/distribution.py 2010-02-26 05:16:22 +0000
499@@ -13,7 +13,7 @@
500
501 from sqlobject import BoolCol, ForeignKey, SQLObjectNotFound, StringCol
502 from sqlobject.sqlbuilder import SQLConstant
503-from storm.locals import Desc, In, Join, SQL
504+from storm.locals import Desc, In, Int, Join, SQL
505 from storm.store import Store
506 from zope.component import getUtility
507 from zope.interface import alsoProvides, implements
508@@ -34,7 +34,6 @@
509 from lp.soyuz.model.binarypackagename import BinaryPackageName
510 from lp.soyuz.model.binarypackagerelease import (
511 BinaryPackageRelease)
512-from lp.bugs.interfaces.bugattachment import BugAttachmentType
513 from lp.bugs.model.bug import (
514 BugSet, get_bug_tags, get_bug_tags_open_count)
515 from lp.bugs.model.bugtarget import (
516@@ -179,6 +178,7 @@
517 official_blueprints = BoolCol(dbName='official_blueprints', notNull=True,
518 default=False)
519 active = True # Required by IPillar interface.
520+ max_bug_heat = Int()
521
522 def __repr__(self):
523 return "<%s '%s' (%s)>" % (
524
525=== modified file 'lib/lp/registry/model/distributionsourcepackage.py'
526--- lib/lp/registry/model/distributionsourcepackage.py 2010-01-06 12:03:42 +0000
527+++ lib/lp/registry/model/distributionsourcepackage.py 2010-02-26 05:16:22 +0000
528@@ -142,6 +142,26 @@
529 _get_bug_reporting_guidelines,
530 _set_bug_reporting_guidelines)
531
532+ def _get_max_bug_heat(self):
533+ """See `IHasBugs`."""
534+ dsp_in_db = self._self_in_database
535+ if dsp_in_db is None:
536+ return None
537+ else:
538+ return dsp_in_db.max_bug_heat
539+
540+ def _set_max_bug_heat(self, value):
541+ """See `IHasBugs`."""
542+ dsp_in_db = self._self_in_database
543+ if dsp_in_db is None:
544+ dsp_in_db = DistributionSourcePackageInDatabase()
545+ dsp_in_db.sourcepackagename = self.sourcepackagename
546+ dsp_in_db.distribution = self.distribution
547+ Store.of(self.distribution).add(dsp_in_db)
548+ dsp_in_db.max_bug_heat = value
549+
550+ max_bug_heat = property(_get_max_bug_heat, _set_max_bug_heat)
551+
552 @property
553 def latest_overall_publication(self):
554 """See `IDistributionSourcePackage`."""
555@@ -474,3 +494,6 @@
556 sourcepackagename_id, 'SourcePackageName.id')
557
558 bug_reporting_guidelines = Unicode()
559+
560+ max_bug_heat = Int()
561+
562
563=== modified file 'lib/lp/registry/model/distroseries.py'
564--- lib/lp/registry/model/distroseries.py 2010-02-22 20:58:16 +0000
565+++ lib/lp/registry/model/distroseries.py 2010-02-26 05:16:22 +0000
566@@ -624,6 +624,11 @@
567 return self.fullseriesname
568
569 @property
570+ def max_bug_heat(self):
571+ """See `IHasBugs`."""
572+ return self.distribution.max_bug_heat
573+
574+ @property
575 def last_full_language_pack_exported(self):
576 return LanguagePack.selectFirstBy(
577 distroseries=self, type=LanguagePackType.FULL,
578
579=== modified file 'lib/lp/registry/model/product.py'
580--- lib/lp/registry/model/product.py 2010-02-09 01:31:05 +0000
581+++ lib/lp/registry/model/product.py 2010-02-26 05:16:22 +0000
582@@ -18,7 +18,7 @@
583 import pytz
584 from sqlobject import (
585 BoolCol, ForeignKey, SQLMultipleJoin, SQLObjectNotFound, StringCol)
586-from storm.locals import And, Desc, Join, SQL, Store, Unicode
587+from storm.locals import And, Desc, Int, Join, SQL, Store, Unicode
588 from zope.interface import implements
589 from zope.component import getUtility
590 from zope.security.proxy import removeSecurityProxy
591@@ -249,6 +249,7 @@
592 dbName='official_rosetta', notNull=True, default=False)
593 remote_product = Unicode(
594 name='remote_product', allow_none=True, default=None)
595+ max_bug_heat = Int()
596
597 def _getMilestoneCondition(self):
598 """See `HasMilestonesMixin`."""
599
600=== modified file 'lib/lp/registry/model/productseries.py'
601--- lib/lp/registry/model/productseries.py 2009-12-13 11:55:40 +0000
602+++ lib/lp/registry/model/productseries.py 2010-02-26 05:16:22 +0000
603@@ -161,6 +161,11 @@
604 return "%s/%s" % (self.product.name, self.name)
605
606 @property
607+ def max_bug_heat(self):
608+ """See `IHasBugs`."""
609+ return self.product.max_bug_heat
610+
611+ @property
612 def drivers(self):
613 """See IProductSeries."""
614 drivers = set()
615
616=== modified file 'lib/lp/registry/model/project.py'
617--- lib/lp/registry/model/project.py 2010-02-17 11:19:42 +0000
618+++ lib/lp/registry/model/project.py 2010-02-26 05:16:22 +0000
619@@ -17,6 +17,7 @@
620 from sqlobject import (
621 AND, ForeignKey, StringCol, BoolCol, SQLObjectNotFound)
622 from storm.expr import And, In, SQL
623+from storm.locals import Int
624 from storm.store import Store
625
626 from canonical.database.sqlbase import SQLBase, sqlvalues, quote
627@@ -121,6 +122,7 @@
628 foreignKey="BugTracker", dbName="bugtracker", notNull=False,
629 default=None)
630 bug_reporting_guidelines = StringCol(default=None)
631+ max_bug_heat = Int()
632
633 # convenient joins
634
635
636=== modified file 'lib/lp/registry/model/sourcepackage.py'
637--- lib/lp/registry/model/sourcepackage.py 2010-02-24 13:49:17 +0000
638+++ lib/lp/registry/model/sourcepackage.py 2010-02-26 05:16:22 +0000
639@@ -443,6 +443,11 @@
640 BugTask.sourcepackagename == self.sourcepackagename),
641 user)
642
643+ @property
644+ def max_bug_heat(self):
645+ """See `IHasBugs`."""
646+ return self.distribution_sourcepackage.max_bug_heat
647+
648 def createBug(self, bug_params):
649 """See canonical.launchpad.interfaces.IBugTarget."""
650 # We don't currently support opening a new bug directly on an

Subscribers

People subscribed via source and target branches

to status/vote changes: