Merge lp:~sinzui/launchpad/upstream-downstream-bug-links-0 into lp:launchpad/db-devel

Proposed by Curtis Hovey on 2010-11-19
Status: Merged
Approved by: Curtis Hovey on 2010-11-23
Approved revision: no longer in the source branch.
Merged at revision: 9993
Proposed branch: lp:~sinzui/launchpad/upstream-downstream-bug-links-0
Merge into: lp:launchpad/db-devel
Diff against target: 452 lines (+233/-61)
7 files modified
lib/canonical/launchpad/icing/style.css (+0/-15)
lib/lp/bugs/browser/bugtarget.py (+0/-13)
lib/lp/bugs/browser/bugtask.py (+50/-1)
lib/lp/bugs/browser/tests/test_buglisting.py (+140/-8)
lib/lp/bugs/stories/bugs/xx-front-page-info.txt (+8/-9)
lib/lp/bugs/templates/bugtarget-bugs.pt (+5/-14)
lib/lp/bugs/templates/bugtarget-macros-search.pt (+30/-1)
To merge this branch: bzr merge lp:~sinzui/launchpad/upstream-downstream-bug-links-0
Reviewer Review Type Date Requested Status
Paul Hummer (community) ui 2010-11-22 Approve on 2010-11-23
Robert Collins (community) 2010-11-19 Approve on 2010-11-21
Review via email: mp+41371@code.launchpad.net

Description of the Change

This is my branch to add reciprocal links between packages and upstream
projects.

    lp:~sinzui/launchpad/upstream-downstream-bug-links-0
    Diff size: 404
    Launchpad bug:
        https://bugs.launchpad.net/bugs/3152
    Test command: ./bin/test -vv \
        -t TestBugTaskSearchListingView -t bugtask-searches
    Pre-implementation: Edwin, Deryck
    Target release: 10.12

Add reciprocal links between packages and upstream projects
-----------------------------------------------------------

The bug search page should contain a notice that bugs are also tracked
in a downstream package or upstream project. Contributors can search the
alternate project or package when investigating bugs.

This link already appears on project pages when the project does not use
Lp to track bugs. The link should also appear when the project uses Lp.
The package page needs the reciprocating link.

This implementation meets the requirements from bridging the gap, but I
am dissatisfied with it. There is an open bug that could be fixed in
another branch to address the awkward experience I had using this feature
in dev: When my search fails, and I see that there is an upstream/downstream
location to search, I expect the link to perform my search. re-typing text
is not difficult, but setting up an advanced search is :( The work to fix the
bug and two related bugs are intrinsic to the search form--the solution is
not obvious.

Screenshots
    http://people.canonical.com/~curtis/downstream-bugs.png
    http://people.canonical.com/~curtis/upstream-bugs.png
    http://people.canonical.com/~curtis/upstream-bugs-searched.png

Rules
-----

    * Refactor the views and templates so that the also-in-ubuntu block
      is available on the project bug search page.
    * Add and also-in-upstream block for DSP and SP pages.

QA
--

    * Visit https://bugs.launchpad.net/jokosher
    * Verify their is a message that bugs are also tracked in Ubuntu.
    * Follow the link to Ubuntu Jokosher bug page.
    * Verify there is a message that bugs are also tracked in Jokosher
      and there is a link back.

Lint
----

Linting changed files:
  lib/canonical/launchpad/icing/style.css
  lib/lp/bugs/browser/bugtarget.py
  lib/lp/bugs/browser/bugtask.py
  lib/lp/bugs/browser/tests/test_buglisting.py
  lib/lp/bugs/templates/bugtarget-bugs.pt
  lib/lp/bugs/templates/bugtarget-macros-search.pt

Test
----

Added tests for the 3 moved/added properties, and verified the also-in-ubuntu
and also-in-upstream blocks in the markup.
    lib/lp/bugs/browser/tests/test_buglisting.py

Implementation
--------------

Extracted the external_bugtracker() method and also-in-ubuntu markup to a
base class and macro template so that they can be reused.
    lib/lp/bugs/browser/bugtarget.py
    lib/lp/bugs/templates/bugtarget-bugs.pt

Added has_bugtracker() and upstream_launchpad_project() to support the
cases in the template that need to show also-in-ubuntu and also-in-upstream.
The template markup needed revision to support presenting the additional
information with proper spacing. This change also removed the exceptional
CSS used by the search form when a search has not been performed.
    lib/lp/bugs/browser/bugtask.py
    lib/lp/bugs/templates/bugtarget-macros-search.pt

Removed obsolete style that changed the color of the text.
    lib/canonical/launchpad/icing/style.css

To post a comment you must log in.
Robert Collins (lifeless) wrote :

Some things you may have already thought of that reading this prompted me to think about. Not really issues with your patch, just things we should do/address/think about.

Firstly, for a derived distro, the 'upstream' is the parent distro; for a package in a derived distro the upstream is that package in the parent distro if it exists, or the upstream otherwise. Or perhaps we should supply all the 'upstreams' at once. Similarly downstream:
product -> [0..N] distro packages.

We probably want a unified search, rather than folk having to manually search again and again and again to consider all locations (once they choose to expand their context, that is).

Duplicate detection will definitely want a unified backend search.

Your tests have a mixin - thats ok, but I think you'll find that one of:
 - using a Fixture
 - improving the Launchpad default Factory

is better: your mixin has only self.factory as state, so really doesn't need to be a mixin at all.

review: Approve
Curtis Hovey (sinzui) wrote :

On Sun, 2010-11-21 at 05:36 +0000, Robert Collins wrote:
> Firstly, for a derived distro, the 'upstream' is the parent distro;
> for a package in a derived distro the upstream is that package in the
> parent distro if it exists, or the upstream otherwise. Or perhaps we
> should supply all the 'upstreams' at once. Similarly downstream:
> product -> [0..N] distro packages.
>
> We probably want a unified search, rather than folk having to manually
> search again and again and again to consider all locations (once they
> choose to expand their context, that is).

Both these issues came up in UDS. The work was deemed to be out of scope
for bridging-the-gap. We recognise that doing the work for the user is
better than telling users where they can do more work. Adding the link
make the page consistent, but I do not think this is a great usability
improvement.

My conversations with mpt and barry makes me believe ubuntu contributors
want an option to expand the search of a bug target.
    [x] include bugs reported in upstream jokosher
    [x] include bugs reported in derived distributions

--
__Curtis C. Hovey_________
http://launchpad.net/

Robert Collins (lifeless) wrote :

That sounds nice.

-Rob

Paul Hummer (rockstar) wrote :

I think this is fine, but I think the better UI would be to have bugs from the sourcepackage show up in search for the project, and bugs from the projects sow up in the search for the sourcepackage.

I'm sure that will probably have some mega database issues, so there's a technical reason not to do that, but I think the idea has merit for the future.

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/icing/style.css'
2--- lib/canonical/launchpad/icing/style.css 2010-10-15 01:48:05 +0000
3+++ lib/canonical/launchpad/icing/style.css 2010-11-23 21:26:54 +0000
4@@ -655,21 +655,6 @@
5 color: #3840be;
6 }
7
8-.search-box {
9- text-align: left;
10- float: left;
11- border:1px solid #d6d6d6;
12- margin: 0.5em 0 0.5em 0;
13- color: #717171;
14- padding: 8px;
15- -moz-border-radius: 5px;
16- -o-border-radius: 5px;
17- -webkit-border-radius: 5px;
18-}
19-
20-.search-box form.primary.search {
21- margin: 0.5em;
22-}
23
24 /* ====== Content area styles ====== */
25
26
27=== modified file 'lib/lp/bugs/browser/bugtarget.py'
28--- lib/lp/bugs/browser/bugtarget.py 2010-11-02 21:38:30 +0000
29+++ lib/lp/bugs/browser/bugtarget.py 2010-11-23 21:26:54 +0000
30@@ -59,7 +59,6 @@
31 )
32 from canonical.launchpad.browser.librarian import ProxiedLibraryFileAlias
33 from canonical.launchpad.interfaces.launchpad import (
34- IHasExternalBugTracker,
35 ILaunchpadCelebrities,
36 )
37 from canonical.launchpad.searchbuilder import any
38@@ -1275,18 +1274,6 @@
39 return service_usage.bug_tracking_usage
40
41 @property
42- def external_bugtracker(self):
43- """External bug tracking system designated for the context.
44-
45- :returns: `IBugTracker` or None
46- """
47- has_external_bugtracker = IHasExternalBugTracker(self.context, None)
48- if has_external_bugtracker is None:
49- return None
50- else:
51- return has_external_bugtracker.getExternalBugTracker()
52-
53- @property
54 def bugtracker(self):
55 """Description of the context's bugtracker.
56
57
58=== modified file 'lib/lp/bugs/browser/bugtask.py'
59--- lib/lp/bugs/browser/bugtask.py 2010-10-31 20:18:45 +0000
60+++ lib/lp/bugs/browser/bugtask.py 2010-11-23 21:26:54 +0000
61@@ -130,7 +130,10 @@
62 BugTargetLatestBugsFeedLink,
63 FeedsMixin,
64 )
65-from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
66+from canonical.launchpad.interfaces.launchpad import (
67+ IHasExternalBugTracker,
68+ ILaunchpadCelebrities,
69+ )
70 from canonical.launchpad.interfaces.validation import (
71 valid_upstreamtask,
72 validate_distrotask,
73@@ -2300,6 +2303,52 @@
74 service_usage = IServiceUsage(self.context)
75 return service_usage.bug_tracking_usage
76
77+ @cachedproperty
78+ def external_bugtracker(self):
79+ """External bug tracking system designated for the context.
80+
81+ :returns: `IBugTracker` or None
82+ """
83+ has_external_bugtracker = IHasExternalBugTracker(self.context, None)
84+ if has_external_bugtracker is None:
85+ return None
86+ else:
87+ return has_external_bugtracker.getExternalBugTracker()
88+
89+ @property
90+ def has_bugtracker(self):
91+ """Does the `IBugTarget` have a bug tracker or use Launchpad?"""
92+ usage = IServiceUsage(self.context)
93+ uses_lp = usage.bug_tracking_usage == ServiceUsage.LAUNCHPAD
94+ if self.external_bugtracker or uses_lp:
95+ return True
96+ return False
97+
98+ @property
99+ def upstream_launchpad_project(self):
100+ """The linked upstream `IProduct` for the package.
101+
102+ If this `IBugTarget` is a `IDistributionSourcePackage` or an
103+ `ISourcePackage` and it is linked to an upstream project that uses
104+ Launchpad to track bugs, return the `IProduct`. Otherwise,
105+ return None
106+
107+ :returns: `IProduct` or None
108+ """
109+ if self._sourcePackageContext():
110+ sp = self.context
111+ elif self._distroSourcePackageContext():
112+ sp = self.context.development_version
113+ else:
114+ sp = None
115+ if sp is not None:
116+ packaging = sp.packaging
117+ if packaging is not None:
118+ product = packaging.productseries.product
119+ if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
120+ return product
121+ return None
122+
123 @property
124 def page_title(self):
125 return "Bugs in %s" % self.context.title
126
127=== modified file 'lib/lp/bugs/browser/tests/test_buglisting.py'
128--- lib/lp/bugs/browser/tests/test_buglisting.py 2010-10-06 22:05:15 +0000
129+++ lib/lp/bugs/browser/tests/test_buglisting.py 2010-11-23 21:26:54 +0000
130@@ -3,19 +3,28 @@
131
132 __metaclass__ = type
133
134+from zope.component import getUtility
135+
136+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
137 from canonical.launchpad.testing.pages import (
138 extract_text,
139 find_tag_by_id,
140 find_tags_by_class,
141 )
142-from canonical.launchpad.webapp import canonical_url
143-from canonical.testing.layers import LaunchpadFunctionalLayer
144-from lp.testing import BrowserTestCase
145-
146-
147-class TestBugTaskSearchListingView(BrowserTestCase):
148-
149- layer = LaunchpadFunctionalLayer
150+from canonical.launchpad.webapp.publisher import canonical_url
151+from canonical.testing.layers import DatabaseFunctionalLayer
152+from lp.testing import (
153+ BrowserTestCase,
154+ login_person,
155+ person_logged_in,
156+ TestCaseWithFactory,
157+ )
158+from lp.testing.views import create_initialized_view
159+
160+
161+class TestBugTaskSearchListingPage(BrowserTestCase):
162+
163+ layer = DatabaseFunctionalLayer
164
165 def _makeDistributionSourcePackage(self):
166 distro = self.factory.makeDistribution('test-distro')
167@@ -120,3 +129,126 @@
168 self.assertIs(None,
169 find_tag_by_id(browser.contents, 'portlet-tags'),
170 "portlet-tags should not be shown.")
171+
172+
173+class BugTargetTestCase(TestCaseWithFactory):
174+ """Test helpers for setting up `IBugTarget` tests."""
175+
176+ def _makeBugTargetProduct(self, bug_tracker=None, packaging=False):
177+ """Return a product that may use Launchpad or an external bug tracker.
178+
179+ bug_tracker may be None, 'launchpad', or 'external'.
180+ """
181+ product = self.factory.makeProduct()
182+ if bug_tracker is not None:
183+ with person_logged_in(product.owner):
184+ if bug_tracker == 'launchpad':
185+ product.official_malone = True
186+ else:
187+ product.bugtracker = self.factory.makeBugTracker()
188+ if packaging:
189+ self.factory.makePackagingLink(
190+ productseries=product.development_focus, in_ubuntu=True)
191+ return product
192+
193+
194+class TestBugTaskSearchListingViewProduct(BugTargetTestCase):
195+
196+ layer = DatabaseFunctionalLayer
197+
198+ def test_external_bugtracker_is_none(self):
199+ bug_target = self._makeBugTargetProduct()
200+ view = create_initialized_view(bug_target, '+bugs')
201+ self.assertEqual(None, view.external_bugtracker)
202+
203+ def test_external_bugtracker(self):
204+ bug_target = self._makeBugTargetProduct(bug_tracker='external')
205+ view = create_initialized_view(bug_target, '+bugs')
206+ self.assertEqual(bug_target.bugtracker, view.external_bugtracker)
207+
208+ def test_has_bugtracker_is_false(self):
209+ bug_target = self.factory.makeProduct()
210+ view = create_initialized_view(bug_target, '+bugs')
211+ self.assertEqual(False, view.has_bugtracker)
212+
213+ def test_has_bugtracker_external_is_true(self):
214+ bug_target = self._makeBugTargetProduct(bug_tracker='external')
215+ view = create_initialized_view(bug_target, '+bugs')
216+ self.assertEqual(True, view.has_bugtracker)
217+
218+ def test_has_bugtracker_launchpad_is_true(self):
219+ bug_target = self._makeBugTargetProduct(bug_tracker='launchpad')
220+ view = create_initialized_view(bug_target, '+bugs')
221+ self.assertEqual(True, view.has_bugtracker)
222+
223+ def test_product_without_packaging_also_in_ubuntu_is_none(self):
224+ bug_target = self._makeBugTargetProduct(bug_tracker='launchpad')
225+ login_person(bug_target.owner)
226+ view = create_initialized_view(
227+ bug_target, '+bugs', principal=bug_target.owner)
228+ self.assertEqual(None, find_tag_by_id(view(), 'also-in-ubuntu'))
229+
230+ def test_product_with_packaging_also_in_ubuntu(self):
231+ bug_target = self._makeBugTargetProduct(
232+ bug_tracker='launchpad', packaging=True)
233+ login_person(bug_target.owner)
234+ view = create_initialized_view(
235+ bug_target, '+bugs', principal=bug_target.owner)
236+ content = find_tag_by_id(view.render(), 'also-in-ubuntu')
237+ link = canonical_url(
238+ bug_target.ubuntu_packages[0], force_local_path=True)
239+ self.assertEqual(link, content.a['href'])
240+
241+
242+class TestBugTaskSearchListingViewDSP(BugTargetTestCase):
243+
244+ layer = DatabaseFunctionalLayer
245+
246+ def _getBugTarget(self, obj):
247+ """Return the `IBugTarget` under test.
248+
249+ Return the object that was passed. Sub-classes can redefine
250+ this method.
251+ """
252+ return obj
253+
254+ def test_package_with_upstream_launchpad_project(self):
255+ upstream_project = self._makeBugTargetProduct(
256+ bug_tracker='launchpad', packaging=True)
257+ login_person(upstream_project.owner)
258+ bug_target = self._getBugTarget(
259+ upstream_project.distrosourcepackages[0])
260+ view = create_initialized_view(
261+ bug_target, '+bugs', principal=upstream_project.owner)
262+ self.assertEqual(upstream_project, view.upstream_launchpad_project)
263+ content = find_tag_by_id(view.render(), 'also-in-upstream')
264+ link = canonical_url(upstream_project, rootsite='bugs')
265+ self.assertEqual(link, content.a['href'])
266+
267+ def test_package_with_upstream_nonlaunchpad_project(self):
268+ upstream_project = self._makeBugTargetProduct(packaging=True)
269+ login_person(upstream_project.owner)
270+ bug_target = self._getBugTarget(
271+ upstream_project.distrosourcepackages[0])
272+ view = create_initialized_view(
273+ bug_target, '+bugs', principal=upstream_project.owner)
274+ self.assertEqual(None, view.upstream_launchpad_project)
275+ self.assertEqual(None, find_tag_by_id(view(), 'also-in-upstream'))
276+
277+ def test_package_without_upstream_project(self):
278+ observer = self.factory.makePerson()
279+ dsp = self.factory.makeDistributionSourcePackage(
280+ 'test-dsp', distribution=getUtility(ILaunchpadCelebrities).ubuntu)
281+ bug_target = self._getBugTarget(dsp)
282+ login_person(observer)
283+ view = create_initialized_view(
284+ bug_target, '+bugs', principal=observer)
285+ self.assertEqual(None, view.upstream_launchpad_project)
286+ self.assertEqual(None, find_tag_by_id(view(), 'also-in-upstream'))
287+
288+
289+class TestBugTaskSearchListingViewSP(TestBugTaskSearchListingViewDSP):
290+
291+ def _getBugTarget(self, dsp):
292+ """Return the current `ISourcePackage` for the dsp."""
293+ return dsp.development_version
294
295=== modified file 'lib/lp/bugs/stories/bugs/xx-front-page-info.txt'
296--- lib/lp/bugs/stories/bugs/xx-front-page-info.txt 2010-09-14 13:25:50 +0000
297+++ lib/lp/bugs/stories/bugs/xx-front-page-info.txt 2010-11-23 21:26:54 +0000
298@@ -69,18 +69,18 @@
299 >>> bug = factory.makeBug(product=uses_malone)
300 >>> logout()
301 >>> anon_browser.open('http://bugs.launchpad.dev/uses-malone')
302- >>> print extract_text(
303- ... find_tags_by_class(anon_browser.contents, 'search-box')[0])
304+ >>> content = find_main_content(anon_browser.contents)
305+ >>> print extract_text(find_tag_by_id(content, 'simple-bug-search'))
306 by importance
307 by status
308 ...
309 Advanced search
310
311
312- >>> print find_tag_by_id(anon_browser.contents, 'no-bugs-filed')
313+ >>> print find_tag_by_id(content, 'no-bugs-filed')
314 None
315
316- >>> hot_bugs = find_tag_by_id(anon_browser.contents, 'hot-bugs')
317+ >>> hot_bugs = find_tag_by_id(content, 'hot-bugs')
318 >>> print extract_text(hot_bugs)
319 Summary Status Importance Last changed
320 #16 ... New Undecided ...
321@@ -95,18 +95,17 @@
322 >>> logout()
323
324 >>> anon_browser.open('http://bugs.launchpad.dev/uses-malone')
325- >>> print find_tag_by_id(anon_browser.contents, 'hot-bugs')
326+ >>> content = find_main_content(anon_browser.contents)
327+ >>> print find_tag_by_id(content, 'hot-bugs')
328 None
329
330- >>> bug_list = find_tag_by_id(
331- ... anon_browser.contents, 'no-bugs-filed')
332+ >>> bug_list = find_tag_by_id(content, 'no-bugs-filed')
333 >>> print extract_text(bug_list)
334 There are currently no open bugs filed against Uses-malone.
335
336 But since the project has a bug, the search box is still visible.
337
338- >>> print extract_text(
339- ... find_tags_by_class(anon_browser.contents, 'search-box')[0])
340+ >>> print extract_text(find_tag_by_id(content, 'simple-bug-search'))
341 by importance
342 by status
343 ...
344
345=== modified file 'lib/lp/bugs/templates/bugtarget-bugs.pt'
346--- lib/lp/bugs/templates/bugtarget-bugs.pt 2010-09-13 21:25:14 +0000
347+++ lib/lp/bugs/templates/bugtarget-bugs.pt 2010-11-23 21:26:54 +0000
348@@ -101,7 +101,7 @@
349 <tal:uses_launchpad_bugtracker
350 condition="view/bug_tracking_usage/enumvalue:LAUNCHPAD">
351 <tal:has_bugtasks condition="context/has_bugtasks">
352- <div class="search-box" style="margin-bottom:2em">
353+ <div id="simple-bug-search" class="portlet-top" style="margin-bottom:2em">
354 <metal:search
355 use-macro="context/@@+bugtarget-macros-search/simple-search-form"
356 />
357@@ -111,7 +111,7 @@
358 --></script>
359 </tal:has_bugtasks>
360
361- <tal:has_hot_bugs condition="view/hot_bugs_info/bugtasks">
362+ <div class="portlet" tal:condition="view/hot_bugs_info/bugtasks">
363 <h2>Hot bugs</h2>
364
365 <table class="listing" id="hot-bugs"
366@@ -157,7 +157,7 @@
367 tal:condition="view/hot_bugs_info/has_more_bugs">
368 <a tal:attributes="href string:${context/fmt:url/+bugs}?orderby=-heat&field.status%3Alist=NEW&field.status%3Alist=INCOMPLETE_WITH_RESPONSE&field.status%3Alist=INCOMPLETE_WITHOUT_RESPONSE&field.status%3Alist=CONFIRMED&field.status%3Alist=TRIAGED&field.status%3Alist=INPROGRESS&field.status%3Alist=FIXCOMMITTED&field.omit_dupes=on">See all hot bugs</a>
369 </p>
370- </tal:has_hot_bugs>
371+ </div>
372
373 <tal:no_hot_bugs condition="not: view/hot_bugs_info/bugtasks">
374 <p id="no-bugs-filed"><strong>There are currently no
375@@ -190,17 +190,8 @@
376
377 <tal:also_in_ubuntu
378 condition="not: view/bug_tracking_usage/enumvalue:LAUNCHPAD">
379- <p tal:define="packages context/ubuntu_packages | nothing"
380- tal:condition="packages"
381- id="also-in-ubuntu">
382- Ubuntu
383- <tal:also condition="view/external_bugtracker">also</tal:also>
384- tracks bugs for packages derived from this project:
385- <tal:packages repeat="package packages">
386- <span style="white-space: nowrap"
387- tal:content="structure package/fmt:link" /><tal:comma
388- condition="not:repeat/package/end">,</tal:comma></tal:packages>.
389- </p>
390+ <metal:also-in-ubuntu
391+ use-macro="context/@@+bugtarget-macros-search/also-in-ubuntu" />
392 </tal:also_in_ubuntu>
393
394 <div
395
396=== modified file 'lib/lp/bugs/templates/bugtarget-macros-search.pt'
397--- lib/lp/bugs/templates/bugtarget-macros-search.pt 2010-01-19 22:34:00 +0000
398+++ lib/lp/bugs/templates/bugtarget-macros-search.pt 2010-11-23 21:26:54 +0000
399@@ -3,6 +3,21 @@
400 xmlns:metal="http://xml.zope.org/namespaces/metal"
401 omit-tag="">
402
403+<metal:block define-macro="also-in-ubuntu">
404+ <p id="also-in-ubuntu"
405+ tal:define="packages context/ubuntu_packages | nothing"
406+ tal:condition="packages">
407+ Ubuntu
408+ <tal:also condition="view/has_bugtracker">also</tal:also>
409+ tracks bugs for packages derived from this project:
410+ <tal:packages repeat="package packages">
411+ <span style="white-space: nowrap"
412+ tal:content="structure package/fmt:link" /><tal:comma
413+ condition="not:repeat/package/end">,</tal:comma></tal:packages>.
414+ </p>
415+</metal:block>
416+
417+
418 <metal:block define-macro="sortwidget">
419 <tal:comment condition="nothing">
420 This macro expects that the callsite's view class has a shouldShowTargetName
421@@ -65,6 +80,7 @@
422 <metal:block define-macro="simple-search-form">
423 <form method="get" name="search" class="primary search"
424 tal:attributes="action search_url|string:">
425+ <p>
426 <tal:searchbox replace="structure view/widgets/searchtext" />
427 <metal:widget use-macro="context/@@+bugtarget-macros-search/sortwidget" />
428 <input type="submit" name="search" value="Search" />
429@@ -77,9 +93,22 @@
430 <tal:widget replace="structure view/widgets/has_patch/hidden" />
431 <tal:widget replace="structure view/widgets/component/hidden" />
432 <tal:widget replace="structure view/widgets/has_no_package/hidden" />
433- <br /><br />
434+ </p>
435+ <p>
436 <a tal:attributes="href advanced_search_url|string:?advanced=1"
437 >Advanced search</a>
438+ </p>
439+
440+ <metal:also-in-ubuntu
441+ use-macro="context/@@+bugtarget-macros-search/also-in-ubuntu" />
442+
443+ <p id="also-in-upstream"
444+ tal:define="product view/upstream_launchpad_project | nothing"
445+ tal:condition="product">
446+ <a tal:replace="structure product/fmt:link:bugs" /> also
447+ tracks bugs for this package.
448+ </p>
449+
450 <div metal:define-slot="extra-search-widgets">
451 </div>
452 </form>

Subscribers

People subscribed via source and target branches

to status/vote changes: