launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #03456
[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"
- >Δ</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"
- >Δ</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"
- >Δ</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"> </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"
+ >Δ</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"
+ >Δ</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"
+ >Δ</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"> </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>