Add delete to member row actions
Change-Id: I6d3d71984dc98bce9d6fcda9c04ccb0999d30a51
This commit is contained in:
parent
536da0119a
commit
dabf7dac27
|
@ -776,6 +776,14 @@ class Member(generic.View):
|
|||
member = conn.load_balancer.find_member(member_id, pool_id)
|
||||
return _get_sdk_object_dict(member)
|
||||
|
||||
@rest_utils.ajax()
|
||||
def delete(self, request, member_id, pool_id):
|
||||
"""Delete a specific member belonging to a specific pool.
|
||||
|
||||
"""
|
||||
conn = _get_sdk_connection(request)
|
||||
conn.load_balancer.delete_member(member_id, pool_id)
|
||||
|
||||
@rest_utils.ajax()
|
||||
def put(self, request, member_id, pool_id):
|
||||
"""Edit a pool member.
|
||||
|
|
|
@ -312,6 +312,9 @@ msgstr "Confirm Delete Load Balancers (konfirmasi hapus beban penyeimbang)"
|
|||
msgid "Confirm Delete Pool"
|
||||
msgstr "Confirm Delete Pool (konfirmasi hapus kolam)"
|
||||
|
||||
msgid "Confirm Delete Member"
|
||||
msgstr "Confirm Delete Member"
|
||||
|
||||
msgid "Confirm Disassociate Floating IP Address"
|
||||
msgstr "Konfirmasi pemisahan alamat IP mengambang"
|
||||
|
||||
|
@ -360,6 +363,9 @@ msgstr "Delete Load Balancers (hapus penyeimbang beban)"
|
|||
msgid "Delete Pool"
|
||||
msgstr "Delete Pool (hapus kolam)"
|
||||
|
||||
msgid "Delete Member"
|
||||
msgstr "Delete Member"
|
||||
|
||||
#, python-format
|
||||
msgid "Deleted health monitor: %s."
|
||||
msgstr "Pemantauan kesehatan yang dihapus: %s."
|
||||
|
@ -376,6 +382,10 @@ msgstr "Penyeimbang beban yang dihapus: %s."
|
|||
msgid "Deleted pool: %s."
|
||||
msgstr "Kolam yang dihapus: %s."
|
||||
|
||||
#, python-format
|
||||
msgid "Deleted member: %s."
|
||||
msgstr "Deleted member: %s."
|
||||
|
||||
msgid "Description"
|
||||
msgstr "Description (gambaran)"
|
||||
|
||||
|
@ -797,6 +807,10 @@ msgstr ""
|
|||
msgid "The following pool could not be deleted: %s."
|
||||
msgstr "Kolam berikut ini tidak bisa dihapus: %s."
|
||||
|
||||
#, python-format
|
||||
msgid "The following member could not be deleted: %s."
|
||||
msgstr "The following member could not be deleted: %s."
|
||||
|
||||
msgid "The health check interval must be greater than or equal to the timeout."
|
||||
msgstr ""
|
||||
"Interval pemeriksaan kesehatan harus lebih besar dari atau sama dengan batas "
|
||||
|
@ -907,6 +921,9 @@ msgstr "Tidak dapat menghapus penyeimbang beban."
|
|||
msgid "Unable to delete pool."
|
||||
msgstr "Tidak dapat menghapus kolam."
|
||||
|
||||
msgid "Unable to delete member."
|
||||
msgstr "Unable to delete member."
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to disassociate floating IP address from load balancer: %s."
|
||||
msgstr ""
|
||||
|
@ -1089,3 +1106,11 @@ msgid ""
|
|||
msgstr ""
|
||||
"Anda telah memilih \"%s\". Harap mengkonfirmasi pilihan Anda. Kolam yang "
|
||||
"telah dihapus tidak dapat dipulihkan."
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You have selected \"%s\". Please confirm your selection. Deleted members are "
|
||||
"not recoverable."
|
||||
msgstr ""
|
||||
"You have selected \"%s\". Please confirm your selection. Deleted members are "
|
||||
"not recoverable."
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
deletePool: deletePool,
|
||||
getMembers: getMembers,
|
||||
getMember: getMember,
|
||||
deleteMember: deleteMember,
|
||||
editMember: editMember,
|
||||
getHealthMonitor: getHealthMonitor,
|
||||
deleteHealthMonitor: deleteHealthMonitor,
|
||||
|
@ -346,6 +347,23 @@
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.deleteMember
|
||||
* @description
|
||||
* Delete a single pool Member by ID.
|
||||
* @param {string} poolId
|
||||
* Specifies the id of the pool the member belongs to.
|
||||
* @param {string} memberId
|
||||
* Specifies the id of the member to request.
|
||||
*/
|
||||
|
||||
function deleteMember(poolId, memberId) {
|
||||
return apiService.delete('/api/lbaas/pools/' + poolId + '/members/' + memberId + '/')
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to delete member.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.lbaasv2.editMember
|
||||
* @description
|
||||
|
|
|
@ -127,6 +127,13 @@
|
|||
error: 'Unable to retrieve member.',
|
||||
testInput: [ '1234', '5678' ]
|
||||
},
|
||||
{
|
||||
func: 'deleteMember',
|
||||
method: 'delete',
|
||||
path: '/api/lbaas/pools/1234/members/5678/',
|
||||
error: 'Unable to delete member.',
|
||||
testInput: [ '1234', '5678' ]
|
||||
},
|
||||
{
|
||||
func: 'editMember',
|
||||
method: 'put',
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2017 Walmart.
|
||||
*
|
||||
* 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.members')
|
||||
.factory('horizon.dashboard.project.lbaasv2.members.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.util.i18n.gettext'
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngDoc factory
|
||||
* @name horizon.dashboard.project.lbaasv2.members.actions.deleteService
|
||||
* @description
|
||||
* Brings up the delete member confirmation modal dialog.
|
||||
* On submit, deletes selected member.
|
||||
* 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 gettext The horizon gettext function for translation.
|
||||
* @returns The load balancers table delete service.
|
||||
*/
|
||||
|
||||
function deleteService($q, $location, $route, deleteModal, api, policy, gettext) {
|
||||
var loadbalancerId, listenerId, poolId, statePromise;
|
||||
var context = {
|
||||
labels: {
|
||||
title: gettext('Confirm Delete Member'),
|
||||
message: gettext('You have selected "%s". Please confirm your selection. Deleted members ' +
|
||||
'are not recoverable.'),
|
||||
submit: gettext('Delete Member'),
|
||||
success: gettext('Deleted member: %s.'),
|
||||
error: gettext('The following member could not be deleted: %s.')
|
||||
},
|
||||
deleteEntity: deleteItem,
|
||||
successEvent: 'success',
|
||||
failedEvent: 'error'
|
||||
};
|
||||
|
||||
var service = {
|
||||
perform: perform,
|
||||
allowed: allowed,
|
||||
init: init
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
//////////////
|
||||
|
||||
function init(_loadbalancerId_, _listenerId_, _poolId_, _statePromise_) {
|
||||
loadbalancerId = _loadbalancerId_;
|
||||
listenerId = _listenerId_;
|
||||
poolId = _poolId_;
|
||||
statePromise = _statePromise_;
|
||||
return service;
|
||||
}
|
||||
|
||||
function perform(item) {
|
||||
deleteModal.open({ $emit: actionComplete }, [item], 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', 'pool_member_delete']] })
|
||||
]);
|
||||
}
|
||||
|
||||
function deleteItem(id) {
|
||||
return api.deleteMember(poolId, id);
|
||||
}
|
||||
|
||||
function actionComplete(eventType) {
|
||||
if (eventType === context.successEvent) {
|
||||
// Success, go back to pool details page
|
||||
var path = 'project/load_balancer/' +
|
||||
loadbalancerId + '/listeners/' + listenerId + '/pools/' + poolId;
|
||||
$location.path(path);
|
||||
}
|
||||
$route.reload();
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright 2017 Walmart.
|
||||
*
|
||||
* 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 Member Delete Service', function() {
|
||||
var service, policy, modal, lbaasv2Api, $scope, $location, $q, toast, member;
|
||||
|
||||
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', 'pool_member_delete']]});
|
||||
return allowed;
|
||||
}
|
||||
|
||||
function makePromise(reject) {
|
||||
var def = $q.defer();
|
||||
def[reject ? 'reject' : 'resolve']();
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
function isActionable(id) {
|
||||
if (id === 'active') {
|
||||
return $q.when();
|
||||
} else {
|
||||
return $q.reject();
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
member = { id: '1', name: 'Member1' };
|
||||
});
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.value('$uibModal', {
|
||||
open: function() {
|
||||
return {
|
||||
result: makePromise()
|
||||
};
|
||||
}
|
||||
});
|
||||
$provide.value('horizon.app.core.openstack-service-api.lbaasv2', {
|
||||
deleteMember: function() {
|
||||
return makePromise();
|
||||
}
|
||||
});
|
||||
$provide.value('$location', {
|
||||
path: function() {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
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();
|
||||
$location = $injector.get('$location');
|
||||
$q = $injector.get('$q');
|
||||
toast = $injector.get('horizon.framework.widgets.toast.service');
|
||||
service = $injector.get('horizon.dashboard.project.lbaasv2.members.actions.delete');
|
||||
service.init('1', '2', '3', isActionable('active'));
|
||||
$scope.$apply();
|
||||
}));
|
||||
|
||||
it('should have the "allowed" and "perform" functions', function() {
|
||||
expect(service.allowed).toBeDefined();
|
||||
expect(service.perform).toBeDefined();
|
||||
});
|
||||
|
||||
it('should allow deleting member from load balancer in ACTIVE state', function() {
|
||||
expect(allowed()).toBe(true);
|
||||
});
|
||||
|
||||
it('should not allow deleting member from load balancer in a PENDING state', function() {
|
||||
service.init('1', '2', '3', isActionable('pending'));
|
||||
expect(allowed()).toBe(false);
|
||||
});
|
||||
|
||||
it('should open the delete modal', function() {
|
||||
spyOn(modal, 'open');
|
||||
service.perform(member);
|
||||
$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([member]);
|
||||
expect(args[2]).toEqual(jasmine.objectContaining({
|
||||
labels: jasmine.any(Object),
|
||||
deleteEntity: jasmine.any(Function)
|
||||
}));
|
||||
expect(args[2].labels.title).toBe('Confirm Delete Member');
|
||||
});
|
||||
|
||||
it('should pass function to modal that deletes the member', function() {
|
||||
spyOn(modal, 'open').and.callThrough();
|
||||
spyOn(lbaasv2Api, 'deleteMember').and.callThrough();
|
||||
service.perform(member);
|
||||
$scope.$apply();
|
||||
expect(lbaasv2Api.deleteMember.calls.count()).toBe(1);
|
||||
expect(lbaasv2Api.deleteMember).toHaveBeenCalledWith('3', '1');
|
||||
});
|
||||
|
||||
it('should show message if any items fail to be deleted', function() {
|
||||
spyOn(modal, 'open').and.callThrough();
|
||||
spyOn(lbaasv2Api, 'deleteMember').and.returnValue(makePromise(true));
|
||||
spyOn(toast, 'add');
|
||||
service.perform(member);
|
||||
$scope.$apply();
|
||||
expect(modal.open).toHaveBeenCalled();
|
||||
expect(lbaasv2Api.deleteMember.calls.count()).toBe(1);
|
||||
expect(toast.add).toHaveBeenCalledWith('error', 'The following member could not ' +
|
||||
'be deleted: Member1.');
|
||||
});
|
||||
|
||||
it('should return to listener details after delete', function() {
|
||||
spyOn($location, 'path');
|
||||
spyOn(toast, 'add');
|
||||
service.perform(member);
|
||||
$scope.$apply();
|
||||
expect($location.path).toHaveBeenCalledWith('project/load_balancer/1/listeners/2/pools/3');
|
||||
expect(toast.add).toHaveBeenCalledWith('success', 'Deleted member: Member1.');
|
||||
});
|
||||
|
||||
});
|
||||
})();
|
|
@ -23,6 +23,7 @@
|
|||
rowActions.$inject = [
|
||||
'horizon.framework.util.i18n.gettext',
|
||||
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
|
||||
'horizon.dashboard.project.lbaasv2.members.actions.delete',
|
||||
'horizon.dashboard.project.lbaasv2.members.actions.edit-member.modal.service'
|
||||
];
|
||||
|
||||
|
@ -39,8 +40,8 @@
|
|||
* @returns Members row actions service object.
|
||||
*/
|
||||
|
||||
function rowActions(gettext, loadBalancersService, editMember) {
|
||||
var loadBalancerIsActionable, poolId;
|
||||
function rowActions(gettext, loadBalancersService, deleteService, editMember) {
|
||||
var loadBalancerIsActionable, loadbalancerId, listenerId, poolId;
|
||||
|
||||
var service = {
|
||||
actions: actions,
|
||||
|
@ -51,9 +52,11 @@
|
|||
|
||||
///////////////
|
||||
|
||||
function init(loadbalancerId, _poolId_) {
|
||||
loadBalancerIsActionable = loadBalancersService.isActionable(loadbalancerId);
|
||||
function init(_loadbalancerId_, _listenerId_, _poolId_) {
|
||||
loadbalancerId = _loadbalancerId_;
|
||||
listenerId = _listenerId_;
|
||||
poolId = _poolId_;
|
||||
loadBalancerIsActionable = loadBalancersService.isActionable(loadbalancerId);
|
||||
return service;
|
||||
}
|
||||
|
||||
|
@ -63,6 +66,12 @@
|
|||
template: {
|
||||
text: gettext('Edit')
|
||||
}
|
||||
},{
|
||||
service: deleteService.init(loadbalancerId, listenerId, poolId, loadBalancerIsActionable),
|
||||
template: {
|
||||
text: gettext('Delete Member'),
|
||||
type: 'delete'
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,15 +28,16 @@
|
|||
beforeEach(inject(function ($injector) {
|
||||
var rowActionsService = $injector.get(
|
||||
'horizon.dashboard.project.lbaasv2.members.actions.rowActions');
|
||||
actions = rowActionsService.init('1', '2').actions();
|
||||
actions = rowActionsService.init('1', '2', '3').actions();
|
||||
var loadbalancerService = $injector.get(
|
||||
'horizon.dashboard.project.lbaasv2.loadbalancers.service');
|
||||
spyOn(loadbalancerService, 'isActionable').and.returnValue(true);
|
||||
}));
|
||||
|
||||
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 Member');
|
||||
});
|
||||
|
||||
it('should have the "allowed" and "perform" functions', function() {
|
||||
|
|
|
@ -50,7 +50,8 @@
|
|||
|
||||
ctrl.loading = true;
|
||||
ctrl.error = false;
|
||||
ctrl.actions = rowActions.init($routeParams.loadbalancerId, $routeParams.poolId).actions;
|
||||
ctrl.actions = rowActions.init($routeParams.loadbalancerId,
|
||||
$routeParams.listenerId, $routeParams.poolId).actions;
|
||||
ctrl.loadbalancerId = $routeParams.loadbalancerId;
|
||||
ctrl.listenerId = $routeParams.listenerId;
|
||||
ctrl.poolId = $routeParams.poolId;
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
expect(ctrl.operatingStatus).toBeDefined();
|
||||
expect(ctrl.provisioningStatus).toBeDefined();
|
||||
expect(ctrl.actions).toBe('member-actions');
|
||||
expect(actions.init).toHaveBeenCalledWith('loadbalancerId', 'poolId');
|
||||
expect(actions.init).toHaveBeenCalledWith('loadbalancerId', 'listenerId', 'poolId');
|
||||
});
|
||||
|
||||
it('should throw error on API fail', function() {
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
ctrl.loadbalancerId = $routeParams.loadbalancerId;
|
||||
ctrl.listenerId = $routeParams.listenerId;
|
||||
ctrl.poolId = $routeParams.poolId;
|
||||
ctrl.rowActions = rowActions.init(ctrl.loadbalancerId, ctrl.poolId);
|
||||
ctrl.rowActions = rowActions.init(ctrl.loadbalancerId, ctrl.listenerId, ctrl.poolId);
|
||||
ctrl.batchActions = batchActions.init(ctrl.loadbalancerId);
|
||||
ctrl.operatingStatus = loadBalancersService.operatingStatus;
|
||||
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
'use strict';
|
||||
|
||||
describe('LBaaS v2 Members Table Controller', function() {
|
||||
var controller, lbaasv2API, scope;
|
||||
var controller, lbaasv2API, scope, actions;
|
||||
var items = [{ foo: 'bar' }];
|
||||
var apiFail = false;
|
||||
|
||||
|
@ -43,19 +43,28 @@
|
|||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.value('$uibModal', {});
|
||||
$provide.value('horizon.dashboard.project.lbaasv2.members.actions.rowActions', {
|
||||
init: function() {
|
||||
return {
|
||||
actions: 'member-actions'
|
||||
};
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
|
||||
actions = $injector.get('horizon.dashboard.project.lbaasv2.members.actions.rowActions');
|
||||
controller = $injector.get('$controller');
|
||||
spyOn(lbaasv2API, 'getMembers').and.callFake(fakeAPI);
|
||||
spyOn(actions, 'init').and.callThrough();
|
||||
}));
|
||||
|
||||
function createController() {
|
||||
return controller('MembersTableController', {
|
||||
$scope: scope,
|
||||
$routeParams: {
|
||||
loadbalancerId: 'loadbaancerId',
|
||||
loadbalancerId: 'loadbalancerId',
|
||||
listenerId: 'listenerId',
|
||||
poolId: 'poolId'
|
||||
}});
|
||||
|
|
Loading…
Reference in New Issue