~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/code/javascript/requestbuild_overlay.js

[r=jtv,
 wallyworld][bug=824435][incr] Add tests for the request builds code
 in requestbuilds_overlay.js.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
var request_build_response_handler;
17
17
var request_daily_build_response_handler;
18
18
 
19
 
function set_up_lp_client() {
 
19
var DISABLED_DISTROSERIES_CHECKBOX_HTML =
 
20
    "<input type='checkbox' class='checkboxType' disabled='disabled'>" +
 
21
    "&nbsp;{distro} (build pending)";
 
22
 
 
23
var set_up_lp_client = function(io_provider) {
20
24
    if (lp_client === undefined) {
21
 
        lp_client = new Y.lp.client.Launchpad();
 
25
        lp_client = new Y.lp.client.Launchpad({io_provider: io_provider});
 
26
    } else {
 
27
        // io_provider may be a different instance of MockIo.
 
28
        lp_client.io_provider = io_provider;
22
29
    }
23
 
}
 
30
};
24
31
 
25
32
// This handler is used to process the results of form submission or other
26
33
// such operation (eg get, post). It provides some boiler plate and allows the
36
43
        return function (id, response) {
37
44
            self.clearProgressUI();
38
45
            // If it was a timeout...
39
 
            if (response.status == 503) {
 
46
            if (response.status === 503) {
40
47
                self.showError(
41
48
                    'Timeout error, please try again in a few minutes.',
42
49
                    null);
43
50
            } else {
44
 
                if (errorCallback != null) {
 
51
                if (errorCallback !== null) {
45
52
                    errorCallback(self, id, response);
46
53
                } else {
47
54
                    self.showError(response.responseText, null);
60
67
 
61
68
namespace.RequestResponseHandler = RequestResponseHandler;
62
69
 
63
 
namespace.connect_requestbuilds = function() {
64
 
 
65
 
    var request_build_handle = Y.one('#request-builds');
 
70
namespace.connect_requestbuilds = function(config) {
 
71
    var io_provider = Y.lp.client.get_configured_io_provider(config),
 
72
        request_build_handle = Y.one('#request-builds');
66
73
    request_build_handle.addClass('js-action');
67
74
    request_build_handle.on('click', function(e) {
68
75
        e.preventDefault();
69
 
        if (request_build_overlay == null) {
 
76
        if (request_build_overlay === null) {
70
77
            // Render the form and load the widgets to display
71
 
            var recipe_name = LP.cache.context['name'];
 
78
            var recipe_name = LP.cache.context.name;
72
79
            request_build_overlay = new Y.lazr.FormOverlay({
73
80
                headerContent: '<h2>Request builds for '
74
81
                                    + recipe_name + ' </h2>',
80
87
                    '>Cancel</button>'),
81
88
                centered: true,
82
89
                form_submit_callback: do_request_builds,
 
90
                io_provider: io_provider,
83
91
                visible: false
84
92
            });
85
93
            Y.after(function() {
86
 
                disable_existing_builds(request_build_submit_button);
 
94
                disable_existing_builds(
 
95
                    request_build_submit_button, io_provider);
87
96
            }, request_build_overlay, "bindUI");
88
97
            request_build_overlay.render();
89
98
            request_build_submit_button =
107
116
        destroy_temporary_spinner();
108
117
    };
109
118
    request_build_response_handler.showError = function(header, error_msgs) {
110
 
        if (header == null) {
111
 
            if (error_msgs == null)
 
119
        if (header === null) {
 
120
            if (error_msgs === null) {
112
121
                header = "An error occurred, please contact an " +
113
122
                         "administrator.";
114
 
            else
 
123
            } else {
115
124
                header = "The following errors occurred:";
 
125
            }
116
126
        }
117
127
        var error_html = header;
118
 
        if (error_msgs != null) {
 
128
        if (error_msgs !== null) {
119
129
            error_html += "<ul>";
120
 
            if (typeof(error_msgs) == "string"){
 
130
            if (typeof(error_msgs) === "string"){
121
131
                error_msgs = [error_msgs];
122
132
            }
123
133
            Y.each(error_msgs, function(error_msg){
130
140
    };
131
141
};
132
142
 
 
143
namespace.destroy_requestbuilds = function() {
 
144
    request_build_overlay.destroy();
 
145
    request_build_overlay = null;
 
146
};
 
147
 
133
148
var NO_BUILDS_MESSAGE = "All requested recipe builds are already queued.";
134
149
var ONE_BUILD_MESSAGE = "1 new recipe build has been queued.";
135
150
var MANY_BUILDS_MESSAGE = "{nr_new} new recipe builds have been queued.";
158
173
        request_daily_build_handle.insert(
159
174
                build_message_node,
160
175
                request_daily_build_handle);
161
 
            
162
176
    };
163
177
    request_daily_build_handle.on('click', function(e) {
164
178
        e.preventDefault();
209
223
            },
210
224
            data: qs
211
225
        };
212
 
        var io = Y.io;
213
 
        if ( config !== undefined && Y.Lang.isValue(config.io)) {
214
 
            io = config.io;
215
 
        }
216
 
        io(submit_url, y_config);
 
226
        var io_provider = Y.lp.client.get_configured_io_provider(config);
 
227
        io_provider.io(submit_url, y_config);
217
228
    });
218
229
 
219
230
    // Wire up the processing hooks
223
234
    };
224
235
    request_daily_build_response_handler.showError = function(header, error) {
225
236
        var error_msg = header;
226
 
        if (error != null)
 
237
        if (error !== null) {
227
238
            error_msg += " " + error;
 
239
        }
228
240
        display_message(error_msg, 'build-error');
229
241
        Y.log(error_msg);
230
242
    };
233
245
/*
234
246
 * A function to return the current build records as displayed on the page
235
247
 */
236
 
function harvest_current_build_records() {
 
248
var harvest_current_build_records = function() {
237
249
    var row_classes = ['package-build', 'binary-build'];
238
 
    var builds = new Array();
 
250
    var builds = [];
239
251
    Y.Array.each(row_classes, function(row_class) {
240
252
        Y.all('.'+row_class).each(function(row) {
241
253
            var row_id = row.getAttribute('id');
245
257
        });
246
258
    });
247
259
    return builds;
248
 
}
 
260
};
249
261
 
250
262
/*
251
263
 * Render build records and flash the new ones
252
264
 */
253
 
function display_build_records(build_records_markup, current_builds) {
 
265
var display_build_records = function (build_records_markup, current_builds) {
254
266
    var target = Y.one('#builds-target');
255
267
    target.set('innerHTML', build_records_markup);
256
268
    var new_builds = harvest_current_build_records();
257
269
    var nr_new_builds = 0;
258
270
    Y.Array.each(new_builds, function(row_id) {
259
 
        if( current_builds.indexOf(row_id)>=0 )
 
271
        if( current_builds.indexOf(row_id)>=0 ) {
260
272
            return;
 
273
        }
261
274
        nr_new_builds += 1;
262
275
        var row = Y.one('#'+row_id);
263
276
        var anim = Y.lp.anim.green_flash({node: row});
264
277
        anim.run();
265
278
    });
266
279
    return nr_new_builds;
267
 
}
 
280
};
268
281
 
269
282
/*
270
283
 * Perform any client side validation
271
284
 * Return: true if data is valid
272
285
 */
273
 
function validate(data) {
274
 
    var distros = data['field.distroseries']
275
 
    if (Y.Object.size(distros) == 0) {
 
286
var validate = function(data) {
 
287
    var distros = data['field.distroseries'];
 
288
    if (Y.Object.size(distros) === 0) {
276
289
        request_build_response_handler.showError(
277
290
                "You need to specify at least one distro series for " +
278
291
                "which to build.", null);
279
292
        return false;
280
293
    }
281
294
    return true;
282
 
}
 
295
};
283
296
 
284
297
/*
285
298
 * The form submit function
286
299
 */
287
 
function do_request_builds(data) {
288
 
    if (!validate(data))
 
300
var do_request_builds = function(data, io_provider) {
 
301
    if (!validate(data)) {
289
302
        return;
 
303
    }
290
304
    request_build_submit_button.setAttribute("disabled", "disabled");
291
305
    var spinner_location = Y.one('.yui3-lazr-formoverlay-actions');
292
306
    create_temporary_spinner("Requesting builds...", spinner_location);
300
314
        on: {
301
315
            failure: request_build_response_handler.getErrorHandler(
302
316
                function(handler, id, response) {
 
317
                    var errors = [],
 
318
                        error_header = null,
 
319
                        error_header_text = "",
 
320
                        build_info, build_html, error_info,
 
321
                        nr_new, new_builds_message, field_name;
303
322
                    if( response.status >= 500 ) {
304
323
                        // There's some error content we need to display.
305
 
                        request_build_overlay.set(
306
 
                                'form_content', response.responseText);
307
 
                        request_build_overlay.get("form_submit_button")
308
 
                                .addClass('unseen');
309
 
                        request_build_overlay.renderUI();
310
 
                        //We want to force the form to be re-created
311
 
                        request_build_overlay = null;
312
 
                        return;
313
 
                    }
314
 
                    // The response will be a json data object containing info
315
 
                    // about what builds there are, informational text about
316
 
                    // any builds already pending, plus any errors. The
317
 
                    // FormOverlay infrastructure only supports displaying
318
 
                    // errors so we will construct our own HTML where the
319
 
                    // informational text will be appropriately displayed.
320
 
                    var build_info = Y.JSON.parse(response.responseText);
321
 
 
322
 
                    // The result of rendering the +builds view
323
 
                    var build_html = build_info['builds'];
324
 
                    // Any builds already pending (informational only)
325
 
                    var pending_build_info = build_info['already_pending'];
326
 
                    // Other more critical errors
327
 
                    var error_info = build_info['errors'];
328
 
 
329
 
                    var error_header_text = "";
330
 
                    if (build_html != null) {
331
 
                        var nr_new = display_build_records(
332
 
                                build_html, current_builds);
333
 
                        var new_builds_message = ONE_BUILD_MESSAGE;
334
 
                        if (nr_new > 1) {
335
 
                            new_builds_message =
336
 
                                    Y.Lang.substitute(
337
 
                                            MANY_BUILDS_MESSAGE,
338
 
                                            {nr_new: nr_new});
339
 
                        }
340
 
                        error_header_text = new_builds_message;
341
 
                    }
342
 
                    if (pending_build_info != null) {
343
 
                        if (error_header_text != "" )
344
 
                            error_header_text += "<p/>" + pending_build_info;
345
 
                        else
346
 
                            error_header_text = pending_build_info;
347
 
                    }
348
 
                    var errors = [];
349
 
                    for (var field_name in error_info)
350
 
                        errors.push(error_info[field_name]);
351
 
                    var error_header;
352
 
                    if (error_header_text != "") {
353
 
                        error_header =
 
324
                        errors = [
 
325
                            response.status + ' ' + response.statusText];
 
326
                    } else {
 
327
                        // The response will be a json data object containing
 
328
                        // info about what builds there are, informational
 
329
                        // text about any builds already pending, plus any
 
330
                        // errors. The FormOverlay infrastructure only
 
331
                        // supports displaying errors so we will construct
 
332
                        // our own HTML where the informational text will be
 
333
                        // appropriately displayed.
 
334
                        build_info = Y.JSON.parse(response.responseText);
 
335
 
 
336
                        // The result of rendering the +builds view
 
337
                        build_html = build_info.builds;
 
338
                        // Any builds already pending (informational only)
 
339
                        pending_build_info = build_info.already_pending;
 
340
                        // Other more critical errors
 
341
                        error_info = build_info.errors;
 
342
 
 
343
                        if (build_html !== null) {
 
344
                            nr_new = display_build_records(
 
345
                                    build_html, current_builds);
 
346
                            new_builds_message = ONE_BUILD_MESSAGE;
 
347
                            if (nr_new > 1) {
 
348
                                new_builds_message =
 
349
                                        Y.Lang.substitute(
 
350
                                                MANY_BUILDS_MESSAGE,
 
351
                                                {nr_new: nr_new});
 
352
                            }
 
353
                            error_header_text = new_builds_message;
 
354
                        }
 
355
                        if (pending_build_info !== null) {
 
356
                            if (error_header_text !== "" ) {
 
357
                                error_header_text += "<p/>" +
 
358
                                pending_build_info;
 
359
                            } else {
 
360
                                error_header_text = pending_build_info;
 
361
                            }
 
362
                        }
 
363
                        for (field_name in error_info) {
 
364
                            if (error_info.hasOwnProperty(field_name)) {
 
365
                                errors.push(error_info[field_name]);
 
366
                            }
 
367
                        }
 
368
                        if (error_header_text !== "") {
 
369
                            error_header =
354
370
                                "<p><div class='popup-build-informational'>"
355
 
                                +error_header_text+"</p></div>";
356
 
                        if (Y.Object.size(errors)>0) {
357
 
                            error_header +=
 
371
                                + error_header_text + "</p></div>";
 
372
                            if (Y.Object.size(errors)>0) {
 
373
                                error_header +=
358
374
                                    "<p/>" + "There were also some errors:";
 
375
                            }
 
376
                        } else {
 
377
                            error_header = "There were some errors:";
359
378
                        }
360
 
                    } else {
361
 
                        error_header = "There were some errors:"
362
379
                    }
363
380
                    handler.showError(error_header, errors);
364
381
                }),
366
383
                function(handler, id, response) {
367
384
                    request_build_overlay.hide();
368
385
                    display_build_records(
369
 
                            response.responseText, current_builds)
 
386
                            response.responseText, current_builds);
370
387
                })
371
388
        },
372
389
        form: {
374
391
            useDisabled: true
375
392
        }
376
393
    };
377
 
    Y.io(submit_url, y_config);
378
 
}
 
394
    io_provider.io(submit_url, y_config);
 
395
};
379
396
 
380
397
/*
381
398
 * Show the temporary "Requesting..." text
382
399
 */
383
 
function create_temporary_spinner(text, node) {
 
400
var create_temporary_spinner = function(text, node) {
384
401
    // Add the temp "Requesting build..." text
385
402
    var temp_spinner = Y.Node.create([
386
403
        '<div id="temp-spinner">',
388
405
        text,
389
406
        '</div>'].join(''));
390
407
    node.insert(temp_spinner, node);
391
 
}
 
408
};
392
409
 
393
410
/*
394
411
 * Destroy the temporary "Requesting..." text
395
412
 */
396
 
function destroy_temporary_spinner() {
 
413
var destroy_temporary_spinner = function() {
397
414
    var temp_spinner = Y.one('#temp-spinner');
398
415
    var spinner_parent = temp_spinner.get('parentNode');
399
416
    spinner_parent.removeChild(temp_spinner);
400
 
}
 
417
};
401
418
 
402
419
 
403
420
//****************************************************************************
410
427
// restore it when needed.
411
428
var distroseries_node_html;
412
429
 
413
 
function get_distroseries_nodes() {
 
430
var get_distroseries_nodes = function() {
414
431
    return request_build_overlay.form_node.all(
415
432
            "label[for^='field.distroseries.']");
416
 
}
417
 
 
418
 
var DISABLED_DISTROSERIES_CHECKBOX_HTML =
419
 
    "<input type='checkbox' class='checkboxType' disabled='disabled'>" +
420
 
    "&nbsp;{distro} (build pending)";
 
433
};
421
434
 
422
435
/*
423
436
 * Callback used to enable/disable distro series selection when a different
424
437
 * ppa is selected.
425
438
 */
426
 
function ppa_changed(ppa_value, submit_button) {
 
439
var ppa_changed = function(ppa_value, submit_button) {
427
440
    // Reset the disro series checkboxs to their default html.
428
 
    var distroseries_nodes = get_distroseries_nodes();
429
 
    for (var i=0; i<distroseries_nodes.size(); i++) {
430
 
        var distroseries_node = distroseries_nodes.item(i);
 
441
    var distroseries_nodes = get_distroseries_nodes(),
 
442
        distroseries_node, distro, escaped_distro,
 
443
        disabled_checkbox_html, i;
 
444
    for (i = 0; i < distroseries_nodes.size(); i++) {
 
445
        distroseries_node = distroseries_nodes.item(i);
431
446
        distroseries_node.set("innerHTML", distroseries_node_html[i]);
432
447
        distroseries_node.removeClass("lowlight");
433
448
    }
434
449
 
435
450
    var nr_matches = 0;
436
451
    Y.Array.each(last_known_pending_builds, function(distroarchive) {
437
 
        if (ppa_value != distroarchive[1])
 
452
        if (ppa_value !== distroarchive[1]) {
438
453
            return;
 
454
        }
439
455
 
440
 
        for (var i=0; i<distroseries_nodes.size(); i++) {
441
 
            var distroseries_node = distroseries_nodes.item(i);
442
 
            var distro = distroseries_node.get("text").trim();
443
 
            if (distro == distroarchive[0]) {
 
456
        for (i = 0; i < distroseries_nodes.size(); i++) {
 
457
            distroseries_node = distroseries_nodes.item(i);
 
458
            distro = distroseries_node.get("text").trim();
 
459
            if (distro === distroarchive[0]) {
444
460
                nr_matches += 1;
445
 
                var escaped_distro = Y.Escape.html(distro);
446
 
                var disabled_checkbox_html = Y.Lang.substitute(
 
461
                escaped_distro = Y.Escape.html(distro);
 
462
                disabled_checkbox_html = Y.Lang.substitute(
447
463
                    DISABLED_DISTROSERIES_CHECKBOX_HTML,
448
464
                    {distro: escaped_distro});
449
465
                distroseries_node.set("innerHTML", disabled_checkbox_html);
453
469
        }
454
470
    });
455
471
    // If no distro series can be selected, no need to show the submit btn.
456
 
    if (nr_matches>0 && nr_matches == distroseries_nodes.size())
 
472
    if (nr_matches>0 && nr_matches === distroseries_nodes.size()) {
457
473
        submit_button.hide();
458
 
    else
 
474
    } else {
459
475
        submit_button.show();
460
 
}
 
476
    }
 
477
};
461
478
 
462
 
function disable_existing_builds(submit_button) {
 
479
var disable_existing_builds = function(submit_button, io_provider) {
463
480
    // Lookup a the exiting builds and parse the info into a data structure so
464
481
    // so that we can attempt to prevent the user from requesting builds
465
482
    // which are already pending. It's not foolproof since a build may finish
466
483
    // or be initiated after this initial lookup, but we do handle that
467
484
    // situation in the error handling.
 
485
    var ppa_selector, y_config;
468
486
 
469
487
    // The ui may not be ready yet.
470
 
    var ppa_selector = request_build_overlay.form_node.one(
 
488
    ppa_selector = request_build_overlay.form_node.one(
471
489
            "[name='field.archive']");
472
 
    if (ppa_selector == null) {
 
490
    if (ppa_selector === null) {
473
491
        return;
474
492
    }
475
 
    distroseries_node_html = new Array();
476
 
    last_known_pending_builds = new Array();
477
 
    var y_config = {
 
493
    distroseries_node_html = [];
 
494
    last_known_pending_builds = [];
 
495
    y_config = {
478
496
        headers: {'Accept': 'application/json;'},
479
497
        on: {
480
498
            success:
481
499
                function(build_info) {
 
500
                    var distro_nodes,
 
501
                        size, build_record, distro_name, archive_token;
482
502
                    // We save the inner html of each distro series checkbox
483
503
                    // so we can restore it when required.
484
 
                    var distro_nodes = get_distroseries_nodes();
 
504
                    distro_nodes = get_distroseries_nodes();
485
505
                    distro_nodes.each(function(distro_node) {
486
506
                        distroseries_node_html.push(
487
507
                                distro_node.get("innerHTML"));
490
510
                    // We have a collection of the pending build info.
491
511
                    // The info is the distroseries displayname and archive
492
512
                    // token.
493
 
                    var size = build_info.length;
494
 
                    for( var i=0; i<size; i++ ) {
495
 
                        var build_record = build_info[i];
496
 
                        var distro_name = build_record["distroseries"];
497
 
                        var archive_token = build_record["archive"];
 
513
                    size = build_info.length;
 
514
                    for ( i = 0; i < size; i++ ) {
 
515
                        build_record = build_info[i];
 
516
                        distro_name = build_record.distroseries;
 
517
                        archive_token = build_record.archive;
498
518
                        last_known_pending_builds.push(
499
519
                                [distro_name, archive_token]);
500
520
                    }
505
525
                }
506
526
        }
507
527
    };
508
 
    set_up_lp_client();
 
528
    set_up_lp_client(io_provider);
509
529
    lp_client.named_get(
510
530
            LP.cache.context.self_link, 'getPendingBuildInfo', y_config);
511
 
}
 
531
};
512
532
}, "0.1", {"requires": [
513
533
    "dom", "node", "escape", "io-base", "lp.anim", "lazr.formoverlay",
514
 
    "lp.client"
515
 
    ]});
 
534
    "lp.client"]});