Merge lp://staging/~deryck/launchpad/hot-bugs-list-515232 into lp://staging/launchpad

Proposed by Deryck Hodge
Status: Merged
Approved by: Gavin Panella
Approved revision: not available
Merged at revision: not available
Proposed branch: lp://staging/~deryck/launchpad/hot-bugs-list-515232
Merge into: lp://staging/launchpad
Diff against target: 256 lines (+104/-37)
4 files modified
lib/lp/bugs/browser/bugtarget.py (+24/-0)
lib/lp/bugs/browser/bugtask.py (+9/-14)
lib/lp/bugs/stories/bugs/xx-product-bugs-page.txt (+61/-18)
lib/lp/bugs/templates/bugtarget-bugs.pt (+10/-5)
To merge this branch: bzr merge lp://staging/~deryck/launchpad/hot-bugs-list-515232
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+19025@code.staging.launchpad.net

Commit message

Create a true hot bugs list for a bugs home page. Also, provide a link to the full list of hot bugs. (The original version of this was backed out for performance reasons.)

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

This branch converts a bugtask.py's hot_bugtasks method to a hot_bugs
method on a bug target. The effect of this change is that the bugs home
page for a project will no longer feature the most recently changed
bugs. The home page will now show the top 10 hottest bugs.

This also fixes bug 442170, bug 77701, and bug 515232.

== Implementation details ==

One version of this landed during week 3 last cycle, but the branch was
reverted last minute due to timeout issues. After much playing on
staging with queries (and after talking with Björn), I feel confident
setting a limit and not doing a secondary sort on -datecreated will fix
the timeouts we had.

I also took the opportunity to clean up some lint and add a link to the
rest of the hot bugs list. Because I added this link, I needed a way to
check that more than 10 hot bugs exist. I made the hot_bugs list a dict
with a flag for has_more_bugs, which allows doing this in one method,
too, avoiding having to look up the hot bugs list again.

== Tests ==

Test with:

./bin/test -cvvt xx-product-bugs-page.txt

== Demo and Q/A ==

To QA, visit any bugs home page (e.g.
https://bugs.launchpad.net/malone/) and confirm that the 10 bugs shown
are the top ten when you follow the link to the full hot bugs list.

= Launchpad lint =

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

Linting changed files:
  lib/lp/bugs/browser/bugtarget.py
  lib/lp/bugs/browser/bugtask.py
  lib/lp/bugs/templates/bugtarget-bugs.pt
  lib/lp/bugs/stories/bugs/xx-product-bugs-page.txt

Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (13.2 KiB)

Hi Deryck,

Woo! This is great :)

There are a few issues that need fixing up I think, but nothing that's
going to pose problems.

Gavin.

> === modified file 'lib/lp/bugs/browser/bugtarget.py'
> --- lib/lp/bugs/browser/bugtarget.py 2010-01-29 19:00:47 +0000
> +++ lib/lp/bugs/browser/bugtarget.py 2010-02-10 16:43:36 +0000
> @@ -40,6 +40,7 @@
> from canonical.config import config
> from lp.bugs.browser.bugtask import BugTaskSearchListingView
> from lp.bugs.interfaces.bug import IBug
> +from lp.bugs.interfaces.bugtask import BugTaskSearchParams
> from canonical.launchpad.browser.feeds import (
> BugFeedLink, BugTargetLatestBugsFeedLink, FeedsMixin,
> PersonLatestBugsFeedLink)
> @@ -55,6 +56,7 @@
> from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
> from canonical.launchpad.interfaces.temporaryblobstorage import (
> ITemporaryStorageManager)
> +from canonical.launchpad.searchbuilder import any
> from canonical.launchpad.webapp import urlappend
> from canonical.launchpad.webapp.breadcrumb import Breadcrumb
> from canonical.launchpad.webapp.interfaces import ILaunchBag, NotFoundError
> @@ -1264,6 +1266,28 @@
> else:
> return 'None specified'
>
> + @property
> + def hot_bugs(self):

It's not returning a straight list of hot bugs, so consider renaming
this to hot_bugtasks_info or something like that?

> + """Return a dict of the 10 hottest tasks and a has_more_bugs flag."""
> + has_more_bugs = False
> + params = BugTaskSearchParams(
> + orderby='-heat', omit_dupes=True,
> + user=self.user, status=any(*UNRESOLVED_BUGTASK_STATUSES))
> + # Use 4x as many tasks as bugs that are needed to improve performance.
> + bugtasks = self.context.searchTasks(params)[:40]
> + hot_bugs = []

This will contain bugtasks, so maybe rename it?

> + count = 0

len(hot_bugs) should contain the same number, and is authoritative, so
consider dropping count.

> + for task in bugtasks:
> + # Ensure we only represent a bug once in the list.
> + if task.bug not in [hot_task.bug for hot_task in hot_bugs]:

YOU'RE BURNING MY EYES!

:)

Actually, seeing as it's never going to create a temporary list of
more than 10 bugtasks, I guess it's not crazy. Still, to code
defensively, I think it would be better to accumulate the bugtask's
bugs in a set, and use that to test for membership.

> + if count < 10:
> + hot_bugs.append(task)
> + count += 1
> + else:
> + has_more_bugs = True
> + break
> + return {'has_more_bugs': has_more_bugs, 'bugtasks': hot_bugs}
> +
>
> class BugTargetBugTagsView(LaunchpadView):
> """Helper methods for rendering the bug tags portlet."""
>
> === modified file 'lib/lp/bugs/browser/bugtask.py'
> --- lib/lp/bugs/browser/bugtask.py 2010-01-26 14:22:41 +0000
> +++ lib/lp/bugs/browser/bugtask.py 2010-02-10 16:43:36 +0000
> @@ -2206,9 +2206,13 @@
> distrosourcepackage_context or sourcepackage_context):
> return ["id", "summary", "importance", "status",...

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

Hi, Gavin.

Thanks for the review! I knew I was doing some dumb things in there, but I was feeling a bit too close to it after so many iterations.

You had good recommendations all the way around, which I've implemented, except for a couple of them. I didn't change the test -- yes, it's fragile, but the whole point of the page test is to show the ordering of bugs by heat. Also, I needed at least status in the monster-long query to "show all bugs." I trimmed what I could, but it's still a bit long.

Otherwise, I think I took care of all your points.

Cheers,
deryck

Revision history for this message
Gavin Panella (allenap) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py 2010-02-10 23:14:56 +0000
+++ lib/lp/bugs/browser/bugtarget.py 2010-02-12 12:39:21 +0000
@@ -40,6 +40,7 @@
40from canonical.config import config40from canonical.config import config
41from lp.bugs.browser.bugtask import BugTaskSearchListingView41from lp.bugs.browser.bugtask import BugTaskSearchListingView
42from lp.bugs.interfaces.bug import IBug42from lp.bugs.interfaces.bug import IBug
43from lp.bugs.interfaces.bugtask import BugTaskSearchParams
43from canonical.launchpad.browser.feeds import (44from canonical.launchpad.browser.feeds import (
44 BugFeedLink, BugTargetLatestBugsFeedLink, FeedsMixin,45 BugFeedLink, BugTargetLatestBugsFeedLink, FeedsMixin,
45 PersonLatestBugsFeedLink)46 PersonLatestBugsFeedLink)
@@ -55,6 +56,7 @@
55from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities56from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
56from canonical.launchpad.interfaces.temporaryblobstorage import (57from canonical.launchpad.interfaces.temporaryblobstorage import (
57 ITemporaryStorageManager)58 ITemporaryStorageManager)
59from canonical.launchpad.searchbuilder import any
58from canonical.launchpad.webapp import urlappend60from canonical.launchpad.webapp import urlappend
59from canonical.launchpad.webapp.breadcrumb import Breadcrumb61from canonical.launchpad.webapp.breadcrumb import Breadcrumb
60from canonical.launchpad.webapp.interfaces import ILaunchBag, NotFoundError62from canonical.launchpad.webapp.interfaces import ILaunchBag, NotFoundError
@@ -1266,6 +1268,28 @@
1266 else:1268 else:
1267 return 'None specified'1269 return 'None specified'
12681270
1271 @cachedproperty
1272 def hot_bugs_info(self):
1273 """Return a dict of the 10 hottest tasks and a has_more_bugs flag."""
1274 has_more_bugs = False
1275 params = BugTaskSearchParams(
1276 orderby='-heat', omit_dupes=True,
1277 user=self.user, status=any(*UNRESOLVED_BUGTASK_STATUSES))
1278 # Use 4x as many tasks as bugs that are needed to improve performance.
1279 bugtasks = self.context.searchTasks(params)[:40]
1280 hot_bugtasks = []
1281 hot_bugs = []
1282 for task in bugtasks:
1283 # Use hot_bugs list to ensure a bug is only listed once.
1284 if task.bug not in hot_bugs:
1285 if len(hot_bugtasks) < 10:
1286 hot_bugtasks.append(task)
1287 hot_bugs.append(task.bug)
1288 else:
1289 has_more_bugs = True
1290 break
1291 return {'has_more_bugs': has_more_bugs, 'bugtasks': hot_bugtasks}
1292
12691293
1270class BugTargetBugTagsView(LaunchpadView):1294class BugTargetBugTagsView(LaunchpadView):
1271 """Helper methods for rendering the bug tags portlet."""1295 """Helper methods for rendering the bug tags portlet."""
12721296
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2010-01-26 14:22:41 +0000
+++ lib/lp/bugs/browser/bugtask.py 2010-02-12 12:39:21 +0000
@@ -2206,9 +2206,13 @@
2206 distrosourcepackage_context or sourcepackage_context):2206 distrosourcepackage_context or sourcepackage_context):
2207 return ["id", "summary", "importance", "status", "heat"]2207 return ["id", "summary", "importance", "status", "heat"]
2208 elif distribution_context or distroseries_context:2208 elif distribution_context or distroseries_context:
2209 return ["id", "summary", "packagename", "importance", "status", "heat"]2209 return [
2210 "id", "summary", "packagename", "importance", "status",
2211 "heat"]
2210 elif project_context:2212 elif project_context:
2211 return ["id", "summary", "productname", "importance", "status", "heat"]2213 return [
2214 "id", "summary", "productname", "importance", "status",
2215 "heat"]
2212 else:2216 else:
2213 raise AssertionError(2217 raise AssertionError(
2214 "Unrecognized context; don't know which report "2218 "Unrecognized context; don't know which report "
@@ -2739,15 +2743,6 @@
2739 return IDistributionSourcePackage(self.context, None)2743 return IDistributionSourcePackage(self.context, None)
27402744
2741 @property2745 @property
2742 def hot_bugtasks(self):
2743 """Return the 10 most recently updated bugtasks for this target."""
2744 params = BugTaskSearchParams(
2745 orderby="-date_last_updated", omit_dupes=True, user=self.user,
2746 status=any(*UNRESOLVED_BUGTASK_STATUSES))
2747 search = self.context.searchTasks(params)
2748 return list(search[:10])
2749
2750 @property
2751 def addquestion_url(self):2746 def addquestion_url(self):
2752 """Return the URL for the +addquestion view for the context."""2747 """Return the URL for the +addquestion view for the context."""
2753 if IQuestionTarget.providedBy(self.context):2748 if IQuestionTarget.providedBy(self.context):
@@ -2803,8 +2798,7 @@
2803 if bug_listing_item.review_action_widget is not None]2798 if bug_listing_item.review_action_widget is not None]
2804 self.widgets = formlib.form.Widgets(widgets_list, len(self.prefix)+1)2799 self.widgets = formlib.form.Widgets(widgets_list, len(self.prefix)+1)
28052800
2806 @action('Save changes', name='submit',2801 @action('Save changes', name='submit', condition=canApproveNominations)
2807 condition=canApproveNominations)
2808 def submit_action(self, action, data):2802 def submit_action(self, action, data):
2809 """Accept/Decline bug nominations."""2803 """Accept/Decline bug nominations."""
2810 accepted = declined = 02804 accepted = declined = 0
@@ -3592,7 +3586,8 @@
3592 """Show the columns that summarise expirable bugs."""3586 """Show the columns that summarise expirable bugs."""
3593 if (IDistribution.providedBy(self.context)3587 if (IDistribution.providedBy(self.context)
3594 or IDistroSeries.providedBy(self.context)):3588 or IDistroSeries.providedBy(self.context)):
3595 return ['id', 'summary', 'packagename', 'date_last_updated', 'heat']3589 return [
3590 'id', 'summary', 'packagename', 'date_last_updated', 'heat']
3596 else:3591 else:
3597 return ['id', 'summary', 'date_last_updated', 'heat']3592 return ['id', 'summary', 'date_last_updated', 'heat']
35983593
35993594
=== modified file 'lib/lp/bugs/stories/bugs/xx-product-bugs-page.txt'
--- lib/lp/bugs/stories/bugs/xx-product-bugs-page.txt 2010-01-26 14:22:41 +0000
+++ lib/lp/bugs/stories/bugs/xx-product-bugs-page.txt 2010-02-12 12:39:21 +0000
@@ -131,38 +131,81 @@
131131
132== Hot Bugs ==132== Hot Bugs ==
133133
134A listing of the 10 'hottest' bugs (currently simply the bugs most134A listing of the 10 'hottest' bugs is displayed to allow a quick
135recently touched) is displayed to allow a quick overview of the project.135overview of the project.
136
137To demonstrate this, we create 10 bugs and adjust their heat values manually.
138
139 >>> from zope.component import getUtility
140 >>> from canonical.launchpad.ftests import login, logout
141 >>> from lp.registry.interfaces.product import IProductSet
142 >>> import transaction
143 >>> login('foo.bar@canonical.com')
144 >>> firefox = getUtility(IProductSet).getByName("firefox")
145 >>> heat_values = [0, 400, 200, 600, 100, 50, 50, 50, 50, 50, 50, 50]
146 >>> for count in range(1, 11):
147 ... summary = 'Summary for new bug %d' % count
148 ... bug = factory.makeBug(title=summary, product=firefox)
149 ... bug.setHeat(heat_values[count])
150 >>> transaction.commit()
151 >>> logout()
152
136For each bug we have the number, title, status, importance and the time153For each bug we have the number, title, status, importance and the time
137since the last update.154since the last update.
138155
139 >>> anon_browser.open('http://bugs.launchpad.dev/firefox')156 >>> anon_browser.open('http://bugs.launchpad.dev/firefox')
140 >>> print extract_text(157 >>> print extract_text(
141 ... find_tag_by_id(anon_browser.contents, 'hot-bugs'))158 ... find_tag_by_id(anon_browser.contents, 'hot-bugs'))
142 Summary Status Importance Last changed159 Summary Status Importance Last changed
143 #5 Firefox install... New Critical on 2006-07-14160 #18 Summary for new bug 3 New Undecided ...
144 #4 Reflow problems... New Medium on 2006-07-14161 #16 Summary for new bug 1 New Undecided ...
145 #1 Firefox does no... New Low on 2006-05-19162 #17 Summary for new bug 2 New Undecided ...
146163 #19 Summary for new bug 4 New Undecided ...
147164 #20 Summary for new bug 5 New Undecided ...
148Fix released bugs are not shown. We demonstrate this by setting the bugtask165 #21 Summary for new bug 6 New Undecided ...
149for bug 4 to be "Fix released".166 #22 Summary for new bug 7 New Undecided ...
150167 #23 Summary for new bug 8 New Undecided ...
151 >>> from zope.component import getUtility168 #24 Summary for new bug 9 New Undecided ...
169 #25 Summary for new bug 10 New Undecided ...
170
171
172Fix Released bugs are not shown. We demonstrate this by setting the bugtask
173for bug 18 to be "Fix Released".
174
152 >>> from lp.bugs.interfaces.bug import BugTaskStatus, IBugSet175 >>> from lp.bugs.interfaces.bug import BugTaskStatus, IBugSet
153 >>> from lp.registry.interfaces.person import IPersonSet176 >>> from lp.registry.interfaces.person import IPersonSet
154 >>> login('foo.bar@canonical.com')177 >>> login('foo.bar@canonical.com')
155 >>> bug_4 = getUtility(IBugSet).get(4)178 >>> bug_18 = getUtility(IBugSet).get(18)
156 >>> project_owner = getUtility(IPersonSet).getByName('name12')179 >>> project_owner = getUtility(IPersonSet).getByName('name12')
157 >>> bug_4.bugtasks[0].transitionToStatus(180 >>> bug_18.bugtasks[0].transitionToStatus(
158 ... BugTaskStatus.FIXRELEASED, project_owner)181 ... BugTaskStatus.FIXRELEASED, project_owner)
159 >>> logout()182 >>> logout()
160183
161And then reloading the page. The Fix released bug, bug 4, is no longer shown.184And then reloading the page. The Fix Released bug, bug 18, is no longer shown.
162185
163 >>> anon_browser.reload()186 >>> anon_browser.reload()
164 >>> print extract_text(187 >>> print extract_text(
165 ... find_tag_by_id(anon_browser.contents, 'hot-bugs'))188 ... find_tag_by_id(anon_browser.contents, 'hot-bugs'))
166 Summary Status Importance Last changed189 Summary Status Importance Last changed
167 #5 Firefox install... New Critical on 2006-07-14190 #16 Summary for new bug 1 New Undecided ...
168 #1 Firefox does no... New Low on 2006-05-19191 #17 Summary for new bug 2 New Undecided ...
192 #19 Summary for new bug 4 New Undecided ...
193 #20 Summary for new bug 5 New Undecided ...
194 #21 Summary for new bug 6 New Undecided ...
195 #22 Summary for new bug 7 New Undecided ...
196 #23 Summary for new bug 8 New Undecided ...
197 #24 Summary for new bug 9 New Undecided ...
198 #25 Summary for new bug 10 New Undecided ...
199
200There is a link to see all bugs by heat if the project has more
201than 10 hot bugs.
202
203 >>> find_tag_by_id(anon_browser.contents, 'more-hot-bugs') is None
204 False
205
206Jokosher does not have more than 10 bugs and does not have a link
207to more hot bugs.
208
209 >>> anon_browser.open('http://bugs.launchpad.dev/jokosher')
210 >>> find_tag_by_id(anon_browser.contents, 'more-hot-bugs') is None
211 True
169212
=== modified file 'lib/lp/bugs/templates/bugtarget-bugs.pt'
--- lib/lp/bugs/templates/bugtarget-bugs.pt 2010-01-26 14:22:41 +0000
+++ lib/lp/bugs/templates/bugtarget-bugs.pt 2010-02-12 12:39:21 +0000
@@ -10,8 +10,9 @@
10 i18n:domain="malone"10 i18n:domain="malone"
11>11>
12 <metal:block fill-slot="head_epilogue">12 <metal:block fill-slot="head_epilogue">
13 <script type="text/javascript" src="/+icing/FormatAndColor.js"></script>13 <style type="text/css">
14 <script type="text/javascript" src="/+icing/PlotKit_Packed.js"></script>14 p#more-hot-bugs {float:right; margin-top:7px;}
15 </style>
15 </metal:block>16 </metal:block>
16 <body>17 <body>
17 <tal:side metal:fill-slot="side" condition="view/uses_launchpad_bugtracker">18 <tal:side metal:fill-slot="side" condition="view/uses_launchpad_bugtracker">
@@ -95,7 +96,7 @@
95 </ul>96 </ul>
96 </div>97 </div>
9798
98 <tal:has_hot_bugs condition="view/hot_bugtasks">99 <tal:has_hot_bugs condition="view/hot_bugs_info/bugtasks">
99 <div class="search-box">100 <div class="search-box">
100 <metal:search101 <metal:search
101 use-macro="context/@@+bugtarget-macros-search/simple-search-form"102 use-macro="context/@@+bugtarget-macros-search/simple-search-form"
@@ -123,7 +124,7 @@
123 </tr>124 </tr>
124 </thead>125 </thead>
125 <tbody>126 <tbody>
126 <tr tal:repeat="bugtask view/hot_bugtasks">127 <tr tal:repeat="bugtask view/hot_bugs_info/bugtasks">
127 <td class="icon left">128 <td class="icon left">
128 <span tal:replace="structure bugtask/image:icon" />129 <span tal:replace="structure bugtask/image:icon" />
129 </td>130 </td>
@@ -146,9 +147,13 @@
146 </tr>147 </tr>
147 </tbody>148 </tbody>
148 </table>149 </table>
150 <p id="more-hot-bugs"
151 tal:condition="view/hot_bugs_info/has_more_bugs">
152 <a tal:attributes="href string:${context/fmt:url/+bugs}?orderby=-heat&field.status%3Alist=NEW&field.status%3Alist=INCOMPLETE_WITH_RESPONSE&field.status%3Alist=INCOMPLETE_WITHOUT_RESPONSE&field.status%3Alist=CONFIRMED&field.status%3Alist=TRIAGED&field.status%3Alist=INPROGRESS&field.status%3Alist=FIXCOMMITTED&field.omit_dupes=on">See all hot bugs</a>
153 </p>
149 </tal:has_hot_bugs>154 </tal:has_hot_bugs>
150155
151 <tal:no_hot_bugs condition="not: view/hot_bugtasks">156 <tal:no_hot_bugs condition="not: view/hot_bugs_info/bugtasks">
152 <p id="no-bugs-filed"><strong>There are currently no bugs filed against157 <p id="no-bugs-filed"><strong>There are currently no bugs filed against
153 <tal:project_title replace="context/title" />.</strong></p>158 <tal:project_title replace="context/title" />.</strong></p>
154159