diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.controller.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.controller.js index 60053f6..908b461 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.controller.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.controller.js @@ -23,7 +23,8 @@ HealthMonitorDetailController.$inject = [ 'horizon.app.core.openstack-service-api.lbaasv2', 'horizon.dashboard.project.lbaasv2.healthmonitors.actions.rowActions', - '$routeParams' + '$routeParams', + '$q' ]; /** @@ -36,12 +37,15 @@ * @param api The LBaaS v2 API service. * @param rowActions The LBaaS v2 health monitor row actions service. * @param $routeParams The angular $routeParams service. + * @param $q The angular service for promises. * @returns undefined */ - function HealthMonitorDetailController(api, rowActions, $routeParams) { + function HealthMonitorDetailController(api, rowActions, $routeParams, $q) { var ctrl = this; + ctrl.loading = true; + ctrl.error = false; ctrl.actions = rowActions.init($routeParams.loadbalancerId, $routeParams.listenerId, $routeParams.poolId).actions; @@ -51,18 +55,46 @@ //////////////////////////////// function init() { - api.getHealthMonitor($routeParams.healthmonitorId).success(set('healthmonitor')); - api.getPool($routeParams.poolId).success(set('pool')); - api.getListener($routeParams.listenerId).success(set('listener')); - api.getLoadBalancer($routeParams.loadbalancerId).success(set('loadbalancer')); + ctrl.healthmonitor = null; + ctrl.pool = null; + ctrl.listener = null; + ctrl.loadbalancer = null; + ctrl.loading = true; + ctrl.error = false; + $q.all([ + api.getHealthMonitor($routeParams.healthmonitorId) + .then(success('healthmonitor'), fail('healthmonitor')), + api.getPool($routeParams.poolId) + .then(success('pool'), fail('pool')), + api.getListener($routeParams.listenerId) + .then(success('listener'), fail('listener')), + api.getLoadBalancer($routeParams.loadbalancerId) + .then(success('loadbalancer'), fail('loadbalancer')) + ]).then(postInit, initError); } - function set(property) { - return angular.bind(null, function setProp(property, value) { - ctrl[property] = value; + function success(property) { + return angular.bind(null, function setProp(property, response) { + ctrl[property] = response.data; }, property); } + function fail(property) { + return angular.bind(null, function setProp(property, error) { + ctrl[property] = null; + throw error; + }, property); + } + + function postInit() { + ctrl.loading = false; + } + + function initError() { + ctrl.loading = false; + ctrl.error = true; + } + } })(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.controller.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.controller.spec.js index fca6b86..fd02239 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.controller.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.controller.spec.js @@ -17,26 +17,42 @@ 'use strict'; describe('LBaaS v2 Healthmonitor Detail Controller', function() { - var lbaasv2API, ctrl; + var lbaasv2API, $controller, apiFail, qAllFail; - function fakeAPI() { + function fakePromise(data, reject) { return { - success: function(callback) { - callback('foo'); + then: function(success, fail) { + if (reject) { + fail(); + } else { + success({ data: data }); + } + return fakePromise(); } }; } + function fakeAPI() { + return fakePromise('foo', apiFail); + } + function loadbalancerAPI() { - var loadbalancer = { provisioning_status: 'ACTIVE' }; - return { - success: function(callback) { - callback(loadbalancer); - }, - then: function(callback) { - callback({ data: loadbalancer }); + return fakePromise({ provisioning_status: 'ACTIVE' }); + } + + function qAll() { + return fakePromise(null, qAllFail); + } + + function createController() { + return $controller('HealthMonitorDetailController', { + $routeParams: { + loadbalancerId: 'loadbalancerId', + listenerId: 'listenerId', + poolId: 'poolId', + healthmonitorId: 'healthmonitorId' } - }; + }); } /////////////////////// @@ -47,24 +63,24 @@ beforeEach(module('horizon.app.core.openstack-service-api')); beforeEach(module('horizon.dashboard.project.lbaasv2')); + beforeEach(module(function($provide) { + apiFail = false; + qAllFail = false; + + $provide.value('$q', { all: qAll }); + })); + beforeEach(inject(function($injector) { lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2'); spyOn(lbaasv2API, 'getHealthMonitor').and.callFake(fakeAPI); spyOn(lbaasv2API, 'getPool').and.callFake(fakeAPI); spyOn(lbaasv2API, 'getListener').and.callFake(fakeAPI); spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI); - var controller = $injector.get('$controller'); - ctrl = controller('HealthMonitorDetailController', { - $routeParams: { - loadbalancerId: 'loadbalancerId', - listenerId: 'listenerId', - poolId: 'poolId', - healthmonitorId: 'healthmonitorId' - } - }); + $controller = $injector.get('$controller'); })); it('should invoke lbaasv2 apis', function() { + var ctrl = createController(); expect(lbaasv2API.getHealthMonitor).toHaveBeenCalledWith('healthmonitorId'); expect(lbaasv2API.getPool).toHaveBeenCalledWith('poolId'); expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId'); @@ -75,6 +91,21 @@ expect(ctrl.healthmonitor).toBe('foo'); }); + it('should throw error on API fail', function() { + apiFail = true; + var init = function() { + createController(); + }; + expect(init).toThrow(); + }); + + it('should set error state if any APIs fail', function() { + qAllFail = true; + var ctrl = createController(); + expect(ctrl.loading).toBe(false); + expect(ctrl.error).toBe(true); + }); + }); })(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.html b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.html index d0eb2ca..fe1b965 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.html +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/healthmonitors/detail.html @@ -1,39 +1,42 @@
- -
-
-
-
Type
-
{$ ::ctrl.healthmonitor.type $}
-
Delay
-
{$ ::ctrl.healthmonitor.delay $}
-
Max Retries
-
{$ ::ctrl.healthmonitor.max_retries $}
-
Timeout
-
{$ ::ctrl.healthmonitor.timeout $}
-
HTTP Method
-
{$ ::ctrl.healthmonitor.http_method $}
-
Expected Codes
-
{$ ::ctrl.healthmonitor.expected_codes $}
-
URL Path
-
{$ ::ctrl.healthmonitor.url_path $}
-
Admin State Up
-
{$ ctrl.healthmonitor.admin_state_up | yesno $}
-
Monitor ID
-
{$ ::ctrl.healthmonitor.id $}
-
Tenant ID
-
{$ ::ctrl.healthmonitor.tenant_id $}
-
+ +
+ +
+
+
+
Type
+
{$ ::ctrl.healthmonitor.type $}
+
Delay
+
{$ ::ctrl.healthmonitor.delay $}
+
Max Retries
+
{$ ::ctrl.healthmonitor.max_retries $}
+
Timeout
+
{$ ::ctrl.healthmonitor.timeout $}
+
HTTP Method
+
{$ ::ctrl.healthmonitor.http_method $}
+
Expected Codes
+
{$ ::ctrl.healthmonitor.expected_codes $}
+
URL Path
+
{$ ::ctrl.healthmonitor.url_path $}
+
Admin State Up
+
{$ ctrl.healthmonitor.admin_state_up | yesno $}
+
Monitor ID
+
{$ ::ctrl.healthmonitor.id $}
+
Tenant ID
+
{$ ::ctrl.healthmonitor.tenant_id $}
+
+
\ No newline at end of file diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/lbaasv2.scss b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/lbaasv2.scss index b75dfe7..6e51091 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/lbaasv2.scss +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/lbaasv2.scss @@ -68,8 +68,9 @@ } } -/* Progress indicator in the table while items are loading */ -[table-status] { +/* Progress indicator while data is loading */ +[table-status], +detail-status { .progress { margin: 0px auto; width: 25%; @@ -78,4 +79,13 @@ width: 100%; } } +} +detail-status { + .progress { + margin-top: 25vh; + } + .error-actions { + text-align: center; + margin-top: 10px; + } } \ No newline at end of file diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.controller.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.controller.js index 680d09d..3fe9dc3 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.controller.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.controller.js @@ -23,7 +23,8 @@ ListenerDetailController.$inject = [ 'horizon.app.core.openstack-service-api.lbaasv2', 'horizon.dashboard.project.lbaasv2.listeners.actions.rowActions', - '$routeParams' + '$routeParams', + '$q' ]; /** @@ -36,12 +37,15 @@ * @param api The LBaaS v2 API service. * @param rowActions The listener row actions service. * @param $routeParams The angular $routeParams service. + * @param $q The angular service for promises. * @returns undefined */ - function ListenerDetailController(api, rowActions, $routeParams) { + function ListenerDetailController(api, rowActions, $routeParams, $q) { var ctrl = this; + ctrl.loading = true; + ctrl.error = false; ctrl.actions = rowActions.init($routeParams.loadbalancerId).actions; init(); @@ -49,16 +53,40 @@ //////////////////////////////// function init() { - api.getListener($routeParams.listenerId).success(set('listener')); - api.getLoadBalancer($routeParams.loadbalancerId).success(set('loadbalancer')); + ctrl.listener = null; + ctrl.loadbalancer = null; + ctrl.loading = true; + ctrl.error = false; + $q.all([ + api.getListener($routeParams.listenerId) + .then(success('listener'), fail('listener')), + api.getLoadBalancer($routeParams.loadbalancerId) + .then(success('loadbalancer'), fail('loadbalancer')) + ]).then(postInit, initError); } - function set(property) { - return angular.bind(null, function setProp(property, value) { - ctrl[property] = value; + function success(property) { + return angular.bind(null, function setProp(property, response) { + ctrl[property] = response.data; }, property); } + function fail(property) { + return angular.bind(null, function setProp(property, error) { + ctrl[property] = null; + throw error; + }, property); + } + + function postInit() { + ctrl.loading = false; + } + + function initError() { + ctrl.loading = false; + ctrl.error = true; + } + } })(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.controller.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.controller.spec.js index 8b70cc8..586fd2c 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.controller.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.controller.spec.js @@ -17,26 +17,40 @@ 'use strict'; describe('LBaaS v2 Listener Detail Controller', function() { - var lbaasv2API, ctrl; + var lbaasv2API, $controller, apiFail, qAllFail; - function fakeAPI() { + function fakePromise(data, reject) { return { - success: function(callback) { - callback('foo'); + then: function(success, fail) { + if (reject) { + fail(); + } else { + success({ data: data }); + } + return fakePromise(); } }; } + function fakeAPI() { + return fakePromise('foo', apiFail); + } + function loadbalancerAPI() { - var loadbalancer = { provisioning_status: 'ACTIVE' }; - return { - success: function(callback) { - callback(loadbalancer); - }, - then: function(callback) { - callback({ data: loadbalancer }); + return fakePromise({ provisioning_status: 'ACTIVE' }); + } + + function qAll() { + return fakePromise(null, qAllFail); + } + + function createController() { + return $controller('ListenerDetailController', { + $routeParams: { + loadbalancerId: 'loadbalancerId', + listenerId: 'listenerId' } - }; + }); } /////////////////////// @@ -48,6 +62,10 @@ beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module(function($provide) { + apiFail = false; + qAllFail = false; + + $provide.value('$q', { all: qAll }); $provide.value('$uibModal', {}); })); @@ -55,22 +73,32 @@ lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2'); spyOn(lbaasv2API, 'getListener').and.callFake(fakeAPI); spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI); - var controller = $injector.get('$controller'); - ctrl = controller('ListenerDetailController', { - $routeParams: { - loadbalancerId: 'loadbalancerId', - listenerId: 'listenerId' - } - }); + $controller = $injector.get('$controller'); })); it('should invoke lbaasv2 apis', function() { + var ctrl = createController(); expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId'); expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('loadbalancerId'); expect(ctrl.loadbalancer).toEqual({ provisioning_status: 'ACTIVE' }); expect(ctrl.listener).toBe('foo'); }); + it('should throw error on API fail', function() { + apiFail = true; + var init = function() { + createController(); + }; + expect(init).toThrow(); + }); + + it('should set error state if any APIs fail', function() { + qAllFail = true; + var ctrl = createController(); + expect(ctrl.loading).toBe(false); + expect(ctrl.error).toBe(true); + }); + }); })(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.html b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.html index 588eee6..d24e60d 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.html +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/detail.html @@ -1,39 +1,42 @@
- -
-
-
-
Protocol
-
{$ ::ctrl.listener.protocol $}
-
Protocol Port
-
{$ ::ctrl.listener.protocol_port $}
-
Connection Limit
-
{$ ctrl.listener.connection_limit | limit $}
-
Admin State Up
-
{$ ctrl.listener.admin_state_up | yesno $}
-
Default Pool ID
-
- - {$ ::ctrl.listener.default_pool_id $} - - - {$ 'None' | translate $} - -
-
Listener ID
-
{$ ::ctrl.listener.id $}
-
Tenant ID
-
{$ ::ctrl.listener.tenant_id $}
-
+ +
+ +
+
+
+
Protocol
+
{$ ::ctrl.listener.protocol $}
+
Protocol Port
+
{$ ::ctrl.listener.protocol_port $}
+
Connection Limit
+
{$ ctrl.listener.connection_limit | limit $}
+
Admin State Up
+
{$ ctrl.listener.admin_state_up | yesno $}
+
Default Pool ID
+
+ + {$ ::ctrl.listener.default_pool_id $} + + + {$ 'None' | translate $} + +
+
Listener ID
+
{$ ::ctrl.listener.id $}
+
Tenant ID
+
{$ ::ctrl.listener.tenant_id $}
+
+
\ No newline at end of file diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.js index b252554..e01f5f5 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.js @@ -50,6 +50,8 @@ ) { var ctrl = this; + ctrl.loading = true; + ctrl.error = false; ctrl.actions = rowActions.actions; ctrl.operatingStatus = loadBalancersService.operatingStatus; ctrl.provisioningStatus = loadBalancersService.provisioningStatus; @@ -60,11 +62,21 @@ //////////////////////////////// function init() { - api.getLoadBalancer($routeParams.loadbalancerId, true).success(success); + ctrl.loadbalancer = null; + ctrl.loading = true; + ctrl.error = false; + api.getLoadBalancer($routeParams.loadbalancerId, true).then(success, fail); } function success(response) { - ctrl.loadbalancer = response; + ctrl.loadbalancer = response.data; + ctrl.loading = false; + } + + function fail(/*response*/) { + ctrl.loadbalancer = null; + ctrl.loading = false; + ctrl.error = true; } // Save the active state of the listeners tab in the global window object so it can stay diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.spec.js index 7192493..538f0b4 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.spec.js @@ -17,16 +17,28 @@ 'use strict'; describe('LBaaS v2 Load Balancer Detail Controller', function() { - var lbaasv2API, ctrl, $scope, $window; + var lbaasv2API, $scope, $window, $controller, apiFail; function fakeAPI() { return { - success: function(callback) { - callback({ id: '1234' }); + then: function(success, fail) { + if (apiFail && fail) { + fail(); + } else { + success({ id: '1234' }); + } } }; } + function createController() { + return $controller('LoadBalancerDetailController', { + $scope: $scope, + $window: $window, + $routeParams: { loadbalancerId: '1234' } + }); + } + /////////////////////// beforeEach(module('horizon.framework.widgets')); @@ -36,6 +48,7 @@ beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module(function($provide) { + apiFail = false; $provide.value('$uibModal', {}); })); @@ -44,19 +57,16 @@ spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(fakeAPI); $scope = $injector.get('$rootScope').$new(); $window = {}; - var controller = $injector.get('$controller'); - ctrl = controller('LoadBalancerDetailController', { - $scope: $scope, - $window: $window, - $routeParams: { loadbalancerId: '1234' } - }); + $controller = $injector.get('$controller'); })); it('should invoke lbaasv2 apis', function() { + createController(); expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('1234', true); }); it('should save changes to listeners tab active state', function() { + var ctrl = createController(); expect($window.listenersTabActive).toBeUndefined(); expect(ctrl.listenersTabActive).toBeUndefined(); ctrl.listenersTabActive = true; @@ -67,6 +77,13 @@ expect($window.listenersTabActive).toBe(false); }); + it('should set error state', function() { + apiFail = true; + var ctrl = createController(); + expect(ctrl.loading).toBe(false); + expect(ctrl.error).toBe(true); + }); + }); })(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.html b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.html index 0561244..74c2572 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.html +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.html @@ -1,54 +1,57 @@
- - - -
-
-
-
Provider
-
{$ ::ctrl.loadbalancer.provider $}
-
Admin State Up
-
{$ ctrl.loadbalancer.admin_state_up | yesno $}
-
Floating IP Address
-
{$ ctrl.loadbalancer.floating_ip.ip | noValue:('None' | translate) $}
-
Load Balancer ID
-
{$ ::ctrl.loadbalancer.id $}
-
Subnet ID
-
- {$ ::ctrl.loadbalancer.vip_subnet_id $} -
-
Port ID
-
- {$ ::ctrl.loadbalancer.vip_port_id $} -
-
+ +
+ + + +
+
+
+
Provider
+
{$ ::ctrl.loadbalancer.provider $}
+
Admin State Up
+
{$ ctrl.loadbalancer.admin_state_up | yesno $}
+
Floating IP Address
+
{$ ctrl.loadbalancer.floating_ip.ip | noValue:('None' | translate) $}
+
Load Balancer ID
+
{$ ::ctrl.loadbalancer.id $}
+
Subnet ID
+
+ {$ ::ctrl.loadbalancer.vip_subnet_id $} +
+
Port ID
+
+ {$ ::ctrl.loadbalancer.vip_port_id $} +
+
+
-
- - - - - + + + + + +
\ No newline at end of file diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.controller.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.controller.js index c073b05..8df0504 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.controller.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.controller.js @@ -24,6 +24,7 @@ 'horizon.app.core.openstack-service-api.lbaasv2', 'horizon.dashboard.project.lbaasv2.members.actions.rowActions', '$routeParams', + '$q', 'horizon.dashboard.project.lbaasv2.loadbalancers.service', 'horizon.dashboard.project.lbaasv2.members.service' ]; @@ -38,16 +39,19 @@ * @param api The LBaaS v2 API service. * @param rowActions The pool members row actions service. * @param $routeParams The angular $routeParams service. + * @param $q The angular service for promises. * @param loadBalancersService The LBaaS v2 load balancers service. * @param membersService The LBaaS v2 members service. * @returns undefined */ function MemberDetailController( - api, rowActions, $routeParams, loadBalancersService, membersService + api, rowActions, $routeParams, $q, loadBalancersService, membersService ) { var ctrl = this; + ctrl.loading = true; + ctrl.error = false; ctrl.actions = rowActions.init($routeParams.loadbalancerId, $routeParams.poolId).actions; ctrl.loadbalancerId = $routeParams.loadbalancerId; ctrl.listenerId = $routeParams.listenerId; @@ -60,25 +64,53 @@ //////////////////////////////// function init() { - api.getMember($routeParams.poolId, $routeParams.memberId).success(memberSuccess); - api.getPool($routeParams.poolId).success(set('pool')); - api.getListener($routeParams.listenerId).success(set('listener')); - api.getLoadBalancer($routeParams.loadbalancerId).success(set('loadbalancer')); + ctrl.member = null; + ctrl.pool = null; + ctrl.listener = null; + ctrl.loadbalancer = null; + ctrl.loading = true; + ctrl.error = false; + $q.all([ + api.getMember($routeParams.poolId, $routeParams.memberId) + .then(success('member'), fail('member')), + api.getPool($routeParams.poolId) + .then(success('pool'), fail('pool')), + api.getListener($routeParams.listenerId) + .then(success('listener'), fail('listener')), + api.getLoadBalancer($routeParams.loadbalancerId) + .then(success('loadbalancer'), fail('loadbalancer')) + ]).then(postInit, initError); } - function set(property) { - return angular.bind(null, function setProp(property, value) { - ctrl[property] = value; + function success(property) { + return angular.bind(null, function setProp(property, response) { + ctrl[property] = response.data; + + if (property === 'member') { + membersService.associateMemberStatuses( + ctrl.loadbalancerId, + ctrl.listenerId, + ctrl.poolId, + [ctrl.member]); + } + }, property); } - function memberSuccess(response) { - ctrl.member = response; - membersService.associateMemberStatuses( - ctrl.loadbalancerId, - ctrl.listenerId, - ctrl.poolId, - [ctrl.member]); + function fail(property) { + return angular.bind(null, function setProp(property, error) { + ctrl[property] = null; + throw error; + }, property); + } + + function postInit() { + ctrl.loading = false; + } + + function initError() { + ctrl.loading = false; + ctrl.error = true; } } diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.controller.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.controller.spec.js index f6e58c0..93542c1 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.controller.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.controller.spec.js @@ -17,26 +17,42 @@ 'use strict'; describe('LBaaS v2 Member Detail Controller', function() { - var controller, lbaasv2API, membersService, ctrl, actions; + var $controller, lbaasv2API, membersService, apiFail, qAllFail, actions; - function fakeAPI() { + function fakePromise(data, reject) { return { - success: function(callback) { - callback('foo'); + then: function(success, fail) { + if (reject) { + fail(); + } else { + success({ data: data }); + } + return fakePromise(); } }; } + function fakeAPI() { + return fakePromise('foo', apiFail); + } + function loadbalancerAPI() { - var loadbalancer = { provisioning_status: 'ACTIVE' }; - return { - success: function(callback) { - callback(loadbalancer); - }, - then: function(callback) { - callback({ data: loadbalancer }); + return fakePromise({ provisioning_status: 'ACTIVE' }); + } + + function qAll() { + return fakePromise(null, qAllFail); + } + + function createController() { + return $controller('MemberDetailController', { + $routeParams: { + loadbalancerId: 'loadbalancerId', + listenerId: 'listenerId', + poolId: 'poolId', + memberId: 'memberId' } - }; + }); } /////////////////////// @@ -48,6 +64,10 @@ beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module(function($provide) { + apiFail = false; + qAllFail = false; + + $provide.value('$q', { all: qAll }); $provide.value('$uibModal', {}); $provide.value('horizon.dashboard.project.lbaasv2.members.actions.rowActions', { init: function() { @@ -68,25 +88,15 @@ spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI); spyOn(actions, 'init').and.callThrough(); spyOn(membersService, 'associateMemberStatuses'); - controller = $injector.get('$controller'); - ctrl = controller('MemberDetailController', { - $routeParams: { - loadbalancerId: 'loadbalancerId', - listenerId: 'listenerId', - poolId: 'poolId', - memberId: 'memberId' - } - }); + $controller = $injector.get('$controller'); })); it('should invoke lbaasv2 apis', function() { + var ctrl = createController(); expect(lbaasv2API.getMember).toHaveBeenCalledWith('poolId','memberId'); expect(lbaasv2API.getPool).toHaveBeenCalledWith('poolId'); expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId'); expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('loadbalancerId'); - }); - - it('should initialize the controller properties correctly', function() { expect(ctrl.loadbalancerId).toBeDefined(); expect(ctrl.listenerId).toBeDefined(); expect(ctrl.poolId).toBeDefined(); @@ -97,10 +107,26 @@ }); it('should invoke the "associateMemberStatuses" method', function() { + var ctrl = createController(); expect(membersService.associateMemberStatuses).toHaveBeenCalledWith( ctrl.loadbalancerId, ctrl.listenerId, ctrl.poolId, [ctrl.member]); }); + it('should throw error on API fail', function() { + apiFail = true; + var init = function() { + createController(); + }; + expect(init).toThrow(); + }); + + it('should set error state if any APIs fail', function() { + qAllFail = true; + var ctrl = createController(); + expect(ctrl.loading).toBe(false); + expect(ctrl.error).toBe(true); + }); + }); })(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.html b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.html index b32f7f1..ebb4079 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.html +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/members/detail.html @@ -1,35 +1,38 @@
- -
-
-
-
Address
-
{$ ::ctrl.member.address $}
-
Protocol Port
-
{$ ::ctrl.member.protocol_port $}
-
Weight
-
{$ ctrl.member.weight $}
-
Operating Status
-
{$ ctrl.member.operating_status | decode:ctrl.operatingStatus $}
-
Provisioning Status
-
{$ ctrl.member.provisioning_status | decode:ctrl.provisioningStatus $}
-
Admin State Up
-
{$ ctrl.member.admin_state_up | yesno $}
-
Member ID
-
{$ ::ctrl.member.id $}
-
Tenant ID
-
{$ ::ctrl.member.tenant_id $}
-
+ +
+ +
+
+
+
Address
+
{$ ::ctrl.member.address $}
+
Protocol Port
+
{$ ::ctrl.member.protocol_port $}
+
Weight
+
{$ ctrl.member.weight $}
+
Operating Status
+
{$ ctrl.member.operating_status | decode:ctrl.operatingStatus $}
+
Provisioning Status
+
{$ ctrl.member.provisioning_status | decode:ctrl.provisioningStatus $}
+
Admin State Up
+
{$ ctrl.member.admin_state_up | yesno $}
+
Member ID
+
{$ ::ctrl.member.id $}
+
Tenant ID
+
{$ ::ctrl.member.tenant_id $}
+
+
-
\ No newline at end of file +
diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.controller.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.controller.js index aabea9f..4f0e188 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.controller.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.controller.js @@ -26,7 +26,8 @@ '$routeParams', 'horizon.framework.util.i18n.gettext', '$window', - '$scope' + '$scope', + '$q' ]; /** @@ -42,12 +43,15 @@ * @param gettext The horizon gettext function for translation. * @param $window Angular's reference to the browser window object. * @param $scope The angular scope object. + * @param $q The angular service for promises. * @returns undefined */ - function PoolDetailController(api, rowActions, $routeParams, gettext, $window, $scope) { + function PoolDetailController(api, rowActions, $routeParams, gettext, $window, $scope, $q) { var ctrl = this; + ctrl.loading = true; + ctrl.error = false; ctrl.loadBalancerAlgorithm = { ROUND_ROBIN: gettext('Round Robin'), LEAST_CONNECTIONS: gettext('Least Connections'), @@ -61,17 +65,43 @@ //////////////////////////////// function init() { - api.getPool($routeParams.poolId).success(set('pool')); - api.getListener($routeParams.listenerId).success(set('listener')); - api.getLoadBalancer($routeParams.loadbalancerId).success(set('loadbalancer')); + ctrl.pool = null; + ctrl.listener = null; + ctrl.loadbalancer = null; + ctrl.loading = true; + ctrl.error = false; + $q.all([ + api.getPool($routeParams.poolId) + .then(success('pool'), fail('pool')), + api.getListener($routeParams.listenerId) + .then(success('listener'), fail('listener')), + api.getLoadBalancer($routeParams.loadbalancerId) + .then(success('loadbalancer'), fail('loadbalancer')) + ]).then(postInit, initError); } - function set(property) { - return angular.bind(null, function setProp(property, value) { - ctrl[property] = value; + function success(property) { + return angular.bind(null, function setProp(property, response) { + ctrl[property] = response.data; }, property); } + function fail(property) { + return angular.bind(null, function setProp(property, error) { + ctrl[property] = null; + throw error; + }, property); + } + + function postInit() { + ctrl.loading = false; + } + + function initError() { + ctrl.loading = false; + ctrl.error = true; + } + // Save the active state of the members tab in the global window object so it can stay // active after reloading the route following an action. $scope.$watch(function() { diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.controller.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.controller.spec.js index 8716523..894383d 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.controller.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.controller.spec.js @@ -17,26 +17,43 @@ 'use strict'; describe('LBaaS v2 Pool Detail Controller', function() { - var lbaasv2API, ctrl, $scope, $window; + var lbaasv2API, $scope, $window, $controller, apiFail, qAllFail; - function fakeAPI() { + function fakePromise(data, reject) { return { - success: function(callback) { - callback('foo'); + then: function(success, fail) { + if (reject) { + fail(); + } else { + success({ data: data }); + } + return fakePromise(); } }; } + function fakeAPI() { + return fakePromise('foo', apiFail); + } + function loadbalancerAPI() { - var loadbalancer = { provisioning_status: 'ACTIVE' }; - return { - success: function(callback) { - callback(loadbalancer); - }, - then: function(callback) { - callback({ data: loadbalancer }); + return fakePromise({ provisioning_status: 'ACTIVE' }); + } + + function qAll() { + return fakePromise(null, qAllFail); + } + + function createController() { + return $controller('PoolDetailController', { + $scope: $scope, + $window: $window, + $routeParams: { + loadbalancerId: 'loadbalancerId', + listenerId: 'listenerId', + poolId: 'poolId' } - }; + }); } /////////////////////// @@ -47,6 +64,13 @@ beforeEach(module('horizon.app.core.openstack-service-api')); beforeEach(module('horizon.dashboard.project.lbaasv2')); + beforeEach(module(function($provide) { + apiFail = false; + qAllFail = false; + + $provide.value('$q', { all: qAll }); + })); + beforeEach(inject(function($injector) { lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2'); spyOn(lbaasv2API, 'getPool').and.callFake(fakeAPI); @@ -54,19 +78,11 @@ spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI); $scope = $injector.get('$rootScope').$new(); $window = {}; - var controller = $injector.get('$controller'); - ctrl = controller('PoolDetailController', { - $scope: $scope, - $window: $window, - $routeParams: { - loadbalancerId: 'loadbalancerId', - listenerId: 'listenerId', - poolId: 'poolId' - } - }); + $controller = $injector.get('$controller'); })); it('should invoke lbaasv2 apis', function() { + var ctrl = createController(); expect(lbaasv2API.getPool).toHaveBeenCalledWith('poolId'); expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId'); expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('loadbalancerId'); @@ -76,10 +92,12 @@ }); it('should define mapping for the load balancer algorithm', function() { + var ctrl = createController(); expect(ctrl.loadBalancerAlgorithm).toBeDefined(); }); it('should save changes to members tab active state', function() { + var ctrl = createController(); expect($window.membersTabActive).toBeUndefined(); expect(ctrl.membersTabActive).toBeUndefined(); ctrl.membersTabActive = true; @@ -90,6 +108,21 @@ expect($window.membersTabActive).toBe(false); }); + it('should throw error on API fail', function() { + apiFail = true; + var init = function() { + createController(); + }; + expect(init).toThrow(); + }); + + it('should set error state if any APIs fail', function() { + qAllFail = true; + var ctrl = createController(); + expect(ctrl.loading).toBe(false); + expect(ctrl.error).toBe(true); + }); + }); })(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.html b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.html index faaa253..510deaa 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.html +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/pools/detail.html @@ -1,47 +1,50 @@
- - - -
-
-
-
Protocol
-
{$ ::ctrl.pool.protocol $}
-
Load Balancer Algorithm
-
{$ ctrl.pool.lb_algorithm | decode:ctrl.loadBalancerAlgorithm $}
-
Session Persistence
-
{$ ctrl.pool.session_persistence | noValue:('None' | translate) $}
-
Admin State Up
-
{$ ctrl.pool.admin_state_up | yesno $}
-
Health Monitor ID
-
- - {$ ::ctrl.pool.healthmonitor_id $} - - - {$ 'None' | translate $} - -
-
Pool ID
-
{$ ::ctrl.pool.id $}
-
Tenant ID
-
{$ ::ctrl.pool.tenant_id $}
-
+ +
+ + + +
+
+
+
Protocol
+
{$ ::ctrl.pool.protocol $}
+
Load Balancer Algorithm
+
{$ ctrl.pool.lb_algorithm | decode:ctrl.loadBalancerAlgorithm $}
+
Session Persistence
+
{$ ctrl.pool.session_persistence | noValue:('None' | translate) $}
+
Admin State Up
+
{$ ctrl.pool.admin_state_up | yesno $}
+
Health Monitor ID
+
+ + {$ ::ctrl.pool.healthmonitor_id $} + + + {$ 'None' | translate $} + +
+
Pool ID
+
{$ ::ctrl.pool.id $}
+
Tenant ID
+
{$ ::ctrl.pool.tenant_id $}
+
+
-
- - - - - + + + + + +
\ No newline at end of file diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/widgets/detail/detail-status.directive.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/widgets/detail/detail-status.directive.js new file mode 100644 index 0000000..24cb0f9 --- /dev/null +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/widgets/detail/detail-status.directive.js @@ -0,0 +1,53 @@ +/* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + 'use strict'; + + angular + .module('horizon.dashboard.project.lbaasv2') + .directive('detailStatus', detailStatus); + + detailStatus.$inject = [ + 'horizon.dashboard.project.lbaasv2.basePath' + ]; + + /** + * @ngdoc directive + * @name horizon.dashboard.project.lbaasv2:detailStatus + * @description + * The `detailStatus` directive provides a status indicator while loading detail pages. It will + * show a loading indicator while the page is loading and an error indicator if there is an + * error loading the page. + * @restrict E + * + * @example + * ``` + * + * ``` + */ + + function detailStatus(basePath) { + var directive = { + restrict: 'E', + templateUrl: basePath + 'widgets/detail/detail-status.html', + scope: { + loading: '=', + error: '=' + } + }; + return directive; + } +}()); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/widgets/detail/detail-status.directive.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/widgets/detail/detail-status.directive.spec.js new file mode 100644 index 0000000..c26c5fd --- /dev/null +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/widgets/detail/detail-status.directive.spec.js @@ -0,0 +1,87 @@ +/* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + 'use strict'; + + function digestMarkup(scope, compile, markup) { + var element = angular.element(markup); + compile(element)(scope); + scope.$apply(); + return element; + } + + describe('detailStatus directive', function() { + var $scope, $compile, markup, ctrl; + + beforeEach(module('templates')); + beforeEach(module('horizon.dashboard.project.lbaasv2')); + + beforeEach(inject(function($injector) { + $compile = $injector.get('$compile'); + $scope = $injector.get('$rootScope').$new(); + ctrl = { + loading: true, + error: false + }; + $scope.ctrl = ctrl; + markup = ''; + })); + + it('initially shows loading status', function() { + var element = digestMarkup($scope, $compile, markup); + expect(element).toBeDefined(); + + expect(element.children().length).toBe(1); + expect(element.find('.progress-bar').hasClass('progress-bar-striped')).toBe(true); + expect(element.find('.progress-bar').hasClass('progress-bar-danger')).toBe(false); + expect(element.find('.progress-bar > span').length).toBe(1); + expect(element.find('.progress-bar > span').hasClass('sr-only')).toBe(true); + expect(element.find('.progress-bar > span').text().trim()).toBe('Loading'); + expect(element.find('.message').length).toBe(0); + }); + + it('indicates error status on error', function() { + var element = digestMarkup($scope, $compile, markup); + expect(element).toBeDefined(); + + ctrl.loading = false; + ctrl.error = true; + $scope.$apply(); + + expect(element.children().length).toBe(1); + expect(element.find('.progress-bar').hasClass('progress-bar-striped')).toBe(false); + expect(element.find('.progress-bar').hasClass('progress-bar-danger')).toBe(true); + expect(element.find('.progress-bar > span').length).toBe(1); + expect(element.find('.progress-bar > span').hasClass('sr-only')).toBe(false); + expect(element.find('.progress-bar > span').text().trim()) + .toBe('An error occurred. Please try again later.'); + expect(element.find('.error-actions').length).toBe(1); + expect(element.find('.error-actions > a').text().trim()).toBe('Back'); + }); + + it('goes away when done loading', function() { + var element = digestMarkup($scope, $compile, markup); + expect(element).toBeDefined(); + + ctrl.loading = false; + ctrl.error = false; + $scope.$apply(); + + expect(element.children().length).toBe(0); + }); + + }); +}()); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/widgets/detail/detail-status.html b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/widgets/detail/detail-status.html new file mode 100644 index 0000000..3278ed1 --- /dev/null +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/widgets/detail/detail-status.html @@ -0,0 +1,12 @@ +
+
+
+ Loading + An error occurred. Please try again later. +
+
+
+ Back +
+
\ No newline at end of file