Merge lp:~wgrant/launchpad/rebuild-projectgroup-filebug into lp:launchpad

Proposed by William Grant
Status: Merged
Merged at revision: 15737
Proposed branch: lp:~wgrant/launchpad/rebuild-projectgroup-filebug
Merge into: lp:launchpad
Diff against target: 917 lines (+100/-469)
11 files modified
lib/lp/bugs/browser/bugtarget.py (+84/-234)
lib/lp/bugs/browser/configure.zcml (+2/-1)
lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt (+0/-14)
lib/lp/bugs/browser/tests/test_bugtarget_filebug.py (+0/-7)
lib/lp/bugs/javascript/filebug_dupefinder.js (+4/-37)
lib/lp/bugs/javascript/tests/test_filebug_dupefinder.js (+0/-80)
lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt (+4/-4)
lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt (+1/-1)
lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt (+4/-74)
lib/lp/bugs/templates/bugtarget-filebug-search.pt (+1/-7)
lib/lp/bugs/templates/bugtarget-macros-filebug.pt (+0/-10)
To merge this branch: bzr merge lp:~wgrant/launchpad/rebuild-projectgroup-filebug
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Review via email: mp+117835@code.launchpad.net

Commit message

Split ProjectGroup:+filebug out to a separate view class, and clean up IMaloneApplication:+filebug remnants.

Description of the change

This branch cleans up some of +filebug's horrors. In particular, it removes the core FileBugViewBase's support for its non-IBugTarget instantiations: IMaloneApplication and IProjectGroup.

The contextless IMaloneApplication:+filebug view was deregistered in 2009, but remnants persist.

ProjectGroup:+filebug has a long and sad history. Once upon a time it was a set of normal forms with a project <select>. Then it was AJAXified like Product:+filebug, retrieving the project-specific bits of the form using a separate request once the project was chosen. But that got too complicated to be worth it, until the JS was changed early last year to just redirect to Product:+filebug once a project was selected. I've split it out into a separate view class, made the classical form redirect to Product:+filebug, and dropped the JS support. This leaves the view in a workable but low-maintenance state, and lets major cleanups happen in the more traditional +filebug views.

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

Looks good, glad to see all this code excised.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bugtarget.py'
2--- lib/lp/bugs/browser/bugtarget.py 2012-08-01 01:05:08 +0000
3+++ lib/lp/bugs/browser/bugtarget.py 2012-08-03 00:21:23 +0000
4@@ -16,7 +16,7 @@
5 "IProductBugConfiguration",
6 "OfficialBugTagsManageView",
7 "ProductConfigureBugTrackerView",
8- "ProjectFileBugGuidedView",
9+ "ProjectGroupFileBugGuidedView",
10 "product_to_productbugconfiguration",
11 ]
12
13@@ -105,7 +105,6 @@
14 UNRESOLVED_BUGTASK_STATUSES,
15 )
16 from lp.bugs.interfaces.bugtracker import IBugTracker
17-from lp.bugs.interfaces.malone import IMaloneApplication
18 from lp.bugs.interfaces.securitycontact import IHasSecurityContact
19 from lp.bugs.model.bugtask import BugTask
20 from lp.bugs.model.structuralsubscription import (
21@@ -149,7 +148,6 @@
22 from lp.services.webapp.authorization import check_permission
23 from lp.services.webapp.batching import BatchNavigator
24 from lp.services.webapp.breadcrumb import Breadcrumb
25-from lp.services.webapp.interfaces import ILaunchBag
26 from lp.services.webapp.menu import structured
27
28 # A simple vocabulary for the subscribe_to_existing_bug form field.
29@@ -253,12 +251,9 @@
30 custom_widget('information_type', LaunchpadRadioWidgetWithDescription)
31
32 extra_data_token = None
33- advanced_form = False
34- frontpage_form = False
35- data_parser = None
36
37 def __init__(self, context, request):
38- LaunchpadFormView.__init__(self, context, request)
39+ super(FileBugViewBase, self).__init__(context, request)
40 self.extra_data = FileBugData()
41
42 def initialize(self):
43@@ -282,15 +277,12 @@
44 type.name for type in PRIVATE_INFORMATION_TYPES]
45 cache.objects['bug_private_by_default'] = (
46 IProduct.providedBy(self.context) and self.context.private_bugs)
47- # Project groups are special. The Next button sends you to
48- # Product:+filebug, so we need none of the usual stuff.
49- if not IProjectGroup.providedBy(self.context):
50- cache.objects['information_type_data'] = [
51- {'value': term.name, 'description': term.description,
52- 'name': term.title,
53- 'description_css_class': 'choice-description'}
54- for term in
55- self.context.pillar.getAllowedBugInformationTypes()]
56+ cache.objects['information_type_data'] = [
57+ {'value': term.name, 'description': term.description,
58+ 'name': term.title,
59+ 'description_css_class': 'choice-description'}
60+ for term in
61+ self.context.pillar.getAllowedBugInformationTypes()]
62 bugtask_status_data = vocabulary_to_choice_edit_items(
63 BugTaskStatus, include_description=True, css_class_prefix='status',
64 excluded_items=[
65@@ -307,8 +299,7 @@
66 excluded_items=[BugTaskImportance.UNKNOWN])
67 cache.objects['bugtask_importance_data'] = bugtask_importance_data
68 cache.objects['enable_bugfiling_duplicate_search'] = (
69- IProjectGroup.providedBy(self.context)
70- or self.context.enable_bugfiling_duplicate_search)
71+ self.context.enable_bugfiling_duplicate_search)
72
73 super(FileBugViewBase, self).initialize()
74
75@@ -366,21 +357,8 @@
76 if (IDistribution.providedBy(context) or
77 IDistributionSourcePackage.providedBy(context)):
78 field_names.append('packagename')
79- elif IMaloneApplication.providedBy(context):
80- field_names.append('bugtarget')
81- elif IProjectGroup.providedBy(context):
82- field_names.append('product')
83- elif not IProduct.providedBy(context):
84- raise AssertionError('Unknown context: %r' % context)
85-
86- # If the context is a project group we want to render the optional
87- # fields since they will initially be hidden and later exposed if the
88- # selected project supports them.
89- include_extra_fields = IProjectGroup.providedBy(context)
90- if not include_extra_fields:
91- include_extra_fields = self.is_bug_supervisor
92-
93- if include_extra_fields:
94+
95+ if self.is_bug_supervisor:
96 field_names.extend(
97 ['assignee', 'importance', 'milestone', 'status'])
98
99@@ -405,14 +383,11 @@
100 return IProduct.providedBy(self.context)
101
102 def contextIsProject(self):
103- return IProjectGroup.providedBy(self.context)
104+ return False
105
106 def targetIsUbuntu(self):
107 ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
108- return (self.context == ubuntu or
109- (IMaloneApplication.providedBy(self.context) and
110- self.request.form.get('field.bugtarget.distribution') ==
111- ubuntu.name))
112+ return self.context == ubuntu
113
114 def getPackageNameFieldCSSClass(self):
115 """Return the CSS class for the packagename field."""
116@@ -455,8 +430,6 @@
117 if packagename:
118 if IDistribution.providedBy(self.context):
119 distribution = self.context
120- elif 'distribution' in data:
121- distribution = data['distribution']
122 else:
123 assert IDistributionSourcePackage.providedBy(self.context)
124 distribution = self.context.distribution
125@@ -479,17 +452,6 @@
126 self.setFieldError("packagename",
127 "Please enter a package name")
128
129- # If we've been called from the frontpage filebug forms we must check
130- # that whatever product or distro is having a bug filed against it
131- # actually uses Malone for its bug tracking.
132- product_or_distro = self.getProductOrDistroFromContext()
133- if (product_or_distro is not None and
134- product_or_distro.bug_tracking_usage != ServiceUsage.LAUNCHPAD):
135- self.setFieldError(
136- 'bugtarget',
137- "%s does not use Launchpad as its bug tracker " %
138- product_or_distro.displayname)
139-
140 def setUpWidgets(self):
141 """Customize the onKeyPress event of the package name chooser."""
142 super(FileBugViewBase, self).setUpWidgets()
143@@ -502,11 +464,6 @@
144 """Set up the form fields. See `LaunchpadFormView`."""
145 super(FileBugViewBase, self).setUpFields()
146
147- # Project groups are special. The Next button sends you to
148- # Product:+filebug, so we need none of the usual stuff.
149- if IProjectGroup.providedBy(self.context):
150- return
151-
152 if self.is_bug_supervisor:
153 info_type_vocab = InformationTypeVocabulary(
154 types=self.context.pillar.getAllowedBugInformationTypes())
155@@ -534,14 +491,8 @@
156
157 def contextUsesMalone(self):
158 """Does the context use Malone as its official bugtracker?"""
159- if IProjectGroup.providedBy(self.context):
160- products_using_malone = [
161- product for product in self.context.products
162- if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD]
163- return len(products_using_malone) > 0
164- else:
165- bug_tracking_usage = self.getMainContext().bug_tracking_usage
166- return bug_tracking_usage == ServiceUsage.LAUNCHPAD
167+ bug_tracking_usage = self.getMainContext().bug_tracking_usage
168+ return bug_tracking_usage == ServiceUsage.LAUNCHPAD
169
170 def shouldSelectPackageName(self):
171 """Should the radio button to select a package be selected?"""
172@@ -559,8 +510,6 @@
173 title = data["title"]
174 comment = data["comment"].rstrip()
175 packagename = data.get("packagename")
176- distribution = data.get(
177- "distribution", getUtility(ILaunchBag).distribution)
178
179 information_type = data.get("information_type")
180 # If the old UI is enabled, security bugs are always embargoed
181@@ -569,14 +518,6 @@
182 information_type = InformationType.PRIVATESECURITY
183
184 context = self.context
185- if distribution is not None:
186- # We're being called from the generic bug filing form, so
187- # manually set the chosen distribution as the context.
188- context = distribution
189- elif IProjectGroup.providedBy(context):
190- context = data['product']
191- elif IMaloneApplication.providedBy(context):
192- context = data['bugtarget']
193
194 # Ensure that no package information is used, if the user
195 # enters a package name but then selects "I don't know".
196@@ -814,22 +755,8 @@
197 return self, ()
198
199 def getProductOrDistroFromContext(self):
200- """Return the product or distribution relative to the context.
201-
202- For instance, if the context is an IDistroSeries, return the
203- distribution related to it. Will return None if the context is
204- not related to a product or a distro.
205- """
206- context = self.context
207- if IProduct.providedBy(context) or IDistribution.providedBy(context):
208- return context
209- elif IProductSeries.providedBy(context):
210- return context.product
211- elif (IDistroSeries.providedBy(context) or
212- IDistributionSourcePackage.providedBy(context)):
213- return context.distribution
214- else:
215- return None
216+ """Return the product or distribution relative to the context."""
217+ return self.context.pillar
218
219 def showOptionalMarker(self, field_name):
220 """See `LaunchpadFormView`."""
221@@ -854,26 +781,11 @@
222 total accuracy, and will return the first 'relevant' bugtask
223 it finds even if there are other candidates. Be warned!
224 """
225- context = self.context
226-
227- if IProjectGroup.providedBy(context):
228- contexts = set(context.products)
229- else:
230- contexts = [context]
231-
232 for bugtask in bug.bugtasks:
233- if bugtask.target in contexts or bugtask.pillar in contexts:
234+ if self.context in (bugtask.target, bugtask.pillar):
235 return bugtask
236 return None
237
238- @property
239- def bugtarget(self):
240- """The bugtarget we're currently assuming.
241-
242- The same as the context.
243- """
244- return self.context
245-
246 default_bug_reported_acknowledgement = "Thank you for your bug report."
247
248 def getAcknowledgementMessage(self, context):
249@@ -947,38 +859,24 @@
250 Returns a list of dicts, with each dict containing values for
251 "preamble" and "content".
252 """
253-
254- def target_name(target):
255- # IProjectGroup can be considered the target of a bug during
256- # the bug filing process, but does not extend IBugTarget
257- # and ultimately cannot actually be the target of a
258- # bug. Hence this function to determine a suitable
259- # name/title to display. Hurrumph.
260- if IBugTarget.providedBy(target):
261- return target.bugtargetdisplayname
262- else:
263- return target.displayname
264-
265 guidelines = []
266- bugtarget = self.context
267- if bugtarget is not None:
268- content = bugtarget.bug_reporting_guidelines
269+ content = self.context.bug_reporting_guidelines
270+ if content is not None and len(content) > 0:
271+ guidelines.append({
272+ "source": self.context.bugtargetdisplayname,
273+ "content": content,
274+ })
275+ # Distribution source packages are shown with both their
276+ # own reporting guidelines and those of their
277+ # distribution.
278+ if IDistributionSourcePackage.providedBy(self.context):
279+ distribution = self.context.distribution
280+ content = distribution.bug_reporting_guidelines
281 if content is not None and len(content) > 0:
282 guidelines.append({
283- "source": target_name(bugtarget),
284- "content": content,
285- })
286- # Distribution source packages are shown with both their
287- # own reporting guidelines and those of their
288- # distribution.
289- if IDistributionSourcePackage.providedBy(bugtarget):
290- distribution = bugtarget.distribution
291- content = distribution.bug_reporting_guidelines
292- if content is not None and len(content) > 0:
293- guidelines.append({
294- "source": target_name(distribution),
295- "content": content,
296- })
297+ "source": distribution.bugtargetdisplayname,
298+ "content": content,
299+ })
300 return guidelines
301
302 def getMainContext(self):
303@@ -995,7 +893,7 @@
304 context, self.user)
305
306
307-class FileBugAdvancedView(FileBugViewBase):
308+class FileBugAdvancedView(LaunchpadView):
309 """Browser view for filing a bug.
310
311 This view exists only to redirect from +filebug-advanced to +filebug.
312@@ -1038,11 +936,6 @@
313 return url
314
315 @property
316- def search_context(self):
317- """Return the context used to search for similar bugs."""
318- return self.context
319-
320- @property
321 def search_text(self):
322 """Return the search string entered by the user."""
323 return self.request.get('title')
324@@ -1053,19 +946,16 @@
325 title = self.search_text
326 if not title:
327 return []
328- search_context = self.search_context
329- if search_context is None:
330- return []
331- elif IProduct.providedBy(search_context):
332- context_params = {'product': search_context}
333- elif IDistribution.providedBy(search_context):
334- context_params = {'distribution': search_context}
335+ elif IProduct.providedBy(self.context):
336+ context_params = {'product': self.context}
337+ elif IDistribution.providedBy(self.context):
338+ context_params = {'distribution': self.context}
339 else:
340- assert IDistributionSourcePackage.providedBy(search_context), (
341- 'Unknown search context: %r' % search_context)
342+ assert IDistributionSourcePackage.providedBy(self.context), (
343+ 'Unknown search context: %r' % self.context)
344 context_params = {
345- 'distribution': search_context.distribution,
346- 'sourcepackagename': search_context.sourcepackagename}
347+ 'distribution': self.context.distribution,
348+ 'sourcepackagename': self.context.sourcepackagename}
349
350 matching_bugtasks = getUtility(IBugTaskSet).findSimilar(
351 self.user, title, **context_params)
352@@ -1118,23 +1008,10 @@
353
354 @property
355 def page_title(self):
356- if IMaloneApplication.providedBy(self.context):
357- return 'Report a bug'
358- else:
359- return 'Report a bug about %s' % self.context.title
360-
361- @safe_action
362- @action("Continue", name="projectgroupsearch",
363- validator="validate_search")
364- def projectgroup_search_action(self, action, data):
365- """Search for similar bug reports."""
366- # Don't give focus to any widget, to ensure that the browser
367- # won't scroll past the "possible duplicates" list.
368- self.initial_focus_widget = None
369- return self._PROJECTGROUP_SEARCH_FOR_DUPES()
370-
371- @safe_action
372- @action("Continue", name="search", validator="validate_search")
373+ return 'Report a bug about %s' % self.context.title
374+
375+ @safe_action
376+ @action("Continue", name="search")
377 def search_action(self, action, data):
378 """Search for similar bug reports."""
379 # Don't give focus to any widget, to ensure that the browser
380@@ -1143,26 +1020,6 @@
381 return self.showFileBugForm()
382
383 @property
384- def search_context(self):
385- """Return the context used to search for similar bugs."""
386- if IDistributionSourcePackage.providedBy(self.context):
387- return self.context
388-
389- search_context = self.getMainContext()
390- if IProjectGroup.providedBy(search_context):
391- assert self.widgets['product'].hasValidInput(), (
392- "This method should be called only when we know which"
393- " product the user selected.")
394- search_context = self.widgets['product'].getInputValue()
395- elif IMaloneApplication.providedBy(search_context):
396- if self.widgets['bugtarget'].hasValidInput():
397- search_context = self.widgets['bugtarget'].getInputValue()
398- else:
399- search_context = None
400-
401- return search_context
402-
403- @property
404 def search_text(self):
405 """Return the search string entered by the user."""
406 try:
407@@ -1170,18 +1027,6 @@
408 except InputErrors:
409 return None
410
411- def validate_search(self, action, data):
412- """Make sure some keywords are provided."""
413- try:
414- data['title'] = self.widgets['title'].getInputValue()
415- except InputErrors as error:
416- self.setFieldError("title", "A summary is required.")
417- return [error]
418-
419- # Return an empty list of errors to satisfy the validation API,
420- # and say "we've handled the validation and found no errors."
421- return []
422-
423 def validate_no_dupe_found(self, action, data):
424 return ()
425
426@@ -1195,13 +1040,24 @@
427 return self._FILEBUG_FORM()
428
429
430-class ProjectFileBugGuidedView(FileBugGuidedView):
431+class ProjectGroupFileBugGuidedView(LaunchpadFormView):
432 """Guided filebug pages for IProjectGroup."""
433
434- # Make inheriting the base class' actions work.
435- actions = FileBugGuidedView.actions
436 schema = IProjectGroupBugAddForm
437
438+ custom_widget('title', TextWidget, displayWidth=40)
439+ custom_widget('tags', BugTagsWidget)
440+
441+ extra_data_to_process = False
442+
443+ @property
444+ def field_names(self):
445+ return ['product', 'title', 'tags']
446+
447+ @property
448+ def page_title(self):
449+ return 'Report a bug about %s' % self.context.title
450+
451 @cachedproperty
452 def products_using_malone(self):
453 return [
454@@ -1215,36 +1071,30 @@
455 else:
456 return None
457
458- @property
459- def inline_filebug_form_url(self):
460- """Return the URL to the inline filebug form.
461-
462- If a token was passed to this view, it will be passed through
463- to the inline bug filing form via the returned URL.
464-
465- The URL returned will be the URL of the first of the current
466- ProjectGroup's products, since that's the product that will be
467- selected by default when the view is rendered.
468- """
469- url = canonical_url(
470- self.default_product, view_name='+filebug-inline-form')
471- if self.extra_data_token is not None:
472- url = urlappend(url, self.extra_data_token)
473- return url
474-
475- @property
476- def duplicate_search_url(self):
477- """Return the URL to the inline duplicate search view.
478-
479- The URL returned will be the URL of the first of the current
480- ProjectGroup's products, since that's the product that will be
481- selected by default when the view is rendered.
482- """
483- url = canonical_url(
484- self.default_product, view_name='+filebug-show-similar')
485- if self.extra_data_token is not None:
486- url = urlappend(url, self.extra_data_token)
487- return url
488+ def contextUsesMalone(self):
489+ return self.default_product is not None
490+
491+ def contextIsProduct(self):
492+ return False
493+
494+ def contextIsProject(self):
495+ return True
496+
497+ def getProductOrDistroFromContext(self):
498+ return None
499+
500+ @safe_action
501+ @action("Continue", name="projectgroupsearch")
502+ def projectgroup_search_action(self, action, data):
503+ """Redirect to the chosen product's form."""
504+ base = canonical_url(data['product'], view_name='+filebug')
505+ query = urllib.urlencode([
506+ ('field.actions.search', 'Continue'),
507+ ('field.title', data['title']),
508+ ('field.tags', ' '.join(data['tags'])),
509+ ])
510+ url = '%s?%s' % (base, query)
511+ self.request.response.redirect(url)
512
513
514 class BugTargetBugListingView(LaunchpadView):
515
516=== modified file 'lib/lp/bugs/browser/configure.zcml'
517--- lib/lp/bugs/browser/configure.zcml 2012-07-31 03:14:11 +0000
518+++ lib/lp/bugs/browser/configure.zcml 2012-08-03 00:21:23 +0000
519@@ -422,7 +422,8 @@
520 <browser:page
521 name="+filebug"
522 for="lp.registry.interfaces.projectgroup.IProjectGroup"
523- class="lp.bugs.browser.bugtarget.ProjectFileBugGuidedView"
524+ class="lp.bugs.browser.bugtarget.ProjectGroupFileBugGuidedView"
525+ template="../templates/bugtarget-filebug-search.pt"
526 permission="launchpad.AnyPerson"/>
527 <browser:page
528 name="+filebug-advanced"
529
530=== modified file 'lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt'
531--- lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt 2012-07-30 23:49:34 +0000
532+++ lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt 2012-08-03 00:21:23 +0000
533@@ -79,20 +79,6 @@
534 >>> print filebug_view.duplicate_search_url
535 http://launchpad.dev/firefox/+filebug-show-similar
536
537-For project groups, the URLs returned will refer to the default product
538-for those project groups.
539-
540- >>> from lp.registry.interfaces.projectgroup import IProjectGroupSet
541- >>> gnome_project = getUtility(IProjectGroupSet).getByName('gnome')
542- >>> gnome_filebug_view = create_initialized_view(
543- ... gnome_project, '+filebug')
544-
545- >>> print gnome_filebug_view.inline_filebug_form_url
546- http://launchpad.dev/evolution/+filebug-inline-form
547-
548- >>> print gnome_filebug_view.duplicate_search_url
549- http://launchpad.dev/evolution/+filebug-show-similar
550-
551
552 Adding extra info to filed bugs
553 -------------------------------
554
555=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_filebug.py'
556--- lib/lp/bugs/browser/tests/test_bugtarget_filebug.py 2012-07-31 03:07:56 +0000
557+++ lib/lp/bugs/browser/tests/test_bugtarget_filebug.py 2012-08-03 00:21:23 +0000
558@@ -610,10 +610,3 @@
559 login_person(user)
560 view = create_initialized_view(product, '+filebug', principal=user)
561 self._assert_cache_values(view, False)
562-
563- def test_project_group(self):
564- project = self.factory.makeProject()
565- user = self.factory.makePerson()
566- login_person(user)
567- view = create_initialized_view(project, '+filebug', principal=user)
568- self._assert_cache_values(view, True)
569
570=== modified file 'lib/lp/bugs/javascript/filebug_dupefinder.js'
571--- lib/lp/bugs/javascript/filebug_dupefinder.js 2012-07-23 11:15:20 +0000
572+++ lib/lp/bugs/javascript/filebug_dupefinder.js 2012-08-03 00:21:23 +0000
573@@ -314,43 +314,20 @@
574 }
575
576 /**
577- * Use the value of the product field to set the relevant urls elements.
578- */
579-function set_product_urls()
580-{
581- var product_field = Y.one(Y.DOM.byId('field.product'));
582- if (Y.Lang.isValue(product_field)) {
583- var product = product_field.get('value');
584- search_url_base =
585- filebug_base_url + product + '/+filebug-show-similar';
586- var submit_url = [product, '+filebug'].join('/');
587- var search_form = Y.one('#filebug-search-form');
588- search_form.setAttribute('action', filebug_base_url+submit_url);
589- }
590-}
591-
592-/**
593 * Set up the dupe finder, overriding the default behaviour of the
594 * +filebug search form.
595 */
596 function set_up_dupe_finder(transaction_id, response, args) {
597 // Grab the inline filebug base url and store it.
598 filebug_base_url = Y.one('#filebug-base-url').getAttribute('href');
599-
600- // Set up the product field change listener and related variables.
601- namespace.setup_product_urls();
602-
603+ search_url_base = Y.one('#duplicate-search-url').getAttribute('href');
604+
605+ search_button = Y.one(Y.DOM.byId('field.actions.search'));
606 search_field = Y.one(Y.DOM.byId('field.title'));
607
608- var product_field = Y.one(Y.DOM.byId('field.product'));
609- if (Y.Lang.isValue(product_field)) {
610- Y.one(
611- Y.DOM.byId('field.actions.projectgroupsearch')).set(
612- 'value', 'Next');
613- } else {
614+ if (Y.Lang.isValue(search_button)) {
615 // Update the label on the search button so that it no longer
616 // says "Continue".
617- search_button = Y.one(Y.DOM.byId('field.actions.search'));
618 search_button.set('value', 'Next');
619
620 // Change the name and id of the search field so that it doesn't
621@@ -459,16 +436,6 @@
622 });
623 };
624
625-namespace.setup_product_urls = function() {
626- // Grab the search_url_base value from the page and store it.
627- search_url_base = Y.one('#duplicate-search-url').getAttribute('href');
628- var product_field = Y.one(Y.DOM.byId('field.product'));
629- if (Y.Lang.isValue(product_field)) {
630- set_product_urls();
631- product_field.on('change', set_product_urls);
632- }
633-};
634-
635 namespace.setup_dupe_finder = function() {
636 var config = {on: {success: set_up_dupe_finder,
637 failure: function() {}}};
638
639=== modified file 'lib/lp/bugs/javascript/tests/test_filebug_dupefinder.js'
640--- lib/lp/bugs/javascript/tests/test_filebug_dupefinder.js 2012-06-28 16:54:20 +0000
641+++ lib/lp/bugs/javascript/tests/test_filebug_dupefinder.js 2012-08-03 00:21:23 +0000
642@@ -242,86 +242,6 @@
643 Y.Assert.areEqual(
644 'https://bugs.launchpad.dev/foo/+filebug',
645 Y.one('#filebug-form').get('action'));
646- },
647-
648- add_project_selector: function() {
649- var project_selector = Y.Node.create([
650- '<tr>',
651- ' <td>',
652- ' <label for="field.product">Project:</label>',
653- ' <select size="1" name="field.product" id="field.product">',
654- ' <option value="foo">Foo</option>',
655- ' <option value="bar">Bar</option>',
656- ' </select>',
657- ' </td>',
658- '</tr>'
659- ].join(''));
660- Y.one('#search-field').insert(project_selector, 'before');
661- module.setup_product_urls();
662- },
663-
664- /**
665- * The filebug form url is correctly updated when the project changes.
666- */
667- test_project_change_filebug_form_action: function() {
668- this.add_project_selector();
669- var project = Y.one(Y.DOM.byId('field.product'));
670- project.set('value', 'bar');
671- simulate(project, undefined, 'change');
672- Y.Assert.areEqual(
673- 'https://bugs.launchpad.dev/bar/+filebug',
674- Y.one('#filebug-search-form').get('action'));
675- },
676-
677- /**
678- * A user first searches for duplicate bugs and there are none.
679- * They can start typing in some detail. They change the project and
680- * perform a new search. Their input should be retained.
681- */
682- test_project_change_retains_user_input_after_dups_serach: function() {
683- Y.one(Y.DOM.byId('field.product')).set('value', 'foo');
684- module.setup_product_urls();
685- // filebug container should not initially be visible
686- this.assertIsNotVisible(null, '#filebug-form-container');
687- var search_text = Y.one(Y.DOM.byId('field.search'));
688- search_text.set('value', 'foo');
689- var search_button = Y.one(Y.DOM.byId('field.actions.search'));
690- this.config.yio.io.responseText = 'No similar bug reports.';
691- this.config.yio.io.doAfter = function() {
692- var comment_text = Y.one(Y.DOM.byId('field.comment'));
693- comment_text.set('value', 'an error occurred');
694-
695- this.config.yio.io.responseText = 'Bug filing details';
696- var project = Y.one(Y.DOM.byId('field.product'));
697- project.set('value', 'bar');
698- simulate(project, undefined, 'change');
699- // filebug container should be visible
700- this.assertIsVisible(null, '#filebug-form-container');
701-
702- // Search button should day 'Check again' because we have
703- // already done a search.
704- var search_button = (Y.one(Y.DOM.byId('field.actions.search')));
705- Y.Assert.areEqual('Check again', search_button.get('value'));
706-
707- this.config.yio.io.responseText = 'No similar bug reports.';
708- this.config.yio.io.doAfter = function() {
709- // filebug container should be visible
710- this.assertIsVisible(null, '#filebug-form-container');
711- // The user input should be retained
712- Y.Assert.areEqual(
713- 'an error occurred', comment_text.get('value'));
714- Y.ArrayAssert.itemsAreEqual(
715- ['https://bugs.launchpad.dev/' +
716- 'foo/+filebug-show-similar?title=foo',
717- 'https://bugs.launchpad.dev/' +
718- 'bar/+filebug-show-similar?title=foo'],
719- this.config.yio.calls);
720- };
721- simulate(search_button, undefined, 'click');
722- this.wait();
723- };
724- simulate(search_button, undefined, 'click');
725- this.wait();
726 }
727
728 }));
729
730=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt'
731--- lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt 2011-04-20 12:59:55 +0000
732+++ lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt 2012-08-03 00:21:23 +0000
733@@ -85,11 +85,11 @@
734 Thank you for filing a bug for https://launchpad.dev/ubuntu
735 *
736 Mozilla
737- <http://launchpad.dev/mozilla/+filebug>
738- The Mozilla Project bug reporting guidelines:
739- The version of Mozilla you're using.
740+ <http://launchpad.dev/firefox/+filebug?field.actions.search=Continue&field.title=It+doesn%27t+work&field.tags=>
741+ Mozilla Firefox bug reporting guidelines:
742+ The version of Firefox you're using.
743 See http://example.com for more details.
744- Thank you for filing a bug for https://launchpad.dev/mozilla
745+ Thank you for filing a bug for https://launchpad.dev/firefox
746 *
747 Firefox
748 <http://launchpad.dev/firefox/+filebug>
749
750=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt'
751--- lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt 2012-02-23 16:15:43 +0000
752+++ lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt 2012-08-03 00:21:23 +0000
753@@ -24,7 +24,7 @@
754 There is 1 error.
755 >>> for message in top_portlet.findAll(attrs={'class': 'message'}):
756 ... print message.renderContents()
757- A summary is required.
758+ Required input is missing.
759
760 The user fills in some keywords, and clicks a button to search existing
761 bugs.
762
763=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt'
764--- lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt 2012-02-23 16:15:43 +0000
765+++ lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt 2012-08-03 00:21:23 +0000
766@@ -16,14 +16,16 @@
767 >>> user_browser.getControl('Project', index=0).options
768 ['evolution']
769
770-After we selected a product and entered a summary, we get presented with
771-a list of possible duplicates.
772+After we selected a product and entered a summary, we're sent to the
773+product's +filebug page and presented with a list of possible duplicates.
774
775 >>> user_browser.getControl('Project', index=0).value = ['evolution']
776 >>> user_browser.getControl('Summary', index=0).value = (
777 ... 'Evolution crashes')
778 >>> user_browser.getControl('Continue').click()
779
780+ >>> user_browser.url
781+ 'http://bugs.launchpad.dev/evolution/+filebug?field.actions.search=Continue&field.title=Evolution+crashes&field.tags='
782 >>> print find_main_content(user_browser.contents).renderContents()
783 <...
784 No similar bug reports were found...
785@@ -40,78 +42,6 @@
786 'Bug #...Evolution crashes... : Bugs : Evolution'
787
788
789-Subscribing to a similar bug
790-----------------------------
791-
792-If our bug is described by one of the suggested similar bugs, we can
793-subscribe to it instead of filing a new bug. This also loosely implies a
794-"me too" vote.
795-
796- >>> user_browser.open("http://bugs.launchpad.dev/gnome/+filebug")
797- >>> user_browser.getControl('Project', index=0).value = ['evolution']
798- >>> user_browser.getControl('Summary', index=0).value = (
799- ... 'Evolution crashes')
800- >>> user_browser.getControl('Continue').click()
801-
802-As before, we get a list of similar bugs to choose from, including the
803-bugs we filed recently.
804-
805- >>> from lp.bugs.tests.bug import print_bugs_list
806- >>> print_bugs_list(user_browser.contents, "similar-bugs")
807- #... Evolution crashes
808- New (0 comments) last updated ...
809-
810-This one matches, so we subscribe.
811-
812- >>> user_browser.getControl(
813- ... "Yes, this is the bug I'm trying to report").click()
814-
815- >>> print user_browser.url
816- http://bugs.launchpad.dev/evolution/+bug/...
817-
818-But, of course, we're already subscribed because we created it.
819-
820- >>> for message in get_feedback_messages(user_browser.contents):
821- ... print message
822- This bug is already marked as affecting you.
823-
824-
825-Filing a bug when there are none similar
826-----------------------------------------
827-
828-When no similar bugs are found the form works the same but appears
829-different in the user agent.
830-
831- >>> user_browser.open("http://launchpad.dev/gnome/+filebug")
832-
833-Submitting some distinctive details...
834-
835- >>> user_browser.getControl('Project', index=0).value = ['evolution']
836- >>> user_browser.getControl('Summary', index=0).value = (
837- ... 'Faznambutron dumps core unless clenching')
838- >>> user_browser.getControl('Continue').click()
839-
840-...yields no similar bugs. In fact, the similar bugs table is not even
841-shown.
842-
843- >>> similar_bugs_list = find_tag_by_id(
844- ... user_browser.contents, "similar-bugs")
845- >>> print similar_bugs_list
846- None
847-
848-But, as before, entering a description and submitting the bug takes the
849-user to the bug page.
850-
851- >>> user_browser.getControl('Further information').value = (
852- ... 'Faznambutron is a plugin designed to ...')
853- >>> user_browser.getControl('Submit Bug Report').click()
854- >>> user_browser.url
855- 'http://bugs.launchpad.dev/evolution/+bug/...'
856-
857- >>> user_browser.title
858- 'Bug #...Faznambutron dumps core... : Bugs : Evolution'
859-
860-
861 Empty ProjectGroups
862 -------------------
863
864
865=== modified file 'lib/lp/bugs/templates/bugtarget-filebug-search.pt'
866--- lib/lp/bugs/templates/bugtarget-filebug-search.pt 2012-07-07 14:00:30 +0000
867+++ lib/lp/bugs/templates/bugtarget-filebug-search.pt 2012-08-03 00:21:23 +0000
868@@ -57,12 +57,6 @@
869 <metal:widget metal:use-macro="context/@@launchpad_form/widget_row" />
870 </tal:product_widget>
871
872- <tal:bugtarget
873- tal:define="widget nocall:view/widgets/bugtarget|nothing"
874- tal:condition="widget">
875- <metal:widget metal:use-macro="context/@@launchpad_form/widget_row" />
876- </tal:bugtarget>
877-
878 <tal:hidden_tags tal:replace="structure view/widgets/tags/hidden" />
879
880 <tr>
881@@ -137,7 +131,7 @@
882 </tal:filebug-form>
883 </div>
884 </tal:not_project_group>
885- <p class="hidden">
886+ <p class="hidden" tal:condition="view/inline_filebug_base_url|nothing">
887 <a id="filebug-base-url"
888 tal:attributes="href view/inline_filebug_base_url"></a>
889 <a id="filebug-form-url"
890
891=== modified file 'lib/lp/bugs/templates/bugtarget-macros-filebug.pt'
892--- lib/lp/bugs/templates/bugtarget-macros-filebug.pt 2012-07-31 03:14:11 +0000
893+++ lib/lp/bugs/templates/bugtarget-macros-filebug.pt 2012-08-03 00:21:23 +0000
894@@ -4,12 +4,6 @@
895 omit-tag="">
896 <metal:basic_filebug_widgets define-macro="basic_filebug_widgets">
897
898- <tal:bugtarget
899- tal:define="widget nocall:view/widgets/bugtarget|nothing"
900- tal:condition="widget">
901- <metal:widget use-macro="context/@@launchpad_form/widget_row" />
902- </tal:bugtarget>
903-
904 <tr tal:condition="view/widgets/packagename|nothing">
905 <th colspan="2" style="text-align: left"><label
906 tal:attributes="for view/widgets/packagename/name"
907@@ -154,10 +148,6 @@
908 Change this <span class="sprite edit action-icon">Edit</span>
909 </a>
910 </div>
911- <span tal:condition="view/frontpage_form">
912- You can <a href="+filebug">refine and resubmit</a> your bug
913- report.
914- </span>
915 <tal:upstream condition="view/contextIsProduct">
916 <tal:defines define="bugtracker product_or_distro/getExternalBugTracker">
917 <h3>