diff --git a/neutron_lbaas_dashboard/api/rest/lbaasv2.py b/neutron_lbaas_dashboard/api/rest/lbaasv2.py index 45b29b25..449d02e0 100644 --- a/neutron_lbaas_dashboard/api/rest/lbaasv2.py +++ b/neutron_lbaas_dashboard/api/rest/lbaasv2.py @@ -551,6 +551,14 @@ class Listener(generic.View): kwargs = {'listener_id': listener_id} update_listener(request, **kwargs) + @rest_utils.ajax() + def delete(self, request, listener_id): + """Delete a specific listener. + + http://localhost/api/lbaas/listeners/cc758c90-3d98-4ea1-af44-aab405c9c915 + """ + neutronclient(request).delete_listener(listener_id) + @urls.register class Pool(generic.View): diff --git a/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.js b/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.js index 524c408b..0c5a7f13 100644 --- a/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.js +++ b/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.js @@ -45,6 +45,7 @@ getListener: getListener, createListener: createListener, editListener: editListener, + deleteListener: deleteListener, getPool: getPool, getMembers: getMembers, getMember: getMember, @@ -214,6 +215,22 @@ }); } + /** + * @name horizon.app.core.openstack-service-api.lbaasv2.deleteListener + * @description + * Delete a single listener by ID + * @param {string} id + * @param {boolean} quiet + * Specifies the id of the listener to delete. + */ + + function deleteListener(id, quiet) { + var promise = apiService.delete('/api/lbaas/listeners/' + id); + return quiet ? promise : promise.error(function () { + toastService.add('error', gettext('Unable to delete listener.')); + }); + } + // Pools /** diff --git a/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.spec.js b/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.spec.js index 2a5329d1..ecd22814 100644 --- a/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.spec.js +++ b/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.spec.js @@ -149,6 +149,13 @@ error: 'Unable to update listener.', data: { name: 'listener-1' }, testInput: [ '1234', { name: 'listener-1' } ] + }, + { + func: 'deleteListener', + method: 'delete', + path: '/api/lbaas/listeners/1234', + error: 'Unable to delete listener.', + testInput: [ '1234' ] } ]; @@ -165,6 +172,11 @@ expect(service.deleteLoadBalancer("whatever", true)).toBe("promise"); }); + it('supresses the error if instructed for deleteListener', function() { + spyOn(apiService, 'delete').and.returnValue("promise"); + expect(service.deleteListener("whatever", true)).toBe("promise"); + }); + }); })(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/batch-actions.service.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/batch-actions.service.js index ce0e73cb..ba55aab6 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/batch-actions.service.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/batch-actions.service.js @@ -27,7 +27,8 @@ 'horizon.dashboard.project.lbaasv2.workflow.modal', 'horizon.app.core.openstack-service-api.policy', 'horizon.framework.util.i18n.gettext', - 'horizon.dashboard.project.lbaasv2.loadbalancers.service' + 'horizon.dashboard.project.lbaasv2.loadbalancers.service', + 'horizon.dashboard.project.lbaasv2.listeners.actions.delete' ]; /** @@ -43,11 +44,14 @@ * @param policy The horizon policy service. * @param gettext The horizon gettext function for translation. * @param loadBalancersService The LBaaS v2 load balancers service. + * @param deleteService The LBaaS v2 listeners delete service. * @returns Listeners table batch actions service object. */ - function tableBatchActions($q, $location, workflowModal, policy, gettext, loadBalancersService) { - var loadBalancerIsActive, loadBalancerId; + function tableBatchActions( + $q, $location, workflowModal, policy, gettext, loadBalancersService, deleteService + ) { + var loadBalancerIsActionable, loadBalancerId, handler; var create = workflowModal.init({ controller: 'CreateListenerWizardController', @@ -65,9 +69,10 @@ /////////////// - function init(loadbalancerId) { - loadBalancerId = loadbalancerId; - loadBalancerIsActive = loadBalancersService.isActive(loadbalancerId); + function init(_loadBalancerId_, _handler_) { + loadBalancerId = _loadBalancerId_; + handler = _handler_; + loadBalancerIsActionable = loadBalancersService.isActionable(loadBalancerId); return service; } @@ -78,12 +83,18 @@ type: 'create', text: gettext('Create Listener') } + },{ + service: deleteService.init(loadBalancerId, loadBalancerIsActionable, handler), + template: { + text: gettext('Delete Listeners'), + type: 'delete-selected' + } }]; } function canCreate() { return $q.all([ - loadBalancerIsActive, + loadBalancerIsActionable, policy.ifAllowed({ rules: [['neutron', 'create_listener']] }) ]); } diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/batch-actions.service.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/batch-actions.service.spec.js index 6b59085a..63984d18 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/batch-actions.service.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/batch-actions.service.spec.js @@ -21,7 +21,7 @@ beforeEach(module('horizon.framework.util')); beforeEach(module('horizon.framework.conf')); - beforeEach(module('horizon.framework.widgets.toast')); + beforeEach(module('horizon.framework.widgets')); beforeEach(module('horizon.app.core.openstack-service-api')); beforeEach(module('horizon.dashboard.project.lbaasv2')); @@ -44,7 +44,7 @@ }; $provide.value('$modal', modal); $provide.value('horizon.dashboard.project.lbaasv2.loadbalancers.service', { - isActive: function() { + isActionable: function() { return $q.when(); } }); @@ -67,8 +67,9 @@ })); it('should define correct table batch actions', function() { - expect(actions.length).toBe(1); + expect(actions.length).toBe(2); expect(actions[0].template.text).toBe('Create Listener'); + expect(actions[1].template.text).toBe('Delete Listeners'); }); it('should have the "allowed" and "perform" functions', function() { diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/delete/delete.action.service.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/delete/delete.action.service.js new file mode 100644 index 00000000..c7096e62 --- /dev/null +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/delete/delete.action.service.js @@ -0,0 +1,129 @@ +/* + * 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.listeners') + .factory('horizon.dashboard.project.lbaasv2.listeners.actions.delete', deleteService); + + deleteService.$inject = [ + '$q', + '$location', + '$route', + 'horizon.framework.widgets.modal.deleteModalService', + 'horizon.app.core.openstack-service-api.lbaasv2', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.util.q.extensions', + 'horizon.framework.util.i18n.gettext' + ]; + + /** + * @ngDoc factory + * @name horizon.dashboard.project.lbaasv2.listeners.actions.deleteService + * @description + * Brings up the delete listeners confirmation modal dialog. + * On submit, deletes selected listeners. + * On cancel, does nothing. + * @param $q The angular service for promises. + * @param $location The angular $location service. + * @param $route The angular $route service. + * @param deleteModal The horizon delete modal service. + * @param api The LBaaS v2 API service. + * @param policy The horizon policy service. + * @param toast The horizon message service. + * @param qExtensions Horizon extensions to the $q service. + * @param gettext The horizon gettext function for translation. + * @returns The load balancers table delete service. + */ + + function deleteService( + $q, $location, $route, deleteModal, api, policy, toast, qExtensions, gettext + ) { + var loadbalancerId, statePromise, handler; + var context = { + labels: { + title: gettext('Confirm Delete Listeners'), + message: gettext('You have selected "%s". Please confirm your selection. Deleted ' + + 'listeners are not recoverable.'), + submit: gettext('Delete Listeners'), + success: gettext('Deleted listeners: %s.'), + error: gettext('The following listeners could not be deleted, possibly due to ' + + 'existing pools: %s.') + }, + deleteEntity: deleteItem, + successEvent: 'success', + failedEvent: 'error' + }; + + var service = { + perform: perform, + allowed: allowed, + init: init + }; + + return service; + + ////////////// + + function init(_loadbalancerId_, _statePromise_, _handler_) { + loadbalancerId = _loadbalancerId_; + statePromise = _statePromise_; + handler = _handler_; + return service; + } + + function perform(items) { + if (!angular.isArray(items)) { + items = [items]; + } + deleteModal.open({ $emit: actionComplete }, items, context); + } + + function allowed(/*item*/) { + return $q.all([ + statePromise, + // This rule is made up and should therefore always pass. I assume at some point there + // will be a valid rule similar to this that we will want to use. + policy.ifAllowed({ rules: [['neutron', 'delete_listener']] }) + ]); + } + + function deleteItem(id) { + return api.deleteListener(id, true); + } + + function actionComplete(eventType) { + if (angular.isFunction(handler)) { + handler(); + } else if (eventType === context.failedEvent) { + // Action failed, reload the page + $route.reload(); + } else { + // If the user is on the listeners table then just reload the page, otherwise they + // are on the details page and we return to the table. + var regex = new RegExp('project\/ngloadbalancersv2\/' + loadbalancerId + '(\/)?$'); + if (regex.test($location.path())) { + $route.reload(); + } else { + $location.path('project/ngloadbalancersv2/' + loadbalancerId); + } + } + } + + } +})(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/delete/delete.action.service.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/delete/delete.action.service.spec.js new file mode 100644 index 00000000..b6b39c1e --- /dev/null +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/delete/delete.action.service.spec.js @@ -0,0 +1,165 @@ +/* + * 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'; + + describe('LBaaS v2 Listeners Delete Service', function() { + var service, policy, modal, lbaasv2Api, $scope, $route, $location, $q, toast, items, path; + + function allowed(item) { + spyOn(policy, 'ifAllowed').and.returnValue(makePromise()); + var promise = service.allowed(item); + var allowed; + promise.then(function() { + allowed = true; + }, function() { + allowed = false; + }); + $scope.$apply(); + expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'delete_listener']]}); + return allowed; + } + + function makePromise(reject) { + var def = $q.defer(); + def[reject ? 'reject' : 'resolve'](); + return def.promise; + } + + beforeEach(module('horizon.framework.util')); + beforeEach(module('horizon.framework.conf')); + beforeEach(module('horizon.framework.widgets')); + beforeEach(module('horizon.app.core.openstack-service-api')); + beforeEach(module('horizon.dashboard.project.lbaasv2')); + + beforeEach(function() { + items = [{ id: '1', name: 'First' }, + { id: '2', name: 'Second' }]; + }); + + beforeEach(module(function($provide) { + $provide.value('$modal', { + open: function() { + return { + result: makePromise() + }; + } + }); + $provide.value('horizon.app.core.openstack-service-api.lbaasv2', { + deleteListener: function() { + return makePromise(); + } + }); + $provide.value('$location', { + path: function() { + return path; + } + }); + })); + + beforeEach(inject(function ($injector) { + policy = $injector.get('horizon.app.core.openstack-service-api.policy'); + lbaasv2Api = $injector.get('horizon.app.core.openstack-service-api.lbaasv2'); + modal = $injector.get('horizon.framework.widgets.modal.deleteModalService'); + $scope = $injector.get('$rootScope').$new(); + $route = $injector.get('$route'); + $location = $injector.get('$location'); + $q = $injector.get('$q'); + toast = $injector.get('horizon.framework.widgets.toast.service'); + service = $injector.get('horizon.dashboard.project.lbaasv2.listeners.actions.delete'); + service.init('1'); + })); + + it('should have the "allowed" and "perform" functions', function() { + expect(service.allowed).toBeDefined(); + expect(service.perform).toBeDefined(); + }); + + it('should check policy to allow deleting a listener (single)', function() { + expect(allowed(items[0])).toBe(true); + }); + + it('should check policy to allow deleting a listener (batch)', function() { + expect(allowed()).toBe(true); + }); + + it('should open the delete modal', function() { + spyOn(modal, 'open'); + service.perform(items[0]); + $scope.$apply(); + expect(modal.open.calls.count()).toBe(1); + var args = modal.open.calls.argsFor(0); + expect(args.length).toBe(3); + expect(args[0]).toEqual({ $emit: jasmine.any(Function) }); + expect(args[1]).toEqual([jasmine.objectContaining({ id: '1' })]); + expect(args[2]).toEqual(jasmine.objectContaining({ + labels: jasmine.any(Object), + deleteEntity: jasmine.any(Function) + })); + expect(args[2].labels.title).toBe('Confirm Delete Listeners'); + }); + + it('should pass function to modal that deletes listeners', function() { + spyOn(modal, 'open').and.callThrough(); + spyOn(lbaasv2Api, 'deleteListener').and.callThrough(); + service.perform(items[0]); + $scope.$apply(); + expect(lbaasv2Api.deleteListener.calls.count()).toBe(1); + expect(lbaasv2Api.deleteListener).toHaveBeenCalledWith('1', true); + }); + + it('should show message if any items fail to be deleted', function() { + spyOn(modal, 'open').and.callThrough(); + spyOn(lbaasv2Api, 'deleteListener').and.returnValue(makePromise(true)); + spyOn(toast, 'add'); + items.splice(1, 1); + service.perform(items); + $scope.$apply(); + expect(modal.open).toHaveBeenCalled(); + expect(lbaasv2Api.deleteListener.calls.count()).toBe(1); + expect(toast.add).toHaveBeenCalledWith('error', 'The following listeners could not ' + + 'be deleted, possibly due to existing pools: First.'); + }); + + it('should reload table after delete', function() { + path = 'project/ngloadbalancersv2/1'; + spyOn($route, 'reload'); + service.perform(items); + $scope.$apply(); + expect($route.reload).toHaveBeenCalled(); + }); + + it('should return to table after delete if on detail page', function() { + path = 'project/ngloadbalancersv2/1/listeners/2'; + spyOn($location, 'path'); + spyOn(toast, 'add'); + service.perform(items[0]); + $scope.$apply(); + expect($location.path).toHaveBeenCalledWith('project/ngloadbalancersv2/1'); + expect(toast.add).toHaveBeenCalledWith('success', 'Deleted listeners: First.'); + }); + + it('should call handler function if provided', function() { + var handler = { handler: angular.noop }; + spyOn(handler, 'handler'); + service.init('1', null, handler.handler); + service.perform(items); + $scope.$apply(); + expect(handler.handler).toHaveBeenCalled(); + }); + + }); +})(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/row-actions.service.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/row-actions.service.js index 5702a9eb..bb6cc282 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/row-actions.service.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/row-actions.service.js @@ -27,7 +27,8 @@ 'horizon.dashboard.project.lbaasv2.workflow.modal', 'horizon.app.core.openstack-service-api.policy', 'horizon.framework.util.i18n.gettext', - 'horizon.dashboard.project.lbaasv2.loadbalancers.service' + 'horizon.dashboard.project.lbaasv2.loadbalancers.service', + 'horizon.dashboard.project.lbaasv2.listeners.actions.delete' ]; /** @@ -43,10 +44,14 @@ * @param policy The horizon policy service. * @param gettext The horizon gettext function for translation. * @param loadBalancersService The LBaaS v2 load balancers service. + * @param deleteService The LBaaS v2 listeners delete service. * @returns Listeners row actions service object. */ - function tableRowActions($q, $route, workflowModal, policy, gettext, loadBalancersService) { + function tableRowActions( + $q, $route, workflowModal, policy, gettext, loadBalancersService, deleteService + ) { + var loadbalancerId, loadBalancerIsActionable, handler; var edit = workflowModal.init({ controller: 'EditListenerWizardController', @@ -60,14 +65,14 @@ init: init }; - var loadBalancerIsActive; - return service; /////////////// - function init(loadbalancerId) { - loadBalancerIsActive = loadBalancersService.isActive(loadbalancerId); + function init(_loadbalancerId_, _handler_) { + loadbalancerId = _loadbalancerId_; + handler = _handler_; + loadBalancerIsActionable = loadBalancersService.isActionable(loadbalancerId); return service; } @@ -77,12 +82,18 @@ template: { text: gettext('Edit') } + },{ + service: deleteService.init(loadbalancerId, loadBalancerIsActionable, handler), + template: { + text: gettext('Delete Listener'), + type: 'delete' + } }]; } function canEdit(/*item*/) { return $q.all([ - loadBalancerIsActive, + loadBalancerIsActionable, policy.ifAllowed({ rules: [['neutron', 'update_listener']] }) ]); } diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/row-actions.service.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/row-actions.service.spec.js index e10b1a12..4c7f40a8 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/row-actions.service.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/actions/row-actions.service.spec.js @@ -33,7 +33,7 @@ return allowed; } - function isActiveMock(id) { + function isActionableMock(id) { if (id === 'active') { return $q.when(); } else { @@ -43,7 +43,7 @@ beforeEach(module('horizon.framework.util')); beforeEach(module('horizon.framework.conf')); - beforeEach(module('horizon.framework.widgets.toast')); + beforeEach(module('horizon.framework.widgets')); beforeEach(module('horizon.app.core.openstack-service-api')); beforeEach(module('horizon.dashboard.project.lbaasv2')); @@ -78,12 +78,13 @@ init = rowActionsService.init; var loadbalancerService = $injector.get( 'horizon.dashboard.project.lbaasv2.loadbalancers.service'); - spyOn(loadbalancerService, 'isActive').and.callFake(isActiveMock); + spyOn(loadbalancerService, 'isActionable').and.callFake(isActionableMock); })); it('should define correct table row actions', function() { - expect(actions.length).toBe(1); + expect(actions.length).toBe(2); expect(actions[0].template.text).toBe('Edit'); + expect(actions[1].template.text).toBe('Delete Listener'); }); it('should allow editing a listener of an ACTIVE load balancer', function() { 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 13b4f2cd..680d09d6 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 @@ -42,7 +42,7 @@ function ListenerDetailController(api, rowActions, $routeParams) { var ctrl = this; - ctrl.actions = rowActions.actions; + ctrl.actions = rowActions.init($routeParams.loadbalancerId).actions; init(); 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 d0070c4f..3f12f4a9 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 @@ -27,10 +27,22 @@ }; } + function loadbalancerAPI() { + var loadbalancer = { provisioning_status: 'ACTIVE' }; + return { + success: function(callback) { + callback(loadbalancer); + }, + then: function(callback) { + callback({ data: loadbalancer }); + } + }; + } + /////////////////////// beforeEach(module('horizon.framework.util')); - beforeEach(module('horizon.framework.widgets.toast')); + beforeEach(module('horizon.framework.widgets')); beforeEach(module('horizon.framework.conf')); beforeEach(module('horizon.app.core.openstack-service-api')); beforeEach(module('horizon.dashboard.project.lbaasv2')); @@ -42,7 +54,7 @@ beforeEach(inject(function($injector) { lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2'); spyOn(lbaasv2API, 'getListener').and.callFake(fakeAPI); - spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(fakeAPI); + spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI); var controller = $injector.get('$controller'); ctrl = controller('ListenerDetailController', { $routeParams: { @@ -55,7 +67,7 @@ it('should invoke lbaasv2 apis', function() { expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId'); expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('loadbalancerId'); - expect(ctrl.loadbalancer).toBe('foo'); + expect(ctrl.loadbalancer).toEqual({ provisioning_status: 'ACTIVE' }); expect(ctrl.listener).toBe('foo'); }); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/table.controller.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/table.controller.js index 4abba18e..4f88761e 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/table.controller.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/table.controller.js @@ -48,8 +48,8 @@ ctrl.src = []; ctrl.checked = {}; ctrl.loadbalancerId = $routeParams.loadbalancerId; - ctrl.batchActions = batchActions.init(ctrl.loadbalancerId); - ctrl.rowActions = rowActions.init(ctrl.loadbalancerId); + ctrl.batchActions = batchActions.init(ctrl.loadbalancerId, init); + ctrl.rowActions = rowActions.init(ctrl.loadbalancerId, init); init(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/table.controller.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/table.controller.spec.js index 3f0b2ec3..d35d6a5b 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/table.controller.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/listeners/table.controller.spec.js @@ -66,7 +66,7 @@ expect(ctrl.src).toEqual(items); expect(ctrl.checked).toEqual({}); expect(ctrl.loadbalancerId).toEqual('1234'); - expect(rowActions.init).toHaveBeenCalledWith(ctrl.loadbalancerId); + expect(rowActions.init).toHaveBeenCalledWith(ctrl.loadbalancerId, jasmine.any(Function)); expect(ctrl.rowActions).toBeDefined(); expect(ctrl.rowActions).toEqual(rowActions); expect(ctrl.batchActions).toBeDefined(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/batch-actions.service.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/batch-actions.service.js index fbe01786..49e77003 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/batch-actions.service.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/batch-actions.service.js @@ -47,6 +47,7 @@ */ function tableBatchActions($location, workflowModal, basePath, deleteService, policy, gettext) { + var handler; var create = workflowModal.init({ controller: 'CreateLoadBalancerWizardController', @@ -56,13 +57,19 @@ }); var service = { - actions: actions + actions: actions, + init: init }; return service; /////////////// + function init(_handler_) { + handler = _handler_; + return service; + } + function actions() { return [{ service: create, @@ -71,7 +78,7 @@ text: gettext('Create Load Balancer') } }, { - service: deleteService, + service: deleteService.init(handler), template: { type: 'delete-selected', text: gettext('Delete Load Balancers') diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/delete/delete.action.service.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/delete/delete.action.service.js index c64f2948..16406fba 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/delete/delete.action.service.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/delete/delete.action.service.js @@ -54,6 +54,8 @@ function deleteService( $q, $location, $route, deleteModal, api, policy, toast, qExtensions, gettext ) { + var handler; + // If a batch delete, then this message is displayed for any selected load balancers not in // ACTIVE or ERROR state. var notAllowedMessage = gettext('The following load balancers are pending and cannot be ' + @@ -68,18 +70,26 @@ error: gettext('The following load balancers could not be deleted, possibly due to ' + 'existing listeners: %s.') }, - deleteEntity: deleteItem + deleteEntity: deleteItem, + successEvent: 'success', + failedEvent: 'error' }; var service = { perform: perform, - allowed: allowed + allowed: allowed, + init: init }; return service; ////////////// + function init(_handler_) { + handler = _handler_; + return service; + } + function perform(items) { if (angular.isArray(items)) { qExtensions.allSettled(items.map(checkPermission)).then(afterCheck); @@ -133,13 +143,20 @@ return result.context; } - function actionComplete() { - // If the user is on the load balancers table then just reload the page, otherwise they - // are on the details page and we return to the table. - if (/\/ngloadbalancersv2(\/)?$/.test($location.path())) { + function actionComplete(eventType) { + if (angular.isFunction(handler)) { + handler(); + } else if (eventType === context.failedEvent) { + // Action failed, reload the page $route.reload(); } else { - $location.path('project/ngloadbalancersv2'); + // If the user is on the load balancers table then just reload the page, otherwise they + // are on the details page and we return to the table. + if (/\/ngloadbalancersv2(\/)?$/.test($location.path())) { + $route.reload(); + } else { + $location.path('project/ngloadbalancersv2'); + } } } diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/delete/delete.action.service.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/delete/delete.action.service.spec.js index 6a47d17f..ced86d6b 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/delete/delete.action.service.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/delete/delete.action.service.spec.js @@ -173,5 +173,14 @@ expect(toast.add).toHaveBeenCalledWith('success', 'Deleted load balancers: First.'); }); + it('should call handler function if provided', function() { + var handler = { handler: angular.noop }; + spyOn(handler, 'handler'); + service.init(handler.handler); + service.perform(items); + $scope.$apply(); + expect(handler.handler).toHaveBeenCalled(); + }); + }); })(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/row-actions.service.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/row-actions.service.js index e8b6a8e2..54b55103 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/row-actions.service.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/row-actions.service.js @@ -66,6 +66,8 @@ qExtensions, gettext ) { + var handler; + var edit = workflowModal.init({ controller: 'EditLoadBalancerWizardController', message: gettext('The load balancer has been updated.'), @@ -74,13 +76,19 @@ }); var service = { - actions: actions + actions: actions, + init: init }; return service; /////////////// + function init(_handler_) { + handler = _handler_; + return service; + } + function actions() { return [{ service: edit, @@ -98,7 +106,7 @@ text: gettext('Disassociate Floating IP') } },{ - service: deleteService, + service: deleteService.init(handler), template: { text: gettext('Delete Load Balancer'), type: 'delete' diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/loadbalancers.service.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/loadbalancers.service.js index 90712737..e22463b0 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/loadbalancers.service.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/loadbalancers.service.js @@ -55,7 +55,7 @@ var service = { operatingStatus: operatingStatus, provisioningStatus: provisioningStatus, - isActive: isActive + isActionable: isActionable }; return service; @@ -64,16 +64,16 @@ /** * @ngdoc method - * @name horizon.dashboard.project.lbaasv2.loadbalancers.service.isActive - * @description Returns a promise that is resolved if the load balancer is active and - * rejected if not. + * @name horizon.dashboard.project.lbaasv2.loadbalancers.service.isActionable + * @description Returns a promise that is resolved if the load balancer is in a state that + * allows for it or child resources to be updated or deleted. * @param id The load balancer id. * @returns {Promise} */ - function isActive(id) { + function isActionable(id) { return api.getLoadBalancer(id).then(function onLoad(response) { - if (response.data.provisioning_status !== 'ACTIVE') { + if (['ACTIVE', 'ERROR'].indexOf(response.data.provisioning_status) < 0) { return $q.reject(); } }); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/loadbalancers.service.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/loadbalancers.service.spec.js index f8eea257..3df0b313 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/loadbalancers.service.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/loadbalancers.service.spec.js @@ -52,14 +52,14 @@ it('should allow checking status of load balancer', function() { var active = null; - service.isActive(0).then(function() { + service.isActionable(0).then(function() { active = true; }); $scope.$apply(); expect(active).toBe(true); active = null; - service.isActive(1).then(angular.noop, function() { + service.isActionable(1).then(angular.noop, function() { active = false; }); $scope.$apply(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/table.controller.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/table.controller.js index 170d8078..98f4cdcd 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/table.controller.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/table.controller.js @@ -47,8 +47,8 @@ ctrl.items = []; ctrl.src = []; ctrl.checked = {}; - ctrl.batchActions = batchActions; - ctrl.rowActions = rowActions; + ctrl.batchActions = batchActions.init(init); + ctrl.rowActions = rowActions.init(init); ctrl.operatingStatus = loadBalancersService.operatingStatus; ctrl.provisioningStatus = loadBalancersService.provisioningStatus;