← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~adeuring/launchpad/bug-277118 into lp:launchpad

 

Abel Deuring has proposed merging lp:~adeuring/launchpad/bug-277118 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #277118 in Launchpad itself: "+upstreamreport oopses for some distributions"
  https://bugs.launchpad.net/launchpad/+bug/277118

For more details, see:
https://code.launchpad.net/~adeuring/launchpad/bug-277118/+merge/59194

The OOPS occurs for distributions which do not have a current distroseries (meaning that they do not have any distroseries at all) but which have at least on bug task filed against a source package.

DistributionUpstreamBugReport.initialize() iterated over the sourcepackages with "interesting bugs" and tried to find a related distroseries sourcepackaage (dssp). If no current distroseries was defind, dssp was set to None. dssp was then used in PackageBugReportData.__init__() to create a URL via canonical_url(dssp). This crashes obviously if dssp is None. Interstingly, the related part of the page template was aware that dssp can be None:

     <a tal:condition="item/dssp"
        tal:attributes="href item/packaging_url">

DistributionUpstreamBugReport.initialize() now creates a new attribute self.has_upstream_report as a flag which combines "distribution uses LP for bug tracking" and "distribution has a current distroseries"; the template now render the upstream report only if has_upstream_report is True.

Not showing the upstream report if context.bug_tracking_usage != ServiceUsage.LAUNCHPAD avoids the weird situation that you could up get an upstream report for a distribution where you could not even search for any bugs... (Just check https://bugs.launchpad.net/fluxbuntu or 
https://bugs.launchpad.net/fluxbuntu/+bugs?advanced=1 )

The longest part of the diff is a trivial change in the template: The new message "This distribution does not use Launchpad for development." is enclosed in

  <div tal:condition="not:view/has_upstream_report">

and most of the template is now enclosed in a corresponding

  <div tal:condition="view/has_upstream_report">

which required to indend the content by two more spaces. Moreover, I changed

  <a tal:condition="item/dssp"
     tal:attributes="href item/packaging_url">
     <small>(link)</small></a>

to 

  <a tal:attributes="href item/packaging_url">
     <small>(link)</small></a>

The computation of packaging_url in the view class requires that dssp is not None, hence there is no need to check this again in the template.

test: ./bin/test bugs -vvt test_distribution_upstream_bug_report

no lint

-- 
https://code.launchpad.net/~adeuring/launchpad/bug-277118/+merge/59194
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~adeuring/launchpad/bug-277118 into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/distribution_upstream_bug_report.py'
--- lib/lp/bugs/browser/distribution_upstream_bug_report.py	2010-09-03 03:12:39 +0000
+++ lib/lp/bugs/browser/distribution_upstream_bug_report.py	2011-04-27 10:06:36 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Browser views for distributions."""
@@ -159,7 +159,6 @@
         self.dssp = dssp
         self.product = product
 
-        dsp_url = canonical_url(dsp)
         dsp_bugs_url = canonical_url(dsp, rootsite='bugs')
 
         self.open_bugs_url = urlappend(
@@ -360,17 +359,22 @@
         packages_to_exclude = self.context.upstream_report_excluded_packages
         counts = self.context.getPackagesAndPublicUpstreamBugCounts(
             limit=self.LIMIT, exclude_packages=packages_to_exclude)
+        # The upstream bug report is not useful if the distibution
+        # does not track its bugs on Lauchpad or if it does not have a
+        # current distroseries.
+        self.has_upstream_report = (
+            self.context.bug_tracking_usage == ServiceUsage.LAUNCHPAD and
+            self.current_distro_series is not None)
+        if not self.has_upstream_report:
+            return
         for (dsp, product, open, triaged, upstream, watched,
              bugs_with_upstream_patches) in counts:
             # The +edit-packaging page is only available for
             # IDistributionSeriesSourcepackages, so deduce one here. If
             # the distribution doesn't have series we can't offer a link
             # to add packaging information.
-            if self.current_distro_series:
-                dssp = self.current_distro_series.getSourcePackage(
-                    dsp.sourcepackagename)
-            else:
-                dssp = None
+            dssp = self.current_distro_series.getSourcePackage(
+                dsp.sourcepackagename)
             self.total.open_bugs += open
             self.total.triaged_bugs += triaged
             self.total.upstream_bugs += upstream

=== modified file 'lib/lp/bugs/browser/tests/test_distribution_upstream_bug_report.py'
--- lib/lp/bugs/browser/tests/test_distribution_upstream_bug_report.py	2010-10-04 19:50:45 +0000
+++ lib/lp/bugs/browser/tests/test_distribution_upstream_bug_report.py	2011-04-27 10:06:36 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Unit tests for DistributionUpstreamBugReport."""
@@ -6,8 +6,11 @@
 __metaclass__ = type
 
 
-import unittest
-
+from soupmatchers import (
+    HTMLContains,
+    Tag,
+    )
+from testtools.matchers import Not
 from zope.component import getUtility
 
 from canonical.launchpad.ftests import (
@@ -18,20 +21,29 @@
 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
 from canonical.launchpad.testing.systemdocs import create_view
 from canonical.testing.layers import LaunchpadFunctionalLayer
+from lp.app.enums import ServiceUsage
 from lp.bugs.browser.distribution_upstream_bug_report import (
     DistributionUpstreamBugReport,
     )
-
-
-class TestDistributionUpstreamBugReport(unittest.TestCase):
+from lp.testing import (
+    BrowserTestCase,
+    person_logged_in,
+    TestCaseWithFactory,
+    )
+from lp.testing.views import create_initialized_view
+
+
+class TestDistributionUpstreamBugReport(TestCaseWithFactory):
 
     layer = LaunchpadFunctionalLayer
 
     def setUp(self):
+        super(TestDistributionUpstreamBugReport, self).setUp()
         login(ANONYMOUS)
 
     def tearDown(self):
         logout()
+        super(TestDistributionUpstreamBugReport, self).tearDown()
 
     def test_valid_sort_keys_are_valid(self):
         # The valid_sort_keys property of the
@@ -72,3 +84,91 @@
             self.assertTrue(view_sort_reversed,
                 "Sort order should be reversed for a sort_by value of "
                 "%s" % reversed_key)
+
+    def test_has_upstream_report__no_series_no_bugracking(self):
+        # The property DistributionUpstreamBugReport.has_upstream_report
+        # is False if a distribution does not use Launchpad for bug
+        # tracking and if no current distroseries exists.
+        distribution = self.factory.makeDistribution()
+        view = create_initialized_view(distribution, '+upstreamreport')
+        self.assertNotEqual(
+            ServiceUsage.LAUNCHPAD, distribution.bug_tracking_usage)
+        self.assertIs(None, distribution.currentseries)
+        self.assertFalse(view.has_upstream_report)
+
+    def test_has_upstream_report__no_distroseries_with_bug_tracking(self):
+        # The property DistributionUpstreamBugReport.has_upstream_report
+        # is False if a distribution does not have a current
+        # distroseries, even if Luanchpad is used for bug tracking.
+        distribution = self.factory.makeDistribution()
+        view = create_initialized_view(distribution, '+upstreamreport')
+        with person_logged_in(distribution.owner):
+            distribution.official_malone = True
+        self.assertIs(None, distribution.currentseries)
+        self.assertFalse(view.has_upstream_report)
+
+    def test_has_upstream_report__with_distroseries_no_bug_tracking(self):
+        # The property DistributionUpstreamBugReport.has_upstream_report
+        # is False if a distribution has a current distroseries, but
+        # if Launchpad is not used for bug tracking.
+        distribution = self.factory.makeDistroSeries().distribution
+        view = create_initialized_view(distribution, '+upstreamreport')
+        self.assertIsNot(None, distribution.currentseries)
+        self.assertNotEqual(
+            ServiceUsage.LAUNCHPAD, distribution.bug_tracking_usage)
+        self.assertFalse(view.has_upstream_report)
+
+    def test_has_upstream_report__with_distroseries_and_bug_tracking(self):
+        # The property DistributionUpstreamBugReport.has_upstream_report
+        # is True if a distribution has a current distroseries and if it
+        # uses Launchpad for bug tracking.
+        distribution = self.factory.makeDistroSeries().distribution
+        with person_logged_in(distribution.owner):
+            distribution.official_malone = True
+        view = create_initialized_view(distribution, '+upstreamreport')
+        self.assertIsNot(None, distribution.currentseries)
+        self.assertEqual(
+            ServiceUsage.LAUNCHPAD, distribution.bug_tracking_usage)
+        self.assertTrue(view.has_upstream_report)
+
+
+class TestDistributionUpstreamBugReportPage(BrowserTestCase):
+
+    """Tests for the +upstream bug report page."""
+
+    layer = LaunchpadFunctionalLayer
+
+    def getTagMatchers(self):
+        """Return matchers for the tags saying "launchpad is not used
+        for development" and "(distro)" has no bugs filed."""
+        no_lp_usage = Tag(
+            'no-lp-usage', 'div', attrs={'id': 'no-lp-usage'})
+        no_bugs_filed = Tag(
+            'no-bugs-filed', 'div', attrs={'id': 'no-bugs-filed'})
+        return no_lp_usage, no_bugs_filed
+
+    def test_no_upstream_report_for_unconfigured_distros(self):
+        # If DistributionUpstreamBugReport.has_upstream_report is False,
+        # the +upstream-report page does not show the report.
+        distribution = self.factory.makeDistribution()
+        browser = self.getViewBrowser(
+            distribution, '+upstreamreport', no_login=True)
+        no_lp_usage, no_bugs_filed = self.getTagMatchers()
+        self.assertThat(browser.contents, Not(HTMLContains(no_bugs_filed)))
+        # Instead, a message tells the user that no report is
+        # available.
+        self.assertThat(browser.contents, HTMLContains(no_lp_usage))
+
+    def test_upstream_report_for_configured_distros(self):
+        # If DistributionUpstreamBugReport.has_upstream_report is True,
+        # the +upstream-report page does shows the report.
+        distribution = self.factory.makeDistroSeries().distribution
+        with person_logged_in(distribution.owner):
+            distribution.official_malone = True
+        browser = self.getViewBrowser(
+            distribution, '+upstreamreport', no_login=True)
+        no_lp_usage, no_bugs_filed = self.getTagMatchers()
+        self.assertThat(browser.contents, HTMLContains(no_bugs_filed))
+        # A message telling the user that no report is available
+        # is not shown.
+        self.assertThat(browser.contents, Not(HTMLContains(no_lp_usage)))

=== modified file 'lib/lp/bugs/templates/distribution-upstream-bug-report.pt'
--- lib/lp/bugs/templates/distribution-upstream-bug-report.pt	2010-08-26 16:10:48 +0000
+++ lib/lp/bugs/templates/distribution-upstream-bug-report.pt	2011-04-27 10:06:36 +0000
@@ -25,268 +25,273 @@
 
   <div metal:fill-slot="main">
     <div class="top-portlet">
-      <tal:no-bugs tal:condition="not: view/data">
-        <span tal:replace="context/displayname" /> has no bugs filed
-        against it.
-      </tal:no-bugs>
-
-      <p tal:condition="view/data">
-        See the
-        <a href="https://wiki.ubuntu.com/Bugs/Upstream/UpstreamReport";
-          >UpstreamReport wiki page</a>
-        for information on how to read this report.
-      </p>
-
-      <table class="listing" id="upstream-report"
-             tal:condition="view/data">
-        <thead tal:define="links view/sort_order_links">
-          <tr>
-            <th>
-              <a id="sort-dsp" tal:attributes="href links/dsp/link">Package</a>
-              <img id="sortarrow-dsp" tal:attributes="src links/dsp/arrow" />
-            </th>
-            <th colspan="2">
-              <a id="sort-product" tal:attributes="href links/product/link">Project</a>
-              <img id="sortarrow-product" tal:attributes="src links/product/arrow" />
-            </th>
-            <th>
-              <a id="sort-bugtracker-name"
-                  tal:attributes="href links/bugtracker_name/link"
-              >Bugtracker</a>
-              <img id="sortarrow-bugtracker-name"
-                  tal:attributes="src links/bugtracker_name/arrow" />
-            </th>
-            <th>
-              <a id="sort-bug-supervisor-name"
-                  tal:attributes="href links/bug_supervisor_name/link"
-              >Upstream Contact</a>
-              <img id="sortarrow-bug-supervisor-name"
-                  tal:attributes="src links/bug_supervisor_name/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-open-bugs"
-                  tal:attributes="href links/open_bugs/link">Open</a>
-              <img id="sortarrow-open-bugs"
-                  tal:attributes="src links/open_bugs/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-triaged-bugs"
-                  tal:attributes="href links/triaged_bugs/link"
-              >Triaged</a>
-              <img id="sortarrow-triaged-bugs"
-                  tal:attributes="src links/triaged_bugs/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-triaged-percentage"
-                  tal:attributes="href links/triaged_bugs_percentage/link"
-              >%</a>
-              <img id="sortarrow-triaged-percentage"
-                  tal:attributes="src links/triaged_bugs_percentage/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-triaged-delta"
-                  tal:attributes="href links/triaged_bugs_delta/link"
-              >&Delta;</a>
-              <img id="sortarrow-triaged-delta"
-                  tal:attributes="src links/triaged_bugs_delta/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-upstream-bugs"
-                  tal:attributes="href links/upstream_bugs/link"
-              >Upstream</a>
-              <img id="sortarrow-upstream-bugs"
-                  tal:attributes="src links/upstream_bugs/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-upstream-percentage"
-                  tal:attributes="href links/upstream_bugs_percentage/link"
-              >%</a>
-              <img id="sortarrow-upstream-percentage"
-                  tal:attributes="src links/upstream_bugs_percentage/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-upstream-delta"
-                  tal:attributes="href links/upstream_bugs_delta/link"
-              >&Delta;</a>
-              <img id="sortarrow-upstream-delta"
-                  tal:attributes="src links/upstream_bugs_delta/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-watched-bugs"
-                  tal:attributes="href links/watched_bugs/link">Watch</a>
-              <img id="sortarrow-watched-bugs"
-                  tal:attributes="src links/watched_bugs/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-watched-percentage"
-                  tal:attributes="href links/watched_bugs_percentage/link"
-              >%</a>
-              <img id="sortarrow-watched-percentage"
-                  tal:attributes="src links/watched_bugs_percentage/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-watched-delta"
-                  tal:attributes="href links/watched_bugs_delta/link"
-              >&Delta;</a>
-              <img id="sortarrow-watched-delta"
-                  tal:attributes="src links/watched_bugs_delta/arrow" />
-            </th>
-            <th class="amount">
-              <a id="sort-bugs-with-upstream-patches"
-                  tal:attributes="href links/bugs_with_upstream_patches/link"
-              >Patches for upstream</a>
-              <img id="sortarrow-bugs-with-upstream-patches"
-                  tal:attributes="src links/bugs_with_upstream_patches/arrow" />
-            </th>
-          </tr>
-        </thead>
-        <tbody id="upstream-report-content">
-          <tr tal:repeat="item view/data" tal:attributes="class item/row_class">
-            <td>
-              <a tal:attributes="href item/dsp/fmt:url"
-                 tal:content="item/dsp/name"></a>
-            </td>
-            <tal:has-product condition="item/product">
-              <td style="padding-right: 0" align="center">
-                <a tal:attributes="href item/branch/fmt:url;
-                   title string:You can use `bzr branch lp:${item/product/name}' to fetch this project's code"
-                   tal:condition="item/branch">
-                  <img alt="(branch)" src="/@@/branch" /></a>
-              </td>
-              <td style="padding-left: 0" align="center">
-                <a tal:attributes="href item/product/fmt:url"
-                  ><img src="/@@/yes"
-                      tal:attributes="title item/product/title" /></a>
-              </td>
-              <tal:has-bugtracker condition="item/bugtracker">
-                <td align="center">
-                  <a tal:attributes="href item/bugtracker/fmt:url"
-                     ><img src="/@@/yes"
-                        tal:attributes="title item/bugtracker/title" /></a>
-                </td>
-              </tal:has-bugtracker>
-              <tal:has-no-bugtracker condition="not: item/bugtracker">
-                <td tal:condition="item/bug_tracking_usage/enumvalue:LAUNCHPAD"
-                  align="center">
-                    <img src="/@@/yes" title="Launchpad" />
-                </td>
-                <td tal:condition="not: item/bug_tracking_usage/enumvalue:LAUNCHPAD"
-                  align="center">
-                    <img src="/@@/no" title="Unknown" />
-                    <a tal:condition="item/product/required:launchpad.Edit"
-                       tal:attributes="href item/product_edit_url">
-                       <small>(fix)</small></a></td>
-              </tal:has-no-bugtracker>
-              <tal:has-bug-supervisor condition="item/product/bug_supervisor">
-                <td align="center">
-                  <a tal:attributes="href item/product/bug_supervisor/fmt:url"
-                     ><img src="/@@/yes"
-                        tal:attributes="title item/product/bug_supervisor/fmt:displayname"
-                        /></a>
-                </td>
-              </tal:has-bug-supervisor>
-              <tal:has-no-bug-supervisor condition="not: item/product/bug_supervisor">
-                <td align="center">
-                    <img src="/@@/no" title="Unspecified" />
-                    <a tal:condition="item/product/required:launchpad.Edit"
-                       tal:attributes="href item/bug_supervisor_url">
-                      <small>(change)</small></a></td>
-              </tal:has-no-bug-supervisor>
-            </tal:has-product>
-            <tal:has-no-product condition="not: item/product">
-                <td align="center" class="bad" colspan="4">
-                    Missing corresponding project.
-                    <a href="/projects"><small>(find)</small></a>
-                    <a tal:condition="item/dssp"
-                       tal:attributes="href item/packaging_url">
-                       <small>(link)</small></a>
-                </td>
-            </tal:has-no-product>
-            <td class="amount">
-              <a tal:attributes="href item/open_bugs_url"
-                 tal:content="item/open_bugs"></a>
-            </td>
-            <td tal:attributes="class string:amount ${item/triaged_bugs_class}">
-              <a tal:attributes="href item/triaged_bugs_url"
-                 tal:content="item/triaged_bugs"></a>
-            </td>
-            <td tal:attributes="class string:amount ${item/triaged_bugs_class}"
-                tal:content="item/triaged_bugs_percentage/fmt:float/.2" />
-            <td tal:attributes="class string:amount ${item/triaged_bugs_class}">
-              <a tal:attributes="href item/triaged_bugs_delta_url"
-                 tal:content="item/triaged_bugs_delta"></a>
-            </td>
-            <td tal:attributes="class string:amount ${item/upstream_bugs_class}">
-              <a tal:attributes="href item/upstream_bugs_url"
-                 tal:content="item/upstream_bugs"></a>
-            </td>
-            <td tal:attributes="class string:amount ${item/upstream_bugs_class}"
-                tal:content="item/upstream_bugs_percentage/fmt:float/.2" />
-            <td tal:attributes="class string:amount ${item/upstream_bugs_class}">
-              <a tal:attributes="href item/upstream_bugs_delta_url"
-                 tal:content="item/upstream_bugs_delta"></a>
-            </td>
-            <tal:upstream-in-launchpad
-                condition="item/bug_tracking_usage/enumvalue:LAUNCHPAD">
-                <td colspan="4" class="good">&nbsp;</td>
-            </tal:upstream-in-launchpad>
-            <tal:upstream-not-in-launchpad
-                condition="not: item/bug_tracking_usage/enumvalue:LAUNCHPAD">
-              <td tal:attributes="class string:amount ${item/watched_bugs_class}"
-                  tal:content="item/watched_bugs" />
-              <td tal:attributes="class string:amount ${item/watched_bugs_class}"
-                  tal:content="item/watched_bugs_percentage/fmt:float/.2" />
-              <td tal:attributes="class string:amount ${item/watched_bugs_class}">
-                <a tal:attributes="href item/watched_bugs_delta_url"
-                   tal:content="item/watched_bugs_delta"></a>
-              </td>
-              <td tal:attributes="class string:amount ${item/watched_bugs_class}">
-                <tal:has-bugs-with-upstream-patches
-                    condition="item/bugs_with_upstream_patches">
-                  <a tal:attributes="href item/bugs_with_upstream_patches_url"
-                      tal:content="item/bugs_with_upstream_patches"></a>
-                </tal:has-bugs-with-upstream-patches>
-                <tal:has-no-bugs-with-upstream-patches
-                    condition="not: item/bugs_with_upstream_patches">
-                   -
-                </tal:has-no-bugs-with-upstream-patches>
-              </td>
-            </tal:upstream-not-in-launchpad>
-          </tr>
-        </tbody>
-        <tfoot id="upstream-report-totals">
-          <tr>
-            <td colspan="5" class="amount"><b>Totals:</b></td>
-            <td class="amount"
-                tal:content="view/total/open_bugs" />
-            <td class="amount"
-                tal:content="view/total/triaged_bugs" />
-            <td class="amount"
-                tal:content="view/total/triaged_bugs_percentage/fmt:float/.2" />
-            <td class="amount"
-                tal:content="view/total/triaged_bugs_delta" />
-            <td class="amount"
-                tal:content="view/total/upstream_bugs" />
-            <td class="amount"
-                tal:content="view/total/upstream_bugs_percentage/fmt:float/.2" />
-            <td class="amount"
-                tal:content="view/total/upstream_bugs_delta" />
-            <td class="amount"
-                tal:content="view/total/watched_bugs" />
-            <td class="amount"
-                tal:content="view/total/watched_bugs_percentage/fmt:float/.2" />
-            <td class="amount"
-                tal:content="view/total/watched_bugs_delta" />
-            <td class="amount"
-                tal:content="view/total/bugs_with_upstream_patches" />
-          </tr>
-        </tfoot>
-      </table>
-
-      <div tal:condition="view/data"
-           align="right"><small>Top <span tal:content="view/data/count:len" />
-        packages listed.</small></div>
-
+      <div tal:condition="not:view/has_upstream_report"
+           id="no-lp-usage">
+        This distribution does not use Launchpad for development.
+      </div>
+      <div tal:condition="view/has_upstream_report"
+           id="no-bugs-filed">
+        <tal:no-bugs tal:condition="not: view/data">
+          <span tal:replace="context/displayname" /> has no bugs filed
+          against it.
+        </tal:no-bugs>
+
+        <p tal:condition="view/data">
+          See the
+          <a href="https://wiki.ubuntu.com/Bugs/Upstream/UpstreamReport";
+            >UpstreamReport wiki page</a>
+          for information on how to read this report.
+        </p>
+
+        <table class="listing" id="upstream-report"
+               tal:condition="view/data">
+          <thead tal:define="links view/sort_order_links">
+            <tr>
+              <th>
+                <a id="sort-dsp" tal:attributes="href links/dsp/link">Package</a>
+                <img id="sortarrow-dsp" tal:attributes="src links/dsp/arrow" />
+              </th>
+              <th colspan="2">
+                <a id="sort-product" tal:attributes="href links/product/link">Project</a>
+                <img id="sortarrow-product" tal:attributes="src links/product/arrow" />
+              </th>
+              <th>
+                <a id="sort-bugtracker-name"
+                    tal:attributes="href links/bugtracker_name/link"
+                >Bugtracker</a>
+                <img id="sortarrow-bugtracker-name"
+                    tal:attributes="src links/bugtracker_name/arrow" />
+              </th>
+              <th>
+                <a id="sort-bug-supervisor-name"
+                    tal:attributes="href links/bug_supervisor_name/link"
+                >Upstream Contact</a>
+                <img id="sortarrow-bug-supervisor-name"
+                    tal:attributes="src links/bug_supervisor_name/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-open-bugs"
+                    tal:attributes="href links/open_bugs/link">Open</a>
+                <img id="sortarrow-open-bugs"
+                    tal:attributes="src links/open_bugs/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-triaged-bugs"
+                    tal:attributes="href links/triaged_bugs/link"
+                >Triaged</a>
+                <img id="sortarrow-triaged-bugs"
+                    tal:attributes="src links/triaged_bugs/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-triaged-percentage"
+                    tal:attributes="href links/triaged_bugs_percentage/link"
+                >%</a>
+                <img id="sortarrow-triaged-percentage"
+                    tal:attributes="src links/triaged_bugs_percentage/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-triaged-delta"
+                    tal:attributes="href links/triaged_bugs_delta/link"
+                >&Delta;</a>
+                <img id="sortarrow-triaged-delta"
+                    tal:attributes="src links/triaged_bugs_delta/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-upstream-bugs"
+                    tal:attributes="href links/upstream_bugs/link"
+                >Upstream</a>
+                <img id="sortarrow-upstream-bugs"
+                    tal:attributes="src links/upstream_bugs/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-upstream-percentage"
+                    tal:attributes="href links/upstream_bugs_percentage/link"
+                >%</a>
+                <img id="sortarrow-upstream-percentage"
+                    tal:attributes="src links/upstream_bugs_percentage/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-upstream-delta"
+                    tal:attributes="href links/upstream_bugs_delta/link"
+                >&Delta;</a>
+                <img id="sortarrow-upstream-delta"
+                    tal:attributes="src links/upstream_bugs_delta/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-watched-bugs"
+                    tal:attributes="href links/watched_bugs/link">Watch</a>
+                <img id="sortarrow-watched-bugs"
+                    tal:attributes="src links/watched_bugs/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-watched-percentage"
+                    tal:attributes="href links/watched_bugs_percentage/link"
+                >%</a>
+                <img id="sortarrow-watched-percentage"
+                    tal:attributes="src links/watched_bugs_percentage/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-watched-delta"
+                    tal:attributes="href links/watched_bugs_delta/link"
+                >&Delta;</a>
+                <img id="sortarrow-watched-delta"
+                    tal:attributes="src links/watched_bugs_delta/arrow" />
+              </th>
+              <th class="amount">
+                <a id="sort-bugs-with-upstream-patches"
+                    tal:attributes="href links/bugs_with_upstream_patches/link"
+                >Patches for upstream</a>
+                <img id="sortarrow-bugs-with-upstream-patches"
+                    tal:attributes="src links/bugs_with_upstream_patches/arrow" />
+              </th>
+            </tr>
+          </thead>
+          <tbody id="upstream-report-content">
+            <tr tal:repeat="item view/data" tal:attributes="class item/row_class">
+              <td>
+                <a tal:attributes="href item/dsp/fmt:url"
+                   tal:content="item/dsp/name"></a>
+              </td>
+              <tal:has-product condition="item/product">
+                <td style="padding-right: 0" align="center">
+                  <a tal:attributes="href item/branch/fmt:url;
+                     title string:You can use `bzr branch lp:${item/product/name}' to fetch this project's code"
+                     tal:condition="item/branch">
+                    <img alt="(branch)" src="/@@/branch" /></a>
+                </td>
+                <td style="padding-left: 0" align="center">
+                  <a tal:attributes="href item/product/fmt:url"
+                    ><img src="/@@/yes"
+                        tal:attributes="title item/product/title" /></a>
+                </td>
+                <tal:has-bugtracker condition="item/bugtracker">
+                  <td align="center">
+                    <a tal:attributes="href item/bugtracker/fmt:url"
+                       ><img src="/@@/yes"
+                          tal:attributes="title item/bugtracker/title" /></a>
+                  </td>
+                </tal:has-bugtracker>
+                <tal:has-no-bugtracker condition="not: item/bugtracker">
+                  <td tal:condition="item/bug_tracking_usage/enumvalue:LAUNCHPAD"
+                    align="center">
+                      <img src="/@@/yes" title="Launchpad" />
+                  </td>
+                  <td tal:condition="not: item/bug_tracking_usage/enumvalue:LAUNCHPAD"
+                    align="center">
+                      <img src="/@@/no" title="Unknown" />
+                      <a tal:condition="item/product/required:launchpad.Edit"
+                         tal:attributes="href item/product_edit_url">
+                         <small>(fix)</small></a></td>
+                </tal:has-no-bugtracker>
+                <tal:has-bug-supervisor condition="item/product/bug_supervisor">
+                  <td align="center">
+                    <a tal:attributes="href item/product/bug_supervisor/fmt:url"
+                       ><img src="/@@/yes"
+                          tal:attributes="title item/product/bug_supervisor/fmt:displayname"
+                          /></a>
+                  </td>
+                </tal:has-bug-supervisor>
+                <tal:has-no-bug-supervisor condition="not: item/product/bug_supervisor">
+                  <td align="center">
+                      <img src="/@@/no" title="Unspecified" />
+                      <a tal:condition="item/product/required:launchpad.Edit"
+                         tal:attributes="href item/bug_supervisor_url">
+                        <small>(change)</small></a></td>
+                </tal:has-no-bug-supervisor>
+              </tal:has-product>
+              <tal:has-no-product condition="not: item/product">
+                  <td align="center" class="bad" colspan="4">
+                      Missing corresponding project.
+                      <a href="/projects"><small>(find)</small></a>
+                      <a tal:attributes="href item/packaging_url">
+                         <small>(link)</small></a>
+                  </td>
+              </tal:has-no-product>
+              <td class="amount">
+                <a tal:attributes="href item/open_bugs_url"
+                   tal:content="item/open_bugs"></a>
+              </td>
+              <td tal:attributes="class string:amount ${item/triaged_bugs_class}">
+                <a tal:attributes="href item/triaged_bugs_url"
+                   tal:content="item/triaged_bugs"></a>
+              </td>
+              <td tal:attributes="class string:amount ${item/triaged_bugs_class}"
+                  tal:content="item/triaged_bugs_percentage/fmt:float/.2" />
+              <td tal:attributes="class string:amount ${item/triaged_bugs_class}">
+                <a tal:attributes="href item/triaged_bugs_delta_url"
+                   tal:content="item/triaged_bugs_delta"></a>
+              </td>
+              <td tal:attributes="class string:amount ${item/upstream_bugs_class}">
+                <a tal:attributes="href item/upstream_bugs_url"
+                   tal:content="item/upstream_bugs"></a>
+              </td>
+              <td tal:attributes="class string:amount ${item/upstream_bugs_class}"
+                  tal:content="item/upstream_bugs_percentage/fmt:float/.2" />
+              <td tal:attributes="class string:amount ${item/upstream_bugs_class}">
+                <a tal:attributes="href item/upstream_bugs_delta_url"
+                   tal:content="item/upstream_bugs_delta"></a>
+              </td>
+              <tal:upstream-in-launchpad
+                  condition="item/bug_tracking_usage/enumvalue:LAUNCHPAD">
+                  <td colspan="4" class="good">&nbsp;</td>
+              </tal:upstream-in-launchpad>
+              <tal:upstream-not-in-launchpad
+                  condition="not: item/bug_tracking_usage/enumvalue:LAUNCHPAD">
+                <td tal:attributes="class string:amount ${item/watched_bugs_class}"
+                    tal:content="item/watched_bugs" />
+                <td tal:attributes="class string:amount ${item/watched_bugs_class}"
+                    tal:content="item/watched_bugs_percentage/fmt:float/.2" />
+                <td tal:attributes="class string:amount ${item/watched_bugs_class}">
+                  <a tal:attributes="href item/watched_bugs_delta_url"
+                     tal:content="item/watched_bugs_delta"></a>
+                </td>
+                <td tal:attributes="class string:amount ${item/watched_bugs_class}">
+                  <tal:has-bugs-with-upstream-patches
+                      condition="item/bugs_with_upstream_patches">
+                    <a tal:attributes="href item/bugs_with_upstream_patches_url"
+                        tal:content="item/bugs_with_upstream_patches"></a>
+                  </tal:has-bugs-with-upstream-patches>
+                  <tal:has-no-bugs-with-upstream-patches
+                      condition="not: item/bugs_with_upstream_patches">
+                     -
+                  </tal:has-no-bugs-with-upstream-patches>
+                </td>
+              </tal:upstream-not-in-launchpad>
+            </tr>
+          </tbody>
+          <tfoot id="upstream-report-totals">
+            <tr>
+              <td colspan="5" class="amount"><b>Totals:</b></td>
+              <td class="amount"
+                  tal:content="view/total/open_bugs" />
+              <td class="amount"
+                  tal:content="view/total/triaged_bugs" />
+              <td class="amount"
+                  tal:content="view/total/triaged_bugs_percentage/fmt:float/.2" />
+              <td class="amount"
+                  tal:content="view/total/triaged_bugs_delta" />
+              <td class="amount"
+                  tal:content="view/total/upstream_bugs" />
+              <td class="amount"
+                  tal:content="view/total/upstream_bugs_percentage/fmt:float/.2" />
+              <td class="amount"
+                  tal:content="view/total/upstream_bugs_delta" />
+              <td class="amount"
+                  tal:content="view/total/watched_bugs" />
+              <td class="amount"
+                  tal:content="view/total/watched_bugs_percentage/fmt:float/.2" />
+              <td class="amount"
+                  tal:content="view/total/watched_bugs_delta" />
+              <td class="amount"
+                  tal:content="view/total/bugs_with_upstream_patches" />
+            </tr>
+          </tfoot>
+        </table>
+
+        <div tal:condition="view/data"
+             align="right"><small>Top <span tal:content="view/data/count:len" />
+          packages listed.</small></div>
+      </div>
     </div>
   </div>