diff options
Diffstat (limited to 'testapi/3rd_party/static/testapi-ui/components/results')
-rw-r--r-- | testapi/3rd_party/static/testapi-ui/components/results/results.html | 247 | ||||
-rw-r--r-- | testapi/3rd_party/static/testapi-ui/components/results/resultsController.js | 339 |
2 files changed, 586 insertions, 0 deletions
diff --git a/testapi/3rd_party/static/testapi-ui/components/results/results.html b/testapi/3rd_party/static/testapi-ui/components/results/results.html new file mode 100644 index 0000000..2a43cd1 --- /dev/null +++ b/testapi/3rd_party/static/testapi-ui/components/results/results.html @@ -0,0 +1,247 @@ +<h3>{{ctrl.pageHeader}}</h3> +<p>{{ctrl.pageParagraph}}</p> + +<div class="result-filters"> + <h4>Filters</h4> + <div class="row"> + <div class="col-md-3"> + <label for="cpid">Start Date</label> + <p class="input-group"> + <input type="text" class="form-control" + uib-datepicker-popup="{{ctrl.format}}" + ng-model="ctrl.startDate" is-open="ctrl.startOpen" + close-text="Close" /> + <span class="input-group-btn"> + <button type="button" class="btn btn-default" ng-click="ctrl.open($event, 'startOpen')"> + <i class="glyphicon glyphicon-calendar"></i> + </button> + </span> + </p> + </div> + <div class="col-md-3"> + <label for="cpid">End Date</label> + <p class="input-group"> + <input type="text" class="form-control" + uib-datepicker-popup="{{ctrl.format}}" + ng-model="ctrl.endDate" is-open="ctrl.endOpen" + close-text="Close" /> + <span class="input-group-btn"> + <button type="button" class="btn btn-default" ng-click="ctrl.open($event, 'endOpen')"> + <i class="glyphicon glyphicon-calendar"></i> + </button> + </span> + </p> + </div> + <div class="col-md-3" style="margin-top:24px;"> + <button type="submit" class="btn btn-primary" ng-click="ctrl.update()">Filter</button> + <button type="submit" class="btn btn-primary btn-danger" ng-click="ctrl.clearFilters()">Clear</button> + </div> + </div> +</div> + +<div cg-busy="{promise:ctrl.authRequest,message:'Loading'}"></div> +<div cg-busy="{promise:ctrl.resultsRequest,message:'Loading'}"></div> + +<div ng-show="ctrl.data" class="results-table"> + <table ng-show="ctrl.data" class="table table-striped table-hover"> + <thead> + <tr> + <th ng-if="ctrl.isUserResults"></th> + <th>Upload Date</th> + <th>Test Run ID</th> + <th ng-if="ctrl.isUserResults">Vendor</th> + <th ng-if="ctrl.isUserResults">Product (version)</th> + <th ng-if="ctrl.isUserResults">Target Program</th> + <th ng-if="ctrl.isUserResults">Guideline</th> + <th ng-if="ctrl.isUserResults">Verified</th> + <th ng-if="ctrl.isUserResults">Shared</th> + </tr> + </thead> + + <tbody> + <tr ng-repeat-start="(index, result) in ctrl.data.results"> + <td ng-if="ctrl.isUserResults"> + <a ng-if="!result.expanded" + class="glyphicon glyphicon-plus" + ng-click="result.expanded = true"> + </a> + <a ng-if="result.expanded" + class="glyphicon glyphicon-minus" + ng-click="result.expanded = false"> + </a> + </td> + <td>{{result.created_at}}</td> + <td><a ui-sref="resultsDetail({testID: result.id})"> + {{result.id.slice(0, 8)}}...{{result.id.slice(-8)}} + </a> + </td> + <td ng-if="ctrl.isUserResults"> + {{ctrl.vendors[result.product_version.product_info.organization_id].name || '-'}} + </td> + <td ng-if="ctrl.isUserResults">{{result.product_version.product_info.name || '-'}} + <span ng-if="result.product_version.version"> + ({{result.product_version.version}}) + </span> + </td> + <td ng-if="ctrl.isUserResults">{{ctrl.targetMappings[result.meta.target] || '-'}}</td> + <td ng-if="ctrl.isUserResults">{{result.meta.guideline.slice(0, -5) || '-'}}</td> + <td ng-if="ctrl.isUserResults"> + <span ng-if="result.verification_status" class="glyphicon glyphicon-ok"></span> + <span ng-if="!result.verification_status">-</span> + + </td> + <td ng-if="ctrl.isUserResults"> + <span ng-show="result.meta.shared" class="glyphicon glyphicon-share"></span> + </td> + </tr> + <tr ng-if="result.expanded" ng-repeat-end> + <td></td> + <td colspan="3"> + <strong>Publicly Shared:</strong> + <span ng-if="result.meta.shared == 'true' && !result.sharedEdit">Yes</span> + <span ng-if="!result.meta.shared && !result.sharedEdit"> + <em>No</em> + </span> + <select ng-if="result.sharedEdit" + ng-model="result.meta.shared" + class="form-inline"> + <option value="true">Yes</option> + <option value="">No</option> + </select> + <a ng-if="!result.sharedEdit" + ng-click="result.sharedEdit = true" + title="Edit" + class="glyphicon glyphicon-pencil"></a> + <a ng-if="result.sharedEdit" + ng-click="ctrl.associateMeta(index,'shared',result.meta.shared)" + title="Save" + class="glyphicon glyphicon-floppy-disk"></a> + <br /> + + <strong>Associated Guideline:</strong> + <span ng-if="!result.meta.guideline && !result.guidelineEdit"> + <em>None</em> + </span> + <span ng-if="result.meta.guideline && !result.guidelineEdit"> + {{result.meta.guideline.slice(0, -5)}} + </span> + <select ng-if="result.guidelineEdit" + ng-model="result.meta.guideline" + ng-options="o as o.slice(0, -5) for o in ctrl.versionList" + class="form-inline"> + <option value="">None</option> + </select> + <a ng-if="!result.guidelineEdit" + ng-click="ctrl.getVersionList();result.guidelineEdit = true" + title="Edit" + class="glyphicon glyphicon-pencil"></a> + <a ng-if="result.guidelineEdit" + ng-click="ctrl.associateMeta(index, 'guideline', result.meta.guideline)" + title="Save" + class="glyphicon glyphicon-floppy-disk"> + </a> + <br /> + + <strong>Associated Target Program:</strong> + <span ng-if="!result.meta.target && !result.targetEdit"> + <em>None</em> + </span> + <span ng-if="result.meta.target && !result.targetEdit"> + {{ctrl.targetMappings[result.meta.target]}}</span> + <select ng-if="result.targetEdit" + ng-model="result.meta.target" + class="form-inline"> + <option value="">None</option> + <option value="platform">OpenStack Powered Platform</option> + <option value="compute">OpenStack Powered Compute</option> + <option value="object">OpenStack Powered Object Storage</option> + </select> + <a ng-if="!result.targetEdit" + ng-click="result.targetEdit = true;" + title="Edit" + class="glyphicon glyphicon-pencil"> + </a> + <a ng-if="result.targetEdit" + ng-click="ctrl.associateMeta(index, 'target', result.meta.target)" + title="Save" + class="glyphicon glyphicon-floppy-disk"> + </a> + <br /> + + <strong>Associated Product:</strong> + <span ng-if="!result.product_version && !result.productEdit"> + <em>None</em> + </span> + <span ng-if="result.product_version && !result.productEdit"> + <span ng-if="ctrl.products[result.product_version.product_info.id].product_type == 0"> + <a ui-sref="distro({id: result.product_version.product_info.id})"> + {{ctrl.products[result.product_version.product_info.id].name}} + <small ng-if="result.product_version.version"> + ({{result.product_version.version}}) + </small> + </a> + </span> + <span ng-if="ctrl.products[result.product_version.product_info.id].product_type != 0"> + <a ui-sref="cloud({id: result.product_version.product_info.id})"> + {{ctrl.products[result.product_version.product_info.id].name}} + <small ng-if="result.product_version.version"> + ({{result.product_version.version}}) + </small> + </a> + </span> + </span> + + <select ng-if="result.productEdit" + ng-options="product as product.name for product in ctrl.products | arrayConverter | orderBy: 'name' track by product.id" + ng-model="result.selectedProduct" + ng-change="ctrl.getProductVersions(result)"> + <option value="">-- No Product --</option> + </select> + + <span ng-if="result.productVersions.length && result.productEdit"> + <span class="glyphicon glyphicon-arrow-right" style="padding-right:3px;color:#303030;"></span> + Version: + <select ng-options="version as version.version for version in result.productVersions | orderBy: 'version' track by version.id" + ng-model="result.selectedVersion"> + </select> + + </span> + <a ng-if="!result.productEdit" + ng-click="ctrl.prepVersionEdit(result)" + title="Edit" + class="glyphicon glyphicon-pencil"> + </a> + <a ng-if="result.productEdit" + ng-click="ctrl.associateProductVersion(result)" + confirm="Once you associate this test to this product, ownership + will be transferred to the product's vendor admins. + Continue?" + title="Save" + class="glyphicon glyphicon-floppy-disk"> + </a> + <br /> + </td> + </tr> + </tbody> + </table> + + <div class="pages"> + <uib-pagination + total-items="ctrl.totalItems" + ng-model="ctrl.currentPage" + items-per-page="ctrl.itemsPerPage" + max-size="ctrl.maxSize" + class="pagination-sm" + boundary-links="true" + rotate="false" + num-pages="ctrl.numPages" + ng-change="ctrl.update()"> + </uib-pagination> + </div> +</div> + +<div ng-show="ctrl.showError" class="alert alert-danger" role="alert"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> + <span class="sr-only">Error:</span> + {{ctrl.error}} +</div> diff --git a/testapi/3rd_party/static/testapi-ui/components/results/resultsController.js b/testapi/3rd_party/static/testapi-ui/components/results/resultsController.js new file mode 100644 index 0000000..2b0338c --- /dev/null +++ b/testapi/3rd_party/static/testapi-ui/components/results/resultsController.js @@ -0,0 +1,339 @@ +/* + * 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('testapiApp') + .controller('ResultsController', ResultsController); + + ResultsController.$inject = [ + '$scope', '$http', '$filter', '$state', 'testapiApiUrl','raiseAlert' + ]; + + /** + * TestAPI Results Controller + * This controller is for the '/results' page where a user can browse + * a listing of community uploaded results. + */ + function ResultsController($scope, $http, $filter, $state, testapiApiUrl, + raiseAlert) { + var ctrl = this; + + ctrl.update = update; + ctrl.open = open; + ctrl.clearFilters = clearFilters; + ctrl.associateMeta = associateMeta; + ctrl.getVersionList = getVersionList; + ctrl.getUserProducts = getUserProducts; + ctrl.getVendors = getVendors; + ctrl.associateProductVersion = associateProductVersion; + ctrl.getProductVersions = getProductVersions; + ctrl.prepVersionEdit = prepVersionEdit; + + /** Mappings of Interop WG components to marketing program names. */ + ctrl.targetMappings = { + 'platform': 'Openstack Powered Platform', + 'compute': 'OpenStack Powered Compute', + 'object': 'OpenStack Powered Object Storage' + }; + + /** Initial page to be on. */ + ctrl.currentPage = 1; + + /** + * How many results should display on each page. Since pagination + * is server-side implemented, this value should match the + * 'results_per_page' configuration of the TestAPI server which + * defaults to 20. + */ + ctrl.itemsPerPage = 20; + + /** + * How many page buttons should be displayed at max before adding + * the '...' button. + */ + ctrl.maxSize = 5; + + /** The upload date lower limit to be used in filtering results. */ + ctrl.startDate = ''; + + /** The upload date upper limit to be used in filtering results. */ + ctrl.endDate = ''; + + /** The date format for the date picker. */ + ctrl.format = 'yyyy-MM-dd'; + + /** Check to see if this page should display user-specific results. */ + ctrl.isUserResults = $state.current.name === 'userResults'; + + // Should only be on user-results-page if authenticated. + if (ctrl.isUserResults && !$scope.auth.isAuthenticated) { + $state.go('home'); + } + + ctrl.pageHeader = ctrl.isUserResults ? + 'Private test results' : 'Community test results'; + + ctrl.pageParagraph = ctrl.isUserResults ? + 'Your most recently uploaded test results are listed here.' : + 'The most recently uploaded community test results are listed ' + + 'here.'; + + if (ctrl.isUserResults) { + ctrl.authRequest = $scope.auth.doSignCheck() + .then(ctrl.update); + ctrl.getUserProducts(); + } else { + ctrl.update(); + } + + ctrl.getVendors(); + + /** + * This will contact the TestAPI API to get a listing of test run + * results. + */ + function update() { + ctrl.showError = false; + // Construct the API URL based on user-specified filters. + var content_url = testapiApiUrl + '/results' + + '?page=' + ctrl.currentPage; + var start = $filter('date')(ctrl.startDate, 'yyyy-MM-dd'); + if (start) { + content_url = + content_url + '&start_date=' + start + ' 00:00:00'; + } + var end = $filter('date')(ctrl.endDate, 'yyyy-MM-dd'); + if (end) { + content_url = content_url + '&end_date=' + end + ' 23:59:59'; + } + if (ctrl.isUserResults) { + content_url = content_url + '&signed'; + } + ctrl.resultsRequest = + $http.get(content_url).success(function (data) { + ctrl.data = data; + ctrl.totalItems = ctrl.data.pagination.total_pages * + ctrl.itemsPerPage; + ctrl.currentPage = ctrl.data.pagination.current_page; + }).error(function (error) { + ctrl.data = null; + ctrl.totalItems = 0; + ctrl.showError = true; + ctrl.error = + 'Error retrieving results listing from server: ' + + angular.toJson(error); + }); + } + + /** + * This is called when the date filter calendar is opened. It + * does some event handling, and sets a scope variable so the UI + * knows which calendar was opened. + * @param {Object} $event - The Event object + * @param {String} openVar - Tells which calendar was opened + */ + function open($event, openVar) { + $event.preventDefault(); + $event.stopPropagation(); + ctrl[openVar] = true; + } + + /** + * This function will clear all filters and update the results + * listing. + */ + function clearFilters() { + ctrl.startDate = null; + ctrl.endDate = null; + ctrl.update(); + } + + /** + * 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 associateMeta(index, key, value) { + var testId = ctrl.data.results[index].id; + var metaUrl = [ + testapiApiUrl, '/results/', testId, '/meta/', key + ].join(''); + + var editFlag = key + 'Edit'; + if (value) { + ctrl.associateRequest = $http.post(metaUrl, value) + .success(function () { + ctrl.data.results[index][editFlag] = false; + }).error(function (error) { + raiseAlert('danger', error.title, error.detail); + }); + } + else { + ctrl.unassociateRequest = $http.delete(metaUrl) + .success(function () { + ctrl.data.results[index][editFlag] = false; + }).error(function (error) { + if (error.code == 404) { + // Key doesn't exist, so count it as a success, + // and don't raise an alert. + ctrl.data.results[index][editFlag] = false; + } + else { + raiseAlert('danger', error.title, error.detail); + } + }); + } + } + + /** + * Retrieve an array of available capability files from the TestAPI + * 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 getVersionList() { + if (ctrl.versionList) { + return; + } + var content_url = testapiApiUrl + '/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'); + }); + } + + /** + * 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 = testapiApiUrl + '/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); + }); + } + + /** + * This will contact the TestAPI API to get a listing of + * vendors. + */ + function getVendors() { + var contentUrl = testapiApiUrl + '/vendors'; + ctrl.vendorsRequest = + $http.get(contentUrl).success(function (data) { + ctrl.vendors = {}; + data.vendors.forEach(function(vendor) { + ctrl.vendors[vendor.id] = vendor; + }); + }).error(function (error) { + ctrl.vendors = null; + ctrl.showError = true; + ctrl.error = + 'Error retrieving vendor 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 = testapiApiUrl + '/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 = testapiApiUrl + '/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); + } + + } +})(); |