From fc810bc8e55e1fb238d57760cf3d92673cc4bb5b Mon Sep 17 00:00:00 2001 From: Qian Min Chen Date: Sun, 14 Jan 2018 16:42:14 +0800 Subject: [PATCH] Adding identity ng-groups delete action This patch adds angular groups delete action. To be added in subsequent patches: - Edit, manage members action Parent patch: https://review.openstack.org/#/c/480426/ To Test - set 'groups_panel': True in settings.py - enable keystone v3 in local_settings.py Change-Id: I9b1265d3f2efe941cbf34407f1fc06b69f86a2a8 Implements: blueprint ng-groups-actions Co-Authored-By: Shu Muto --- .../identity/groups/actions/actions.module.js | 22 +++ .../groups/actions/delete.action.service.js | 120 +++++++++++++ .../actions/delete.action.service.spec.js | 157 ++++++++++++++++++ 3 files changed, 299 insertions(+) create mode 100644 openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/delete.action.service.js create mode 100644 openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/delete.action.service.spec.js diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/actions.module.js b/openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/actions.module.js index 8308b827db..2f40a0b880 100644 --- a/openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/actions.module.js +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/actions.module.js @@ -33,16 +33,38 @@ registerGroupActions.$inject = [ 'horizon.framework.conf.resource-type-registry.service', 'horizon.dashboard.identity.groups.actions.create.service', + 'horizon.dashboard.identity.groups.actions.delete.service', 'horizon.dashboard.identity.groups.resourceType' ]; function registerGroupActions( registry, createService, + deleteService, groupResourceTypeCode ) { var groupResourceType = registry.getResourceType(groupResourceTypeCode); + groupResourceType.itemActions + .append({ + id: 'deleteAction', + service: deleteService, + template: { + text: gettext('Delete Group'), + type: 'delete' + } + }); + + groupResourceType.batchActions + .append({ + id: 'batchDeleteAction', + service: deleteService, + template: { + type: 'delete-selected', + text: gettext('Delete Groups') + } + }); + groupResourceType.globalActions .append({ id: 'createGroupAction', diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/delete.action.service.js b/openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/delete.action.service.js new file mode 100644 index 0000000000..bf06868362 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/delete.action.service.js @@ -0,0 +1,120 @@ +/** + * Copyright 2016 99Cloud + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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.identity.groups') + .factory('horizon.dashboard.identity.groups.actions.delete.service', deleteGroupService); + + deleteGroupService.$inject = [ + '$q', + 'horizon.app.core.openstack-service-api.keystone', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.widgets.modal.deleteModalService', + 'horizon.dashboard.identity.groups.resourceType' + ]; + + /* + * @ngdoc factory + * @name horizon.dashboard.identity.groups.actions.delete.service + * + * @Description + * Brings up the delete groups confirmation modal dialog. + + * On submit, delete given groups. + * On cancel, do nothing. + */ + function deleteGroupService( + $q, + keystone, + policy, + actionResultService, + deleteModal, + groupResourceType + ) { + var service = { + allowed: allowed, + perform: perform, + deleteResult: deleteResult + }; + + return service; + + ////////////// + + function allowed() { + return $q.all([ + keystone.canEditIdentity('group'), + policy.ifAllowed({ rules: [['identity', 'identity:delete_group']] }) + ]); + } + + function perform(items, scope) { + var groups = angular.isArray(items) ? items : [items]; + var context = { + labels: labelize(groups.length), + deleteEntity: deleteGroup + }; + return deleteModal.open(scope, groups, context).then(deleteResult); + } + + function deleteResult(deleteModalResult) { + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + var actionResult = actionResultService.getActionResult(); + deleteModalResult.pass.forEach(function markDeleted(item) { + actionResult.deleted(groupResourceType, item.context.id); + }); + deleteModalResult.fail.forEach(function markFailed(item) { + actionResult.failed(groupResourceType, item.context.id); + }); + return actionResult.result; + } + + function labelize(count) { + return { + + title: ngettext( + 'Confirm Delete Group', + 'Confirm Delete Groups', count), + + message: ngettext( + 'You have selected "%s". Deleted group is not recoverable.', + 'You have selected "%s". Deleted groups are not recoverable.', count), + + submit: ngettext( + 'Delete Group', + 'Delete Groups', count), + + success: ngettext( + 'Deleted Group: %s.', + 'Deleted Groups: %s.', count), + + error: ngettext( + 'Unable to delete Group: %s.', + 'Unable to delete Groups: %s.', count) + }; + } + + function deleteGroup(group) { + return keystone.deleteGroup(group); + } + + } +})(); diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/delete.action.service.spec.js b/openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/delete.action.service.spec.js new file mode 100644 index 0000000000..8d726027d7 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/actions/delete.action.service.spec.js @@ -0,0 +1,157 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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('horizon.dashboard.identity.groups.actions.delete.service', function() { + + var service, $scope, deferredModal, deferredPolicy, deferredCanEdit; + + var deleteModalService = { + onlyPass: false, + open: function () { + var res = { + pass: [{context: {id: 'a'}}], + fail: [] + }; + if (!deleteModalService.onlyPass) { + res.fail.push({context: {id: 'b'}}); + } + deferredModal.resolve(res); + return deferredModal.promise; + } + }; + + var keystoneAPI = { + deleteGroup: function() { + return; + }, + isResolve: true, + canEditIdentity: function() { + success(); + + function success() { + if (keystoneAPI.isResolve) { + deferredCanEdit.resolve(); + } else { + deferredCanEdit.reject(); + } + } + return deferredCanEdit.promise; + } + }; + + var policyAPI = { + isResolve: true, + ifAllowed: function() { + success(); + + function success() { + if (policyAPI.isResolve) { + deferredPolicy.resolve(); + } else { + deferredPolicy.reject(); + } + } + return deferredPolicy.promise; + } + }; + + function generateItems(count) { + var items = []; + for (var i = 0; i < count; i++) { + items.push({ name: 'delete_test', id: i + 1}); + } + return items; + } + + beforeEach(module('horizon.app.core')); + beforeEach(module('horizon.framework')); + beforeEach(module('horizon.dashboard.identity.groups')); + + beforeEach(module('horizon.framework.widgets.modal', function($provide) { + $provide.value('horizon.framework.widgets.modal.deleteModalService', deleteModalService); + })); + + beforeEach(module('horizon.app.core.openstack-service-api', function($provide) { + $provide.value('horizon.app.core.openstack-service-api.keystone', keystoneAPI); + $provide.value('horizon.app.core.openstack-service-api.policy', policyAPI); + spyOn(keystoneAPI, 'canEditIdentity').and.callThrough(); + spyOn(policyAPI, 'ifAllowed').and.callThrough(); + })); + + beforeEach(inject(function($injector, _$rootScope_, $q) { + $scope = _$rootScope_.$new(); + service = $injector.get('horizon.dashboard.identity.groups.actions.delete.service'); + deferredModal = $q.defer(); + deferredPolicy = $q.defer(); + deferredCanEdit = $q.defer(); + })); + + describe('perform method and pass only', function() { + it('should open the delete modal', function() { + spyOn(deleteModalService, 'open').and.callThrough(); + spyOn(keystoneAPI, 'deleteGroup'); + deleteModalService.onlyPass = true; + + var group = generateItems(1)[0]; + service.perform(group); + $scope.$apply(); + + var contextArg = deleteModalService.open.calls.argsFor(0)[2]; + var deleteFunction = contextArg.deleteEntity; + + deleteFunction(group.id); + }); + + it('should pass and fail in a function that delete group by item action', function() { + spyOn(deleteModalService, 'open').and.callThrough(); + spyOn(keystoneAPI, 'deleteGroup'); + deleteModalService.onlyPass = false; + + var group = generateItems(1)[0]; + service.perform(group); + $scope.$apply(); + + var contextArg = deleteModalService.open.calls.argsFor(0)[2]; + var deleteFunction = contextArg.deleteEntity; + + deleteFunction(group.id); + }); + + it('should pass and fail in a function that delete group by batch action', function() { + spyOn(deleteModalService, 'open').and.callThrough(); + spyOn(keystoneAPI, 'deleteGroup'); + deleteModalService.onlyPass = false; + + var group = generateItems(1)[0]; + service.perform([group]); + $scope.$apply(); + + var contextArg = deleteModalService.open.calls.argsFor(0)[2]; + var deleteFunction = contextArg.deleteEntity; + + deleteFunction(group.id); + }); + + it('should call policy check', function() { + service.allowed(); + $scope.$apply(); + + expect(policyAPI.ifAllowed).toHaveBeenCalled(); + }); + }); + }); +})();