Merge lp:~gmb/launchpad/prince-of-ajax-dupefinding-bug-358510 into lp:launchpad

Proposed by Graham Binns
Status: Merged
Approved by: Graham Binns
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~gmb/launchpad/prince-of-ajax-dupefinding-bug-358510
Merge into: lp:launchpad
Diff against target: 477 lines (+247/-135)
3 files modified
lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js (+242/-133)
lib/canonical/launchpad/templates/launchpad-form.pt (+2/-1)
lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt (+3/-1)
To merge this branch: bzr merge lp:~gmb/launchpad/prince-of-ajax-dupefinding-bug-358510
Reviewer Review Type Date Requested Status
Michael Nelson (community) code js Approve
Canonical Launchpad Engineering code Pending
Review via email: mp+15273@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

This branch lays the groundwork for making the +filebug dupefinder
asynchronous. The branch contains several JavaScript changes designed to
make async dupefinding work; once this branch lands a subsequent branch
will actually switch those changes on and hook them up as appropriate.

The contents of the branch have been reviewed before, but those branches
got far too out of date to be merged, so I'm resubmitting this for
review.

Here's a list of the changes I've made:

== lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js ==

 - I've added a couple of globals which will be used to hold references
   to the search field and search button on the +filebug form.
 - I've updated show_bug_reporting_form to cope with situations where
   there aren't any suggested dupes. I've also altered the function so
   that it's ready for the switch to async dupe finding, whereafter the
   filebug form will be loaded in a hidden container.
 - I've added a function, search_for_and_display_dupes(). This function
   takes care of the asynchronous dupefinding. It grabs the duplicate
   search URL from the +filebug page and uses it to make a GET request
   to Launchpad for the list of possible duplicates for a given bug
   title, then updates the page with that list. This function is
   currently dormant (see below).
 - I've added a function, set_up_dupe_finder, which will be called when
   the async dupe finder functionality is turned on and which sets up
   the dupe finder to work asynchronously.
 - I've moved the body of the old Y.bugs.setup_dupe_finder() into a
   function called Y.bugs.setup_dupes() (its previous name was something
   of a misnomer, really).
 - I've made Y.bugs.setup_dupe_finder() into a much smaller function,
   which will check for the ability to load duplicates asynchronously
   and then call set_up_dupe_finder() appropriately. Currently, this
   function isn't called anywhere, thus disabling the async dupe search.

== lib/canonical/launchpad/templates/launchpad-form.pt ==

 - I've added an 'id' attribute to the launchpad form macro, so that we
   can pass identifiers to forms to be able to better grab them with
   JavaScript.

== lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt ==

 - I've updated this template to call Y.bugs.setup_dupes() instead of
   .setup_dupe_finder()

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js
  lib/canonical/launchpad/templates/launchpad-form.pt
  lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt

== JSLint notices ==
jslint: No problem found in '/home/graham/canonical/lp-branches/prince-of-ajax-dupefinding-bug-358510/lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js'.

jslint: 1 file to lint.

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

A note on testing this:

Basically, we know this branch works if:

 1. None of the pagetests fail.
 2. The behaviour of the +filebug form is not altered in any way.

I.e., this branch lands code, but the code shouldn't change the way that Launchpad behaves. There's a bit of refactoring and some new functionality, but the new stuff isn't turned on yet.

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

Hi Graham,

Your code looks great - it'll be an excellent change when it lands - but
it's quite difficult to review it like this when I can't see what
it does, or test the functionality (as opposed to testing that non of
it is currently run).

Also, there are a few points below where you've still got work-in-progress
code - so I'm ok with approving this branch as long as you're not landing
it on its own like this, but rather as part of a subsequent branch
(just use the 'Prerequisite branch' option when you create the MP
via the UI, so that the next reviewer won't see all these changes.) As it really needs a review where the functionality can be observed and tested.

It would be great if this new functionality could be tested with some JS unit-tests in lib/canonical/launchpad/javascript/bugs/tests/ in a subsequent branch too.

Thanks for all the cleanups too!

> === modified file 'lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js'
> --- lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-11-23 19:29:02 +0000
> +++ lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-11-26 13:39:58 +0000
[...]
> @@ -99,6 +113,67 @@
> }
>
> /**
> + * Search for bugs that may match the text that the user has entered and
> + * display them in-line.
> + */
> +function search_for_and_display_dupes() {
> + function show_failure_message() {
> + Y.get('#possible-duplicates').set(INNER_HTML, 'FAIL');

erm, that's a temporary message right? I understood from your MP that
this branch doesn't enable the functionality, but are you planning to
hold off landing it until the subsequent branch is done?

There is a new error api in lazrjs which needs a little love, but will
be useful here soon.

> @@ -185,124 +260,160 @@
> }
>
>
> -Y.bugs.setup_dupe_finder = function() {
> - Y.on('domready', function() {
> - bug_already_reported_expanders = Y.all(
> - 'img.bug-already-reported-expander');
> - bug_reporting_form = Y.get('#bug_reporting_form');
> -
> - if (bug_already_reported_expanders !== null &&
> - bug_already_reported_expanders !== undefined) {
> -
> - // Set up the onclick handlers for the expanders.
> - Y.each(Y.all('.similar-bug'), function(row) {
> - var bug_details_div = row.query('div.duplicate-details');
> - var image = row.query('img.bug-already-reported-expander');
> - var bug_title_link = row.query('.duplicate-bug-link');
> - var view_bug_link = row.query('.view-bug-link');
> -
> - // Grab the initial height of the element and create a
> - // config for the slide_out animation. This allows us to
> - // deal with browsers like Opera in which the JS engine
> - // takes a punt at the right height for the slid-out
> - // drawer and gets it completely and utterly wrong.
> - var initial_height = bug_details_div.get('scrollHeight');
> - var slide_out_config = {
> - to: {
> - height: initial_height
> - }
> - };
...

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

Marking this as Approved for the sake of getting it off my to-do list.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js'
2--- lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-11-24 09:30:01 +0000
3+++ lib/canonical/launchpad/javascript/bugs/filebug-dupefinder.js 2009-12-08 11:01:17 +0000
4@@ -12,6 +12,9 @@
5 DISPLAY = 'display',
6 EXPANDER_COLLAPSED = '/@@/treeCollapsed',
7 EXPANDER_EXPANDED = '/@@/treeExpanded',
8+ INLINE = 'inline',
9+ INNER_HTML = 'innerHTML',
10+ LAZR_CLOSED = 'lazr-closed',
11 NONE = 'none',
12 SRC = 'src';
13
14@@ -21,12 +24,14 @@
15 * The NodeList of possible duplicates.
16 */
17 var bug_already_reported_expanders;
18-
19-/*
20- * The div in which the bug reporting form resides.
21- */
22-var bug_reporting_form;
23-
24+/*
25+ * The search field on the +filebug form
26+ */
27+var search_field;
28+/*
29+ * The search button on the +filebug form
30+ */
31+var search_button;
32 /*
33 * The boilerplate elements for the do-you-want-to-subscribe
34 * FormOverlay.
35@@ -50,7 +55,7 @@
36
37 // Check that the details_div actually exists and raise an error if
38 // we can't find it.
39- if (details_div === null) {
40+ if (!Y.Lang.isValue(details_div)) {
41 Y.fail(
42 "Unable to find details div for expander " + expander.get('id'));
43 } else {
44@@ -76,29 +81,99 @@
45 * @param e The Event triggering this function.
46 */
47 function show_bug_reporting_form(e) {
48- // Collapse all the duplicate-details divs.
49- bug_already_reported_expanders.each(function(expander) {
50- collapse_bug_details(expander);
51- });
52+ if (Y.Lang.isValue(bug_already_reported_expanders)) {
53+ // Collapse any duplicate-details divs.
54+ Y.each(bug_already_reported_expanders, function(expander) {
55+ collapse_bug_details(expander);
56+ });
57+ }
58+
59+ // If the bug reporting form is in a hidden container, as it is on
60+ // the AJAX dupe search, show it.
61+ var filebug_form_container = Y.get('#filebug-form-container');
62+ if (Y.Lang.isValue(filebug_form_container)) {
63+ filebug_form_container.setStyle(DISPLAY, BLOCK);
64+ }
65
66 // Show the bug reporting form.
67+ var bug_reporting_form = Y.get('#bug_reporting_form');
68 bug_reporting_form.setStyle(DISPLAY, BLOCK);
69- Y.one(Y.DOM.byId('field.actions.submit_bug')).focus();
70- window.location.href = '#form-start';
71+
72+ Y.get(Y.DOM.byId('field.actions.submit_bug')).focus();
73
74 // Focus the relevant elements of the form based on
75 // whether the package drop-down is displayed.
76 var bugtarget_package_btn = Y.one(
77 Y.DOM.byId('field.bugtarget.option.package'));
78- if (bugtarget_package_btn !== null &&
79- bugtarget_package_btn !== undefined) {
80- Y.one(Y.DOM.byId('field.bugtarget.package')).focus();
81+ if (Y.Lang.isValue(bugtarget_package_btn)) {
82+ Y.get(Y.DOM.byId('field.bugtarget.package')).focus();
83 } else {
84 Y.one(Y.DOM.byId('field.comment')).focus();
85 }
86 }
87
88 /**
89+ * Search for bugs that may match the text that the user has entered and
90+ * display them in-line.
91+ */
92+function search_for_and_display_dupes() {
93+ function show_failure_message() {
94+ Y.get('#possible-duplicates').set(INNER_HTML, 'FAIL');
95+ }
96+
97+ function on_success(transaction_id, response, args) {
98+ // Hide the spinner and show the duplicates.
99+ Y.get('#spinner').setStyle(DISPLAY, NONE);
100+
101+ var duplicate_div = Y.get('#possible-duplicates');
102+ duplicate_div.set(INNER_HTML, response.responseText);
103+
104+ bug_already_reported_expanders = Y.all(
105+ 'img.bug-already-reported-expander');
106+ if (Y.Lang.isValue(bug_already_reported_expanders)) {
107+ // If there are duplicates shown, change the title of the page
108+ // and set up the JavaScript of the duplicates that have been
109+ // returned.
110+ Y.bugs.setup_dupes();
111+ Y.get('#page-title').set(
112+ INNER_HTML,
113+ 'Is the bug you’re reporting one of these?');
114+ } else {
115+ // Otherwise, set the title to one that doesn't suggest
116+ // there were dupes returned and show the bug reporting
117+ // form.
118+ Y.get('#page-title').set(INNER_HTML, 'Report a bug');
119+ show_bug_reporting_form();
120+ }
121+
122+ // Copy the value from the search field into the title field
123+ // on the filebug form.
124+ Y.get(Y.DOM.byId('field.title')).set(
125+ 'value', search_field.get('value'));
126+
127+ // Finally, change the label on the search button and show it
128+ // again.
129+ search_button.set('value', 'Check again');
130+ search_button.setStyle(DISPLAY, INLINE);
131+ }
132+
133+ var search_term = encodeURI(search_field.get('value'));
134+ var search_url_base = Y.get(
135+ '#duplicate-search-url').getAttribute('href');
136+ var search_url = search_url_base + '?title=' + search_term;
137+
138+ // Hide the button, show the spinner and clear the contents of the
139+ // possible duplicates div.
140+ search_button.setStyle(DISPLAY, NONE);
141+ Y.get('#spinner').setStyle(DISPLAY, INLINE);
142+ Y.get('#possible-duplicates').set(INNER_HTML, '');
143+
144+ config = {on: {success: on_success,
145+ failure: show_failure_message}};
146+ Y.io(search_url, config);
147+}
148+
149+/*
150 * Create the overlay for a user to optionally subscribe to a bug that
151 * affects them.
152 * @param form The form to which the FormOverlay is going to be
153@@ -108,10 +183,10 @@
154 // Grab the bug id and title from the "Yes, this is my bug" form.
155 var bug_id = form.one(
156 'input.bug-already-reported-as').get('value');
157- var bug_title = Y.one('#bug-' + bug_id + '-title').get('innerHTML');
158+ var bug_title = Y.get('#bug-' + bug_id + '-title').get(INNER_HTML);
159
160 if (bug_title.length > 35) {
161- // Truncate the bug title if it's more than 30 characters long.
162+ // Truncate the bug title if it's more than 35 characters long.
163 bug_title = bug_title.substring(0, 35) + '...';
164 }
165
166@@ -161,11 +236,8 @@
167
168 // Add an on-click handler to the radio buttons to ensure that their
169 // labels' styles are set correctly when they're selected.
170- var radio_buttons = form.all('input.subscribe-option');
171- radio_buttons.on('click', function(e) {
172- // Loop over the radio buttons and set their parent
173- // div's font-weight depending on whether they're
174- // checked or not.
175+ var radio_buttons = form.queryAll('input.subscribe-option');
176+ if (Y.Lang.isValue(radio_buttons)) {
177 Y.each(radio_buttons, function(radio_button) {
178 var weight = radio_button.get('checked') ? 'bold' : 'normal';
179 radio_button.get('parentNode').setStyle('fontWeight', weight);
180@@ -176,121 +248,158 @@
181 }
182
183
184-Y.bugs.setup_dupe_finder = function() {
185- Y.on('domready', function() {
186- bug_already_reported_expanders = Y.all(
187- 'img.bug-already-reported-expander');
188- bug_reporting_form = Y.one('#bug_reporting_form');
189-
190- if (bug_already_reported_expanders.size() !== 0) {
191-
192- // Set up the onclick handlers for the expanders.
193- Y.all('.similar-bug').each(function(row) {
194- var bug_details_div = row.one('div.duplicate-details');
195- var image = row.one('img.bug-already-reported-expander');
196- var bug_title_link = row.one('.duplicate-bug-link');
197- var view_bug_link = row.one('.view-bug-link');
198-
199- // Grab the initial height of the element and create a
200- // config for the slide_out animation. This allows us to
201- // deal with browsers like Opera in which the JS engine
202- // takes a punt at the right height for the slid-out
203- // drawer and gets it completely and utterly wrong.
204- var initial_height = bug_details_div.get('scrollHeight');
205- var slide_out_config = {
206- to: {
207- height: initial_height
208- }
209- };
210-
211- // Shut down the default action for the link and mark it
212- // as a JS'd link. We do this as it's simpler than
213- // trying to find all the bits of the row that we want
214- // to make clickable.
215- bug_title_link.addClass('js-action');
216- bug_title_link.on('click', function(e) {
217- e.preventDefault();
218- });
219-
220- // The "view this bug" link shouldn't trigger the
221- // collapsible, so we stop the event from propagating.
222- view_bug_link.on('click', function(e) {
223- e.stopPropagation();
224- });
225-
226- // The same is true for the collapsible section. People
227- // may want to copy and paste this, which involves
228- // clicking, so we stop the onclick event from
229- // propagating here, too.
230- bug_details_div.on('click', function(e) {
231- e.stopPropagation();
232- });
233-
234- // Set up the on focus handler for the link so that
235- // tabbing will expand the different bugs.
236- bug_title_link.on('focus', function(e) {
237- if (!bug_details_div.hasClass('lazr-opened')) {
238- var anim = Y.lazr.effects.slide_out(
239- bug_details_div, slide_out_config);
240- anim.run();
241-
242- image.set(SRC, EXPANDER_EXPANDED);
243-
244- // If the bug reporting form is shown, hide it.
245- if (bug_reporting_form.getStyle(DISPLAY) == BLOCK) {
246- bug_reporting_form.setStyle(DISPLAY, NONE);
247- }
248- }
249- });
250-
251- row.on('click', function(e) {
252- if (bug_details_div.hasClass('lazr-opened')) {
253- collapse_bug_details(image);
254- } else {
255- var anim = Y.lazr.effects.slide_out(
256- bug_details_div, slide_out_config);
257- anim.run();
258-
259- image.set(SRC, EXPANDER_EXPANDED);
260- }
261+/**
262+ * Set up the dupe finder, overriding the default behaviour of the
263+ * +filebug search form.
264+ */
265+function set_up_dupe_finder(transaction_id, response, args) {
266+ var filebug_form_container = Y.get('#filebug-form-container');
267+ filebug_form_container.set(INNER_HTML, response.responseText);
268+
269+ // Activate the extra options collapsible section on the bug
270+ // reporting form.
271+ var bug_reporting_form = Y.get('#bug_reporting_form');
272+ if (Y.Lang.isValue(bug_reporting_form)) {
273+ activateCollapsibles();
274+ }
275+
276+ search_button = Y.get(Y.DOM.byId('field.actions.search'));
277+
278+ // Change the name and id of the search field so that it doesn't
279+ // confuse the view when we submit a bug report.
280+ search_field = Y.get(Y.DOM.byId('field.title'));
281+ search_field.set('name', 'field.search');
282+ search_field.set('id', 'field.search');
283+
284+ // Disable the form so that hitting "enter" in the Summary
285+ // field no longer sends us through to the next page.
286+ // Y.on('submit', function(e) { e.halt(); }, '#my-form')
287+
288+ // Update the label on the search button so that it no longer
289+ // says "Continue".
290+ search_button.set('value', 'Next');
291+ search_button.set('type', 'button');
292+
293+ // Set up the handlers for the search button and the input
294+ // field.
295+ search_button.on('click', search_for_and_display_dupes);
296+}
297+
298+Y.bugs.setup_dupes = function() {
299+ bug_already_reported_expanders = Y.all(
300+ 'img.bug-already-reported-expander');
301+ bug_reporting_form = Y.get('#bug_reporting_form');
302+
303+ if (Y.Lang.isValue(bug_already_reported_expanders)) {
304+ // Collapse all the details divs, since we don't want them
305+ // expanded first up.
306+ Y.each(Y.all('div.duplicate-details'), function(div) {
307+ collapse_bug_details(div);
308+ });
309+
310+ // Set up the onclick handlers for the expanders.
311+ Y.each(Y.all('.similar-bug'), function(row) {
312+ var bug_details_div = row.query('div.duplicate-details');
313+ var image = row.query('img.bug-already-reported-expander');
314+ var bug_title_link = row.query('.duplicate-bug-link');
315+ var view_bug_link = row.query('.view-bug-link');
316+
317+ // Shut down the default action for the link and mark it
318+ // as a JS'd link. We do this as it's simpler than
319+ // trying to find all the bits of the row that we want
320+ // to make clickable.
321+ bug_title_link.addClass('js-action');
322+ bug_title_link.on('click', function(e) {
323+ e.preventDefault();
324+ });
325+
326+ // The "view this bug" link shouldn't trigger the
327+ // collapsible, so we stop the event from propagating.
328+ view_bug_link.on('click', function(e) {
329+ e.stopPropagation();
330+ });
331+
332+ // The same is true for the collapsible section. People
333+ // may want to copy and paste this, which involves
334+ // clicking, so we stop the onclick event from
335+ // propagating here, too.
336+ bug_details_div.on('click', function(e) {
337+ e.stopPropagation();
338+ });
339+
340+ // Set up the on focus handler for the link so that
341+ // tabbing will expand the different bugs.
342+ bug_title_link.on('focus', function(e) {
343+ if (!bug_details_div.hasClass('lazr-opened')) {
344+ var anim = Y.lazr.effects.slide_out(bug_details_div);
345+ anim.run();
346+
347+ image.set(SRC, EXPANDER_EXPANDED);
348
349 // If the bug reporting form is shown, hide it.
350 if (bug_reporting_form.getStyle(DISPLAY) == BLOCK) {
351 bug_reporting_form.setStyle(DISPLAY, NONE);
352 }
353- });
354- });
355-
356- // Hide the bug reporting form.
357- bug_reporting_form.setStyle(DISPLAY, NONE);
358-
359- // Collapse all the details divs, since we don't want them
360- // expanded first up.
361- Y.all('div.duplicate-details').each(function(div) {
362- collapse_bug_details(div);
363- });
364-
365- }
366-
367- bug_not_reported_button = Y.one('#bug-not-already-reported');
368- if (bug_not_reported_button !== null &&
369- bug_not_reported_button !== undefined) {
370- // The bug_not_reported_button won't show up if there aren't any
371- // possible duplicates.
372- bug_not_reported_button.on('click', show_bug_reporting_form);
373- }
374-
375- // Attach the form overlay to the "Yes, this is my bug" forms.
376- Y.all('form.this-is-my-bug-form').each(function(form) {
377- var subscribe_form_overlay = create_subscribe_overlay(form);
378-
379- form.on('submit', function(e) {
380- // We don't care about the original event, so stop it
381- // and show the form overlay that we just created.
382- e.halt();
383- subscribe_form_overlay.show();
384- });
385- });
386+ }
387+ });
388+
389+ row.on('click', function(e) {
390+ if (bug_details_div.hasClass('lazr-opened')) {
391+ collapse_bug_details(image);
392+ } else {
393+ var anim = Y.lazr.effects.slide_out(bug_details_div);
394+ anim.run();
395+
396+ image.set(SRC, EXPANDER_EXPANDED);
397+ }
398+
399+ // If the bug reporting form is shown, hide it.
400+ if (bug_reporting_form.getStyle(DISPLAY) == BLOCK) {
401+ bug_reporting_form.setStyle(DISPLAY, NONE);
402+ }
403+ });
404+ });
405+
406+ // Hide the bug reporting form.
407+ bug_reporting_form.setStyle(DISPLAY, NONE);
408+ }
409+
410+ bug_not_reported_button = Y.get('#bug-not-already-reported');
411+ if (Y.Lang.isValue(bug_not_reported_button)) {
412+ // The bug_not_reported_button won't show up if there aren't any
413+ // possible duplicates.
414+ bug_not_reported_button.on('click', show_bug_reporting_form);
415+ }
416+
417+ // Attach the form overlay to the "Yes, this is my bug" forms.
418+ var this_is_my_bug_forms = Y.all('form.this-is-my-bug-form');
419+ Y.each(this_is_my_bug_forms, function(form) {
420+ var subscribe_form_overlay = create_subscribe_overlay(form);
421+
422+ form.on('submit', function(e) {
423+ // We don't care about the original event, so stop it
424+ // and show the form overlay that we just created.
425+ e.halt();
426+ subscribe_form_overlay.show();
427+ });
428+ });
429+};
430+
431+Y.bugs.setup_dupe_finder = function() {
432+ Y.on('domready', function() {
433+ config = {on: {success: set_up_dupe_finder,
434+ failure: function() {}}};
435+
436+ // Load the filebug form asynchronously. If this fails we
437+ // degrade to the standard mode for bug filing, clicking through
438+ // to the second part of the bug filing form.
439+ var filebug_form_url_element = Y.get(
440+ '#filebug-form-url');
441+ if (Y.Lang.isValue(filebug_form_url_element)) {
442+ var filebug_form_url = filebug_form_url_element.getAttribute(
443+ 'href');
444+ Y.io(filebug_form_url, config);
445+ }
446 });
447 };
448
449
450=== modified file 'lib/canonical/launchpad/templates/launchpad-form.pt'
451--- lib/canonical/launchpad/templates/launchpad-form.pt 2009-09-03 15:39:22 +0000
452+++ lib/canonical/launchpad/templates/launchpad-form.pt 2009-12-08 11:01:17 +0000
453@@ -8,7 +8,8 @@
454 <div metal:define-macro="form">
455
456 <form action="."
457- tal:attributes="action view/action_url"
458+ tal:attributes="action view/action_url;
459+ id launchpad_form_id|nothing;"
460 name="launchpadform"
461 method="post"
462 enctype="multipart/form-data"
463
464=== modified file 'lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt'
465--- lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt 2009-12-03 18:33:22 +0000
466+++ lib/lp/bugs/templates/bugtarget-filebug-submit-bug.pt 2009-12-08 11:01:17 +0000
467@@ -15,7 +15,9 @@
468 tal:attributes="src string:${lp_js}/bugs/filebug-dupefinder.js"></script>
469 <script type="text/javascript">
470 LPS.use('base', 'node', 'oop', 'event', 'bugs.dupe_finder', function(Y) {
471- Y.bugs.setup_dupe_finder();
472+ Y.on('domready', function() {
473+ Y.bugs.setup_dupes();
474+ });
475 });
476 </script>
477 </metal:block>