diff --git a/.jshintrc b/.jshintrc index 71993ab30..7d3590114 100644 --- a/.jshintrc +++ b/.jshintrc @@ -10,12 +10,13 @@ "debug" : false, "devel" : true, "es5" : true, - "strict" : false, + "strict" : true, "globalstrict" : false, "asi" : false, "laxbreak" : false, "bitwise" : true, "boss" : false, + "unused" : true, "curly" : true, "eqeqeq" : true, "eqnull" : false, diff --git a/jstests/tests/home/mock_jsapi.js b/jstests/tests/home/mock_jsapi.js new file mode 100644 index 000000000..eb18ad07b --- /dev/null +++ b/jstests/tests/home/mock_jsapi.js @@ -0,0 +1,64 @@ +define(['jquery'], function ($) { + "use strict"; + + return { + Application: { + create: function() { + }, + delete: function() { + }, + retrieve: function(id) { + var data = { + "12345": { + image: { + name: "app1", + ui_name: "Application 1", + icon_128: "", + description: "description", + policy: { + allow_home: true, + volume_source: "", + volume_target: "", + volume_mode: "" + }, + configurables: [ + "resolution" + ] + } + }, + "67890": { + image: { + name: "app2", + ui_name: "Application 2", + icon_128: "", + description: "description", + policy: { + allow_home: true, + volume_source: "", + volume_target: "", + volume_mode: "" + }, + configurables: [] + } + } + }; + + return $.when(data[id]); + + }, + items: function() { + return $.when(["12345", "67890"]); + } + }, + Container: { + create: function() { + }, + delete: function() { + }, + retrieve: function() { + }, + items: function() { + } + } + }; +}); diff --git a/jstests/tests/home/mock_remoteappapi.js b/jstests/tests/home/mock_remoteappapi.js deleted file mode 100644 index 76ba2432b..000000000 --- a/jstests/tests/home/mock_remoteappapi.js +++ /dev/null @@ -1,45 +0,0 @@ -define(function () { - "use strict"; - var MockApi = function () { - this.available_applications_info = function () { - return [{ - image: { - name: "app1", - ui_name: "Application 1", - icon_128: "", - description: "description", - policy: { - allow_home: true, - volume_source: "", - volume_target: "", - volume_mode: "" - }, - configurables: [ - "resolution" - ] - }, - mapping_id: "12345" - }, - { - image: { - name: "app2", - ui_name: "Application 2", - icon_128: "", - description: "description", - policy: { - allow_home: true, - volume_source: "", - volume_target: "", - volume_mode: "" - }, - configurables: [] - }, - mapping_id: "67890" - }]; - }; - }; - - return { - MockApi: MockApi - }; -}); diff --git a/jstests/tests/home/test_models.js b/jstests/tests/home/test_models.js index 8d4849d80..4f53008b2 100644 --- a/jstests/tests/home/test_models.js +++ b/jstests/tests/home/test_models.js @@ -1,13 +1,11 @@ define([ - "home/models", - "../../../../../jstests/tests/home/mock_remoteappapi" -], function (models, mock_api) { + "home/models" +], function (models) { "use strict"; QUnit.module("home.models"); QUnit.test("instantiation", function (assert) { - var api = new mock_api.MockApi(); - var model = new models.ApplicationListModel(api); + var model = new models.ApplicationListModel(); assert.equal(model.app_data.length, 0); model.update().done(function() { assert.equal(model.app_data.length, 2); diff --git a/jstests/tests/home/test_views.js b/jstests/tests/home/test_views.js index 614d0580e..d6769e2f2 100644 --- a/jstests/tests/home/test_views.js +++ b/jstests/tests/home/test_views.js @@ -1,14 +1,12 @@ define([ "home/models", "home/views", - "../../../../../jstests/tests/home/mock_remoteappapi", "jquery" -], function (models, views, mock_api, $) { +], function (models, views, $) { "use strict"; QUnit.module("home.views"); QUnit.test("rendering", function (assert) { - var api = new mock_api.MockApi(); - var model = new models.ApplicationListModel(api); + var model = new models.ApplicationListModel(); var view = new views.ApplicationListView(model); model.update() .done(function() { view.render(); } ) diff --git a/jstests/tests/test_remoteappapi.js b/jstests/tests/test_remoteappapi.js deleted file mode 100644 index fa2e5e683..000000000 --- a/jstests/tests/test_remoteappapi.js +++ /dev/null @@ -1,9 +0,0 @@ -define([ - "remoteappapi" -], function (RemoteAppAPI) { - QUnit.module("Remote App API"); - QUnit.test("test", function (assert) { - var api = new RemoteAppAPI(); - assert.ok(api !== null); - }); -}); diff --git a/jstests/testsuite.js b/jstests/testsuite.js index 205740412..ef74dcd3a 100644 --- a/jstests/testsuite.js +++ b/jstests/testsuite.js @@ -6,6 +6,7 @@ jquery: '../components/jquery/jquery.min', bootstrap: '../components/bootstrap/js/bootstrap.min', moment: "../components/moment/moment", + "jsapi/v1/resources": "../../../jstests/tests/home/mock_jsapi" }, shim: { bootstrap: { @@ -19,7 +20,6 @@ "tests/home/test_configurables.js", "tests/home/test_models.js", "tests/home/test_views.js", - "tests/test_remoteappapi.js", "tests/test_utils.js", "tests/test_analytics.js" ], function() { diff --git a/remoteappmanager/static/js/admin/accounting/controller.js b/remoteappmanager/static/js/admin/accounting/controller.js index d3d3387e3..7516d2925 100644 --- a/remoteappmanager/static/js/admin/accounting/controller.js +++ b/remoteappmanager/static/js/admin/accounting/controller.js @@ -5,7 +5,6 @@ define([ "jsapi/v1/resources" ], function ($, bootstrap, dialogs, resources) { "use strict"; - var base_url = window.apidata.base_url; $('#create-new-policy-dialog').on('show.bs.modal', function () { var dialog = $(this); @@ -75,7 +74,7 @@ define([ resources.Accounting.create(rep) .done(function() { window.location.reload(); }) - .fail(dialogs.error); + .fail(dialogs.webapi_error_dialog); }; var cancel_callback = function () { @@ -112,7 +111,7 @@ define([ function() { resources.Accounting.delete(id) .done(function() { window.location.reload(); }) - .fail(dialogs.error); + .fail(dialogs.webapi_error_dialog); } ); }); diff --git a/remoteappmanager/static/js/admin/applications/controller.js b/remoteappmanager/static/js/admin/applications/controller.js index b61ad0431..34fb6d2c8 100644 --- a/remoteappmanager/static/js/admin/applications/controller.js +++ b/remoteappmanager/static/js/admin/applications/controller.js @@ -5,7 +5,6 @@ require([ "jsapi/v1/resources" ], function ($, bootstrap, dialogs, resources) { "use strict"; - var base_url = window.apidata.base_url; $('#create-new-dialog').on('show.bs.modal', function () { var dialog = $(this); @@ -20,7 +19,7 @@ require([ dialog.modal('hide'); resources.Application.create({ image_name: image_name }) .done(function() { window.location.reload(); }) - .fail(dialogs.ajax_error_dialog); + .fail(dialogs.webapi_error_dialog); }; var cancel_callback = function () { @@ -58,7 +57,7 @@ require([ function() { resources.Application.delete(id) .done(function() { window.location.reload(); }) - .fail(dialogs.ajax_error_dialog); + .fail(dialogs.webapi_error_dialog); } ); }); diff --git a/remoteappmanager/static/js/admin/containers/controller.js b/remoteappmanager/static/js/admin/containers/controller.js index b964b2198..f51231126 100644 --- a/remoteappmanager/static/js/admin/containers/controller.js +++ b/remoteappmanager/static/js/admin/containers/controller.js @@ -5,7 +5,6 @@ require([ "jsapi/v1/resources" ], function ($, bootstrap, dialogs, resources) { "use strict"; - var base_url = window.apidata.base_url; $('#action-dialog').on('show.bs.modal', function (event) { var button = $(event.relatedTarget); @@ -19,7 +18,7 @@ require([ function () { resources.Container.delete(url_id) .done(function () { window.location.reload(); }) - .fail(dialogs.ajax_error_dialog); + .fail(dialogs.webapi_error_dialog); } ); }); diff --git a/remoteappmanager/static/js/admin/home/controller.js b/remoteappmanager/static/js/admin/home/controller.js index 3459e33df..d4d99a7e7 100644 --- a/remoteappmanager/static/js/admin/home/controller.js +++ b/remoteappmanager/static/js/admin/home/controller.js @@ -1,6 +1,7 @@ /*globals: require, console*/ require([ "jquery" -], function($) { +], function() { "use strict"; + }); diff --git a/remoteappmanager/static/js/admin/tabular/controller.js b/remoteappmanager/static/js/admin/tabular/controller.js index f96c54a9e..756972e8f 100644 --- a/remoteappmanager/static/js/admin/tabular/controller.js +++ b/remoteappmanager/static/js/admin/tabular/controller.js @@ -1,7 +1,7 @@ define([ "jquery", "datatables.net" -], function ($, dt) { +], function ($, dt) { // jshint ignore:line "use strict"; $("#datatable").DataTable(); diff --git a/remoteappmanager/static/js/admin/users/controller.js b/remoteappmanager/static/js/admin/users/controller.js index 1c776d111..61c906c5d 100644 --- a/remoteappmanager/static/js/admin/users/controller.js +++ b/remoteappmanager/static/js/admin/users/controller.js @@ -5,7 +5,6 @@ define([ "jsapi/v1/resources" ], function ($, bootstrap, dialogs, resources) { "use strict"; - var base_url = window.apidata.base_url; $('#create-new-dialog').on('show.bs.modal', function () { var dialog = $(this); @@ -20,7 +19,7 @@ define([ dialog.modal('hide'); resources.User.create({ name: user_name }) .done(function() { window.location.reload(); }) - .fail(dialogs.ajax_error_dialog); + .fail(dialogs.webapi_error_dialog); }; var cancel_callback = function () { @@ -58,7 +57,7 @@ define([ function() { resources.User.delete(id) .done(function() { window.location.reload(); }) - .fail(dialogs.ajax_error_dialog); + .fail(dialogs.webapi_error_dialog); } ); }); diff --git a/remoteappmanager/static/js/analytics.js b/remoteappmanager/static/js/analytics.js index 3943ef66e..597f3a9a2 100644 --- a/remoteappmanager/static/js/analytics.js +++ b/remoteappmanager/static/js/analytics.js @@ -1,4 +1,8 @@ -define(function (require) { +define([ + "require" +], function (require) { + "use strict"; + function init() { var module; diff --git a/remoteappmanager/static/js/dialogs.js b/remoteappmanager/static/js/dialogs.js index 93f66ed45..21a4c9095 100644 --- a/remoteappmanager/static/js/dialogs.js +++ b/remoteappmanager/static/js/dialogs.js @@ -3,30 +3,19 @@ define([ ], function ($) { "use strict"; - var ajax_error_msg = function (jqXHR) { - // Return a JSON error message if there is one, - // otherwise the basic HTTP status text. - if (jqXHR.responseJSON && jqXHR.responseJSON.message) { - return jqXHR.responseJSON.message; - } else { - return jqXHR.statusText; + var webapi_error_dialog = function (error) { + // Shows the error dialog with a webapi error. + // The webapi error is an object with the following props: + // - status : HTTP error status + // - message: user-readable message from the server via the + // response payload. may be undefined. + + var msg = error.message; + if (!msg) { + msg = "Unknown error"; } - }; - - var log_ajax_error = function (jqXHR, status, error) { - // log ajax failures with informative messages - var msg = "API request failed (" + jqXHR.status + "): "; - console.log(jqXHR); - msg += ajax_error_msg(jqXHR); - console.log(msg); - return msg; - }; - - var ajax_error_dialog = function (jqXHR, status, error) { - console.log("ajax dialog", arguments); - var msg = log_ajax_error(jqXHR, status, error); var dialog = $("#error-dialog"); - dialog.find(".ajax-error").text(msg); + dialog.find(".error-msg").text(error.code + " - "+msg); dialog.modal(); }; @@ -52,7 +41,7 @@ define([ }; return { - ajax_error_dialog : ajax_error_dialog, + webapi_error_dialog : webapi_error_dialog, config_dialog : config_dialog }; diff --git a/remoteappmanager/static/js/home/controller.js b/remoteappmanager/static/js/home/controller.js index 96e5c046b..0d1477161 100644 --- a/remoteappmanager/static/js/home/controller.js +++ b/remoteappmanager/static/js/home/controller.js @@ -3,20 +3,19 @@ require([ "jquery", "urlutils", "dialogs", - "remoteappapi", "analytics", "home/models", - "home/views" -], function($, urlutils, dialogs, RemoteAppAPI, analytics, models, views) { + "home/views", + "jsapi/v1/resources" +], function($, urlutils, dialogs, analytics, models, views, resources) { "use strict"; var ga = analytics.init(); var base_url = window.apidata.base_url; - var appapi = new RemoteAppAPI(base_url); // This model keeps the retrieved content from the REST query locally. // It is only synchronized at initial load. - var model = new models.ApplicationListModel(appapi); + var model = new models.ApplicationListModel(); var view = new views.ApplicationListView(model); var new_container_window = function (url_id) { @@ -45,16 +44,18 @@ require([ var url_id = app_info.container.url_id; - appapi.stop_application(url_id, { - success: function () { + resources.Container.delete(url_id) + .done(function () { model.update_idx(index) .done(promise.resolve) .fail(promise.reject); - }, - error: function (jqXHR, status, error) { - dialogs.ajax_error_dialog(jqXHR, status, error); - promise.reject(); - }}); + }) + .fail( + function (error) { + dialogs.webapi_error_dialog(error); + promise.reject(); + }); + return promise; }; @@ -68,7 +69,7 @@ require([ configurables_data = {}; Object.getOwnPropertyNames(configurables).forEach( - function(val, idx, array) { + function(val, idx, array) { // jshint ignore:line var configurable = configurables[val]; var tag = configurable.tag; configurables_data[tag] = configurable.as_config_dict(); @@ -76,28 +77,20 @@ require([ ); var promise = $.Deferred(); - appapi.start_application(mapping_id, configurables_data, { - error: function(jqXHR, status, error) { - promise.reject(); - }, - statusCode: { - 201: function (data, textStatus, request) { - var location = request.getResponseHeader('Location'); - var url = urlutils.parse(location); - var arr = url.pathname.replace(/\/$/, "").split('/'); - var url_id = arr[arr.length-1]; - - ga("send", "event", { - eventCategory: "Application", - eventAction: "start", - eventLabel: image_name - }); - - new_container_window(url_id); - model.update_idx(index).done(promise.resolve); - } - } - }); + + resources.Container.create({ + mapping_id: mapping_id, + configurables: configurables_data + }).done(function(id) { + ga("send", "event", { + eventCategory: "Application", + eventAction: "start", + eventLabel: image_name + }); + + new_container_window(id); + model.update_idx(index).done(promise.resolve); + }).fail(function() { promise.reject(); }); return promise; }; diff --git a/remoteappmanager/static/js/home/models.js b/remoteappmanager/static/js/home/models.js index 27e903014..765bb5a5f 100644 --- a/remoteappmanager/static/js/home/models.js +++ b/remoteappmanager/static/js/home/models.js @@ -1,15 +1,82 @@ define([ 'jquery', - 'home/configurables' -], function ($, configurables) { + 'home/configurables', + 'utils', + 'jsapi/v1/resources' +], function ($, configurables, utils, resources) { "use strict"; + + // This routine will go away when we provide the representation data + // inline with the items at tornado-webapi level. - var ApplicationListModel = function(remote_app_api) { + var available_applications_info = function () { + // Retrieve information from the various applications and + // connect the cascading callbacks. + // Returns a single promise. When resolved, the attached + // callbacks will be passed an array of the promises for the various + // retrieve operations, successful or not. + + var promise = $.Deferred(); + + resources.Application.items() + .done(function (ids) { + // We neutralize the potential error from a jXHR request + // and make sure that all our requests "succeed" so that + // all/when can guarantee everything is done. + + // These will go out of scope but they are still alive + // and performing to completion + var requests = []; + for (var i = 0; i < ids.length; i++) { + requests.push($.Deferred()); + } + + // We need to bind with the current i index in the loop + // Hence we create these closures to grab the index for + // each new index + var done_callback = function(index) { + return function(rep) { + requests[index].resolve(rep); + }; + }; + var fail_callback = function(index) { + return function() { + requests[index].resolve(null); + }; + }; + + for (i = 0; i < ids.length; i++) { + var id = ids[i]; + resources.Application.retrieve(id) + .done(done_callback(i)) + .fail(fail_callback(i)); + + } + + utils.all(requests) + .done(function (promises) { + // Fills the local application model with the results of the + // retrieve promises. + var data = []; + for (var i = 0; i < promises.length; i++) { + var result = promises[i]; + if (result !== null) { + data.push(result); + } + } + promise.resolve(data); + }); + + }) + .fail(function() { + promise.resolve([]); + }); + + return promise; + }; + + var ApplicationListModel = function() { // (constructor) Model for the application list. - // Parameters - // remote_app_api : RemoteAppAPI - // The remote application API stub object - this._appapi = remote_app_api; // Contains the data retrieved from the remote API this.app_data = []; @@ -30,7 +97,7 @@ define([ var self = this; return $.when( - self._appapi.available_applications_info() + available_applications_info() ).done(function (app_data) { self.app_data = app_data; self.configurables = []; @@ -48,12 +115,11 @@ define([ var entry = this.app_data[index]; var mapping_id = entry.mapping_id; - return $.when( - self._appapi.application_info(mapping_id) - ).done(function(new_data) { - self.app_data[index] = new_data; - self._update_configurables(index); - }); + return resources.Application.retrieve(mapping_id) + .done(function(new_data) { + self.app_data[index] = new_data; + self._update_configurables(index); + }); }; ApplicationListModel.prototype._update_configurables = function(index) { diff --git a/remoteappmanager/static/js/home/views.js b/remoteappmanager/static/js/home/views.js index 416e46e6a..41d83b81f 100644 --- a/remoteappmanager/static/js/home/views.js +++ b/remoteappmanager/static/js/home/views.js @@ -19,9 +19,9 @@ define([ // Handlers for button clicking. // replace them to override default behavior (doing nothing). // Can return a value or a promise. - this.stop_button_clicked = function(index) {}; - this.start_button_clicked = function(index) {}; - this.view_button_clicked = function(index) {}; + this.stop_button_clicked = function(index) {}; // jshint ignore:line + this.start_button_clicked = function(index) {}; // jshint ignore:line + this.view_button_clicked = function(index) {}; // jshint ignore:line this._x_button_clicked = function () { // Triggered when the button X (left side) is clicked @@ -146,7 +146,7 @@ define([ if (app_data.container === null) { var ul = $("