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

Proposed by Deryck Hodge
Status: Merged
Approved by: Gavin Panella
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~deryck/launchpad/hot-bugs-list-515232
Merge into: lp: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:~deryck/launchpad/hot-bugs-list-515232
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+19025@code.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
1=== modified file 'lib/lp/bugs/browser/bugtarget.py'
2--- lib/lp/bugs/browser/bugtarget.py 2010-02-10 23:14:56 +0000
3+++ lib/lp/bugs/browser/bugtarget.py 2010-02-12 12:39:21 +0000
4@@ -40,6 +40,7 @@
5 from canonical.config import config
6 from lp.bugs.browser.bugtask import BugTaskSearchListingView
7 from lp.bugs.interfaces.bug import IBug
8+from lp.bugs.interfaces.bugtask import BugTaskSearchParams
9 from canonical.launchpad.browser.feeds import (
10 BugFeedLink, BugTargetLatestBugsFeedLink, FeedsMixin,
11 PersonLatestBugsFeedLink)
12@@ -55,6 +56,7 @@
13 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
14 from canonical.launchpad.interfaces.temporaryblobstorage import (
15 ITemporaryStorageManager)
16+from canonical.launchpad.searchbuilder import any
17 from canonical.launchpad.webapp import urlappend
18 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
19 from canonical.launchpad.webapp.interfaces import ILaunchBag, NotFoundError
20@@ -1266,6 +1268,28 @@
21 else:
22 return 'None specified'
23
24+ @cachedproperty
25+ def hot_bugs_info(self):
26+ """Return a dict of the 10 hottest tasks and a has_more_bugs flag."""
27+ has_more_bugs = False
28+ params = BugTaskSearchParams(
29+ orderby='-heat', omit_dupes=True,
30+ user=self.user, status=any(*UNRESOLVED_BUGTASK_STATUSES))
31+ # Use 4x as many tasks as bugs that are needed to improve performance.
32+ bugtasks = self.context.searchTasks(params)[:40]
33+ hot_bugtasks = []
34+ hot_bugs = []
35+ for task in bugtasks:
36+ # Use hot_bugs list to ensure a bug is only listed once.
37+ if task.bug not in hot_bugs:
38+ if len(hot_bugtasks) < 10:
39+ hot_bugtasks.append(task)
40+ hot_bugs.append(task.bug)
41+ else:
42+ has_more_bugs = True
43+ break
44+ return {'has_more_bugs': has_more_bugs, 'bugtasks': hot_bugtasks}
45+
46
47 class BugTargetBugTagsView(LaunchpadView):
48 """Helper methods for rendering the bug tags portlet."""
49
50=== modified file 'lib/lp/bugs/browser/bugtask.py'
51--- lib/lp/bugs/browser/bugtask.py 2010-01-26 14:22:41 +0000
52+++ lib/lp/bugs/browser/bugtask.py 2010-02-12 12:39:21 +0000
53@@ -2206,9 +2206,13 @@
54 distrosourcepackage_context or sourcepackage_context):
55 return ["id", "summary", "importance", "status", "heat"]
56 elif distribution_context or distroseries_context:
57- return ["id", "summary", "packagename", "importance", "status", "heat"]
58+ return [
59+ "id", "summary", "packagename", "importance", "status",
60+ "heat"]
61 elif project_context:
62- return ["id", "summary", "productname", "importance", "status", "heat"]
63+ return [
64+ "id", "summary", "productname", "importance", "status",
65+ "heat"]
66 else:
67 raise AssertionError(
68 "Unrecognized context; don't know which report "
69@@ -2739,15 +2743,6 @@
70 return IDistributionSourcePackage(self.context, None)
71
72 @property
73- def hot_bugtasks(self):
74- """Return the 10 most recently updated bugtasks for this target."""
75- params = BugTaskSearchParams(
76- orderby="-date_last_updated", omit_dupes=True, user=self.user,
77- status=any(*UNRESOLVED_BUGTASK_STATUSES))
78- search = self.context.searchTasks(params)
79- return list(search[:10])
80-
81- @property
82 def addquestion_url(self):
83 """Return the URL for the +addquestion view for the context."""
84 if IQuestionTarget.providedBy(self.context):
85@@ -2803,8 +2798,7 @@
86 if bug_listing_item.review_action_widget is not None]
87 self.widgets = formlib.form.Widgets(widgets_list, len(self.prefix)+1)
88
89- @action('Save changes', name='submit',
90- condition=canApproveNominations)
91+ @action('Save changes', name='submit', condition=canApproveNominations)
92 def submit_action(self, action, data):
93 """Accept/Decline bug nominations."""
94 accepted = declined = 0
95@@ -3592,7 +3586,8 @@
96 """Show the columns that summarise expirable bugs."""
97 if (IDistribution.providedBy(self.context)
98 or IDistroSeries.providedBy(self.context)):
99- return ['id', 'summary', 'packagename', 'date_last_updated', 'heat']
100+ return [
101+ 'id', 'summary', 'packagename', 'date_last_updated', 'heat']
102 else:
103 return ['id', 'summary', 'date_last_updated', 'heat']
104
105
106=== modified file 'lib/lp/bugs/stories/bugs/xx-product-bugs-page.txt'
107--- lib/lp/bugs/stories/bugs/xx-product-bugs-page.txt 2010-01-26 14:22:41 +0000
108+++ lib/lp/bugs/stories/bugs/xx-product-bugs-page.txt 2010-02-12 12:39:21 +0000
109@@ -131,38 +131,81 @@
110
111 == Hot Bugs ==
112
113-A listing of the 10 'hottest' bugs (currently simply the bugs most
114-recently touched) is displayed to allow a quick overview of the project.
115+A listing of the 10 'hottest' bugs is displayed to allow a quick
116+overview of the project.
117+
118+To demonstrate this, we create 10 bugs and adjust their heat values manually.
119+
120+ >>> from zope.component import getUtility
121+ >>> from canonical.launchpad.ftests import login, logout
122+ >>> from lp.registry.interfaces.product import IProductSet
123+ >>> import transaction
124+ >>> login('foo.bar@canonical.com')
125+ >>> firefox = getUtility(IProductSet).getByName("firefox")
126+ >>> heat_values = [0, 400, 200, 600, 100, 50, 50, 50, 50, 50, 50, 50]
127+ >>> for count in range(1, 11):
128+ ... summary = 'Summary for new bug %d' % count
129+ ... bug = factory.makeBug(title=summary, product=firefox)
130+ ... bug.setHeat(heat_values[count])
131+ >>> transaction.commit()
132+ >>> logout()
133+
134 For each bug we have the number, title, status, importance and the time
135 since the last update.
136
137 >>> anon_browser.open('http://bugs.launchpad.dev/firefox')
138 >>> print extract_text(
139 ... find_tag_by_id(anon_browser.contents, 'hot-bugs'))
140- Summary Status Importance Last changed
141- #5 Firefox install... New Critical on 2006-07-14
142- #4 Reflow problems... New Medium on 2006-07-14
143- #1 Firefox does no... New Low on 2006-05-19
144-
145-
146-Fix released bugs are not shown. We demonstrate this by setting the bugtask
147-for bug 4 to be "Fix released".
148-
149- >>> from zope.component import getUtility
150+ Summary Status Importance Last changed
151+ #18 Summary for new bug 3 New Undecided ...
152+ #16 Summary for new bug 1 New Undecided ...
153+ #17 Summary for new bug 2 New Undecided ...
154+ #19 Summary for new bug 4 New Undecided ...
155+ #20 Summary for new bug 5 New Undecided ...
156+ #21 Summary for new bug 6 New Undecided ...
157+ #22 Summary for new bug 7 New Undecided ...
158+ #23 Summary for new bug 8 New Undecided ...
159+ #24 Summary for new bug 9 New Undecided ...
160+ #25 Summary for new bug 10 New Undecided ...
161+
162+
163+Fix Released bugs are not shown. We demonstrate this by setting the bugtask
164+for bug 18 to be "Fix Released".
165+
166 >>> from lp.bugs.interfaces.bug import BugTaskStatus, IBugSet
167 >>> from lp.registry.interfaces.person import IPersonSet
168 >>> login('foo.bar@canonical.com')
169- >>> bug_4 = getUtility(IBugSet).get(4)
170+ >>> bug_18 = getUtility(IBugSet).get(18)
171 >>> project_owner = getUtility(IPersonSet).getByName('name12')
172- >>> bug_4.bugtasks[0].transitionToStatus(
173+ >>> bug_18.bugtasks[0].transitionToStatus(
174 ... BugTaskStatus.FIXRELEASED, project_owner)
175 >>> logout()
176
177-And then reloading the page. The Fix released bug, bug 4, is no longer shown.
178+And then reloading the page. The Fix Released bug, bug 18, is no longer shown.
179
180 >>> anon_browser.reload()
181 >>> print extract_text(
182 ... find_tag_by_id(anon_browser.contents, 'hot-bugs'))
183- Summary Status Importance Last changed
184- #5 Firefox install... New Critical on 2006-07-14
185- #1 Firefox does no... New Low on 2006-05-19
186+ Summary Status Importance Last changed
187+ #16 Summary for new bug 1 New Undecided ...
188+ #17 Summary for new bug 2 New Undecided ...
189+ #19 Summary for new bug 4 New Undecided ...
190+ #20 Summary for new bug 5 New Undecided ...
191+ #21 Summary for new bug 6 New Undecided ...
192+ #22 Summary for new bug 7 New Undecided ...
193+ #23 Summary for new bug 8 New Undecided ...
194+ #24 Summary for new bug 9 New Undecided ...
195+ #25 Summary for new bug 10 New Undecided ...
196+
197+There is a link to see all bugs by heat if the project has more
198+than 10 hot bugs.
199+
200+ >>> find_tag_by_id(anon_browser.contents, 'more-hot-bugs') is None
201+ False
202+
203+Jokosher does not have more than 10 bugs and does not have a link
204+to more hot bugs.
205+
206+ >>> anon_browser.open('http://bugs.launchpad.dev/jokosher')
207+ >>> find_tag_by_id(anon_browser.contents, 'more-hot-bugs') is None
208+ True
209
210=== modified file 'lib/lp/bugs/templates/bugtarget-bugs.pt'
211--- lib/lp/bugs/templates/bugtarget-bugs.pt 2010-01-26 14:22:41 +0000
212+++ lib/lp/bugs/templates/bugtarget-bugs.pt 2010-02-12 12:39:21 +0000
213@@ -10,8 +10,9 @@
214 i18n:domain="malone"
215 >
216 <metal:block fill-slot="head_epilogue">
217- <script type="text/javascript" src="/+icing/FormatAndColor.js"></script>
218- <script type="text/javascript" src="/+icing/PlotKit_Packed.js"></script>
219+ <style type="text/css">
220+ p#more-hot-bugs {float:right; margin-top:7px;}
221+ </style>
222 </metal:block>
223 <body>
224 <tal:side metal:fill-slot="side" condition="view/uses_launchpad_bugtracker">
225@@ -95,7 +96,7 @@
226 </ul>
227 </div>
228
229- <tal:has_hot_bugs condition="view/hot_bugtasks">
230+ <tal:has_hot_bugs condition="view/hot_bugs_info/bugtasks">
231 <div class="search-box">
232 <metal:search
233 use-macro="context/@@+bugtarget-macros-search/simple-search-form"
234@@ -123,7 +124,7 @@
235 </tr>
236 </thead>
237 <tbody>
238- <tr tal:repeat="bugtask view/hot_bugtasks">
239+ <tr tal:repeat="bugtask view/hot_bugs_info/bugtasks">
240 <td class="icon left">
241 <span tal:replace="structure bugtask/image:icon" />
242 </td>
243@@ -146,9 +147,13 @@
244 </tr>
245 </tbody>
246 </table>
247+ <p id="more-hot-bugs"
248+ tal:condition="view/hot_bugs_info/has_more_bugs">
249+ <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>
250+ </p>
251 </tal:has_hot_bugs>
252
253- <tal:no_hot_bugs condition="not: view/hot_bugtasks">
254+ <tal:no_hot_bugs condition="not: view/hot_bugs_info/bugtasks">
255 <p id="no-bugs-filed"><strong>There are currently no bugs filed against
256 <tal:project_title replace="context/title" />.</strong></p>
257