Merge lp:~salgado/launchpad/bug-482235 into lp:launchpad

Proposed by Guilherme Salgado
Status: Merged
Merged at revision: not available
Proposed branch: lp:~salgado/launchpad/bug-482235
Merge into: lp:launchpad
Diff against target: 270 lines (+87/-33)
10 files modified
Makefile (+0/-3)
lib/canonical/launchpad/javascript/bugs/bugtask-index.js (+6/-6)
lib/canonical/launchpad/javascript/code/codereview.js (+5/-7)
lib/canonical/launchpad/javascript/lp/picker.js (+11/-9)
lib/canonical/launchpad/javascript/registry/team.js (+2/-1)
lib/canonical/launchpad/windmill/testing/lpuser.py (+2/-0)
lib/canonical/widgets/popup.py (+1/-1)
lib/canonical/widgets/templates/vocabulary-picker.js.template (+4/-5)
lib/lp/registry/windmill/tests/test_person_picker.py (+55/-0)
versions.cfg (+1/-1)
To merge this branch: bzr merge lp:~salgado/launchpad/bug-482235
Reviewer Review Type Date Requested Status
Māris Fogels (community) Approve
Canonical Launchpad Engineering Pending
Review via email: mp+17585@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Guilherme Salgado (salgado) wrote :

= Summary =

Change our make_picker() function to use the new
Y.lazr.TextFieldPickerPlugin plugin so that the picker's default value
is taken from the input field associated with it.

Also updates to a newer lazr-js, containing just one extra revision with
the fix for bug 482235.

== Tests ==

./bin/test test_person_picker

== Demo and Q/A ==

= Launchpad lint =

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

Linting changed files:
  versions.cfg
  lib/canonical/widgets/templates/vocabulary-picker.js
  lib/canonical/launchpad/javascript/lp/picker.js
  lib/canonical/launchpad/javascript/bugs/bugtask-index.js
  lib/canonical/launchpad/javascript/code/codereview.js
  lib/lp/registry/windmill/tests/test_person_picker.py

== JSLint notices ==
jslint: No problem found in '/home/salgado/devel/launchpad/bug-482235/lib/canonical/launchpad/javascript/bugs/bugtask-index.js'.

jslint: No problem found in '/home/salgado/devel/launchpad/bug-482235/lib/canonical/launchpad/javascript/code/codereview.js'.

jslint: No problem found in '/home/salgado/devel/launchpad/bug-482235/lib/canonical/launchpad/javascript/lp/picker.js'.

jslint: Lint found in '/home/salgado/devel/launchpad/bug-482235/lib/canonical/widgets/templates/vocabulary-picker.js':
Line 8 character 16: Expected an identifier and instead saw '%'.
    var args = %s;

Line 8 character 16: Stopping, unable to continue. (14% scanned).

The lint error above is expected because that file is actually a
template used from within python code.

Revision history for this message
Māris Fogels (mars) wrote :

Hi Salgado,

Your changes look good, and thanks for updating lazr-js.

I see one coding convention that needs fixing, and had some small questions about implementation details.

• line 62: By convention, the optional 'save' function should be a key in the config dictionary. Only the simplest of functions use optional positional args in JavaScript, as getting around them is a pain (unlike in Python).
• line 78: Might be better to use Y.Lang.isFunction()
• line 116: Does YUI understand field IDs with dots in them yet? If it does, then the input_element string construction can be greatly simplified using a '#'
• line 163: What is the 'shadow' element? Some invisible element in the DOM? Just asking to make sure that 'shadow' is a behavioural object, not a presentation object :)
• line 170: Why do you have a hard sleep here?
• Regarding the JSLint error: should we rename that file to .js.pt or .js.template, as it isn't a valid JavaScript file?

Maris

review: Needs Fixing
Revision history for this message
Guilherme Salgado (salgado) wrote :

On Mon, 2010-01-18 at 20:22 +0000, Māris Fogels wrote:
> Review: Needs Fixing
> Hi Salgado,
>
> Your changes look good, and thanks for updating lazr-js.

Thanks for the review. In the future, would you mind including the diff
in your review and adding your comments around the code you're referring
to? That makes the conversation a lot smoother as we don't have to keep
going back to the diff for context.

>
> I see one coding convention that needs fixing, and had some small
> questions about implementation details.
>
> • line 62: By convention, the optional 'save' function should be a key
> in the config dictionary. Only the simplest of functions use optional
> positional args in JavaScript, as getting around them is a pain
> (unlike in Python).

While I was doing this change I noticed that both Y.lp.picker.create and
Y.lp.picker.addPickerPatcher treat the config argument as optional
(although it's not documented). I don't see a reason for that and all
their callsites pass in a config dictionary, as expected. Would it be
OK for me to change both methods to make the config required?

> • line 78: Might be better to use Y.Lang.isFunction()

Sure, I can do that.

> • line 116: Does YUI understand field IDs with dots in them yet? If
> it does, then the input_element string construction can be greatly
> simplified using a '#'

It doesn't yet, I tried. :/

> • line 163: What is the 'shadow' element? Some invisible element in
> the DOM? Just asking to make sure that 'shadow' is a behavioural
> object, not a presentation object :)

I *think* it's the shadow around the overlay; I've used it because it
was one of the few elements in the overlay that had an ID. But maybe I
didn't look carefully for other elements with an ID there.

> • line 170: Why do you have a hard sleep here?

Because that triggers a search, which might take a while to complete.

I think I read somewhere that the assertProperty() call will also wait
for the element to show up, in which case the sleep() would be
unnecessary. Is that why you asked?

> • Regarding the JSLint error: should we rename that file to .js.pt
> or .js.template, as it isn't a valid JavaScript file?
>

Yeah, .js.template sounds good to me.

--
Guilherme Salgado <email address hidden>

Revision history for this message
Māris Fogels (mars) wrote :

On Tue, Jan 19, 2010 at 7:15 AM, Guilherme Salgado <email address hidden> wrote:
> On Mon, 2010-01-18 at 20:22 +0000, Māris Fogels wrote:
>> Review: Needs Fixing
>> Hi Salgado,
>>
>> Your changes look good, and thanks for updating lazr-js.
>
> Thanks for the review.  In the future, would you mind including the diff
> in your review and adding your comments around the code you're referring
> to?  That makes the conversation a lot smoother as we don't have to keep
> going back to the diff for context.
>

Ok. For JS reviews I find in-context diffs don't work, as I often spot problems in multiple places, with multiple problems per line. As a result, I note them like this:

  line 23,45,67: foo is missing
  line 23: bar should be baz

>>
>> I see one coding convention that needs fixing, and had some small
>> questions about implementation details.
>>
>> • line 62: By convention, the optional 'save' function should be a key
>> in the config dictionary.  Only the simplest of functions use optional
>> positional args in JavaScript, as getting around them is a pain
>> (unlike in Python).
>
> While I was doing this change I noticed that both Y.lp.picker.create and
> Y.lp.picker.addPickerPatcher treat the config argument as optional
> (although it's not documented).  I don't see a reason for that and all
> their callsites pass in a config dictionary, as expected.  Would it be
> OK for me to change both methods to make the config required?

Sometimes the config only contains optional settings, so you could obviously mark the entire config as an optional argument. However, for some objects, like the Y.Anim() object, the config contains required settings, and is *not* optional.

In this case, I leave the judgement up to you.

>> • line 163: What is the 'shadow' element?  Some invisible element in
>> the DOM?  Just asking to make sure that 'shadow' is a behavioural
>> object, not a presentation object :)
>
> I *think* it's the shadow around the overlay; I've used it because it
> was one of the few elements in the overlay that had an ID. But maybe I
> didn't look carefully for other elements with an ID there.

You might want to ask Tom how the bugs team tests for overlay presence and absence.

There are CSS classes associated with overlay state you may be able to use instead: yui-mywidgetname-hidden and yui-mywidget-disabled (also yui-mywidget-focused)

http://developer.yahoo.com/yui/3/widget/#CSS

>
>> • line 170: Why do you have a hard sleep here?
>
> Because that triggers a search, which might take a while to complete.
>
> I think I read somewhere that the assertProperty() call will also wait
> for the element to show up, in which case the sleep() would be
> unnecessary. Is that why you asked?
>

Nope, it is just habit to question a hard sleep like that. Usually you wait for an element in the DOM to show up to signal that the search is done.

Ok, this code looks good to me. Change what you desire, and r=mars.

Thanks,
Maris

Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (8.3 KiB)

On Tue, 2010-01-19 at 15:12 +0000, Māris Fogels wrote:
>
> On Tue, Jan 19, 2010 at 7:15 AM, Guilherme Salgado <email address hidden> wrote:
> > On Mon, 2010-01-18 at 20:22 +0000, Māris Fogels wrote:
> >> Review: Needs Fixing
> >> Hi Salgado,
> >>
> >> Your changes look good, and thanks for updating lazr-js.
> >
> > Thanks for the review. In the future, would you mind including the diff
> > in your review and adding your comments around the code you're referring
> > to? That makes the conversation a lot smoother as we don't have to keep
> > going back to the diff for context.
> >
>
> Ok. For JS reviews I find in-context diffs don't work, as I often

I don't think JS reviews are intrinsically different from, say, python
reviews.

> spot problems in multiple places, with multiple problems per line.
> As a result, I note them like this:
>
> line 23,45,67: foo is missing
> line 23: bar should be baz

When doing reviews I often spot problems that apply to multiple lines as
well, and in those cases I just copy-n-paste the comment or make a
reference to it. But it'd also be fine to have it at the top of the
message, I think, as long as the diff is included.

The fact that the diff is not included here means we have to switch back
and forth between the browser and the email client, which is a bit
disturbing.

>
> >>
> >> I see one coding convention that needs fixing, and had some small
> >> questions about implementation details.
> >>
> >> • line 62: By convention, the optional 'save' function should be a key
> >> in the config dictionary. Only the simplest of functions use optional
> >> positional args in JavaScript, as getting around them is a pain
> >> (unlike in Python).
> >
> > While I was doing this change I noticed that both Y.lp.picker.create and
> > Y.lp.picker.addPickerPatcher treat the config argument as optional
> > (although it's not documented). I don't see a reason for that and all
> > their callsites pass in a config dictionary, as expected. Would it be
> > OK for me to change both methods to make the config required?
>
> Sometimes the config only contains optional settings, so you could obviously mark the entire config as an optional argument. However, for some objects, like the Y.Anim() object, the config contains required settings, and is *not* optional.
>
> In this case, I leave the judgement up to you.

I changed the docs to state that the config is optional.

>
>
> >> • line 163: What is the 'shadow' element? Some invisible element in
> >> the DOM? Just asking to make sure that 'shadow' is a behavioural
> >> object, not a presentation object :)
> >
> > I *think* it's the shadow around the overlay; I've used it because it
> > was one of the few elements in the overlay that had an ID. But maybe I
> > didn't look carefully for other elements with an ID there.
>
> You might want to ask Tom how the bugs team tests for overlay presence
> and absence.
>
> There are CSS classes associated with overlay state you may be able to
> use instead: yui-mywidgetname-hidden and yui-mywidget-disabled (also
> yui-mywidget-focused)

They use that on huge conditional xpaths, which look rather ugly and
hard to maintain -- that...

Read more...

Revision history for this message
Māris Fogels (mars) wrote :

Hi Salgado,

Your changes look good. The optional argument looks much cleaner. Thanks! r=mars

Maris

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2010-01-22 06:24:01 +0000
3+++ Makefile 2010-02-01 14:05:26 +0000
4@@ -357,9 +357,6 @@
5 reload-apache: enable-apache-launchpad
6 /etc/init.d/apache2 restart
7
8-static:
9- $(PY) scripts/make-static.py
10-
11 TAGS: compile
12 # emacs tags
13 bin/tags -e
14
15=== modified file 'lib/canonical/launchpad/javascript/bugs/bugtask-index.js'
16--- lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2010-01-21 21:54:12 +0000
17+++ lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2010-02-01 14:05:26 +0000
18@@ -251,10 +251,10 @@
19 picker_activator: '.menu-link-addsubscriber'
20 };
21
22- var picker = Y.lp.picker.create(
23- 'ValidPersonOrTeam',
24- function(result) { subscribe_someone_else(result, subscription); },
25- config);
26+ config.save = function(result) {
27+ subscribe_someone_else(result, subscription);
28+ };
29+ var picker = Y.lp.picker.create('ValidPersonOrTeam', config);
30 }
31
32 /*
33@@ -606,8 +606,8 @@
34 picker_activator: '.menu-link-addbranch'
35 };
36
37- var picker = Y.lp.picker.create(
38- 'Branch', get_branch_and_link_to_bug, config);
39+ config.save = get_branch_and_link_to_bug;
40+ var picker = Y.lp.picker.create('Branch', config);
41 }
42 }
43
44
45=== modified file 'lib/canonical/launchpad/javascript/code/codereview.js'
46--- lib/canonical/launchpad/javascript/code/codereview.js 2010-01-15 04:34:57 +0000
47+++ lib/canonical/launchpad/javascript/code/codereview.js 2010-02-01 14:05:26 +0000
48@@ -139,13 +139,11 @@
49 step_title: 'Search'
50 };
51
52- reviewer_picker = Y.lp.picker.create(
53- 'ValidPersonOrTeam',
54- function(result) {
55- var review_type = Y.one("[id=field.review_type]").get('value');
56- request_reviewer(result, review_type);
57- },
58- config);
59+ config.save = function(result) {
60+ var review_type = Y.one("[id=field.review_type]").get('value');
61+ request_reviewer(result, review_type);
62+ };
63+ reviewer_picker = Y.lp.picker.create('ValidPersonOrTeam', config);
64 reviewer_picker.set('footer_slot', Y.Node.create([
65 '<div>',
66 '<div style="float: left; padding-right: 9px;">',
67
68=== modified file 'lib/canonical/launchpad/javascript/lp/picker.js'
69--- lib/canonical/launchpad/javascript/lp/picker.js 2009-12-09 15:07:39 +0000
70+++ lib/canonical/launchpad/javascript/lp/picker.js 2010-02-01 14:05:26 +0000
71@@ -150,7 +150,8 @@
72 });
73 };
74
75- var picker = Y.lp.picker.create(vocabulary, save, config);
76+ config.save = save;
77+ var picker = Y.lp.picker.create(vocabulary, config);
78 picker._resource_uri = resource_uri;
79 var extra_buttons = Y.Node.create(
80 '<div style="text-align: center; height: 3em; ' +
81@@ -192,17 +193,18 @@
82 * @requires dom, dump, lazr.overlay, lazr.picker
83 * @method create
84 * @param {String} vocabulary Name of the vocabulary to query.
85- * @param {Function} save Function which takes a single string argument.
86- * @param {Object} config Object literal of config name/value pairs.
87+ * @param {Object} config Optional Object literal of config name/value pairs.
88 * config.header is a line of text at the top of
89 * the widget.
90 * config.step_title overrides the subtitle.
91- * description strings.
92+ * config.save is a Function (optional) which takes
93+ * a single string argument.
94 */
95-Y.lp.picker.create = function (vocabulary, save, config) {
96+Y.lp.picker.create = function (vocabulary, config) {
97 if (Y.UA.ie) {
98 return;
99 }
100+
101 if (config !== undefined) {
102 var header = 'Choose an item.';
103 if (config.header !== undefined) {
104@@ -233,13 +235,13 @@
105 zIndex: 1000,
106 visible: false
107 });
108- var picker = new Y.Picker(new_config);
109+ var picker = new Y.lazr.Picker(new_config);
110
111 picker.subscribe('save', function (e) {
112 Y.log('Got save event.');
113- // Y.one() uses CSS3 selectors which don't work with ids containing
114- // a period, so we have to use Y.DOM.byId().
115- save(e.details[Y.Picker.SAVE_RESULT]);
116+ if (Y.Lang.isFunction(config.save)) {
117+ config.save(e.details[Y.lazr.Picker.SAVE_RESULT]);
118+ }
119 });
120
121 picker.subscribe('cancel', function (e) {
122
123=== modified file 'lib/canonical/launchpad/javascript/registry/team.js'
124--- lib/canonical/launchpad/javascript/registry/team.js 2009-12-22 20:30:39 +0000
125+++ lib/canonical/launchpad/javascript/registry/team.js 2010-02-01 14:05:26 +0000
126@@ -25,7 +25,8 @@
127 picker_activator: '.menu-link-add_member'
128 };
129
130- Y.lp.picker.create('ValidTeamMember', _add_member, config);
131+ config.save = _add_member;
132+ Y.lp.picker.create('ValidTeamMember', config);
133 };
134
135 var _add_member = function(selected_person) {
136
137=== modified file 'lib/canonical/launchpad/windmill/testing/lpuser.py'
138--- lib/canonical/launchpad/windmill/testing/lpuser.py 2009-10-27 13:01:29 +0000
139+++ lib/canonical/launchpad/windmill/testing/lpuser.py 2010-02-01 14:05:26 +0000
140@@ -19,6 +19,8 @@
141
142 def ensure_login(self, client):
143 """Ensure that this user is logged on the page under windmill."""
144+ # Refresh the page to make sure we're not fooled by a stale one.
145+ client.refresh()
146 client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
147 result = client.asserts.assertNode(
148 name=u'loginpage_submit_login', assertion=False)
149
150=== modified file 'lib/canonical/widgets/popup.py'
151--- lib/canonical/widgets/popup.py 2009-07-30 17:58:16 +0000
152+++ lib/canonical/widgets/popup.py 2010-02-01 14:05:26 +0000
153@@ -127,7 +127,7 @@
154
155 def chooseLink(self):
156 js_file = os.path.join(os.path.dirname(__file__),
157- 'templates/vocabulary-picker.js')
158+ 'templates/vocabulary-picker.js.template')
159 js_template = open(js_file).read()
160
161 if self.header is None:
162
163=== renamed file 'lib/canonical/widgets/templates/vocabulary-picker.js' => 'lib/canonical/widgets/templates/vocabulary-picker.js.template'
164--- lib/canonical/widgets/templates/vocabulary-picker.js 2009-09-21 12:27:14 +0000
165+++ lib/canonical/widgets/templates/vocabulary-picker.js.template 2010-02-01 14:05:26 +0000
166@@ -1,4 +1,4 @@
167-YUI().use('node', 'lp.picker', function(Y) {
168+YUI().use('node', 'lp.picker', 'plugin', function(Y) {
169 if (Y.UA.ie) {
170 return;
171 }
172@@ -9,15 +9,12 @@
173 // The vocabulary picker, created when used for the first time.
174 var picker = null;
175 function make_picker() {
176- var save = function (result) {
177- Y.DOM.byId(args.input_id).value = result.value;
178- };
179 var config = {
180 header: args.header,
181 step_title: args.step_title,
182 extra_no_results_message: args.extra_no_results_message
183 };
184- var picker = Y.lp.picker.create(args.vocabulary, save, config);
185+ var picker = Y.lp.picker.create(args.vocabulary, config);
186 if (config.extra_no_results_message !== null) {
187 picker.before('resultsChange', function (e) {
188 var new_results = e.details[0].newVal;
189@@ -30,6 +27,8 @@
190 }
191 });
192 }
193+ picker.plug(Y.lazr.TextFieldPickerPlugin,
194+ {input_element: '[id="' + args.input_id + '"]'});
195 return picker;
196 }
197
198
199=== added file 'lib/lp/registry/windmill/tests/test_person_picker.py'
200--- lib/lp/registry/windmill/tests/test_person_picker.py 1970-01-01 00:00:00 +0000
201+++ lib/lp/registry/windmill/tests/test_person_picker.py 2010-02-01 14:05:26 +0000
202@@ -0,0 +1,55 @@
203+# Copyright 2010 Canonical Ltd. This software is licensed under the
204+# GNU Affero General Public License version 3 (see the file LICENSE).
205+
206+"""Test for using a person picker widget."""
207+
208+__metaclass__ = type
209+__all__ = []
210+
211+import unittest
212+
213+from windmill.authoring import WindmillTestClient
214+
215+from canonical.launchpad.windmill.testing import constants, lpuser
216+
217+from lp.registry.windmill.testing import RegistryWindmillLayer
218+from lp.testing import TestCaseWithFactory
219+
220+
221+class TesPersonPickerWidget(TestCaseWithFactory):
222+
223+ layer = RegistryWindmillLayer
224+
225+ def setUp(self):
226+ super(TesPersonPickerWidget, self).setUp()
227+ self.client = WindmillTestClient('PersonPickerWidget')
228+
229+ def test_person_picker_widget(self):
230+ client = self.client
231+ lpuser.SAMPLE_PERSON.ensure_login(client)
232+
233+ client.open(url=u'http://launchpad.dev:8085/people/+requestmerge')
234+ client.waits.forPageLoad(timeout=constants.PAGE_LOAD)
235+ client.waits.forElement(id=u'show-widget-field-dupe_person',
236+ timeout=constants.FOR_ELEMENT)
237+
238+ client.type(text=u'guilherme', name=u'field.dupe_person')
239+
240+ client.click(id=u'show-widget-field-dupe_person')
241+ client.waits.forElement(id=u'shadow', timeout=constants.FOR_ELEMENT)
242+
243+ client.asserts.assertProperty(
244+ xpath=u'//div[@class="yui-picker-search-box"]/input',
245+ validator=u'value|guilherme')
246+
247+ client.click(xpath=u'//div[@class="yui-picker-search-box"]/button')
248+ client.waits.sleep(milliseconds=constants.SLEEP)
249+
250+ client.click(xpath=u'//ul[@class="yui-picker-results"]/li[1]')
251+ client.asserts.assertProperty(
252+ xpath=u'//input[@name="field.dupe_person"]',
253+ validator='value|salgado')
254+
255+
256+def test_suite():
257+ return unittest.TestLoader().loadTestsFromName(__name__)
258
259=== modified file 'versions.cfg'
260--- versions.cfg 2010-01-22 04:01:17 +0000
261+++ versions.cfg 2010-02-01 14:05:26 +0000
262@@ -37,7 +37,7 @@
263 lazr.smtptest = 1.1
264 lazr.testing = 0.1.1
265 lazr.uri = 1.0.2
266-lazr-js = 0.9.2DEV
267+lazr-js = 0.9.2
268 martian = 0.11
269 mechanize = 0.1.11
270 mercurial = 1.3.1