Merge lp:~rvb/launchpad/init-multiple-parents-packagesetpicker into lp:launchpad

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 13198
Proposed branch: lp:~rvb/launchpad/init-multiple-parents-packagesetpicker
Merge into: lp:launchpad
Diff against target: 164 lines (+87/-5)
2 files modified
lib/lp/registry/javascript/distroseries.js (+28/-5)
lib/lp/registry/javascript/tests/test_distroseries.js (+59/-0)
To merge this branch: bzr merge lp:~rvb/launchpad/init-multiple-parents-packagesetpicker
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+63775@code.launchpad.net

Commit message

[r=jtv][bug=793603] Fix the packagesets widget on initseries page.

Description of the change

This branch fixes the packagesets widget on the +initseries page. The packagesets from a parent where not removed properly when the parent was removed from the parent picker widget.

= Q/A =

On DF:
- Create a new series
- Go to the initialisation page series/+initseries
- Add a parent with packagesets
- Make sure the parent's packagesets are added to the packagesets widget
- Remove the parent
- Make sure the packagesets from this parent are properly removed from the packagesets widget

= Tests =

lib/lp/registry/javascript/tests/test_distroseries.html
(testRemoveDistroSeriesUpdatesPackageSets)

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (6.7 KiB)

Hi Raphaël,

I really need some help with this. Without comments, I don't know what anything does and where it's tested! That makes it very hard for me to say anything more useful about the code than that it needs comments. I *think* there's a couple of bugs, but I'm not sure.

Let's have a look at the diff:

 === modified file 'lib/lp/registry/javascript/distroseries.js'
2 --- lib/lp/registry/javascript/distroseries.js 2011-06-01 10:31:05 +0000
3 +++ lib/lp/registry/javascript/distroseries.js 2011-06-07 21:48:32 +0000

15 @@ -1073,6 +1073,7 @@
16 * @method add_packagesets
17 */
18 add_packagesets: function(packagesets, distroseries) {
19 + this._packagesets[distroseries.value] = packagesets.entries;

What is distroseries exactly, and more to the point, what is distroseries.value? It would be helpful to have some documentation of the parameters. Is distroseries.value a distroseries id?

23 @@ -1095,9 +1096,12 @@
24 * @method remove_distroseries
25 */
26 remove_distroseries: function(distroseries_id) {
27 - this.fieldNode.all(
28 - 'option[value$="-' + distroseries_id + '"]').remove();
29 - Y.lazr.anim.green_flash({node: this.fieldNode}).run();
30 + this._packagesets[distroseries_id].forEach(
31 + function(packageset) {
32 + this.fieldNode.all(
33 + 'option[value$="' + packageset.get("id") + '"]').remove();
34 + }, this);
35 + Y.lazr.anim.green_flash({node: this.fieldNode}).run();

The new code looks like it may be slower. Could that be an issue?

I had a hard time understanding the this.fieldNode.all(). The old code looked for:

    'option[value$="-' + distroseries_id + '"]'

As far as I can tell, that's a CSS3 selector for "value ends with a dash followed by distroseries_id." Is it safe for us to use CSS3 selectors? AFAICT CSS2 does not have $=.

The new code looks for:

    'option[value$="' + packageset.get("id") + '"]'

That means "value ends with the packageset id." I may be misunderstanding it, but it looks wrong to me in two ways:

1. So what does that value end in — distroseries id or packageset id!?

2. What about spurious substring matches, e.g. won't this also remove "321" when you really want to remove only "21"?

39 @@ -1109,6 +1113,7 @@
40 this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
41 this.error_handler.showError = Y.bind(this.showError, this);
42 this.clean_display();
43 + this._packagesets = {};

It would be helpful to say here what structure _packagesets will have, e.g. that it maps distroseries ids to arrays of packageset ids. We minify our JS, so comments won't slow the code down but they can save oodles of time in maintenance.

48 === modified file 'lib/lp/registry/javascript/tests/test_distroseries.js'
49 --- lib/lp/registry/javascript/tests/test_distroseries.js 2011-06-01 09:17:43 +0000
50 +++ lib/lp/registry/javascript/tests/test_distroseries.js 2011-06-07 21:48:32 +0000
51 @@ -854,6 +854,48 @@
52 attrselect("value")(this.widget.get("choices")));
53 },
54
55 + testRemoveDistroSer...

Read more...

review: Needs Fixing (js)
Revision history for this message
Raphaël Badin (rvb) wrote :
Download full text (3.2 KiB)

Hi Jeroen,

Sorry about the fact that this code is not really readable. I've tried to improve it quite a bit.

> What is distroseries exactly, and more to the point, what is
> distroseries.value? It would be helpful to have some documentation of the
> parameters. Is distroseries.value a distroseries id?

Added documentation.

> The new code looks like it may be slower. Could that be an issue?

I don't think so, it'll just iterate over the packagesets of a distroseries.

> As far as I can tell, that's a CSS3 selector for "value ends with a dash
> followed by distroseries_id." Is it safe for us to use CSS3 selectors?
> AFAICT CSS2 does not have $=.
>
> The new code looks for:
>
> 'option[value$="' + packageset.get("id") + '"]'
>
> That means "value ends with the packageset id." I may be misunderstanding it,
> but it looks wrong to me in two ways:
>
> 1. So what does that value end in — distroseries id or packageset id!?
>
> 2. What about spurious substring matches, e.g. won't this also remove "321"
> when you really want to remove only "21"?

Ah! Thanks for spotting this! I've changed it to:

{{{
this.fieldNode.one(
    'option[value="' + packageset.get("id") + '"]').remove();
}}}

Just so you know, before this change, the packageset was identified by packagesetname-distro_id. Hence left-over/buggy the substring matches ... I guess staring at javascript too much made me dizzy.

> It would be helpful to say here what structure _packagesets will have, e.g.
> that it maps distroseries ids to arrays of packageset ids. We minify our JS,
> so comments won't slow the code down but they can save oodles of time in
> maintenance.

Good point, I've added a small explanation for this._packagesets.

> At 40 lines this test is far too long for me to see what it does without any
> comments or helpful identifiers. Is there anything you can do to express what
> this test tests?

I've refactored the test to make it more clear ... I hope. But I'm afraid js is kinda verbose :(.

> The substr('-5') is highly opaque. If there is no clearer way to write this
> (I can't guess because I have no indication what it's for) then can you
> isolate it in a function with a helpful name, or comment it?
> [...]
> Except that "name" may not actually be the distroseries name, because it's
> just the last 5 characters… in which case, why not use the full "ubuntu/hoary"
> and "ubuntu/breezy" here and drop the confusing "substr('-5')" trick?

Again, very good point, I refactored the code.

> Are the ids 4 and 5 here in any way related to the ids 4 and 5 you use above?
> If you're looking to check for possible confusion between the two, then that's
> a good thing to put in a separate test. Don't lob multiple aspects into a
> single test because they can grow, grow, grow without bound.

I've changed the ids to make it more clean. It was not my intention to test for that (and I don't think it's worth adding a test for this).

> I guess these are packageset ids. But the only thing that really tells me
> that is the "6"! It's hard to come up with anything that would make this
> clearer. Maybe you could wrap the widget.get("choices") in a function
> get_packageset_cho...

Read more...

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Thanks for making these changes. Much better now!

One thing that still needs fixing, as mentioned on IRC: in the test, the two distroseries have the same id. That happens to produce the right results here, but it does upset the bookkeeping a little bit.

The ordering of the ids in the test comparison seems a little arbitrary (5, 7, 6, 4 IIRC). If possible, use ordering-insensitive comparisons where ordering is not important and test ordering separately. Less brittle, easier to comprehend.

But now I'm nit-picking. Well done!

Jeroen

review: Approve (js)
Revision history for this message
Raphaël Badin (rvb) wrote :

> One thing that still needs fixing, as mentioned on IRC: in the test, the two
> distroseries have the same id. That happens to produce the right results
> here, but it does upset the bookkeeping a little bit.

Done.

> The ordering of the ids in the test comparison seems a little arbitrary (5, 7,
> 6, 4 IIRC). If possible, use ordering-insensitive comparisons where ordering
> is not important and test ordering separately. Less brittle, easier to
> comprehend.

Done.

> But now I'm nit-picking. Well done!

Thanks again for the review, I reckon the code has improved quite a bit thanks to you remarks!

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

JS, or code, or whatever. It's all good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/javascript/distroseries.js'
2--- lib/lp/registry/javascript/distroseries.js 2011-06-06 08:51:09 +0000
3+++ lib/lp/registry/javascript/distroseries.js 2011-06-09 09:14:43 +0000
4@@ -702,6 +702,8 @@
5 * distroseries to the possible choices.
6 *
7 * @method add_distroseries
8+ * @param {Object} The distroseries to add ({value:distroseries_id),
9+ * api_uri:distroseries_uri}).
10 */
11 add_distroseries: function(distroseries) {
12 var path = distroseries.api_uri + "/architectures";
13@@ -757,6 +759,8 @@
14 * Add a list of distroarchseries.
15 *
16 * @method add_distroarchseries
17+ * @param {String} distroseries_id The distroarchseries id.
18+ * @param {Object} distroarchseries The distroarchseries object.
19 */
20 add_distroarchseries: function(distroseries_id, distroarchseries) {
21 this._distroseries[distroseries_id] = distroarchseries;
22@@ -990,6 +994,7 @@
23 * Add a distroseries: add its packagesets to the packageset picker.
24 *
25 * @method add_distroseries
26+ * @param {Object} distroseries The distroseries object.
27 */
28 add_distroseries: function(distroseries) {
29 var distro_series_uri = Y.lp.client.get_absolute_uri(
30@@ -1045,13 +1050,15 @@
31 * Add a choice to the picker.
32 *
33 * @method add_choice
34+ * @param {Object} choice The choice object to be added
35+ * ({text: choice_text, value: choice_value, data: choice_data}).
36 */
37 add_choice: function(choice) {
38 var select = this.init_select();
39 var option = Y.Node.create("<option />");
40 option.set("value", choice.value)
41- .set("text", choice.text)
42- .setData("data", choice.data);
43+ .set("text", choice.text)
44+ .setData("data", choice.data);
45 var options = select.all('option');
46 if (options.isEmpty()) {
47 select.append(option);
48@@ -1071,8 +1078,13 @@
49 * Add choices (a set of packagesets) to the picker.
50 *
51 * @method add_packagesets
52+ * @param {Y.lp.client.Collection} packagesets The collection of
53+ * packagesets to add.
54+ * @param {Object} distroseries The distroseries object
55+ * ({value:distroseries_id), api_uri:distroseries_uri}).
56 */
57 add_packagesets: function(packagesets, distroseries) {
58+ this._packagesets[distroseries.value] = packagesets.entries;
59 packagesets.entries.forEach(
60 function(packageset) {
61 var value = packageset.get("id");
62@@ -1093,11 +1105,16 @@
63 * Remove a distroseries: remove its packagesets from the picker.
64 *
65 * @method remove_distroseries
66+ * @param {String} distroseries_id The id of the distroseries to be
67+ * removed.
68 */
69 remove_distroseries: function(distroseries_id) {
70- this.fieldNode.all(
71- 'option[value$="-' + distroseries_id + '"]').remove();
72- Y.lazr.anim.green_flash({node: this.fieldNode}).run();
73+ this._packagesets[distroseries_id].forEach(
74+ function(packageset) {
75+ this.fieldNode.one(
76+ 'option[value="' + packageset.get("id") + '"]').remove();
77+ }, this);
78+ Y.lazr.anim.green_flash({node: this.fieldNode}).run();
79 if (this.fieldNode.all('option').isEmpty()) {
80 this.clean_display();
81 }
82@@ -1109,6 +1126,12 @@
83 this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
84 this.error_handler.showError = Y.bind(this.showError, this);
85 this.clean_display();
86+ // _packagesets maps each distroseries' id to a collection of ids
87+ // of its packagesets.
88+ // It's populated each time a new distroseries is added as a parent
89+ // and used when a distroseries is removed to get all the
90+ // corresponding packagesets to be removed from the widget.
91+ this._packagesets = {};
92 }
93
94 });
95
96=== modified file 'lib/lp/registry/javascript/tests/test_distroseries.js'
97--- lib/lp/registry/javascript/tests/test_distroseries.js 2011-06-01 09:17:43 +0000
98+++ lib/lp/registry/javascript/tests/test_distroseries.js 2011-06-09 09:14:43 +0000
99@@ -854,6 +854,65 @@
100 attrselect("value")(this.widget.get("choices")));
101 },
102
103+ testRemoveDistroSeriesUpdatesPackageSets: function() {
104+ // Calling remove_distroseries removes the packagesets
105+ // related to the distroseries from the widget.
106+
107+ // Setup a first distroseries with a bunch of packagesets.
108+ var distroseries1 = {
109+ value: "1", title: "series1",
110+ api_uri: "ubuntu/hoary"};
111+ var package_sets1 = [
112+ {id: "4", name: "aa", description: "Aa"},
113+ {id: "5", name: "bb", description: "Bb"}
114+ ];
115+ var package_sets_collection1 =
116+ new Y.lp.client.Collection(
117+ null, {entries: package_sets1}, null);
118+
119+ // Setup a second distroseries with other packagesets.
120+ var distroseries2 = {
121+ value: "2", title: "series2",
122+ api_uri: "ubuntu/breezy"};
123+ var package_sets2 = [
124+ {id: "6", name: "cc", description: "Cc"},
125+ {id: "7", name: "dd", description: "Dd"}
126+ ];
127+ var package_sets_collection2 =
128+ new Y.lp.client.Collection(
129+ null, {entries: package_sets2}, null);
130+
131+ // Setup the client so that the proper packagesets are returned
132+ // for each distroseries.
133+ var package_set_collections = {
134+ 'hoary': package_sets_collection1,
135+ 'breezy': package_sets_collection2
136+ };
137+ this.widget.client = {
138+ named_get: function(path, operation, config) {
139+ var series_name = config
140+ .parameters.distroseries.split('/')[6];
141+ config.on.success(package_set_collections[series_name]);
142+ }
143+ };
144+
145+ // Add the two series.
146+ this.widget.add_distroseries(distroseries1);
147+ this.widget.add_distroseries(distroseries2);
148+ // The packagesets widget has been populated with the
149+ // packagesets from the series.
150+ ArrayAssert.itemsAreEqual(
151+ ["4", "5", "6", "7"],
152+ attrselect("value")(this.widget.get("choices")));
153+
154+ // Remove a series.
155+ this.widget.remove_distroseries("2");
156+ // The remaining packagesets are those from the remaining series.
157+ ArrayAssert.itemsAreEqual(
158+ ["4", "5"],
159+ attrselect("value")(this.widget.get("choices")));
160+ },
161+
162 testSetDistroSeriesSpinner: function() {
163 var widget = this.widget;
164 widget.client = {