Merge lp:~gmb/launchpad/ajax-dupefinder-has-risen-from-the-grave into lp:launchpad

Proposed by Graham Binns
Status: Merged
Approved by: Gavin Panella
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~gmb/launchpad/ajax-dupefinder-has-risen-from-the-grave
Merge into: lp:launchpad
Prerequisite: lp:~gmb/launchpad/prince-of-ajax-dupefinding-bug-358510
Diff against target: 838 lines (+351/-135)
10 files modified
lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js (+71/-57)
lib/lp/bugs/browser/bugtarget.py (+58/-20)
lib/lp/bugs/browser/configure.zcml (+7/-0)
lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt (+19/-0)
lib/lp/bugs/interfaces/bug.py (+9/-2)
lib/lp/bugs/templates/bugtarget-filebug-inline-form.pt (+11/-0)
lib/lp/bugs/templates/bugtarget-filebug-search.pt (+103/-33)
lib/lp/bugs/templates/bugtarget-filebug-show-similar.pt (+7/-2)
lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt (+2/-2)
lib/lp/bugs/templates/bugtarget-macros-filebug.pt (+64/-19)
To merge this branch: bzr merge lp:~gmb/launchpad/ajax-dupefinder-has-risen-from-the-grave
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Abstain
Gavin Panella (community) code js Approve
Michael Nelson (community) ui Approve
Review via email: mp+15599@code.launchpad.net

Commit message

The +filebug dupe finder should now work in-line asynchronously and in-line for Products.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

This branch does the final connecting up of JS to make the Asynchronous Dupefinder stuff work.

How to test this branch (UI):

 1. Go to launchpad.dev/firefox/+filebug
 2. Enter a simple title (say, 'a') and hit "next"
 3. You should get a list of possible duplicates for that bug.
 4. You should be able to interact with those dupes in the same way as you always do (subscribe, mark as affecting you, etc).
 5. You should also be able to file a new bug if you need to in the same way as you do currently.

Issues that I'm aware of at this stage:

 1. The animations seem to happen at the wrong time, so slide_ins get played after the element has already been rendered as closed. I'm looking into this.
 2. The way that the dupes appear in the page isn't exactly pretty. I'm open to suggestions about how this should work.

Revision history for this message
Michael Nelson (michael.nelson) wrote :

> This branch does the final connecting up of JS to make the Asynchronous
> Dupefinder stuff work.

This is great! I've got three points below that I think are worth addressing - see what you think.

>
> How to test this branch (UI):
>
> 1. Go to launchpad.dev/firefox/+filebug
> 2. Enter a simple title (say, 'a') and hit "next"
> 3. You should get a list of possible duplicates for that bug.
> 4. You should be able to interact with those dupes in the same way as you
> always do (subscribe, mark as affecting you, etc).
> 5. You should also be able to file a new bug if you need to in the same way
> as you do currently.
>
> Issues that I'm aware of at this stage:
>
> 1. The animations seem to happen at the wrong time, so slide_ins get played
> after the element has already been rendered as closed. I'm looking into this.

OK - at first I thought this was a feature :-) (as I try not to read the MP before trying out the feature).

> 2. The way that the dupes appear in the page isn't exactly pretty. I'm open
> to suggestions about how this should work.

It would be nice to see them slide in... once you have the results, add them to the dom, but hidden, and then slide out the div containing them all?

My first thought is that the phrase: 'Is "a" one of these bugs?' seems strange. Even with a proper summary, 'Is "A bunch of soyuz source pages are missing headings" one of these bugs?' seems strange. Do you know the history of that decision? My guess would be that, since the summary entered by the user *wasn't* displayed anywhere else once they had searched, it needed to be included on the page somewhere. But now with your changes here, that the summary and input box remain on the page (at least for the JS version), I think we could just say something like 'Do any of the following bugs describe the bug you are trying to report?'. What do you think? It's great that they can now still see their search and update it if desired!

Secondly, currently in your branch it seems that you cannot have the 'No, I need to report a new bug' section and one of the other potential duplicates open at the same time. For example, as a person I:

1. enter a summary and hit Next,
2. briefly read the titles, but nothing rings a bell, so I click 'No, I need to report a new bug",
3. I start adding the further information, and then realise, hey I want to check one of those bugs above, as it actually could be the same,
4. I click on the expander and zap, my new bug description disappears.

Of course, the info I entered is still there, and I'll see it again if I click again on "No, I need to report a new bug", but I don't see any reason for it disappearing in the first place? (with less justification, the same goes in reverse, closing the expanded dups when I click on 'No, I need to report a new bug.').

Third, just an implementation detail, but hitting Enter while the focus in the summary field doesn't filter - it re-loads the page, which I'm assuming is not what you want.

Thanks for resurrecting this work! It'll be a great improvement :)

review: Needs Information (ui)
Revision history for this message
Graham Binns (gmb) wrote :
Download full text (4.4 KiB)

On Thu, Dec 03, 2009 at 11:22:46AM -0000, Michael Nelson wrote:
> Review: Needs Information ui
> > This branch does the final connecting up of JS to make the
> > Asynchronous Dupefinder stuff work.
>
> This is great! I've got three points below that I think are worth
> addressing - see what you think.
>
> >
> > How to test this branch (UI):
> >
> > 1. Go to launchpad.dev/firefox/+filebug 2. Enter a simple title
> > (say, 'a') and hit "next" 3. You should get a list of possible
> > duplicates for that bug. 4. You should be able to interact with
> > those dupes in the same way as you always do (subscribe, mark as
> > affecting you, etc). 5. You should also be able to file a new bug
> > if you need to in the same way as you do currently.
> >
> > Issues that I'm aware of at this stage:
> >
> > 1. The animations seem to happen at the wrong time, so slide_ins
> > get played after the element has already been rendered as closed.
> > I'm looking into this.
>
> OK - at first I thought this was a feature :-) (as I try not to read
> the MP before trying out the feature).
>

I've tried to fix this by making the animation duration as short as
possible but that doesn't work terribly well. I think that I can fix it
using some event jiggery-pokery, and I'm still working on that (see next
comment).

>
> > 2. The way that the dupes appear in the page isn't exactly pretty.
> > I'm open to suggestions about how this should work.
>
> It would be nice to see them slide in... once you have the results,
> add them to the dom, but hidden, and then slide out the div containing
> them all?

I'm still working on getting this right. Please feel free to read the
rest of my response and I'll get back to you about this and the above
item.

> My first thought is that the phrase: 'Is "a" one of these bugs?' seems
> strange. Even with a proper summary, 'Is "A bunch of soyuz source
> pages are missing headings" one of these bugs?' seems strange. Do you
> know the history of that decision? My guess would be that, since the
> summary entered by the user *wasn't* displayed anywhere else once they
> had searched, it needed to be included on the page somewhere. But now
> with your changes here, that the summary and input box remain on the
> page (at least for the JS version), I think we could just say
> something like 'Do any of the following bugs describe the bug you are
> trying to report?'. What do you think? It's great that they can now
> still see their search and update it if desired!

Right, I agree. That text is a hold-over, so I've updated the view and
the templates to make it a bit more meaningful in an inline dupe list.
It'll remain the same should someone use the non-ajax version of the
page, though.

> Secondly, currently in your branch it seems that you cannot have the
> 'No, I need to report a new bug' section and one of the other
> potential duplicates open at the same time. For example, as a person
> I:
>
> 1. enter a summary and hit Next, 2. briefly read the titles, but
> nothing rings a bell, so I click 'No, I need to report a new bug", 3.
> I start adding the further information, and then realise, hey I want
> to check one of those bugs ab...

Read more...

Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (6.3 KiB)

> On Thu, Dec 03, 2009 at 11:22:46AM -0000, Michael Nelson wrote:
> > Review: Needs Information ui
> > > This branch does the final connecting up of JS to make the
> > > Asynchronous Dupefinder stuff work.
> >
> > This is great! I've got three points below that I think are worth
> > addressing - see what you think.
> >
> > >
> > > How to test this branch (UI):
> > >
> > > 1. Go to launchpad.dev/firefox/+filebug 2. Enter a simple title
> > > (say, 'a') and hit "next" 3. You should get a list of possible
> > > duplicates for that bug. 4. You should be able to interact with
> > > those dupes in the same way as you always do (subscribe, mark as
> > > affecting you, etc). 5. You should also be able to file a new bug
> > > if you need to in the same way as you do currently.
> > >
> > > Issues that I'm aware of at this stage:
> > >
> > > 1. The animations seem to happen at the wrong time, so slide_ins
> > > get played after the element has already been rendered as closed.
> > > I'm looking into this.
> >
> > OK - at first I thought this was a feature :-) (as I try not to read
> > the MP before trying out the feature).
> >
>
> I've tried to fix this by making the animation duration as short as
> possible but that doesn't work terribly well. I think that I can fix it
> using some event jiggery-pokery, and I'm still working on that (see next
> comment).
>

I think this is great as it is currently.

My initial comment above was related to when you initially search, and the results appear expanded and then slide in quickly. I wasn't sure why there was any animation there at all (rather than just displaying the closed items) - or whether that was the actual issue you were referring to, and if so, why it is not simple to ensure there is no animation triggered there (as the closed state should just be a css class?)

But as I said, I actually thought it was a feature (bit of bling ;) ).

> >
> > > 2. The way that the dupes appear in the page isn't exactly pretty.
> > > I'm open to suggestions about how this should work.
> >
> > It would be nice to see them slide in... once you have the results,
> > add them to the dom, but hidden, and then slide out the div containing
> > them all?
>
> I'm still working on getting this right. Please feel free to read the
> rest of my response and I'll get back to you about this and the above
> item.

Great, but don't worry if there's no time, it's just a 'would be nice' ;).

>
> > My first thought is that the phrase: 'Is "a" one of these bugs?' seems
> > strange. Even with a proper summary, 'Is "A bunch of soyuz source
> > pages are missing headings" one of these bugs?' seems strange. Do you
> > know the history of that decision? My guess would be that, since the
> > summary entered by the user *wasn't* displayed anywhere else once they
> > had searched, it needed to be included on the page somewhere. But now
> > with your changes here, that the summary and input box remain on the
> > page (at least for the JS version), I think we could just say
> > something like 'Do any of the following bugs describe the bug you are
> > trying to report?'. What do you think? It's great that they can now
> > still see their...

Read more...

review: Approve (ui)
Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (6.9 KiB)

Hi Graham,

Not a lot to say. I have a few suggestions, but that's all they
are. Looking good :)

Gavin.

> === modified file 'lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js'
> --- lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-12-08 10:57:44 +0000
> +++ lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-12-09 12:13:43 +0000
> @@ -81,13 +81,6 @@
> * @param e The Event triggering this function.
> */
> function show_bug_reporting_form(e) {
> - if (Y.Lang.isValue(bug_already_reported_expanders)) {
> - // Collapse any duplicate-details divs.
> - Y.each(bug_already_reported_expanders, function(expander) {
> - collapse_bug_details(expander);
> - });
> - }
> -
> // If the bug reporting form is in a hidden container, as it is on
> // the AJAX dupe search, show it.
> var filebug_form_container = Y.get('#filebug-form-container');

Salgado, in his YUI() -> LPS branch, changed a lot of Y.get() calls to
Y.one(). I guess that means things break non-silently when the targets
don't exist. Might be a good idea here too.

> @@ -117,8 +110,37 @@
> * display them in-line.
> */
> function search_for_and_display_dupes() {
> - function show_failure_message() {
> - Y.get('#possible-duplicates').set(INNER_HTML, 'FAIL');
> + function show_failure_message(transaction_id, response, args) {
> + // If the request failed due to a timeout, display a message
> + // explaining how the user may be able to work around it.
> + var error_message = '';
> + if (response.status == 503) {
> + // We treat 503 (service unavailable) as a timeout because
> + // that's what timeouts in LP return.
> + error_message =
> + "<p>Searching for your bug in Launchpad took too long. " +
> + "Try reducing the number of words in the summary " +
> + "field and click \"Check again\" to retry your search. " +
> + "Alternatively, you can enter the details of your bug " +
> + "below.</p>";
> + } else {
> + // Any other error code gets a generic message.
> + error_message =
> + "<p>An error occured whilst trying to find bugs matching " +
> + "the summary you entered. Click \"Check again\" to retry " +
> + "your search. Alternatively, you can enter the " +
> + "details of your bug below.</p>";
> + }
> +
> + Y.get('#possible-duplicates').set(INNER_HTML, error_message);

It works, but I think using innerHTML is bad except for a few
cases. Consider having error_message as just a message, then create a
<p> node and .set('text', error_message). Don't stress too much
though.

> +
> + Y.get('#spinner').setStyle(DISPLAY, NONE);

Consider using the "unseen" CSS class instead, and remove it to make
the spinner visible. Doesn't matter too much though. I'm sure I've
heard arguments that classes are better than manipulating style
directly, but I can't summon them right now.

> + show_bug_reporting_form();
> +
> + Y.get(Y.DOM.byId('field.t...

Read more...

review: Approve (js)
Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (5.2 KiB)

Hi,

The first part of the code review. Only a suggestion about using a
view argument to canonical_url(). Other than that, it all looks good.

Gavin.

> === modified file 'lib/lp/bugs/browser/bugtarget.py'
> --- lib/lp/bugs/browser/bugtarget.py 2009-11-26 11:32:54 +0000
> +++ lib/lp/bugs/browser/bugtarget.py 2009-12-09 12:13:43 +0000
> @@ -56,6 +56,7 @@
> from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
> from canonical.launchpad.interfaces.temporaryblobstorage import (
> ITemporaryStorageManager)
> +from canonical.launchpad.webapp import urlappend
> from canonical.launchpad.webapp.breadcrumb import Breadcrumb
> from canonical.launchpad.webapp.interfaces import ILaunchBag, NotFoundError
> from lp.bugs.interfaces.bug import (
> @@ -368,7 +369,6 @@
>
> def validate(self, data):
> """Make sure the package name, if provided, exists in the distro."""
> -
> # The comment field is only required if filing a new bug.
> if self.submit_bug_action.submitted():
> comment = data.get('comment')
> @@ -700,6 +700,26 @@
> """Override this method in base classes to show the filebug form."""
> raise NotImplementedError
>
> + @property
> + def inline_filebug_form_url(self):
> + """The URL to the inline filebug form.
> +
> + If a token was passed to this view, it will be be passed through
> + to the inline bug filing form via the returned URL.
> + """
> + url = urlappend(canonical_url(self.context), '+filebug-inline-form')

No need for urlappend here; pass view="+filebug-inline-form" to
canonical_url().

> + if self.extra_data_token is not None:
> + url = urlappend(url, self.extra_data_token)
> + return url
> +
> + @property
> + def duplicate_search_url(self):
> + """The URL to the inline duplicate search view."""
> + url = urlappend(canonical_url(self.context), '+filebug-show-similar')

Again with the view argument to canonical_url().

> + if self.extra_data_token is not None:
> + url = urlappend(url, self.extra_data_token)
> + return url
> +
> def publishTraverse(self, request, name):
> """See IBrowserPublisher."""
> if self.extra_data_token is not None:
> @@ -831,6 +851,11 @@
> return guidelines
>
>
> +class FileBugInlineFormView(FileBugViewBase):
> + """A browser view for displaying the inline filebug form."""
> + schema = IBugAddForm
> +
> +
> class FileBugAdvancedView(FileBugViewBase):
> """Browser view for filing a bug.
>
> @@ -851,7 +876,14 @@
> """
> schema = IBugAddForm
>
> + # XXX: Brad Bollenbach 2006-10-04: This assignment to actions is a
> + # hack to make the action decorator Just Work across inheritance.
> + actions = FileBugViewBase.actions
> + custom_widget('title', TextWidget, displayWidth=40)
> + custom_widget('tags', BugTagsWidget)
> +
> _MATCHING_BUGS_LIMIT = 10
> + show_summary_in_results = False
>
> @property
> def search_context(self):
> @@ -894,22 +926,37 @@
>
> return matching_bugs
>
> + @property
> + def show_duplicate_list(self)...

Read more...

review: Abstain (code)
Revision history for this message
Graham Binns (gmb) wrote :
Download full text (7.4 KiB)

> > === modified file 'lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js'
> > --- lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-12-08 10:57:44 +0000
> > +++ lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-12-09 12:13:43 +0000
> > @@ -81,13 +81,6 @@
> > * @param e The Event triggering this function.
> > */
> > function show_bug_reporting_form(e) {
> > - if (Y.Lang.isValue(bug_already_reported_expanders)) {
> > - // Collapse any duplicate-details divs.
> > - Y.each(bug_already_reported_expanders, function(expander) {
> > - collapse_bug_details(expander);
> > - });
> > - }
> > -
> > // If the bug reporting form is in a hidden container, as it is on
> > // the AJAX dupe search, show it.
> > var filebug_form_container = Y.get('#filebug-form-container');
>
> Salgado, in his YUI() -> LPS branch, changed a lot of Y.get() calls to
> Y.one(). I guess that means things break non-silently when the targets
> don't exist. Might be a good idea here too.
>

Right. Fixed.

> > @@ -117,8 +110,37 @@
> > * display them in-line.
> > */
> > function search_for_and_display_dupes() {
> > - function show_failure_message() {
> > - Y.get('#possible-duplicates').set(INNER_HTML, 'FAIL');
> > + function show_failure_message(transaction_id, response, args) {
> > + // If the request failed due to a timeout, display a message
> > + // explaining how the user may be able to work around it.
> > + var error_message = '';
> > + if (response.status == 503) {
> > + // We treat 503 (service unavailable) as a timeout because
> > + // that's what timeouts in LP return.
> > + error_message =
> > + "<p>Searching for your bug in Launchpad took too long. " +
> > + "Try reducing the number of words in the summary " +
> > + "field and click \"Check again\" to retry your search. " +
> > + "Alternatively, you can enter the details of your bug " +
> > + "below.</p>";
> > + } else {
> > + // Any other error code gets a generic message.
> > + error_message =
> > + "<p>An error occured whilst trying to find bugs matching " +
> > + "the summary you entered. Click \"Check again\" to retry " +
> > + "your search. Alternatively, you can enter the " +
> > + "details of your bug below.</p>";
> > + }
> > +
> > + Y.get('#possible-duplicates').set(INNER_HTML, error_message);
>
> It works, but I think using innerHTML is bad except for a few
> cases. Consider having error_message as just a message, then create a
> <p> node and .set('text', error_message). Don't stress too much
> though.
>

I agree. Fixed.

> > +
> > + Y.get('#spinner').setStyle(DISPLAY, NONE);
>
> Consider using the "unseen" CSS class instead, and remove it to make
> the spinner visible. Doesn't matter too much though. I'm sure I've
> heard arguments that classes are better than manipulating style
> directly, but I can't summon them right now.
> o

I've heard similar ...

Read more...

Revision history for this message
Graham Binns (gmb) wrote :

> > === modified file 'lib/lp/bugs/browser/bugtarget.py'
> > --- lib/lp/bugs/browser/bugtarget.py 2009-11-26 11:32:54 +0000
> > +++ lib/lp/bugs/browser/bugtarget.py 2009-12-09 12:13:43 +0000
> > @@ -56,6 +56,7 @@
> > from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
> > from canonical.launchpad.interfaces.temporaryblobstorage import (
> > ITemporaryStorageManager)
> > +from canonical.launchpad.webapp import urlappend
> > from canonical.launchpad.webapp.breadcrumb import Breadcrumb
> > from canonical.launchpad.webapp.interfaces import ILaunchBag, NotFoundError
> > from lp.bugs.interfaces.bug import (
> > @@ -368,7 +369,6 @@
> >
> > def validate(self, data):
> > """Make sure the package name, if provided, exists in the distro."""
> > -
> > # The comment field is only required if filing a new bug.
> > if self.submit_bug_action.submitted():
> > comment = data.get('comment')
> > @@ -700,6 +700,26 @@
> > """Override this method in base classes to show the filebug form."""
> > raise NotImplementedError
> >
> > + @property
> > + def inline_filebug_form_url(self):
> > + """The URL to the inline filebug form.
> > +
> > + If a token was passed to this view, it will be be passed through
> > + to the inline bug filing form via the returned URL.
> > + """
> > + url = urlappend(canonical_url(self.context), '+filebug-inline-form')
>
> No need for urlappend here; pass view="+filebug-inline-form" to
> canonical_url().
>

Right. No idea what I was smoking when I wrote that.

> > + if self.extra_data_token is not None:
> > + url = urlappend(url, self.extra_data_token)
> > + return url
> > +
> > + @property
> > + def duplicate_search_url(self):
> > + """The URL to the inline duplicate search view."""
> > + url = urlappend(canonical_url(self.context), '+filebug-show-similar')
>
> Again with the view argument to canonical_url().
>

Yep.

Revision history for this message
Graham Binns (gmb) wrote :
Download full text (5.8 KiB)

Oops. Incremental diff for the first two parts of the code / js reviews:

=== modified file 'lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js'
--- lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-12-09 13:05:28 +0000
+++ lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-12-09 13:20:27 +0000
@@ -16,7 +16,8 @@
     INNER_HTML = 'innerHTML',
     LAZR_CLOSED = 'lazr-closed',
     NONE = 'none',
- SRC = 'src';
+ SRC = 'src',
+ UNSEEN = 'unseen';

 var bugs = Y.namespace('bugs');

@@ -118,34 +119,36 @@
             // We treat 503 (service unavailable) as a timeout because
             // that's what timeouts in LP return.
             error_message =
- "<p>Searching for your bug in Launchpad took too long. " +
+ "Searching for your bug in Launchpad took too long. " +
                 "Try reducing the number of words in the summary " +
                 "field and click \"Check again\" to retry your search. " +
                 "Alternatively, you can enter the details of your bug " +
- "below.</p>";
+ "below.";
         } else {
             // Any other error code gets a generic message.
             error_message =
- "<p>An error occured whilst trying to find bugs matching " +
+ "An error occured whilst trying to find bugs matching " +
                 "the summary you entered. Click \"Check again\" to retry " +
                 "your search. Alternatively, you can enter the " +
- "details of your bug below.</p>";
+ "details of your bug below.";
         }

- Y.one('#possible-duplicates').set(INNER_HTML, error_message);
+ var error_node = Y.Node.create('<p></p>');
+ error_node.set('text', error_message);
+ Y.one('#possible-duplicates').appendChild(error_node);

- Y.one('#spinner').setStyle(DISPLAY, NONE);
+ Y.one('#spinner').addClass(UNSEEN);
         show_bug_reporting_form();

         Y.one(Y.DOM.byId('field.title')).set(
             'value', search_field.get('value'));
         search_button.set('value', 'Check again');
- search_button.setStyle(DISPLAY, INLINE);
+ search_button.removeClass(UNSEEN);
     }

     function on_success(transaction_id, response, args) {
         // Hide the spinner and show the duplicates.
- Y.one('#spinner').setStyle(DISPLAY, NONE);
+ Y.one('#spinner').addClass(UNSEEN);

         var duplicate_div = Y.one('#possible-duplicates');
         duplicate_div.set(INNER_HTML, response.responseText);
@@ -163,13 +166,13 @@

         // Copy the value from the search field into the title field
         // on the filebug form.
- Y.one(Y.DOM.byId('field.title')).set(
+ Y.one('#bug_reporting_form input[name=field.title]').set(
             'value', search_field.get('value'));

         // Finally, change the label on the search button and show it
         // again.
         search_button.set('value', 'Check again');
- search_button.setStyle(DISPLAY, INLINE);
+ search_button.removeClass(UNSEEN);
     }

     var search_term = encodeURI(search_fi...

Read more...

Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (16.8 KiB)

The rest of the code review. A few minor things, but nothing serious. Rock on :) Get this landed, people want to buy you beer :)

> === modified file 'lib/lp/bugs/browser/configure.zcml'
> --- lib/lp/bugs/browser/configure.zcml 2009-12-09 12:13:11 +0000
> +++ lib/lp/bugs/browser/configure.zcml 2009-12-09 12:13:43 +0000
> @@ -95,6 +95,13 @@
> facet="bugs"
> permission="launchpad.AnyPerson"/>
> <browser:page
> + name="+filebug-inline-form"
> + for="lp.bugs.interfaces.bugtarget.IBugTarget"
> + class="lp.bugs.browser.bugtarget.FilebugShowSimilarBugsView"
> + template="../templates/bugtarget-filebug-inline-form.pt"
> + facet="bugs"
> + permission="launchpad.AnyPerson"/>
> + <browser:page
> name="+manage-official-tags"
> for="lp.bugs.interfaces.bugtarget.IOfficialBugTagTargetRestricted"
> class="lp.bugs.browser.bugtarget.OfficialBugTagsManageView"
>
> === modified file 'lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt'
> --- lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt 2009-11-26 09:18:22 +0000
> +++ lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt 2009-12-09 12:13:43 +0000
> @@ -43,6 +43,25 @@
> u'Test description.'
>
>
> +== URLs to additional FileBug elements ==
> +
> +FileBugViewBase provides properties that return the URLs of further
> +useful parts of the +filebug process.
> +
> +The inline_filebug_form_url property returns the URL of the inline
> +filebug form so that it may be loaded asynchronously.
> +
> + >>> print filebug_view.inline_filebug_form_url
> + http://launchpad.dev/ubuntu/+source/mozilla-firefox/+filebug-inline-form
> +
> +Similarly, the duplicate_search_url property returns the base URL for
> +the duplicate search view, which can be used to load the list of
> +possible duplicates for a bug asynchronously.
> +
> + >>> print filebug_view.duplicate_search_url
> + http://launchpad.dev/ubuntu/+source/mozilla-firefox/+filebug-show-similar
> +
> +
> == Adding extra info to filed bugs ==
>
> It's possible for bug reporting tools to upload a file with debug
>
> === modified file 'lib/lp/bugs/interfaces/bug.py'
> --- lib/lp/bugs/interfaces/bug.py 2009-11-26 09:18:22 +0000
> +++ lib/lp/bugs/interfaces/bug.py 2009-12-09 12:13:43 +0000
> @@ -26,6 +26,7 @@
> from zope.interface import Interface, Attribute
> from zope.schema import (
> Bool, Bytes, Choice, Datetime, Int, List, Object, Text, TextLine)
> +from zope.schema.vocabulary import SimpleVocabulary
> from zope.security.interfaces import Unauthorized
>
> from canonical.launchpad import _
> @@ -789,6 +790,11 @@
> "A sequence of IBugTaskDeltas, one IBugTaskDelta or None.")
>
>
> +# A simple vocabulary for the subscribe_to_existing_bug form field.
> +SUBSCRIBE_TO_BUG_VOCABULARY = SimpleVocabulary.fromItems(
> + [('yes', True), ('no', False)])
> +
> +
> class IBugAddForm(IBug):
> """Information we need to create a bug"""
> id = Int(title=_("Bug #"), required=False)
> @@ -842,8 +848,9 @@
> assignee = PublicPersonChoice(
> title=_('Assign to'), required=False,
> ...

review: Approve (code)
Revision history for this message
Graham Binns (gmb) wrote :

> > > + <td colspan="2">
> > > + <p>
> > > + Please describe the bug in a few words, for example,
> > "weather
> > > + applet crashes on logout":
> > > + </p>
> >
> > I don't think the <p></p> is needed here (i.e. <td>s can have contain
> > inline content)... unless it affects layout.
> >

It does. The paragraphing is deliberate.

> > > + </td>
> > > + </tr>
> > > + <tal:title_widget tal:define="widget nocall:view/widgets/title">
> > > + <tal:comment replace="nothing">
> > > + The desire to have more control over the styling of this
> > widget
> > > + prevents us from using the widget_row macro here.
> > > + </tal:comment>
> > > + <tr>
> > > + <td tal:define="field_name widget/context/__name__;
> > > + error python:view.getFieldError(field_name);
> > > + error_class python:error and 'error' or
> > None;"
> >
> > I think you can say this now:
> >
> > ('error' if error else None)
> >
> > (Woohoo!)

Indeed. Fixed.

> > > + <p>
> > > + <a id="filebug-form-url"
> > > + tal:attributes="href view/inline_filebug_form_url"
> > > + style="display: none;"></a>
> > > + <a id="duplicate-search-url"
> > > + tal:attributes="href view/duplicate_search_url"
> > > + style="display: none;"></a>
> > > + </p>
> >
> > Maybe it's simpler to put the display:none on the <p>? I think empty
> > paragraphs still influence layout.

Fixed.

Revision history for this message
Gavin Panella (allenap) :
review: Approve (code js)
Revision history for this message
Edwin Grubbs (edwin-grubbs) :
review: Abstain (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js'
--- lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-12-09 17:25:34 +0000
+++ lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-12-09 17:25:37 +0000
@@ -16,7 +16,8 @@
16 INNER_HTML = 'innerHTML',16 INNER_HTML = 'innerHTML',
17 LAZR_CLOSED = 'lazr-closed',17 LAZR_CLOSED = 'lazr-closed',
18 NONE = 'none',18 NONE = 'none',
19 SRC = 'src';19 SRC = 'src',
20 UNSEEN = 'unseen';
2021
21var bugs = Y.namespace('bugs');22var bugs = Y.namespace('bugs');
2223
@@ -81,32 +82,25 @@
81 * @param e The Event triggering this function.82 * @param e The Event triggering this function.
82 */83 */
83function show_bug_reporting_form(e) {84function show_bug_reporting_form(e) {
84 if (Y.Lang.isValue(bug_already_reported_expanders)) {
85 // Collapse any duplicate-details divs.
86 Y.each(bug_already_reported_expanders, function(expander) {
87 collapse_bug_details(expander);
88 });
89 }
90
91 // If the bug reporting form is in a hidden container, as it is on85 // If the bug reporting form is in a hidden container, as it is on
92 // the AJAX dupe search, show it.86 // the AJAX dupe search, show it.
93 var filebug_form_container = Y.get('#filebug-form-container');87 var filebug_form_container = Y.one('#filebug-form-container');
94 if (Y.Lang.isValue(filebug_form_container)) {88 if (Y.Lang.isValue(filebug_form_container)) {
95 filebug_form_container.setStyle(DISPLAY, BLOCK);89 filebug_form_container.setStyle(DISPLAY, BLOCK);
96 }90 }
9791
98 // Show the bug reporting form.92 // Show the bug reporting form.
99 var bug_reporting_form = Y.get('#bug_reporting_form');93 var bug_reporting_form = Y.one('#bug_reporting_form');
100 bug_reporting_form.setStyle(DISPLAY, BLOCK);94 bug_reporting_form.setStyle(DISPLAY, BLOCK);
10195
102 Y.get(Y.DOM.byId('field.actions.submit_bug')).focus();96 Y.one(Y.DOM.byId('field.actions.submit_bug')).focus();
10397
104 // Focus the relevant elements of the form based on98 // Focus the relevant elements of the form based on
105 // whether the package drop-down is displayed.99 // whether the package drop-down is displayed.
106 var bugtarget_package_btn = Y.one(100 var bugtarget_package_btn = Y.one(
107 Y.DOM.byId('field.bugtarget.option.package'));101 Y.DOM.byId('field.bugtarget.option.package'));
108 if (Y.Lang.isValue(bugtarget_package_btn)) {102 if (Y.Lang.isValue(bugtarget_package_btn)) {
109 Y.get(Y.DOM.byId('field.bugtarget.package')).focus();103 Y.one(Y.DOM.byId('field.bugtarget.package')).focus();
110 } else {104 } else {
111 Y.one(Y.DOM.byId('field.comment')).focus();105 Y.one(Y.DOM.byId('field.comment')).focus();
112 }106 }
@@ -117,56 +111,80 @@
117 * display them in-line.111 * display them in-line.
118 */112 */
119function search_for_and_display_dupes() {113function search_for_and_display_dupes() {
120 function show_failure_message() {114 function show_failure_message(transaction_id, response, args) {
121 Y.get('#possible-duplicates').set(INNER_HTML, 'FAIL');115 // If the request failed due to a timeout, display a message
116 // explaining how the user may be able to work around it.
117 var error_message = '';
118 if (response.status == 503) {
119 // We treat 503 (service unavailable) as a timeout because
120 // that's what timeouts in LP return.
121 error_message =
122 "Searching for your bug in Launchpad took too long. " +
123 "Try reducing the number of words in the summary " +
124 "field and click \"Check again\" to retry your search. " +
125 "Alternatively, you can enter the details of your bug " +
126 "below.";
127 } else {
128 // Any other error code gets a generic message.
129 error_message =
130 "An error occured whilst trying to find bugs matching " +
131 "the summary you entered. Click \"Check again\" to retry " +
132 "your search. Alternatively, you can enter the " +
133 "details of your bug below.";
134 }
135
136 var error_node = Y.Node.create('<p></p>');
137 error_node.set('text', error_message);
138 Y.one('#possible-duplicates').appendChild(error_node);
139
140 Y.one('#spinner').addClass(UNSEEN);
141 show_bug_reporting_form();
142
143 Y.one(Y.DOM.byId('field.title')).set(
144 'value', search_field.get('value'));
145 search_button.set('value', 'Check again');
146 search_button.removeClass(UNSEEN);
122 }147 }
123148
124 function on_success(transaction_id, response, args) {149 function on_success(transaction_id, response, args) {
125 // Hide the spinner and show the duplicates.150 // Hide the spinner and show the duplicates.
126 Y.get('#spinner').setStyle(DISPLAY, NONE);151 Y.one('#spinner').addClass(UNSEEN);
127152
128 var duplicate_div = Y.get('#possible-duplicates');153 var duplicate_div = Y.one('#possible-duplicates');
129 duplicate_div.set(INNER_HTML, response.responseText);154 duplicate_div.set(INNER_HTML, response.responseText);
130155
131 bug_already_reported_expanders = Y.all(156 bug_already_reported_expanders = Y.all(
132 'img.bug-already-reported-expander');157 'img.bug-already-reported-expander');
133 if (Y.Lang.isValue(bug_already_reported_expanders)) {158 if (Y.Lang.isValue(bug_already_reported_expanders)) {
134 // If there are duplicates shown, change the title of the page159 // If there are duplicates shown, set up the JavaScript of
135 // and set up the JavaScript of the duplicates that have been160 // the duplicates that have been returned.
136 // returned.
137 Y.bugs.setup_dupes();161 Y.bugs.setup_dupes();
138 Y.get('#page-title').set(
139 INNER_HTML,
140 'Is the bug you&rsquo;re reporting one of these?');
141 } else {162 } else {
142 // Otherwise, set the title to one that doesn't suggest163 // Otherwise, show the bug reporting form.
143 // there were dupes returned and show the bug reporting
144 // form.
145 Y.get('#page-title').set(INNER_HTML, 'Report a bug');
146 show_bug_reporting_form();164 show_bug_reporting_form();
147 }165 }
148166
149 // Copy the value from the search field into the title field167 // Copy the value from the search field into the title field
150 // on the filebug form.168 // on the filebug form.
151 Y.get(Y.DOM.byId('field.title')).set(169 Y.one('#bug_reporting_form input[name=field.title]').set(
152 'value', search_field.get('value'));170 'value', search_field.get('value'));
153171
154 // Finally, change the label on the search button and show it172 // Finally, change the label on the search button and show it
155 // again.173 // again.
156 search_button.set('value', 'Check again');174 search_button.set('value', 'Check again');
157 search_button.setStyle(DISPLAY, INLINE);175 search_button.removeClass(UNSEEN);
158 }176 }
159177
160 var search_term = encodeURI(search_field.get('value'));178 var search_term = encodeURI(search_field.get('value'));
161 var search_url_base = Y.get(179 var search_url_base = Y.one(
162 '#duplicate-search-url').getAttribute('href');180 '#duplicate-search-url').getAttribute('href');
163 var search_url = search_url_base + '?title=' + search_term;181 var search_url = search_url_base + '?title=' + search_term;
164182
165 // Hide the button, show the spinner and clear the contents of the183 // Hide the button, show the spinner and clear the contents of the
166 // possible duplicates div.184 // possible duplicates div.
167 search_button.setStyle(DISPLAY, NONE);185 search_button.addClass(UNSEEN);
168 Y.get('#spinner').setStyle(DISPLAY, INLINE);186 Y.one('#spinner').removeClass(UNSEEN);
169 Y.get('#possible-duplicates').set(INNER_HTML, '');187 Y.one('#possible-duplicates').set(INNER_HTML, '');
170188
171 config = {on: {success: on_success,189 config = {on: {success: on_success,
172 failure: show_failure_message}};190 failure: show_failure_message}};
@@ -183,7 +201,7 @@
183 // Grab the bug id and title from the "Yes, this is my bug" form.201 // Grab the bug id and title from the "Yes, this is my bug" form.
184 var bug_id = form.one(202 var bug_id = form.one(
185 'input.bug-already-reported-as').get('value');203 'input.bug-already-reported-as').get('value');
186 var bug_title = Y.get('#bug-' + bug_id + '-title').get(INNER_HTML);204 var bug_title = Y.one('#bug-' + bug_id + '-title').get(INNER_HTML);
187205
188 if (bug_title.length > 35) {206 if (bug_title.length > 35) {
189 // Truncate the bug title if it's more than 35 characters long.207 // Truncate the bug title if it's more than 35 characters long.
@@ -242,7 +260,7 @@
242 var weight = radio_button.get('checked') ? 'bold' : 'normal';260 var weight = radio_button.get('checked') ? 'bold' : 'normal';
243 radio_button.get('parentNode').setStyle('fontWeight', weight);261 radio_button.get('parentNode').setStyle('fontWeight', weight);
244 });262 });
245 });263 }
246264
247 return subscribe_form_overlay;265 return subscribe_form_overlay;
248}266}
@@ -253,42 +271,42 @@
253 * +filebug search form.271 * +filebug search form.
254 */272 */
255function set_up_dupe_finder(transaction_id, response, args) {273function set_up_dupe_finder(transaction_id, response, args) {
256 var filebug_form_container = Y.get('#filebug-form-container');274 var filebug_form_container = Y.one('#filebug-form-container');
257 filebug_form_container.set(INNER_HTML, response.responseText);275 filebug_form_container.set(INNER_HTML, response.responseText);
258276
259 // Activate the extra options collapsible section on the bug277 // Activate the extra options collapsible section on the bug
260 // reporting form.278 // reporting form.
261 var bug_reporting_form = Y.get('#bug_reporting_form');279 var bug_reporting_form = Y.one('#bug_reporting_form');
262 if (Y.Lang.isValue(bug_reporting_form)) {280 if (Y.Lang.isValue(bug_reporting_form)) {
263 activateCollapsibles();281 activateCollapsibles();
264 }282 }
265283
266 search_button = Y.get(Y.DOM.byId('field.actions.search'));284 search_button = Y.one(Y.DOM.byId('field.actions.search'));
267285
268 // Change the name and id of the search field so that it doesn't286 // Change the name and id of the search field so that it doesn't
269 // confuse the view when we submit a bug report.287 // confuse the view when we submit a bug report.
270 search_field = Y.get(Y.DOM.byId('field.title'));288 search_field = Y.one(Y.DOM.byId('field.title'));
271 search_field.set('name', 'field.search');289 search_field.set('name', 'field.search');
272 search_field.set('id', 'field.search');290 search_field.set('id', 'field.search');
273291
274 // Disable the form so that hitting "enter" in the Summary
275 // field no longer sends us through to the next page.
276 // Y.on('submit', function(e) { e.halt(); }, '#my-form')
277
278 // Update the label on the search button so that it no longer292 // Update the label on the search button so that it no longer
279 // says "Continue".293 // says "Continue".
280 search_button.set('value', 'Next');294 search_button.set('value', 'Next');
281 search_button.set('type', 'button');
282295
283 // Set up the handlers for the search button and the input296 // Set up the handler for the search form.
284 // field.297 search_form = Y.one('#filebug-search-form');
285 search_button.on('click', search_for_and_display_dupes);298 search_form.on('submit', function(e) {
299 // Prevent the event from propagating; we don't want to reload
300 // the page.
301 e.halt();
302 search_for_and_display_dupes();
303 });
286}304}
287305
288Y.bugs.setup_dupes = function() {306Y.bugs.setup_dupes = function() {
289 bug_already_reported_expanders = Y.all(307 bug_already_reported_expanders = Y.all(
290 'img.bug-already-reported-expander');308 'img.bug-already-reported-expander');
291 bug_reporting_form = Y.get('#bug_reporting_form');309 bug_reporting_form = Y.one('#bug_reporting_form');
292310
293 if (Y.Lang.isValue(bug_already_reported_expanders)) {311 if (Y.Lang.isValue(bug_already_reported_expanders)) {
294 // Collapse all the details divs, since we don't want them312 // Collapse all the details divs, since we don't want them
@@ -338,7 +356,7 @@
338356
339 // If the bug reporting form is shown, hide it.357 // If the bug reporting form is shown, hide it.
340 if (bug_reporting_form.getStyle(DISPLAY) == BLOCK) {358 if (bug_reporting_form.getStyle(DISPLAY) == BLOCK) {
341 bug_reporting_form.setStyle(DISPLAY, NONE);359 bug_reporting_form.addClass(UNSEEN);
342 }360 }
343 }361 }
344 });362 });
@@ -352,19 +370,14 @@
352370
353 image.set(SRC, EXPANDER_EXPANDED);371 image.set(SRC, EXPANDER_EXPANDED);
354 }372 }
355
356 // If the bug reporting form is shown, hide it.
357 if (bug_reporting_form.getStyle(DISPLAY) == BLOCK) {
358 bug_reporting_form.setStyle(DISPLAY, NONE);
359 }
360 });373 });
361 });374 });
362375
363 // Hide the bug reporting form.376 // Hide the bug reporting form.
364 bug_reporting_form.setStyle(DISPLAY, NONE);377 bug_reporting_form.addClass(UNSEEN);
365 }378 }
366379
367 bug_not_reported_button = Y.get('#bug-not-already-reported');380 bug_not_reported_button = Y.one('#bug-not-already-reported');
368 if (Y.Lang.isValue(bug_not_reported_button)) {381 if (Y.Lang.isValue(bug_not_reported_button)) {
369 // The bug_not_reported_button won't show up if there aren't any382 // The bug_not_reported_button won't show up if there aren't any
370 // possible duplicates.383 // possible duplicates.
@@ -386,6 +399,7 @@
386};399};
387400
388Y.bugs.setup_dupe_finder = function() {401Y.bugs.setup_dupe_finder = function() {
402 Y.log("In setup_dupe_finder");
389 Y.on('domready', function() {403 Y.on('domready', function() {
390 config = {on: {success: set_up_dupe_finder,404 config = {on: {success: set_up_dupe_finder,
391 failure: function() {}}};405 failure: function() {}}};
@@ -393,7 +407,7 @@
393 // Load the filebug form asynchronously. If this fails we407 // Load the filebug form asynchronously. If this fails we
394 // degrade to the standard mode for bug filing, clicking through408 // degrade to the standard mode for bug filing, clicking through
395 // to the second part of the bug filing form.409 // to the second part of the bug filing form.
396 var filebug_form_url_element = Y.get(410 var filebug_form_url_element = Y.one(
397 '#filebug-form-url');411 '#filebug-form-url');
398 if (Y.Lang.isValue(filebug_form_url_element)) {412 if (Y.Lang.isValue(filebug_form_url_element)) {
399 var filebug_form_url = filebug_form_url_element.getAttribute(413 var filebug_form_url = filebug_form_url_element.getAttribute(
@@ -404,4 +418,4 @@
404};418};
405419
406}, "0.1", {"requires": [420}, "0.1", {"requires": [
407 "base", "oop", "node", "event", "lazr.formoverlay", "lazr.effects"]});421 "base", "io", "oop", "node", "event", "lazr.formoverlay", "lazr.effects"]});
408422
=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py 2009-11-19 13:03:51 +0000
+++ lib/lp/bugs/browser/bugtarget.py 2009-12-09 17:25:37 +0000
@@ -56,6 +56,7 @@
56from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities56from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
57from canonical.launchpad.interfaces.temporaryblobstorage import (57from canonical.launchpad.interfaces.temporaryblobstorage import (
58 ITemporaryStorageManager)58 ITemporaryStorageManager)
59from canonical.launchpad.webapp import urlappend
59from canonical.launchpad.webapp.breadcrumb import Breadcrumb60from canonical.launchpad.webapp.breadcrumb import Breadcrumb
60from canonical.launchpad.webapp.interfaces import ILaunchBag, NotFoundError61from canonical.launchpad.webapp.interfaces import ILaunchBag, NotFoundError
61from lp.bugs.interfaces.bug import (62from lp.bugs.interfaces.bug import (
@@ -359,6 +360,11 @@
359 if 'field.actions' in key] != [] or360 if 'field.actions' in key] != [] or
360 self.user.inTeam(bug_supervisor))361 self.user.inTeam(bug_supervisor))
361362
363 @property
364 def use_asynchronous_dupefinder(self):
365 """Return True if the asynchronous dupe finder can be used."""
366 return IProduct.providedBy(self.context)
367
362 def getPackageNameFieldCSSClass(self):368 def getPackageNameFieldCSSClass(self):
363 """Return the CSS class for the packagename field."""369 """Return the CSS class for the packagename field."""
364 if self.widget_errors.get("packagename"):370 if self.widget_errors.get("packagename"):
@@ -368,7 +374,6 @@
368374
369 def validate(self, data):375 def validate(self, data):
370 """Make sure the package name, if provided, exists in the distro."""376 """Make sure the package name, if provided, exists in the distro."""
371
372 # The comment field is only required if filing a new bug.377 # The comment field is only required if filing a new bug.
373 if self.submit_bug_action.submitted():378 if self.submit_bug_action.submitted():
374 comment = data.get('comment')379 comment = data.get('comment')
@@ -700,6 +705,26 @@
700 """Override this method in base classes to show the filebug form."""705 """Override this method in base classes to show the filebug form."""
701 raise NotImplementedError706 raise NotImplementedError
702707
708 @property
709 def inline_filebug_form_url(self):
710 """The URL to the inline filebug form.
711
712 If a token was passed to this view, it will be be passed through
713 to the inline bug filing form via the returned URL.
714 """
715 url = canonical_url(self.context, view_name='+filebug-inline-form')
716 if self.extra_data_token is not None:
717 url = urlappend(url, self.extra_data_token)
718 return url
719
720 @property
721 def duplicate_search_url(self):
722 """The URL to the inline duplicate search view."""
723 url = canonical_url(self.context, view_name='+filebug-show-similar')
724 if self.extra_data_token is not None:
725 url = urlappend(url, self.extra_data_token)
726 return url
727
703 def publishTraverse(self, request, name):728 def publishTraverse(self, request, name):
704 """See IBrowserPublisher."""729 """See IBrowserPublisher."""
705 if self.extra_data_token is not None:730 if self.extra_data_token is not None:
@@ -831,6 +856,11 @@
831 return guidelines856 return guidelines
832857
833858
859class FileBugInlineFormView(FileBugViewBase):
860 """A browser view for displaying the inline filebug form."""
861 schema = IBugAddForm
862
863
834class FileBugAdvancedView(FileBugViewBase):864class FileBugAdvancedView(FileBugViewBase):
835 """Browser view for filing a bug.865 """Browser view for filing a bug.
836866
@@ -851,7 +881,14 @@
851 """881 """
852 schema = IBugAddForm882 schema = IBugAddForm
853883
884 # XXX: Brad Bollenbach 2006-10-04: This assignment to actions is a
885 # hack to make the action decorator Just Work across inheritance.
886 actions = FileBugViewBase.actions
887 custom_widget('title', TextWidget, displayWidth=40)
888 custom_widget('tags', BugTagsWidget)
889
854 _MATCHING_BUGS_LIMIT = 10890 _MATCHING_BUGS_LIMIT = 10
891 show_summary_in_results = False
855892
856 @property893 @property
857 def search_context(self):894 def search_context(self):
@@ -894,22 +931,37 @@
894931
895 return matching_bugs932 return matching_bugs
896933
934 @property
935 def show_duplicate_list(self):
936 """Return whether or not to show the duplicate list.
937
938 We only show the dupes if:
939 - The context uses Malone AND
940 - There are dupes to show AND
941 - There are no widget errors.
942 """
943 return (
944 self.contextUsesMalone and
945 len(self.similar_bugs) > 0 and
946 len(self.widget_errors) == 0)
947
897948
898class FileBugGuidedView(FilebugShowSimilarBugsView):949class FileBugGuidedView(FilebugShowSimilarBugsView):
899 # XXX: Brad Bollenbach 2006-10-04: This assignment to actions is a
900 # hack to make the action decorator Just Work across inheritance.
901 actions = FileBugViewBase.actions
902 custom_widget('title', TextWidget, displayWidth=65)
903 custom_widget('tags', BugTagsWidget)
904950
905 _SEARCH_FOR_DUPES = ViewPageTemplateFile(951 _SEARCH_FOR_DUPES = ViewPageTemplateFile(
906 "../templates/bugtarget-filebug-search.pt")952 "../templates/bugtarget-filebug-search.pt")
907 _FILEBUG_FORM = ViewPageTemplateFile(953 _FILEBUG_FORM = ViewPageTemplateFile(
908 "../templates/bugtarget-filebug-submit-bug.pt")954 "../templates/bugtarget-filebug-submit-bug.pt")
909955
956 # XXX 2009-07-17 Graham Binns
957 # As above, this assignment to actions is to make sure that the
958 # actions from the ancestor views are preserved, otherwise they
959 # get nuked.
960 actions = FilebugShowSimilarBugsView.actions
910 template = _SEARCH_FOR_DUPES961 template = _SEARCH_FOR_DUPES
911962
912 focused_element_id = 'field.title'963 focused_element_id = 'field.title'
964 show_summary_in_results = True
913965
914 @safe_action966 @safe_action
915 @action("Continue", name="search", validator="validate_search")967 @action("Continue", name="search", validator="validate_search")
@@ -948,20 +1000,6 @@
948 except InputErrors:1000 except InputErrors:
949 return None1001 return None
9501002
951 @property
952 def show_duplicate_list(self):
953 """Return whether or not to show the duplicate list.
954
955 We only show the dupes if:
956 - The context uses Malone AND
957 - There are dupes to show AND
958 - There are no widget errors.
959 """
960 return (
961 self.contextUsesMalone and
962 len(self.similar_bugs) > 0 and
963 len(self.widget_errors) == 0)
964
965 def validate_search(self, action, data):1003 def validate_search(self, action, data):
966 """Make sure some keywords are provided."""1004 """Make sure some keywords are provided."""
967 try:1005 try:
9681006
=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml 2009-11-26 17:28:13 +0000
+++ lib/lp/bugs/browser/configure.zcml 2009-12-09 17:25:37 +0000
@@ -95,6 +95,13 @@
95 facet="bugs"95 facet="bugs"
96 permission="launchpad.AnyPerson"/>96 permission="launchpad.AnyPerson"/>
97 <browser:page97 <browser:page
98 name="+filebug-inline-form"
99 for="lp.bugs.interfaces.bugtarget.IBugTarget"
100 class="lp.bugs.browser.bugtarget.FilebugShowSimilarBugsView"
101 template="../templates/bugtarget-filebug-inline-form.pt"
102 facet="bugs"
103 permission="launchpad.AnyPerson"/>
104 <browser:page
98 name="+manage-official-tags"105 name="+manage-official-tags"
99 for="lp.bugs.interfaces.bugtarget.IOfficialBugTagTargetRestricted"106 for="lp.bugs.interfaces.bugtarget.IOfficialBugTagTargetRestricted"
100 class="lp.bugs.browser.bugtarget.OfficialBugTagsManageView"107 class="lp.bugs.browser.bugtarget.OfficialBugTagsManageView"
101108
=== modified file 'lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt'
--- lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt 2009-08-13 15:12:16 +0000
+++ lib/lp/bugs/browser/tests/bugtarget-filebug-views.txt 2009-12-09 17:25:37 +0000
@@ -43,6 +43,25 @@
43 u'Test description.'43 u'Test description.'
4444
4545
46== URLs to additional FileBug elements ==
47
48FileBugViewBase provides properties that return the URLs of further
49useful parts of the +filebug process.
50
51The inline_filebug_form_url property returns the URL of the inline
52filebug form so that it may be loaded asynchronously.
53
54 >>> print filebug_view.inline_filebug_form_url
55 http://launchpad.dev/ubuntu/+source/mozilla-firefox/+filebug-inline-form
56
57Similarly, the duplicate_search_url property returns the base URL for
58the duplicate search view, which can be used to load the list of
59possible duplicates for a bug asynchronously.
60
61 >>> print filebug_view.duplicate_search_url
62 http://launchpad.dev/ubuntu/+source/mozilla-firefox/+filebug-show-similar
63
64
46== Adding extra info to filed bugs ==65== Adding extra info to filed bugs ==
4766
48It's possible for bug reporting tools to upload a file with debug67It's possible for bug reporting tools to upload a file with debug
4968
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2009-11-25 23:09:12 +0000
+++ lib/lp/bugs/interfaces/bug.py 2009-12-09 17:25:37 +0000
@@ -26,6 +26,7 @@
26from zope.interface import Interface, Attribute26from zope.interface import Interface, Attribute
27from zope.schema import (27from zope.schema import (
28 Bool, Bytes, Choice, Datetime, Int, List, Object, Text, TextLine)28 Bool, Bytes, Choice, Datetime, Int, List, Object, Text, TextLine)
29from zope.schema.vocabulary import SimpleVocabulary
29from zope.security.interfaces import Unauthorized30from zope.security.interfaces import Unauthorized
3031
31from canonical.launchpad import _32from canonical.launchpad import _
@@ -789,6 +790,11 @@
789 "A sequence of IBugTaskDeltas, one IBugTaskDelta or None.")790 "A sequence of IBugTaskDeltas, one IBugTaskDelta or None.")
790791
791792
793# A simple vocabulary for the subscribe_to_existing_bug form field.
794SUBSCRIBE_TO_BUG_VOCABULARY = SimpleVocabulary.fromItems(
795 [('yes', True), ('no', False)])
796
797
792class IBugAddForm(IBug):798class IBugAddForm(IBug):
793 """Information we need to create a bug"""799 """Information we need to create a bug"""
794 id = Int(title=_("Bug #"), required=False)800 id = Int(title=_("Bug #"), required=False)
@@ -842,8 +848,9 @@
842 assignee = PublicPersonChoice(848 assignee = PublicPersonChoice(
843 title=_('Assign to'), required=False,849 title=_('Assign to'), required=False,
844 vocabulary='ValidAssignee')850 vocabulary='ValidAssignee')
845 subscribe_to_existing_bug = Bool(851 subscribe_to_existing_bug = Choice(
846 title=_(u'Subscribe to this bug'),852 title=u'Subscribe to this bug',
853 vocabulary=SUBSCRIBE_TO_BUG_VOCABULARY,
847 required=True, default=False)854 required=True, default=False)
848855
849856
850857
=== added file 'lib/lp/bugs/templates/bugtarget-filebug-inline-form.pt'
--- lib/lp/bugs/templates/bugtarget-filebug-inline-form.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/templates/bugtarget-filebug-inline-form.pt 2009-12-09 17:25:37 +0000
@@ -0,0 +1,11 @@
1<tal:root
2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 omit-tag="">
5
6 <tal:filebug-form define="launchpad_form_id string:filebug-form">
7 <metal:display-similar-bugs
8 use-macro="context/@@+filebug-macros/inline-filebug-form" />
9 </tal:filebug-form>
10
11</tal:root>
012
=== modified file 'lib/lp/bugs/templates/bugtarget-filebug-search.pt'
--- lib/lp/bugs/templates/bugtarget-filebug-search.pt 2009-08-18 16:15:18 +0000
+++ lib/lp/bugs/templates/bugtarget-filebug-search.pt 2009-12-09 17:25:37 +0000
@@ -1,10 +1,29 @@
1<filebug-search1<html
2 xmlns="http://www.w3.org/1999/xhtml"2 xmlns="http://www.w3.org/1999/xhtml"
3 xmlns:tal="http://xml.zope.org/namespaces/tal"3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 xmlns:metal="http://xml.zope.org/namespaces/metal"4 xmlns:metal="http://xml.zope.org/namespaces/metal"
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 metal:use-macro="view/macro:page/main_only">6 metal:use-macro="view/macro:page/main_only">
77
8 <metal:block fill-slot="head_epilogue">
9 <script type="text/javascript"
10 tal:condition="devmode"
11 tal:content="string:var yui_base='${yui}';"></script>
12 <script type="text/javascript"
13 tal:condition="devmode"
14 tal:define="lp_js string:${icingroot}/build"
15 tal:attributes="src string:${lp_js}/bugs/filebug-dupefinder.js"></script>
16
17 <tal:dupe-finder-js condition="view/use_asynchronous_dupefinder">
18 <script type="text/javascript">
19 LPS.use(
20 'base', 'node', 'oop', 'event', 'bugs.dupe_finder', function(Y) {
21 Y.bugs.setup_dupe_finder();
22 });
23 </script>
24 </tal:dupe-finder-js>
25 </metal:block>
26
8 <div metal:fill-slot="heading">27 <div metal:fill-slot="heading">
9 <div tal:condition="view/isPrivate" id="privacy" class="aside private">28 <div tal:condition="view/isPrivate" id="privacy" class="aside private">
10 This report will be private, though you can disclose it later.29 This report will be private, though you can disclose it later.
@@ -23,38 +42,89 @@
23 </tal:does-not-use-malone>42 </tal:does-not-use-malone>
2443
25 <tal:uses-malone tal:condition="view/contextUsesMalone">44 <tal:uses-malone tal:condition="view/contextUsesMalone">
26 <div class="top-portlet">45 <div class="top-portlet"
46 tal:define="launchpad_form_id string:filebug-search-form">
27 <div metal:use-macro="context/@@launchpad_form/form">47 <div metal:use-macro="context/@@launchpad_form/form">
28 <table class="form" metal:fill-slot="widgets">48
29 <tal:product_widget49 <table metal:fill-slot="widgets">
30 tal:define="widget nocall:view/widgets/product|nothing"50
31 tal:condition="widget">51 <tal:product_widget
32 <metal:widget metal:use-macro="context/@@launchpad_form/widget_row" />52 tal:define="widget nocall:view/widgets/product|nothing"
33 </tal:product_widget>53 tal:condition="widget">
34 <tal:bugtarget54 <metal:widget metal:use-macro="context/@@launchpad_form/widget_row" />
35 tal:define="widget nocall:view/widgets/bugtarget|nothing"55 </tal:product_widget>
36 tal:condition="widget">56
37 <metal:widget metal:use-macro="context/@@launchpad_form/widget_row" />57 <tal:bugtarget
38 </tal:bugtarget>58 tal:define="widget nocall:view/widgets/bugtarget|nothing"
39 <tal:hidden_tags tal:replace="structure view/widgets/tags/hidden" />59 tal:condition="widget">
40 <tr>60 <metal:widget metal:use-macro="context/@@launchpad_form/widget_row" />
41 <td colspan="2">61 </tal:bugtarget>
42 Please describe the bug in a few words, for example,62
43 "weather applet crashes on logout":63 <tal:hidden_tags tal:replace="structure view/widgets/tags/hidden" />
44 </td>64
45 </tr>65 <tr>
46 <tal:title_widget tal:define="widget nocall:view/widgets/title">66 <td colspan="2">
47 <metal:widget use-macro="context/@@launchpad_form/widget_row" />67 <p>
48 </tal:title_widget>68 Please describe the bug in a few words, for example, "weather
49 </table>69 applet crashes on logout":
50 <div class="actions" metal:fill-slot="buttons">70 </p>
51 <input tal:replace="structure view/actions/field.actions.search/render" />71 </td>
52 </div>72 </tr>
53 </div>73 <tal:title_widget tal:define="widget nocall:view/widgets/title">
74 <tal:comment replace="nothing">
75 The desire to have more control over the styling of this widget
76 prevents us from using the widget_row macro here.
77 </tal:comment>
78 <tr>
79 <td tal:define="field_name widget/context/__name__;
80 error python:view.getFieldError(field_name);
81 error_class python:('error' if error else None);"
82 tal:attributes="class error_class"
83 style="text-align: left; padding-left: 5em;">
84 <div>
85 <label tal:attributes="for widget/name"
86 tal:content="string:${widget/label}:">Label</label>
87 <input tal:replace="structure widget" />
88 </div>
89 <div class="message" tal:condition="error"
90 tal:content="structure error">Error message</div>
91 <p class="formHelp"
92 tal:condition="widget/hint"
93 tal:content="widget/hint">Some Help Text
94 </p>
95 </td>
96 <td style="text-align: left;">
97 <input tal:replace="structure view/actions/field.actions.search/render" />
98 <span id="spinner" style="text-align: center" class="unseen">
99 <img src="/@@/spinner" />
100 </span>
101 </td>
102 </tr>
103 </tal:title_widget>
104 </table>
105
106 <div metal:fill-slot="buttons">
107 <tal:comment replace="nothing">
108 We add this to hide the standard action buttons.
109 </tal:comment>
54 </div>110 </div>
55 </tal:uses-malone>111 </div>
56
57 </div>112 </div>
58 </div>113 </tal:uses-malone>
59114 </div>
60</filebug-search>115
116 <div id="possible-duplicates" style="text-align: left;">
117 </div>
118 <div id="filebug-form-container" style="display: none;">
119 </div>
120
121 <p style="display: none" tal:condition="view/use_asynchronous_dupefinder">
122 <a id="filebug-form-url"
123 tal:attributes="href view/inline_filebug_form_url"></a>
124 <a id="duplicate-search-url"
125 tal:attributes="href view/duplicate_search_url"></a>
126 </p>
127
128 </div>
129
130</html>
61131
=== modified file 'lib/lp/bugs/templates/bugtarget-filebug-show-similar.pt'
--- lib/lp/bugs/templates/bugtarget-filebug-show-similar.pt 2009-07-17 17:59:07 +0000
+++ lib/lp/bugs/templates/bugtarget-filebug-show-similar.pt 2009-12-09 17:25:37 +0000
@@ -3,7 +3,12 @@
3 xmlns:metal="http://xml.zope.org/namespaces/metal"3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 omit-tag="">4 omit-tag="">
55
6 <metal:display-similar-bugs6 <tal:similar-bugs-list condition="view/similar_bugs">
7 use-macro="context/@@+filebug-macros/display-similar-bugs" />7 <metal:display-similar-bugs
8 use-macro="context/@@+filebug-macros/show-similar-bugs-and-filebug-button" />
9 </tal:similar-bugs-list>
10 <tal:no-similar-bugs condition="not: view/similar_bugs">
11 <p>No similar bug reports were found.</p>
12 </tal:no-similar-bugs>
813
9</tal:root>14</tal:root>
1015
=== modified file 'lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt'
--- lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt 2009-12-09 17:25:34 +0000
+++ lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt 2009-12-09 17:25:37 +0000
@@ -1,4 +1,4 @@
1<filebug-submit1<div
2 xmlns="http://www.w3.org/1999/xhtml"2 xmlns="http://www.w3.org/1999/xhtml"
3 xmlns:tal="http://xml.zope.org/namespaces/tal"3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 xmlns:metal="http://xml.zope.org/namespaces/metal"4 xmlns:metal="http://xml.zope.org/namespaces/metal"
@@ -64,4 +64,4 @@
64 </div>64 </div>
65 </div>65 </div>
6666
67</filebug-submit>67</div>
6868
=== modified file 'lib/lp/bugs/templates/bugtarget-macros-filebug.pt'
--- lib/lp/bugs/templates/bugtarget-macros-filebug.pt 2009-10-01 12:09:37 +0000
+++ lib/lp/bugs/templates/bugtarget-macros-filebug.pt 2009-12-09 17:25:37 +0000
@@ -100,7 +100,7 @@
100100
101 <tr>101 <tr>
102 <td colspan="2">102 <td colspan="2">
103 <fieldset class="collapsible collapsed">103 <fieldset id="filebug-extra-options" class="collapsible collapsed">
104 <legend>Extra options</legend>104 <legend>Extra options</legend>
105 <table>105 <table>
106 <tal:status106 <tal:status
@@ -361,8 +361,19 @@
361</metal:bug_reporting_guidelines>361</metal:bug_reporting_guidelines>
362362
363<metal:similar-bugs define-macro="display-similar-bugs">363<metal:similar-bugs define-macro="display-similar-bugs">
364 <p id="filebug-query-heading">Is "<strong><span tal:replace="view/search_text">my submitted bug364 <tal:results-intro-with-summary condition="view/show_summary_in_results">
365 summary</span></strong>" one of these bugs?</p>365 <p id="filebug-query-heading">
366 Is "<strong><span tal:replace="view/search_text">my submitted bug summary</span></strong>"
367 one of these bugs?
368 </p>
369 </tal:results-intro-with-summary>
370 <tal:results-intro-without-summary
371 condition="not: view/show_summary_in_results">
372 <p id="filebug-query-heading">
373 Do any of the following bugs describe the bug you're trying to
374 report?
375 </p>
376 </tal:results-intro-without-summary>
366 <ul id="similar-bugs">377 <ul id="similar-bugs">
367 <li tal:repeat="bug view/similar_bugs" class="similar-bug">378 <li tal:repeat="bug view/similar_bugs" class="similar-bug">
368 <table tal:define="bugtask python:view.getRelevantBugTask(bug)">379 <table tal:define="bugtask python:view.getRelevantBugTask(bug)">
@@ -461,8 +472,9 @@
461 tal:condition="error_message" tal:content="error_message" class="error message" />472 tal:condition="error_message" tal:content="error_message" class="error message" />
462</metal:similar-bugs>473</metal:similar-bugs>
463474
464<metal:similar-bugs-and-filebug-form475<metal:similar-bugs-and-filebug-button
465 define-macro="show-similar-bugs-and-filebug-form">476 define-macro="show-similar-bugs-and-filebug-button">
477
466 <tal:similar-bugs condition="view/show_duplicate_list">478 <tal:similar-bugs condition="view/show_duplicate_list">
467 <metal:similar-bugs479 <metal:similar-bugs
468 use-macro="context/@@+filebug-macros/display-similar-bugs" />480 use-macro="context/@@+filebug-macros/display-similar-bugs" />
@@ -477,22 +489,55 @@
477 value="No, I need to report a new bug" />489 value="No, I need to report a new bug" />
478 </p>490 </p>
479 </tal:similar-bugs>491 </tal:similar-bugs>
492</metal:similar-bugs-and-filebug-button>
493
494<metal:similar-bugs-and-filebug-form
495 define-macro="show-similar-bugs-and-filebug-form">
496
497 <metal:similar-bugs
498 use-macro="context/@@+filebug-macros/show-similar-bugs-and-filebug-button" />
499
480 <tal:submit-new-bug>500 <tal:submit-new-bug>
481 <div id="bug_reporting_form">501 <metal:basic_filebug_widgets
482 <metal:form use-macro="context/@@launchpad_form/form">502 metal:use-macro="context/@@+filebug-macros/inline-filebug-form" />
483 <a name="form-start" />
484 <metal:widgets metal:fill-slot="widgets">
485 <table class="form">
486 <metal:basic_filebug_widgets
487 metal:use-macro="context/@@+filebug-macros/basic_filebug_widgets" />
488 </table>
489 </metal:widgets>
490 <div class="actions" metal:fill-slot="buttons">
491 <input tal:replace="structure view/submit_bug_action/render" />
492 </div>
493 </metal:form>
494 </div>
495 </tal:submit-new-bug>503 </tal:submit-new-bug>
496</metal:similar-bugs-and-filebug-form>504</metal:similar-bugs-and-filebug-form>
497505
506<metal:inline-filebug-form define-macro="inline-filebug-form">
507 <div id="bug_reporting_form">
508 <metal:form use-macro="context/@@launchpad_form/form">
509 <a name="form-start" />
510 <metal:widgets metal:fill-slot="widgets">
511 <table class="form">
512 <metal:basic_filebug_widgets
513 metal:use-macro="context/@@+filebug-macros/basic_filebug_widgets" />
514 </table>
515 </metal:widgets>
516 <div class="actions" metal:fill-slot="buttons">
517 <input tal:replace="structure view/submit_bug_action/render" />
518 </div>
519 </metal:form>
520 </div>
521</metal:inline-filebug-form>
522
523<metal:filebug-form define-macro="simple-filebug-form">
524 <form action="."
525 tal:attributes="action view/action_url"
526 name="launchpadform"
527 method="post"
528 enctype="multipart/form-data"
529 accept-charset="UTF-8"
530 id="filebug-form">
531
532 <table class="form">
533 <metal:basic_filebug_widgets
534 metal:use-macro="context/@@+filebug-macros/basic_filebug_widgets" />
535 </table>
536 <div class="actions">
537 <input tal:replace="structure view/submit_bug_action/render" />
538 </div>
539
540 </form>
541</metal:filebug-form>
542
498</tal:root>543</tal:root>