Merge lp:~wallyworld/launchpad/recipe-pending-build-testfix into lp:launchpad

Proposed by Ian Booth
Status: Merged
Approved by: Brad Crittenden
Approved revision: no longer in the source branch.
Merged at revision: 12593
Proposed branch: lp:~wallyworld/launchpad/recipe-pending-build-testfix
Merge into: lp:launchpad
Diff against target: 237 lines (+90/-30)
4 files modified
lib/lp/code/interfaces/sourcepackagerecipe.py (+16/-1)
lib/lp/code/javascript/requestbuild_overlay.js (+24/-28)
lib/lp/code/model/sourcepackagerecipe.py (+11/-0)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+39/-1)
To merge this branch: bzr merge lp:~wallyworld/launchpad/recipe-pending-build-testfix
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+53033@code.launchpad.net

Commit message

[r=bac][bug=728789] Ensure pending builds are always correctly disabled on the recipe request build popup form.

Description of the change

This change is a more complex but necessary way of fixing the correct rendering of existing pending builds on the Request Builds popup. The original way worked with the sample data but failed on some production data.

== Implementation ==

Is is necessary for the popup form, when displayed, to perform a named_get call to the new getPendingBuildInfo() method on the recipe. This call returns the required data (distroseries displayname and archive token) for each pending build, allowing the corresponding checkboxes to be disabled on the popup form. Previously a simple pending_builds_collection_link retrieval was performed but this only returns links to the distroseries and archive items and not the required data.

== Tests ==

A windmill test:
bin/test -vvt test_recipe_build_request_already_pending

== Lint ==

Linting changed files:
  lib/lp/code/interfaces/sourcepackagerecipe.py
  lib/lp/code/javascript/requestbuild_overlay.js
  lib/lp/code/model/sourcepackagerecipe.py

./lib/lp/code/interfaces/sourcepackagerecipe.py
     228: E501 line too long (83 characters)
     228: Line exceeds 78 characters.
./lib/lp/code/javascript/requestbuild_overlay.js
     110: Line exceeds 78 characters.
     148: Line exceeds 78 characters.
     180: Line exceeds 78 characters.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Hi Ian,

Thanks for the branch. It all seems to make sense.

I would like to see a unit test for your new method getPendingBuildInfo.
And please clean up the issues pointed out by lint if they are real.

review: Needs Fixing (code)
Revision history for this message
Ian Booth (wallyworld) wrote :

> Hi Ian,
>
> Thanks for the branch. It all seems to make sense.
>
> I would like to see a unit test for your new method getPendingBuildInfo.
> And please clean up the issues pointed out by lint if they are real.

Thanks for the review. I've added a unit test for the new method and also for the web service invocation. I've also cleaned up up the pre-existing lint issues.

Revision history for this message
Brad Crittenden (bac) wrote :

Thanks for adding the tests Ian -- it looks good.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
2--- lib/lp/code/interfaces/sourcepackagerecipe.py 2011-03-01 18:02:21 +0000
3+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2011-03-11 23:58:19 +0000
4@@ -24,6 +24,7 @@
5 from lazr.restful.declarations import (
6 call_with,
7 export_as_webservice_entry,
8+ export_read_operation,
9 export_write_operation,
10 exported,
11 mutator_for,
12@@ -166,6 +167,19 @@
13 def performDailyBuild():
14 """Perform a build into the daily build archive."""
15
16+ @export_read_operation()
17+ @operation_for_version("devel")
18+ def getPendingBuildInfo():
19+ """Find distroseries and archive data for pending builds.
20+
21+ Return a list of dict(
22+ distroseries:distroseries.displayname
23+ archive:archive.token)
24+ The archive token is the same as that defined by the archive vocab:
25+ archive.owner.name/archive.name
26+ This information is used to construct the request builds popup form.
27+ """
28+
29
30 class ISourcePackageRecipeEdit(Interface):
31 """ISourcePackageRecipe methods that require launchpad.Edit permission."""
32@@ -211,7 +225,8 @@
33 readonly=False)
34 build_daily = exported(Bool(
35 title=_("Built daily"),
36- description=_("Automatically build each day, if the source has changed.")))
37+ description=_(
38+ "Automatically build each day, if the source has changed.")))
39
40 name = exported(TextLine(
41 title=_("Name"), required=True,
42
43=== modified file 'lib/lp/code/javascript/requestbuild_overlay.js'
44--- lib/lp/code/javascript/requestbuild_overlay.js 2011-03-07 07:13:52 +0000
45+++ lib/lp/code/javascript/requestbuild_overlay.js 2011-03-11 23:58:19 +0000
46@@ -107,7 +107,8 @@
47 request_build_response_handler.showError = function(header, error_msgs) {
48 if (header == null) {
49 if (error_msgs == null)
50- header = "An error occurred, please contact an administrator.";
51+ header = "An error occurred, please contact an " +
52+ "administrator.";
53 else
54 header = "The following errors occurred:";
55 }
56@@ -145,7 +146,8 @@
57 var submit_url = base_url+"/+request-daily-build";
58 var current_builds = harvest_current_build_records();
59
60- var qs = Y.lp.client.append_qs('', 'field.actions.build', 'Build now');
61+ var qs = Y.lp.client.append_qs(
62+ '', 'field.actions.build', 'Build now');
63 var y_config = {
64 method: "POST",
65 headers: {'Accept': 'application/xhtml'},
66@@ -177,7 +179,8 @@
67 {nr_new: nr_new});
68 }
69 var build_message_node = Y.Node.create([
70- '<div id="new-builds-info" class="build-informational">',
71+ '<div id="new-builds-info" ' +
72+ 'class="build-informational">',
73 new_builds_message,
74 '</div>'].join(''));
75 request_daily_build_handle.insert(
76@@ -410,15 +413,13 @@
77
78 var nr_matches = 0;
79 Y.Array.each(last_known_pending_builds, function(distroarchive) {
80- var archive_parts = ppa_value.split("/");
81- var archive = archive_parts.pop();
82- if( archive.toLowerCase() != distroarchive[1].toLowerCase() )
83+ if (ppa_value != distroarchive[1])
84 return;
85
86 for (var i=0; i<distroseries_nodes.size(); i++) {
87 var distroseries_node = distroseries_nodes.item(i);
88 var distro = distroseries_node.get("text").trim();
89- if (distro.toLowerCase() == distroarchive[0].toLowerCase() ) {
90+ if (distro == distroarchive[0]) {
91 nr_matches += 1;
92 var disabled_checkbox_html = Y.Lang.substitute(
93 DISABLED_DISTROSERIES_CHECKBOX_HTML, {distro: distro});
94@@ -442,6 +443,12 @@
95 // or be initiated after this initial lookup, but we do handle that
96 // situation in the error handling.
97
98+ // The ui may not be ready yet.
99+ var ppa_selector = request_build_overlay.form_node.one(
100+ "[name='field.archive']");
101+ if (ppa_selector == null) {
102+ return;
103+ }
104 distroseries_node_html = new Array();
105 last_known_pending_builds = new Array();
106 var y_config = {
107@@ -457,28 +464,16 @@
108 distro_node.get("innerHTML"));
109 });
110
111- //We have a json collection of the pending build records.
112- var size = build_info.total_size;
113+ // We have a collection of the pending build info.
114+ // The info is the distroseries displayname and archive
115+ // token.
116+ var size = build_info.length;
117 for( var i=0; i<size; i++ ) {
118- var build_record = build_info.entries[i];
119- var distro_link = build_record.get(
120- "distro_series_link");
121- var archive_link = build_record.get("archive_link");
122-
123- // The very last segment of the distro series and
124- // archive links will be the bits we want to match on.
125- // eg for distro series, the link will be like:
126- // https://code.launchpad.dev/api/devel/ubuntu/warty
127- // and "warty" is what we match on.
128- var distro_parts = distro_link.split("/");
129- var archive_parts = archive_link.split("/");
130+ var build_record = build_info[i];
131+ var distro_name = build_record["distroseries"];
132+ var archive_token = build_record["archive"];
133 last_known_pending_builds.push(
134- [distro_parts.pop(), archive_parts.pop()])
135- }
136- var ppa_selector = request_build_overlay.form_node.one(
137- "[name='field.archive']");
138- if (ppa_selector == null) {
139- return;
140+ [distro_name, archive_token]);
141 }
142 ppa_selector.on("change", function(e) {
143 ppa_changed(ppa_selector.get("value"), submit_button);
144@@ -488,7 +483,8 @@
145 }
146 };
147 set_up_lp_client();
148- lp_client.get(LP.cache.context.pending_builds_collection_link, y_config);
149+ lp_client.named_get(
150+ LP.cache.context.self_link, 'getPendingBuildInfo', y_config);
151 }
152 }, "0.1", {"requires": [
153 "dom", "node", "io-base", "lazr.anim", "lazr.formoverlay", "lp.client"
154
155=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
156--- lib/lp/code/model/sourcepackagerecipe.py 2011-02-25 08:27:08 +0000
157+++ lib/lp/code/model/sourcepackagerecipe.py 2011-03-11 23:58:19 +0000
158@@ -338,6 +338,17 @@
159 result.order_by(order_by)
160 return result
161
162+ def getPendingBuildInfo(self):
163+ """See `ISourcePackageRecipe`."""
164+ builds = self.pending_builds
165+ result = []
166+ for build in builds:
167+ result.append(
168+ {"distroseries": build.distroseries.displayname,
169+ "archive": '%s/%s' %
170+ (build.archive.owner.name, build.archive.name)})
171+ return result
172+
173 @property
174 def last_build(self):
175 """See `ISourcePackageRecipeBuild`."""
176
177=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
178--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-03-09 20:12:10 +0000
179+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-03-11 23:58:19 +0000
180@@ -599,6 +599,22 @@
181 self.assertEqual(builds[1:], list(recipe.pending_builds))
182 self.assertEqual(builds, list(recipe.builds))
183
184+ def test_getPendingBuildInfo(self):
185+ """SourcePackageRecipe.getPendingBuildInfo() is as expected."""
186+ person = self.factory.makePerson()
187+ archives = [self.factory.makeArchive(owner=person) for x in range(4)]
188+ distroseries= self.factory.makeSourcePackageRecipeDistroseries()
189+ recipe = self.factory.makeSourcePackageRecipe()
190+
191+ build_info = []
192+ for archive in archives:
193+ build = recipe.requestBuild(archive, person, distroseries)
194+ build_info.insert(0, {
195+ "distroseries": distroseries.displayname,
196+ "archive": '%s/%s' %
197+ (archive.owner.name, archive.name)})
198+ self.assertEqual(build_info, list(recipe.getPendingBuildInfo()))
199+
200 def test_getBuilds_cancelled(self):
201 # Cancelled builds are not considered pending.
202 recipe = self.factory.makeSourcePackageRecipe()
203@@ -847,7 +863,8 @@
204 branch = self.factory.makeBranch()
205 return MINIMAL_RECIPE_TEXT % branch.bzr_identity
206
207- def makeRecipe(self, user=None, owner=None, recipe_text=None, version='devel'):
208+ def makeRecipe(self, user=None, owner=None, recipe_text=None,
209+ version='devel'):
210 # rockstar 21 Jul 2010 - This function does more commits than I'd
211 # like, but it's the result of the fact that the webservice runs in a
212 # separate thread so doesn't get the database updates without those
213@@ -1006,3 +1023,24 @@
214 self.assertEqual(builds, list(recipe.pending_builds))
215 self.assertEqual(builds, list(recipe.builds))
216 self.assertEqual([], list(recipe.completed_builds))
217+
218+ def test_getPendingBuildInfo(self):
219+ """SourcePackageRecipe.getPendingBuildInfo() is as expected."""
220+ person = self.factory.makePerson()
221+ archives = [self.factory.makeArchive(owner=person) for x in range(4)]
222+ distroseries= self.factory.makeSourcePackageRecipeDistroseries()
223+
224+ recipe, user, launchpad = self.makeRecipe(person)
225+ ws_distroseries = ws_object(launchpad, distroseries)
226+
227+ build_info = []
228+ for archive in archives:
229+ ws_archive = ws_object(launchpad, archive)
230+ build = recipe.requestBuild(
231+ archive=ws_archive, distroseries=ws_distroseries,
232+ pocket=PackagePublishingPocket.RELEASE.title)
233+ build_info.insert(0, {
234+ "distroseries": distroseries.displayname,
235+ "archive": '%s/%s' %
236+ (archive.owner.name, archive.name)})
237+ self.assertEqual(build_info, list(recipe.getPendingBuildInfo()))