From 73b43115d51e589d2b6660290b2deb17ef499c1b Mon Sep 17 00:00:00 2001 From: Kenji Ishii Date: Mon, 14 Nov 2016 18:27:22 +0900 Subject: [PATCH] Add create domain action in angular domain panel This patch adds Create domain action and this patch is using angular-schema-form. Other actions will be added in subsequent patches. To test this patch, it needs to show domain panel and enable angular feature for domain panel. To show domain panel (after installing OpenStack with latest Devstack), modify local_settings.py as follows: - using backends except signed cookie as SESSION_ENGINE. e.g memcached ---- CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', } } SESSION_ENGINE = 'django.contrib.sessions.backends.cache' ---- - enable OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT ---- OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True ---- - set OPENSTACK_KEYSTONE_DEFAULT_DOMAIN to 'Default' domain ---- OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'Default' ---- After that, if admin user logged in, the user could see Domain panel and could operate actions for domains like Create. To test this patch, after above, set 'domains_panel': True in 'ANGULAR_FEATURES'. Co-Authored-By: Richard Jones Co-Authored-By: Shu Muto Change-Id: I75d8e566daf451b12933f43ef9ed0b1df1cd24a8 Partially-Implements: blueprint ng-domains --- .../domains/actions/actions.module.js | 54 +++++++++ .../domains/actions/actions.module.spec.js | 24 ++++ .../domains/actions/create.service.js | 76 ++++++++++++ .../domains/actions/create.service.spec.js | 90 ++++++++++++++ .../domains/actions/workflow/info.help.html | 1 + .../actions/workflow/workflow.service.js | 112 ++++++++++++++++++ .../actions/workflow/workflow.service.spec.js | 42 +++++++ .../identity/domains/domains.module.js | 3 +- 8 files changed, 401 insertions(+), 1 deletion(-) create mode 100644 openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/actions.module.js create mode 100644 openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/actions.module.spec.js create mode 100644 openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/create.service.js create mode 100644 openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/create.service.spec.js create mode 100644 openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/info.help.html create mode 100644 openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/workflow.service.js create mode 100644 openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/workflow.service.spec.js diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/actions.module.js b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/actions.module.js new file mode 100644 index 0000000000..22293e216d --- /dev/null +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/actions.module.js @@ -0,0 +1,54 @@ +/* + * 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'; + + /** + * @ngdoc overview + * @ngname horizon.dashboard.identity.domains.actions + * + * @description + * Provides all of the actions for domains. + */ + angular.module('horizon.dashboard.identity.domains.actions', [ + 'horizon.dashboard.identity.domains' + ]) + .run(registerDomainActions); + + registerDomainActions.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'horizon.dashboard.identity.domains.actions.create.service', + 'horizon.dashboard.identity.domains.resourceType' + ]; + + function registerDomainActions( + registry, + createDomainService, + domainResourceType + ) { + var resourceType = registry.getResourceType(domainResourceType); + + resourceType.globalActions + .append({ + id: 'createDomainAction', + service: createDomainService, + template: { + text: gettext('Create Domain'), + type: 'create' + } + }); + } + +})(); diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/actions.module.spec.js b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/actions.module.spec.js new file mode 100644 index 0000000000..2e272c447c --- /dev/null +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/actions.module.spec.js @@ -0,0 +1,24 @@ +/** + * 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('horizon.dashboard.identity.domains.actions', function () { + it('should exist', function () { + expect(angular.module('horizon.dashboard.identity.domains.actions')).toBeDefined(); + }); + }); + +})(); diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/create.service.js b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/create.service.js new file mode 100644 index 0000000000..eb6f5a7a19 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/create.service.js @@ -0,0 +1,76 @@ +/** + * 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'; + + /** + * @ngdoc factory + * @name horizon.dashboard.identity.domains.actions.create.service + * @description + * Service for the domain create modal + */ + angular + .module('horizon.dashboard.identity.domains.actions') + .factory('horizon.dashboard.identity.domains.actions.create.service', createService); + + createService.$inject = [ + '$q', + 'horizon.app.core.openstack-service-api.keystone', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.dashboard.identity.domains.actions.workflow.service', + 'horizon.dashboard.identity.domains.resourceType', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service' + ]; + + function createService( + $q, keystone, policy, workflow, resourceType, actionResult, modal, toast + ) { + + var service = { + allowed: allowed, + perform: perform + }; + + return service; + + /////// + + function allowed() { + return $q.all([ + keystone.canEditIdentity('domain'), + policy.ifAllowed({ rules: [['identity', 'create_domain']] }) + ]); + } + + function perform() { + var config = workflow.init(); + config.title = gettext("Create Domain"); + return modal.open(config).then(submit); + } + + function submit(context) { + return keystone.createDomain(context.model).then(success); + } + + function success(response) { + var domain = response.data; + var message = gettext('Domain %s was successfully created.'); + toast.add('success', interpolate(message, [domain.name])); + return actionResult.getActionResult().created(resourceType, domain.id).result; + } + } +})(); diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/create.service.spec.js b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/create.service.spec.js new file mode 100644 index 0000000000..f6d94b651d --- /dev/null +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/create.service.spec.js @@ -0,0 +1,90 @@ +/** + * 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.domains.actions.create.service', function() { + + var service, $scope, $q, modal, keystoneAPI, policyAPI; + var createDomainResponse = { + data: { + id: '1234', + name: 'test', + description: 'desc', + enabled: true + } + }; + + beforeEach(module('horizon.framework')); + beforeEach(module('horizon.app.core')); + beforeEach(module('horizon.dashboard.identity.domains')); + + beforeEach(inject(function($injector, _$rootScope_, _$q_) { + $scope = _$rootScope_.$new(); + $q = _$q_; + + keystoneAPI = $injector.get('horizon.app.core.openstack-service-api.keystone'); + policyAPI = $injector.get('horizon.app.core.openstack-service-api.policy'); + modal = $injector.get('horizon.framework.widgets.form.ModalFormService'); + service = $injector.get('horizon.dashboard.identity.domains.actions.create.service'); + })); + + describe('should open the modal', function() { + it('open the modal', function() { + var deferred = $q.defer(); + deferred.resolve(true); + spyOn(modal, "open").and.returnValue($q.defer().promise); + + service.perform(); + $scope.$apply(); + + expect(modal.open).toHaveBeenCalled(); + }); + + it('should call keystone.createDomain', function() { + var deferred = $q.defer(); + deferred.resolve(true); + spyOn(modal, "open").and.returnValue(deferred.promise); + + var deferredCreateDomain = $q.defer(); + deferredCreateDomain.resolve(createDomainResponse); + spyOn(keystoneAPI, 'createDomain').and.returnValue(deferredCreateDomain.promise); + + service.perform(); + $scope.$apply(); + + expect(keystoneAPI.createDomain).toHaveBeenCalled(); + }); + }); + + describe('allowed', function() { + it('should allow create domain', function() { + var deferred = $q.defer(); + deferred.resolve(true); + spyOn(policyAPI, 'ifAllowed').and.returnValue(deferred.promise); + var deferredCanEdit = $q.defer(); + deferredCanEdit.resolve(true); + spyOn(keystoneAPI, 'canEditIdentity').and.returnValue(deferredCanEdit.promise); + + var allowed = service.allowed(); + + expect(allowed).toBeTruthy(); + expect(keystoneAPI.canEditIdentity).toHaveBeenCalledWith('domain'); + expect(policyAPI.ifAllowed).toHaveBeenCalledWith( + { rules: [['identity', 'create_domain']] }); + }); + }); + }); +})(); diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/info.help.html b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/info.help.html new file mode 100644 index 0000000000..47b611c5f6 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/info.help.html @@ -0,0 +1 @@ +

Domains provide separation between users and infrastructure used by different organizations.

diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/workflow.service.js b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/workflow.service.js new file mode 100644 index 0000000000..0ff2da4f8b --- /dev/null +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/workflow.service.js @@ -0,0 +1,112 @@ +/** + * 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'; + + /** + * @ngdoc factory + * @name horizon.dashboard.identity.domains.actions.workflow.service + * @ngController + * + * @description + * Workflow for creating/updating domain + */ + angular + .module('horizon.dashboard.identity.domains.actions') + .factory('horizon.dashboard.identity.domains.actions.workflow.service', DomainWorkflow); + + DomainWorkflow.$inject = [ + 'horizon.dashboard.identity.domains.basePath' + ]; + + function DomainWorkflow(basePath) { + + var workflow = { + init: init + }; + + function init() { + var schema = { + type: "object", + properties: { + name: { + title: gettext('Domain Name'), + type: 'string' + }, + description: { + title: gettext('Description'), + type: 'string' + }, + enabled: { + title: gettext('Enabled'), + type: 'boolean', + default: true + } + }, + required: ['name'] + }; + + var form = [ + { + type: 'section', + htmlClass: 'row', + items: [ + { + type: 'section', + htmlClass: 'col-sm-6', + items: [ + { + key: "name" + }, + { + key: "description", + type: "textarea" + }, + { + key: "enabled", + type: "radiobuttons", + titleMap: [ + {value: true, name: gettext("Yes")}, + {value: false, name: gettext("No")} + ] + } + ] + }, + { + type: 'template', + templateUrl: basePath + "actions/workflow/info.help.html" + } + ] + } + ]; + + var model = { + name: "", + description: "", + enabled: true + }; + + var config = { + schema: schema, + form: form, + model: model + }; + + return config; + } + + return workflow; + } +})(); diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/workflow.service.spec.js b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/workflow.service.spec.js new file mode 100644 index 0000000000..4d09c0a663 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/actions/workflow/workflow.service.spec.js @@ -0,0 +1,42 @@ +/** + * 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('horizon.dashboard.identity.domains.actions.workflow.service', function() { + + var workflow; + + beforeEach(module('horizon.framework')); + beforeEach(module('horizon.app.core')); + beforeEach(module('horizon.dashboard.identity.domains')); + + beforeEach(inject(function($injector) { + workflow = $injector.get('horizon.dashboard.identity.domains.actions.workflow.service'); + })); + + function testInitWorkflow() { + var config = workflow.init(); + expect(config.schema).toBeDefined(); + expect(config.form).toBeDefined(); + expect(config.model).toBeDefined(); + return config; + } + + it('should be create workflow config for creation', function() { + testInitWorkflow(); + }); + }); +})(); diff --git a/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/domains.module.js b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/domains.module.js index b5e918df76..163b7d1721 100644 --- a/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/domains.module.js +++ b/openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/domains.module.js @@ -28,7 +28,8 @@ angular .module('horizon.dashboard.identity.domains', [ 'ngRoute', - 'horizon.dashboard.identity.domains.details' + 'horizon.dashboard.identity.domains.details', + 'horizon.dashboard.identity.domains.actions' ]) .constant('horizon.dashboard.identity.domains.resourceType', 'OS::Keystone::Domain') .run(run)