Merge lp:~cjwatson/launchpad/global-bugs-search into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18386
Proposed branch: lp:~cjwatson/launchpad/global-bugs-search
Merge into: lp:launchpad
Diff against target: 287 lines (+124/-18)
7 files modified
lib/lp/bugs/feed/bug.py (+9/-3)
lib/lp/bugs/interfaces/malone.py (+6/-6)
lib/lp/bugs/stories/feeds/xx-bug-atom.txt (+38/-0)
lib/lp/bugs/stories/feeds/xx-bug-html.txt (+27/-0)
lib/lp/bugs/tests/test_searchtasks_webservice.py (+24/-1)
lib/lp/services/feeds/browser.py (+10/-3)
lib/lp/systemhomes.py (+10/-5)
To merge this branch: bzr merge lp:~cjwatson/launchpad/global-bugs-search
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+324122@code.launchpad.net

Commit message

Export MaloneApplication.searchTasks on the webservice, and implement a global bugs feed to go with it.

Description of the change

MaloneApplication was very close to being a valid IHasBugs implementation, so I just adjusted things a little until it actually is one.

I had to implement a global bugs feed as well, since adding IHasBugs caused the feeds mechanism to spring into action.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/feed/bug.py'
2--- lib/lp/bugs/feed/bug.py 2014-11-29 01:33:59 +0000
3+++ lib/lp/bugs/feed/bug.py 2017-05-17 06:46:56 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Bug feed (syndication) views."""
10@@ -228,7 +228,10 @@
11 @property
12 def title(self):
13 """See `IFeed`."""
14- return "Bugs in %s" % self.context.displayname
15+ if IMaloneApplication.providedBy(self.context):
16+ return "Launchpad bugs"
17+ else:
18+ return "Bugs in %s" % self.context.displayname
19
20 @property
21 def feed_id(self):
22@@ -240,7 +243,10 @@
23 datecreated = self.context.datecreated.date().isoformat()
24 else:
25 datecreated = '2008'
26- url_path = urlparse(self.link_alternate)[2]
27+ if IMaloneApplication.providedBy(self.context):
28+ url_path = ''
29+ else:
30+ url_path = urlparse(self.link_alternate)[2]
31 id_ = 'tag:launchpad.net,%s:/%s%s' % (
32 datecreated,
33 self.rootsite,
34
35=== modified file 'lib/lp/bugs/interfaces/malone.py'
36--- lib/lp/bugs/interfaces/malone.py 2013-01-07 02:40:55 +0000
37+++ lib/lp/bugs/interfaces/malone.py 2017-05-17 06:46:56 +0000
38@@ -1,4 +1,4 @@
39-# Copyright 2009 Canonical Ltd. This software is licensed under the
40+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
41 # GNU Affero General Public License version 3 (see the file LICENSE).
42
43 """Interfaces pertaining to the launchpad Malone application."""
44@@ -20,7 +20,10 @@
45 from zope.interface import Attribute
46
47 from lp.bugs.interfaces.bug import IBug
48-from lp.bugs.interfaces.bugtarget import IBugTarget
49+from lp.bugs.interfaces.bugtarget import (
50+ IBugTarget,
51+ IHasBugs,
52+ )
53 from lp.services.webapp.interfaces import ILaunchpadApplication
54
55
56@@ -30,13 +33,10 @@
57 ]
58
59
60-class IMaloneApplication(ILaunchpadApplication):
61+class IMaloneApplication(ILaunchpadApplication, IHasBugs):
62 """Application root for malone."""
63 export_as_webservice_collection(IBug)
64
65- def searchTasks(search_params):
66- """Search IBugTasks with the given search parameters."""
67-
68 @call_with(user=REQUEST_USER)
69 @operation_parameters(
70 bug_id=copy_field(IBug['id']),
71
72=== modified file 'lib/lp/bugs/stories/feeds/xx-bug-atom.txt'
73--- lib/lp/bugs/stories/feeds/xx-bug-atom.txt 2016-07-27 22:36:34 +0000
74+++ lib/lp/bugs/stories/feeds/xx-bug-atom.txt 2017-05-17 06:46:56 +0000
75@@ -437,6 +437,44 @@
76 ... "Published dates are not sorted.")
77
78
79+== Latest bugs for any target ==
80+
81+This feed gets the latest bugs reported against any target.
82+
83+ >>> browser.open('http://feeds.launchpad.dev/bugs/latest-bugs.atom')
84+ >>> validate_feed(browser.contents,
85+ ... browser.headers['content-type'], browser.url)
86+ No Errors
87+ >>> BSS(browser.contents).title.contents
88+ [u'Launchpad bugs']
89+ >>> browser.url
90+ 'http://feeds.launchpad.dev/bugs/latest-bugs.atom'
91+
92+ >>> soup = BSS(browser.contents, parseOnlyThese=SoupStrainer('id'))
93+ >>> print extract_text(soup.find('id'))
94+ tag:launchpad.net,2008:/bugs
95+
96+ >>> self_links = parse_links(browser.contents, 'self')
97+ >>> for link in self_links:
98+ ... print link
99+ <link rel="self" href="http://feeds.launchpad.dev/bugs/latest-bugs.atom" />
100+
101+ >>> entries = parse_entries(browser.contents)
102+ >>> print len(entries)
103+ 13
104+
105+ >>> entry = entries[0]
106+ >>> print extract_text(entry.title)
107+ [15] Nonsensical bugs are useless
108+ >>> print extract_text(entry.author('name')[0])
109+ Foo Bar
110+ >>> print extract_text(entry.author('uri')[0])
111+ http://bugs.launchpad.dev/~name16
112+
113+ >>> assert check_entries_order(entries), (
114+ ... "Published dates are not sorted.")
115+
116+
117 == General bug search ==
118
119 This feed is the most useful of them all. Any bug search can be turned into
120
121=== modified file 'lib/lp/bugs/stories/feeds/xx-bug-html.txt'
122--- lib/lp/bugs/stories/feeds/xx-bug-html.txt 2016-07-27 22:36:34 +0000
123+++ lib/lp/bugs/stories/feeds/xx-bug-html.txt 2017-05-17 06:46:56 +0000
124@@ -146,6 +146,33 @@
125 [15, 15, 12, 11, 10, 9, 9, 7, 7, 3, 3, 3]
126
127
128+== Latest bugs for any target ==
129+
130+This feed gets the latest bugs reported against any target.
131+
132+ >>> browser.open('http://feeds.launchpad.dev/bugs/latest-bugs.html?'
133+ ... 'show_column=bugtargetdisplayname')
134+ >>> browser.title
135+ 'Launchpad bugs'
136+ >>> browser.url
137+ 'http://feeds.launchpad.dev/bugs/latest-bugs.html?show_column=bugtargetdisplayname'
138+
139+ >>> entries = parse_entries(browser.contents)
140+ >>> print len(entries)
141+ 27
142+
143+ >>> print_entry(entries[1])
144+ number: 15
145+ href: http://bugs.launchpad.dev/redfish/+bug/15
146+ title: Nonsensical bugs are useless
147+ importance: Undecided
148+ status: New
149+
150+ >>> get_bug_numbers(entries)
151+ [15, 15, 13, 12, 11, 10, 9, 9, 8, 7, 7, 5, 5, 5, 4, 3, 3, 3, 2, 2, 2, 2,
152+ 2, 1, 1, 1]
153+
154+
155 == General bug search ==
156
157 This feed is the most useful of them all. Any bug search can be turned into
158
159=== modified file 'lib/lp/bugs/tests/test_searchtasks_webservice.py'
160--- lib/lp/bugs/tests/test_searchtasks_webservice.py 2012-10-09 10:28:02 +0000
161+++ lib/lp/bugs/tests/test_searchtasks_webservice.py 2017-05-17 06:46:56 +0000
162@@ -1,4 +1,4 @@
163-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
164+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
165 # GNU Affero General Public License version 3 (see the file LICENSE).
166
167 """Webservice unit tests related to Launchpad Bugs."""
168@@ -124,6 +124,29 @@
169 self.assertEqual(response['total_size'], 2)
170
171
172+class TestMaloneApplicationSearchTasks(TestCaseWithFactory):
173+ """Test the searchTasks operation on the top-level /bugs collection."""
174+
175+ layer = DatabaseFunctionalLayer
176+
177+ def test_global_search_by_tag(self):
178+ project1 = self.factory.makeProduct()
179+ project2 = self.factory.makeProduct()
180+ bug1 = self.factory.makeBug(target=project1, tags=["foo"])
181+ self.factory.makeBug(target=project1, tags=["bar"])
182+ bug3 = self.factory.makeBug(target=project2, tags=["foo"])
183+ self.factory.makeBug(target=project2, tags=["baz"])
184+ webservice = LaunchpadWebServiceCaller(
185+ "launchpad-library", "salgado-change-anything")
186+ response = webservice.named_get(
187+ "/bugs", "searchTasks", api_version="devel", tags="foo").jsonBody()
188+ self.assertEqual(2, response["total_size"])
189+ self.assertContentEqual(
190+ [bug1.id, bug3.id],
191+ [int(entry["bug_link"].split("/")[-1])
192+ for entry in response["entries"]])
193+
194+
195 class TestGetBugData(TestCaseWithFactory):
196 """Tests for the /bugs getBugData operation."""
197
198
199=== modified file 'lib/lp/services/feeds/browser.py'
200--- lib/lp/services/feeds/browser.py 2015-07-08 16:05:11 +0000
201+++ lib/lp/services/feeds/browser.py 2017-05-17 06:46:56 +0000
202@@ -1,4 +1,4 @@
203-# Copyright 2009 Canonical Ltd. This software is licensed under the
204+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
205 # GNU Affero General Public License version 3 (see the file LICENSE).
206
207 """View support classes for feeds."""
208@@ -35,6 +35,7 @@
209 IBugTask,
210 IBugTaskSet,
211 )
212+from lp.bugs.interfaces.malone import IMaloneApplication
213 from lp.code.interfaces.branch import IBranch
214 from lp.layers import FeedsLayer
215 from lp.registry.interfaces.announcement import (
216@@ -121,7 +122,8 @@
217 redirect = RedirectionView(target, self.request, 301)
218 return redirect
219
220- # Handle the two formats of urls:
221+ # Handle the three formats of urls:
222+ # http://feeds.launchpad.net/bugs/latest-bugs.atom
223 # http://feeds.launchpad.net/bugs/+bugs.atom?...
224 # http://feeds.launchpad.net/bugs/1/bug.atom
225 if name == 'bugs':
226@@ -134,6 +136,8 @@
227 return getUtility(IBugTaskSet)
228 else:
229 raise Unauthorized("Bug search feed deactivated")
230+ elif bug_id.startswith('latest-bugs.'):
231+ return getUtility(IMaloneApplication)
232 else:
233 self.request.stepstogo.consume()
234 return getUtility(IBugSet).getByNameOrID(bug_id)
235@@ -209,7 +213,10 @@
236
237 @property
238 def title(self):
239- return 'Latest Bugs for %s' % self.context.displayname
240+ if IMaloneApplication.providedBy(self.context):
241+ return 'Latest Bugs'
242+ else:
243+ return 'Latest Bugs for %s' % self.context.displayname
244
245 @property
246 def href(self):
247
248=== modified file 'lib/lp/systemhomes.py'
249--- lib/lp/systemhomes.py 2015-07-08 16:05:11 +0000
250+++ lib/lp/systemhomes.py 2017-05-17 06:46:56 +0000
251@@ -1,4 +1,4 @@
252-# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
253+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
254 # GNU Affero General Public License version 3 (see the file LICENSE).
255
256 """Content classes for the 'home pages' of the subsystems of Launchpad."""
257@@ -40,6 +40,7 @@
258 IPrivateMaloneApplication,
259 )
260 from lp.bugs.model.bug import Bug
261+from lp.bugs.model.bugtarget import HasBugsBase
262 from lp.code.interfaces.codehosting import (
263 IBazaarApplication,
264 ICodehostingApplication,
265@@ -117,14 +118,18 @@
266
267
268 @implementer(IMaloneApplication)
269-class MaloneApplication:
270+class MaloneApplication(HasBugsBase):
271
272 def __init__(self):
273 self.title = 'Malone: the Launchpad bug tracker'
274
275- def searchTasks(self, search_params):
276- """See `IMaloneApplication`."""
277- return getUtility(IBugTaskSet).search(search_params)
278+ def _customizeSearchParams(self, search_params):
279+ """See `HasBugsBase`."""
280+ pass
281+
282+ def getBugSummaryContextWhereClause(self):
283+ """See `HasBugsBase`."""
284+ return True
285
286 def getBugData(self, user, bug_id, related_bug=None):
287 """See `IMaloneApplication`."""