diff --git a/magnum_ui/api/rest/magnum.py b/magnum_ui/api/rest/magnum.py index c5bbcc6a..477cca95 100644 --- a/magnum_ui/api/rest/magnum.py +++ b/magnum_ui/api/rest/magnum.py @@ -264,8 +264,8 @@ class Quotas(generic.View): """ created = magnum.quotas_create(request, **request.DATA) return rest_utils.CreatedResponse( - ('/api/container_infra/quotas/%s/%s' % created.id, - created.resource), + ('/api/container_infra/quotas/%s/%s' % ( + created.project_id, created.resource)), created.to_dict()) diff --git a/magnum_ui/static/dashboard/container-infra/magnum.service.js b/magnum_ui/static/dashboard/container-infra/magnum.service.js index e3e87ebc..1264846e 100644 --- a/magnum_ui/static/dashboard/container-infra/magnum.service.js +++ b/magnum_ui/static/dashboard/container-infra/magnum.service.js @@ -205,8 +205,8 @@ }); } - function createQuota(projectId, resource, params) { - return apiService.post('/api/container_infra/quotas/' + projectId + '/' + resource, params) + function createQuota(params) { + return apiService.post('/api/container_infra/quotas/', params) .error(function() { toastService.add('error', gettext('Unable to create quota.')); }); diff --git a/magnum_ui/static/dashboard/container-infra/magnum.service.spec.js b/magnum_ui/static/dashboard/container-infra/magnum.service.spec.js index be8bb19b..59ffb764 100644 --- a/magnum_ui/static/dashboard/container-infra/magnum.service.spec.js +++ b/magnum_ui/static/dashboard/container-infra/magnum.service.spec.js @@ -176,20 +176,18 @@ { "func": "createQuota", "method": "post", - "path": "/api/container_infra/quotas/123/Cluster", + "path": "/api/container_infra/quotas/", "data": { "project_id": "123", "resource": "Cluster", - "hard_limit": "1" + "hard_limit": 1 }, "error": "Unable to create quota.", "testInput": [ - "123", - "Cluster", { "project_id": "123", "resource": "Cluster", - "hard_limit": "1" + "hard_limit": 1 } ] }, diff --git a/magnum_ui/static/dashboard/container-infra/quotas/actions.module.js b/magnum_ui/static/dashboard/container-infra/quotas/actions.module.js new file mode 100644 index 00000000..7e3b7df3 --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/quotas/actions.module.js @@ -0,0 +1,57 @@ +/** + * 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.container-infra.quotas.actions + * + * @description + * Provides all of the actions for quotas. + */ + angular.module('horizon.dashboard.container-infra.quotas.actions', + [ + 'horizon.framework', + 'horizon.dashboard.container-infra' + ]) + .run(registerQuotaActions); + + registerQuotaActions.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.dashboard.container-infra.quotas.create.service', + 'horizon.dashboard.container-infra.quotas.resourceType' + ]; + + function registerQuotaActions ( + registry, + gettext, + createQuotaService, + resourceType) { + + var quotaResourceType = registry.getResourceType(resourceType); + quotaResourceType.globalActions + .append({ + id: 'createQuotaAction', + service: createQuotaService, + template: { + type: 'create', + text: gettext('Create Quota') + } + }); + } + +})(); diff --git a/magnum_ui/static/dashboard/container-infra/quotas/actions.module.spec.js b/magnum_ui/static/dashboard/container-infra/quotas/actions.module.spec.js new file mode 100644 index 00000000..5a520fdb --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/quotas/actions.module.spec.js @@ -0,0 +1,41 @@ +/** + * 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('register quota actions module', function() { + var registry; + beforeEach(module('horizon.dashboard.container-infra.quotas.actions')); + + beforeEach(inject(function($injector) { + registry = $injector.get('horizon.framework.conf.resource-type-registry.service'); + })); + + it('registers Create Quota as a batch action', function() { + var actions = registry.getResourceType('OS::Magnum::Quota').globalActions; + expect(actionHasId(actions, 'createQuotaAction')).toBe(true); + }); + + function actionHasId(list, value) { + return list.filter(matchesId).length === 1; + + function matchesId(action) { + if (action.id === value) { + return true; + } + } + } + }); +})(); diff --git a/magnum_ui/static/dashboard/container-infra/quotas/create/create.service.js b/magnum_ui/static/dashboard/container-infra/quotas/create/create.service.js new file mode 100644 index 00000000..411e50f4 --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/quotas/create/create.service.js @@ -0,0 +1,95 @@ +/** + * 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 + * @name horizon.dashboard.container-infra.quotas.create.service + * @description Service for the container-infra quota create modal + */ + angular + .module('horizon.dashboard.container-infra.quotas') + .factory('horizon.dashboard.container-infra.quotas.create.service', createService); + + createService.$inject = [ + '$location', + 'horizon.app.core.openstack-service-api.magnum', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service', + 'horizon.dashboard.container-infra.quotas.resourceType', + 'horizon.dashboard.container-infra.quotas.workflow' + ]; + + function createService( + $location, magnum, policy, actionResult, gettext, $qExtensions, modal, toast, + resourceType, workflow + ) { + + var config; + var message = { + success: gettext('Quota %s was successfully created.') + }; + + var service = { + perform: perform, + allowed: allowed + }; + + return service; + + ////////////// + + function perform(selected, $scope) { + config = workflow.init('create', gettext('Create'), $scope); + if (typeof selected !== 'undefined') { + config.model.id = selected.id; + } + return modal.open(config).then(submit); + } + + function allowed() { + return $qExtensions.booleanAsPromise(true); + } + + function submit(context) { + context.model = cleanNullProperties(context.model); + return magnum.createQuota(context.model).then(success, true); + } + + function cleanNullProperties(model) { + // Initially clean fields that don't have any value. + // Not only "null", blank too. + for (var key in model) { + if (model.hasOwnProperty(key) && model[key] === null || model[key] === "") { + delete model[key]; + } + } + return model; + } + + function success(response) { + response.data.id = response.data.uuid; + toast.add('success', interpolate(message.success, [response.data.id])); + var result = actionResult.getActionResult() + .created(resourceType, response.data.id); + return result.result; + } + } +})(); diff --git a/magnum_ui/static/dashboard/container-infra/quotas/create/create.service.spec.js b/magnum_ui/static/dashboard/container-infra/quotas/create/create.service.spec.js new file mode 100644 index 00000000..58de6cc5 --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/quotas/create/create.service.spec.js @@ -0,0 +1,72 @@ +/** + * 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.container-infra.quotas.create.service', function() { + + var service, $scope, $q, deferred, magnum, workflow; + var model = { + id: 1 + }; + var modal = { + open: function(config) { + config.model = model; + deferred = $q.defer(); + deferred.resolve(config); + return deferred.promise; + } + }; + + /////////////////// + + beforeEach(module('horizon.app.core')); + beforeEach(module('horizon.framework')); + beforeEach(module('horizon.dashboard.container-infra.quotas')); + + beforeEach(module(function($provide) { + $provide.value('horizon.framework.widgets.form.ModalFormService', modal); + })); + + beforeEach(inject(function($injector, _$rootScope_, _$q_) { + $q = _$q_; + $scope = _$rootScope_.$new(); + service = $injector.get('horizon.dashboard.container-infra.quotas.create.service'); + magnum = $injector.get('horizon.app.core.openstack-service-api.magnum'); + workflow = $injector.get('horizon.dashboard.container-infra.quotas.workflow'); + deferred = $q.defer(); + deferred.resolve({data: {id: 1}}); + spyOn(magnum, 'createQuota').and.returnValue(deferred.promise); + spyOn(modal, 'open').and.callThrough(); + spyOn(workflow, 'init').and.returnValue({model: model}); + })); + + it('should check the policy if the user is allowed to create quota', function() { + var allowed = service.allowed(); + expect(allowed).toBeTruthy(); + }); + + it('open the modal', inject(function($timeout) { + service.perform(model, $scope); + + expect(modal.open).toHaveBeenCalled(); + + $timeout.flush(); + $scope.$apply(); + + expect(magnum.createQuota).toHaveBeenCalled(); + })); + }); +})(); diff --git a/magnum_ui/static/dashboard/container-infra/quotas/quotas.module.js b/magnum_ui/static/dashboard/container-infra/quotas/quotas.module.js index 37383aca..7a233b95 100644 --- a/magnum_ui/static/dashboard/container-infra/quotas/quotas.module.js +++ b/magnum_ui/static/dashboard/container-infra/quotas/quotas.module.js @@ -25,7 +25,8 @@ angular .module('horizon.dashboard.container-infra.quotas', [ - 'ngRoute' + 'ngRoute', + 'horizon.dashboard.container-infra.quotas.actions' ]) .constant('horizon.dashboard.container-infra.quotas.events', events()) .constant( diff --git a/magnum_ui/static/dashboard/container-infra/quotas/workflow/workflow.service.js b/magnum_ui/static/dashboard/container-infra/quotas/workflow/workflow.service.js new file mode 100644 index 00000000..727eb540 --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/quotas/workflow/workflow.service.js @@ -0,0 +1,127 @@ +/** + * 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.container-infra.quotas') + .factory( + 'horizon.dashboard.container-infra.quotas.workflow', + QuotaWorkflow); + + QuotaWorkflow.$inject = [ + 'horizon.dashboard.container-infra.basePath', + 'horizon.app.core.workflow.factory', + 'horizon.framework.util.i18n.gettext', + 'horizon.app.core.openstack-service-api.keystone' + ]; + + function QuotaWorkflow(basePath, workflowService, gettext, keystone) { + var workflow = { + init: init + }; + + function init(action, title, $scope) { + var schema, form, model; + var projects = [{value:"", name: gettext("Choose a Project")}]; + var resources = [{value:"Cluster", name: gettext("Cluster")}]; + + // schema + schema = { + type: 'object', + properties: { + 'project_id': { + title: gettext('Project'), + type: 'string' + }, + 'resource': { + title: gettext('Resource'), + type: 'string' + }, + 'hard_limit': { + title: gettext('Hard Limit'), + type: 'number', + minimum: 1 + } + } + }; + + // form + form = [ + { + type: 'section', + htmlClass: 'row', + items: [ + { + type: 'section', + htmlClass: 'col-xs-12', + items: [ + { + key: 'project_id', + type: 'select', + titleMap: projects, + required: true, + readonly: action === 'update' + }, + { + key: 'resource', + type: 'select', + titleMap: resources, + required: true, + readonly: action === 'update' + }, + { + key: 'hard_limit', + placeholder: gettext('Limit for this resource.'), + required: true + } + ] + } + ] + } + ]; + + keystone.getProjects().then(onGetProjects); + + function onGetProjects(response) { + angular.forEach(response.data.items, function(item) { + projects.push({value: item.id, name: item.name}); + }); + form[0].items[0].items[0].titleMap = projects; + } + + model = { + id: "", + project_id: "", + resource: "Cluster", + hard_limit: null + }; + + var config = { + title: title, + schema: schema, + form: form, + model: model + }; + + $scope.model = model; + + return config; + } + + return workflow; + } + +})(); diff --git a/magnum_ui/static/dashboard/container-infra/quotas/workflow/workflow.service.spec.js b/magnum_ui/static/dashboard/container-infra/quotas/workflow/workflow.service.spec.js new file mode 100644 index 00000000..96abde3b --- /dev/null +++ b/magnum_ui/static/dashboard/container-infra/quotas/workflow/workflow.service.spec.js @@ -0,0 +1,45 @@ +/** + * 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.container-infra.quotas.workflow', function() { + + var workflow, $scope, $q, keystone, deferred; + + beforeEach(module('horizon.app.core')); + beforeEach(module('horizon.framework')); + beforeEach(module('horizon.dashboard.container-infra.quotas')); + + beforeEach(inject(function($injector, _$rootScope_, _$q_) { + $q = _$q_; + $scope = _$rootScope_.$new(); + workflow = $injector.get('horizon.dashboard.container-infra.quotas.workflow'); + keystone = $injector.get('horizon.app.core.openstack-service-api.keystone'); + deferred = $q.defer(); + deferred.resolve({data:{items:{1:{name:1},2:{name:2}}}}); + spyOn(keystone, 'getProjects').and.returnValue(deferred.promise); + })); + + it('should be init', inject(function($timeout) { + var config = workflow.init('create', 'Create Quota', $scope); + $timeout.flush(); + expect(config.title).toBeDefined(); + expect(config.schema).toBeDefined(); + expect(config.form).toBeDefined(); + expect(config.model).toBeDefined(); + })); + }); +})();