/* * 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('ResultsReportController', ResultsReportController); ResultsReportController.$inject = [ '$http', '$stateParams', '$window', '$uibModal', 'refstackApiUrl', 'raiseAlert' ]; /** * RefStack Results Report Controller * This controller is for the '/results/' page where a user can * view details for a specific test run. */ function ResultsReportController($http, $stateParams, $window, $uibModal, refstackApiUrl, raiseAlert) { var ctrl = this; ctrl.getVersionList = getVersionList; ctrl.getResults = getResults; ctrl.isEditingAllowed = isEditingAllowed; ctrl.isShared = isShared; ctrl.shareTestRun = shareTestRun; ctrl.deleteTestRun = deleteTestRun; ctrl.updateGuidelines = updateGuidelines; ctrl.getTargetCapabilities = getTargetCapabilities; ctrl.buildCapabilityV1_2 = buildCapabilityV1_2; ctrl.buildCapabilityV1_3 = buildCapabilityV1_3; ctrl.buildCapabilitiesObject = buildCapabilitiesObject; ctrl.isTestFlagged = isTestFlagged; ctrl.getFlaggedReason = getFlaggedReason; ctrl.isCapabilityShown = isCapabilityShown; ctrl.isTestShown = isTestShown; ctrl.getCapabilityTestCount = getCapabilityTestCount; ctrl.getStatusTestCount = getStatusTestCount; ctrl.openFullTestListModal = openFullTestListModal; /** The testID extracted from the URL route. */ ctrl.testId = $stateParams.testID; /** The target OpenStack marketing program to compare against. */ ctrl.target = 'platform'; /** Mappings of DefCore components to marketing program names. */ ctrl.targetMappings = { 'platform': 'Openstack Powered Platform', 'compute': 'OpenStack Powered Compute', 'object': 'OpenStack Powered Object Storage' }; /** The schema version of the currently selected guideline data. */ ctrl.schemaVersion = null; /** The selected test status used for test filtering. */ ctrl.testStatus = 'total'; /** The HTML template that all accordian groups will use. */ ctrl.detailsTemplate = 'components/results-report/partials/' + 'reportDetails.html'; /** * Retrieve an array of available guideline files from the Refstack * API server, sort this array reverse-alphabetically, and store it in * a scoped variable. The scope's selected version is initialized to * the latest (i.e. first) version here as well. After a successful API * call, the function to update the capabilities is called. * Sample API return array: ["2015.03.json", "2015.04.json"] */ function getVersionList() { var content_url = refstackApiUrl + '/guidelines'; ctrl.versionsRequest = $http.get(content_url).success(function (data) { ctrl.versionList = data.sort().reverse(); if (!ctrl.version) { // Default to the first approved guideline which is // expected to be at index 1. ctrl.version = ctrl.versionList[1]; } ctrl.updateGuidelines(); }).error(function (error) { ctrl.showError = true; ctrl.error = 'Error retrieving version list: ' + angular.toJson(error); }); } /** * Retrieve results from the Refstack API server based on the test * run id in the URL. This function is the first function that will * be called from the controller. Upon successful retrieval of results, * the function that gets the version list will be called. */ function getResults() { var content_url = refstackApiUrl + '/results/' + ctrl.testId; ctrl.resultsRequest = $http.get(content_url).success(function (data) { ctrl.resultsData = data; ctrl.version = ctrl.resultsData.meta.guideline; if (ctrl.resultsData.meta.target) { ctrl.target = ctrl.resultsData.meta.target; } getVersionList(); }).error(function (error) { ctrl.showError = true; ctrl.resultsData = null; ctrl.error = 'Error retrieving results from server: ' + angular.toJson(error); }); } /** * This tells you whether the current results can be edited/managed * based on if the current user is the owner of the results set. * @returns {Boolean} true if editing is allowed */ function isEditingAllowed() { return Boolean(ctrl.resultsData && ctrl.resultsData.user_role === 'owner'); } /** * This tells you whether the current results are shared with the * community or not. * @returns {Boolean} true if the results are shared */ function isShared() { return Boolean(ctrl.resultsData && 'shared' in ctrl.resultsData.meta); } /** * This will send an API request in order to share or unshare the * current results based on the passed in shareState. * @param {Boolean} shareState - Whether to share or unshare results. */ function shareTestRun(shareState) { var content_url = [ refstackApiUrl, '/results/', ctrl.testId, '/meta/shared' ].join(''); if (shareState) { ctrl.shareRequest = $http.post(content_url, 'true').success(function () { ctrl.resultsData.meta.shared = 'true'; raiseAlert('success', '', 'Test run shared!'); }).error(function (error) { raiseAlert('danger', error.title, error.detail); }); } else { ctrl.shareRequest = $http.delete(content_url).success(function () { delete ctrl.resultsData.meta.shared; raiseAlert('success', '', 'Test run unshared!'); }).error(function (error) { raiseAlert('danger', error.title, error.detail); }); } } /** * This will send a request to the API to delete the current * test results set. */ function deleteTestRun() { var content_url = [ refstackApiUrl, '/results/', ctrl.testId ].join(''); ctrl.deleteRequest = $http.delete(content_url).success(function () { $window.history.back(); }).error(function (error) { raiseAlert('danger', error.title, error.detail); }); } /** * This will contact the Refstack API server to retrieve the JSON * content of the guideline file corresponding to the selected * version. A function to construct an object from the capability * data will be called upon successful retrieval. */ function updateGuidelines() { ctrl.guidelineData = null; ctrl.showError = false; var content_url = refstackApiUrl + '/guidelines/' + ctrl.version; ctrl.capsRequest = $http.get(content_url).success(function (data) { ctrl.guidelineData = data; ctrl.schemaVersion = data.schema; ctrl.buildCapabilitiesObject(); }).error(function (error) { ctrl.showError = true; ctrl.guidelineData = null; ctrl.error = 'Error retrieving guideline date: ' + angular.toJson(error); }); } /** * This will get all the capabilities relevant to the target and * their corresponding statuses. * @returns {Object} Object containing each capability and their status */ function getTargetCapabilities() { var components = ctrl.guidelineData.components; var targetCaps = {}; // The 'platform' target is comprised of multiple components, so // we need to get the capabilities belonging to each of its // components. if (ctrl.target === 'platform') { var platform_components = ctrl.guidelineData.platform.required; // This will contain status priority values, where lower // values mean higher priorities. var statusMap = { required: 1, advisory: 2, deprecated: 3, removed: 4 }; // For each component required for the platform program. angular.forEach(platform_components, function (component) { // Get each capability list belonging to each status. angular.forEach(components[component], function (caps, status) { // For each capability. angular.forEach(caps, function(cap) { // If the capability has already been added. if (cap in targetCaps) { // If the status priority value is less // than the saved priority value, update // the value. if (statusMap[status] < statusMap[targetCaps[cap]]) { targetCaps[cap] = status; } } else { targetCaps[cap] = status; } }); }); }); } else { angular.forEach(components[ctrl.target], function (caps, status) { angular.forEach(caps, function(cap) { targetCaps[cap] = status; }); }); } return targetCaps; } /** * This will build the a capability object for schema version 1.2. * This object will contain the information needed to form a report in * the HTML template. * @param {String} capId capability ID */ function buildCapabilityV1_2(capId) { var cap = { 'id': capId, 'passedTests': [], 'notPassedTests': [], 'passedFlagged': [], 'notPassedFlagged': [] }; var capDetails = ctrl.guidelineData.capabilities[capId]; // Loop through each test belonging to the capability. angular.forEach(capDetails.tests, function (testId) { // If the test ID is in the results' test list, add // it to the passedTests array. if (ctrl.resultsData.results.indexOf(testId) > -1) { cap.passedTests.push(testId); if (capDetails.flagged.indexOf(testId) > -1) { cap.passedFlagged.push(testId); } } else { cap.notPassedTests.push(testId); if (capDetails.flagged.indexOf(testId) > -1) { cap.notPassedFlagged.push(testId); } } }); return cap; } /** * This will build the a capability object for schema version 1.3 and * above. This object will contain the information needed to form a * report in the HTML template. * @param {String} capId capability ID */ function buildCapabilityV1_3(capId) { var cap = { 'id': capId, 'passedTests': [], 'notPassedTests': [], 'passedFlagged': [], 'notPassedFlagged': [] }; // Loop through each test belonging to the capability. angular.forEach(ctrl.guidelineData.capabilities[capId].tests, function (details, testId) { var passed = false; // If the test ID is in the results' test list. if (ctrl.resultsData.results.indexOf(testId) > -1) { passed = true; } else if ('aliases' in details) { var len = details.aliases.length; for (var i = 0; i < len; i++) { var alias = details.aliases[i]; if (ctrl.resultsData.results.indexOf(alias) > -1) { passed = true; break; } } } // Add to correct array based on whether the test was // passed or not. if (passed) { cap.passedTests.push(testId); if ('flagged' in details) { cap.passedFlagged.push(testId); } } else { cap.notPassedTests.push(testId); if ('flagged' in details) { cap.notPassedFlagged.push(testId); } } }); return cap; } /** * This will check the schema version of the current capabilities file, * and will call the correct method to build an object based on the * capability data retrieved from the Refstack API server. */ function buildCapabilitiesObject() { // This is the object template where 'count' is the number of // total tests that fall under the given status, and 'passedCount' // is the number of tests passed. The 'caps' array will contain // objects with details regarding each capability. ctrl.caps = { 'required': {'caps': [], 'count': 0, 'passedCount': 0, 'flagFailCount': 0, 'flagPassCount': 0}, 'advisory': {'caps': [], 'count': 0, 'passedCount': 0, 'flagFailCount': 0, 'flagPassCount': 0}, 'deprecated': {'caps': [], 'count': 0, 'passedCount': 0, 'flagFailCount': 0, 'flagPassCount': 0}, 'removed': {'caps': [], 'count': 0, 'passedCount': 0, 'flagFailCount': 0, 'flagPassCount': 0} }; switch (ctrl.schemaVersion) { case '1.2': var capMethod = 'buildCapabilityV1_2'; break; case '1.3': case '1.4': case '1.5': case '1.6': capMethod = 'buildCapabilityV1_3'; break; default: ctrl.showError = true; ctrl.guidelineData = null; ctrl.error = 'The schema version for the guideline ' + 'file selected (' + ctrl.schemaVersion + ') is currently not supported.'; return; } // Get test details for each relevant capability and store // them in the scope's 'caps' object. var targetCaps = ctrl.getTargetCapabilities(); angular.forEach(targetCaps, function(status, capId) { var cap = ctrl[capMethod](capId); ctrl.caps[status].count += cap.passedTests.length + cap.notPassedTests.length; ctrl.caps[status].passedCount += cap.passedTests.length; ctrl.caps[status].flagPassCount += cap.passedFlagged.length; ctrl.caps[status].flagFailCount += cap.notPassedFlagged.length; ctrl.caps[status].caps.push(cap); }); ctrl.requiredPassPercent = (ctrl.caps.required.passedCount * 100 / ctrl.caps.required.count); ctrl.totalRequiredFailCount = ctrl.caps.required.count - ctrl.caps.required.passedCount; ctrl.totalRequiredFlagCount = ctrl.caps.required.flagFailCount + ctrl.caps.required.flagPassCount; ctrl.totalNonFlagCount = ctrl.caps.required.count - ctrl.totalRequiredFlagCount; ctrl.nonFlagPassCount = ctrl.totalNonFlagCount - (ctrl.totalRequiredFailCount - ctrl.caps.required.flagFailCount); ctrl.nonFlagRequiredPassPercent = (ctrl.nonFlagPassCount * 100 / ctrl.totalNonFlagCount); } /** * This will check if a given test is flagged. * @param {String} test ID of the test to check * @param {Object} capObj capability that test is under * @returns {Boolean} truthy value if test is flagged */ function isTestFlagged(test, capObj) { if (!capObj) { return false; } return (((ctrl.schemaVersion === '1.2') && (capObj.flagged.indexOf(test) > -1)) || ((ctrl.schemaVersion >= '1.3') && (capObj.tests[test].flagged))); } /** * This will return the reason a test is flagged. An empty string * will be returned if the passed in test is not flagged. * @param {String} test ID of the test to check * @param {String} capObj capability that test is under * @returns {String} reason */ function getFlaggedReason(test, capObj) { if ((ctrl.schemaVersion === '1.2') && (ctrl.isTestFlagged(test, capObj))) { // Return a generic message since schema 1.2 does not // provide flag reasons. return 'DefCore has flagged this test.'; } else if ((ctrl.schemaVersion >= '1.3') && (ctrl.isTestFlagged(test, capObj))) { return capObj.tests[test].flagged.reason; } else { return ''; } } /** * This will check the if a capability should be shown based on the * test filter selected. If a capability does not have any tests * belonging under the given filter, it should not be shown. * @param {Object} capability Built object for capability * @returns {Boolean} true if capability should be shown */ function isCapabilityShown(capability) { return ((ctrl.testStatus === 'total') || (ctrl.testStatus === 'passed' && capability.passedTests.length > 0) || (ctrl.testStatus === 'not passed' && capability.notPassedTests.length > 0) || (ctrl.testStatus === 'flagged' && (capability.passedFlagged.length + capability.notPassedFlagged.length > 0))); } /** * This will check the if a test should be shown based on the test * filter selected. * @param {String} test ID of the test * @param {Object} capability Built object for capability * @return {Boolean} true if test should be shown */ function isTestShown(test, capability) { return ((ctrl.testStatus === 'total') || (ctrl.testStatus === 'passed' && capability.passedTests.indexOf(test) > -1) || (ctrl.testStatus === 'not passed' && capability.notPassedTests.indexOf(test) > -1) || (ctrl.testStatus === 'flagged' && (capability.passedFlagged.indexOf(test) > -1 || capability.notPassedFlagged.indexOf(test) > -1))); } /** * This will give the number of tests belonging under the selected * test filter for a given capability. * @param {Object} capability Built object for capability * @returns {Number} number of tests under filter */ function getCapabilityTestCount(capability) { if (ctrl.testStatus === 'total') { return capability.passedTests.length + capability.notPassedTests.length; } else if (ctrl.testStatus === 'passed') { return capability.passedTests.length; } else if (ctrl.testStatus === 'not passed') { return capability.notPassedTests.length; } else if (ctrl.testStatus === 'flagged') { return capability.passedFlagged.length + capability.notPassedFlagged.length; } else { return 0; } } /** * This will give the number of tests belonging under the selected * test filter for a given status. * @param {String} capability status * @returns {Number} number of tests for status under filter */ function getStatusTestCount(status) { if (!ctrl.caps) { return -1; } else if (ctrl.testStatus === 'total') { return ctrl.caps[status].count; } else if (ctrl.testStatus === 'passed') { return ctrl.caps[status].passedCount; } else if (ctrl.testStatus === 'not passed') { return ctrl.caps[status].count - ctrl.caps[status].passedCount; } else if (ctrl.testStatus === 'flagged') { return ctrl.caps[status].flagFailCount + ctrl.caps[status].flagPassCount; } else { return -1; } } /** * This will open the modal that will show the full list of passed * tests for the current results. */ function openFullTestListModal() { $uibModal.open({ templateUrl: '/components/results-report/partials' + '/fullTestListModal.html', backdrop: true, windowClass: 'modal', animation: true, controller: 'FullTestListModalController as modal', size: 'lg', resolve: { tests: function () { return ctrl.resultsData.results; } } }); } getResults(); } angular .module('refstackApp') .controller('FullTestListModalController', FullTestListModalController); FullTestListModalController.$inject = ['$uibModalInstance', 'tests']; /** * Full Test List Modal Controller * This controller is for the modal that appears if a user wants to see the * full list of passed tests on a report page. */ function FullTestListModalController($uibModalInstance, tests) { var ctrl = this; ctrl.tests = tests; /** * This function will close/dismiss the modal. */ ctrl.close = function () { $uibModalInstance.dismiss('exit'); }; /** * This function will return a string representing the sorted * tests list separated by newlines. */ ctrl.getTestListString = function () { return ctrl.tests.sort().join('\n'); }; } })();