Merge lp:~maxiberta/launchpad/app-browser-dedup-sitesearch-doctests into lp:launchpad
- app-browser-dedup-sitesearch-doctests
- Merge into devel
Proposed by
Maximiliano Bertacchini
Status: | Merged |
---|---|
Merged at revision: | 18626 |
Proposed branch: | lp:~maxiberta/launchpad/app-browser-dedup-sitesearch-doctests |
Merge into: | lp:launchpad |
Diff against target: |
965 lines (+55/-768) 6 files modified
lib/lp/app/browser/doc/launchpad-search-pages-bing.txt (+0/-726) lib/lp/app/browser/doc/launchpad-search-pages.txt (+20/-18) lib/lp/app/browser/tests/test_views.py (+18/-7) lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-1.json (+1/-1) lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-2.json (+11/-11) lib/lp/services/sitesearch/tests/test_bing.py (+5/-5) |
To merge this branch: | bzr merge lp:~maxiberta/launchpad/app-browser-dedup-sitesearch-doctests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+343250@code.launchpad.net |
Commit message
Deduplicate lp.app.browser sitesearch doctests.
Description of the change
Deduplicate lp.app.browser sitesearch doctests. In particular, launchpad-
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === removed file 'lib/lp/app/browser/doc/launchpad-search-pages-bing.txt' |
2 | --- lib/lp/app/browser/doc/launchpad-search-pages-bing.txt 2018-04-10 18:17:07 +0000 |
3 | +++ lib/lp/app/browser/doc/launchpad-search-pages-bing.txt 1970-01-01 00:00:00 +0000 |
4 | @@ -1,726 +0,0 @@ |
5 | -Launchpad search page |
6 | -===================== |
7 | - |
8 | -Users can search for Launchpad objects and pages from the search form |
9 | -located on all pages. The search is performed and displayed by the |
10 | -LaunchpadSearchView. |
11 | - |
12 | - >>> from zope.component import getMultiAdapter, getUtility |
13 | - >>> from lp.services.webapp.interfaces import ILaunchpadRoot |
14 | - >>> from lp.services.webapp.servers import LaunchpadTestRequest |
15 | - |
16 | - >>> root = getUtility(ILaunchpadRoot) |
17 | - >>> request = LaunchpadTestRequest() |
18 | - >>> search_view = getMultiAdapter((root, request), name="+search") |
19 | - >>> search_view.initialize() |
20 | - >>> search_view |
21 | - <....SimpleViewClass from .../templates/launchpad-search.pt ...> |
22 | - |
23 | - |
24 | -Page title and heading |
25 | ----------------------- |
26 | - |
27 | -The page title and heading suggest to the user to search launchpad |
28 | -when there is no search text. |
29 | - |
30 | - >>> print search_view.text |
31 | - None |
32 | - >>> search_view.page_title |
33 | - 'Search Launchpad' |
34 | - >>> search_view.page_heading |
35 | - 'Search Launchpad' |
36 | - |
37 | -When text is not None, the title indicates what was searched. |
38 | - |
39 | - >>> def getSearchView(form): |
40 | - ... search_param_list = [] |
41 | - ... for name in sorted(form): |
42 | - ... value = form[name] |
43 | - ... search_param_list.append('%s=%s' % (name, value)) |
44 | - ... query_string = '&'.join(search_param_list) |
45 | - ... request = LaunchpadTestRequest( |
46 | - ... SERVER_URL='https://launchpad.dev/+search', |
47 | - ... QUERY_STRING=query_string, form=form, PATH_INFO='/+search') |
48 | - ... search_view = getMultiAdapter((root, request), name="+search") |
49 | - ... search_view.initialize() |
50 | - ... return search_view |
51 | - |
52 | - >>> search_view = getSearchView( |
53 | - ... form={'field.text': 'albatross'}) |
54 | - |
55 | - >>> search_view.text |
56 | - u'albatross' |
57 | - >>> search_view.page_title |
58 | - u'Pages matching "albatross" in Launchpad' |
59 | - >>> search_view.page_heading |
60 | - u'Pages matching "albatross" in Launchpad' |
61 | - |
62 | - |
63 | -No matches |
64 | ----------- |
65 | - |
66 | -There were no matches for 'albatross'. |
67 | - |
68 | - >>> search_view.has_matches |
69 | - False |
70 | - |
71 | -When search text is not submitted there are no matches. Search text is |
72 | -required to perform a search. Note that field.actions.search is not a |
73 | -required param to call the Search Action. The view always calls the |
74 | -search action. |
75 | - |
76 | - >>> search_view = getSearchView(form={}) |
77 | - |
78 | - >>> print search_view.text |
79 | - None |
80 | - >>> search_view.has_matches |
81 | - False |
82 | - |
83 | - |
84 | -Bug and Question Searches |
85 | -------------------------- |
86 | - |
87 | -When a numeric token can be extracted from the submitted search text, |
88 | -the view tries to match a bug and question. Bugs and questions are |
89 | -matched by their id. |
90 | - |
91 | - >>> search_view = getSearchView( |
92 | - ... form={'field.text': '5'}) |
93 | - >>> search_view._getNumericToken(search_view.text) |
94 | - u'5' |
95 | - >>> search_view.has_matches |
96 | - True |
97 | - >>> search_view.bug.title |
98 | - u'Firefox install instructions should be complete' |
99 | - >>> search_view.question.title |
100 | - u'Installation failed' |
101 | - |
102 | -Bugs and questions are matched independent of each other. The number |
103 | -extracted may only match one kind of object. For example, there are |
104 | -more bugs than questions. |
105 | - |
106 | - >>> search_view = getSearchView( |
107 | - ... form={'field.text': '15'}) |
108 | - >>> search_view._getNumericToken(search_view.text) |
109 | - u'15' |
110 | - >>> search_view.has_matches |
111 | - True |
112 | - >>> search_view.bug.title |
113 | - u'Nonsensical bugs are useless' |
114 | - >>> print search_view.question |
115 | - None |
116 | - |
117 | -Private bugs are not matched if the user does not have permission to |
118 | -see them. For example, Sample Person can see a private bug that they |
119 | -created because they are the owner. |
120 | - |
121 | - >>> from lp.services.webapp.interfaces import ILaunchBag |
122 | - >>> from lp.app.enums import InformationType |
123 | - |
124 | - >>> login('test@canonical.com') |
125 | - >>> sample_person = getUtility(ILaunchBag).user |
126 | - >>> private_bug = factory.makeBug( |
127 | - ... owner=sample_person, information_type=InformationType.USERDATA) |
128 | - |
129 | - >>> search_view = getSearchView( |
130 | - ... form={'field.text': private_bug.id}) |
131 | - >>> search_view.bug.private |
132 | - True |
133 | - |
134 | -But anonymous and unprivileged users cannot see the private bug. |
135 | - |
136 | - >>> login(ANONYMOUS) |
137 | - >>> search_view = getSearchView( |
138 | - ... form={'field.text': private_bug.id}) |
139 | - >>> print search_view.bug |
140 | - None |
141 | - |
142 | -The text and punctuation in the search text is ignored, and only the |
143 | -first group of numbers is matched. For example a user searches for three |
144 | -questions by number ('Question #15, #7, and 5.'). Only the first number |
145 | -is used, and it matches a bug, not a question. The second and third |
146 | -numbers do match questions, but they are not used. |
147 | - |
148 | - >>> search_view = getSearchView( |
149 | - ... form={'field.text': 'Question #15, #7, and 5.'}) |
150 | - >>> search_view._getNumericToken(search_view.text) |
151 | - u'15' |
152 | - >>> search_view.has_matches |
153 | - True |
154 | - >>> search_view.bug.title |
155 | - u'Nonsensical bugs are useless' |
156 | - >>> print search_view.question |
157 | - None |
158 | - |
159 | -It is not an error to search for a non-existent bug or question. |
160 | - |
161 | - >>> search_view = getSearchView( |
162 | - ... form={'field.text': '55555'}) |
163 | - >>> search_view._getNumericToken(search_view.text) |
164 | - u'55555' |
165 | - >>> search_view.has_matches |
166 | - False |
167 | - >>> print search_view.bug |
168 | - None |
169 | - >>> print search_view.question |
170 | - None |
171 | - |
172 | -There is no error if a number cannot be extracted from the search text. |
173 | - |
174 | - >>> search_view = getSearchView( |
175 | - ... form={'field.text': 'fifteen'}) |
176 | - >>> print search_view._getNumericToken( |
177 | - ... search_view.text) |
178 | - None |
179 | - >>> search_view.has_matches |
180 | - False |
181 | - >>> print search_view.bug |
182 | - None |
183 | - >>> print search_view.question |
184 | - None |
185 | - |
186 | -Bugs and questions are only returned for the first page of search, |
187 | -when the start param is 0. |
188 | - |
189 | - >>> search_view = getSearchView( |
190 | - ... form={'field.text': '5', |
191 | - ... 'start': '20'}) |
192 | - >>> search_view.has_matches |
193 | - False |
194 | - >>> print search_view.bug |
195 | - None |
196 | - >>> print search_view.question |
197 | - None |
198 | - |
199 | - |
200 | - |
201 | -Projects and Persons and Teams searches |
202 | ---------------------------------------- |
203 | - |
204 | -When a Launchpad name can be made from the search text, the view tries |
205 | -to match the name to a pillar or person. a pillar is a distribution, |
206 | -product, or project group. A person is a person or a team. |
207 | - |
208 | - >>> search_view = getSearchView( |
209 | - ... form={'field.text': 'launchpad'}) |
210 | - >>> search_view._getNameToken(search_view.text) |
211 | - u'launchpad' |
212 | - >>> search_view.has_matches |
213 | - True |
214 | - >>> search_view.pillar.displayname |
215 | - u'Launchpad' |
216 | - >>> search_view.person_or_team.displayname |
217 | - u'Launchpad Developers' |
218 | - |
219 | -A launchpad name is constructed from the search text. The letters are |
220 | -converted to lowercase. groups of spaces and punctuation are replaced |
221 | -with a hyphen. |
222 | - |
223 | - >>> search_view = getSearchView( |
224 | - ... form={'field.text': 'Gnome Terminal'}) |
225 | - >>> search_view._getNameToken(search_view.text) |
226 | - u'gnome-terminal' |
227 | - >>> search_view.has_matches |
228 | - True |
229 | - >>> search_view.pillar.displayname |
230 | - u'GNOME Terminal' |
231 | - >>> print search_view.person_or_team |
232 | - None |
233 | - |
234 | -Since our pillars can have aliases, it's also possible to look up a pillar |
235 | -by any of its aliases. |
236 | - |
237 | - >>> from lp.registry.interfaces.product import IProductSet |
238 | - >>> firefox = getUtility(IProductSet)['firefox'] |
239 | - >>> login('foo.bar@canonical.com') |
240 | - >>> firefox.setAliases(['iceweasel']) |
241 | - >>> login(ANONYMOUS) |
242 | - >>> search_view = getSearchView( |
243 | - ... form={'field.text': 'iceweasel'}) |
244 | - >>> search_view._getNameToken(search_view.text) |
245 | - u'iceweasel' |
246 | - >>> search_view.has_matches |
247 | - True |
248 | - >>> search_view.pillar.displayname |
249 | - u'Mozilla Firefox' |
250 | - |
251 | -This is a harder example that illustrates that text that is clearly not |
252 | -the name of a pillar will none-the-less be tried. See the `Page searches` |
253 | -section for how this kind of search can return matches. |
254 | - |
255 | - >>> search_view = getSearchView( |
256 | - ... form={'field.text': "YAHOO! webservice's Python API."}) |
257 | - >>> search_view._getNameToken(search_view.text) |
258 | - u'yahoo-webservices-python-api.' |
259 | - >>> search_view.has_matches |
260 | - False |
261 | - >>> print search_view.pillar |
262 | - None |
263 | - >>> print search_view.person_or_team |
264 | - None |
265 | - |
266 | -Leading and trailing punctuation and whitespace are stripped. |
267 | - |
268 | - >>> search_view = getSearchView( |
269 | - ... form={'field.text': "~name12"}) |
270 | - >>> search_view._getNameToken(search_view.text) |
271 | - u'name12' |
272 | - >>> search_view.has_matches |
273 | - True |
274 | - >>> print search_view.pillar |
275 | - None |
276 | - >>> search_view.person_or_team.displayname |
277 | - u'Sample Person' |
278 | - |
279 | -Pillars, persons and teams are only returned for the first page of |
280 | -search, when the start param is 0. |
281 | - |
282 | - >>> search_view = getSearchView( |
283 | - ... form={'field.text': 'launchpad', |
284 | - ... 'start': '20'}) |
285 | - >>> search_view.has_matches |
286 | - True |
287 | - >>> print search_view.bug |
288 | - None |
289 | - >>> print search_view.question |
290 | - None |
291 | - >>> print search_view.pillar |
292 | - None |
293 | - |
294 | -Deactivated pillars and non-valid persons and teams cannot be exact |
295 | -matches. For example, the python-gnome2-dev product will not match a |
296 | -pillar, nor will nsv match Nicolas Velin's unclaimed account. |
297 | - |
298 | - >>> from lp.registry.interfaces.person import IPersonSet |
299 | - |
300 | - >>> python_gnome2 = getUtility(IProductSet).getByName('python-gnome2-dev') |
301 | - >>> python_gnome2.active |
302 | - False |
303 | - |
304 | - >>> search_view = getSearchView( |
305 | - ... form={'field.text': 'python-gnome2-dev', |
306 | - ... 'start': '0'}) |
307 | - >>> search_view._getNameToken(search_view.text) |
308 | - u'python-gnome2-dev' |
309 | - >>> print search_view.pillar |
310 | - None |
311 | - |
312 | - >>> nsv = getUtility(IPersonSet).getByName('nsv') |
313 | - >>> nsv.displayname |
314 | - u'Nicolas Velin' |
315 | - >>> nsv.is_valid_person_or_team |
316 | - False |
317 | - |
318 | - >>> search_view = getSearchView( |
319 | - ... form={'field.text': 'nsv', |
320 | - ... 'start': '0'}) |
321 | - >>> search_view._getNameToken(search_view.text) |
322 | - u'nsv' |
323 | - >>> print search_view.person_or_team |
324 | - None |
325 | - |
326 | -Private pillars are not matched if the user does not have permission to see |
327 | -them. For example, Sample Person can see a private project that they created |
328 | -because they are the owner. |
329 | - |
330 | - >>> from lp.registry.interfaces.product import License |
331 | - |
332 | - >>> login('test@canonical.com') |
333 | - >>> private_product = factory.makeProduct( |
334 | - ... owner=sample_person, information_type=InformationType.PROPRIETARY, |
335 | - ... licenses=[License.OTHER_PROPRIETARY]) |
336 | - >>> private_product_name = private_product.name |
337 | - |
338 | - >>> search_view = getSearchView(form={'field.text': private_product_name}) |
339 | - >>> search_view.pillar.private |
340 | - True |
341 | - |
342 | -But anonymous and unprivileged users cannot see the private project. |
343 | - |
344 | - >>> login(ANONYMOUS) |
345 | - >>> search_view = getSearchView(form={'field.text': private_product_name}) |
346 | - >>> print search_view.pillar |
347 | - None |
348 | - |
349 | - |
350 | -Shipit CD searches |
351 | ------------------- |
352 | - |
353 | -The has_shipit property will be True when the search looks like the user |
354 | -is searching for Shipit CDs. There is no correct object in Launchpad to |
355 | -display. The page template decides how to handle when has_shipit is |
356 | -True. |
357 | - |
358 | -The match is based on an intersection to the words in the search text |
359 | -and the shipit_keywords. The comparison is case-insensitive, has_shipit |
360 | -is True when 2 or more words match. |
361 | - |
362 | - >>> sorted(search_view.shipit_keywords) |
363 | - ['cd', 'cds', 'disc', 'dvd', 'dvds', 'edubuntu', 'free', 'get', 'kubuntu', |
364 | - 'mail', 'send', 'ship', 'shipit', 'ubuntu'] |
365 | - >>> search_view = getSearchView( |
366 | - ... form={'field.text': 'ubuntu CDs', |
367 | - ... 'start': '0'}) |
368 | - >>> search_view.has_shipit |
369 | - True |
370 | - |
371 | - >>> search_view = getSearchView( |
372 | - ... form={'field.text': 'shipit', |
373 | - ... 'start': '0'}) |
374 | - >>> search_view.has_shipit |
375 | - False |
376 | - |
377 | - >>> search_view = getSearchView( |
378 | - ... form={'field.text': 'get Kubuntu cds', |
379 | - ... 'start': '0'}) |
380 | - >>> search_view.has_shipit |
381 | - True |
382 | - |
383 | -There are shipit_anti_keywords too, words that indicate the search is |
384 | -not for free CDs from Shipit. Search that have any of these word will |
385 | -set has_shipit to False. |
386 | - |
387 | - >>> sorted(search_view.shipit_anti_keywords) |
388 | - ['burn', 'burning', 'enable', 'error', 'errors', 'image', 'iso', |
389 | - 'read', 'rip', 'write'] |
390 | - |
391 | - >>> search_view = getSearchView( |
392 | - ... form={'field.text': 'ubuntu CD write', |
393 | - ... 'start': '0'}) |
394 | - >>> search_view.has_shipit |
395 | - False |
396 | - |
397 | - >>> search_view = getSearchView( |
398 | - ... form={'field.text': 'shipit error', |
399 | - ... 'start': '0'}) |
400 | - >>> search_view.has_shipit |
401 | - False |
402 | - |
403 | - |
404 | -The shipit FAQ URL is provides by the view for the template to use. |
405 | - |
406 | - >>> search_view.shipit_faq_url |
407 | - 'http://www.ubuntu.com/getubuntu/shipit-faq' |
408 | - |
409 | - |
410 | -Page searches |
411 | -------------- |
412 | - |
413 | -The view uses the BingSearchService to locate pages that match the |
414 | -search terms. |
415 | - |
416 | - >>> search_view = getSearchView( |
417 | - ... form={'field.text': " bug"}) |
418 | - >>> search_view.text |
419 | - u'bug' |
420 | - >>> search_view.has_matches |
421 | - True |
422 | - >>> search_view.pages |
423 | - <...SiteSearchBatchNavigator ...> |
424 | - |
425 | -The BingSearchService may not be available due to connectivity problems. |
426 | -The view's has_page_service attribute reports when the search was performed |
427 | -with Bing page matches. |
428 | - |
429 | - >>> search_view.has_page_service |
430 | - True |
431 | - |
432 | -The batch navigation heading is created by the view. The heading |
433 | -property returns a 2-tuple of singular and plural heading. There |
434 | -is a heading when there are only Bing page matches... |
435 | - |
436 | - >>> search_view.has_exact_matches |
437 | - False |
438 | - >>> search_view.batch_heading |
439 | - (u'page matching "bug"', u'pages matching "bug"') |
440 | - |
441 | -...and a heading for when there are exact matches and Bing page |
442 | -matches. |
443 | - |
444 | - >>> search_view = getSearchView( |
445 | - ... form={'field.text': " launchpad"}) |
446 | - >>> search_view.has_exact_matches |
447 | - True |
448 | - >>> search_view.batch_heading |
449 | - (u'other page matching "launchpad"', u'other pages matching "launchpad"') |
450 | - |
451 | -The SiteSearchBatchNavigator behaves like most BatchNavigators, except that |
452 | -its batch size is always 20. The size restriction conforms to Google's |
453 | -maximum number of results that can be returned per request. |
454 | - |
455 | - >>> search_view.start |
456 | - 0 |
457 | - >>> search_view.pages.currentBatch().size |
458 | - 20 |
459 | - >>> pages = list(search_view.pages.currentBatch()) |
460 | - >>> len(pages) |
461 | - 20 |
462 | - >>> for page in pages[0:5]: |
463 | - ... page.title |
464 | - u'Launchpad Bugs' |
465 | - u'Bugs in Ubuntu Linux' |
466 | - u'Bugs related to Sample Person' |
467 | - u'Bug #1 in Mozilla Firefox: Firefox does not support SVG' |
468 | - u'Question #232632 : Questions : OpenStack Heat' |
469 | - |
470 | -The batch navigator provides access to the other batches. There are two |
471 | -batches of pages that match the search text 'bugs'. The navigator |
472 | -provides a link to the next batch, which also happens to be the last |
473 | -batch. |
474 | - |
475 | - >>> search_view.pages.nextBatchURL() |
476 | - '...start=20' |
477 | - >>> search_view.pages.lastBatchURL() |
478 | - '...start=20' |
479 | - |
480 | -The second batch has only five matches in it, even though the batch size |
481 | -is 20. That is because there were only 25 matching pages. |
482 | - |
483 | - >>> search_view = getSearchView( |
484 | - ... form={'field.text': "bug", |
485 | - ... 'start': '20'}) |
486 | - >>> search_view.start |
487 | - 20 |
488 | - >>> search_view.text |
489 | - u'bug' |
490 | - >>> search_view.has_matches |
491 | - True |
492 | - |
493 | - >>> search_view.pages.currentBatch().size |
494 | - 20 |
495 | - >>> pages = list(search_view.pages.currentBatch()) |
496 | - >>> len(pages) |
497 | - 5 |
498 | - >>> for page in pages: |
499 | - ... page.title |
500 | - u'Bugs - Launchpad Help' |
501 | - u'Of Bugs and Statuses - Launchpad Blog' |
502 | - u'Mahara 1.8.0' |
503 | - u'Mighty Box in Launchpad' |
504 | - u'Bug tracking - Launchpad Bugs' |
505 | - |
506 | - >>> search_view.pages.nextBatchURL() |
507 | - '' |
508 | - >>> search_view.pages.lastBatchURL() |
509 | - '' |
510 | - |
511 | -The PageMatch object has a title, url, and summary. The title and url |
512 | -are used for making links to the pages. The summary contains markup |
513 | -showing the matching terms in context of the page text. |
514 | - |
515 | - >>> print range(20) |
516 | - [0, 1, ..., 18, 19] |
517 | - >>> page = pages[0] |
518 | - >>> page |
519 | - <...PageMatch ...> |
520 | - >>> page.title |
521 | - u'Bugs - Launchpad Help' |
522 | - >>> page.url |
523 | - 'https://help.launchpad.net/Bugs' |
524 | - >>> page.summary # doctest: +ELLIPSIS |
525 | - u'Launchpad Help > Bugs . Use Launchpad's bug tracker for your |
526 | - project...' |
527 | - |
528 | -See `google-searchservice.txt` for more information about the |
529 | -BingSearchService and PageMatch objects. |
530 | - |
531 | - |
532 | -No page matches |
533 | ---------------- |
534 | - |
535 | -When an empty PageMatches object is returned by the BingSearchService to |
536 | -the view, there are no matches to show. |
537 | - |
538 | - >>> search_view = getSearchView(form={'field.text': 'no-meaningful'}) |
539 | - >>> search_view.has_matches |
540 | - False |
541 | - |
542 | - |
543 | -Unintelligible searches |
544 | ------------------------ |
545 | - |
546 | -When a user searches for a malformed string, we don't OOPS, but show an |
547 | -error. Also disable warnings, since we are tossing around malformed Unicode. |
548 | - |
549 | - >>> import warnings |
550 | - >>> with warnings.catch_warnings(): |
551 | - ... warnings.simplefilter('ignore') |
552 | - ... search_view = getSearchView( |
553 | - ... form={'field.text': '\xfe\xfckr\xfc'}) |
554 | - >>> html = search_view() |
555 | - >>> 'Can not convert your search term' in html |
556 | - True |
557 | - |
558 | - |
559 | -Bad Bing response handling |
560 | ----------------------------- |
561 | - |
562 | -Connectivity problems can cause missing or incomplete responses from |
563 | -Bing. The LaunchpadSearchView will display the other searches and |
564 | -show a message explaining that the user can search again to find |
565 | -matching pages. |
566 | - |
567 | - >>> search_view = getSearchView(form={'field.text': 'gnomebaker'}) |
568 | - >>> search_view.has_matches |
569 | - True |
570 | - >>> search_view.pillar.displayname |
571 | - u'gnomebaker' |
572 | - >>> search_view.has_page_service |
573 | - False |
574 | - |
575 | -The view provides the requested URL so that the template can make a |
576 | -link to try the search again |
577 | - |
578 | - >>> print search_view.url |
579 | - https://launchpad.dev/+search?field.text=gnomebaker |
580 | - |
581 | - |
582 | -SearchFormView and SearchFormPrimaryView |
583 | ----------------------------------------- |
584 | - |
585 | -Two companion views are used to help render the global search form. |
586 | -They define the required attributes to render the form in the |
587 | -correct state. |
588 | - |
589 | -The LaunchpadSearchFormView provides the minimum information to display |
590 | -the form, but cannot handled the submitted data. It appends a suffix |
591 | -('-secondary') to the id= and name= of the form and inputs, to prevent |
592 | -them from conflicting with the other form. The search text is not the |
593 | -default value of the text field; 'bug' was submitted above, but is not |
594 | -present in the rendered form. |
595 | - |
596 | - >>> search_form_view = getMultiAdapter( |
597 | - ... (search_view, request), name='+search-form') |
598 | - >>> search_form_view.initialize() |
599 | - >>> search_form_view.id_suffix |
600 | - '-secondary' |
601 | - >>> print search_form_view.render() |
602 | - <form action="http://launchpad.dev/+search" method="get" |
603 | - accept-charset="UTF-8" id="sitesearch-secondary" |
604 | - name="sitesearch-secondary"> |
605 | - <div> |
606 | - <input class="textType" type="text" size="36" |
607 | - id="field.text-secondary" name="field.text" /> |
608 | - <input class="button" type="submit" value="Search" |
609 | - id="field.text-secondary" name="field.actions.search-secondary" /> |
610 | - </div> |
611 | - </form> |
612 | - |
613 | -LaunchpadPrimarySearchFormView can handle submitted form by deferring to |
614 | -its context (the LaunchpadSearchView) for the needed information. The |
615 | -view does not append a suffix to the form and input ids. The search |
616 | -field's value is 'bug', as was submitted above. |
617 | - |
618 | - >>> search_form_view = getMultiAdapter( |
619 | - ... (search_view, request), name='+primary-search-form') |
620 | - >>> search_form_view.initialize() |
621 | - >>> search_form_view.id_suffix |
622 | - '' |
623 | - >>> print search_form_view.render() |
624 | - <form action="http://launchpad.dev/+search" method="get" |
625 | - accept-charset="UTF-8" id="sitesearch" |
626 | - name="sitesearch"> |
627 | - <div> |
628 | - <input class="textType" type="text" size="36" |
629 | - id="field.text" value="gnomebaker" name="field.text" /> |
630 | - <input class="button" type="submit" value="Search" |
631 | - id="field.text" name="field.actions.search" /> |
632 | - </div> |
633 | - </form> |
634 | - |
635 | -WindowedList and SiteSearchBatchNavigator |
636 | -------------------------------------- |
637 | - |
638 | -The LaunchpadSearchView uses two helper classes to work with |
639 | -PageMatches. |
640 | - |
641 | -The PageMatches object returned by the BingSearchService contains 20 |
642 | -or fewer PageMatches of what could be thousands of matches. Bing |
643 | -requires client's to make repeats request to step though the batches of |
644 | -matches. The Windowed list is a list that contains only a subset of its |
645 | -reported size. It is used to make batches in the SiteSearchBatchNavigator. |
646 | - |
647 | -For example, the last batch of the 'bug' search contained 5 of the 25 |
648 | -matching pages. The WindowList claims to be 25 items in length, but |
649 | -the first 20 items are None. Only the last 5 items are PageMatches. |
650 | - |
651 | - >>> from lp.app.browser.root import WindowedList |
652 | - >>> from lp.services.sitesearch import BingSearchService |
653 | - |
654 | - >>> bing_search = BingSearchService() |
655 | - >>> page_matches = bing_search.search(terms='bug', start=20) |
656 | - >>> results = WindowedList( |
657 | - ... page_matches, page_matches.start, page_matches.total) |
658 | - >>> len(results) |
659 | - 25 |
660 | - >>> print results[0] |
661 | - None |
662 | - >>> results[24].title |
663 | - u'Bug tracking - Launchpad Bugs' |
664 | - >>> results[18, 22] |
665 | - [None, None, <...PageMatch ...>, <...PageMatch ...>] |
666 | - |
667 | -The SiteSearchBatchNavigator restricts the batch size to 20. the 'batch' |
668 | -parameter that comes from the URL is ignored. For example, setting |
669 | -the 'batch' parameter to 100 has no affect upon the Bing search |
670 | -or on the navigator object. |
671 | - |
672 | - >>> from lp.app.browser.root import SiteSearchBatchNavigator |
673 | - |
674 | - >>> SiteSearchBatchNavigator.batch_variable_name |
675 | - 'batch' |
676 | - |
677 | - >>> search_view = getSearchView( |
678 | - ... form={'field.text': "bug", |
679 | - ... 'start': '0', |
680 | - ... 'batch': '100',}) |
681 | - |
682 | - >>> navigator = search_view.pages |
683 | - >>> navigator.currentBatch().size |
684 | - 20 |
685 | - >>> len(navigator.currentBatch()) |
686 | - 20 |
687 | - >>> navigator.nextBatchURL() |
688 | - '...start=20' |
689 | - |
690 | -Even if the PageMatch object to have an impossibly large size, the |
691 | -navigator conforms to Google's maximum size of 20. |
692 | - |
693 | - >>> matches = list(range(0, 100)) |
694 | - >>> page_matches._matches = matches |
695 | - >>> page_matches.start = 0 |
696 | - >>> page_matches.total = 100 |
697 | - >>> navigator = SiteSearchBatchNavigator( |
698 | - ... page_matches, search_view.request, page_matches.start, size=100) |
699 | - >>> navigator.currentBatch().size |
700 | - 20 |
701 | - >>> len(navigator.currentBatch()) |
702 | - 20 |
703 | - >>> navigator.nextBatchURL() |
704 | - '...start=20' |
705 | - |
706 | -The PageMatches object can be smaller than 20, for instance, pages |
707 | -without titles are skipped when parsing the Bing Search JSON. The size |
708 | -of the batch is still 20, but when the items in the batch are iterated, |
709 | -the true size can be seen. For example there could be only 3 matches in |
710 | -the PageMatches object, so only 3 are yielded. The start of the next |
711 | -batch is 20, which is the start of the next batch from Bing. |
712 | - |
713 | - >>> matches = list(range(0, 3)) |
714 | - >>> page_matches._matches = matches |
715 | - >>> navigator = SiteSearchBatchNavigator( |
716 | - ... page_matches, search_view.request, page_matches.start, size=100) |
717 | - >>> batch = navigator.currentBatch() |
718 | - >>> batch.size |
719 | - 20 |
720 | - >>> len(batch) |
721 | - 20 |
722 | - >>> batch.endNumber() |
723 | - 3 |
724 | - >>> for item in batch: |
725 | - ... print item |
726 | - 0 |
727 | - 1 |
728 | - 2 |
729 | - >>> navigator.nextBatchURL() |
730 | - '...start=20' |
731 | |
732 | === renamed file 'lib/lp/app/browser/doc/launchpad-search-pages-google.txt' => 'lib/lp/app/browser/doc/launchpad-search-pages.txt' |
733 | --- lib/lp/app/browser/doc/launchpad-search-pages-google.txt 2018-03-28 19:31:02 +0000 |
734 | +++ lib/lp/app/browser/doc/launchpad-search-pages.txt 2018-04-25 15:46:57 +0000 |
735 | @@ -456,12 +456,12 @@ |
736 | >>> len(pages) |
737 | 20 |
738 | >>> for page in pages[0:5]: |
739 | - ... page.title |
740 | - 'Launchpad Bugs' |
741 | - 'Bugs in Ubuntu Linux' |
742 | - 'Bugs related to Sample Person' |
743 | - u'<b>Bug</b> #1 in Mozilla Firefox: ...Firefox does not support SVG...' |
744 | - 'Bugs in Source Package "thunderbird" in Ubuntu Linux' |
745 | + ... unicode(page.title) |
746 | + u'Launchpad Bugs' |
747 | + u'Bugs in Ubuntu Linux' |
748 | + u'Bugs related to Sample Person' |
749 | + u'...Bug... #1 in Mozilla Firefox: ...Firefox does not support SVG...' |
750 | + u'Bugs in Source Package ...thunderbird... in Ubuntu Linux' |
751 | |
752 | The batch navigator provides access to the other batches. There are two |
753 | batches of pages that match the search text 'bugs'. The navigator |
754 | @@ -492,12 +492,12 @@ |
755 | >>> len(pages) |
756 | 5 |
757 | >>> for page in pages: |
758 | - ... page.title |
759 | - u'<b>Bug</b> #2 in Ubuntu Hoary: \u201cBlackhole Trash folder\u201d' |
760 | - u'<b>Bug</b> #2 in mozilla-firefox (Debian): ...Blackhole Trash folder...' |
761 | - u'<b>Bug</b> #3 in mozilla-firefox (Debian): \u201cBug Title Test\u201d' |
762 | - '<b>Bug</b> trackers registered in Launchpad' |
763 | - u'<b>Bug</b> tracker \u201cDebian Bug tracker\u201d' |
764 | + ... unicode(page.title) |
765 | + u'...Bug... #2 in Ubuntu Hoary: \u201cBlackhole Trash folder\u201d' |
766 | + u'...Bug... #2 in mozilla-firefox (Debian): ...Blackhole Trash folder...' |
767 | + u'...Bug... #3 in mozilla-firefox (Debian): \u201cBug Title Test\u201d' |
768 | + u'...Bug... trackers registered in Launchpad' |
769 | + u'...Bug... tracker \u201cDebian Bug tracker\u201d' |
770 | |
771 | >>> search_view.pages.nextBatchURL() |
772 | '' |
773 | @@ -512,11 +512,11 @@ |
774 | >>> page |
775 | <...PageMatch ...> |
776 | >>> page.title |
777 | - u'<b>Bug</b> #2 in Ubuntu Hoary: \u201cBlackhole Trash folder\u201d' |
778 | + u'...Bug... #2 in Ubuntu Hoary: \u201cBlackhole Trash folder\u201d' |
779 | >>> page.url |
780 | 'http://bugs.launchpad.dev/ubuntu/hoary/+bug/2' |
781 | >>> page.summary |
782 | - u'<b>Bug</b> tracking <b>...</b> Search <b>bugs</b> reports ...' |
783 | + u'...Launchpad\u2019s ...bug... tracker allows collaboration...' |
784 | |
785 | See `google-searchservice.txt` for more information about the |
786 | GoogleSearchService and PageMatch objects. |
787 | @@ -642,10 +642,12 @@ |
788 | the first 20 items are None. Only the last 5 items are PageMatches. |
789 | |
790 | >>> from lp.app.browser.root import WindowedList |
791 | - >>> from lp.services.sitesearch import GoogleSearchService |
792 | + >>> from lp.services.sitesearch.interfaces import active_search_service |
793 | + >>> from zope.security.proxy import removeSecurityProxy |
794 | |
795 | - >>> google_search = GoogleSearchService() |
796 | - >>> page_matches = google_search.search(terms='bug', start=20) |
797 | + >>> site_search = active_search_service() |
798 | + >>> naked_site_search = removeSecurityProxy(site_search) |
799 | + >>> page_matches = naked_site_search.search(terms='bug', start=20) |
800 | >>> results = WindowedList( |
801 | ... page_matches, page_matches.start, page_matches.total) |
802 | >>> len(results) |
803 | @@ -653,7 +655,7 @@ |
804 | >>> print results[0] |
805 | None |
806 | >>> results[24].title |
807 | - u'<b>Bug</b> tracker \u201cDebian Bug tracker\u201d' |
808 | + u'...Bug... tracker \u201cDebian Bug tracker\u201d' |
809 | >>> results[18, 22] |
810 | [None, None, <...PageMatch ...>, <...PageMatch ...>] |
811 | |
812 | |
813 | === modified file 'lib/lp/app/browser/tests/test_views.py' |
814 | --- lib/lp/app/browser/tests/test_views.py 2018-03-28 19:31:02 +0000 |
815 | +++ lib/lp/app/browser/tests/test_views.py 2018-04-25 15:46:57 +0000 |
816 | @@ -1,9 +1,11 @@ |
817 | # Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
818 | # GNU Affero General Public License version 3 (see the file LICENSE). |
819 | |
820 | -""" |
821 | -Run the view tests. |
822 | -""" |
823 | +"""Run the view tests.""" |
824 | + |
825 | +from __future__ import absolute_import, print_function, unicode_literals |
826 | + |
827 | +__metaclass__ = type |
828 | |
829 | import logging |
830 | import os |
831 | @@ -13,6 +15,7 @@ |
832 | from lp.testing.layers import ( |
833 | BingLaunchpadFunctionalLayer, |
834 | GoogleLaunchpadFunctionalLayer, |
835 | + PageTestLayer, |
836 | ) |
837 | from lp.testing.systemdocs import ( |
838 | LayeredDocFileSuite, |
839 | @@ -50,16 +53,24 @@ |
840 | # that require something special like the librarian or mailman must run |
841 | # on a layer that sets those services up. |
842 | special = { |
843 | - 'launchpad-search-pages-bing.txt': LayeredDocFileSuite( |
844 | - '../doc/launchpad-search-pages-bing.txt', |
845 | + 'launchpad-search-pages.txt(Bing)': LayeredDocFileSuite( |
846 | + '../doc/launchpad-search-pages.txt', |
847 | + id_extensions=['launchpad-search-pages.txt(Bing)'], |
848 | setUp=setUp_bing, tearDown=tearDown_bing, |
849 | layer=BingLaunchpadFunctionalLayer, |
850 | stdout_logging_level=logging.WARNING), |
851 | - 'launchpad-search-pages-google.txt': LayeredDocFileSuite( |
852 | - '../doc/launchpad-search-pages-google.txt', |
853 | + 'launchpad-search-pages.txt(Google)': LayeredDocFileSuite( |
854 | + '../doc/launchpad-search-pages.txt', |
855 | + id_extensions=['launchpad-search-pages.txt(Google)'], |
856 | setUp=setUp_google, tearDown=tearDown_google, |
857 | layer=GoogleLaunchpadFunctionalLayer, |
858 | stdout_logging_level=logging.WARNING), |
859 | + # Run these doctests again with the default search engine. |
860 | + 'launchpad-search-pages.txt': LayeredDocFileSuite( |
861 | + '../doc/launchpad-search-pages.txt', |
862 | + setUp=setUp, tearDown=tearDown, |
863 | + layer=PageTestLayer, |
864 | + stdout_logging_level=logging.WARNING), |
865 | } |
866 | |
867 | |
868 | |
869 | === modified file 'lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-1.json' |
870 | --- lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-1.json 2018-03-28 19:11:16 +0000 |
871 | +++ lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-1.json 2018-04-25 15:46:57 +0000 |
872 | @@ -58,7 +58,7 @@ |
873 | }, |
874 | { |
875 | "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.4", |
876 | - "name": "Question #232632 : Questions : OpenStack Heat", |
877 | + "name": "Bugs in Source Package \"thunderbird\" in Ubuntu Linux", |
878 | "url": "https://answers.launchpad.net/heat/+question/232632", |
879 | "urlPingSuffix": "DevEx,5140.1", |
880 | "datePublished": "2013-07-18T00:00:00.0000000", |
881 | |
882 | === modified file 'lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-2.json' |
883 | --- lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-2.json 2018-03-27 20:45:52 +0000 |
884 | +++ lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-2.json 2018-04-25 15:46:57 +0000 |
885 | @@ -14,12 +14,12 @@ |
886 | "value": [ |
887 | { |
888 | "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.0", |
889 | - "name": "Bugs - Launchpad Help", |
890 | - "url": "https://help.launchpad.net/Bugs", |
891 | + "name": "Bug #2 in Ubuntu Hoary: “Blackhole Trash folder”", |
892 | + "url": "http://bugs.launchpad.dev/ubuntu/hoary/+bug/2", |
893 | "urlPingSuffix": "DevEx,5103.1", |
894 | "isFamilyFriendly": true, |
895 | "displayUrl": "https://help.launchpad.net/Bugs", |
896 | - "snippet": "Launchpad Help > Bugs . Use Launchpad's bug tracker for your project. Bug heat: a computed estimate of a bug's significance. Learn about automatic bug expiry", |
897 | + "snippet": "Bug tracking ... Search bugs reports ... Launchpad’s bug tracker allows collaboration ...", |
898 | "deepLinks": [ |
899 | { |
900 | "name": "Bugs/EmailInterface", |
901 | @@ -57,8 +57,8 @@ |
902 | }, |
903 | { |
904 | "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.1", |
905 | - "name": "Of Bugs and Statuses - Launchpad Blog", |
906 | - "url": "https://blog.launchpad.net/general/of-bugs-and-statuses", |
907 | + "name": "Bug #2 in mozilla-firefox (Debian): “Blackhole Trash folder”", |
908 | + "url": "http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+bug/2", |
909 | "urlPingSuffix": "DevEx,5149.1", |
910 | "isFamilyFriendly": true, |
911 | "displayUrl": "https://blog.launchpad.net/general/of-bugs-and-statuses", |
912 | @@ -68,8 +68,8 @@ |
913 | }, |
914 | { |
915 | "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.2", |
916 | - "name": "Mahara 1.8.0", |
917 | - "url": "https://launchpad.net/mahara/+milestone/1.8.0", |
918 | + "name": "Bug #3 in mozilla-firefox (Debian): “Bug Title Test”", |
919 | + "url": "http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+bug/3", |
920 | "urlPingSuffix": "DevEx,5178.1", |
921 | "isFamilyFriendly": true, |
922 | "displayUrl": "https://launchpad.net/mahara/+milestone/1.8.0", |
923 | @@ -79,8 +79,8 @@ |
924 | }, |
925 | { |
926 | "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.3", |
927 | - "name": "Mighty Box in Launchpad", |
928 | - "url": "https://launchpad.net/mb", |
929 | + "name": "Bug trackers registered in Launchpad", |
930 | + "url": "http://bugs.launchpad.dev/bugs/bugtrackers", |
931 | "urlPingSuffix": "DevEx,5193.1", |
932 | "isFamilyFriendly": true, |
933 | "displayUrl": "https://launchpad.net/mb", |
934 | @@ -90,8 +90,8 @@ |
935 | }, |
936 | { |
937 | "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.4", |
938 | - "name": "Bug tracking - Launchpad Bugs", |
939 | - "url": "https://launchpad.net/bugs", |
940 | + "name": "Bug tracker “Debian Bug tracker”", |
941 | + "url": "http://bugs.launchpad.dev/bugs/bugtrackers/debbugs", |
942 | "urlPingSuffix": "DevEx,5208.1", |
943 | "about": [ |
944 | { |
945 | |
946 | === modified file 'lib/lp/services/sitesearch/tests/test_bing.py' |
947 | --- lib/lp/services/sitesearch/tests/test_bing.py 2018-04-16 18:43:52 +0000 |
948 | +++ lib/lp/services/sitesearch/tests/test_bing.py 2018-04-25 15:46:57 +0000 |
949 | @@ -246,11 +246,11 @@ |
950 | self.assertEqual(25, matches.total) |
951 | self.assertEqual(5, len(matches)) |
952 | self.assertEqual([ |
953 | - 'https://help.launchpad.net/Bugs', |
954 | - 'http://blog.launchpad.dev/general/of-bugs-and-statuses', |
955 | - 'http://launchpad.dev/mahara/+milestone/1.8.0', |
956 | - 'http://launchpad.dev/mb', |
957 | - 'http://launchpad.dev/bugs'], |
958 | + 'http://bugs.launchpad.dev/ubuntu/hoary/+bug/2', |
959 | + 'http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+bug/2', |
960 | + 'http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+bug/3', |
961 | + 'http://bugs.launchpad.dev/bugs/bugtrackers', |
962 | + 'http://bugs.launchpad.dev/bugs/bugtrackers/debbugs'], |
963 | [match.url for match in matches]) |
964 | |
965 | def test_search_no_results(self): |