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
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2009-11-17 02:33:27 +0000
+++ lib/lp/bugs/browser/bugtask.py 2009-12-07 15:04:15 +0000
@@ -8,7 +8,8 @@
8__all__ = [8__all__ = [
9 'assignee_renderer',9 'assignee_renderer',
10 'BugListingBatchNavigator',10 'BugListingBatchNavigator',
11 'BugListingPortletView',11 'BugListingPortletInfoView',
12 'BugListingPortletStatsView',
12 'BugNominationsView',13 'BugNominationsView',
13 'bugtarget_renderer',14 'bugtarget_renderer',
14 'BugTargetTraversalMixin',15 'BugTargetTraversalMixin',
@@ -56,7 +57,8 @@
56 IInputWidget, IDisplayWidget, InputErrors, WidgetsError)57 IInputWidget, IDisplayWidget, InputErrors, WidgetsError)
57from zope.app.form.utility import setUpWidget, setUpWidgets58from zope.app.form.utility import setUpWidget, setUpWidgets
58from zope.component import (59from zope.component import (
59 ComponentLookupError, getAdapter, getUtility, getMultiAdapter)60 ComponentLookupError, getAdapter, getMultiAdapter, getUtility,
61 queryMultiAdapter)
60from zope.event import notify62from zope.event import notify
61from zope import formlib63from zope import formlib
62from zope.interface import implementer, implements, Interface, providedBy64from zope.interface import implementer, implements, Interface, providedBy
@@ -107,8 +109,7 @@
107 IDistroSeriesBugTask, IFrontPageBugTaskSearch,109 IDistroSeriesBugTask, IFrontPageBugTaskSearch,
108 INominationsReviewTableBatchNavigator, INullBugTask, IPersonBugTaskSearch,110 INominationsReviewTableBatchNavigator, INullBugTask, IPersonBugTaskSearch,
109 IProductSeriesBugTask, IRemoveQuestionFromBugTaskForm, IUpstreamBugTask,111 IProductSeriesBugTask, IRemoveQuestionFromBugTaskForm, IUpstreamBugTask,
110 IUpstreamProductBugTaskSearch, RESOLVED_BUGTASK_STATUSES,112 IUpstreamProductBugTaskSearch, UNRESOLVED_BUGTASK_STATUSES)
111 UNRESOLVED_BUGTASK_STATUSES)
112from lp.bugs.interfaces.bugtracker import BugTrackerType113from lp.bugs.interfaces.bugtracker import BugTrackerType
113from lp.bugs.interfaces.cve import ICveSet114from lp.bugs.interfaces.cve import ICveSet
114from lp.registry.interfaces.distribution import IDistribution115from lp.registry.interfaces.distribution import IDistribution
@@ -292,6 +293,24 @@
292 return ["-importance"]293 return ["-importance"]
293294
294295
296def get_default_search_params(user):
297 """Return a BugTaskSearchParams instance with default values.
298
299 By default, a search includes any bug that is unresolved and not a
300 duplicate of another bug.
301
302 If this search will be used to display a list of bugs to the user
303 it may be a good idea to set the orderby attribute using
304 get_sortorder_from_request():
305
306 params = get_default_search_params(user)
307 params.orderby = get_sortorder_from_request(request)
308
309 """
310 return BugTaskSearchParams(
311 user=user, status=any(*UNRESOLVED_BUGTASK_STATUSES), omit_dupes=True)
312
313
295OLD_BUGTASK_STATUS_MAP = {314OLD_BUGTASK_STATUS_MAP = {
296 'Unconfirmed': 'New',315 'Unconfirmed': 'New',
297 'Needs Info': 'Incomplete',316 'Needs Info': 'Incomplete',
@@ -1668,59 +1687,162 @@
1668 return u""1687 return u""
16691688
16701689
1671class BugListingPortletView(LaunchpadView):1690class BugsInfoMixin:
1672 """Portlet containing all available bug listings."""1691 """Contains properties giving URLs to bug information."""
1673 def getOpenBugsURL(self):1692
1674 """Return the URL for open bugs on this bug target."""1693 @property
1675 return get_buglisting_search_filter_url(1694 def bugs_fixed_elsewhere_url(self):
1676 status=[status.title for status in UNRESOLVED_BUGTASK_STATUSES])1695 """A URL to a list of bugs fixed elsewhere."""
16771696 return "%s?field.status_upstream=resolved_upstream" % (
1678 def getBugsAssignedToMeURL(self):1697 canonical_url(self.context, view_name='+bugs'))
1679 """Return the URL for bugs assigned to the current user on target."""1698
1680 if self.user:1699 @property
1681 return get_buglisting_search_filter_url(assignee=self.user.name)1700 def open_cve_bugs_url(self):
1701 """A URL to a list of open bugs linked to CVEs."""
1702 return "%s?field.has_cve=on" % (
1703 canonical_url(self.context, view_name='+bugs'))
1704
1705 @property
1706 def open_cve_bugs_has_report(self):
1707 """Whether or not the context has a CVE report page."""
1708 return queryMultiAdapter(
1709 (self.context, self.request), name='+cve') is not None
1710
1711 @property
1712 def pending_bugwatches_url(self):
1713 """A URL to a list of bugs that need a bugwatch.
1714
1715 None is returned if the context is not an upstream product.
1716 """
1717 if not IProduct.providedBy(self.context):
1718 return None
1719 if self.context.official_malone:
1720 return None
1721 return "%s?field.status_upstream=pending_bugwatch" % (
1722 canonical_url(self.context, view_name='+bugs'))
1723
1724 @property
1725 def expirable_bugs_url(self):
1726 """A URL to a list of bugs that can expire, or None.
1727
1728 If the bugtarget is not a supported implementation, or its pillar
1729 does not have enable_bug_expiration set to True, None is returned.
1730 The bugtarget may be an `IDistribution`, `IDistroSeries`, `IProduct`,
1731 or `IProductSeries`.
1732 """
1733 if target_has_expirable_bugs_listing(self.context):
1734 return canonical_url(self.context, view_name='+expirable-bugs')
1682 else:1735 else:
1683 return str(self.request.URL) + "/+login"1736 return None
16841737
1685 def getBugsAssignedToMeCount(self):1738 @property
1686 """Return the count of bugs assinged to the logged-in user."""1739 def new_bugs_url(self):
1687 assert self.user, (1740 """A URL to a page of new bugs."""
1688 "Counting 'bugs assigned to me' requires a logged-in user")1741 return get_buglisting_search_filter_url(
16891742 status=BugTaskStatus.NEW.title)
1690 search_params = BugTaskSearchParams(1743
1691 user=self.user, assignee=self.user,1744 @property
1692 status=any(*UNRESOLVED_BUGTASK_STATUSES),1745 def open_bugs_url(self):
1693 omit_dupes=True)1746 """A URL to a list of open bugs."""
16941747 return canonical_url(self.context, view_name='+bugs')
1695 return self.context.searchTasks(search_params).count()1748
16961749 @property
1697 def getCriticalBugsURL(self):1750 def critical_bugs_url(self):
1698 """Return the URL for critical bugs on this bug target."""1751 """A URL to a list of critical bugs."""
1699 return get_buglisting_search_filter_url(1752 return get_buglisting_search_filter_url(
1700 status=[status.title for status in UNRESOLVED_BUGTASK_STATUSES],1753 status=[status.title for status in UNRESOLVED_BUGTASK_STATUSES],
1701 importance=BugTaskImportance.CRITICAL.title)1754 importance=BugTaskImportance.CRITICAL.title)
17021755
1703 def getUnassignedBugsURL(self):1756 @property
1704 """Return the URL for critical bugs on this bug target."""1757 def my_bugs_url(self):
1705 unresolved_tasks_query_string = get_buglisting_search_filter_url(1758 """A URL to a list of bugs assigned to the user, or None."""
1706 status=[status.title for status in UNRESOLVED_BUGTASK_STATUSES])1759 if self.user is None:
17071760 return None
1708 return unresolved_tasks_query_string + "&assignee_option=none"1761 else:
17091762 return get_buglisting_search_filter_url(assignee=self.user.name)
1710 def getNewBugsURL(self):1763
1711 """Return the URL for new bugs on this bug target."""1764
1712 return get_buglisting_search_filter_url(1765class BugsStatsMixin(BugsInfoMixin):
1713 status=BugTaskStatus.NEW.title)1766 """Contains properties giving bug stats.
17141767
1715 def getAllBugsEverReportedURL(self):1768 These can be expensive to obtain.
1716 """Return the URL to list all bugs reported."""1769 """
1717 all_statuses = UNRESOLVED_BUGTASK_STATUSES + RESOLVED_BUGTASK_STATUSES1770
1718 all_status_query_string = get_buglisting_search_filter_url(1771 @property
1719 status=[status.title for status in all_statuses])1772 def bugs_fixed_elsewhere_count(self):
17201773 """A count of bugs fixed elsewhere."""
1721 # Add the bit that simulates the "omit dupes" checkbox1774 params = get_default_search_params(self.user)
1722 # being unchecked.1775 params.resolved_upstream = True
1723 return all_status_query_string + "&field.omit_dupes.used="1776 return self.context.searchTasks(params).count()
1777
1778 @property
1779 def open_cve_bugs_count(self):
1780 """A count of open bugs linked to CVEs."""
1781 params = get_default_search_params(self.user)
1782 params.has_cve = True
1783 return self.context.searchTasks(params).count()
1784
1785 @property
1786 def pending_bugwatches_count(self):
1787 """A count of bugs that need a bugwatch.
1788
1789 None is returned if the context is not an upstream product.
1790 """
1791 if not IProduct.providedBy(self.context):
1792 return None
1793 if self.context.official_malone:
1794 return None
1795 params = get_default_search_params(self.user)
1796 params.pending_bugwatch_elsewhere = True
1797 return self.context.searchTasks(params).count()
1798
1799 @property
1800 def expirable_bugs_count(self):
1801 """A count of bugs that can expire, or None.
1802
1803 If the bugtarget is not a supported implementation, or its pillar
1804 does not have enable_bug_expiration set to True, None is returned.
1805 The bugtarget may be an `IDistribution`, `IDistroSeries`, `IProduct`,
1806 or `IProductSeries`.
1807 """
1808 if target_has_expirable_bugs_listing(self.context):
1809 return getUtility(IBugTaskSet).findExpirableBugTasks(
1810 0, user=self.user, target=self.context).count()
1811 else:
1812 return None
1813
1814 @property
1815 def new_bugs_count(self):
1816 """A count of new bugs."""
1817 return self.context.new_bugtasks.count()
1818
1819 @property
1820 def open_bugs_count(self):
1821 """A count of open bugs."""
1822 return self.context.open_bugtasks.count()
1823
1824 @property
1825 def critical_bugs_count(self):
1826 """A count of critical bugs."""
1827 return self.context.critical_bugtasks.count()
1828
1829 @property
1830 def my_bugs_count(self):
1831 """A count of bugs assigned to the user, or None."""
1832 if self.user is None:
1833 return None
1834 else:
1835 params = get_default_search_params(self.user)
1836 params.assignee = self.user
1837 return self.context.searchTasks(params).count()
1838
1839
1840class BugListingPortletInfoView(LaunchpadView, BugsInfoMixin):
1841 """Portlet containing available bug listings without stats."""
1842
1843
1844class BugListingPortletStatsView(LaunchpadView, BugsStatsMixin):
1845 """Portlet containing available bug listings with stats."""
17241846
17251847
1726def get_buglisting_search_filter_url(1848def get_buglisting_search_filter_url(
@@ -1972,7 +2094,7 @@
1972 return Link('+nominations', 'Review nominations', icon='bug')2094 return Link('+nominations', 'Review nominations', icon='bug')
19732095
19742096
1975class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin):2097class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
1976 """View that renders a list of bugs for a given set of search criteria."""2098 """View that renders a list of bugs for a given set of search criteria."""
19772099
1978 implements(IBugTaskSearchListingMenu)2100 implements(IBugTaskSearchListingMenu)
@@ -2131,18 +2253,6 @@
2131 # LaunchpadFormView's own validation.2253 # LaunchpadFormView's own validation.
2132 pass2254 pass
21332255
2134 def _getDefaultSearchParams(self):
2135 """Return a BugTaskSearchParams instance with default values.
2136
2137 By default, a search includes any bug that is unresolved and not
2138 a duplicate of another bug.
2139 """
2140 search_params = BugTaskSearchParams(
2141 user=self.user, status=any(*UNRESOLVED_BUGTASK_STATUSES),
2142 omit_dupes=True)
2143 search_params.orderby = get_sortorder_from_request(self.request)
2144 return search_params
2145
2146 def buildSearchParams(self, searchtext=None, extra_params=None):2256 def buildSearchParams(self, searchtext=None, extra_params=None):
2147 """Build the BugTaskSearchParams object for the given arguments and2257 """Build the BugTaskSearchParams object for the given arguments and
2148 values specified by the user on this form's widgets.2258 values specified by the user on this form's widgets.
@@ -2213,7 +2323,8 @@
2213 else:2323 else:
2214 form_values['tag'] = tags2324 form_values['tag'] = tags
22152325
2216 search_params = self._getDefaultSearchParams()2326 search_params = get_default_search_params(self.user)
2327 search_params.orderby = get_sortorder_from_request(self.request)
2217 for name, value in form_values.items():2328 for name, value in form_values.items():
2218 setattr(search_params, name, value)2329 setattr(search_params, name, value)
2219 return search_params2330 return search_params
@@ -2602,140 +2713,6 @@
2602 """2713 """
2603 return IDistributionSourcePackage(self.context, None)2714 return IDistributionSourcePackage(self.context, None)
26042715
2605 def _bugOrBugs(self, count):
2606 """Return 'bug' if the count is 1, otherwise return 'bugs'.
2607
2608 zope.i18n does not support for ngettext-like functionality.
2609 """
2610 if count == 1:
2611 return 'bug'
2612 else:
2613 return 'bugs'
2614
2615 @property
2616 def bugs_fixed_elsewhere_info(self):
2617 """Return a dict with count and URL of bugs fixed elsewhere.
2618
2619 The available keys are:
2620 * 'count' - The number of bugs.
2621 * 'url' - The URL of the search.
2622 * 'label' - Either 'bug' or 'bugs' depending on the count.
2623 """
2624 params = self._getDefaultSearchParams()
2625 params.resolved_upstream = True
2626 fixed_elsewhere = self.context.searchTasks(params)
2627 count = fixed_elsewhere.count()
2628 label = self._bugOrBugs(count)
2629 search_url = (
2630 "%s/+bugs?field.status_upstream=resolved_upstream" %
2631 canonical_url(self.context))
2632 return dict(count=count, url=search_url, label=label)
2633
2634 @property
2635 def open_cve_bugs_info(self):
2636 """Return a dict with count and URL of open bugs linked to CVEs.
2637
2638 The available keys are:
2639 * 'count' - The number of bugs.
2640 * 'url' - The URL of the search.
2641 * 'label' - Either 'bug' or 'bugs' depending on the count.
2642 """
2643 params = self._getDefaultSearchParams()
2644 params.has_cve = True
2645 open_cve_bugs = self.context.searchTasks(params)
2646 count = open_cve_bugs.count()
2647 label = self._bugOrBugs(count)
2648 search_url = (
2649 "%s/+bugs?field.has_cve=on" % canonical_url(self.context))
2650 return dict(count=count, url=search_url, label=label)
2651
2652 @property
2653 def pending_bugwatches_info(self):
2654 """Return a dict with count and URL of bugs that need a bugwatch.
2655
2656 None is returned if the context is not an upstream product.
2657
2658 The available keys are:
2659 * 'count' - The number of bugs.
2660 * 'url' - The URL of the search.
2661 * 'label' - Either 'bug' or 'bugs' depending on the count.
2662 """
2663 if not self.isUpstreamProduct:
2664 return None
2665 params = self._getDefaultSearchParams()
2666 params.pending_bugwatch_elsewhere = True
2667 pending_bugwatch_elsewhere = self.context.searchTasks(params)
2668 count = pending_bugwatch_elsewhere.count()
2669 label = self._bugOrBugs(count)
2670 search_url = (
2671 "%s/+bugs?field.status_upstream=pending_bugwatch" %
2672 canonical_url(self.context))
2673 return dict(count=count, url=search_url, label=label)
2674
2675 @property
2676 def expirable_bugs_info(self):
2677 """Return a dict with count and url of bugs that can expire, or None.
2678
2679 If the bugtarget is not a supported implementation, or its pillar
2680 does not have enable_bug_expiration set to True, None is returned.
2681 The bugtarget may be an `IDistribution`, `IDistroSeries`, `IProduct`,
2682 or `IProductSeries`.
2683
2684 The available keys are:
2685 * 'count' - The number of bugs.
2686 * 'url' - The URL of the search, or None.
2687 * 'label' - Either 'bug' or 'bugs' depending on the count.
2688 """
2689 if not target_has_expirable_bugs_listing(self.context):
2690 return None
2691 bugtaskset = getUtility(IBugTaskSet)
2692 expirable_bugtasks = bugtaskset.findExpirableBugTasks(
2693 0, user=self.user, target=self.context)
2694 count = expirable_bugtasks.count()
2695 label = self._bugOrBugs(count)
2696 url = "%s/+expirable-bugs" % canonical_url(self.context)
2697 return dict(count=count, url=url, label=label)
2698
2699 @property
2700 def new_bugs_info(self):
2701 """Return a dict with new bugs info."""
2702 return dict(
2703 count=self.context.new_bugtasks.count,
2704 url=get_buglisting_search_filter_url(
2705 status=BugTaskStatus.NEW.title))
2706
2707 @property
2708 def open_bugs_info(self):
2709 """Return a dict with open bugs info."""
2710 return dict(
2711 count=self.context.open_bugtasks.count,
2712 url=canonical_url(
2713 self.context, rootsite='bugs', view_name='+bugs'))
2714
2715 @property
2716 def critical_bugs_info(self):
2717 """Return a dict with critical bugs info."""
2718 return dict(
2719 count=self.context.critical_bugtasks.count,
2720 url=get_buglisting_search_filter_url(
2721 status=[status.title for status
2722 in UNRESOLVED_BUGTASK_STATUSES],
2723 importance=BugTaskImportance.CRITICAL.title))
2724
2725 @property
2726 def my_bugs_info(self):
2727 """Return a dict with info on bugs assigned to the user, or None."""
2728 if self.user:
2729 return dict(
2730 count=self.context.searchTasks(
2731 BugTaskSearchParams(
2732 user=self.user, assignee=self.user,
2733 status=any(*UNRESOLVED_BUGTASK_STATUSES),
2734 omit_dupes=True)).count(),
2735 url=get_buglisting_search_filter_url(assignee=self.user.name))
2736 else:
2737 return None
2738
2739 @property2716 @property
2740 def hot_bugtasks(self):2717 def hot_bugtasks(self):
2741 """Return the 10 most recently updated bugtasks for this target."""2718 """Return the 10 most recently updated bugtasks for this target."""
@@ -3689,7 +3666,7 @@
3689 # If the user does not have permission to view the bug for3666 # If the user does not have permission to view the bug for
3690 # whatever reason, raise ComponentLookupError.3667 # whatever reason, raise ComponentLookupError.
3691 try:3668 try:
3692 name = context.bug.displayname3669 context.bug.displayname
3693 except Unauthorized:3670 except Unauthorized:
3694 raise ComponentLookupError()3671 raise ComponentLookupError()
36953672
36963673
=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml 2009-11-19 05:56:30 +0000
+++ lib/lp/bugs/browser/configure.zcml 2009-12-07 15:04:15 +0000
@@ -24,15 +24,21 @@
24 template="../templates/bugtarget-portlet-recently-touched-bugs.pt"/>24 template="../templates/bugtarget-portlet-recently-touched-bugs.pt"/>
25 <browser:page25 <browser:page
26 for="lp.bugs.interfaces.bugtarget.IHasBugs"26 for="lp.bugs.interfaces.bugtarget.IHasBugs"
27 class="lp.bugs.browser.bugtask.BugListingPortletView"27 class="lp.bugs.browser.bugtask.BugListingPortletInfoView"
28 permission="zope.Public"28 permission="zope.Public"
29 name="+portlet-bugfilters"29 name="+portlet-bugfilters"
30 template="../templates/bugtarget-portlet-bugfilters.pt"/>30 template="../templates/bugtarget-portlet-bugfilters.pt"/>
31 <browser:page31 <browser:page
32 for="lp.bugs.interfaces.bugtarget.IHasBugs"32 for="lp.bugs.interfaces.bugtarget.IHasBugs"
33 class="lp.bugs.browser.bugtask.BugListingPortletView"33 class="lp.bugs.browser.bugtask.BugListingPortletInfoView"
34 permission="zope.Public"34 permission="zope.Public"
35 name="+bugtarget-portlet-bugfilters-content"35 name="+bugtarget-portlet-bugfilters-info"
36 template="../templates/bugtarget-portlet-bugfilters-content.pt"/>
37 <browser:page
38 for="lp.bugs.interfaces.bugtarget.IHasBugs"
39 class="lp.bugs.browser.bugtask.BugListingPortletStatsView"
40 permission="zope.Public"
41 name="+bugtarget-portlet-bugfilters-stats"
36 template="../templates/bugtarget-portlet-bugfilters-content.pt"/>42 template="../templates/bugtarget-portlet-bugfilters-content.pt"/>
37 <browser:page43 <browser:page
38 for="lp.bugs.interfaces.bugtarget.IHasBugs"44 for="lp.bugs.interfaces.bugtarget.IHasBugs"
3945
=== modified file 'lib/lp/bugs/browser/tests/bugtask-search-views.txt'
--- lib/lp/bugs/browser/tests/bugtask-search-views.txt 2009-08-13 15:12:16 +0000
+++ lib/lp/bugs/browser/tests/bugtask-search-views.txt 2009-12-07 15:04:15 +0000
@@ -350,158 +350,6 @@
350 Mozilla Firefox 1.0350 Mozilla Firefox 1.0
351351
352352
353Bug Filters Portlet
354-------------------
355
356The bug filters portlet has links to various "canned" searches, and
357shows the number of bugs matching that filter beside each. The counts
358are (mostly) gotten from the context object itself. Since these URLs and
359counts are dependant on the current user, we will log in as Sample
360Person.
361
362 >>> from canonical.launchpad.interfaces import IProductSet
363
364 >>> login("test@canonical.com")
365
366 >>> firefox = getUtility(IProductSet).get(4)
367 >>> print firefox.name
368 firefox
369
370 >>> bugfilters_portlet_view = create_view(firefox, '+portlet-bugfilters')
371
372 >>> print bugfilters_portlet_view.getOpenBugsURL()
373 +bugs?search=Search&field.status=New&field.status=Incomplete&field.status=Confirmed&field.status=Triaged&field.status=In+Progress&field.status=Fix+Committed
374 >>> print bugfilters_portlet_view.context.open_bugtasks.count()
375 3
376
377 >>> print bugfilters_portlet_view.getBugsAssignedToMeURL()
378 +bugs?search=Search&field.assignee=name12
379
380 >>> print bugfilters_portlet_view.getBugsAssignedToMeCount()
381 1
382
383If Sample Person were assigned to second bug, but that bug was a dupe,
384it would not be included in the counts.
385
386 >>> from canonical.launchpad.interfaces import IBugTaskSet
387
388 >>> bug_six_in_firefox = getUtility(IBugTaskSet).get(15)
389 >>> print bug_six_in_firefox.bug.duplicateof.id
390 5
391 >>> print bugfilters_portlet_view.user.displayname
392 Sample Person
393
394 >>> bug_six_in_firefox.transitionToAssignee(
395 ... bugfilters_portlet_view.user)
396 >>> flush_database_updates()
397
398 >>> print bugfilters_portlet_view.getBugsAssignedToMeCount()
399 1
400
401The critical bugs link and counts consider only unresolved bugs.
402
403 >>> print bugfilters_portlet_view.getCriticalBugsURL()
404 +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
405
406 >>> bugfilters_portlet_view.context.critical_bugtasks.count()
407 1
408
409 >>> critical_task = bugfilters_portlet_view.context.critical_bugtasks[0]
410 >>> prev_status = critical_task.status
411 >>> critical_task.transitionToStatus(
412 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
413 >>> flush_database_updates()
414
415 >>> print bugfilters_portlet_view.context.critical_bugtasks.count()
416 0
417
418 >>> critical_task.transitionToStatus(
419 ... prev_status, getUtility(ILaunchBag).user)
420 >>> flush_database_updates()
421
422Moving along...
423
424 >>> print bugfilters_portlet_view.getNewBugsURL()
425 +bugs?search=Search&field.status=New
426
427 >>> print bugfilters_portlet_view.context.new_bugtasks.count()
428 3
429
430 >>> print bugfilters_portlet_view.getUnassignedBugsURL()
431 +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
432 >>> print bugfilters_portlet_view.context.unassigned_bugtasks.count()
433 1
434
435And finally, all bugs ever reported.
436
437 >>> print bugfilters_portlet_view.getAllBugsEverReportedURL()
438 +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=
439
440 >>> print bugfilters_portlet_view.context.all_bugtasks.count()
441 4
442
443All counts ignore bugtasks with status Unknown. Let's set bug four's
444status in firefox to Unknown and note how the count is decremented by 1
445for the open, new, unassigned, and all bugs ever reported
446counts.
447
448 >>> bug_four_in_firefox = getUtility(IBugTaskSet).get(13)
449
450 >>> print bug_four_in_firefox.bug.id
451 4
452 >>> print bug_four_in_firefox.product.name
453 firefox
454
455We'll examine the counts once more, to verify they are what we think
456they are.
457
458 >>> print bugfilters_portlet_view.context.open_bugtasks.count()
459 3
460 >>> print bugfilters_portlet_view.context.critical_bugtasks.count()
461 1
462 >>> print bugfilters_portlet_view.context.new_bugtasks.count()
463 3
464 >>> print bugfilters_portlet_view.context.unassigned_bugtasks.count()
465 1
466 >>> print bugfilters_portlet_view.context.all_bugtasks.count()
467 4
468
469Then change the status.
470
471 >>> bug_four_in_firefox.transitionToStatus(
472 ... BugTaskStatus.UNKNOWN, getUtility(ILaunchBag).user)
473 >>> flush_database_updates()
474
475And the affected counts have been decremented.
476
477 >>> print bugfilters_portlet_view.context.open_bugtasks.count()
478 2
479 >>> print bugfilters_portlet_view.context.critical_bugtasks.count()
480 1
481 >>> print bugfilters_portlet_view.context.new_bugtasks.count()
482 2
483 >>> print bugfilters_portlet_view.context.unassigned_bugtasks.count()
484 0
485 >>> print bugfilters_portlet_view.context.all_bugtasks.count()
486 3
487
488The unassigned bug counts consider only bugs with an unresolved status.
489
490 >>> bug_four_in_firefox.transitionToStatus(
491 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
492 >>> flush_database_updates()
493
494 >>> print bugfilters_portlet_view.context.unassigned_bugtasks.count()
495 0
496
497 >>> bug_four_in_firefox.transitionToStatus(
498 ... BugTaskStatus.FIXCOMMITTED, getUtility(ILaunchBag).user)
499 >>> flush_database_updates()
500
501 >>> print bugfilters_portlet_view.context.unassigned_bugtasks.count()
502 1
503
504
505== Constructing search filter urls ==353== Constructing search filter urls ==
506354
507There is a helper method, get_buglisting_search_filter_url(), which can355There is a helper method, get_buglisting_search_filter_url(), which can
508356
=== modified file 'lib/lp/bugs/browser/tests/special/bugs-fixed-elsewhere.txt'
--- lib/lp/bugs/browser/tests/special/bugs-fixed-elsewhere.txt 2009-06-12 16:36:02 +0000
+++ lib/lp/bugs/browser/tests/special/bugs-fixed-elsewhere.txt 2009-12-07 15:04:15 +0000
@@ -1,21 +1,35 @@
1= Bugs Fixed Elsewhere =1= Bugs Fixed Elsewhere =
22
3The +bugs-index view for a distribution or product contains a method for3The +bugtarget-portlet-bugfilters-info view for a distribution or
4getting information about bugs fixed in some other context. It includes4product contains a property for a URL to a list of bugs fixed
5both a count of how many bugs that are fixed elsewhere, as well as a5elsewhere.
6URL to the full list.
76
8 >>> from zope.component import getMultiAdapter7 >>> from zope.component import getMultiAdapter
9 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest8 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
10 >>> view = getMultiAdapter(9
11 ... (bugtarget, LaunchpadTestRequest()), name='+bugs-index')10 >>> view = getMultiAdapter(
12 >>> view.initialize()11 ... (bugtarget, LaunchpadTestRequest()),
1312 ... name='+bugtarget-portlet-bugfilters-info')
14 >>> bugs_fixed = view.bugs_fixed_elsewhere_info13 >>> view.initialize()
15 >>> bugs_fixed['count']14
15 >>> view.bugs_fixed_elsewhere_url
16 u'http://.../+bugs?field.status_upstream=resolved_upstream'
17
18The +bugtarget-portlet-bugfilters-stats view for a distribution or
19product contains the URL as above in addition to a count of how many
20bugs that are fixed elsewhere. This count can take a while to
21calculate, so it is put on this separate view which can be requested
22asyncronously.
23
24 >>> view = getMultiAdapter(
25 ... (bugtarget, LaunchpadTestRequest()),
26 ... name='+bugtarget-portlet-bugfilters-stats')
27 >>> view.initialize()
28
29 >>> view.bugs_fixed_elsewhere_url
30 u'http://.../+bugs?field.status_upstream=resolved_upstream'
31 >>> view.bugs_fixed_elsewhere_count
16 032 0
17 >>> bugs_fixed['url']
18 u'http://.../+bugs?field.status_upstream=resolved_upstream'
1933
20Simply opening a bug elsewhere won't increase the count.34Simply opening a bug elsewhere won't increase the count.
2135
@@ -29,8 +43,7 @@
29 >>> from canonical.launchpad.interfaces import IBugTaskSet43 >>> from canonical.launchpad.interfaces import IBugTaskSet
30 >>> elsewhere = getUtility(IBugTaskSet).createTask(44 >>> elsewhere = getUtility(IBugTaskSet).createTask(
31 ... bug, owner=getUtility(ILaunchBag).user, product=evolution)45 ... bug, owner=getUtility(ILaunchBag).user, product=evolution)
32 >>> bugs_fixed = view.bugs_fixed_elsewhere_info46 >>> view.bugs_fixed_elsewhere_count
33 >>> bugs_fixed['count']
34 047 0
3548
36But if we mark the bug as fixed in the other, the count will increase49But if we mark the bug as fixed in the other, the count will increase
@@ -42,23 +55,22 @@
42 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)55 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
43 >>> syncUpdate(elsewhere)56 >>> syncUpdate(elsewhere)
4457
45 >>> bugs_fixed = view.bugs_fixed_elsewhere_info58 >>> view.bugs_fixed_elsewhere_count
46 >>> bugs_fixed['count']
47 159 1
4860
49Bugs fixed elsewhere also show up when we perform an advanced bug61Bugs fixed elsewhere also show up when we perform an advanced bug
50search, using the appropriate query string parameter to ask for "bugs62search, using the appropriate query string parameter to ask for "bugs
51resolved elsewhere":63resolved elsewhere":
5264
53 >>> view = getMultiAdapter(65 >>> search_view = getMultiAdapter(
54 ... (bugtarget,66 ... (bugtarget,
55 ... LaunchpadTestRequest(67 ... LaunchpadTestRequest(
56 ... form={'field.status_upstream': 'resolved_upstream'})),68 ... form={'field.status_upstream': 'resolved_upstream'})),
57 ... name='+bugs')69 ... name='+bugs')
58 >>> view.initialize()70 >>> search_view.initialize()
59 >>> navigator = view.search()71 >>> navigator = search_view.search()
6072
61 >>> for task in view.search().batch:73 >>> for task in search_view.search().batch:
62 ... for related_task in task.related_tasks:74 ... for related_task in task.related_tasks:
63 ... print related_task.target.name75 ... print related_task.target.name
64 ... print related_task.status.name76 ... print related_task.status.name
@@ -81,8 +93,7 @@
81 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)93 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
82 >>> syncUpdate(another_elsewhere)94 >>> syncUpdate(another_elsewhere)
8395
84 >>> bugs_fixed = view.bugs_fixed_elsewhere_info96 >>> view.bugs_fixed_elsewhere_count
85 >>> bugs_fixed['count']
86 297 2
8798
88This means that No Privileges Person will see that there is only one bug99This means that No Privileges Person will see that there is only one bug
@@ -90,11 +101,11 @@
90101
91 >>> login('no-priv@canonical.com')102 >>> login('no-priv@canonical.com')
92 >>> view = getMultiAdapter(103 >>> view = getMultiAdapter(
93 ... (bugtarget, LaunchpadTestRequest()), name='+bugs-index')104 ... (bugtarget, LaunchpadTestRequest()),
105 ... name='+bugtarget-portlet-bugfilters-stats')
94 >>> view.initialize()106 >>> view.initialize()
95107
96 >>> bugs_fixed = view.bugs_fixed_elsewhere_info108 >>> view.bugs_fixed_elsewhere_count
97 >>> bugs_fixed['count']
98 1109 1
99110
100If the private bug is made public again, he will of course see that111If the private bug is made public again, he will of course see that
@@ -106,8 +117,7 @@
106 >>> syncUpdate(another_bug)117 >>> syncUpdate(another_bug)
107118
108 >>> login('no-priv@canonical.com')119 >>> login('no-priv@canonical.com')
109 >>> bugs_fixed = view.bugs_fixed_elsewhere_info120 >>> view.bugs_fixed_elsewhere_count
110 >>> bugs_fixed['count']
111 2121 2
112122
113123
@@ -118,8 +128,7 @@
118 >>> another_bug.duplicateof = bug128 >>> another_bug.duplicateof = bug
119 >>> syncUpdate(another_bug)129 >>> syncUpdate(another_bug)
120130
121 >>> bugs_fixed = view.bugs_fixed_elsewhere_info131 >>> view.bugs_fixed_elsewhere_count
122 >>> bugs_fixed['count']
123 1132 1
124133
125134
@@ -136,6 +145,5 @@
136 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)145 ... BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
137 >>> syncUpdate(bugtask)146 >>> syncUpdate(bugtask)
138147
139 >>> bugs_fixed = view.bugs_fixed_elsewhere_info148 >>> view.bugs_fixed_elsewhere_count
140 >>> bugs_fixed['count']
141 0149 0
142150
=== modified file 'lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt'
--- lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt 2009-09-20 14:58:50 +0000
+++ lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt 2009-12-07 15:04:15 +0000
@@ -19,7 +19,8 @@
19 'Bugs in ALSA utilities'19 'Bugs in ALSA utilities'
2020
21 >>> pending_watches_link = user_browser.getLink(21 >>> pending_watches_link = user_browser.getLink(
22 ... 'need forwarding upstream')22 ... 'Bugs fixed elsewhere')
23
2324
24== Marking and searching for pending bugwatches ==25== Marking and searching for pending bugwatches ==
2526
2627
=== modified file 'lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt'
--- lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt 2009-09-11 14:03:37 +0000
+++ lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt 2009-12-07 15:04:15 +0000
@@ -137,7 +137,7 @@
137request, issued by regular browsers via Javascript.137request, issued by regular browsers via Javascript.
138138
139 >>> browser.open(139 >>> browser.open(
140 ... "http://launchpad.dev/ubuntu/+bugtarget-portlet-bugfilters-content")140 ... "http://launchpad.dev/ubuntu/+bugtarget-portlet-bugfilters-info")
141 >>> browser.getLink("New").click()141 >>> browser.getLink("New").click()
142142
143The result set is filtered to show only New bugs.143The result set is filtered to show only New bugs.
144144
=== removed file 'lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-authenticated.txt'
--- lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-authenticated.txt 2009-06-12 16:36:02 +0000
+++ lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-authenticated.txt 1970-01-01 00:00:00 +0000
@@ -1,19 +0,0 @@
1The distribution bug listing contains a portlet that shows bug
2statistics for the distro. Each statistic is a link to filter the
3listing to show just those bugs. This portlet is served in a separate
4request; the request is issued via Javascript from main bugs page
5of a distribution.
6
7 >>> print http(r"""
8 ... GET /debian/+bugtarget-portlet-bugfilters-content HTTP/1.1
9 ... Authorization: Basic dGVzdEBjYW5vbmljYWwuY29tOnRlc3Q=
10 ... Accept-Language: en-ca,en-us;q=0.8,en;q=0.5,fr-ca;q=0.3
11 ... """)
12 HTTP/1.1 200 Ok
13 ...Open...3...
14 ...Assigned to me...1...
15 ...Critical...0...
16 ...New...1...
17 ...Unassigned...2...
18 ...All bugs ever reported...4...
19 ...
200
=== removed file 'lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-unauthenticated.txt'
--- lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-unauthenticated.txt 2009-06-12 16:36:02 +0000
+++ lib/lp/bugs/stories/distribution/xx-distribution-bug-statistics-portlet-unauthenticated.txt 1970-01-01 00:00:00 +0000
@@ -1,16 +0,0 @@
1The distribution bug listing contains a portlet that shows bug
2statistics for the distro. Each statistic is a link to filter the
3listing to show just those bugs. This portlet is served in a separate
4request; the request is issued via Javascript from main bugs page
5of a distribution.
6
7 >>> print http(r"""
8 ... GET /debian/+bugtarget-portlet-bugfilters-content HTTP/1.1
9 ... """)
10 HTTP/1.1 200 Ok
11 ...Open...3...
12 ...Critical...0...
13 ...New...1...
14 ...Unassigned...2...
15 ...All bugs ever reported...4...
16 ...
170
=== added file 'lib/lp/bugs/stories/xx-bugs-statistics-portlet.txt'
--- lib/lp/bugs/stories/xx-bugs-statistics-portlet.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/stories/xx-bugs-statistics-portlet.txt 2009-12-07 15:04:15 +0000
@@ -0,0 +1,334 @@
1= Bug statistics portlet =
2
3The distribution, project group and project bug listings contain a
4portlet that shows bug statistics for the target. Each statistic is a
5link to filter the listing to show just those bugs. This portlet is
6served in a separate request; the request is issued via Javascript and
7inserted into the page later.
8
9 >>> from BeautifulSoup import BeautifulSoup
10
11 >>> def print_portlet(browser, path):
12 ... browser.open(
13 ... 'http://bugs.launchpad.dev/%s/+portlet-bugfilters' % path)
14 ... table = BeautifulSoup(browser.contents).find('table', 'bug-links')
15 ... tbody_info, tbody_links = table('tbody')
16 ... print_table(tbody_info)
17 ... for link in tbody_links('a'):
18 ... text = extract_text(link)
19 ... if len(text) > 0:
20 ... print "%s\n --> %s" % (text, link['href'])
21
22 >>> def print_portlet_contents(browser, path):
23 ... browser.open(
24 ... 'http://bugs.launchpad.dev'
25 ... '/%s/+bugtarget-portlet-bugfilters-stats' % path)
26 ... table = BeautifulSoup('<table>%s</table>' % browser.contents)
27 ... print_table(table)
28
29
30== Distribution ==
31
32 >>> path = 'debian'
33
34If the user is not logged-in a subscribe link is shown along with some
35general stats.
36
37 >>> print_portlet(anon_browser, path)
38 Open bugs
39 Critical bugs
40 Bugs fixed elsewhere
41 New bugs
42 Open CVE bugs - CVE reports
43 Subscribe to bug mail
44 --> http://bugs.launchpad.dev/debian/+subscribe
45
46 >>> print_portlet_contents(anon_browser, path)
47 3 Open bugs
48 0 Critical bugs
49 0 Bugs fixed elsewhere
50 1 New bugs
51 2 Open CVE bugs - CVE reports
52
53Once the user has identified him or herself, information on assigned
54bugs is also shown.
55
56 >>> print_portlet(user_browser, path)
57 Open bugs
58 Bugs assigned to me
59 Critical bugs
60 Bugs fixed elsewhere
61 New bugs
62 Open CVE bugs - CVE reports
63 Subscribe to bug mail
64 --> http://bugs.launchpad.dev/debian/+subscribe
65
66 >>> print_portlet_contents(user_browser, path)
67 3 Open bugs
68 0 Bugs assigned to me
69 0 Critical bugs
70 0 Bugs fixed elsewhere
71 1 New bugs
72 2 Open CVE bugs - CVE reports
73
74The content includes a link to the distribution CVE report.
75
76 >>> print user_browser.getLink('CVE reports').url
77 http://bugs.launchpad.dev/debian/+cve
78
79
80== Distribution Series ==
81
82 >>> path = 'debian/woody'
83
84If the user is not logged-in general stats are shown. There is also a
85link to review nominations.
86
87 >>> print_portlet(anon_browser, path)
88 Open bugs
89 Critical bugs
90 Bugs fixed elsewhere
91 New bugs
92 Open CVE bugs - CVE reports
93 Subscribe to bug mail
94 --> http://bugs.launchpad.dev/debian/woody/+subscribe
95 Review nominations
96 --> http://bugs.launchpad.dev/debian/woody/+nominations
97
98 >>> print_portlet_contents(anon_browser, path)
99 2 Open bugs
100 0 Critical bugs
101 0 Bugs fixed elsewhere
102 2 New bugs
103 1 Open CVE bugs - CVE reports
104
105Once the user has identified him or herself, information on assigned
106bugs is also shown.
107
108 >>> print_portlet(user_browser, path)
109 Open bugs
110 Bugs assigned to me
111 Critical bugs
112 Bugs fixed elsewhere
113 New bugs
114 Open CVE bugs - CVE reports
115 Subscribe to bug mail
116 --> http://bugs.launchpad.dev/debian/woody/+subscribe
117 Review nominations
118 --> http://bugs.launchpad.dev/debian/woody/+nominations
119
120 >>> print_portlet_contents(user_browser, path)
121 2 Open bugs
122 0 Bugs assigned to me
123 0 Critical bugs
124 0 Bugs fixed elsewhere
125 2 New bugs
126 1 Open CVE bugs - CVE reports
127
128The content includes a link to the distribution CVE report.
129
130 >>> print user_browser.getLink('CVE reports').url
131 http://bugs.launchpad.dev/debian/woody/+cve
132
133
134== Distribution Source Package ==
135
136 >>> path = 'debian/+source/mozilla-firefox'
137
138If the user is not logged-in general stats are shown.
139
140 >>> print_portlet(anon_browser, path)
141 Open bugs
142 Critical bugs
143 Bugs fixed elsewhere
144 New bugs
145 Open CVE bugs
146 Subscribe to bug mail
147 --> http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+subscribe
148
149 >>> print_portlet_contents(anon_browser, path)
150 3 Open bugs
151 0 Critical bugs
152 0 Bugs fixed elsewhere
153 1 New bugs
154 2 Open CVE bugs
155
156Once the user has identified him or herself, information on assigned
157bugs is also shown.
158
159 >>> print_portlet(user_browser, path)
160 Open bugs
161 Bugs assigned to me
162 Critical bugs
163 Bugs fixed elsewhere
164 New bugs
165 Open CVE bugs
166 Subscribe to bug mail
167 --> http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+subscribe
168
169 >>> print_portlet_contents(user_browser, path)
170 3 Open bugs
171 0 Bugs assigned to me
172 0 Critical bugs
173 0 Bugs fixed elsewhere
174 1 New bugs
175 2 Open CVE bugs
176
177Note that the "CVE reports" link is not shown above; distribution
178source packages do not have a CVE reports page.
179
180 >>> print user_browser.getLink('CVE reports').url
181 Traceback (most recent call last):
182 ...
183 LinkNotFoundError
184
185
186== Source Package in Distribution Series ==
187
188 >>> path = 'debian/woody/+source/mozilla-firefox'
189
190If the user is not logged-in general stats are shown. There is no
191option to subscribe to bug mail.
192
193 >>> print_portlet(anon_browser, path)
194 Open bugs
195 Critical bugs
196 Bugs fixed elsewhere
197 New bugs
198 Open CVE bugs
199
200 >>> print_portlet_contents(anon_browser, path)
201 2 Open bugs
202 0 Critical bugs
203 0 Bugs fixed elsewhere
204 2 New bugs
205 1 Open CVE bugs
206
207Once the user has identified him or herself, information on assigned
208bugs is also shown.
209
210 >>> print_portlet(user_browser, path)
211 Open bugs
212 Bugs assigned to me
213 Critical bugs
214 Bugs fixed elsewhere
215 New bugs
216 Open CVE bugs
217
218 >>> print_portlet_contents(user_browser, path)
219 2 Open bugs
220 0 Bugs assigned to me
221 0 Critical bugs
222 0 Bugs fixed elsewhere
223 2 New bugs
224 1 Open CVE bugs
225
226Note that the "CVE reports" link is not shown above; source packages
227do not have a CVE reports page.
228
229 >>> print user_browser.getLink('CVE reports').url
230 Traceback (most recent call last):
231 ...
232 LinkNotFoundError
233
234
235== Project group ==
236
237 >>> path = 'mozilla'
238
239If the user is not logged-in general stats are shown.
240
241 >>> print_portlet(anon_browser, path)
242 Open bugs
243 Critical bugs
244 Bugs fixed elsewhere
245 New bugs
246 Open CVE bugs
247 Subscribe to bug mail
248 --> http://bugs.launchpad.dev/mozilla/+subscribe
249
250 >>> print_portlet_contents(anon_browser, path)
251 4 Open bugs
252 1 Critical bugs
253 0 Bugs fixed elsewhere
254 4 New bugs
255 1 Open CVE bugs
256
257Once the user has identified him or herself, information on assigned
258bugs is also shown.
259
260 >>> print_portlet(user_browser, path)
261 Open bugs
262 Bugs assigned to me
263 Critical bugs
264 Bugs fixed elsewhere
265 New bugs
266 Open CVE bugs
267 Subscribe to bug mail
268 --> http://bugs.launchpad.dev/mozilla/+subscribe
269
270 >>> print_portlet_contents(user_browser, path)
271 4 Open bugs
272 0 Bugs assigned to me
273 1 Critical bugs
274 0 Bugs fixed elsewhere
275 4 New bugs
276 1 Open CVE bugs
277
278Note that the "CVE reports" link is not shown above; project groups do
279not have a CVE reports page.
280
281 >>> print user_browser.getLink('CVE reports').url
282 Traceback (most recent call last):
283 ...
284 LinkNotFoundError
285
286
287== Project ==
288
289 >>> path = 'firefox'
290
291If the user is not logged-in general stats are shown.
292
293 >>> print_portlet(anon_browser, path)
294 Open bugs
295 Critical bugs
296 Bugs fixed elsewhere
297 New bugs
298 Open CVE bugs - CVE reports
299 Subscribe to bug mail
300 --> http://bugs.launchpad.dev/firefox/+subscribe
301
302 >>> print_portlet_contents(anon_browser, path)
303 3 Open bugs
304 1 Critical bugs
305 0 Bugs fixed elsewhere
306 3 New bugs
307 1 Open CVE bugs - CVE reports
308
309Once the user has identified him or herself, information on assigned
310bugs is also shown.
311
312 >>> print_portlet(user_browser, path)
313 Open bugs
314 Bugs assigned to me
315 Critical bugs
316 Bugs fixed elsewhere
317 New bugs
318 Open CVE bugs - CVE reports
319 Subscribe to bug mail
320 --> http://bugs.launchpad.dev/firefox/+subscribe
321
322 >>> print_portlet_contents(user_browser, path)
323 3 Open bugs
324 0 Bugs assigned to me
325 1 Critical bugs
326 0 Bugs fixed elsewhere
327 3 New bugs
328 1 Open CVE bugs - CVE reports
329
330
331The content includes a link to the distribution CVE report.
332
333 >>> print user_browser.getLink('CVE reports').url
334 http://bugs.launchpad.dev/firefox/+cve
0335
=== modified file 'lib/lp/bugs/templates/bugtarget-bugs.pt'
--- lib/lp/bugs/templates/bugtarget-bugs.pt 2009-11-18 20:28:03 +0000
+++ lib/lp/bugs/templates/bugtarget-bugs.pt 2009-12-07 15:04:15 +0000
@@ -34,94 +34,7 @@
34 </li>34 </li>
35 </ul>35 </ul>
36 </div>36 </div>
37 <div class="portlet">37 <div tal:replace="structure context/@@+portlet-bugfilters" />
38 <table class="bug-links">
39 <tr tal:define="open_bugs_info view/open_bugs_info">
40 <td class="bugs-count" tal:content="open_bugs_info/count" />
41 <td class="bugs-link">
42 <a tal:attributes="href open_bugs_info/url">Open bugs</a>
43 </td>
44 </tr>
45 <tr tal:condition="view/user"
46 tal:define="my_bugs_info view/my_bugs_info">
47 <td class="bugs-count" tal:content="my_bugs_info/count" />
48 <td class="bugs-link">
49 <a tal:attributes="href my_bugs_info/url">Bugs assigned to me</a>
50 </td>
51 </tr>
52 <tr tal:define="critical_bugs_info view/critical_bugs_info">
53 <td class="bugs-count" tal:content="critical_bugs_info/count" />
54 <td class="bugs-link">
55 <a tal:attributes="href critical_bugs_info/url">Critical bugs</a>
56 </td>
57 </tr>
58 <tr tal:define="fixed_elsewhere view/bugs_fixed_elsewhere_info">
59 <td class="bugs-count" tal:content="fixed_elsewhere/count" />
60 <td class="bugs-link">
61 <a tal:attributes="href fixed_elsewhere/url">
62 Bugs fixed elsewhere
63 </a>
64 </td>
65 </tr>
66 <tr tal:define="new_bugs_info view/new_bugs_info">
67 <td class="bugs-count" tal:content="new_bugs_info/count" />
68 <td class="bugs-link">
69 <a tal:attributes="href new_bugs_info/url">New bugs</a>
70 </td>
71 </tr>
72 <tr tal:define="cve_bugs_info view/open_cve_bugs_info">
73 <td class="bugs-count" tal:content="cve_bugs_info/count" />
74 <td class="bugs-link">
75 <a tal:attributes="href cve_bugs_info/url">Open CVE bugs</a>
76 -
77 <a href="+cve">CVE reports</a>
78 </td>
79 </tr>
80 <tr tal:define="expirable_bugs_info view/expirable_bugs_info"
81 tal:condition="expirable_bugs_info">
82 <td class="bugs-count" tal:content="expirable_bugs_info/count" />
83 <td class="bugs-link">
84 <a tal:attributes="href expirable_bugs_info/url">
85 Incomplete bugs
86 </a>
87 (can expire)
88 </td>
89 </tr>
90 <tr tal:define="pending_bugwatches view/pending_bugwatches_info"
91 tal:condition="pending_bugwatches">
92 <td class="bugs-count" tal:content="pending_bugwatches/count" />
93 <td class="bugs-link">
94 <a tal:attributes="href pending_bugwatches/url">
95 Bugs need forwarding upstream
96 </a>
97 </td>
98 </tr>
99 <tr tal:define="subscribe_link context/menu:bugs/subscribe">
100 <td class="bugs-count" style="padding-top: 1em">
101 <a tal:attributes="href subscribe_link/url">
102 <img tal:attributes="src subscribe_link/icon_url" />
103 </a>
104 </td>
105 <td class="bugs-link">
106 <a tal:attributes="href subscribe_link/url"
107 tal:content="subscribe_link/escapedtext" />
108 </td>
109 </tr>
110 <tr tal:define="
111 review_nominations_link context/menu:bugs/nominations|nothing"
112 tal:condition="review_nominations_link">
113 <td class="bugs-count" style="padding-top: 1em">
114 <a tal:attributes="href review_nominations_link/url">
115 <img tal:attributes="src review_nominations_link/icon_url" />
116 </a>
117 </td>
118 <td class="bugs-link">
119 <a tal:attributes="href review_nominations_link/url"
120 tal:content="review_nominations_link/escapedtext" />
121 </td>
122 </tr>
123 </table>
124 </div>
125 <div tal:replace="structure context/@@+portlet-bugtags" />38 <div tal:replace="structure context/@@+portlet-bugtags" />
126 <tal:releasecriticalbugs39 <tal:releasecriticalbugs
127 tal:condition="view/shouldShowReleaseCriticalPortlet"40 tal:condition="view/shouldShowReleaseCriticalPortlet"
12841
=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters-content.pt'
--- lib/lp/bugs/templates/bugtarget-portlet-bugfilters-content.pt 2009-09-13 20:29:46 +0000
+++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters-content.pt 2009-12-07 15:04:15 +0000
@@ -1,41 +1,67 @@
1<div xmlns:tal="http://xml.zope.org/namespaces/tal"1<tal:portlet-bug-filters-content
2 xmlns:metal="http://xml.zope.org/namespaces/metal">2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3<table width="100%">3 xmlns:metal="http://xml.zope.org/namespaces/metal">
4 <tal:comment condition="nothing">
5 The view/*_count|nothing expressions below are so that this
6 template can be rendered by a view that does not have count
7 information available.
8 </tal:comment>
4 <tr>9 <tr>
5 <td><a tal:attributes="href view/getOpenBugsURL">Open</a></td>10 <td class="bugs-count" tal:content="view/open_bugs_count|nothing" />
6 <td tal:content="context/open_bugtasks/count">10</td>11 <td class="bugs-link">
12 <a tal:attributes="href view/open_bugs_url">Open bugs</a>
13 </td>
7 </tr>14 </tr>
8 <tr tal:condition="view/user">15 <tr tal:condition="view/user">
9 <td>16 <td class="bugs-count" tal:content="view/my_bugs_count|nothing" />
10 <a href="#" tal:attributes="href view/getBugsAssignedToMeURL">17 <td class="bugs-link">
11 Assigned to me18 <a tal:attributes="href view/my_bugs_url">Bugs assigned to me</a>
12 </a>19 </td>
13 </td>20 </tr>
14 <td tal:content="view/getBugsAssignedToMeCount">50</td>21 <tr>
15 </tr>22 <td class="bugs-count" tal:content="view/critical_bugs_count|nothing" />
16 <tr>23 <td class="bugs-link">
17 <td><a tal:attributes="href view/getCriticalBugsURL">Critical</a></td>24 <a tal:attributes="href view/critical_bugs_url">Critical bugs</a>
18 <td tal:content="context/critical_bugtasks/count">42</td>25 </td>
19 </tr>26 </tr>
20 <tr>27 <tr>
21 <td>28 <td class="bugs-count" tal:content="view/bugs_fixed_elsewhere_count|nothing" />
22 <a tal:attributes="href view/getNewBugsURL">New</a>29 <td class="bugs-link">
23 </td>30 <a tal:attributes="href view/bugs_fixed_elsewhere_url">
24 <td tal:content="context/new_bugtasks/count">42</td>31 Bugs fixed elsewhere
25 </tr>32 </a>
26 <tr>33 </td>
27 <td>34 </tr>
28 <a tal:attributes="href view/getUnassignedBugsURL">Unassigned</a>35 <tr>
29 </td>36 <td class="bugs-count" tal:content="view/new_bugs_count|nothing" />
30 <td tal:content="context/unassigned_bugtasks/count">42</td>37 <td class="bugs-link">
31 </tr>38 <a tal:attributes="href view/new_bugs_url">New bugs</a>
32 <tr>39 </td>
33 <td>40 </tr>
34 <a tal:attributes="href view/getAllBugsEverReportedURL">41 <tr>
35 All bugs ever reported42 <td class="bugs-count" tal:content="view/open_cve_bugs_count|nothing" />
36 </a>43 <td class="bugs-link">
37 </td>44 <a tal:attributes="href view/open_cve_bugs_url">Open CVE bugs</a>
38 <td tal:content="context/all_bugtasks/count">42</td>45 <span tal:condition="view/open_cve_bugs_has_report">
39 </tr>46 - <a tal:attributes="href context/fmt:url/+cve">CVE reports</a>
40</table>47 </span>
41</div>48 </td>
49 </tr>
50 <tr tal:condition="view/expirable_bugs_url">
51 <td class="bugs-count" tal:content="view/expirable_bugs_count|nothing" />
52 <td class="bugs-link">
53 <a tal:attributes="href view/expirable_bugs_url">
54 Incomplete bugs
55 </a>
56 (can expire)
57 </td>
58 </tr>
59 <tr tal:condition="view/pending_bugwatches_url">
60 <td class="bugs-count" tal:content="view/pending_bugwatches_count|nothing" />
61 <td class="bugs-link">
62 <a tal:attributes="href view/pending_bugwatches_url">
63 Bugs need forwarding upstream
64 </a>
65 </td>
66 </tr>
67</tal:portlet-bug-filters-content>
4268
=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt'
--- lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-12-03 18:33:22 +0000
+++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-12-07 15:04:15 +0000
@@ -4,34 +4,53 @@
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 class="portlet" id="portlet-bugfilters">5 class="portlet" id="portlet-bugfilters">
6 <a id="bugtarget-bugfilters-link"6 <a id="bugtarget-bugfilters-link"
7 tal:attributes="href context/fmt:url/+bugtarget-portlet-bugfilters-content" >7 tal:attributes="href context/fmt:url/+bugtarget-portlet-bugfilters-stats">
8 </a>8 </a>
9 <h2>Filters</h2>9 <table class="bug-links">
10 <div id="bugfilters-portlet-spinner"10 <tbody id="bugfilters-portlet-content"
11 style="text-align: center; display: none">11 tal:content="structure context/@@+bugtarget-portlet-bugfilters-info" />
12 <img src="/@@/spinner" />12 <tbody tal:define="menu context/menu:bugs">
13 </div>13 <tr tal:define="subscribe_link menu/subscribe|nothing"
14 <script type="text/javascript">14 tal:condition="subscribe_link">
15 <td class="bugs-count" style="padding-top: 1em">
16 <a tal:attributes="href subscribe_link/url">
17 <img tal:attributes="src subscribe_link/icon_url" />
18 </a>
19 </td>
20 <td class="bugs-link">
21 <a tal:attributes="href subscribe_link/url"
22 tal:content="subscribe_link/escapedtext" />
23 </td>
24 </tr>
25 <tr tal:define="review_nominations_link context/menu:bugs/nominations|nothing"
26 tal:condition="review_nominations_link">
27 <td class="bugs-count" style="padding-top: 1em">
28 <a tal:attributes="href review_nominations_link/url">
29 <img tal:attributes="src review_nominations_link/icon_url" />
30 </a>
31 </td>
32 <td class="bugs-link">
33 <a tal:attributes="href review_nominations_link/url"
34 tal:content="review_nominations_link/escapedtext" />
35 </td>
36 </tr>
37 </tbody>
38 </table>
39 <script type="text/javascript">
15 LPS.use('io-base', 'node', function(Y) {40 LPS.use('io-base', 'node', function(Y) {
16 Y.on('domready', function() {41 Y.on('domready', function() {
17 var portlet = Y.one('#portlet-bugfilters');42 var url = Y.one('#bugtarget-bugfilters-link').getAttribute('href');
18 Y.one('#bugfilters-portlet-spinner').setStyle('display', 'block');43 var handlers = {
1944 success: function(transactionid, response, arguments) {
20 function hide_spinner() {45 Y.one('#bugfilters-portlet-content').set(
21 Y.one('#bugfilters-portlet-spinner').setStyle('display', 'none');46 'innerHTML', response.responseText);
22 }47 },
2348 failure: function() {
24 function on_success(transactionid, response, arguments) {49 Y.one('#bugfilters-portlet-content').set('innerHTML', '');
25 hide_spinner();50 }
26 portlet.set('innerHTML',51 };
27 portlet.get('innerHTML') + response.responseText);52 var request = Y.io(url, {on: handlers});
28 }53 });
29
30 var config = {on: {success: on_success,
31 failure: hide_spinner}};
32 var url = Y.one('#bugtarget-bugfilters-link').getAttribute('href');
33 var request = Y.io(url, config);
34 });
35 });54 });
36 </script>55 </script>
37</div>56</div>
3857
=== modified file 'lib/lp/registry/browser/project.py'
--- lib/lp/registry/browser/project.py 2009-11-13 21:55:42 +0000
+++ lib/lp/registry/browser/project.py 2009-12-07 15:04:15 +0000
@@ -292,12 +292,16 @@
292292
293 usedfor = IProject293 usedfor = IProject
294 facet = 'bugs'294 facet = 'bugs'
295 links = ['new']295 links = ['new', 'subscribe']
296296
297 def new(self):297 def new(self):
298 text = 'Report a Bug'298 text = 'Report a Bug'
299 return Link('+filebug', text, icon='add')299 return Link('+filebug', text, icon='add')
300300
301 def subscribe(self):
302 text = 'Subscribe to bug mail'
303 return Link('+subscribe', text, icon='edit')
304
301305
302class ProjectView(HasAnnouncementsView, FeedsMixin):306class ProjectView(HasAnnouncementsView, FeedsMixin):
303 implements(IProjectActionMenu)307 implements(IProjectActionMenu)