Merge lp:~edwin-grubbs/launchpad/bug-35728-bugtracker-overlay into lp:launchpad

Proposed by Edwin Grubbs
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: 11051
Proposed branch: lp:~edwin-grubbs/launchpad/bug-35728-bugtracker-overlay
Merge into: lp:launchpad
Diff against target: 554 lines (+325/-19)
13 files modified
lib/canonical/launchpad/icing/style-3-0.css.in (+14/-0)
lib/canonical/launchpad/webapp/launchpadform.py (+6/-1)
lib/canonical/widgets/popup.py (+50/-0)
lib/canonical/widgets/product.py (+4/-4)
lib/lp/app/templates/base-layout-macros.pt (+2/-0)
lib/lp/bugs/browser/bugtracker.py (+2/-2)
lib/lp/bugs/interfaces/bugtracker.py (+3/-2)
lib/lp/bugs/javascript/bugtracker_overlay.js (+131/-0)
lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt (+8/-4)
lib/lp/registry/javascript/milestoneoverlay.js (+2/-2)
lib/lp/registry/windmill/tests/test_add_bugtracker.py (+100/-0)
lib/lp/registry/windmill/tests/test_add_milestone.py (+2/-4)
utilities/lp-deps.py (+1/-0)
To merge this branch: bzr merge lp:~edwin-grubbs/launchpad/bug-35728-bugtracker-overlay
Reviewer Review Type Date Requested Status
Curtis Hovey (community) ui Approve
Aaron Bentley (community) Approve
Review via email: mp+28100@code.launchpad.net

Commit message

Add form overlay for creating a new bug tracker to the $project/+configure-bugtracker page.

Description of the change

Summary
-------

Turned /bugs/bugtrackers/+newbugtracker into a form overlay to be
used on the $project/+configure-bugtracker page.

Implementation details
----------------------

The ProductBugTrackerWidget now uses the BugTrackerPickerWidget as one
of its subwidgets.
    lib/canonical/widgets/product.py
    lib/canonical/widgets/popup.py
    lib/lp/bugs/javascript/bugtracker_overlay.js
    lib/lp/registry/windmill/tests/test_add_bugtracker.py
    lib/lp/app/templates/base-layout-macros.pt
    utilities/lp-deps.py

Re-ordered the field names so that the required fields are at the top. I
also put the bug tracker type select-menu at the very top, since it
makes it very clear as to what this form is all about.
    lib/lp/bugs/browser/bugtracker.py

Minor fixes.
    lib/canonical/launchpad/icing/style-3-0.css.in
    lib/canonical/launchpad/webapp/launchpadform.py
    lib/lp/bugs/interfaces/bugtracker.py

Prevent the link that displays the form overlay from following the link
when an error interrupts the event handler, since it makes hard to
figure out what the error is when you immediately jump to another page.
    lib/lp/registry/javascript/milestoneoverlay.js
    lib/lp/registry/windmill/tests/test_add_milestone.py

Tests
-----

./bin/test -vv --layer=RegistryWindmillLayer \
    -t test_add_bugtracker -t test_add_milestone

Demo and Q/A
------------

* Open http://launchpad.dev/bazaar/+configure-bugtracker
  * Click on "Register an external bug tracker"
  * Enter name, title, and location.
  * Submit form.
  * The +configure-bugtracker form should now have the
    name of the newly created bug tracker entered, and
    the radio button should be selected.

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

Please stop manually creating a unique string for the bugtracker_name. Other than that, this looks fine.

review: Approve
Revision history for this message
Curtis Hovey (sinzui) wrote :

The UI looks good and behaves well in Firefox.

It behaves oddly in Epiphany (Webkit) and I suspect there is a deeper issue than your branch at play. I can use the overlay with my mouse once. The Choose and Register links do not work (no hover, no click). I cannot correct a mistake using my mouse. I can use the keyboard.

This is still vast improvement over what can do, so I think this is good to land, but report a bug so that we can follow up on this.

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/icing/style-3-0.css.in'
2--- lib/canonical/launchpad/icing/style-3-0.css.in 2010-06-17 00:39:03 +0000
3+++ lib/canonical/launchpad/icing/style-3-0.css.in 2010-06-23 23:09:29 +0000
4@@ -126,6 +126,20 @@
5 Universal presentation
6 Block elements.
7 */
8+/* XXX EdwinGrubbs 2010-06-18 bug=570354
9+ * The PrettyOverlay css uses static values for the width, but
10+ * the overlay needs to stretch for forms with wide input fields.
11+ */
12+.yui-pretty-overlay {
13+ width: auto !important;
14+ min-width: 402px;
15+ }
16+
17+.yui-pretty-overlay #yui-pretty-overlay-modal {
18+ width: auto !important;
19+ min-width: 340px;
20+ }
21+
22 html, body {
23 font-family: "dejavu sans", "bitstream vera sans", verdana, sans-serif;
24 font-size: 93%;
25
26=== renamed directory 'lib/lp/bugs/javascript' => 'lib/canonical/launchpad/javascript/bugs'
27=== modified file 'lib/canonical/launchpad/webapp/launchpadform.py'
28--- lib/canonical/launchpad/webapp/launchpadform.py 2010-03-15 16:58:49 +0000
29+++ lib/canonical/launchpad/webapp/launchpadform.py 2010-06-23 23:09:29 +0000
30@@ -16,6 +16,7 @@
31 ]
32
33 import transaction
34+
35 from zope.interface import classImplements, providedBy
36 from zope.interface.advice import addClassAdvisor
37 from zope.event import notify
38@@ -243,7 +244,7 @@
39 self.errors.append(cleanmsg)
40
41 @staticmethod
42- def validate_none(self, action, data):
43+ def validate_none(form, action, data):
44 """Do not do any validation.
45
46 This is to be used in subclasses that have actions in which no
47@@ -473,6 +474,10 @@
48 if referrer is None:
49 # "referer" is misspelled in the HTTP specification.
50 referrer = self.request.getHeader('referer')
51+ # Windmill doesn't pass in a correct referer.
52+ if (referrer is not None
53+ and '/windmill-serv/remote.html' in referrer):
54+ referrer = None
55 else:
56 attribute_name = self.request.form.get('_return_attribute_name')
57 attribute_value = self.request.form.get('_return_attribute_value')
58
59=== modified file 'lib/canonical/widgets/popup.py'
60--- lib/canonical/widgets/popup.py 2010-01-29 10:52:58 +0000
61+++ lib/canonical/widgets/popup.py 2010-06-23 23:09:29 +0000
62@@ -180,6 +180,56 @@
63 return '/people/'
64
65
66+class BugTrackerPickerWidget(VocabularyPickerWidget):
67+ link_template = """
68+ or (<a id="%(activator_id)s" href="/bugs/bugtrackers/+newbugtracker"
69+ >Register an external bug tracker&hellip;</a>)
70+ <script>
71+ LPS.use('lp.bugs.bugtracker_overlay', function(Y) {
72+ if (Y.UA.ie) {
73+ return;
74+ }
75+ Y.on('domready', function () {
76+ // After the success handler finishes, it calls the
77+ // next_step function.
78+ var next_step = function(bug_tracker) {
79+ // Fill in the text field with either the name of
80+ // the newly created bug tracker or the name of an
81+ // existing bug tracker whose base_url matches.
82+ var bugtracker_text_box = Y.one(
83+ Y.DOM.byId('field.bugtracker.bugtracker'));
84+ if (bugtracker_text_box !== null) {
85+ bugtracker_text_box.set(
86+ 'value', bug_tracker.get('name'));
87+ // It doesn't appear possible to use onChange
88+ // event, so the onKeyPress event is explicitely
89+ // fired here.
90+ if (bugtracker_text_box.get('onkeypress')) {
91+ bugtracker_text_box.get('onkeypress')();
92+ }
93+ bugtracker_text_box.scrollIntoView();
94+ }
95+ }
96+ Y.lp.bugs.bugtracker_overlay.attach_widget({
97+ activate_node: Y.get('#%(activator_id)s'),
98+ next_step: next_step
99+ });
100+ });
101+ });
102+ </script>
103+ """
104+
105+ def chooseLink(self):
106+ link = super(BugTrackerPickerWidget, self).chooseLink()
107+ link += self.link_template % dict(
108+ activator_id='create-bugtracker-link')
109+ return link
110+
111+ @property
112+ def nonajax_uri(self):
113+ return '/bugs/bugtrackers/'
114+
115+
116 class SearchForUpstreamPopupWidget(VocabularyPickerWidget):
117 """A SinglePopupWidget with a custom error message.
118
119
120=== modified file 'lib/canonical/widgets/product.py'
121--- lib/canonical/widgets/product.py 2010-06-16 16:56:58 +0000
122+++ lib/canonical/widgets/product.py 2010-06-23 23:09:29 +0000
123@@ -37,7 +37,7 @@
124 from canonical.launchpad.webapp import canonical_url
125 from canonical.widgets.itemswidgets import (
126 CheckBoxMatrixWidget, LaunchpadRadioWidget)
127-from canonical.widgets.popup import VocabularyPickerWidget
128+from canonical.widgets.popup import BugTrackerPickerWidget
129 from canonical.widgets.textwidgets import (
130 LowerCaseTextWidget, StrippedTextWidget)
131 from lp.registry.interfaces.product import IProduct
132@@ -57,7 +57,7 @@
133 self.bugtracker = Choice(
134 vocabulary="WebBugTracker",
135 __name__='bugtracker')
136- self.bugtracker_widget = CustomWidgetFactory(VocabularyPickerWidget)
137+ self.bugtracker_widget = CustomWidgetFactory(BugTrackerPickerWidget)
138 setUpWidget(
139 self, 'bugtracker', self.bugtracker, IInputWidget,
140 prefix=self.name, value=field.context.bugtracker,
141@@ -82,7 +82,7 @@
142 if self.upstream_email_address_widget.extra is None:
143 self.upstream_email_address_widget.extra = ''
144 self.upstream_email_address_widget.extra += (
145- ' onkeypress="selectWidget(\'%s.3\', event);"' % self.name)
146+ ''' onkeypress="selectWidget('%s.3', event);"\n''' % self.name)
147
148 def _renderItem(self, index, text, value, name, cssClass, checked=False):
149 # This form has a custom need to render their labels separately,
150@@ -192,7 +192,7 @@
151 self.upstream_email_address_widget.setRenderedValue(
152 value.baseurl.lstrip('mailto:'))
153 external_bugtracker_email_text = "%s %s" % (
154- self._renderLabel("By emailing an upstream bug contact:", 3),
155+ self._renderLabel("By emailing an upstream bug contact:\n", 3),
156 self.upstream_email_address_widget())
157 external_bugtracker_email_arguments = dict(
158 index=3, text=external_bugtracker_email_text,
159
160=== modified file 'lib/lp/app/templates/base-layout-macros.pt'
161--- lib/lp/app/templates/base-layout-macros.pt 2010-06-17 19:25:53 +0000
162+++ lib/lp/app/templates/base-layout-macros.pt 2010-06-23 23:09:29 +0000
163@@ -181,6 +181,8 @@
164 <script type="text/javascript"
165 tal:attributes="src string:${lp_js}/lp/mapping.js"></script>
166 <script type="text/javascript"
167+ tal:attributes="src string:${lp_js}/bugs/bugtracker_overlay.js"></script>
168+ <script type="text/javascript"
169 tal:attributes="src string:${lp_js}/registry/milestoneoverlay.js"></script>
170 <script type="text/javascript"
171 tal:attributes="src string:${lp_js}/registry/milestonetable.js"></script>
172
173=== modified file 'lib/lp/bugs/browser/bugtracker.py'
174--- lib/lp/bugs/browser/bugtracker.py 2009-09-04 08:17:15 +0000
175+++ lib/lp/bugs/browser/bugtracker.py 2010-06-23 23:09:29 +0000
176@@ -82,8 +82,8 @@
177 page_title = u"Register an external bug tracker"
178 schema = IBugTracker
179 label = page_title
180- field_names = ['name', 'bugtrackertype', 'title', 'summary',
181- 'baseurl', 'contactdetails']
182+ field_names = ['bugtrackertype', 'name', 'title', 'baseurl', 'summary',
183+ 'contactdetails']
184
185 def setUpWidgets(self, context=None):
186 # We only show those bug tracker types for which there can be
187
188=== modified file 'lib/lp/bugs/interfaces/bugtracker.py'
189--- lib/lp/bugs/interfaces/bugtracker.py 2010-04-15 08:45:31 +0000
190+++ lib/lp/bugs/interfaces/bugtracker.py 2010-06-23 23:09:29 +0000
191@@ -68,7 +68,8 @@
192 bugtracker = getUtility(IBugTrackerSet).queryByBaseURL(input)
193 if bugtracker is not None and bugtracker != self.context:
194 raise LaunchpadValidationError(
195- "%s is already registered in Launchpad." % input)
196+ '%s is already registered in Launchpad as "%s" (%s).'
197+ % (input, bugtracker.title, bugtracker.name))
198
199
200 class BugTrackerType(DBEnumeratedType):
201@@ -183,7 +184,7 @@
202 BugTrackerNameField(
203 title=_('Name'),
204 constraint=name_validator,
205- description=_('An URL-friendly name for the bug tracker, '
206+ description=_('A URL-friendly name for the bug tracker, '
207 'such as "mozilla-bugs".')))
208 title = exported(
209 TextLine(
210
211=== added directory 'lib/lp/bugs/javascript'
212=== added file 'lib/lp/bugs/javascript/bugtracker_overlay.js'
213--- lib/lp/bugs/javascript/bugtracker_overlay.js 1970-01-01 00:00:00 +0000
214+++ lib/lp/bugs/javascript/bugtracker_overlay.js 2010-06-23 23:09:29 +0000
215@@ -0,0 +1,131 @@
216+/* Copyright 2010 Canonical Ltd. This software is licensed under the
217+ * GNU Affero General Public License version 3 (see the file LICENSE).
218+ *
219+ * A bugtracker form overlay that can create a bugtracker within any page.
220+ *
221+ * @namespace Y.lp.bugs.bugtracker_overlay
222+ * @requires dom, node, io-base, lazr.anim, lazr.formoverlay
223+ */
224+YUI.add('lp.bugs.bugtracker_overlay', function(Y) {
225+ Y.log('loading lp.bugs.bugtracker_overlay');
226+ var namespace = Y.namespace('lp.bugs.bugtracker_overlay');
227+
228+ var bugtracker_form;
229+ var next_step;
230+
231+ var save_new_bugtracker = function(data) {
232+
233+ var parameters = {
234+ bug_tracker_type: data['field.bugtrackertype'][0],
235+ name: data['field.name'][0].toLowerCase(),
236+ title: data['field.title'][0],
237+ base_url: data['field.baseurl'][0],
238+ summary: data['field.summary'][0],
239+ contact_details: data['field.contactdetails'][0]
240+ };
241+
242+ var finish_new_bugtracker = function(entry) {
243+ bugtracker_form.clearError();
244+ bugtracker_form.hide();
245+ // Reset the HTML form inside the widget.
246+ bugtracker_form.get('contentBox').one('form').reset();
247+ next_step(entry);
248+ };
249+
250+ var client = new LP.client.Launchpad();
251+ client.named_post('/bugs/bugtrackers', 'ensureBugTracker', {
252+ parameters: parameters,
253+ on: {
254+ success: finish_new_bugtracker,
255+ failure: function (ignore, response, args) {
256+ var error_box = Y.one('#bugtracker-error');
257+ var error_message = response.statusText + '\n\n' +
258+ response.responseText;
259+ bugtracker_form.showError(error_message);
260+ // XXX EdwinGrubbs 2007-06-18 bug=596025
261+ // This should be done by FormOverlay.showError().
262+ bugtracker_form.error_node.scrollIntoView();
263+ }
264+ }
265+ });
266+ };
267+
268+
269+ var setup_bugtracker_form = function () {
270+ var form_submit_button = Y.Node.create(
271+ '<input type="submit" name="field.actions.register" ' +
272+ 'id="formoverlay-add-bugtracker" value="Create bug tracker"/>');
273+ bugtracker_form = new Y.lazr.FormOverlay({
274+ headerContent: '<h2>Create Bug Tracker</h2>',
275+ form_submit_button: form_submit_button,
276+ centered: true,
277+ form_submit_callback: save_new_bugtracker,
278+ visible: false
279+ });
280+ bugtracker_form.loadFormContentAndRender(
281+ '/bugs/bugtrackers/+newbugtracker/++form++');
282+ // XXX EdwinGrubbs 2010-06-18 bug=596130
283+ // render() and show() will actually be called before the
284+ // asynchronous io call finishes, so the widget appears first
285+ // without any content. However, this is better than loading the
286+ // form every time the page loads despite the form overlay being
287+ // used rarely.
288+ bugtracker_form.render();
289+ bugtracker_form.show();
290+ };
291+
292+ var show_bugtracker_form = function(e) {
293+ e.preventDefault();
294+ if (bugtracker_form) {
295+ bugtracker_form.show();
296+ } else {
297+ // This function call is asynchronous, so we can move
298+ // bugtracker_form.show() below it.
299+ setup_bugtracker_form();
300+ }
301+
302+ // XXX EdwinGrubbs 2010-06-18 bug=596113
303+ // FormOverlay calls centered(), which can cause this tall form
304+ // to be position where the top of the form is no longer
305+ // accessible.
306+ var bounding_box = bugtracker_form.get('boundingBox');
307+ var min_top = 10;
308+ if (bounding_box.get('offsetTop') < min_top) {
309+ bounding_box.setStyle('top', min_top + 'px');
310+ }
311+ };
312+
313+ /**
314+ * Attaches a bugtracker form overlay widget to an element.
315+ *
316+ * @method attach_widget
317+ * @param {Object} config Object literal of config name/value pairs.
318+ * activate_node is the node that shows the form
319+ * when it is clicked.
320+ * next_step is the function to be called after
321+ * the bugtracker is created.
322+ */
323+ namespace.attach_widget = function(config) {
324+ Y.log('lp.bugs.bugtracker_overlay.attach_widget()');
325+ if (Y.UA.ie) {
326+ return;
327+ }
328+ if (config === undefined) {
329+ throw new Error(
330+ "Missing attach_widget config for bugtracker_overlay.");
331+ }
332+ if (config.activate_node === undefined ||
333+ config.next_step === undefined) {
334+ throw new Error(
335+ "attach_widget config for bugtracker_overlay has " +
336+ "undefined properties.");
337+ }
338+ next_step = config.next_step;
339+ Y.log('lp.bugs.bugtracker_overlay.attach_widget() setup onclick');
340+ config.activate_node.addClass('js-action');
341+ config.activate_node.on('click', show_bugtracker_form);
342+ };
343+
344+}, "0.1", {"requires": [
345+ "dom", "node", "io-base", "lazr.anim", "lazr.formoverlay", "lp.calendar"
346+ ]});
347
348=== added directory 'lib/lp/bugs/javascript/tests'
349=== modified file 'lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt'
350--- lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt 2010-06-16 15:56:08 +0000
351+++ lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt 2010-06-23 23:09:29 +0000
352@@ -72,7 +72,8 @@
353 >>> for message in find_tags_by_class(user_browser.contents, 'message'):
354 ... print extract_text(message)
355 There is 1 error.
356- http://bugzilla.mozilla.org/ is already registered in Launchpad.
357+ http://bugzilla.mozilla.org/ is already registered in Launchpad
358+ as "The Mozilla.org Bug Tracker" (mozilla.org).
359
360 The same happens if the requested URL is aliased to another bug
361 tracker. Aliases can be edited once a bug tracker has been added, but
362@@ -94,7 +95,8 @@
363 >>> for message in find_tags_by_class(user_browser.contents, 'message'):
364 ... print extract_text(message)
365 There is 1 error.
366- http://alias.example.com/ is already registered in Launchpad.
367+ http://alias.example.com/ is already registered in Launchpad
368+ as "GnomeGBug GTracker" (gnome-bugzilla).
369
370 After successfully registering the bug tracker, the user is redirected
371 to the bug tracker page.
372@@ -201,7 +203,8 @@
373 >>> for message in get_feedback_messages(user_browser.contents):
374 ... print message
375 There is 1 error.
376- http://bugzilla.mozilla.org/ is already registered in Launchpad.
377+ http://bugzilla.mozilla.org/ is already registered in Launchpad
378+ as "The Mozilla.org Bug Tracker" (mozilla.org).
379
380 If the user inadvertently enters an invalid URL, they are shown an
381 informative error message explaining why it is invalid.
382@@ -304,7 +307,8 @@
383 >>> for message in get_feedback_messages(user_browser.contents):
384 ... print message
385 There is 1 error.
386- http://bugzilla.mozilla.org/ is already registered in Launchpad.
387+ http://bugzilla.mozilla.org/ is already registered in Launchpad
388+ as "The Mozilla.org Bug Tracker" (mozilla.org).
389
390 Multiple aliases can be entered by separating URLs with whitespace.
391
392
393=== modified file 'lib/lp/registry/javascript/milestoneoverlay.js'
394--- lib/lp/registry/javascript/milestoneoverlay.js 2010-04-29 15:21:05 +0000
395+++ lib/lp/registry/javascript/milestoneoverlay.js 2010-06-23 23:09:29 +0000
396@@ -71,7 +71,8 @@
397 milestone_form.show();
398 };
399
400- show_milestone_form = function(e) {
401+ var show_milestone_form = function(e) {
402+ e.preventDefault();
403 if (milestone_form) {
404 milestone_form.show();
405 } else {
406@@ -79,7 +80,6 @@
407 // milestone_form.show() below it.
408 setup_milestone_form();
409 }
410- e.preventDefault();
411 };
412
413 /**
414
415=== added file 'lib/lp/registry/windmill/tests/test_add_bugtracker.py'
416--- lib/lp/registry/windmill/tests/test_add_bugtracker.py 1970-01-01 00:00:00 +0000
417+++ lib/lp/registry/windmill/tests/test_add_bugtracker.py 2010-06-23 23:09:29 +0000
418@@ -0,0 +1,100 @@
419+# Copyright 2009 Canonical Ltd. This software is licensed under the
420+# GNU Affero General Public License version 3 (see the file LICENSE).
421+
422+"""Test adding bug tracker in formoverlay."""
423+
424+__metaclass__ = type
425+__all__ = []
426+
427+import unittest
428+
429+from canonical.launchpad.windmill.testing import lpuser
430+
431+from lp.registry.windmill.testing import RegistryWindmillLayer
432+from lp.testing import WindmillTestCase
433+
434+
435+def test_inline_add_bugtracker(client, url, name=None, suite='bugtracker',
436+ user=lpuser.FOO_BAR):
437+ """Test the form overlay for adding a bugtracker.
438+
439+ :param name: Name of the test.
440+ :param url: Starting url.
441+ :param suite: The suite in which this test is part of.
442+ :param user: The user who should be logged in.
443+ """
444+ bugtracker_name = u'FOObar'
445+ title = u'\xdf-title-%s' % bugtracker_name
446+ location = u'http://example.com/%s' % bugtracker_name
447+
448+ user.ensure_login(client)
449+ client.open(url=url)
450+ client.waits.forPageLoad(timeout=u'20000')
451+
452+ client.waits.forElement(id=u'create-bugtracker-link')
453+
454+ # Click the "Create external bug tracker" link.
455+ client.click(id=u'create-bugtracker-link')
456+
457+ # Submit bugtracker form.
458+ client.waits.forElement(id=u'field.name')
459+ client.type(id='field.name', text=bugtracker_name)
460+ client.type(id='field.title', text=title)
461+ client.type(id='field.baseurl', text=location)
462+ client.click(id=u'formoverlay-add-bugtracker')
463+
464+ # Verify that the bugtracker name was entered in the text box.
465+ client.waits.sleep(milliseconds='1000')
466+ client.asserts.assertProperty(
467+ id="field.bugtracker.bugtracker",
468+ validator='value|%s' % bugtracker_name.lower())
469+ client.asserts.assertChecked(id="field.bugtracker.2")
470+
471+ # Verify error message when trying to create a bugtracker with a
472+ # conflicting name.
473+ client.click(id=u'create-bugtracker-link')
474+ client.waits.forElement(id=u'field.name')
475+ client.type(id='field.name', text=bugtracker_name)
476+ client.click(id=u'formoverlay-add-bugtracker')
477+ client.waits.forElement(
478+ xpath="//div[contains(@class, 'yui-lazr-formoverlay-errors')]/ul/li")
479+ client.asserts.assertTextIn(
480+ classname='yui-lazr-formoverlay-errors',
481+ validator='name: %s is already in use' % bugtracker_name.lower())
482+ client.click(classname='close-button')
483+
484+ # Configure bug tracker for the project.
485+ client.click(id=u'field.actions.change')
486+
487+ # You should now be on the project index page.
488+ client.waits.forElement(
489+ xpath="//a[contains(@class, 'menu-link-configure_bugtracker')]")
490+ client.click(
491+ xpath="//a[contains(@class, 'menu-link-configure_bugtracker')]")
492+
493+ # Verify that the new bug tracker was configured for this project.
494+ client.waits.forElement(id="field.bugtracker.bugtracker")
495+ client.asserts.assertProperty(
496+ id="field.bugtracker.bugtracker",
497+ validator='value|%s' % bugtracker_name.lower())
498+ client.asserts.assertChecked(id="field.bugtracker.2")
499+
500+
501+class TestAddBugTracker(WindmillTestCase):
502+ """Test form overlay widget for adding a bug tracker."""
503+
504+ # This test doesn't run well in the BugsWindmillLayer, since
505+ # submitting the +configure-bugtracker form takes you back to
506+ # the project index page, which is not on the bugs.launchpad.dev.
507+ layer = RegistryWindmillLayer
508+ suite_name = 'AddBugTracker'
509+
510+ def test_adding_bugtracker_for_project(self):
511+ test_inline_add_bugtracker(
512+ self.client,
513+ url='http://launchpad.dev:8085/bzr/+configure-bugtracker',
514+ name='test_inline_add_bugtracker_for_project')
515+
516+
517+def test_suite():
518+ return unittest.TestLoader().loadTestsFromName(__name__)
519
520=== modified file 'lib/lp/registry/windmill/tests/test_add_milestone.py'
521--- lib/lp/registry/windmill/tests/test_add_milestone.py 2010-02-01 18:37:00 +0000
522+++ lib/lp/registry/windmill/tests/test_add_milestone.py 2010-06-23 23:09:29 +0000
523@@ -1,7 +1,7 @@
524 # Copyright 2009 Canonical Ltd. This software is licensed under the
525 # GNU Affero General Public License version 3 (see the file LICENSE).
526
527-"""Test for translation import queue behaviour."""
528+"""Test adding milestone in formoverlay."""
529
530 __metaclass__ = type
531 __all__ = []
532@@ -24,9 +24,7 @@
533 :param suite: The suite in which this test is part of.
534 :param user: The user who should be logged in.
535 """
536- # Ensure that the milestone name doesn't conflict with previous
537- # test runs, and test that it correctly lowercases the name.
538- milestone_name = u'FOObar%x' % int(time.time())
539+ milestone_name = u'FOObar'
540 code_name = u'code-%s' % milestone_name
541
542 user.ensure_login(client)
543
544=== modified file 'utilities/lp-deps.py'
545--- utilities/lp-deps.py 2010-06-14 22:18:14 +0000
546+++ utilities/lp-deps.py 2010-06-23 23:09:29 +0000
547@@ -20,6 +20,7 @@
548 # JS_DIRSET is a tuple of the dir where the code exists, and the name of the
549 # symlink it should be linked as in the icing build directory.
550 JS_DIRSET = [
551+ (os.path.join('lib', 'lp', 'bugs', 'javascript'), 'bugs'),
552 (os.path.join('lib', 'lp', 'code', 'javascript'), 'code'),
553 (os.path.join('lib', 'lp', 'registry', 'javascript'), 'registry'),
554 (os.path.join('lib', 'lp', 'translations', 'javascript'), 'translations'),