Merge lp:~allenap/launchpad/update-project-group-page-bug-434764 into lp:launchpad

Proposed by Gavin Panella
Status: Merged
Merged at revision: not available
Proposed branch: lp:~allenap/launchpad/update-project-group-page-bug-434764
Merge into: lp:launchpad
Diff against target: 1522 lines (+680/-579)
13 files modified
lib/lp/bugs/browser/bugtask.py (+178/-201)
lib/lp/bugs/browser/configure.zcml (+10/-4)
lib/lp/bugs/browser/tests/bugtask-search-views.txt (+0/-152)
lib/lp/bugs/browser/tests/special/bugs-fixed-elsewhere.txt (+39/-31)
lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt (+2/-1)
lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt (+1/-1)
lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-authenticated.txt (+0/-19)
lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-unauthenticated.txt (+0/-16)
lib/lp/bugs/stories/xx-bugs-statistics-portlet.txt (+334/-0)
lib/lp/bugs/templates/bugtarget-bugs.pt (+1/-88)
lib/lp/bugs/templates/bugtarget-portlet-bugfilters-content.pt (+64/-38)
lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt (+46/-27)
lib/lp/registry/browser/project.py (+5/-1)
To merge this branch: bzr merge lp:~allenap/launchpad/update-project-group-page-bug-434764
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+15322@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (3.2 KiB)

This branch makes the bug search portlet consistent between the pages
for projects, project groups, distributions, distribution series,
source packages in distribution, and source packages in distribution
series. All of these pages now have a quick-to-load portlet that only
contains links to "canned" searches, and which makes an async request
to load the stats information.

It's a big branch, but most of it is refactoring or removal.

lib/lp/bugs/browser/bugtask.py

  New function get_default_search_params(), which is just the breaking
  out of _getDefaultSearchParams(). Also, _getDefaultSearchParams()
  applied an ordering to the parameters, but none of the call-sites
  cared, so I dropped that.

  Two new mixin classes: BugsInfoMixin and BugsStatsMixin. The latter
  inherits from the former. These provide several properties, like
  open_bugs_url and pending_bugwatches_count. These properties are
  refactored from methods like getUnassignedBugsURL() and several
  *_info properties that hung directly off the view. The *_info
  properties returned dicts containing a count and url; the mixins now
  provide two properties.

  The BugsInfoMixin is mixed into views that need URLs the "canned"
  searches it knows about. BugsStatsMixin is used when views need URLs
  and counts. Because calculating the counts can take a long time,
  this typically means that the latter views are fetched
  asynchronously by the browser.

  Two of these views are BugListingPortletInfoView and
  BugListingPortletStatsView.

lib/lp/bugs/browser/configure.zcml

  Replace the registration of +bugtarget-portlet-bugfilters-content
  with two views, one for just the URLs and the other for URLs and
  counts.

lib/lp/bugs/browser/tests/bugtask-search-views.txt

  Remove some tests that are now in xx-bugs-statistics-portlet. In
  fact, the tests removed were poor tests because a lot of the time
  they were not testing the view at all.

lib/lp/bugs/browser/tests/special/bugs-fixed-elsewhere.txt

  Fix this up to work with the new properties in the mixins. Mostly
  mechanical changes.

lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt
lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt

  Small mechanical changes.

lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-authenticated.txt
lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-unauthenticated.txt

  Removed because it's now all in xx-bugs-statistics-portlet, and
  these are very old style tests.

lib/lp/bugs/stories/xx-bugs-statistics-portlet.txt

  New. Explains and demonstrates the new portlet views.

lib/lp/bugs/templates/bugtarget-bugs.pt

  Bits moved to bugtarget-portlet-bugfilters-content.pt.

lib/lp/bugs/templates/bugtarget-portlet-bugfilters-content.pt

  Originally from bugtarget-bugs.pt, but changed since. Pretty simple
  table stuff. There's an comment in there to explain how the template
  can be used for both the without-counts and the with-count views.

lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt

  Clean up the portlet body and update the javascript to load the
  stats.

lib/lp/registry/browser/project.py

  Add a subscrib...

Read more...

Revision history for this message
Abel Deuring (adeuring) wrote :

Hi Gavin,

nice work. Just one nitpick: In xx-bugs.statistics-portlet.txt, several paragraphs start with "One the user has identified...". I think this should be be "Once the user has identified..."

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bugtask.py'
2--- lib/lp/bugs/browser/bugtask.py 2009-11-17 02:33:27 +0000
3+++ lib/lp/bugs/browser/bugtask.py 2009-12-07 15:04:15 +0000
4@@ -8,7 +8,8 @@
5 __all__ = [
6 'assignee_renderer',
7 'BugListingBatchNavigator',
8- 'BugListingPortletView',
9+ 'BugListingPortletInfoView',
10+ 'BugListingPortletStatsView',
11 'BugNominationsView',
12 'bugtarget_renderer',
13 'BugTargetTraversalMixin',
14@@ -56,7 +57,8 @@
15 IInputWidget, IDisplayWidget, InputErrors, WidgetsError)
16 from zope.app.form.utility import setUpWidget, setUpWidgets
17 from zope.component import (
18- ComponentLookupError, getAdapter, getUtility, getMultiAdapter)
19+ ComponentLookupError, getAdapter, getMultiAdapter, getUtility,
20+ queryMultiAdapter)
21 from zope.event import notify
22 from zope import formlib
23 from zope.interface import implementer, implements, Interface, providedBy
24@@ -107,8 +109,7 @@
25 IDistroSeriesBugTask, IFrontPageBugTaskSearch,
26 INominationsReviewTableBatchNavigator, INullBugTask, IPersonBugTaskSearch,
27 IProductSeriesBugTask, IRemoveQuestionFromBugTaskForm, IUpstreamBugTask,
28- IUpstreamProductBugTaskSearch, RESOLVED_BUGTASK_STATUSES,
29- UNRESOLVED_BUGTASK_STATUSES)
30+ IUpstreamProductBugTaskSearch, UNRESOLVED_BUGTASK_STATUSES)
31 from lp.bugs.interfaces.bugtracker import BugTrackerType
32 from lp.bugs.interfaces.cve import ICveSet
33 from lp.registry.interfaces.distribution import IDistribution
34@@ -292,6 +293,24 @@
35 return ["-importance"]
36
37
38+def get_default_search_params(user):
39+ """Return a BugTaskSearchParams instance with default values.
40+
41+ By default, a search includes any bug that is unresolved and not a
42+ duplicate of another bug.
43+
44+ If this search will be used to display a list of bugs to the user
45+ it may be a good idea to set the orderby attribute using
46+ get_sortorder_from_request():
47+
48+ params = get_default_search_params(user)
49+ params.orderby = get_sortorder_from_request(request)
50+
51+ """
52+ return BugTaskSearchParams(
53+ user=user, status=any(*UNRESOLVED_BUGTASK_STATUSES), omit_dupes=True)
54+
55+
56 OLD_BUGTASK_STATUS_MAP = {
57 'Unconfirmed': 'New',
58 'Needs Info': 'Incomplete',
59@@ -1668,59 +1687,162 @@
60 return u""
61
62
63-class BugListingPortletView(LaunchpadView):
64- """Portlet containing all available bug listings."""
65- def getOpenBugsURL(self):
66- """Return the URL for open bugs on this bug target."""
67- return get_buglisting_search_filter_url(
68- status=[status.title for status in UNRESOLVED_BUGTASK_STATUSES])
69-
70- def getBugsAssignedToMeURL(self):
71- """Return the URL for bugs assigned to the current user on target."""
72- if self.user:
73- return get_buglisting_search_filter_url(assignee=self.user.name)
74+class BugsInfoMixin:
75+ """Contains properties giving URLs to bug information."""
76+
77+ @property
78+ def bugs_fixed_elsewhere_url(self):
79+ """A URL to a list of bugs fixed elsewhere."""
80+ return "%s?field.status_upstream=resolved_upstream" % (
81+ canonical_url(self.context, view_name='+bugs'))
82+
83+ @property
84+ def open_cve_bugs_url(self):
85+ """A URL to a list of open bugs linked to CVEs."""
86+ return "%s?field.has_cve=on" % (
87+ canonical_url(self.context, view_name='+bugs'))
88+
89+ @property
90+ def open_cve_bugs_has_report(self):
91+ """Whether or not the context has a CVE report page."""
92+ return queryMultiAdapter(
93+ (self.context, self.request), name='+cve') is not None
94+
95+ @property
96+ def pending_bugwatches_url(self):
97+ """A URL to a list of bugs that need a bugwatch.
98+
99+ None is returned if the context is not an upstream product.
100+ """
101+ if not IProduct.providedBy(self.context):
102+ return None
103+ if self.context.official_malone:
104+ return None
105+ return "%s?field.status_upstream=pending_bugwatch" % (
106+ canonical_url(self.context, view_name='+bugs'))
107+
108+ @property
109+ def expirable_bugs_url(self):
110+ """A URL to a list of bugs that can expire, or None.
111+
112+ If the bugtarget is not a supported implementation, or its pillar
113+ does not have enable_bug_expiration set to True, None is returned.
114+ The bugtarget may be an `IDistribution`, `IDistroSeries`, `IProduct`,
115+ or `IProductSeries`.
116+ """
117+ if target_has_expirable_bugs_listing(self.context):
118+ return canonical_url(self.context, view_name='+expirable-bugs')
119 else:
120- return str(self.request.URL) + "/+login"
121-
122- def getBugsAssignedToMeCount(self):
123- """Return the count of bugs assinged to the logged-in user."""
124- assert self.user, (
125- "Counting 'bugs assigned to me' requires a logged-in user")
126-
127- search_params = BugTaskSearchParams(
128- user=self.user, assignee=self.user,
129- status=any(*UNRESOLVED_BUGTASK_STATUSES),
130- omit_dupes=True)
131-
132- return self.context.searchTasks(search_params).count()
133-
134- def getCriticalBugsURL(self):
135- """Return the URL for critical bugs on this bug target."""
136+ return None
137+
138+ @property
139+ def new_bugs_url(self):
140+ """A URL to a page of new bugs."""
141+ return get_buglisting_search_filter_url(
142+ status=BugTaskStatus.NEW.title)
143+
144+ @property
145+ def open_bugs_url(self):
146+ """A URL to a list of open bugs."""
147+ return canonical_url(self.context, view_name='+bugs')
148+
149+ @property
150+ def critical_bugs_url(self):
151+ """A URL to a list of critical bugs."""
152 return get_buglisting_search_filter_url(
153 status=[status.title for status in UNRESOLVED_BUGTASK_STATUSES],
154 importance=BugTaskImportance.CRITICAL.title)
155
156- def getUnassignedBugsURL(self):
157- """Return the URL for critical bugs on this bug target."""
158- unresolved_tasks_query_string = get_buglisting_search_filter_url(
159- status=[status.title for status in UNRESOLVED_BUGTASK_STATUSES])
160-
161- return unresolved_tasks_query_string + "&assignee_option=none"
162-
163- def getNewBugsURL(self):
164- """Return the URL for new bugs on this bug target."""
165- return get_buglisting_search_filter_url(
166- status=BugTaskStatus.NEW.title)
167-
168- def getAllBugsEverReportedURL(self):
169- """Return the URL to list all bugs reported."""
170- all_statuses = UNRESOLVED_BUGTASK_STATUSES + RESOLVED_BUGTASK_STATUSES
171- all_status_query_string = get_buglisting_search_filter_url(
172- status=[status.title for status in all_statuses])
173-
174- # Add the bit that simulates the "omit dupes" checkbox
175- # being unchecked.
176- return all_status_query_string + "&field.omit_dupes.used="
177+ @property
178+ def my_bugs_url(self):
179+ """A URL to a list of bugs assigned to the user, or None."""
180+ if self.user is None:
181+ return None
182+ else:
183+ return get_buglisting_search_filter_url(assignee=self.user.name)
184+
185+
186+class BugsStatsMixin(BugsInfoMixin):
187+ """Contains properties giving bug stats.
188+
189+ These can be expensive to obtain.
190+ """
191+
192+ @property
193+ def bugs_fixed_elsewhere_count(self):
194+ """A count of bugs fixed elsewhere."""
195+ params = get_default_search_params(self.user)
196+ params.resolved_upstream = True
197+ return self.context.searchTasks(params).count()
198+
199+ @property
200+ def open_cve_bugs_count(self):
201+ """A count of open bugs linked to CVEs."""
202+ params = get_default_search_params(self.user)
203+ params.has_cve = True
204+ return self.context.searchTasks(params).count()
205+
206+ @property
207+ def pending_bugwatches_count(self):
208+ """A count of bugs that need a bugwatch.
209+
210+ None is returned if the context is not an upstream product.
211+ """
212+ if not IProduct.providedBy(self.context):
213+ return None
214+ if self.context.official_malone:
215+ return None
216+ params = get_default_search_params(self.user)
217+ params.pending_bugwatch_elsewhere = True
218+ return self.context.searchTasks(params).count()
219+
220+ @property
221+ def expirable_bugs_count(self):
222+ """A count of bugs that can expire, or None.
223+
224+ If the bugtarget is not a supported implementation, or its pillar
225+ does not have enable_bug_expiration set to True, None is returned.
226+ The bugtarget may be an `IDistribution`, `IDistroSeries`, `IProduct`,
227+ or `IProductSeries`.
228+ """
229+ if target_has_expirable_bugs_listing(self.context):
230+ return getUtility(IBugTaskSet).findExpirableBugTasks(
231+ 0, user=self.user, target=self.context).count()
232+ else:
233+ return None
234+
235+ @property
236+ def new_bugs_count(self):
237+ """A count of new bugs."""
238+ return self.context.new_bugtasks.count()
239+
240+ @property
241+ def open_bugs_count(self):
242+ """A count of open bugs."""
243+ return self.context.open_bugtasks.count()
244+
245+ @property
246+ def critical_bugs_count(self):
247+ """A count of critical bugs."""
248+ return self.context.critical_bugtasks.count()
249+
250+ @property
251+ def my_bugs_count(self):
252+ """A count of bugs assigned to the user, or None."""
253+ if self.user is None:
254+ return None
255+ else:
256+ params = get_default_search_params(self.user)
257+ params.assignee = self.user
258+ return self.context.searchTasks(params).count()
259+
260+
261+class BugListingPortletInfoView(LaunchpadView, BugsInfoMixin):
262+ """Portlet containing available bug listings without stats."""
263+
264+
265+class BugListingPortletStatsView(LaunchpadView, BugsStatsMixin):
266+ """Portlet containing available bug listings with stats."""
267
268
269 def get_buglisting_search_filter_url(
270@@ -1972,7 +2094,7 @@
271 return Link('+nominations', 'Review nominations', icon='bug')
272
273
274-class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin):
275+class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
276 """View that renders a list of bugs for a given set of search criteria."""
277
278 implements(IBugTaskSearchListingMenu)
279@@ -2131,18 +2253,6 @@
280 # LaunchpadFormView's own validation.
281 pass
282
283- def _getDefaultSearchParams(self):
284- """Return a BugTaskSearchParams instance with default values.
285-
286- By default, a search includes any bug that is unresolved and not
287- a duplicate of another bug.
288- """
289- search_params = BugTaskSearchParams(
290- user=self.user, status=any(*UNRESOLVED_BUGTASK_STATUSES),
291- omit_dupes=True)
292- search_params.orderby = get_sortorder_from_request(self.request)
293- return search_params
294-
295 def buildSearchParams(self, searchtext=None, extra_params=None):
296 """Build the BugTaskSearchParams object for the given arguments and
297 values specified by the user on this form's widgets.
298@@ -2213,7 +2323,8 @@
299 else:
300 form_values['tag'] = tags
301
302- search_params = self._getDefaultSearchParams()
303+ search_params = get_default_search_params(self.user)
304+ search_params.orderby = get_sortorder_from_request(self.request)
305 for name, value in form_values.items():
306 setattr(search_params, name, value)
307 return search_params
308@@ -2602,140 +2713,6 @@
309 """
310 return IDistributionSourcePackage(self.context, None)
311
312- def _bugOrBugs(self, count):
313- """Return 'bug' if the count is 1, otherwise return 'bugs'.
314-
315- zope.i18n does not support for ngettext-like functionality.
316- """
317- if count == 1:
318- return 'bug'
319- else:
320- return 'bugs'
321-
322- @property
323- def bugs_fixed_elsewhere_info(self):
324- """Return a dict with count and URL of bugs fixed elsewhere.
325-
326- The available keys are:
327- * 'count' - The number of bugs.
328- * 'url' - The URL of the search.
329- * 'label' - Either 'bug' or 'bugs' depending on the count.
330- """
331- params = self._getDefaultSearchParams()
332- params.resolved_upstream = True
333- fixed_elsewhere = self.context.searchTasks(params)
334- count = fixed_elsewhere.count()
335- label = self._bugOrBugs(count)
336- search_url = (
337- "%s/+bugs?field.status_upstream=resolved_upstream" %
338- canonical_url(self.context))
339- return dict(count=count, url=search_url, label=label)
340-
341- @property
342- def open_cve_bugs_info(self):
343- """Return a dict with count and URL of open bugs linked to CVEs.
344-
345- The available keys are:
346- * 'count' - The number of bugs.
347- * 'url' - The URL of the search.
348- * 'label' - Either 'bug' or 'bugs' depending on the count.
349- """
350- params = self._getDefaultSearchParams()
351- params.has_cve = True
352- open_cve_bugs = self.context.searchTasks(params)
353- count = open_cve_bugs.count()
354- label = self._bugOrBugs(count)
355- search_url = (
356- "%s/+bugs?field.has_cve=on" % canonical_url(self.context))
357- return dict(count=count, url=search_url, label=label)
358-
359- @property
360- def pending_bugwatches_info(self):
361- """Return a dict with count and URL of bugs that need a bugwatch.
362-
363- None is returned if the context is not an upstream product.
364-
365- The available keys are:
366- * 'count' - The number of bugs.
367- * 'url' - The URL of the search.
368- * 'label' - Either 'bug' or 'bugs' depending on the count.
369- """
370- if not self.isUpstreamProduct:
371- return None
372- params = self._getDefaultSearchParams()
373- params.pending_bugwatch_elsewhere = True
374- pending_bugwatch_elsewhere = self.context.searchTasks(params)
375- count = pending_bugwatch_elsewhere.count()
376- label = self._bugOrBugs(count)
377- search_url = (
378- "%s/+bugs?field.status_upstream=pending_bugwatch" %
379- canonical_url(self.context))
380- return dict(count=count, url=search_url, label=label)
381-
382- @property
383- def expirable_bugs_info(self):
384- """Return a dict with count and url of bugs that can expire, or None.
385-
386- If the bugtarget is not a supported implementation, or its pillar
387- does not have enable_bug_expiration set to True, None is returned.
388- The bugtarget may be an `IDistribution`, `IDistroSeries`, `IProduct`,
389- or `IProductSeries`.
390-
391- The available keys are:
392- * 'count' - The number of bugs.
393- * 'url' - The URL of the search, or None.
394- * 'label' - Either 'bug' or 'bugs' depending on the count.
395- """
396- if not target_has_expirable_bugs_listing(self.context):
397- return None
398- bugtaskset = getUtility(IBugTaskSet)
399- expirable_bugtasks = bugtaskset.findExpirableBugTasks(
400- 0, user=self.user, target=self.context)
401- count = expirable_bugtasks.count()
402- label = self._bugOrBugs(count)
403- url = "%s/+expirable-bugs" % canonical_url(self.context)
404- return dict(count=count, url=url, label=label)
405-
406- @property
407- def new_bugs_info(self):
408- """Return a dict with new bugs info."""
409- return dict(
410- count=self.context.new_bugtasks.count,
411- url=get_buglisting_search_filter_url(
412- status=BugTaskStatus.NEW.title))
413-
414- @property
415- def open_bugs_info(self):
416- """Return a dict with open bugs info."""
417- return dict(
418- count=self.context.open_bugtasks.count,
419- url=canonical_url(
420- self.context, rootsite='bugs', view_name='+bugs'))
421-
422- @property
423- def critical_bugs_info(self):
424- """Return a dict with critical bugs info."""
425- return dict(
426- count=self.context.critical_bugtasks.count,
427- url=get_buglisting_search_filter_url(
428- status=[status.title for status
429- in UNRESOLVED_BUGTASK_STATUSES],
430- importance=BugTaskImportance.CRITICAL.title))
431-
432- @property
433- def my_bugs_info(self):
434- """Return a dict with info on bugs assigned to the user, or None."""
435- if self.user:
436- return dict(
437- count=self.context.searchTasks(
438- BugTaskSearchParams(
439- user=self.user, assignee=self.user,
440- status=any(*UNRESOLVED_BUGTASK_STATUSES),
441- omit_dupes=True)).count(),
442- url=get_buglisting_search_filter_url(assignee=self.user.name))
443- else:
444- return None
445-
446 @property
447 def hot_bugtasks(self):
448 """Return the 10 most recently updated bugtasks for this target."""
449@@ -3689,7 +3666,7 @@
450 # If the user does not have permission to view the bug for
451 # whatever reason, raise ComponentLookupError.
452 try:
453- name = context.bug.displayname
454+ context.bug.displayname
455 except Unauthorized:
456 raise ComponentLookupError()
457
458
459=== modified file 'lib/lp/bugs/browser/configure.zcml'
460--- lib/lp/bugs/browser/configure.zcml 2009-11-19 05:56:30 +0000
461+++ lib/lp/bugs/browser/configure.zcml 2009-12-07 15:04:15 +0000
462@@ -24,15 +24,21 @@
463 template="../templates/bugtarget-portlet-recently-touched-bugs.pt"/>
464 <browser:page
465 for="lp.bugs.interfaces.bugtarget.IHasBugs"
466- class="lp.bugs.browser.bugtask.BugListingPortletView"
467+ class="lp.bugs.browser.bugtask.BugListingPortletInfoView"
468 permission="zope.Public"
469 name="+portlet-bugfilters"
470 template="../templates/bugtarget-portlet-bugfilters.pt"/>
471 <browser:page
472 for="lp.bugs.interfaces.bugtarget.IHasBugs"
473- class="lp.bugs.browser.bugtask.BugListingPortletView"
474- permission="zope.Public"
475- name="+bugtarget-portlet-bugfilters-content"
476+ class="lp.bugs.browser.bugtask.BugListingPortletInfoView"
477+ permission="zope.Public"
478+ name="+bugtarget-portlet-bugfilters-info"
479+ template="../templates/bugtarget-portlet-bugfilters-content.pt"/>
480+ <browser:page
481+ for="lp.bugs.interfaces.bugtarget.IHasBugs"
482+ class="lp.bugs.browser.bugtask.BugListingPortletStatsView"
483+ permission="zope.Public"
484+ name="+bugtarget-portlet-bugfilters-stats"
485 template="../templates/bugtarget-portlet-bugfilters-content.pt"/>
486 <browser:page
487 for="lp.bugs.interfaces.bugtarget.IHasBugs"
488
489=== modified file 'lib/lp/bugs/browser/tests/bugtask-search-views.txt'
490--- lib/lp/bugs/browser/tests/bugtask-search-views.txt 2009-08-13 15:12:16 +0000
491+++ lib/lp/bugs/browser/tests/bugtask-search-views.txt 2009-12-07 15:04:15 +0000
492@@ -350,158 +350,6 @@
493 Mozilla Firefox 1.0
494
495
496-Bug Filters Portlet
497--------------------
498-
499-The bug filters portlet has links to various "canned" searches, and
500-shows the number of bugs matching that filter beside each. The counts
501-are (mostly) gotten from the context object itself. Since these URLs and
502-counts are dependant on the current user, we will log in as Sample
503-Person.
504-
505- >>> from canonical.launchpad.interfaces import IProductSet
506-
507- >>> login("test@canonical.com")
508-
509- >>> firefox = getUtility(IProductSet).get(4)
510- >>> print firefox.name
511- firefox
512-
513- >>> bugfilters_portlet_view = create_view(firefox, '+portlet-bugfilters')
514-
515- >>> print bugfilters_portlet_view.getOpenBugsURL()
516- +bugs?search=Search&field.status=New&field.status=Incomplete&field.status=Confirmed&field.status=Triaged&field.status=In+Progress&field.status=Fix+Committed
517- >>> print bugfilters_portlet_view.context.open_bugtasks.count()
518- 3
519-
520- >>> print bugfilters_portlet_view.getBugsAssignedToMeURL()
521- +bugs?search=Search&field.assignee=name12
522-
523- >>> print bugfilters_portlet_view.getBugsAssignedToMeCount()
524- 1
525-
526-If Sample Person were assigned to second bug, but that bug was a dupe,
527-it would not be included in the counts.
528-
529- >>> from canonical.launchpad.interfaces import IBugTaskSet
530-
531- >>> bug_six_in_firefox = getUtility(IBugTaskSet).get(15)
532- >>> print bug_six_in_firefox.bug.duplicateof.id
533- 5
534- >>> print bugfilters_portlet_view.user.displayname
535- Sample Person
536-
537- >>> bug_six_in_firefox.transitionToAssignee(
538- ... bugfilters_portlet_view.user)
539- >>> flush_database_updates()
540-
541- >>> print bugfilters_portlet_view.getBugsAssignedToMeCount()
542- 1
543-
544-The critical bugs link and counts consider only unresolved bugs.
545-
546- >>> print bugfilters_portlet_view.getCriticalBugsURL()
547- +bugs?search=Search&field.importance=Critical&field.status=New&field.status=Incomplete&field.status=Confirmed&field.status=Triaged&field.status=In+Progress&field.status=Fix+Committed
548-
549- >>> bugfilters_portlet_view.context.critical_bugtasks.count()
550- 1
551-
552- >>> critical_task = bugfilters_portlet_view.context.critical_bugtasks[0]
553- >>> prev_status = critical_task.status
554- >>> critical_task.transitionToStatus(
555- ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
556- >>> flush_database_updates()
557-
558- >>> print bugfilters_portlet_view.context.critical_bugtasks.count()
559- 0
560-
561- >>> critical_task.transitionToStatus(
562- ... prev_status, getUtility(ILaunchBag).user)
563- >>> flush_database_updates()
564-
565-Moving along...
566-
567- >>> print bugfilters_portlet_view.getNewBugsURL()
568- +bugs?search=Search&field.status=New
569-
570- >>> print bugfilters_portlet_view.context.new_bugtasks.count()
571- 3
572-
573- >>> print bugfilters_portlet_view.getUnassignedBugsURL()
574- +bugs?search=Search&field.status=New&field.status=Incomplete&field.status=Confirmed&field.status=Triaged&field.status=In+Progress&field.status=Fix+Committed&assignee_option=none
575- >>> print bugfilters_portlet_view.context.unassigned_bugtasks.count()
576- 1
577-
578-And finally, all bugs ever reported.
579-
580- >>> print bugfilters_portlet_view.getAllBugsEverReportedURL()
581- +bugs?search=Search&field.status=New&field.status=Incomplete&field.status=Confirmed&field.status=Triaged&field.status=In+Progress&field.status=Fix+Committed&field.status=Fix+Released&field.status=Invalid&field.status=Won%27t+Fix&field.omit_dupes.used=
582-
583- >>> print bugfilters_portlet_view.context.all_bugtasks.count()
584- 4
585-
586-All counts ignore bugtasks with status Unknown. Let's set bug four's
587-status in firefox to Unknown and note how the count is decremented by 1
588-for the open, new, unassigned, and all bugs ever reported
589-counts.
590-
591- >>> bug_four_in_firefox = getUtility(IBugTaskSet).get(13)
592-
593- >>> print bug_four_in_firefox.bug.id
594- 4
595- >>> print bug_four_in_firefox.product.name
596- firefox
597-
598-We'll examine the counts once more, to verify they are what we think
599-they are.
600-
601- >>> print bugfilters_portlet_view.context.open_bugtasks.count()
602- 3
603- >>> print bugfilters_portlet_view.context.critical_bugtasks.count()
604- 1
605- >>> print bugfilters_portlet_view.context.new_bugtasks.count()
606- 3
607- >>> print bugfilters_portlet_view.context.unassigned_bugtasks.count()
608- 1
609- >>> print bugfilters_portlet_view.context.all_bugtasks.count()
610- 4
611-
612-Then change the status.
613-
614- >>> bug_four_in_firefox.transitionToStatus(
615- ... BugTaskStatus.UNKNOWN, getUtility(ILaunchBag).user)
616- >>> flush_database_updates()
617-
618-And the affected counts have been decremented.
619-
620- >>> print bugfilters_portlet_view.context.open_bugtasks.count()
621- 2
622- >>> print bugfilters_portlet_view.context.critical_bugtasks.count()
623- 1
624- >>> print bugfilters_portlet_view.context.new_bugtasks.count()
625- 2
626- >>> print bugfilters_portlet_view.context.unassigned_bugtasks.count()
627- 0
628- >>> print bugfilters_portlet_view.context.all_bugtasks.count()
629- 3
630-
631-The unassigned bug counts consider only bugs with an unresolved status.
632-
633- >>> bug_four_in_firefox.transitionToStatus(
634- ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
635- >>> flush_database_updates()
636-
637- >>> print bugfilters_portlet_view.context.unassigned_bugtasks.count()
638- 0
639-
640- >>> bug_four_in_firefox.transitionToStatus(
641- ... BugTaskStatus.FIXCOMMITTED, getUtility(ILaunchBag).user)
642- >>> flush_database_updates()
643-
644- >>> print bugfilters_portlet_view.context.unassigned_bugtasks.count()
645- 1
646-
647-
648 == Constructing search filter urls ==
649
650 There is a helper method, get_buglisting_search_filter_url(), which can
651
652=== modified file 'lib/lp/bugs/browser/tests/special/bugs-fixed-elsewhere.txt'
653--- lib/lp/bugs/browser/tests/special/bugs-fixed-elsewhere.txt 2009-06-12 16:36:02 +0000
654+++ lib/lp/bugs/browser/tests/special/bugs-fixed-elsewhere.txt 2009-12-07 15:04:15 +0000
655@@ -1,21 +1,35 @@
656 = Bugs Fixed Elsewhere =
657
658-The +bugs-index view for a distribution or product contains a method for
659-getting information about bugs fixed in some other context. It includes
660-both a count of how many bugs that are fixed elsewhere, as well as a
661-URL to the full list.
662+The +bugtarget-portlet-bugfilters-info view for a distribution or
663+product contains a property for a URL to a list of bugs fixed
664+elsewhere.
665
666 >>> from zope.component import getMultiAdapter
667 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
668- >>> view = getMultiAdapter(
669- ... (bugtarget, LaunchpadTestRequest()), name='+bugs-index')
670- >>> view.initialize()
671-
672- >>> bugs_fixed = view.bugs_fixed_elsewhere_info
673- >>> bugs_fixed['count']
674+
675+ >>> view = getMultiAdapter(
676+ ... (bugtarget, LaunchpadTestRequest()),
677+ ... name='+bugtarget-portlet-bugfilters-info')
678+ >>> view.initialize()
679+
680+ >>> view.bugs_fixed_elsewhere_url
681+ u'http://.../+bugs?field.status_upstream=resolved_upstream'
682+
683+The +bugtarget-portlet-bugfilters-stats view for a distribution or
684+product contains the URL as above in addition to a count of how many
685+bugs that are fixed elsewhere. This count can take a while to
686+calculate, so it is put on this separate view which can be requested
687+asyncronously.
688+
689+ >>> view = getMultiAdapter(
690+ ... (bugtarget, LaunchpadTestRequest()),
691+ ... name='+bugtarget-portlet-bugfilters-stats')
692+ >>> view.initialize()
693+
694+ >>> view.bugs_fixed_elsewhere_url
695+ u'http://.../+bugs?field.status_upstream=resolved_upstream'
696+ >>> view.bugs_fixed_elsewhere_count
697 0
698- >>> bugs_fixed['url']
699- u'http://.../+bugs?field.status_upstream=resolved_upstream'
700
701 Simply opening a bug elsewhere won't increase the count.
702
703@@ -29,8 +43,7 @@
704 >>> from canonical.launchpad.interfaces import IBugTaskSet
705 >>> elsewhere = getUtility(IBugTaskSet).createTask(
706 ... bug, owner=getUtility(ILaunchBag).user, product=evolution)
707- >>> bugs_fixed = view.bugs_fixed_elsewhere_info
708- >>> bugs_fixed['count']
709+ >>> view.bugs_fixed_elsewhere_count
710 0
711
712 But if we mark the bug as fixed in the other, the count will increase
713@@ -42,23 +55,22 @@
714 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
715 >>> syncUpdate(elsewhere)
716
717- >>> bugs_fixed = view.bugs_fixed_elsewhere_info
718- >>> bugs_fixed['count']
719+ >>> view.bugs_fixed_elsewhere_count
720 1
721
722 Bugs fixed elsewhere also show up when we perform an advanced bug
723 search, using the appropriate query string parameter to ask for "bugs
724 resolved elsewhere":
725
726- >>> view = getMultiAdapter(
727+ >>> search_view = getMultiAdapter(
728 ... (bugtarget,
729 ... LaunchpadTestRequest(
730 ... form={'field.status_upstream': 'resolved_upstream'})),
731 ... name='+bugs')
732- >>> view.initialize()
733- >>> navigator = view.search()
734+ >>> search_view.initialize()
735+ >>> navigator = search_view.search()
736
737- >>> for task in view.search().batch:
738+ >>> for task in search_view.search().batch:
739 ... for related_task in task.related_tasks:
740 ... print related_task.target.name
741 ... print related_task.status.name
742@@ -81,8 +93,7 @@
743 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
744 >>> syncUpdate(another_elsewhere)
745
746- >>> bugs_fixed = view.bugs_fixed_elsewhere_info
747- >>> bugs_fixed['count']
748+ >>> view.bugs_fixed_elsewhere_count
749 2
750
751 This means that No Privileges Person will see that there is only one bug
752@@ -90,11 +101,11 @@
753
754 >>> login('no-priv@canonical.com')
755 >>> view = getMultiAdapter(
756- ... (bugtarget, LaunchpadTestRequest()), name='+bugs-index')
757+ ... (bugtarget, LaunchpadTestRequest()),
758+ ... name='+bugtarget-portlet-bugfilters-stats')
759 >>> view.initialize()
760
761- >>> bugs_fixed = view.bugs_fixed_elsewhere_info
762- >>> bugs_fixed['count']
763+ >>> view.bugs_fixed_elsewhere_count
764 1
765
766 If the private bug is made public again, he will of course see that
767@@ -106,8 +117,7 @@
768 >>> syncUpdate(another_bug)
769
770 >>> login('no-priv@canonical.com')
771- >>> bugs_fixed = view.bugs_fixed_elsewhere_info
772- >>> bugs_fixed['count']
773+ >>> view.bugs_fixed_elsewhere_count
774 2
775
776
777@@ -118,8 +128,7 @@
778 >>> another_bug.duplicateof = bug
779 >>> syncUpdate(another_bug)
780
781- >>> bugs_fixed = view.bugs_fixed_elsewhere_info
782- >>> bugs_fixed['count']
783+ >>> view.bugs_fixed_elsewhere_count
784 1
785
786
787@@ -136,6 +145,5 @@
788 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
789 >>> syncUpdate(bugtask)
790
791- >>> bugs_fixed = view.bugs_fixed_elsewhere_info
792- >>> bugs_fixed['count']
793+ >>> view.bugs_fixed_elsewhere_count
794 0
795
796=== modified file 'lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt'
797--- lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt 2009-09-20 14:58:50 +0000
798+++ lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt 2009-12-07 15:04:15 +0000
799@@ -19,7 +19,8 @@
800 'Bugs in ALSA utilities'
801
802 >>> pending_watches_link = user_browser.getLink(
803- ... 'need forwarding upstream')
804+ ... 'Bugs fixed elsewhere')
805+
806
807 == Marking and searching for pending bugwatches ==
808
809
810=== modified file 'lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt'
811--- lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt 2009-09-11 14:03:37 +0000
812+++ lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt 2009-12-07 15:04:15 +0000
813@@ -137,7 +137,7 @@
814 request, issued by regular browsers via Javascript.
815
816 >>> browser.open(
817- ... "http://launchpad.dev/ubuntu/+bugtarget-portlet-bugfilters-content")
818+ ... "http://launchpad.dev/ubuntu/+bugtarget-portlet-bugfilters-info")
819 >>> browser.getLink("New").click()
820
821 The result set is filtered to show only New bugs.
822
823=== removed file 'lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-authenticated.txt'
824--- lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-authenticated.txt 2009-06-12 16:36:02 +0000
825+++ lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-authenticated.txt 1970-01-01 00:00:00 +0000
826@@ -1,19 +0,0 @@
827-The distribution bug listing contains a portlet that shows bug
828-statistics for the distro. Each statistic is a link to filter the
829-listing to show just those bugs. This portlet is served in a separate
830-request; the request is issued via Javascript from main bugs page
831-of a distribution.
832-
833- >>> print http(r"""
834- ... GET /debian/+bugtarget-portlet-bugfilters-content HTTP/1.1
835- ... Authorization: Basic dGVzdEBjYW5vbmljYWwuY29tOnRlc3Q=
836- ... Accept-Language: en-ca,en-us;q=0.8,en;q=0.5,fr-ca;q=0.3
837- ... """)
838- HTTP/1.1 200 Ok
839- ...Open...3...
840- ...Assigned to me...1...
841- ...Critical...0...
842- ...New...1...
843- ...Unassigned...2...
844- ...All bugs ever reported...4...
845- ...
846
847=== removed file 'lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-unauthenticated.txt'
848--- lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-unauthenticated.txt 2009-06-12 16:36:02 +0000
849+++ lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-unauthenticated.txt 1970-01-01 00:00:00 +0000
850@@ -1,16 +0,0 @@
851-The distribution bug listing contains a portlet that shows bug
852-statistics for the distro. Each statistic is a link to filter the
853-listing to show just those bugs. This portlet is served in a separate
854-request; the request is issued via Javascript from main bugs page
855-of a distribution.
856-
857- >>> print http(r"""
858- ... GET /debian/+bugtarget-portlet-bugfilters-content HTTP/1.1
859- ... """)
860- HTTP/1.1 200 Ok
861- ...Open...3...
862- ...Critical...0...
863- ...New...1...
864- ...Unassigned...2...
865- ...All bugs ever reported...4...
866- ...
867
868=== added file 'lib/lp/bugs/stories/xx-bugs-statistics-portlet.txt'
869--- lib/lp/bugs/stories/xx-bugs-statistics-portlet.txt 1970-01-01 00:00:00 +0000
870+++ lib/lp/bugs/stories/xx-bugs-statistics-portlet.txt 2009-12-07 15:04:15 +0000
871@@ -0,0 +1,334 @@
872+= Bug statistics portlet =
873+
874+The distribution, project group and project bug listings contain a
875+portlet that shows bug statistics for the target. Each statistic is a
876+link to filter the listing to show just those bugs. This portlet is
877+served in a separate request; the request is issued via Javascript and
878+inserted into the page later.
879+
880+ >>> from BeautifulSoup import BeautifulSoup
881+
882+ >>> def print_portlet(browser, path):
883+ ... browser.open(
884+ ... 'http://bugs.launchpad.dev/%s/+portlet-bugfilters' % path)
885+ ... table = BeautifulSoup(browser.contents).find('table', 'bug-links')
886+ ... tbody_info, tbody_links = table('tbody')
887+ ... print_table(tbody_info)
888+ ... for link in tbody_links('a'):
889+ ... text = extract_text(link)
890+ ... if len(text) > 0:
891+ ... print "%s\n --> %s" % (text, link['href'])
892+
893+ >>> def print_portlet_contents(browser, path):
894+ ... browser.open(
895+ ... 'http://bugs.launchpad.dev'
896+ ... '/%s/+bugtarget-portlet-bugfilters-stats' % path)
897+ ... table = BeautifulSoup('<table>%s</table>' % browser.contents)
898+ ... print_table(table)
899+
900+
901+== Distribution ==
902+
903+ >>> path = 'debian'
904+
905+If the user is not logged-in a subscribe link is shown along with some
906+general stats.
907+
908+ >>> print_portlet(anon_browser, path)
909+ Open bugs
910+ Critical bugs
911+ Bugs fixed elsewhere
912+ New bugs
913+ Open CVE bugs - CVE reports
914+ Subscribe to bug mail
915+ --> http://bugs.launchpad.dev/debian/+subscribe
916+
917+ >>> print_portlet_contents(anon_browser, path)
918+ 3 Open bugs
919+ 0 Critical bugs
920+ 0 Bugs fixed elsewhere
921+ 1 New bugs
922+ 2 Open CVE bugs - CVE reports
923+
924+Once the user has identified him or herself, information on assigned
925+bugs is also shown.
926+
927+ >>> print_portlet(user_browser, path)
928+ Open bugs
929+ Bugs assigned to me
930+ Critical bugs
931+ Bugs fixed elsewhere
932+ New bugs
933+ Open CVE bugs - CVE reports
934+ Subscribe to bug mail
935+ --> http://bugs.launchpad.dev/debian/+subscribe
936+
937+ >>> print_portlet_contents(user_browser, path)
938+ 3 Open bugs
939+ 0 Bugs assigned to me
940+ 0 Critical bugs
941+ 0 Bugs fixed elsewhere
942+ 1 New bugs
943+ 2 Open CVE bugs - CVE reports
944+
945+The content includes a link to the distribution CVE report.
946+
947+ >>> print user_browser.getLink('CVE reports').url
948+ http://bugs.launchpad.dev/debian/+cve
949+
950+
951+== Distribution Series ==
952+
953+ >>> path = 'debian/woody'
954+
955+If the user is not logged-in general stats are shown. There is also a
956+link to review nominations.
957+
958+ >>> print_portlet(anon_browser, path)
959+ Open bugs
960+ Critical bugs
961+ Bugs fixed elsewhere
962+ New bugs
963+ Open CVE bugs - CVE reports
964+ Subscribe to bug mail
965+ --> http://bugs.launchpad.dev/debian/woody/+subscribe
966+ Review nominations
967+ --> http://bugs.launchpad.dev/debian/woody/+nominations
968+
969+ >>> print_portlet_contents(anon_browser, path)
970+ 2 Open bugs
971+ 0 Critical bugs
972+ 0 Bugs fixed elsewhere
973+ 2 New bugs
974+ 1 Open CVE bugs - CVE reports
975+
976+Once the user has identified him or herself, information on assigned
977+bugs is also shown.
978+
979+ >>> print_portlet(user_browser, path)
980+ Open bugs
981+ Bugs assigned to me
982+ Critical bugs
983+ Bugs fixed elsewhere
984+ New bugs
985+ Open CVE bugs - CVE reports
986+ Subscribe to bug mail
987+ --> http://bugs.launchpad.dev/debian/woody/+subscribe
988+ Review nominations
989+ --> http://bugs.launchpad.dev/debian/woody/+nominations
990+
991+ >>> print_portlet_contents(user_browser, path)
992+ 2 Open bugs
993+ 0 Bugs assigned to me
994+ 0 Critical bugs
995+ 0 Bugs fixed elsewhere
996+ 2 New bugs
997+ 1 Open CVE bugs - CVE reports
998+
999+The content includes a link to the distribution CVE report.
1000+
1001+ >>> print user_browser.getLink('CVE reports').url
1002+ http://bugs.launchpad.dev/debian/woody/+cve
1003+
1004+
1005+== Distribution Source Package ==
1006+
1007+ >>> path = 'debian/+source/mozilla-firefox'
1008+
1009+If the user is not logged-in general stats are shown.
1010+
1011+ >>> print_portlet(anon_browser, path)
1012+ Open bugs
1013+ Critical bugs
1014+ Bugs fixed elsewhere
1015+ New bugs
1016+ Open CVE bugs
1017+ Subscribe to bug mail
1018+ --> http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+subscribe
1019+
1020+ >>> print_portlet_contents(anon_browser, path)
1021+ 3 Open bugs
1022+ 0 Critical bugs
1023+ 0 Bugs fixed elsewhere
1024+ 1 New bugs
1025+ 2 Open CVE bugs
1026+
1027+Once the user has identified him or herself, information on assigned
1028+bugs is also shown.
1029+
1030+ >>> print_portlet(user_browser, path)
1031+ Open bugs
1032+ Bugs assigned to me
1033+ Critical bugs
1034+ Bugs fixed elsewhere
1035+ New bugs
1036+ Open CVE bugs
1037+ Subscribe to bug mail
1038+ --> http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+subscribe
1039+
1040+ >>> print_portlet_contents(user_browser, path)
1041+ 3 Open bugs
1042+ 0 Bugs assigned to me
1043+ 0 Critical bugs
1044+ 0 Bugs fixed elsewhere
1045+ 1 New bugs
1046+ 2 Open CVE bugs
1047+
1048+Note that the "CVE reports" link is not shown above; distribution
1049+source packages do not have a CVE reports page.
1050+
1051+ >>> print user_browser.getLink('CVE reports').url
1052+ Traceback (most recent call last):
1053+ ...
1054+ LinkNotFoundError
1055+
1056+
1057+== Source Package in Distribution Series ==
1058+
1059+ >>> path = 'debian/woody/+source/mozilla-firefox'
1060+
1061+If the user is not logged-in general stats are shown. There is no
1062+option to subscribe to bug mail.
1063+
1064+ >>> print_portlet(anon_browser, path)
1065+ Open bugs
1066+ Critical bugs
1067+ Bugs fixed elsewhere
1068+ New bugs
1069+ Open CVE bugs
1070+
1071+ >>> print_portlet_contents(anon_browser, path)
1072+ 2 Open bugs
1073+ 0 Critical bugs
1074+ 0 Bugs fixed elsewhere
1075+ 2 New bugs
1076+ 1 Open CVE bugs
1077+
1078+Once the user has identified him or herself, information on assigned
1079+bugs is also shown.
1080+
1081+ >>> print_portlet(user_browser, path)
1082+ Open bugs
1083+ Bugs assigned to me
1084+ Critical bugs
1085+ Bugs fixed elsewhere
1086+ New bugs
1087+ Open CVE bugs
1088+
1089+ >>> print_portlet_contents(user_browser, path)
1090+ 2 Open bugs
1091+ 0 Bugs assigned to me
1092+ 0 Critical bugs
1093+ 0 Bugs fixed elsewhere
1094+ 2 New bugs
1095+ 1 Open CVE bugs
1096+
1097+Note that the "CVE reports" link is not shown above; source packages
1098+do not have a CVE reports page.
1099+
1100+ >>> print user_browser.getLink('CVE reports').url
1101+ Traceback (most recent call last):
1102+ ...
1103+ LinkNotFoundError
1104+
1105+
1106+== Project group ==
1107+
1108+ >>> path = 'mozilla'
1109+
1110+If the user is not logged-in general stats are shown.
1111+
1112+ >>> print_portlet(anon_browser, path)
1113+ Open bugs
1114+ Critical bugs
1115+ Bugs fixed elsewhere
1116+ New bugs
1117+ Open CVE bugs
1118+ Subscribe to bug mail
1119+ --> http://bugs.launchpad.dev/mozilla/+subscribe
1120+
1121+ >>> print_portlet_contents(anon_browser, path)
1122+ 4 Open bugs
1123+ 1 Critical bugs
1124+ 0 Bugs fixed elsewhere
1125+ 4 New bugs
1126+ 1 Open CVE bugs
1127+
1128+Once the user has identified him or herself, information on assigned
1129+bugs is also shown.
1130+
1131+ >>> print_portlet(user_browser, path)
1132+ Open bugs
1133+ Bugs assigned to me
1134+ Critical bugs
1135+ Bugs fixed elsewhere
1136+ New bugs
1137+ Open CVE bugs
1138+ Subscribe to bug mail
1139+ --> http://bugs.launchpad.dev/mozilla/+subscribe
1140+
1141+ >>> print_portlet_contents(user_browser, path)
1142+ 4 Open bugs
1143+ 0 Bugs assigned to me
1144+ 1 Critical bugs
1145+ 0 Bugs fixed elsewhere
1146+ 4 New bugs
1147+ 1 Open CVE bugs
1148+
1149+Note that the "CVE reports" link is not shown above; project groups do
1150+not have a CVE reports page.
1151+
1152+ >>> print user_browser.getLink('CVE reports').url
1153+ Traceback (most recent call last):
1154+ ...
1155+ LinkNotFoundError
1156+
1157+
1158+== Project ==
1159+
1160+ >>> path = 'firefox'
1161+
1162+If the user is not logged-in general stats are shown.
1163+
1164+ >>> print_portlet(anon_browser, path)
1165+ Open bugs
1166+ Critical bugs
1167+ Bugs fixed elsewhere
1168+ New bugs
1169+ Open CVE bugs - CVE reports
1170+ Subscribe to bug mail
1171+ --> http://bugs.launchpad.dev/firefox/+subscribe
1172+
1173+ >>> print_portlet_contents(anon_browser, path)
1174+ 3 Open bugs
1175+ 1 Critical bugs
1176+ 0 Bugs fixed elsewhere
1177+ 3 New bugs
1178+ 1 Open CVE bugs - CVE reports
1179+
1180+Once the user has identified him or herself, information on assigned
1181+bugs is also shown.
1182+
1183+ >>> print_portlet(user_browser, path)
1184+ Open bugs
1185+ Bugs assigned to me
1186+ Critical bugs
1187+ Bugs fixed elsewhere
1188+ New bugs
1189+ Open CVE bugs - CVE reports
1190+ Subscribe to bug mail
1191+ --> http://bugs.launchpad.dev/firefox/+subscribe
1192+
1193+ >>> print_portlet_contents(user_browser, path)
1194+ 3 Open bugs
1195+ 0 Bugs assigned to me
1196+ 1 Critical bugs
1197+ 0 Bugs fixed elsewhere
1198+ 3 New bugs
1199+ 1 Open CVE bugs - CVE reports
1200+
1201+
1202+The content includes a link to the distribution CVE report.
1203+
1204+ >>> print user_browser.getLink('CVE reports').url
1205+ http://bugs.launchpad.dev/firefox/+cve
1206
1207=== modified file 'lib/lp/bugs/templates/bugtarget-bugs.pt'
1208--- lib/lp/bugs/templates/bugtarget-bugs.pt 2009-11-18 20:28:03 +0000
1209+++ lib/lp/bugs/templates/bugtarget-bugs.pt 2009-12-07 15:04:15 +0000
1210@@ -34,94 +34,7 @@
1211 </li>
1212 </ul>
1213 </div>
1214- <div class="portlet">
1215- <table class="bug-links">
1216- <tr tal:define="open_bugs_info view/open_bugs_info">
1217- <td class="bugs-count" tal:content="open_bugs_info/count" />
1218- <td class="bugs-link">
1219- <a tal:attributes="href open_bugs_info/url">Open bugs</a>
1220- </td>
1221- </tr>
1222- <tr tal:condition="view/user"
1223- tal:define="my_bugs_info view/my_bugs_info">
1224- <td class="bugs-count" tal:content="my_bugs_info/count" />
1225- <td class="bugs-link">
1226- <a tal:attributes="href my_bugs_info/url">Bugs assigned to me</a>
1227- </td>
1228- </tr>
1229- <tr tal:define="critical_bugs_info view/critical_bugs_info">
1230- <td class="bugs-count" tal:content="critical_bugs_info/count" />
1231- <td class="bugs-link">
1232- <a tal:attributes="href critical_bugs_info/url">Critical bugs</a>
1233- </td>
1234- </tr>
1235- <tr tal:define="fixed_elsewhere view/bugs_fixed_elsewhere_info">
1236- <td class="bugs-count" tal:content="fixed_elsewhere/count" />
1237- <td class="bugs-link">
1238- <a tal:attributes="href fixed_elsewhere/url">
1239- Bugs fixed elsewhere
1240- </a>
1241- </td>
1242- </tr>
1243- <tr tal:define="new_bugs_info view/new_bugs_info">
1244- <td class="bugs-count" tal:content="new_bugs_info/count" />
1245- <td class="bugs-link">
1246- <a tal:attributes="href new_bugs_info/url">New bugs</a>
1247- </td>
1248- </tr>
1249- <tr tal:define="cve_bugs_info view/open_cve_bugs_info">
1250- <td class="bugs-count" tal:content="cve_bugs_info/count" />
1251- <td class="bugs-link">
1252- <a tal:attributes="href cve_bugs_info/url">Open CVE bugs</a>
1253- -
1254- <a href="+cve">CVE reports</a>
1255- </td>
1256- </tr>
1257- <tr tal:define="expirable_bugs_info view/expirable_bugs_info"
1258- tal:condition="expirable_bugs_info">
1259- <td class="bugs-count" tal:content="expirable_bugs_info/count" />
1260- <td class="bugs-link">
1261- <a tal:attributes="href expirable_bugs_info/url">
1262- Incomplete bugs
1263- </a>
1264- (can expire)
1265- </td>
1266- </tr>
1267- <tr tal:define="pending_bugwatches view/pending_bugwatches_info"
1268- tal:condition="pending_bugwatches">
1269- <td class="bugs-count" tal:content="pending_bugwatches/count" />
1270- <td class="bugs-link">
1271- <a tal:attributes="href pending_bugwatches/url">
1272- Bugs need forwarding upstream
1273- </a>
1274- </td>
1275- </tr>
1276- <tr tal:define="subscribe_link context/menu:bugs/subscribe">
1277- <td class="bugs-count" style="padding-top: 1em">
1278- <a tal:attributes="href subscribe_link/url">
1279- <img tal:attributes="src subscribe_link/icon_url" />
1280- </a>
1281- </td>
1282- <td class="bugs-link">
1283- <a tal:attributes="href subscribe_link/url"
1284- tal:content="subscribe_link/escapedtext" />
1285- </td>
1286- </tr>
1287- <tr tal:define="
1288- review_nominations_link context/menu:bugs/nominations|nothing"
1289- tal:condition="review_nominations_link">
1290- <td class="bugs-count" style="padding-top: 1em">
1291- <a tal:attributes="href review_nominations_link/url">
1292- <img tal:attributes="src review_nominations_link/icon_url" />
1293- </a>
1294- </td>
1295- <td class="bugs-link">
1296- <a tal:attributes="href review_nominations_link/url"
1297- tal:content="review_nominations_link/escapedtext" />
1298- </td>
1299- </tr>
1300- </table>
1301- </div>
1302+ <div tal:replace="structure context/@@+portlet-bugfilters" />
1303 <div tal:replace="structure context/@@+portlet-bugtags" />
1304 <tal:releasecriticalbugs
1305 tal:condition="view/shouldShowReleaseCriticalPortlet"
1306
1307=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters-content.pt'
1308--- lib/lp/bugs/templates/bugtarget-portlet-bugfilters-content.pt 2009-09-13 20:29:46 +0000
1309+++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters-content.pt 2009-12-07 15:04:15 +0000
1310@@ -1,41 +1,67 @@
1311-<div xmlns:tal="http://xml.zope.org/namespaces/tal"
1312- xmlns:metal="http://xml.zope.org/namespaces/metal">
1313-<table width="100%">
1314+<tal:portlet-bug-filters-content
1315+ xmlns:tal="http://xml.zope.org/namespaces/tal"
1316+ xmlns:metal="http://xml.zope.org/namespaces/metal">
1317+ <tal:comment condition="nothing">
1318+ The view/*_count|nothing expressions below are so that this
1319+ template can be rendered by a view that does not have count
1320+ information available.
1321+ </tal:comment>
1322 <tr>
1323- <td><a tal:attributes="href view/getOpenBugsURL">Open</a></td>
1324- <td tal:content="context/open_bugtasks/count">10</td>
1325+ <td class="bugs-count" tal:content="view/open_bugs_count|nothing" />
1326+ <td class="bugs-link">
1327+ <a tal:attributes="href view/open_bugs_url">Open bugs</a>
1328+ </td>
1329 </tr>
1330 <tr tal:condition="view/user">
1331- <td>
1332- <a href="#" tal:attributes="href view/getBugsAssignedToMeURL">
1333- Assigned to me
1334- </a>
1335- </td>
1336- <td tal:content="view/getBugsAssignedToMeCount">50</td>
1337- </tr>
1338- <tr>
1339- <td><a tal:attributes="href view/getCriticalBugsURL">Critical</a></td>
1340- <td tal:content="context/critical_bugtasks/count">42</td>
1341- </tr>
1342- <tr>
1343- <td>
1344- <a tal:attributes="href view/getNewBugsURL">New</a>
1345- </td>
1346- <td tal:content="context/new_bugtasks/count">42</td>
1347- </tr>
1348- <tr>
1349- <td>
1350- <a tal:attributes="href view/getUnassignedBugsURL">Unassigned</a>
1351- </td>
1352- <td tal:content="context/unassigned_bugtasks/count">42</td>
1353- </tr>
1354- <tr>
1355- <td>
1356- <a tal:attributes="href view/getAllBugsEverReportedURL">
1357- All bugs ever reported
1358- </a>
1359- </td>
1360- <td tal:content="context/all_bugtasks/count">42</td>
1361- </tr>
1362-</table>
1363-</div>
1364+ <td class="bugs-count" tal:content="view/my_bugs_count|nothing" />
1365+ <td class="bugs-link">
1366+ <a tal:attributes="href view/my_bugs_url">Bugs assigned to me</a>
1367+ </td>
1368+ </tr>
1369+ <tr>
1370+ <td class="bugs-count" tal:content="view/critical_bugs_count|nothing" />
1371+ <td class="bugs-link">
1372+ <a tal:attributes="href view/critical_bugs_url">Critical bugs</a>
1373+ </td>
1374+ </tr>
1375+ <tr>
1376+ <td class="bugs-count" tal:content="view/bugs_fixed_elsewhere_count|nothing" />
1377+ <td class="bugs-link">
1378+ <a tal:attributes="href view/bugs_fixed_elsewhere_url">
1379+ Bugs fixed elsewhere
1380+ </a>
1381+ </td>
1382+ </tr>
1383+ <tr>
1384+ <td class="bugs-count" tal:content="view/new_bugs_count|nothing" />
1385+ <td class="bugs-link">
1386+ <a tal:attributes="href view/new_bugs_url">New bugs</a>
1387+ </td>
1388+ </tr>
1389+ <tr>
1390+ <td class="bugs-count" tal:content="view/open_cve_bugs_count|nothing" />
1391+ <td class="bugs-link">
1392+ <a tal:attributes="href view/open_cve_bugs_url">Open CVE bugs</a>
1393+ <span tal:condition="view/open_cve_bugs_has_report">
1394+ - <a tal:attributes="href context/fmt:url/+cve">CVE reports</a>
1395+ </span>
1396+ </td>
1397+ </tr>
1398+ <tr tal:condition="view/expirable_bugs_url">
1399+ <td class="bugs-count" tal:content="view/expirable_bugs_count|nothing" />
1400+ <td class="bugs-link">
1401+ <a tal:attributes="href view/expirable_bugs_url">
1402+ Incomplete bugs
1403+ </a>
1404+ (can expire)
1405+ </td>
1406+ </tr>
1407+ <tr tal:condition="view/pending_bugwatches_url">
1408+ <td class="bugs-count" tal:content="view/pending_bugwatches_count|nothing" />
1409+ <td class="bugs-link">
1410+ <a tal:attributes="href view/pending_bugwatches_url">
1411+ Bugs need forwarding upstream
1412+ </a>
1413+ </td>
1414+ </tr>
1415+</tal:portlet-bug-filters-content>
1416
1417=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt'
1418--- lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-12-03 18:33:22 +0000
1419+++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-12-07 15:04:15 +0000
1420@@ -4,34 +4,53 @@
1421 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
1422 class="portlet" id="portlet-bugfilters">
1423 <a id="bugtarget-bugfilters-link"
1424- tal:attributes="href context/fmt:url/+bugtarget-portlet-bugfilters-content" >
1425+ tal:attributes="href context/fmt:url/+bugtarget-portlet-bugfilters-stats">
1426 </a>
1427- <h2>Filters</h2>
1428- <div id="bugfilters-portlet-spinner"
1429- style="text-align: center; display: none">
1430- <img src="/@@/spinner" />
1431- </div>
1432- <script type="text/javascript">
1433+ <table class="bug-links">
1434+ <tbody id="bugfilters-portlet-content"
1435+ tal:content="structure context/@@+bugtarget-portlet-bugfilters-info" />
1436+ <tbody tal:define="menu context/menu:bugs">
1437+ <tr tal:define="subscribe_link menu/subscribe|nothing"
1438+ tal:condition="subscribe_link">
1439+ <td class="bugs-count" style="padding-top: 1em">
1440+ <a tal:attributes="href subscribe_link/url">
1441+ <img tal:attributes="src subscribe_link/icon_url" />
1442+ </a>
1443+ </td>
1444+ <td class="bugs-link">
1445+ <a tal:attributes="href subscribe_link/url"
1446+ tal:content="subscribe_link/escapedtext" />
1447+ </td>
1448+ </tr>
1449+ <tr tal:define="review_nominations_link context/menu:bugs/nominations|nothing"
1450+ tal:condition="review_nominations_link">
1451+ <td class="bugs-count" style="padding-top: 1em">
1452+ <a tal:attributes="href review_nominations_link/url">
1453+ <img tal:attributes="src review_nominations_link/icon_url" />
1454+ </a>
1455+ </td>
1456+ <td class="bugs-link">
1457+ <a tal:attributes="href review_nominations_link/url"
1458+ tal:content="review_nominations_link/escapedtext" />
1459+ </td>
1460+ </tr>
1461+ </tbody>
1462+ </table>
1463+ <script type="text/javascript">
1464 LPS.use('io-base', 'node', function(Y) {
1465- Y.on('domready', function() {
1466- var portlet = Y.one('#portlet-bugfilters');
1467- Y.one('#bugfilters-portlet-spinner').setStyle('display', 'block');
1468-
1469- function hide_spinner() {
1470- Y.one('#bugfilters-portlet-spinner').setStyle('display', 'none');
1471- }
1472-
1473- function on_success(transactionid, response, arguments) {
1474- hide_spinner();
1475- portlet.set('innerHTML',
1476- portlet.get('innerHTML') + response.responseText);
1477- }
1478-
1479- var config = {on: {success: on_success,
1480- failure: hide_spinner}};
1481- var url = Y.one('#bugtarget-bugfilters-link').getAttribute('href');
1482- var request = Y.io(url, config);
1483- });
1484+ Y.on('domready', function() {
1485+ var url = Y.one('#bugtarget-bugfilters-link').getAttribute('href');
1486+ var handlers = {
1487+ success: function(transactionid, response, arguments) {
1488+ Y.one('#bugfilters-portlet-content').set(
1489+ 'innerHTML', response.responseText);
1490+ },
1491+ failure: function() {
1492+ Y.one('#bugfilters-portlet-content').set('innerHTML', '');
1493+ }
1494+ };
1495+ var request = Y.io(url, {on: handlers});
1496+ });
1497 });
1498- </script>
1499+ </script>
1500 </div>
1501
1502=== modified file 'lib/lp/registry/browser/project.py'
1503--- lib/lp/registry/browser/project.py 2009-11-13 21:55:42 +0000
1504+++ lib/lp/registry/browser/project.py 2009-12-07 15:04:15 +0000
1505@@ -292,12 +292,16 @@
1506
1507 usedfor = IProject
1508 facet = 'bugs'
1509- links = ['new']
1510+ links = ['new', 'subscribe']
1511
1512 def new(self):
1513 text = 'Report a Bug'
1514 return Link('+filebug', text, icon='add')
1515
1516+ def subscribe(self):
1517+ text = 'Subscribe to bug mail'
1518+ return Link('+subscribe', text, icon='edit')
1519+
1520
1521 class ProjectView(HasAnnouncementsView, FeedsMixin):
1522 implements(IProjectActionMenu)