← Back to team overview

launchpad-reviewers team mailing list archive

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

 

William Grant has proposed merging lp:~wgrant/launchpad/rebuild-projectgroup-filebug into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1031210 in Launchpad itself: "ProjectGroup:+filebug doesn't really know what it is meant to be"
  https://bugs.launchpad.net/launchpad/+bug/1031210

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/rebuild-projectgroup-filebug/+merge/117835

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.
-- 
https://code.launchpad.net/~wgrant/launchpad/rebuild-projectgroup-filebug/+merge/117835
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/rebuild-projectgroup-filebug into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py	2012-08-01 01:05:08 +0000
+++ lib/lp/bugs/browser/bugtarget.py	2012-08-02 08:44:19 +0000
@@ -16,7 +16,7 @@
     "IProductBugConfiguration",
     "OfficialBugTagsManageView",
     "ProductConfigureBugTrackerView",
-    "ProjectFileBugGuidedView",
+    "ProjectGroupFileBugGuidedView",
     "product_to_productbugconfiguration",
     ]
 
@@ -105,7 +105,6 @@
     UNRESOLVED_BUGTASK_STATUSES,
     )
 from lp.bugs.interfaces.bugtracker import IBugTracker
-from lp.bugs.interfaces.malone import IMaloneApplication
 from lp.bugs.interfaces.securitycontact import IHasSecurityContact
 from lp.bugs.model.bugtask import BugTask
 from lp.bugs.model.structuralsubscription import (
@@ -149,7 +148,6 @@
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.batching import BatchNavigator
 from lp.services.webapp.breadcrumb import Breadcrumb
-from lp.services.webapp.interfaces import ILaunchBag
 from lp.services.webapp.menu import structured
 
 # A simple vocabulary for the subscribe_to_existing_bug form field.
@@ -253,12 +251,9 @@
     custom_widget('information_type', LaunchpadRadioWidgetWithDescription)
 
     extra_data_token = None
-    advanced_form = False
-    frontpage_form = False
-    data_parser = None
 
     def __init__(self, context, request):
-        LaunchpadFormView.__init__(self, context, request)
+        super(FileBugViewBase, self).__init__(context, request)
         self.extra_data = FileBugData()
 
     def initialize(self):
@@ -282,15 +277,12 @@
             type.name for type in PRIVATE_INFORMATION_TYPES]
         cache.objects['bug_private_by_default'] = (
             IProduct.providedBy(self.context) and self.context.private_bugs)
-        # Project groups are special. The Next button sends you to
-        # Product:+filebug, so we need none of the usual stuff.
-        if not IProjectGroup.providedBy(self.context):
-            cache.objects['information_type_data'] = [
-                {'value': term.name, 'description': term.description,
-                'name': term.title,
-                'description_css_class': 'choice-description'}
-                for term in
-                self.context.pillar.getAllowedBugInformationTypes()]
+        cache.objects['information_type_data'] = [
+            {'value': term.name, 'description': term.description,
+            'name': term.title,
+            'description_css_class': 'choice-description'}
+            for term in
+            self.context.pillar.getAllowedBugInformationTypes()]
         bugtask_status_data = vocabulary_to_choice_edit_items(
             BugTaskStatus, include_description=True, css_class_prefix='status',
             excluded_items=[
@@ -307,8 +299,7 @@
             excluded_items=[BugTaskImportance.UNKNOWN])
         cache.objects['bugtask_importance_data'] = bugtask_importance_data
         cache.objects['enable_bugfiling_duplicate_search'] = (
-            IProjectGroup.providedBy(self.context)
-            or self.context.enable_bugfiling_duplicate_search)
+            self.context.enable_bugfiling_duplicate_search)
 
         super(FileBugViewBase, self).initialize()
 
@@ -366,21 +357,8 @@
         if (IDistribution.providedBy(context) or
             IDistributionSourcePackage.providedBy(context)):
             field_names.append('packagename')
-        elif IMaloneApplication.providedBy(context):
-            field_names.append('bugtarget')
-        elif IProjectGroup.providedBy(context):
-            field_names.append('product')
-        elif not IProduct.providedBy(context):
-            raise AssertionError('Unknown context: %r' % context)
-
-        # If the context is a project group we want to render the optional
-        # fields since they will initially be hidden and later exposed if the
-        # selected project supports them.
-        include_extra_fields = IProjectGroup.providedBy(context)
-        if not include_extra_fields:
-            include_extra_fields = self.is_bug_supervisor
-
-        if include_extra_fields:
+
+        if self.is_bug_supervisor:
             field_names.extend(
                 ['assignee', 'importance', 'milestone', 'status'])
 
@@ -405,14 +383,11 @@
         return IProduct.providedBy(self.context)
 
     def contextIsProject(self):
-        return IProjectGroup.providedBy(self.context)
+        return False
 
     def targetIsUbuntu(self):
         ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
-        return (self.context == ubuntu or
-                (IMaloneApplication.providedBy(self.context) and
-                 self.request.form.get('field.bugtarget.distribution') ==
-                 ubuntu.name))
+        return self.context == ubuntu
 
     def getPackageNameFieldCSSClass(self):
         """Return the CSS class for the packagename field."""
@@ -455,8 +430,6 @@
             if packagename:
                 if IDistribution.providedBy(self.context):
                     distribution = self.context
-                elif 'distribution' in data:
-                    distribution = data['distribution']
                 else:
                     assert IDistributionSourcePackage.providedBy(self.context)
                     distribution = self.context.distribution
@@ -479,17 +452,6 @@
                 self.setFieldError("packagename",
                                    "Please enter a package name")
 
-        # If we've been called from the frontpage filebug forms we must check
-        # that whatever product or distro is having a bug filed against it
-        # actually uses Malone for its bug tracking.
-        product_or_distro = self.getProductOrDistroFromContext()
-        if (product_or_distro is not None and
-            product_or_distro.bug_tracking_usage != ServiceUsage.LAUNCHPAD):
-            self.setFieldError(
-                'bugtarget',
-                "%s does not use Launchpad as its bug tracker " %
-                product_or_distro.displayname)
-
     def setUpWidgets(self):
         """Customize the onKeyPress event of the package name chooser."""
         super(FileBugViewBase, self).setUpWidgets()
@@ -502,11 +464,6 @@
         """Set up the form fields. See `LaunchpadFormView`."""
         super(FileBugViewBase, self).setUpFields()
 
-        # Project groups are special. The Next button sends you to
-        # Product:+filebug, so we need none of the usual stuff.
-        if IProjectGroup.providedBy(self.context):
-            return
-
         if self.is_bug_supervisor:
             info_type_vocab = InformationTypeVocabulary(
                 types=self.context.pillar.getAllowedBugInformationTypes())
@@ -534,14 +491,8 @@
 
     def contextUsesMalone(self):
         """Does the context use Malone as its official bugtracker?"""
-        if IProjectGroup.providedBy(self.context):
-            products_using_malone = [
-                product for product in self.context.products
-                if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD]
-            return len(products_using_malone) > 0
-        else:
-            bug_tracking_usage = self.getMainContext().bug_tracking_usage
-            return bug_tracking_usage == ServiceUsage.LAUNCHPAD
+        bug_tracking_usage = self.getMainContext().bug_tracking_usage
+        return bug_tracking_usage == ServiceUsage.LAUNCHPAD
 
     def shouldSelectPackageName(self):
         """Should the radio button to select a package be selected?"""
@@ -559,8 +510,6 @@
         title = data["title"]
         comment = data["comment"].rstrip()
         packagename = data.get("packagename")
-        distribution = data.get(
-            "distribution", getUtility(ILaunchBag).distribution)
 
         information_type = data.get("information_type")
         # If the old UI is enabled, security bugs are always embargoed
@@ -569,14 +518,6 @@
             information_type = InformationType.PRIVATESECURITY
 
         context = self.context
-        if distribution is not None:
-            # We're being called from the generic bug filing form, so
-            # manually set the chosen distribution as the context.
-            context = distribution
-        elif IProjectGroup.providedBy(context):
-            context = data['product']
-        elif IMaloneApplication.providedBy(context):
-            context = data['bugtarget']
 
         # Ensure that no package information is used, if the user
         # enters a package name but then selects "I don't know".
@@ -814,22 +755,8 @@
         return self, ()
 
     def getProductOrDistroFromContext(self):
-        """Return the product or distribution relative to the context.
-
-        For instance, if the context is an IDistroSeries, return the
-        distribution related to it. Will return None if the context is
-        not related to a product or a distro.
-        """
-        context = self.context
-        if IProduct.providedBy(context) or IDistribution.providedBy(context):
-            return context
-        elif IProductSeries.providedBy(context):
-            return context.product
-        elif (IDistroSeries.providedBy(context) or
-              IDistributionSourcePackage.providedBy(context)):
-            return context.distribution
-        else:
-            return None
+        """Return the product or distribution relative to the context."""
+        return self.context.pillar
 
     def showOptionalMarker(self, field_name):
         """See `LaunchpadFormView`."""
@@ -854,26 +781,11 @@
         total accuracy, and will return the first 'relevant' bugtask
         it finds even if there are other candidates. Be warned!
         """
-        context = self.context
-
-        if IProjectGroup.providedBy(context):
-            contexts = set(context.products)
-        else:
-            contexts = [context]
-
         for bugtask in bug.bugtasks:
-            if bugtask.target in contexts or bugtask.pillar in contexts:
+            if self.context in (bugtask.target, bugtask.pillar):
                 return bugtask
         return None
 
-    @property
-    def bugtarget(self):
-        """The bugtarget we're currently assuming.
-
-        The same as the context.
-        """
-        return self.context
-
     default_bug_reported_acknowledgement = "Thank you for your bug report."
 
     def getAcknowledgementMessage(self, context):
@@ -947,38 +859,24 @@
         Returns a list of dicts, with each dict containing values for
         "preamble" and "content".
         """
-
-        def target_name(target):
-            # IProjectGroup can be considered the target of a bug during
-            # the bug filing process, but does not extend IBugTarget
-            # and ultimately cannot actually be the target of a
-            # bug. Hence this function to determine a suitable
-            # name/title to display. Hurrumph.
-            if IBugTarget.providedBy(target):
-                return target.bugtargetdisplayname
-            else:
-                return target.displayname
-
         guidelines = []
-        bugtarget = self.context
-        if bugtarget is not None:
-            content = bugtarget.bug_reporting_guidelines
+        content = self.context.bug_reporting_guidelines
+        if content is not None and len(content) > 0:
+            guidelines.append({
+                "source": self.context.bugtargetdisplayname,
+                "content": content,
+                })
+        # Distribution source packages are shown with both their
+        # own reporting guidelines and those of their
+        # distribution.
+        if IDistributionSourcePackage.providedBy(self.context):
+            distribution = self.context.distribution
+            content = distribution.bug_reporting_guidelines
             if content is not None and len(content) > 0:
                 guidelines.append({
-                        "source": target_name(bugtarget),
-                        "content": content,
-                        })
-            # Distribution source packages are shown with both their
-            # own reporting guidelines and those of their
-            # distribution.
-            if IDistributionSourcePackage.providedBy(bugtarget):
-                distribution = bugtarget.distribution
-                content = distribution.bug_reporting_guidelines
-                if content is not None and len(content) > 0:
-                    guidelines.append({
-                            "source": target_name(distribution),
-                            "content": content,
-                            })
+                    "source": distribution.bugtargetdisplayname,
+                    "content": content,
+                    })
         return guidelines
 
     def getMainContext(self):
@@ -995,7 +893,7 @@
             context, self.user)
 
 
-class FileBugAdvancedView(FileBugViewBase):
+class FileBugAdvancedView(LaunchpadView):
     """Browser view for filing a bug.
 
     This view exists only to redirect from +filebug-advanced to +filebug.
@@ -1038,11 +936,6 @@
         return url
 
     @property
-    def search_context(self):
-        """Return the context used to search for similar bugs."""
-        return self.context
-
-    @property
     def search_text(self):
         """Return the search string entered by the user."""
         return self.request.get('title')
@@ -1053,19 +946,16 @@
         title = self.search_text
         if not title:
             return []
-        search_context = self.search_context
-        if search_context is None:
-            return []
-        elif IProduct.providedBy(search_context):
-            context_params = {'product': search_context}
-        elif IDistribution.providedBy(search_context):
-            context_params = {'distribution': search_context}
+        elif IProduct.providedBy(self.context):
+            context_params = {'product': self.context}
+        elif IDistribution.providedBy(self.context):
+            context_params = {'distribution': self.context}
         else:
-            assert IDistributionSourcePackage.providedBy(search_context), (
-                    'Unknown search context: %r' % search_context)
+            assert IDistributionSourcePackage.providedBy(self.context), (
+                    'Unknown search context: %r' % self.context)
             context_params = {
-                'distribution': search_context.distribution,
-                'sourcepackagename': search_context.sourcepackagename}
+                'distribution': self.context.distribution,
+                'sourcepackagename': self.context.sourcepackagename}
 
         matching_bugtasks = getUtility(IBugTaskSet).findSimilar(
             self.user, title, **context_params)
@@ -1118,23 +1008,10 @@
 
     @property
     def page_title(self):
-        if IMaloneApplication.providedBy(self.context):
-            return 'Report a bug'
-        else:
-            return 'Report a bug about %s' % self.context.title
-
-    @safe_action
-    @action("Continue", name="projectgroupsearch",
-            validator="validate_search")
-    def projectgroup_search_action(self, action, data):
-        """Search for similar bug reports."""
-        # Don't give focus to any widget, to ensure that the browser
-        # won't scroll past the "possible duplicates" list.
-        self.initial_focus_widget = None
-        return self._PROJECTGROUP_SEARCH_FOR_DUPES()
-
-    @safe_action
-    @action("Continue", name="search", validator="validate_search")
+        return 'Report a bug about %s' % self.context.title
+
+    @safe_action
+    @action("Continue", name="search")
     def search_action(self, action, data):
         """Search for similar bug reports."""
         # Don't give focus to any widget, to ensure that the browser
@@ -1143,26 +1020,6 @@
         return self.showFileBugForm()
 
     @property
-    def search_context(self):
-        """Return the context used to search for similar bugs."""
-        if IDistributionSourcePackage.providedBy(self.context):
-            return self.context
-
-        search_context = self.getMainContext()
-        if IProjectGroup.providedBy(search_context):
-            assert self.widgets['product'].hasValidInput(), (
-                "This method should be called only when we know which"
-                " product the user selected.")
-            search_context = self.widgets['product'].getInputValue()
-        elif IMaloneApplication.providedBy(search_context):
-            if self.widgets['bugtarget'].hasValidInput():
-                search_context = self.widgets['bugtarget'].getInputValue()
-            else:
-                search_context = None
-
-        return search_context
-
-    @property
     def search_text(self):
         """Return the search string entered by the user."""
         try:
@@ -1170,18 +1027,6 @@
         except InputErrors:
             return None
 
-    def validate_search(self, action, data):
-        """Make sure some keywords are provided."""
-        try:
-            data['title'] = self.widgets['title'].getInputValue()
-        except InputErrors as error:
-            self.setFieldError("title", "A summary is required.")
-            return [error]
-
-        # Return an empty list of errors to satisfy the validation API,
-        # and say "we've handled the validation and found no errors."
-        return []
-
     def validate_no_dupe_found(self, action, data):
         return ()
 
@@ -1195,13 +1040,24 @@
         return self._FILEBUG_FORM()
 
 
-class ProjectFileBugGuidedView(FileBugGuidedView):
+class ProjectGroupFileBugGuidedView(LaunchpadFormView):
     """Guided filebug pages for IProjectGroup."""
 
-    # Make inheriting the base class' actions work.
-    actions = FileBugGuidedView.actions
     schema = IProjectGroupBugAddForm
 
+    custom_widget('title', TextWidget, displayWidth=40)
+    custom_widget('tags', BugTagsWidget)
+
+    extra_data_to_process = False
+
+    @property
+    def field_names(self):
+        return ['product', 'title', 'tags']
+
+    @property
+    def page_title(self):
+        return 'Report a bug about %s' % self.context.title
+
     @cachedproperty
     def products_using_malone(self):
         return [
@@ -1215,36 +1071,30 @@
         else:
             return None
 
-    @property
-    def inline_filebug_form_url(self):
-        """Return the URL to the inline filebug form.
-
-        If a token was passed to this view, it will be passed through
-        to the inline bug filing form via the returned URL.
-
-        The URL returned will be the URL of the first of the current
-        ProjectGroup's products, since that's the product that will be
-        selected by default when the view is rendered.
-        """
-        url = canonical_url(
-            self.default_product, view_name='+filebug-inline-form')
-        if self.extra_data_token is not None:
-            url = urlappend(url, self.extra_data_token)
-        return url
-
-    @property
-    def duplicate_search_url(self):
-        """Return the URL to the inline duplicate search view.
-
-        The URL returned will be the URL of the first of the current
-        ProjectGroup's products, since that's the product that will be
-        selected by default when the view is rendered.
-        """
-        url = canonical_url(
-            self.default_product, view_name='+filebug-show-similar')
-        if self.extra_data_token is not None:
-            url = urlappend(url, self.extra_data_token)
-        return url
+    def contextUsesMalone(self):
+        return self.default_product is not None
+
+    def contextIsProduct(self):
+        return False
+
+    def contextIsProject(self):
+        return True
+
+    def getProductOrDistroFromContext(self):
+        return None
+
+    @safe_action
+    @action("Continue", name="projectgroupsearch")
+    def projectgroup_search_action(self, action, data):
+        """Redirect to the chosen product's form."""
+        base = canonical_url(data['product'], view_name='+filebug')
+        query = urllib.urlencode([
+            ('field.actions.search', 'Continue'),
+            ('field.title', data['title']),
+            ('field.tags', ' '.join(data['tags'])),
+            ])
+        url = '%s?%s' % (base, query)
+        self.request.response.redirect(url)
 
 
 class BugTargetBugListingView(LaunchpadView):

=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml	2012-07-31 03:14:11 +0000
+++ lib/lp/bugs/browser/configure.zcml	2012-08-02 08:44:19 +0000
@@ -422,7 +422,8 @@
     <browser:page
         name="+filebug"
         for="lp.registry.interfaces.projectgroup.IProjectGroup"
-        class="lp.bugs.browser.bugtarget.ProjectFileBugGuidedView"
+        class="lp.bugs.browser.bugtarget.ProjectGroupFileBugGuidedView"
+        template="../templates/bugtarget-filebug-search.pt"
         permission="launchpad.AnyPerson"/>
     <browser:page
         name="+filebug-advanced"

=== modified file 'lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt'
--- lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt	2012-07-30 23:49:34 +0000
+++ lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt	2012-08-02 08:44:19 +0000
@@ -79,20 +79,6 @@
     >>> print filebug_view.duplicate_search_url
     http://launchpad.dev/firefox/+filebug-show-similar
 
-For project groups, the URLs returned will refer to the default product
-for those project groups.
-
-    >>> from lp.registry.interfaces.projectgroup import IProjectGroupSet
-    >>> gnome_project = getUtility(IProjectGroupSet).getByName('gnome')
-    >>> gnome_filebug_view = create_initialized_view(
-    ...     gnome_project, '+filebug')
-
-    >>> print gnome_filebug_view.inline_filebug_form_url
-    http://launchpad.dev/evolution/+filebug-inline-form
-
-    >>> print gnome_filebug_view.duplicate_search_url
-    http://launchpad.dev/evolution/+filebug-show-similar
-
 
 Adding extra info to filed bugs
 -------------------------------

=== modified file 'lib/lp/bugs/javascript/filebug_dupefinder.js'
--- lib/lp/bugs/javascript/filebug_dupefinder.js	2012-07-23 11:15:20 +0000
+++ lib/lp/bugs/javascript/filebug_dupefinder.js	2012-08-02 08:44:19 +0000
@@ -314,43 +314,20 @@
 }
 
 /**
- * Use the value of the product field to set the relevant urls elements.
- */
-function set_product_urls()
-{
-    var product_field = Y.one(Y.DOM.byId('field.product'));
-    if (Y.Lang.isValue(product_field)) {
-        var product = product_field.get('value');
-        search_url_base =
-            filebug_base_url + product + '/+filebug-show-similar';
-        var submit_url = [product, '+filebug'].join('/');
-        var search_form = Y.one('#filebug-search-form');
-        search_form.setAttribute('action', filebug_base_url+submit_url);
-    }
-}
-
-/**
  * Set up the dupe finder, overriding the default behaviour of the
  * +filebug search form.
  */
 function set_up_dupe_finder(transaction_id, response, args) {
     // Grab the inline filebug base url and store it.
     filebug_base_url = Y.one('#filebug-base-url').getAttribute('href');
-
-    // Set up the product field change listener and related variables.
-    namespace.setup_product_urls();
-
+    search_url_base = Y.one('#duplicate-search-url').getAttribute('href');
+
+    search_button = Y.one(Y.DOM.byId('field.actions.search'));
     search_field = Y.one(Y.DOM.byId('field.title'));
 
-    var product_field = Y.one(Y.DOM.byId('field.product'));
-    if (Y.Lang.isValue(product_field)) {
-        Y.one(
-            Y.DOM.byId('field.actions.projectgroupsearch')).set(
-                'value', 'Next');
-    } else {
+    if (Y.Lang.isValue(search_button)) {
         // Update the label on the search button so that it no longer
         // says "Continue".
-        search_button = Y.one(Y.DOM.byId('field.actions.search'));
         search_button.set('value', 'Next');
 
         // Change the name and id of the search field so that it doesn't
@@ -459,16 +436,6 @@
     });
 };
 
-namespace.setup_product_urls = function() {
-    // Grab the search_url_base value from the page and store it.
-    search_url_base = Y.one('#duplicate-search-url').getAttribute('href');
-    var product_field = Y.one(Y.DOM.byId('field.product'));
-    if (Y.Lang.isValue(product_field)) {
-        set_product_urls();
-        product_field.on('change', set_product_urls);
-    }
-};
-
 namespace.setup_dupe_finder = function() {
     var config = {on: {success: set_up_dupe_finder,
                    failure: function() {}}};

=== modified file 'lib/lp/bugs/javascript/tests/test_filebug_dupefinder.js'
--- lib/lp/bugs/javascript/tests/test_filebug_dupefinder.js	2012-06-28 16:54:20 +0000
+++ lib/lp/bugs/javascript/tests/test_filebug_dupefinder.js	2012-08-02 08:44:19 +0000
@@ -242,86 +242,6 @@
             Y.Assert.areEqual(
                 'https://bugs.launchpad.dev/foo/+filebug',
                 Y.one('#filebug-form').get('action'));
-        },
-
-        add_project_selector: function() {
-            var project_selector = Y.Node.create([
-            '<tr>',
-            '    <td>',
-            '        <label for="field.product">Project:</label>',
-            '        <select size="1" name="field.product" id="field.product">',
-            '            <option value="foo">Foo</option>',
-            '            <option value="bar">Bar</option>',
-            '        </select>',
-            '    </td>',
-            '</tr>'
-            ].join(''));
-            Y.one('#search-field').insert(project_selector, 'before');
-            module.setup_product_urls();
-        },
-
-        /**
-         * The filebug form url is correctly updated when the project changes.
-         */
-        test_project_change_filebug_form_action: function() {
-            this.add_project_selector();
-            var project = Y.one(Y.DOM.byId('field.product'));
-            project.set('value', 'bar');
-            simulate(project, undefined, 'change');
-            Y.Assert.areEqual(
-                'https://bugs.launchpad.dev/bar/+filebug',
-                Y.one('#filebug-search-form').get('action'));
-        },
-
-        /**
-         * A user first searches for duplicate bugs and there are none.
-         * They can start typing in some detail. They change the project and
-         * perform a new search. Their input should be retained.
-         */
-        test_project_change_retains_user_input_after_dups_serach: function() {
-            Y.one(Y.DOM.byId('field.product')).set('value', 'foo');
-            module.setup_product_urls();
-            // filebug container should not initially be visible
-            this.assertIsNotVisible(null, '#filebug-form-container');
-            var search_text = Y.one(Y.DOM.byId('field.search'));
-            search_text.set('value', 'foo');
-            var search_button = Y.one(Y.DOM.byId('field.actions.search'));
-            this.config.yio.io.responseText = 'No similar bug reports.';
-            this.config.yio.io.doAfter = function() {
-                var comment_text = Y.one(Y.DOM.byId('field.comment'));
-                comment_text.set('value', 'an error occurred');
-
-                this.config.yio.io.responseText = 'Bug filing details';
-                var project = Y.one(Y.DOM.byId('field.product'));
-                project.set('value', 'bar');
-                simulate(project, undefined, 'change');
-                // filebug container should be visible
-                this.assertIsVisible(null, '#filebug-form-container');
-
-                // Search button should day 'Check again' because we have
-                // already done a search.
-                var search_button = (Y.one(Y.DOM.byId('field.actions.search')));
-                Y.Assert.areEqual('Check again', search_button.get('value'));
-
-                this.config.yio.io.responseText = 'No similar bug reports.';
-                this.config.yio.io.doAfter = function() {
-                    // filebug container should be visible
-                    this.assertIsVisible(null, '#filebug-form-container');
-                    // The user input should be retained
-                    Y.Assert.areEqual(
-                        'an error occurred', comment_text.get('value'));
-                    Y.ArrayAssert.itemsAreEqual(
-                            ['https://bugs.launchpad.dev/' +
-                             'foo/+filebug-show-similar?title=foo',
-                             'https://bugs.launchpad.dev/' +
-                             'bar/+filebug-show-similar?title=foo'],
-                            this.config.yio.calls);
-                };
-                simulate(search_button, undefined, 'click');
-                this.wait();
-            };
-            simulate(search_button, undefined, 'click');
-            this.wait();
         }
 
     }));

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt	2012-02-23 16:15:43 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt	2012-08-02 08:44:19 +0000
@@ -24,7 +24,7 @@
     There is 1 error.
     >>> for message in top_portlet.findAll(attrs={'class': 'message'}):
     ...     print message.renderContents()
-    A summary is required.
+    Required input is missing.
 
 The user fills in some keywords, and clicks a button to search existing
 bugs.

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt	2012-02-23 16:15:43 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt	2012-08-02 08:44:19 +0000
@@ -16,14 +16,16 @@
     >>> user_browser.getControl('Project', index=0).options
     ['evolution']
 
-After we selected a product and entered a summary, we get presented with
-a list of possible duplicates.
+After we selected a product and entered a summary, we're sent to the
+product's +filebug page and presented with a list of possible duplicates.
 
     >>> user_browser.getControl('Project', index=0).value = ['evolution']
     >>> user_browser.getControl('Summary', index=0).value = (
     ...     'Evolution crashes')
     >>> user_browser.getControl('Continue').click()
 
+    >>> user_browser.url
+    'http://bugs.launchpad.dev/evolution/+filebug?field.actions.search=Continue&field.title=Evolution+crashes&field.tags='
     >>> print find_main_content(user_browser.contents).renderContents()
     <...
     No similar bug reports were found...
@@ -40,78 +42,6 @@
     'Bug #...Evolution crashes... : Bugs : Evolution'
 
 
-Subscribing to a similar bug
-----------------------------
-
-If our bug is described by one of the suggested similar bugs, we can
-subscribe to it instead of filing a new bug. This also loosely implies a
-"me too" vote.
-
-    >>> user_browser.open("http://bugs.launchpad.dev/gnome/+filebug";)
-    >>> user_browser.getControl('Project', index=0).value = ['evolution']
-    >>> user_browser.getControl('Summary', index=0).value = (
-    ...     'Evolution crashes')
-    >>> user_browser.getControl('Continue').click()
-
-As before, we get a list of similar bugs to choose from, including the
-bugs we filed recently.
-
-    >>> from lp.bugs.tests.bug import print_bugs_list
-    >>> print_bugs_list(user_browser.contents, "similar-bugs")
-    #... Evolution crashes
-    New (0 comments) last updated ...
-
-This one matches, so we subscribe.
-
-    >>> user_browser.getControl(
-    ...     "Yes, this is the bug I'm trying to report").click()
-
-    >>> print user_browser.url
-    http://bugs.launchpad.dev/evolution/+bug/...
-
-But, of course, we're already subscribed because we created it.
-
-    >>> for message in get_feedback_messages(user_browser.contents):
-    ...     print message
-    This bug is already marked as affecting you.
-
-
-Filing a bug when there are none similar
-----------------------------------------
-
-When no similar bugs are found the form works the same but appears
-different in the user agent.
-
-    >>> user_browser.open("http://launchpad.dev/gnome/+filebug";)
-
-Submitting some distinctive details...
-
-    >>> user_browser.getControl('Project', index=0).value = ['evolution']
-    >>> user_browser.getControl('Summary', index=0).value = (
-    ...     'Faznambutron dumps core unless clenching')
-    >>> user_browser.getControl('Continue').click()
-
-...yields no similar bugs. In fact, the similar bugs table is not even
-shown.
-
-    >>> similar_bugs_list = find_tag_by_id(
-    ...     user_browser.contents, "similar-bugs")
-    >>> print similar_bugs_list
-    None
-
-But, as before, entering a description and submitting the bug takes the
-user to the bug page.
-
-    >>> user_browser.getControl('Further information').value = (
-    ...     'Faznambutron is a plugin designed to ...')
-    >>> user_browser.getControl('Submit Bug Report').click()
-    >>> user_browser.url
-    'http://bugs.launchpad.dev/evolution/+bug/...'
-
-    >>> user_browser.title
-    'Bug #...Faznambutron dumps core... : Bugs : Evolution'
-
-
 Empty ProjectGroups
 -------------------
 

=== modified file 'lib/lp/bugs/templates/bugtarget-filebug-search.pt'
--- lib/lp/bugs/templates/bugtarget-filebug-search.pt	2012-07-07 14:00:30 +0000
+++ lib/lp/bugs/templates/bugtarget-filebug-search.pt	2012-08-02 08:44:19 +0000
@@ -57,12 +57,6 @@
                   <metal:widget metal:use-macro="context/@@launchpad_form/widget_row" />
                 </tal:product_widget>
 
-                <tal:bugtarget
-                    tal:define="widget nocall:view/widgets/bugtarget|nothing"
-                    tal:condition="widget">
-                  <metal:widget metal:use-macro="context/@@launchpad_form/widget_row" />
-                </tal:bugtarget>
-
                 <tal:hidden_tags tal:replace="structure view/widgets/tags/hidden" />
 
                 <tr>
@@ -137,7 +131,7 @@
             </tal:filebug-form>
           </div>
         </tal:not_project_group>
-        <p class="hidden">
+        <p class="hidden" tal:condition="view/inline_filebug_base_url|nothing">
           <a id="filebug-base-url"
               tal:attributes="href view/inline_filebug_base_url"></a>
           <a id="filebug-form-url"

=== modified file 'lib/lp/bugs/templates/bugtarget-macros-filebug.pt'
--- lib/lp/bugs/templates/bugtarget-macros-filebug.pt	2012-07-31 03:14:11 +0000
+++ lib/lp/bugs/templates/bugtarget-macros-filebug.pt	2012-08-02 08:44:19 +0000
@@ -4,12 +4,6 @@
   omit-tag="">
 <metal:basic_filebug_widgets define-macro="basic_filebug_widgets">
 
-  <tal:bugtarget
-      tal:define="widget nocall:view/widgets/bugtarget|nothing"
-      tal:condition="widget">
-    <metal:widget use-macro="context/@@launchpad_form/widget_row" />
-  </tal:bugtarget>
-
   <tr tal:condition="view/widgets/packagename|nothing">
     <th colspan="2" style="text-align: left"><label
         tal:attributes="for view/widgets/packagename/name"
@@ -154,10 +148,6 @@
           Change this <span class="sprite edit action-icon">Edit</span>
         </a>
       </div>
-      <span tal:condition="view/frontpage_form">
-        You can <a href="+filebug">refine and resubmit</a> your bug
-        report.
-      </span>
       <tal:upstream condition="view/contextIsProduct">
         <tal:defines define="bugtracker product_or_distro/getExternalBugTracker">
           <h3>


Follow ups