diff --git a/refstack-ui/app/app.js b/refstack-ui/app/app.js index 44ad016b..6efa35ea 100644 --- a/refstack-ui/app/app.js +++ b/refstack-ui/app/app.js @@ -93,6 +93,26 @@ url: '/vendor/:vendorID', templateUrl: '/components/vendors/vendor.html', controller: 'VendorController as ctrl' + }). + state('userProducts', { + url: '/user_products', + templateUrl: '/components/products/products.html', + controller: 'ProductsController as ctrl' + }). + state('publicProducts', { + url: '/public_products', + templateUrl: '/components/products/products.html', + controller: 'ProductsController as ctrl' + }). + state('cloud', { + url: '/cloud/:id', + templateUrl: '/components/products/cloud.html', + controller: 'ProductController as ctrl' + }). + state('distro', { + url: '/distro/:id', + templateUrl: '/components/products/distro.html', + controller: 'ProductController as ctrl' }); } diff --git a/refstack-ui/app/components/products/cloud.html b/refstack-ui/app/components/products/cloud.html new file mode 100644 index 00000000..4b8af445 --- /dev/null +++ b/refstack-ui/app/components/products/cloud.html @@ -0,0 +1,26 @@ +

Cloud Product

+
+
+
+
+
+ Name: {{ctrl.product.name}}
+ Product ID: {{ctrl.id}}
+ Description: {{ctrl.product.description}}
+ Publicity: {{ctrl.product.public ? 'Public' : 'Private'}}
+ Vendor Name: {{ctrl.vendor.name}}
+ Vendor Description: {{ctrl.vendor.description || '-'}}
+
+
+
+
+
+
+
+
+
+ diff --git a/refstack-ui/app/components/products/distro.html b/refstack-ui/app/components/products/distro.html new file mode 100644 index 00000000..bfe668c2 --- /dev/null +++ b/refstack-ui/app/components/products/distro.html @@ -0,0 +1,26 @@ +

Distro Product

+
+
+
+
+
+ Name: {{ctrl.product.name}}
+ Product ID: {{ctrl.id}}
+ Description: {{ctrl.product.description}}
+ Publicity: {{ctrl.product.public ? 'Public' : 'Private'}}
+ Vendor Name: {{ctrl.vendor.name}}
+ Vendor Description: {{ctrl.vendor.description || '-'}}
+
+
+
+
+
+
+
+
+
+ diff --git a/refstack-ui/app/components/products/partials/management.html b/refstack-ui/app/components/products/partials/management.html new file mode 100644 index 00000000..f6ec4950 --- /dev/null +++ b/refstack-ui/app/components/products/partials/management.html @@ -0,0 +1,13 @@ +
+ + Delete +
+ + Make Product PrivatePublic +
+
diff --git a/refstack-ui/app/components/products/partials/testsTable.html b/refstack-ui/app/components/products/partials/testsTable.html new file mode 100644 index 00000000..b5be705c --- /dev/null +++ b/refstack-ui/app/components/products/partials/testsTable.html @@ -0,0 +1,137 @@ +

Test Runs on Product

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Upload DateTest Run IDProduct VersionShared
+ + + + + {{result.created_at}}{{result.id}}{{result.product_version.version}} + +
+ Publicly Shared: + Yes + + No + + + + +
+ + Associated Guideline: + + None + + + {{result.meta.guideline.slice(0, -5)}} + + + + + +
+ + Associated Target Program: + + None + + + {{ctrl.targetMappings[result.meta.target]}} + + + + + +
+
+ + + Unassociate test result from product + + +
+ +
+ + +
+ + diff --git a/refstack-ui/app/components/products/partials/versions.html b/refstack-ui/app/components/products/partials/versions.html new file mode 100644 index 00000000..fc666327 --- /dev/null +++ b/refstack-ui/app/components/products/partials/versions.html @@ -0,0 +1,29 @@ +Version(s) Available: + + + {{item.version}} + + {{item.version}} + +  + + + +
+
+
+ + + + +
+
+
diff --git a/refstack-ui/app/components/products/partials/versionsModal.html b/refstack-ui/app/components/products/partials/versionsModal.html new file mode 100644 index 00000000..da0f3e12 --- /dev/null +++ b/refstack-ui/app/components/products/partials/versionsModal.html @@ -0,0 +1,51 @@ + diff --git a/refstack-ui/app/components/products/productController.js b/refstack-ui/app/components/products/productController.js new file mode 100644 index 00000000..dd7a284e --- /dev/null +++ b/refstack-ui/app/components/products/productController.js @@ -0,0 +1,357 @@ +/* + * 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('refstackApp') + .controller('ProductController', ProductController); + + ProductController.$inject = [ + '$scope', '$http', '$state', '$stateParams', '$window', '$uibModal', + 'refstackApiUrl', 'raiseAlert' + ]; + + /** + * RefStack Product Controller + * This controller is for the '/product/' details page where owner can + * view details of the product. + */ + function ProductController($scope, $http, $state, $stateParams, + $window, $uibModal, refstackApiUrl, raiseAlert) { + var ctrl = this; + + ctrl.getProduct = getProduct; + ctrl.getProductVersions = getProductVersions; + ctrl.deleteProduct = deleteProduct; + ctrl.deleteProductVersion = deleteProductVersion; + ctrl.getProductTests = getProductTests; + ctrl.switchProductPublicity = switchProductPublicity; + ctrl.associateTestMeta = associateTestMeta; + ctrl.getGuidelineVersionList = getGuidelineVersionList; + ctrl.addProductVersion = addProductVersion; + ctrl.unassociateTest = unassociateTest; + ctrl.openVersionModal = openVersionModal; + + /** The product id extracted from the URL route. */ + ctrl.id = $stateParams.id; + ctrl.productVersions = []; + + if (!$scope.auth.isAuthenticated) { + $state.go('home'); + } + + /** Mappings of DefCore components to marketing program names. */ + ctrl.targetMappings = { + 'platform': 'Openstack Powered Platform', + 'compute': 'OpenStack Powered Compute', + 'object': 'OpenStack Powered Object Storage' + }; + + // Pagination controls. + ctrl.currentPage = 1; + ctrl.itemsPerPage = 20; + ctrl.maxSize = 5; + + ctrl.getProduct(); + ctrl.getProductVersions(); + ctrl.getProductTests(); + + /** + * This will contact the Refstack API to get a product information. + */ + function getProduct() { + ctrl.showError = false; + ctrl.product = null; + var content_url = refstackApiUrl + '/products/' + ctrl.id; + ctrl.productRequest = $http.get(content_url).success( + function(data) { + ctrl.product = data; + ctrl.product_properties = + angular.fromJson(data.properties); + } + ).error(function(error) { + ctrl.showError = true; + ctrl.error = + 'Error retrieving from server: ' + + angular.toJson(error); + }).then(function() { + var url = refstackApiUrl + '/vendors/' + + ctrl.product.organization_id; + $http.get(url).success(function(data) { + ctrl.vendor = data; + }).error(function(error) { + ctrl.showError = true; + ctrl.error = + 'Error retrieving from server: ' + + angular.toJson(error); + }); + }); + } + + /** + * This will contact the Refstack API to get product versions. + */ + function getProductVersions() { + ctrl.showError = false; + var content_url = refstackApiUrl + '/products/' + ctrl.id + + '/versions'; + ctrl.productVersionsRequest = $http.get(content_url).success( + function(data) { + ctrl.productVersions = data; + } + ).error(function(error) { + ctrl.showError = true; + ctrl.error = + 'Error retrieving versions from server: ' + + angular.toJson(error); + }); + } + + /** + * This will delete the product. + */ + function deleteProduct() { + var url = [refstackApiUrl, '/products/', ctrl.id].join(''); + $http.delete(url).success(function () { + $window.location.href = '/'; + }).error(function (error) { + raiseAlert('danger', 'Error: ', error.detail); + }); + } + + /** + * This will delete the given product versions. + */ + function deleteProductVersion(versionId) { + var url = [ + refstackApiUrl, '/products/', ctrl.id, + '/versions/', versionId ].join(''); + $http.delete(url).success(function () { + ctrl.getProductVersions(); + }).error(function (error) { + raiseAlert('danger', 'Error: ', error.detail); + }); + } + + /** + * Set a POST request to the API server to add a new version for + * the product. + */ + function addProductVersion() { + var url = [refstackApiUrl, '/products/', ctrl.id, + '/versions'].join(''); + ctrl.addVersionRequest = $http.post(url, + {'version': ctrl.newProductVersion}) + .success(function (data) { + ctrl.productVersions.push(data); + ctrl.newProductVersion = ''; + ctrl.showNewVersionInput = false; + }).error(function (error) { + raiseAlert('danger', error.title, error.detail); + }); + } + + /** + * Get tests runs associated with the current product. + */ + function getProductTests() { + ctrl.showTestsError = false; + var content_url = refstackApiUrl + '/results' + + '?page=' + ctrl.currentPage + '&product_id=' + + ctrl.id; + + ctrl.testsRequest = $http.get(content_url).success( + function(data) { + ctrl.testsData = data.results; + ctrl.totalItems = data.pagination.total_pages * + ctrl.itemsPerPage; + ctrl.currentPage = data.pagination.current_page; + } + ).error(function(error) { + ctrl.showTestsError = true; + ctrl.testsError = + 'Error retrieving tests from server: ' + + angular.toJson(error); + }); + } + + /** + * This will switch public/private property of the product. + */ + function switchProductPublicity() { + var url = [refstackApiUrl, '/products/', ctrl.id].join(''); + $http.put(url, {public: !ctrl.product.public}).success( + function (data) { + ctrl.product = data; + ctrl.product_properties = angular.fromJson(data.properties); + }).error(function (error) { + raiseAlert('danger', 'Error: ', error.detail); + }); + } + + /** + * This will send an API request in order to associate a metadata + * key-value pair with the given testId + * @param {Number} index - index of the test object in the results list + * @param {String} key - metadata key + * @param {String} value - metadata value + */ + function associateTestMeta(index, key, value) { + var testId = ctrl.testsData[index].id; + var metaUrl = [ + refstackApiUrl, '/results/', testId, '/meta/', key + ].join(''); + + var editFlag = key + 'Edit'; + if (value) { + ctrl.associateRequest = $http.post(metaUrl, value) + .success(function () { + ctrl.testsData[index][editFlag] = false; + }).error(function (error) { + raiseAlert('danger', error.title, error.detail); + }); + } + else { + ctrl.unassociateRequest = $http.delete(metaUrl) + .success(function () { + ctrl.testsData[index][editFlag] = false; + }).error(function (error) { + raiseAlert('danger', error.title, error.detail); + }); + } + } + + /** + * Retrieve an array of available capability files from the Refstack + * API server, sort this array reverse-alphabetically, and store it in + * a scoped variable. + * Sample API return array: ["2015.03.json", "2015.04.json"] + */ + function getGuidelineVersionList() { + if (ctrl.versionList) { + return; + } + var content_url = refstackApiUrl + '/guidelines'; + ctrl.versionsRequest = + $http.get(content_url).success(function (data) { + ctrl.versionList = data.sort().reverse(); + }).error(function (error) { + raiseAlert('danger', error.title, + 'Unable to retrieve version list'); + }); + } + + /** + * Send a PUT request to the API server to unassociate a product with + * a test result. + */ + function unassociateTest(index) { + var testId = ctrl.testsData[index].id; + var url = refstackApiUrl + '/results/' + testId; + ctrl.associateRequest = $http.put(url, {'product_version_id': null}) + .success(function () { + ctrl.testsData.splice(index, 1); + }).error(function (error) { + raiseAlert('danger', error.title, error.detail); + }); + } + + /** + * This will open the modal that will allow a product version + * to be managed. + */ + function openVersionModal(version) { + $uibModal.open({ + templateUrl: '/components/products/partials' + + '/versionsModal.html', + backdrop: true, + windowClass: 'modal', + animation: true, + controller: 'ProductVersionModalController as modal', + size: 'lg', + resolve: { + version: function () { + return version; + }, + parent: function () { + return ctrl; + } + } + }); + } + } + + angular + .module('refstackApp') + .controller('ProductVersionModalController', + ProductVersionModalController); + + ProductVersionModalController.$inject = [ + '$uibModalInstance', '$http', 'refstackApiUrl', 'version', 'parent' + ]; + + /** + * Product Version Modal Controller + * This controller is for the modal that appears if a user wants to + * manage a product version. + */ + function ProductVersionModalController($uibModalInstance, $http, + refstackApiUrl, version, parent) { + + var ctrl = this; + + ctrl.version = version; + ctrl.parent = parent; + + ctrl.close = close; + ctrl.deleteProductVersion = deleteProductVersion; + ctrl.saveChanges = saveChanges; + + /** + * This function will close/dismiss the modal. + */ + function close() { + $uibModalInstance.dismiss('exit'); + } + + /** + * Call the parent function to delete a version, then close the modal. + */ + function deleteProductVersion() { + ctrl.parent.deleteProductVersion(ctrl.version.id); + ctrl.close(); + } + + /** + * This will update the current version, saving changes. + */ + function saveChanges() { + ctrl.showSuccess = false; + ctrl.showError = false; + var url = [ + refstackApiUrl, '/products/', ctrl.version.product_id, + '/versions/', ctrl.version.id ].join(''); + var content = {'cpid': ctrl.version.cpid}; + $http.put(url, content).success(function() { + ctrl.showSuccess = true; + }).error(function(error) { + ctrl.showError = true; + ctrl.error = error.detail; + }); + } + + } +})(); diff --git a/refstack-ui/app/components/products/products.html b/refstack-ui/app/components/products/products.html new file mode 100644 index 00000000..679adcc2 --- /dev/null +++ b/refstack-ui/app/components/products/products.html @@ -0,0 +1,79 @@ +

{{ctrl.pageHeader}}

+

{{ctrl.pageParagraph}}

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
NameProduct TypeDescriptionVendorVisibility
{{product.name}}{{product.name}}{{product.name}}{{ctrl.getProductTypeDescription(product.product_type)}}{{product.description}}{{ctrl.allVendors[product.organization_id].name}}{{product.public ? 'Public' : 'Private'}}
+
+ +
+
+

Add new Product

+
+
+ +

+ +

+
+
+ +

+ +

+
+
+ + +
+
+ + +
+
+ +
+
+
+ +
+
+
+ + diff --git a/refstack-ui/app/components/products/productsController.js b/refstack-ui/app/components/products/productsController.js new file mode 100644 index 00000000..2338bdcd --- /dev/null +++ b/refstack-ui/app/components/products/productsController.js @@ -0,0 +1,204 @@ +/* + * 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('refstackApp') + .controller('ProductsController', ProductsController); + + ProductsController.$inject = [ + '$rootScope', '$scope', '$http', '$state', + 'refstackApiUrl','raiseAlert' + ]; + + /** + * RefStack Products Controller + */ + function ProductsController($rootScope, $scope, $http, $state, + refstackApiUrl, raiseAlert) { + var ctrl = this; + + ctrl.update = update; + ctrl.updateData = updateData; + ctrl._filterProduct = _filterProduct; + ctrl.addProduct = addProduct; + ctrl.updateVendors = updateVendors; + ctrl.getProductTypeDescription = getProductTypeDescription; + + /** Check to see if this page should display user-specific products. */ + ctrl.isUserProducts = $state.current.name === 'userProducts'; + /** Show private products in list for foundation admin */ + ctrl.withPrivate = false; + + /** Properties for adding new products */ + ctrl.name = ''; + ctrl.description = ''; + ctrl.organizationId = ''; + + // Should only be on user-products-page if authenticated. + if (ctrl.isUserProducts && !$scope.auth.isAuthenticated) { + $state.go('home'); + } + + ctrl.pageHeader = ctrl.isUserProducts ? + 'My Products' : 'Public Products'; + + ctrl.pageParagraph = ctrl.isUserProducts ? + 'Your added products are listed here.' : + 'Public products are listed here.'; + + if (ctrl.isUserProducts) { + ctrl.authRequest = $scope.auth.doSignCheck() + .then(ctrl.updateVendors) + .then(ctrl.update); + } else { + ctrl.updateVendors(); + ctrl.update(); + } + + ctrl.rawData = null; + ctrl.allVendors = {}; + ctrl.isAdminView = $rootScope.auth + && $rootScope.auth.currentUser + && $rootScope.auth.currentUser.is_admin; + + /** + * This will contact the Refstack API to get a listing of products. + */ + function update() { + ctrl.showError = false; + // Construct the API URL based on user-specified filters. + var contentUrl = refstackApiUrl + '/products'; + if (typeof ctrl.rawData == 'undefined' + || ctrl.rawData === null) { + ctrl.productsRequest = + $http.get(contentUrl).success(function (data) { + ctrl.rawData = data; + ctrl.updateData(); + }).error(function (error) { + ctrl.rawData = null; + ctrl.showError = true; + ctrl.error = + 'Error retrieving Products listing from server: ' + + angular.toJson(error); + }); + } else { + ctrl.updateData(); + } + } + + /** + * This will update data for view with current settings on page. + */ + function updateData() { + ctrl.data = {}; + ctrl.data.products = ctrl.rawData.products.filter(function(s) { + return ctrl._filterProduct(s); }); + ctrl.data.products.sort(function(a, b) { + return a.name.localeCompare(b.name); + }); + } + + /** + * Returns true if a specific product can be displayed on this page. + */ + function _filterProduct(product) { + if (!ctrl.isUserProducts) { + return product.public; + } + + if ($rootScope.auth.currentUser.is_admin) { + // TO-DO: filter out non-admin's items + // because public is not a correct flag for this + return product.public || ctrl.withPrivate; + } + + return product.can_manage; + } + + /** + * Get the product type description given the type integer. + */ + function getProductTypeDescription(product_type) { + switch (product_type) { + case 0: + return 'Distro'; + case 1: + return 'Public Cloud'; + case 2: + return 'Hosted Private Cloud'; + default: + return 'Unknown'; + } + } + + /** + * This will contact the Refstack API to get a listing of + * available vendors that can be used to associate with products. + */ + function updateVendors() { + // Construct the API URL based on user-specified filters. + var contentUrl = refstackApiUrl + '/vendors'; + ctrl.vendorsRequest = + $http.get(contentUrl).success(function (data) { + ctrl.vendors = Array(); + ctrl.allVendors = {}; + data.vendors.forEach(function(vendor) { + ctrl.allVendors[vendor.id] = vendor; + if (vendor.can_manage) { + ctrl.vendors.push(vendor); + } + }); + ctrl.vendors.sort(function(a, b) { + return a.name.localeCompare(b.name); + }); + if (ctrl.vendors.length == 0) { + ctrl.vendors.push({name: 'Create New...', id: ''}); + } + ctrl.organizationId = ctrl.vendors[0].id; + }).error(function (error) { + ctrl.vendors = null; + ctrl.showError = true; + ctrl.error = + 'Error retrieving vendor listing from server: ' + + angular.toJson(error); + }); + } + + /** + * This will add new Product record. + */ + function addProduct() { + var url = refstackApiUrl + '/products'; + var data = { + name: ctrl.name, + description: ctrl.description, + organization_id: ctrl.organizationId, + product_type: parseInt(ctrl.productType) + }; + ctrl.name = ''; + ctrl.description = ''; + $http.post(url, data).success(function (data) { + ctrl.rawData = null; + ctrl.update(); + }).error(function (error) { + ctrl.showError = true; + ctrl.error = + 'Error adding new Product: ' + angular.toJson(error); + }); + } + } +})(); diff --git a/refstack-ui/app/components/results-report/resultsReportController.js b/refstack-ui/app/components/results-report/resultsReportController.js index caedb285..d28053a6 100644 --- a/refstack-ui/app/components/results-report/resultsReportController.js +++ b/refstack-ui/app/components/results-report/resultsReportController.js @@ -133,7 +133,8 @@ */ function isEditingAllowed() { return Boolean(ctrl.resultsData && - ctrl.resultsData.user_role === 'owner'); + (ctrl.resultsData.user_role === 'owner' || + ctrl.resultsData.user_role == 'foundation')); } /** * This tells you whether the current results are shared with the diff --git a/refstack-ui/app/components/results/results.html b/refstack-ui/app/components/results/results.html index d75048a3..2552fba3 100644 --- a/refstack-ui/app/components/results/results.html +++ b/refstack-ui/app/components/results/results.html @@ -134,7 +134,7 @@ @@ -143,6 +143,60 @@ title="Save" class="glyphicon glyphicon-floppy-disk"> +
+ + Associated Product: + + None + + + + + {{ctrl.products[result.product_version.product_info.id].name}} + + ({{result.product_version.version}}) + + + + + + {{ctrl.products[result.product_version.product_info.id].name}} + + ({{result.product_version.version}}) + + + + + + + + + + Version: + + + + + + + +
diff --git a/refstack-ui/app/components/results/resultsController.js b/refstack-ui/app/components/results/resultsController.js index c3f14829..044b4232 100644 --- a/refstack-ui/app/components/results/resultsController.js +++ b/refstack-ui/app/components/results/resultsController.js @@ -37,6 +37,10 @@ ctrl.clearFilters = clearFilters; ctrl.associateMeta = associateMeta; ctrl.getVersionList = getVersionList; + ctrl.getUserProducts = getUserProducts; + ctrl.associateProductVersion = associateProductVersion; + ctrl.getProductVersions = getProductVersions; + ctrl.prepVersionEdit = prepVersionEdit; /** Mappings of DefCore components to marketing program names. */ ctrl.targetMappings = { @@ -90,6 +94,7 @@ if (ctrl.isUserResults) { ctrl.authRequest = $scope.auth.doSignCheck() .then(ctrl.update); + ctrl.getUserProducts(); } else { ctrl.update(); } @@ -206,5 +211,98 @@ }); } + /** + * Get products user has management rights to or all products depending + * on the passed in parameter value. + */ + function getUserProducts() { + if (ctrl.products) { + return; + } + var contentUrl = refstackApiUrl + '/products'; + ctrl.productsRequest = + $http.get(contentUrl).success(function (data) { + ctrl.products = {}; + angular.forEach(data.products, function(prod) { + if (prod.can_manage) { + ctrl.products[prod.id] = prod; + } + }); + }).error(function (error) { + ctrl.products = null; + ctrl.showError = true; + ctrl.error = + 'Error retrieving Products listing from server: ' + + angular.toJson(error); + }); + } + + /** + * Send a PUT request to the API server to associate a product with + * a test result. + */ + function associateProductVersion(result) { + var verId = (result.selectedVersion ? + result.selectedVersion.id : null); + var testId = result.id; + var url = refstackApiUrl + '/results/' + testId; + ctrl.associateRequest = $http.put(url, {'product_version_id': + verId}) + .success(function (data) { + result.product_version = result.selectedVersion; + if (result.selectedVersion) { + result.product_version.product_info = + result.selectedProduct; + } + result.productEdit = false; + }).error(function (error) { + raiseAlert('danger', error.title, error.detail); + }); + } + + /** + * Get all versions for a product. + */ + function getProductVersions(result) { + if (!result.selectedProduct) { + result.productVersions = []; + result.selectedVersion = null; + return; + } + + var url = refstackApiUrl + '/products/' + + result.selectedProduct.id + '/versions'; + ctrl.getVersionsRequest = $http.get(url) + .success(function (data) { + result.productVersions = data; + + // If the test result isn't already associated to a + // version, default it to the null version. + if (!result.product_version) { + angular.forEach(data, function(ver) { + if (!ver.version) { + result.selectedVersion = ver; + } + }); + } + }).error(function (error) { + raiseAlert('danger', error.title, error.detail); + }); + } + + /** + * Instantiate variables needed for editing product/version + * associations. + */ + function prepVersionEdit(result) { + result.productEdit = true; + if (result.product_version) { + result.selectedProduct = + ctrl.products[result.product_version.product_info.id]; + } + result.selectedVersion = result.product_version; + ctrl.getProductVersions(result); + } + } })(); diff --git a/refstack-ui/app/components/vendors/partials/vendorEditModal.html b/refstack-ui/app/components/vendors/partials/vendorEditModal.html new file mode 100644 index 00000000..10ca0df4 --- /dev/null +++ b/refstack-ui/app/components/vendors/partials/vendorEditModal.html @@ -0,0 +1,61 @@ + diff --git a/refstack-ui/app/components/vendors/vendor.html b/refstack-ui/app/components/vendors/vendor.html index 4938e2c3..ad8f5f9a 100644 --- a/refstack-ui/app/components/vendors/vendor.html +++ b/refstack-ui/app/components/vendors/vendor.html @@ -10,12 +10,21 @@ Official
Name: {{ctrl.vendor.name}}
- Description: {{ctrl.vendor.description}}
+ Description: {{ctrl.vendor.description || '-'}}
+
+ Properties: + +
Delete
+ Edit
Register with Foundation
Approve registration
diff --git a/refstack-ui/app/components/vendors/vendorController.js b/refstack-ui/app/components/vendors/vendorController.js index 4ae22c3e..621bf430 100644 --- a/refstack-ui/app/components/vendors/vendorController.js +++ b/refstack-ui/app/components/vendors/vendorController.js @@ -21,7 +21,7 @@ VendorController.$inject = [ '$rootScope', '$scope', '$http', '$state', '$stateParams', '$window', - 'refstackApiUrl', 'raiseAlert', 'confirmModal' + '$uibModal', 'refstackApiUrl', 'raiseAlert', 'confirmModal' ]; /** @@ -30,7 +30,7 @@ * view details of the Vendor and manage users. */ function VendorController($rootScope, $scope, $http, $state, $stateParams, - $window, refstackApiUrl, raiseAlert, confirmModal) { + $window, $uibModal, refstackApiUrl, raiseAlert, confirmModal) { var ctrl = this; ctrl.getVendor = getVendor; @@ -41,6 +41,7 @@ ctrl.deleteVendor = deleteVendor; ctrl.removeUserFromVendor = removeUserFromVendor; ctrl.addUserToVendor = addUserToVendor; + ctrl.openVendorEditModal = openVendorEditModal; /** The vendor id extracted from the URL route. */ ctrl.vendorId = $stateParams.vendorID; @@ -62,7 +63,8 @@ $http.get(contentUrl).success(function(data) { ctrl.vendor = data; var isAdmin = $rootScope.auth.currentUser.is_admin; - ctrl.vendor.canDelete = ctrl.vendor.type != 0 + ctrl.vendor.canDelete = ctrl.vendor.canEdit = + ctrl.vendor.type != 0 && (ctrl.vendor.can_manage || isAdmin); ctrl.vendor.canRegister = ctrl.vendor.type == 1; @@ -181,5 +183,121 @@ error.detail); }); } + + /** + * This will open the modal that will allow a user to edit + */ + function openVendorEditModal() { + $uibModal.open({ + templateUrl: '/components/vendors/partials' + + '/vendorEditModal.html', + backdrop: true, + windowClass: 'modal', + animation: true, + controller: 'VendorEditModalController as modal', + size: 'lg', + resolve: { + vendor: function () { + return ctrl.vendor; + } + } + }); + } + } + + angular + .module('refstackApp') + .controller('VendorEditModalController', VendorEditModalController); + + VendorEditModalController.$inject = [ + '$uibModalInstance', '$http', '$state', 'vendor', 'refstackApiUrl' + ]; + + /** + * Vendor Edit Modal Controller + * This controls the modal that allows editing a vendor. + */ + function VendorEditModalController($uibModalInstance, $http, $state, + vendor, refstackApiUrl) { + + var ctrl = this; + + ctrl.close = close; + ctrl.addField = addField; + ctrl.saveChanges = saveChanges; + ctrl.removeProperty = removeProperty; + + ctrl.vendor = vendor; + ctrl.vendorProperties = []; + + parseVendorProperties(); + + /** + * Close the vendor edit modal. + */ + function close() { + $uibModalInstance.dismiss('exit'); + } + + /** + * Push a blank property key-value pair into the vendorProperties + * array. This will spawn new input boxes. + */ + function addField() { + ctrl.vendorProperties.push({'key': '', 'value': ''}); + } + + /** + * Send a PUT request to the server with the changes. + */ + function saveChanges() { + ctrl.showError = false; + ctrl.showSuccess = false; + var url = [refstackApiUrl, '/vendors/', ctrl.vendor.id].join(''); + var properties = propertiesToJson(); + var content = {'name': ctrl.vendor.name, + 'description': ctrl.vendor.description, + 'properties': properties}; + $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 vendorProperties array at the given index. + */ + function removeProperty(index) { + ctrl.vendorProperties.splice(index, 1); + } + + /** + * Parse the vendor properties and put them in a format more suitable + * for forms. + */ + function parseVendorProperties() { + var props = angular.fromJson(ctrl.vendor.properties); + angular.forEach(props, function(value, key) { + ctrl.vendorProperties.push({'key': key, 'value': value}); + }); + } + + /** + * Convert the list of property objects to a dict containing the + * each key-value pair.. + */ + function propertiesToJson() { + var properties = {}; + for (var i = 0, len = ctrl.vendorProperties.length; i < len; i++) { + var prop = ctrl.vendorProperties[i]; + if (prop.key && prop.value) { + properties[prop.key] = prop.value; + } + } + return properties; + } } })(); diff --git a/refstack-ui/app/components/vendors/vendorsController.js b/refstack-ui/app/components/vendors/vendorsController.js index 6386d328..d597915c 100644 --- a/refstack-ui/app/components/vendors/vendorsController.js +++ b/refstack-ui/app/components/vendors/vendorsController.js @@ -74,8 +74,7 @@ && $rootScope.auth.currentUser.is_admin; /** - * This will contact the Refstack API to get a listing of test run - * results. + * This will contact the Refstack API to get a listing of vendors */ function update() { ctrl.showError = false; diff --git a/refstack-ui/app/index.html b/refstack-ui/app/index.html index f3111645..426f2628 100644 --- a/refstack-ui/app/index.html +++ b/refstack-ui/app/index.html @@ -47,6 +47,8 @@ + + diff --git a/refstack-ui/app/shared/filters.js b/refstack-ui/app/shared/filters.js index 84b171b6..e38789f5 100644 --- a/refstack-ui/app/shared/filters.js +++ b/refstack-ui/app/shared/filters.js @@ -31,7 +31,9 @@ return function (objects) { var array = []; angular.forEach(objects, function (object, key) { - object.id = key; + if (!('id' in object)) { + object.id = key; + } array.push(object); }); return array; diff --git a/refstack-ui/app/shared/header/header.html b/refstack-ui/app/shared/header/header.html index 8312679d..aa6984fd 100644 --- a/refstack-ui/app/shared/header/header.html +++ b/refstack-ui/app/shared/header/header.html @@ -19,29 +19,27 @@ RefStack
  • About
  • DefCore Guidelines
  • Community Results
  • -