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
=== modified file 'database/schema/comments.sql'
--- database/schema/comments.sql 2010-02-23 15:43:49 +0000
+++ database/schema/comments.sql 2010-02-26 05:16:22 +0000
@@ -461,7 +461,7 @@
461461
462COMMENT ON TABLE DistributionSourcePackage IS 'Representing a sourcepackage in a distribution across all distribution series.';462COMMENT ON TABLE DistributionSourcePackage IS 'Representing a sourcepackage in a distribution across all distribution series.';
463COMMENT ON COLUMN DistributionSourcePackage.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on a particular a source package in a distribution.';463COMMENT ON COLUMN DistributionSourcePackage.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on a particular a source package in a distribution.';
464464COMMENT ON COLUMN DistributionSourcePackage.max_bug_heat IS 'The highest heat value across bugs for this source package.';
465465
466-- DistributionSourcePackageCache466-- DistributionSourcePackageCache
467467
@@ -475,7 +475,6 @@
475COMMENT ON COLUMN DistributionSourcePackageCache.changelog IS 'A concatenation of the source package release changelogs for this source package, where the status is not REMOVED.';475COMMENT ON COLUMN DistributionSourcePackageCache.changelog IS 'A concatenation of the source package release changelogs for this source package, where the status is not REMOVED.';
476COMMENT ON COLUMN DistributionSourcePackageCache.archive IS 'The archive where the source is published.';476COMMENT ON COLUMN DistributionSourcePackageCache.archive IS 'The archive where the source is published.';
477477
478
479-- DistroSeriesPackageCache478-- DistroSeriesPackageCache
480479
481COMMENT 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.';480COMMENT 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.';
@@ -612,6 +611,7 @@
612COMMENT 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.';611COMMENT 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.';
613COMMENT ON COLUMN Product.license_approved IS 'The Other/Open Source license has been approved by an administrator.';612COMMENT ON COLUMN Product.license_approved IS 'The Other/Open Source license has been approved by an administrator.';
614COMMENT ON COLUMN Product.remote_product IS 'The ID of this product on its remote bug tracker.';613COMMENT ON COLUMN Product.remote_product IS 'The ID of this product on its remote bug tracker.';
614COMMENT ON COLUMN Product.max_bug_heat IS 'The highest heat value across bugs for this product.';
615615
616-- ProductLicense616-- ProductLicense
617COMMENT ON TABLE ProductLicense IS 'The licenses that cover the software for a product.';617COMMENT ON TABLE ProductLicense IS 'The licenses that cover the software for a product.';
@@ -703,6 +703,7 @@
703COMMENT ON COLUMN Project.logo IS 'The library file alias of a smaller version of this product''s mugshot.';703COMMENT ON COLUMN Project.logo IS 'The library file alias of a smaller version of this product''s mugshot.';
704COMMENT ON COLUMN Project.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on products in this project.';704COMMENT ON COLUMN Project.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on products in this project.';
705COMMENT 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.';705COMMENT 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.';
706COMMENT ON COLUMN Project.max_bug_heat IS 'The highest heat value across bugs for products in this project.';
706707
707-- ProjectRelationship708-- ProjectRelationship
708COMMENT 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").';709COMMENT 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").';
@@ -966,6 +967,7 @@
966COMMENT ON COLUMN Distribution.enable_bug_expiration IS 'Indicates whether automatic bug expiration is enabled.';967COMMENT ON COLUMN Distribution.enable_bug_expiration IS 'Indicates whether automatic bug expiration is enabled.';
967COMMENT ON COLUMN Distribution.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on this distribution.';968COMMENT ON COLUMN Distribution.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on this distribution.';
968COMMENT 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.';969COMMENT 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.';
970COMMENT ON COLUMN Distribution.max_bug_heat IS 'The highest heat value across bugs for this distribution.';
969971
970-- DistroSeries972-- DistroSeries
971973
972974
=== added file 'database/schema/patch-2207-34-0.sql'
--- database/schema/patch-2207-34-0.sql 1970-01-01 00:00:00 +0000
+++ database/schema/patch-2207-34-0.sql 2010-02-26 05:16:22 +0000
@@ -0,0 +1,11 @@
1-- Copyright 2010 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6ALTER TABLE product ADD COLUMN max_bug_heat integer;
7ALTER TABLE project ADD COLUMN max_bug_heat integer;
8ALTER TABLE distribution ADD COLUMN max_bug_heat integer;
9ALTER TABLE distributionsourcepackage ADD COLUMN max_bug_heat integer;
10
11INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 34, 0);
012
=== modified file 'lib/lp/bugs/browser/bug.py'
--- lib/lp/bugs/browser/bug.py 2010-02-02 17:26:53 +0000
+++ lib/lp/bugs/browser/bug.py 2010-02-26 05:16:22 +0000
@@ -10,7 +10,6 @@
10 'BugContextMenu',10 'BugContextMenu',
11 'BugEditView',11 'BugEditView',
12 'BugFacets',12 'BugFacets',
13 'BugHeatView',
14 'BugMarkAsAffectingUserView',13 'BugMarkAsAffectingUserView',
15 'BugMarkAsDuplicateView',14 'BugMarkAsDuplicateView',
16 'BugNavigation',15 'BugNavigation',
@@ -23,13 +22,11 @@
23 'BugWithoutContextView',22 'BugWithoutContextView',
24 'DeprecatedAssignedBugsView',23 'DeprecatedAssignedBugsView',
25 'MaloneView',24 'MaloneView',
26 'MAX_HEAT',
27 ]25 ]
2826
29from datetime import datetime, timedelta27from datetime import datetime, timedelta
30from email.MIMEMultipart import MIMEMultipart28from email.MIMEMultipart import MIMEMultipart
31from email.MIMEText import MIMEText29from email.MIMEText import MIMEText
32from math import floor
33import re30import re
3431
35import pytz32import pytz
@@ -77,10 +74,6 @@
77from canonical.widgets.bug import BugTagsWidget74from canonical.widgets.bug import BugTagsWidget
78from canonical.widgets.project import ProjectScopeWidget75from canonical.widgets.project import ProjectScopeWidget
7976
80# Constant for the maximum bug heat we'll use for converting
81# IBug.heat to ratio. In the future this should come from the DB.
82# The value must be a float
83MAX_HEAT = 5000.0
8477
85class BugNavigation(Navigation):78class BugNavigation(Navigation):
86 """Navigation for the `IBug`."""79 """Navigation for the `IBug`."""
@@ -969,18 +962,3 @@
969 return html.encode('utf-8')962 return html.encode('utf-8')
970 return renderer963 return renderer
971964
972
973class BugHeatView(LaunchpadView):
974 """View for rendering the graphical (HTML) representation of bug heat."""
975
976 def __call__(self):
977 """Render the bug heat representation."""
978 heat_ratio = floor((self.context.heat / MAX_HEAT) * 4)
979 html = '<span>'
980 for flame in range(1, 5):
981 if flame <= heat_ratio:
982 html += '<img src="/@@/flame-icon" />'
983 else:
984 html += '<img src="/@@/flame-bw-icon" />'
985 html += '</span>'
986 return html
987965
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2010-02-25 22:00:26 +0000
+++ lib/lp/bugs/browser/bugtask.py 2010-02-26 05:16:22 +0000
@@ -33,7 +33,9 @@
33 'BugTaskTextView',33 'BugTaskTextView',
34 'BugTaskView',34 'BugTaskView',
35 'BugTasksAndNominationsView',35 'BugTasksAndNominationsView',
36 'bugtask_heat_html',
36 'BugsBugTaskSearchListingView',37 'BugsBugTaskSearchListingView',
38 'calculate_heat_display',
37 'NominationsReviewTableBatchNavigatorView',39 'NominationsReviewTableBatchNavigatorView',
38 'TextualBugTaskSearchListingView',40 'TextualBugTaskSearchListingView',
39 'get_buglisting_search_filter_url',41 'get_buglisting_search_filter_url',
@@ -49,6 +51,7 @@
49from simplejson import dumps51from simplejson import dumps
50import urllib52import urllib
51from operator import attrgetter, itemgetter53from operator import attrgetter, itemgetter
54from math import floor, log
5255
53from zope import component56from zope import component
54from zope.app.form import CustomWidgetFactory57from zope.app.form import CustomWidgetFactory
@@ -1080,10 +1083,35 @@
1080 @property1083 @property
1081 def bug_heat_html(self):1084 def bug_heat_html(self):
1082 """HTML representation of the bug heat."""1085 """HTML representation of the bug heat."""
1083 view = getMultiAdapter(1086 return bugtask_heat_html(self.context)
1084 (self.context.bug, self.request),1087
1085 name='+bug-heat')1088
1086 return view()1089def calculate_heat_display(heat, max_bug_heat):
1090 """Calculate the number of heat 'flames' to display."""
1091 heat = float(heat)
1092 max_bug_heat = float(max_bug_heat)
1093 if heat / max_bug_heat < 0.33333:
1094 return 0
1095 if heat / max_bug_heat < 0.66666:
1096 return int(floor((heat / max_bug_heat) * 4))
1097 else:
1098 return int(floor((log(heat) / log(max_bug_heat)) * 4))
1099
1100
1101def bugtask_heat_html(bugtask):
1102 """Render the HTML representing bug heat for a given bugask."""
1103 max_bug_heat = bugtask.target.max_bug_heat
1104 if max_bug_heat is None:
1105 max_bug_heat = 5000
1106 heat_ratio = calculate_heat_display(bugtask.bug.heat, max_bug_heat)
1107 html = '<span>'
1108 for flame in range(1, 5):
1109 if flame <= heat_ratio:
1110 html += '<img src="/@@/flame-icon" />'
1111 else:
1112 html += '<img src="/@@/flame-bw-icon" />'
1113 html += '</span>'
1114 return html
10871115
10881116
1089class BugTaskPortletView:1117class BugTaskPortletView:
@@ -1967,10 +1995,7 @@
1967 @property1995 @property
1968 def bug_heat_html(self):1996 def bug_heat_html(self):
1969 """Returns the bug heat flames HTML."""1997 """Returns the bug heat flames HTML."""
1970 view = getMultiAdapter(1998 return bugtask_heat_html(self.bugtask)
1971 (self.bugtask.bug, self.request),
1972 name='+bug-heat')
1973 return view()
19741999
19752000
1976class BugListingBatchNavigator(TableBatchNavigator):2001class BugListingBatchNavigator(TableBatchNavigator):
19772002
=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml 2010-02-20 13:16:38 +0000
+++ lib/lp/bugs/browser/configure.zcml 2010-02-26 05:16:22 +0000
@@ -1044,11 +1044,6 @@
1044 name="+bug-portlet-subscribers-ids"1044 name="+bug-portlet-subscribers-ids"
1045 class="lp.bugs.browser.bugsubscription.BugPortletSubcribersIds"1045 class="lp.bugs.browser.bugsubscription.BugPortletSubcribersIds"
1046 permission="zope.Public"/>1046 permission="zope.Public"/>
1047 <browser:page
1048 for="lp.bugs.interfaces.bug.IBug"
1049 class="lp.bugs.browser.bug.BugHeatView"
1050 permission="zope.Public"
1051 name="+bug-heat"/>
1052 <browser:navigation1047 <browser:navigation
1053 module="lp.bugs.browser.bug"1048 module="lp.bugs.browser.bug"
1054 classes="1049 classes="
10551050
=== modified file 'lib/lp/bugs/browser/tests/bug-heat-view.txt'
--- lib/lp/bugs/browser/tests/bug-heat-view.txt 2010-01-18 22:11:00 +0000
+++ lib/lp/bugs/browser/tests/bug-heat-view.txt 2010-02-26 05:16:22 +0000
@@ -1,15 +1,16 @@
1= Bug heat view =1= Bug heat view =
22
3Bug heat is represented as four flame icons. The quantity of flames that are3Bug heat is represented as four flame icons. The quantity of flames that are
4coloured is dependent on the value of the heat field. The view BugHeatView4coloured is dependent on the value of the heat field. The function
5is used to render the flames.5bugtask_heat_html is used to render the flames.
66
7 >>> from lp.bugs.browser.bug import MAX_HEAT7 >>> MAX_HEAT = 5000.0
8 >>> from canonical.launchpad.ftests import login, logout8 >>> from canonical.launchpad.ftests import login, logout
9 >>> from canonical.launchpad.webapp import canonical_url
10 >>> from zope.security.proxy import removeSecurityProxy9 >>> from zope.security.proxy import removeSecurityProxy
11 >>> from BeautifulSoup import BeautifulSoup10 >>> from BeautifulSoup import BeautifulSoup
12 >>> def print_flames(html):11 >>> from lp.bugs.browser.bugtask import bugtask_heat_html
12 >>> def print_flames(bugtask):
13 ... html = bugtask_heat_html(bugtask)
13 ... soup = BeautifulSoup(html)14 ... soup = BeautifulSoup(html)
14 ... for img in soup.span.contents:15 ... for img in soup.span.contents:
15 ... print img['src']16 ... print img['src']
@@ -20,9 +21,9 @@
20a heat of half the maximum will result in a display of two coloured flames21a heat of half the maximum will result in a display of two coloured flames
21and two black-and-white flames.22and two black-and-white flames.
2223
24 >>> removeSecurityProxy(bug.default_bugtask.target).max_bug_heat = MAX_HEAT
23 >>> removeSecurityProxy(bug).heat = MAX_HEAT / 225 >>> removeSecurityProxy(bug).heat = MAX_HEAT / 2
24 >>> bug_heat_view = create_initialized_view(bug, name='+bug-heat')26 >>> print_flames(bug.default_bugtask)
25 >>> print_flames(bug_heat_view())
26 /@@/flame-icon27 /@@/flame-icon
27 /@@/flame-icon28 /@@/flame-icon
28 /@@/flame-bw-icon29 /@@/flame-bw-icon
@@ -31,8 +32,7 @@
31A bug with a maximum heat will display all four flames coloured.32A bug with a maximum heat will display all four flames coloured.
3233
33 >>> removeSecurityProxy(bug).heat = MAX_HEAT34 >>> removeSecurityProxy(bug).heat = MAX_HEAT
34 >>> bug_heat_view = create_initialized_view(bug, name='+bug-heat')35 >>> print_flames(bug.default_bugtask)
35 >>> print_flames(bug_heat_view())
36 /@@/flame-icon36 /@@/flame-icon
37 /@@/flame-icon37 /@@/flame-icon
38 /@@/flame-icon38 /@@/flame-icon
@@ -41,11 +41,44 @@
41A heat of less than a quarter of the maximum will display no coloured flames.41A heat of less than a quarter of the maximum will display no coloured flames.
4242
43 >>> removeSecurityProxy(bug).heat = 0.1 * MAX_HEAT43 >>> removeSecurityProxy(bug).heat = 0.1 * MAX_HEAT
44 >>> bug_heat_view = create_initialized_view(bug, name='+bug-heat')44 >>> print_flames(bug.default_bugtask)
45 >>> print_flames(bug_heat_view())
46 /@@/flame-bw-icon45 /@@/flame-bw-icon
47 /@@/flame-bw-icon46 /@@/flame-bw-icon
48 /@@/flame-bw-icon47 /@@/flame-bw-icon
49 /@@/flame-bw-icon48 /@@/flame-bw-icon
5049
51 >>> logout()50 >>> logout()
51
52
53== Scaling Bug Heat ==
54
55To ensure a reasonable proportion of cold and hot bugs, the number used to
56calculate the number of flames to display is not a straight-forward ratio.
57Instead, we transform it by forcing low heat bugs to produce no flames and
58scaling the hottest bugs logarithmically.
59
60 >>> from lp.bugs.browser.bugtask import calculate_heat_display
61 >>> from math import floor
62
63Heat values less than a third of the maximum heat don't produce any flames.
64
65 >>> print int(floor((300.0 / 1000.0) * 4))
66 1
67 >>> print calculate_heat_display(300.0, 1000.0)
68 0
69
70Heat values higher than a third of the max but lower than two thirds are treated
71as a straightforward ratio.
72
73 >>> print int(floor((500.0 / 1000.0) * 4))
74 2
75 >>> print calculate_heat_display(500.0, 1000.0)
76 2
77
78Heat values higher than two thirds of the maximum heat are scaled upwards.
79
80 >>> print int(floor((700.0 / 1000.0) * 4))
81 2
82 >>> print calculate_heat_display(800.0, 1000.0)
83 3
84
5285
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2010-02-23 15:34:15 +0000
+++ lib/lp/bugs/interfaces/bug.py 2010-02-26 05:16:22 +0000
@@ -15,6 +15,7 @@
15 'IBugBecameQuestionEvent',15 'IBugBecameQuestionEvent',
16 'IBugDelta',16 'IBugDelta',
17 'IBugSet',17 'IBugSet',
18 'IFileBugData',
18 'IFrontPageBugAddForm',19 'IFrontPageBugAddForm',
19 'IProjectGroupBugAddForm',20 'IProjectGroupBugAddForm',
20 'InvalidBugTargetType',21 'InvalidBugTargetType',
2122
=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
--- lib/lp/bugs/interfaces/bugtarget.py 2010-02-25 22:00:26 +0000
+++ lib/lp/bugs/interfaces/bugtarget.py 2010-02-26 05:16:22 +0000
@@ -61,6 +61,8 @@
61 description=_("The list of bug tags defined as official."),61 description=_("The list of bug tags defined as official."),
62 value_type=Tag(),62 value_type=Tag(),
63 readonly=True))63 readonly=True))
64 max_bug_heat = Attribute(
65 "The current highest bug heat value for this target.")
6466
65 @call_with(search_params=None, user=REQUEST_USER)67 @call_with(search_params=None, user=REQUEST_USER)
66 @operation_parameters(68 @operation_parameters(
@@ -219,6 +221,9 @@
219 None, all statuses will be included.221 None, all statuses will be included.
220 """222 """
221223
224 def setMaxBugHeat(heat):
225 """Set the max_bug_heat for this context."""
226
222227
223class IBugTarget(IHasBugs):228class IBugTarget(IHasBugs):
224 """An entity on which a bug can be reported.229 """An entity on which a bug can be reported.
225230
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2010-02-19 16:39:25 +0000
+++ lib/lp/bugs/model/bug.py 2010-02-26 05:16:22 +0000
@@ -13,6 +13,7 @@
13 'BugBecameQuestionEvent',13 'BugBecameQuestionEvent',
14 'BugSet',14 'BugSet',
15 'BugTag',15 'BugTag',
16 'FileBugData',
16 'get_bug_tags',17 'get_bug_tags',
17 'get_bug_tags_open_count',18 'get_bug_tags_open_count',
18 ]19 ]
1920
=== modified file 'lib/lp/bugs/model/bugtarget.py'
--- lib/lp/bugs/model/bugtarget.py 2010-02-25 22:00:26 +0000
+++ lib/lp/bugs/model/bugtarget.py 2010-02-26 05:16:22 +0000
@@ -25,7 +25,10 @@
25from canonical.launchpad.webapp.interfaces import ILaunchBag25from canonical.launchpad.webapp.interfaces import ILaunchBag
26from lp.bugs.interfaces.bugtarget import IOfficialBugTag26from lp.bugs.interfaces.bugtarget import IOfficialBugTag
27from lp.registry.interfaces.distribution import IDistribution27from lp.registry.interfaces.distribution import IDistribution
28from lp.registry.interfaces.distributionsourcepackage import (
29 IDistributionSourcePackage)
28from lp.registry.interfaces.product import IProduct30from lp.registry.interfaces.product import IProduct
31from lp.registry.interfaces.projectgroup import IProjectGroup
29from lp.bugs.interfaces.bugtask import (32from lp.bugs.interfaces.bugtask import (
30 BugTagsSearchCombinator, BugTaskImportance, BugTaskSearchParams,33 BugTagsSearchCombinator, BugTaskImportance, BugTaskSearchParams,
31 BugTaskStatus, RESOLVED_BUGTASK_STATUSES, UNRESOLVED_BUGTASK_STATUSES)34 BugTaskStatus, RESOLVED_BUGTASK_STATUSES, UNRESOLVED_BUGTASK_STATUSES)
@@ -156,6 +159,17 @@
156159
157 return self.searchTasks(all_tasks_query)160 return self.searchTasks(all_tasks_query)
158161
162 def setMaxBugHeat(self, heat):
163 """See `IHasBugs`."""
164 if (IDistribution.providedBy(self)
165 or IProduct.providedBy(self)
166 or IProjectGroup.providedBy(self)
167 or IDistributionSourcePackage.providedBy(self)):
168 # Only objects that don't delegate have a setter.
169 self.max_bug_heat = heat
170 else:
171 raise NotImplementedError
172
159 def getBugCounts(self, user, statuses=None):173 def getBugCounts(self, user, statuses=None):
160 """See `IHasBugs`."""174 """See `IHasBugs`."""
161 if statuses is None:175 if statuses is None:
162176
=== modified file 'lib/lp/bugs/tests/test_bugheat.py'
--- lib/lp/bugs/tests/test_bugheat.py 2010-01-21 22:59:32 +0000
+++ lib/lp/bugs/tests/test_bugheat.py 2010-02-26 05:16:22 +0000
@@ -20,6 +20,7 @@
20from lp.bugs.model.bugheat import CalculateBugHeatJob20from lp.bugs.model.bugheat import CalculateBugHeatJob
21from lp.bugs.scripts.bugheat import BugHeatCalculator21from lp.bugs.scripts.bugheat import BugHeatCalculator
22from lp.testing import TestCaseWithFactory22from lp.testing import TestCaseWithFactory
23from lp.testing.factory import LaunchpadObjectFactory
2324
2425
25class CalculateBugHeatJobTestCase(TestCaseWithFactory):26class CalculateBugHeatJobTestCase(TestCaseWithFactory):
@@ -214,5 +215,86 @@
214 self.assertEqual(bug_job.bug, new_bug)215 self.assertEqual(bug_job.bug, new_bug)
215216
216217
218class MaxHeatByTargetBase:
219 """Base class for testing a bug target's max_bug_heat attribute."""
220
221 layer = LaunchpadZopelessLayer
222
223 factory = LaunchpadObjectFactory()
224
225 # The target to test.
226 target = None
227
228 # Does the target have a set method?
229 delegates_setter = False
230
231 def test_target_max_bug_heat_default(self):
232 self.assertEqual(self.target.max_bug_heat, None)
233
234 def test_set_target_max_bug_heat(self):
235 if self.delegates_setter:
236 self.assertRaises(
237 NotImplementedError, self.target.setMaxBugHeat, 1000)
238 else:
239 self.target.setMaxBugHeat(1000)
240 self.assertEqual(self.target.max_bug_heat, 1000)
241
242
243class ProjectMaxHeatByTargetTest(MaxHeatByTargetBase, unittest.TestCase):
244 """Ensure a project has a max_bug_heat value that can be set."""
245
246 def setUp(self):
247 self.target = self.factory.makeProduct()
248
249
250class DistributionMaxHeatByTargetTest(MaxHeatByTargetBase, unittest.TestCase):
251 """Ensure a distribution has a max_bug_heat value that can be set."""
252
253 def setUp(self):
254 self.target = self.factory.makeDistribution()
255
256
257class DistributionSourcePackageMaxHeatByTargetTest(
258 MaxHeatByTargetBase, unittest.TestCase):
259 """Ensure distro source package has max_bug_heat value that can be set."""
260
261 def setUp(self):
262 self.target = self.factory.makeDistributionSourcePackage()
263
264
265class SourcePackageMaxHeatByTargetTest(
266 MaxHeatByTargetBase, unittest.TestCase):
267 """Ensure a source package has a max_bug_heat value that can be set."""
268
269 def setUp(self):
270 self.target = self.factory.makeSourcePackage()
271 self.delegates_setter = True
272
273
274class ProductSeriesMaxHeatByTargetTest(
275 MaxHeatByTargetBase, unittest.TestCase):
276 """Ensure a product series has a max_bug_heat value that can be set."""
277
278 def setUp(self):
279 self.target = self.factory.makeProductSeries()
280 self.delegates_setter = True
281
282
283class DistroSeriesMaxHeatByTargetTest(
284 MaxHeatByTargetBase, unittest.TestCase):
285 """Ensure a distro series has a max_bug_heat value that can be set."""
286
287 def setUp(self):
288 self.target = self.factory.makeDistroSeries()
289 self.delegates_setter = True
290
291
292class ProjectGroupMaxHeatByTargetTest(
293 MaxHeatByTargetBase, unittest.TestCase):
294 """Ensure a project group has a max_bug_heat value that can be set."""
295
296 def setUp(self):
297 self.target = self.factory.makeProject()
298
217def test_suite():299def test_suite():
218 return unittest.TestLoader().loadTestsFromName(__name__)300 return unittest.TestLoader().loadTestsFromName(__name__)
219301
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2010-02-18 09:04:51 +0000
+++ lib/lp/registry/configure.zcml 2010-02-26 05:16:22 +0000
@@ -407,7 +407,8 @@
407 official_bug_tags407 official_bug_tags
408 findRelatedArchives408 findRelatedArchives
409 findRelatedArchivePublications409 findRelatedArchivePublications
410 userHasBugSubscriptions"/>410 userHasBugSubscriptions
411 max_bug_heat"/>
411 <require412 <require
412 permission="launchpad.AnyPerson"413 permission="launchpad.AnyPerson"
413 attributes="414 attributes="
414415
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2010-02-16 15:08:03 +0000
+++ lib/lp/registry/model/distribution.py 2010-02-26 05:16:22 +0000
@@ -13,7 +13,7 @@
1313
14from sqlobject import BoolCol, ForeignKey, SQLObjectNotFound, StringCol14from sqlobject import BoolCol, ForeignKey, SQLObjectNotFound, StringCol
15from sqlobject.sqlbuilder import SQLConstant15from sqlobject.sqlbuilder import SQLConstant
16from storm.locals import Desc, In, Join, SQL16from storm.locals import Desc, In, Int, Join, SQL
17from storm.store import Store17from storm.store import Store
18from zope.component import getUtility18from zope.component import getUtility
19from zope.interface import alsoProvides, implements19from zope.interface import alsoProvides, implements
@@ -34,7 +34,6 @@
34from lp.soyuz.model.binarypackagename import BinaryPackageName34from lp.soyuz.model.binarypackagename import BinaryPackageName
35from lp.soyuz.model.binarypackagerelease import (35from lp.soyuz.model.binarypackagerelease import (
36 BinaryPackageRelease)36 BinaryPackageRelease)
37from lp.bugs.interfaces.bugattachment import BugAttachmentType
38from lp.bugs.model.bug import (37from lp.bugs.model.bug import (
39 BugSet, get_bug_tags, get_bug_tags_open_count)38 BugSet, get_bug_tags, get_bug_tags_open_count)
40from lp.bugs.model.bugtarget import (39from lp.bugs.model.bugtarget import (
@@ -179,6 +178,7 @@
179 official_blueprints = BoolCol(dbName='official_blueprints', notNull=True,178 official_blueprints = BoolCol(dbName='official_blueprints', notNull=True,
180 default=False)179 default=False)
181 active = True # Required by IPillar interface.180 active = True # Required by IPillar interface.
181 max_bug_heat = Int()
182182
183 def __repr__(self):183 def __repr__(self):
184 return "<%s '%s' (%s)>" % (184 return "<%s '%s' (%s)>" % (
185185
=== modified file 'lib/lp/registry/model/distributionsourcepackage.py'
--- lib/lp/registry/model/distributionsourcepackage.py 2010-01-06 12:03:42 +0000
+++ lib/lp/registry/model/distributionsourcepackage.py 2010-02-26 05:16:22 +0000
@@ -142,6 +142,26 @@
142 _get_bug_reporting_guidelines,142 _get_bug_reporting_guidelines,
143 _set_bug_reporting_guidelines)143 _set_bug_reporting_guidelines)
144144
145 def _get_max_bug_heat(self):
146 """See `IHasBugs`."""
147 dsp_in_db = self._self_in_database
148 if dsp_in_db is None:
149 return None
150 else:
151 return dsp_in_db.max_bug_heat
152
153 def _set_max_bug_heat(self, value):
154 """See `IHasBugs`."""
155 dsp_in_db = self._self_in_database
156 if dsp_in_db is None:
157 dsp_in_db = DistributionSourcePackageInDatabase()
158 dsp_in_db.sourcepackagename = self.sourcepackagename
159 dsp_in_db.distribution = self.distribution
160 Store.of(self.distribution).add(dsp_in_db)
161 dsp_in_db.max_bug_heat = value
162
163 max_bug_heat = property(_get_max_bug_heat, _set_max_bug_heat)
164
145 @property165 @property
146 def latest_overall_publication(self):166 def latest_overall_publication(self):
147 """See `IDistributionSourcePackage`."""167 """See `IDistributionSourcePackage`."""
@@ -474,3 +494,6 @@
474 sourcepackagename_id, 'SourcePackageName.id')494 sourcepackagename_id, 'SourcePackageName.id')
475495
476 bug_reporting_guidelines = Unicode()496 bug_reporting_guidelines = Unicode()
497
498 max_bug_heat = Int()
499
477500
=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2010-02-22 20:58:16 +0000
+++ lib/lp/registry/model/distroseries.py 2010-02-26 05:16:22 +0000
@@ -624,6 +624,11 @@
624 return self.fullseriesname624 return self.fullseriesname
625625
626 @property626 @property
627 def max_bug_heat(self):
628 """See `IHasBugs`."""
629 return self.distribution.max_bug_heat
630
631 @property
627 def last_full_language_pack_exported(self):632 def last_full_language_pack_exported(self):
628 return LanguagePack.selectFirstBy(633 return LanguagePack.selectFirstBy(
629 distroseries=self, type=LanguagePackType.FULL,634 distroseries=self, type=LanguagePackType.FULL,
630635
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2010-02-09 01:31:05 +0000
+++ lib/lp/registry/model/product.py 2010-02-26 05:16:22 +0000
@@ -18,7 +18,7 @@
18import pytz18import pytz
19from sqlobject import (19from sqlobject import (
20 BoolCol, ForeignKey, SQLMultipleJoin, SQLObjectNotFound, StringCol)20 BoolCol, ForeignKey, SQLMultipleJoin, SQLObjectNotFound, StringCol)
21from storm.locals import And, Desc, Join, SQL, Store, Unicode21from storm.locals import And, Desc, Int, Join, SQL, Store, Unicode
22from zope.interface import implements22from zope.interface import implements
23from zope.component import getUtility23from zope.component import getUtility
24from zope.security.proxy import removeSecurityProxy24from zope.security.proxy import removeSecurityProxy
@@ -249,6 +249,7 @@
249 dbName='official_rosetta', notNull=True, default=False)249 dbName='official_rosetta', notNull=True, default=False)
250 remote_product = Unicode(250 remote_product = Unicode(
251 name='remote_product', allow_none=True, default=None)251 name='remote_product', allow_none=True, default=None)
252 max_bug_heat = Int()
252253
253 def _getMilestoneCondition(self):254 def _getMilestoneCondition(self):
254 """See `HasMilestonesMixin`."""255 """See `HasMilestonesMixin`."""
255256
=== modified file 'lib/lp/registry/model/productseries.py'
--- lib/lp/registry/model/productseries.py 2009-12-13 11:55:40 +0000
+++ lib/lp/registry/model/productseries.py 2010-02-26 05:16:22 +0000
@@ -161,6 +161,11 @@
161 return "%s/%s" % (self.product.name, self.name)161 return "%s/%s" % (self.product.name, self.name)
162162
163 @property163 @property
164 def max_bug_heat(self):
165 """See `IHasBugs`."""
166 return self.product.max_bug_heat
167
168 @property
164 def drivers(self):169 def drivers(self):
165 """See IProductSeries."""170 """See IProductSeries."""
166 drivers = set()171 drivers = set()
167172
=== modified file 'lib/lp/registry/model/project.py'
--- lib/lp/registry/model/project.py 2010-02-17 11:19:42 +0000
+++ lib/lp/registry/model/project.py 2010-02-26 05:16:22 +0000
@@ -17,6 +17,7 @@
17from sqlobject import (17from sqlobject import (
18 AND, ForeignKey, StringCol, BoolCol, SQLObjectNotFound)18 AND, ForeignKey, StringCol, BoolCol, SQLObjectNotFound)
19from storm.expr import And, In, SQL19from storm.expr import And, In, SQL
20from storm.locals import Int
20from storm.store import Store21from storm.store import Store
2122
22from canonical.database.sqlbase import SQLBase, sqlvalues, quote23from canonical.database.sqlbase import SQLBase, sqlvalues, quote
@@ -121,6 +122,7 @@
121 foreignKey="BugTracker", dbName="bugtracker", notNull=False,122 foreignKey="BugTracker", dbName="bugtracker", notNull=False,
122 default=None)123 default=None)
123 bug_reporting_guidelines = StringCol(default=None)124 bug_reporting_guidelines = StringCol(default=None)
125 max_bug_heat = Int()
124126
125 # convenient joins127 # convenient joins
126128
127129
=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py 2010-02-24 13:49:17 +0000
+++ lib/lp/registry/model/sourcepackage.py 2010-02-26 05:16:22 +0000
@@ -443,6 +443,11 @@
443 BugTask.sourcepackagename == self.sourcepackagename),443 BugTask.sourcepackagename == self.sourcepackagename),
444 user)444 user)
445445
446 @property
447 def max_bug_heat(self):
448 """See `IHasBugs`."""
449 return self.distribution_sourcepackage.max_bug_heat
450
446 def createBug(self, bug_params):451 def createBug(self, bug_params):
447 """See canonical.launchpad.interfaces.IBugTarget."""452 """See canonical.launchpad.interfaces.IBugTarget."""
448 # We don't currently support opening a new bug directly on an453 # We don't currently support opening a new bug directly on an

Subscribers

People subscribed via source and target branches

to status/vote changes: