Merge lp:~henninge/launchpad/bug-824435-failure-reporting into lp:launchpad

Proposed by Henning Eggers
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: 13744
Proposed branch: lp:~henninge/launchpad/bug-824435-failure-reporting
Merge into: lp:launchpad
Diff against target: 313 lines (+200/-19)
5 files modified
lib/lp/app/javascript/testing/iorecorder.js (+18/-5)
lib/lp/code/javascript/requestbuild_overlay.js (+34/-13)
lib/lp/code/javascript/tests/test_requestbuild_overlay.html (+38/-0)
lib/lp/code/javascript/tests/test_requestbuild_overlay.js (+104/-0)
lib/lp/code/templates/sourcepackagerecipe-index.pt (+6/-1)
To merge this branch: bzr merge lp:~henninge/launchpad/bug-824435-failure-reporting
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+72255@code.launchpad.net

Commit message

[r=abentley][bug=824435][incr] Remove Javascript alert and add test for requestdailybuild.

Description of the change

= Bug 824435 =

This is the first part of the fix. Does two things:

 1. Remove the Javascript alert
 2. Add a test as a fixture for the current situation

The next part will contain the actual fix.

== Proposed fix ==

The Javascript alert is a Bad Thing and I also don't know how to test
it. I replaced it with an in-place message which was already used for
displaying the result for the successful case.
I changed the behaviour of this message box by adding a "Dismiss"
button instead of having it disappear after 20 minutes. In case of the
error message it is a Good Idea to not have it disappear before you
can copy it to IRC. Also, since the page shifts around abit when the
message disappears, it used to be a bit surprising when that happened
by itself.

== Pre-implementation notes ==

I talked this through with Deryck and Aaron and they also helped me a
lot. There has been no pre-imp for the UI change, though.

== Implementation details ==

This is the second usage of IORecorder and I had to extend it to make
failure responses possible. The next step will see more extensions.

== Tests ==

The tests test the two possible errors that can currently be detected by
the failure handler: 503 and !503.

lib/lp/code/javascript/tests/test_requestbuild_overlay.html

== Demo and Q/A ==

Create a new recipe without changing the standard settings. You will see
the "Buld now" link. Clicking on it should create new builds and the
success message will be displayed. When it is dismissed, the "Build now"
button is gone, too.

I am not sure how to trigger an error yet.

= Launchpad lint =

I will fix these in the next go.

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/javascript/tests/test_requestbuild_overlay.html
  lib/lp/code/templates/sourcepackagerecipe-index.pt
  lib/lp/code/javascript/requestbuild_overlay.js
  lib/lp/app/javascript/testing/iorecorder.js
  lib/lp/code/javascript/tests/test_requestbuild_overlay.js

./lib/lp/code/javascript/requestbuild_overlay.js
      40: Expected '===' and instead saw '=='.
      45: Expected '!==' and instead saw '!='.
      70: Expected '===' and instead saw '=='.
      72: ['name'] is better written in dot notation.
     111: Expected '===' and instead saw '=='.
     112: Expected '===' and instead saw '=='.
     113: Expected '{' and instead saw 'header'.
     116: Expected '{' and instead saw 'header'.
     119: Expected '!==' and instead saw '!='.
     121: Expected '===' and instead saw '=='.
     227: Expected '!==' and instead saw '!='.
     228: Expected '{' and instead saw 'error_msg'.
     239: Use the array literal notation [].
     261: Expected '{' and instead saw 'return'.
     275: Expected ';' and instead saw 'if'.
     276: Expected '===' and instead saw '=='.
     290: Expected '{' and instead saw 'return'.
     324: ['builds'] is better written in dot notation.
     326: ['already_pending'] is better written in dot notation.
     328: ['errors'] is better written in dot notation.
     331: Expected '!==' and instead saw '!='.
     343: Expected '!==' and instead saw '!='.
     344: Expected '!==' and instead saw '!='.
     345: Expected '{' and instead saw 'error_header_text'.
     347: Expected '{' and instead saw 'error_header_text'.
     350: Move 'var' declarations to the top of the function.
     350: Stopping. (67% scanned).
       0: JSLINT had a fatal error.
     161: Line has trailing whitespace.
./lib/lp/code/javascript/tests/test_requestbuild_overlay.js
      21: ['context'] is better written in dot notation.
      99: Unexpected ','.
      43: Line has trailing whitespace.
      47: Line has trailing whitespace.

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

This is a syntax error in some browsers, so it should be fixed before landing:

./lib/lp/code/javascript/tests/test_requestbuild_overlay.js
      21: ['context'] is better written in dot notation.
      99: Unexpected ','.

Aside from that, this is landable.

200 is far from the only "success" code. It might be better to trigger success on < 400.

review: Needs Fixing
Revision history for this message
Henning Eggers (henninge) wrote :
Revision history for this message
Aaron Bentley (abentley) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/javascript/testing/iorecorder.js'
2--- lib/lp/app/javascript/testing/iorecorder.js 2011-07-27 18:10:47 +0000
3+++ lib/lp/app/javascript/testing/iorecorder.js 2011-08-19 20:45:30 +0000
4@@ -1,8 +1,10 @@
5 /* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
6
7 YUI.add('lp.testing.iorecorder', function(Y) {
8- namespace = Y.namespace("lp.testing.iorecorder");
9- function MockHttpResponse () {
10+ var namespace = Y.namespace("lp.testing.iorecorder");
11+
12+ function MockHttpResponse (status) {
13+ this.status = status;
14 this.responseText = '[]';
15 this.responseHeaders = {};
16 }
17@@ -26,12 +28,23 @@
18 }
19
20
21- IORecorderRequest.prototype.success = function(value, headers){
22- this.response = new MockHttpResponse();
23+ IORecorderRequest.prototype.respond = function(status, value, headers){
24+ this.response = new MockHttpResponse(status);
25 this.response.setResponseHeader(
26 'Content-Type', headers['Content-Type']);
27 this.response.responseText = value;
28- this.config.on.success(null, this.response, this.config['arguments']);
29+ var callback;
30+ if (status === 200) {
31+ callback = this.config.on.success;
32+ } else {
33+ callback = this.config.on.failure;
34+ }
35+ callback(null, this.response, this.config['arguments']);
36+ };
37+
38+
39+ IORecorderRequest.prototype.success = function(value, headers){
40+ this.respond(200, value, headers);
41 };
42
43
44
45=== modified file 'lib/lp/code/javascript/requestbuild_overlay.js'
46--- lib/lp/code/javascript/requestbuild_overlay.js 2011-08-09 14:18:02 +0000
47+++ lib/lp/code/javascript/requestbuild_overlay.js 2011-08-19 20:45:30 +0000
48@@ -58,6 +58,8 @@
49 }
50 };
51
52+namespace.RequestResponseHandler = RequestResponseHandler;
53+
54 namespace.connect_requestbuilds = function() {
55
56 var request_build_handle = Y.one('#request-builds');
57@@ -132,9 +134,32 @@
58 var ONE_BUILD_MESSAGE = "1 new recipe build has been queued.";
59 var MANY_BUILDS_MESSAGE = "{nr_new} new recipe builds have been queued.";
60
61-namespace.connect_requestdailybuild = function() {
62+namespace.connect_requestdailybuild = function(config) {
63
64 var request_daily_build_handle = Y.one('#request-daily-build');
65+ var display_message = function (message, css_class){
66+ var build_message_node = Y.Node.create('<div></div>')
67+ .set('id', 'new-builds-info')
68+ .addClass(css_class)
69+ .set('text', message)
70+ .append(Y.Node.create('<br />'))
71+ .append(Y.Node.create('<a></a>')
72+ .set('href', '#')
73+ .addClass('discreet')
74+ .addClass('js-action')
75+ .set('text', 'Dismiss'));
76+ build_message_node.one('a').on('click', function(e) {
77+ e.preventDefault();
78+ build_message_node.hide();
79+ if (css_class === 'build-error') {
80+ request_daily_build_handle.removeClass("unseen");
81+ }
82+ });
83+ request_daily_build_handle.insert(
84+ build_message_node,
85+ request_daily_build_handle);
86+
87+ };
88 request_daily_build_handle.on('click', function(e) {
89 e.preventDefault();
90
91@@ -154,7 +179,6 @@
92 on: {
93 failure: request_daily_build_response_handler.getErrorHandler(
94 function(handler, id, response) {
95- request_daily_build_handle.removeClass("unseen");
96 var server_error = 'Server error, ' +
97 'please contact an administrator.';
98 handler.showError(server_error, null);
99@@ -178,21 +202,18 @@
100 MANY_BUILDS_MESSAGE,
101 {nr_new: nr_new});
102 }
103- var build_message_node = Y.Node.create([
104- '<div id="new-builds-info" ' +
105- 'class="build-informational">',
106- new_builds_message,
107- '</div>'].join(''));
108- request_daily_build_handle.insert(
109- build_message_node,
110- request_daily_build_handle);
111- Y.later(20000, build_message_node, 'hide', true);
112+ display_message(
113+ new_builds_message, 'build-informational');
114 }
115 )
116 },
117 data: qs
118 };
119- Y.io(submit_url, y_config);
120+ var io = Y.io;
121+ if ( config !== undefined && Y.Lang.isValue(config.io)) {
122+ io = config.io;
123+ }
124+ io(submit_url, y_config);
125 });
126
127 // Wire up the processing hooks
128@@ -204,7 +225,7 @@
129 var error_msg = header;
130 if (error != null)
131 error_msg += " " + error;
132- alert(error_msg);
133+ display_message(error_msg, 'build-error');
134 Y.log(error_msg);
135 };
136 };
137
138=== added file 'lib/lp/code/javascript/tests/test_requestbuild_overlay.html'
139--- lib/lp/code/javascript/tests/test_requestbuild_overlay.html 1970-01-01 00:00:00 +0000
140+++ lib/lp/code/javascript/tests/test_requestbuild_overlay.html 2011-08-19 20:45:30 +0000
141@@ -0,0 +1,38 @@
142+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
143+<html>
144+ <head>
145+ <title>Launchpad lp.code.requestbuild_overlay module</title>
146+
147+ <!-- YUI and test setup -->
148+ <script type="text/javascript"
149+ src="../../../../canonical/launchpad/icing/yui/yui/yui.js">
150+ </script>
151+ <link rel="stylesheet" href="../../../app/javascript/testing/test.css" />
152+ <script type="text/javascript">
153+ // The code expects this to be present.
154+ var LP = {
155+ cache: {},
156+ links: {}
157+ };
158+ </script>
159+ <script type="text/javascript"
160+ src="../../../app/javascript/testing/testrunner.js"></script>
161+ <script type="text/javascript"
162+ src="../../../app/javascript/testing/iorecorder.js"></script>
163+
164+ <script type="text/javascript"
165+ src="../../../app/javascript/client.js"></script>
166+ <script type="text/javascript"
167+ src="../../../app/javascript/extras/extras.js"></script>
168+ <script type="text/javascript"
169+ src="../../../app/javascript/anim/anim.js"></script>
170+ <!-- The module under test -->
171+ <script type="text/javascript" src="../requestbuild_overlay.js"></script>
172+
173+ <!-- The test suite -->
174+ <script type="text/javascript" src="test_requestbuild_overlay.js"></script>
175+</head>
176+<body class="yui3-skin-sam">
177+<div id="testbed"></div>
178+</body>
179+</html>
180
181=== added file 'lib/lp/code/javascript/tests/test_requestbuild_overlay.js'
182--- lib/lp/code/javascript/tests/test_requestbuild_overlay.js 1970-01-01 00:00:00 +0000
183+++ lib/lp/code/javascript/tests/test_requestbuild_overlay.js 2011-08-19 20:45:30 +0000
184@@ -0,0 +1,104 @@
185+/* Copyright (c) 2011, Canonical Ltd. All rights reserved. */
186+
187+YUI().use('test', 'console', 'node-event-simulate',
188+ 'lp.testing.iorecorder', 'lp.testing.runner',
189+ 'lp.code.requestbuild_overlay', function(Y) {
190+
191+var suite = new Y.Test.Suite("lp.code.requestbuild_overlay Tests");
192+
193+var module = Y.lp.code.requestbuild_overlay;
194+
195+var builds_target_markup = '<table>' +
196+ '<tr class="package-build" id="build-1"></tr>' +
197+ '<tr class="package-build" id="build-2"></tr>' +
198+ '</table>';
199+
200+suite.add(new Y.Test.Case({
201+ name: "lp.code.requestbuild_overlay",
202+
203+ setUp: function() {
204+ LP.cache['context'] = {
205+ web_link: "http://code.launchpad.dev/~foobar/myrecipe"};
206+ // Prepare testbed.
207+ var testbed = Y.one("#testbed").set('innerHTML', '');
208+ testbed.append(
209+ Y.Node.create('<a></a>')
210+ .set('id', 'request-daily-build')
211+ .set('href', '#')
212+ .addClass('unseen')
213+ .set('text', 'Build now')
214+ ).append(
215+ Y.Node.create('<div></div>')
216+ .set('id', 'builds-target')
217+ );
218+ },
219+
220+ _makeRequest: function() {
221+ var recorder = new Y.lp.testing.iorecorder.IORecorder();
222+ var build_now_link = Y.one('#request-daily-build');
223+ build_now_link.removeClass('unseen');
224+ module.connect_requestdailybuild(
225+ {io: Y.bind(recorder.do_io, recorder)});
226+ build_now_link.simulate('click');
227+
228+ Y.Assert.areSame(1, recorder.requests.length);
229+ return recorder.requests[0];
230+ },
231+
232+ test_requestbuild_success: function() {
233+ var request = this._makeRequest();
234+ request.success(
235+ builds_target_markup, {'Content-Type': 'application/xhtml'});
236+
237+ // The markup has been inserted.
238+ Y.Assert.areSame(
239+ 2, Y.one("#builds-target").all('.package-build').size());
240+ // The message is being displayed as informational.
241+ var info = Y.one("#new-builds-info");
242+ Y.Assert.isTrue(info.hasClass('build-informational'));
243+ Y.Assert.areSame(
244+ "2 new recipe builds have been queued.Dismiss",
245+ info.get('text'));
246+ // The message can be dismissed.
247+ info.one('a').simulate('click');
248+ Y.Assert.areSame('none', info.getStyle('display'));
249+ // The build now button is hidden.
250+ Y.Assert.isTrue(Y.one('#request-daily-build').hasClass('unseen'));
251+ },
252+
253+
254+ _testRequestbuildFailure: function(status, expected_message) {
255+ var request = this._makeRequest();
256+ request.respond(status, '', {});
257+
258+ // No build targets.
259+ Y.Assert.areSame(
260+ 0, Y.one("#builds-target").all('.package-build').size());
261+ // The message is being displayed as an error.
262+ var error = Y.one("#new-builds-info");
263+ Y.Assert.isTrue(error.hasClass('build-error'));
264+ Y.Assert.areSame(expected_message + "Dismiss", error.get('text'));
265+ // The message can be dismissed.
266+ error.one('a').simulate('click');
267+ Y.Assert.areSame('none', error.getStyle('display'));
268+ // The build now button stays visible.
269+ Y.Assert.isFalse(Y.one('#request-daily-build').hasClass('unseen'));
270+ },
271+
272+
273+ test_requestbuild_failure_503: function() {
274+ this._testRequestbuildFailure(
275+ 503, "Timeout error, please try again in a few minutes.");
276+ },
277+
278+
279+ test_requestbuild_failure_500: function() {
280+ this._testRequestbuildFailure(
281+ 500, "Server error, please contact an administrator.");
282+ }
283+
284+}));
285+
286+Y.lp.testing.Runner.run(suite);
287+
288+});
289
290=== modified file 'lib/lp/code/templates/sourcepackagerecipe-index.pt'
291--- lib/lp/code/templates/sourcepackagerecipe-index.pt 2011-03-31 02:29:28 +0000
292+++ lib/lp/code/templates/sourcepackagerecipe-index.pt 2011-08-19 20:45:30 +0000
293@@ -20,7 +20,8 @@
294 div#edit-description.yui3-editable_text-content {
295 margin-top: -15px;
296 }
297- .build-informational {
298+ .build-informational,
299+ .build-error {
300 background: #d4e8ff url('/+icing/blue-fade-to-grey');
301 border: solid #666;
302 border-width: 1px 2px 2px 1px;
303@@ -32,6 +33,10 @@
304 padding-right: 5px;
305 content: url('/@@/info');
306 }
307+ .build-error::before {
308+ padding-right: 5px;
309+ content: url('/@@/error');
310+ }
311 .popup-build-informational {
312 color: black;
313 }