From 34829d30b619d2ffbdf6edbf4706bbee3302ee77 Mon Sep 17 00:00:00 2001 From: Paul Van Eck Date: Tue, 31 Jan 2017 16:49:39 -0800 Subject: [PATCH] Add ability to edit products in UI This feature will allow vendor admins to edit a vendor's products. Change-Id: I4b6d3e576ef1b20c16845786555d51a86cbc746d --- .../app/components/products/cloud.html | 11 +- .../app/components/products/distro.html | 11 +- .../products/partials/management.html | 5 + .../products/partials/productEditModal.html | 61 ++++++++ .../components/products/productController.js | 130 +++++++++++++++++- refstack-ui/tests/unit/ControllerSpec.js | 61 ++++++++ 6 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 refstack-ui/app/components/products/partials/productEditModal.html diff --git a/refstack-ui/app/components/products/cloud.html b/refstack-ui/app/components/products/cloud.html index 4b8af445..bf812d6c 100644 --- a/refstack-ui/app/components/products/cloud.html +++ b/refstack-ui/app/components/products/cloud.html @@ -8,8 +8,15 @@ Product ID: {{ctrl.id}}
Description: {{ctrl.product.description}}
Publicity: {{ctrl.product.public ? 'Public' : 'Private'}}
- Vendor Name: {{ctrl.vendor.name}}
- Vendor Description: {{ctrl.vendor.description || '-'}}
+ Vendor Name: {{ctrl.vendor.name}}
+
+ Properties: + +
diff --git a/refstack-ui/app/components/products/distro.html b/refstack-ui/app/components/products/distro.html index bfe668c2..ec12d380 100644 --- a/refstack-ui/app/components/products/distro.html +++ b/refstack-ui/app/components/products/distro.html @@ -8,8 +8,15 @@ Product ID: {{ctrl.id}}
Description: {{ctrl.product.description}}
Publicity: {{ctrl.product.public ? 'Public' : 'Private'}}
- Vendor Name: {{ctrl.vendor.name}}
- Vendor Description: {{ctrl.vendor.description || '-'}}
+ Vendor Name: {{ctrl.vendor.name}}
+
+ Properties: + +
diff --git a/refstack-ui/app/components/products/partials/management.html b/refstack-ui/app/components/products/partials/management.html index f6ec4950..ced8e59d 100644 --- a/refstack-ui/app/components/products/partials/management.html +++ b/refstack-ui/app/components/products/partials/management.html @@ -1,4 +1,9 @@
+ + Edit +
+ + + +
diff --git a/refstack-ui/app/components/products/productController.js b/refstack-ui/app/components/products/productController.js index dd7a284e..5541c6e5 100644 --- a/refstack-ui/app/components/products/productController.js +++ b/refstack-ui/app/components/products/productController.js @@ -44,6 +44,7 @@ ctrl.addProductVersion = addProductVersion; ctrl.unassociateTest = unassociateTest; ctrl.openVersionModal = openVersionModal; + ctrl.openProductEditModal = openProductEditModal; /** The product id extracted from the URL route. */ ctrl.id = $stateParams.id; @@ -79,7 +80,7 @@ ctrl.productRequest = $http.get(content_url).success( function(data) { ctrl.product = data; - ctrl.product_properties = + ctrl.productProperties = angular.fromJson(data.properties); } ).error(function(error) { @@ -196,7 +197,7 @@ $http.put(url, {public: !ctrl.product.public}).success( function (data) { ctrl.product = data; - ctrl.product_properties = angular.fromJson(data.properties); + ctrl.productProperties = angular.fromJson(data.properties); }).error(function (error) { raiseAlert('danger', 'Error: ', error.detail); }); @@ -292,6 +293,28 @@ } }); } + + /** + * This will open the modal that will allow product details + * to be edited. + */ + function openProductEditModal() { + $uibModal.open({ + templateUrl: '/components/products/partials' + + '/productEditModal.html', + backdrop: true, + windowClass: 'modal', + animation: true, + controller: 'ProductEditModalController as modal', + size: 'lg', + resolve: { + product: function () { + return ctrl.product; + } + } + }); + + } } angular @@ -354,4 +377,107 @@ } } + + angular + .module('refstackApp') + .controller('ProductEditModalController', ProductEditModalController); + + ProductEditModalController.$inject = [ + '$uibModalInstance', '$http', '$state', 'product', 'refstackApiUrl' + ]; + + /** + * Product Edit Modal Controller + * This controls the modal that allows editing a product. + */ + function ProductEditModalController($uibModalInstance, $http, + $state, product, refstackApiUrl) { + + var ctrl = this; + + ctrl.close = close; + ctrl.addField = addField; + ctrl.saveChanges = saveChanges; + ctrl.removeProperty = removeProperty; + + ctrl.product = product; + ctrl.productName = product.name; + ctrl.productProperties = []; + + parseProductProperties(); + + /** + * Close the product edit modal. + */ + function close() { + $uibModalInstance.dismiss('exit'); + } + + /** + * Push a blank property key-value pair into the productProperties + * array. This will spawn new input boxes. + */ + function addField() { + ctrl.productProperties.push({'key': '', 'value': ''}); + } + + /** + * Send a PUT request to the server with the changes. + */ + function saveChanges() { + ctrl.showError = false; + ctrl.showSuccess = false; + var url = [refstackApiUrl, '/products/', ctrl.product.id].join(''); + var properties = propertiesToJson(); + var content = {'description': ctrl.product.description, + 'properties': properties}; + if (ctrl.productName != ctrl.product.name) { + content.name = ctrl.product.name; + } + $http.put(url, content).success(function() { + ctrl.showSuccess = true; + $state.reload(); + }).error(function(error) { + ctrl.showError = true; + ctrl.error = error.detail; + }); + } + + /** + * Remove a property from the productProperties array at the given + * index. + */ + function removeProperty(index) { + ctrl.productProperties.splice(index, 1); + } + + /** + * Parse the product properties and put them in a format more suitable + * for forms. + */ + function parseProductProperties() { + var props = angular.fromJson(ctrl.product.properties); + angular.forEach(props, function(value, key) { + ctrl.productProperties.push({'key': key, 'value': value}); + }); + } + + /** + * Convert the list of property objects to a dict containing the + * each key-value pair. + */ + function propertiesToJson() { + if (!ctrl.productProperties.length) { + return null; + } + var properties = {}; + for (var i = 0, len = ctrl.productProperties.length; i < len; i++) { + var prop = ctrl.productProperties[i]; + if (prop.key && prop.value) { + properties[prop.key] = prop.value; + } + } + return properties; + } + } })(); diff --git a/refstack-ui/tests/unit/ControllerSpec.js b/refstack-ui/tests/unit/ControllerSpec.js index 044f2ca9..e8610634 100644 --- a/refstack-ui/tests/unit/ControllerSpec.js +++ b/refstack-ui/tests/unit/ControllerSpec.js @@ -1423,6 +1423,17 @@ describe('Refstack controllers', function () { ctrl.openVersionModal(); expect(modal.open).toHaveBeenCalled(); }); + + it('should have a method to open a modal for product editing', + function () { + var modal; + inject(function ($uibModal) { + modal = $uibModal; + }); + spyOn(modal, 'open'); + ctrl.openProductEditModal(); + expect(modal.open).toHaveBeenCalled(); + }); }); describe('ProductVersionModalController', function() { @@ -1463,4 +1474,54 @@ describe('Refstack controllers', function () { $httpBackend.flush(); }); }); + + describe('ProductEditModalController', function() { + var ctrl, modalInstance, state; + var fakeProduct = {'name': 'Foo', 'description': 'Bar', 'id': '1234', + 'properties': {'key1': 'value1'}}; + + beforeEach(inject(function ($controller) { + modalInstance = { + dismiss: jasmine.createSpy('modalInstance.dismiss') + }; + state = { + reload: jasmine.createSpy('state.reload') + }; + ctrl = $controller('ProductEditModalController', + {$uibModalInstance: modalInstance, $state: state, + product: fakeProduct} + ); + })); + + it('should be able to add/remove properties', + function () { + var expected = [{'key': 'key1', 'value': 'value1'}]; + expect(ctrl.productProperties).toEqual(expected); + ctrl.removeProperty(0); + expect(ctrl.productProperties).toEqual([]); + ctrl.addField(); + expected = [{'key': '', 'value': ''}]; + expect(ctrl.productProperties).toEqual(expected); + }); + + it('should have a function to save changes', + function () { + var expectedContent = { + 'name': 'Foo1', 'description': 'Bar', + 'properties': {'key1': 'value1'} + }; + $httpBackend.expectPUT( + fakeApiUrl + '/products/1234', expectedContent) + .respond(200, ''); + ctrl.product.name = 'Foo1'; + ctrl.saveChanges(); + $httpBackend.flush(); + }); + + it('should have a function to exit the modal', + function () { + ctrl.close(); + expect(modalInstance.dismiss).toHaveBeenCalledWith('exit'); + }); + }); });