Merge "Add ability to edit products in UI"

This commit is contained in:
Jenkins 2017-02-07 19:24:11 +00:00 committed by Gerrit Code Review
commit 4b5ceaf0ed
6 changed files with 273 additions and 6 deletions

View File

@ -8,8 +8,15 @@
<strong>Product ID:</strong> {{ctrl.id}}<br />
<strong>Description:</strong> {{ctrl.product.description}}<br />
<strong>Publicity:</strong> {{ctrl.product.public ? 'Public' : 'Private'}}<br />
<strong>Vendor Name:</strong> {{ctrl.vendor.name}}<br />
<strong>Vendor Description:</strong> {{ctrl.vendor.description || '-'}}<br />
<strong>Vendor Name:</strong> <a ui-sref="vendor({vendorID: ctrl.vendor.id})">{{ctrl.vendor.name}}</a><br />
<div ng-if="ctrl.productProperties">
<strong>Properties:</strong>
<ul>
<li ng-repeat="(key, value) in ctrl.productProperties">
<em>{{key}}</em>: {{value}}
</li>
</ul>
</div>
</div>
</div>
<div ng-include src="'components/products/partials/management.html'"></div>

View File

@ -8,8 +8,15 @@
<strong>Product ID:</strong> {{ctrl.id}}<br />
<strong>Description:</strong> {{ctrl.product.description}}<br />
<strong>Publicity:</strong> {{ctrl.product.public ? 'Public' : 'Private'}}<br />
<strong>Vendor Name:</strong> {{ctrl.vendor.name}}<br />
<strong>Vendor Description:</strong> {{ctrl.vendor.description || '-'}}<br />
<strong>Vendor Name:</strong> <a ui-sref="vendor({vendorID: ctrl.vendor.id})">{{ctrl.vendor.name}}</a><br />
<div ng-if="ctrl.productProperties">
<strong>Properties:</strong>
<ul>
<li ng-repeat="(key, value) in ctrl.productProperties">
<em>{{key}}</em>: {{value}}
</li>
</ul>
</div>
</div>
</div>
<div ng-include src="'components/products/partials/management.html'"></div>

View File

@ -1,4 +1,9 @@
<div class="pull-right">
<a ng-ig="ctrl.product.can_manage"
href="javascript:void(0)"
ng-click="ctrl.openProductEditModal()">
Edit
</a><br />
<a ng-if="ctrl.product.can_manage"
href="javascript:void(0)"
ng-click="ctrl.deleteProduct()"

View File

@ -0,0 +1,61 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" aria-hidden="true" ng-click="modal.close()">&times;</button>
<h4>Edit Product</h4>
<p>Make changes to your product.</p>
</div>
<div class="modal-body">
<div class="form-group">
<label for="name">Name</label>
<input type="text"
class="form-control"
id="name"
ng-model="modal.product.name">
<br />
<label for="description">Description</label>
<textarea type="text"
class="form-control"
id="description"
ng-model="modal.product.description"
rows="4"
wrap="off">
</textarea>
<br />
<label for="properties">Properties</label>
<small><span class="text-muted glyphicon glyphicon-info-sign" title="Add arbitrary custom properties to your product."></span></small>
<div class="row" ng-repeat="(index, prop) in modal.productProperties">
<div class="col-md-2">
<input type="text"
class="form-control"
ng-model="prop.key">
</div>
<div class="col-md-6">
<input type="text"
class="form-control"
ng-model="prop.value">
</div>
<div class="col-md-2">
<a class="text-danger glyphicon glyphicon-remove"
title="Delete this property?"
ng-click="modal.removeProperty(index)"
style='top:8px'></a>
</div>
</div>
<div><small><a ng-click="modal.addField()"><span class="glyphicon glyphicon-plus"></span>&nbsp;Add new property</a></small></div>
</div>
<div ng-show="modal.showError" class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<span class="sr-only">Error:</span>
{{modal.error}}
</div>
<div ng-show="modal.showSuccess" class="alert alert-success" role="success">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
<span class="sr-only">Success:</span>
Changes saved successfully.
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="button" ng-click="modal.saveChanges()">Save Changes</button>
<button class="btn btn-primary" type="button" ng-click="modal.close()">Close</button>
</div>
</div>

View File

@ -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;
}
}
})();

View File

@ -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');
});
});
});