2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
6 * http://www.apache.org/licenses/LICENSE-2.0
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
20 .controller('ResultsReportController', ResultsReportController);
22 ResultsReportController.$inject = [
23 '$http', '$stateParams', '$window',
24 '$uibModal', 'testapiApiUrl', 'raiseAlert'
28 * TestAPI Results Report Controller
29 * This controller is for the '/results/<test run ID>' page where a user can
30 * view details for a specific test run.
32 function ResultsReportController($http, $stateParams, $window,
33 $uibModal, testapiApiUrl, raiseAlert) {
37 ctrl.getVersionList = getVersionList;
38 ctrl.getResults = getResults;
39 ctrl.isResultAdmin = isResultAdmin;
40 ctrl.isShared = isShared;
41 ctrl.shareTestRun = shareTestRun;
42 ctrl.deleteTestRun = deleteTestRun;
43 ctrl.updateVerificationStatus = updateVerificationStatus;
44 ctrl.updateGuidelines = updateGuidelines;
45 ctrl.getTargetCapabilities = getTargetCapabilities;
46 ctrl.buildCapabilityV1_2 = buildCapabilityV1_2;
47 ctrl.buildCapabilityV1_3 = buildCapabilityV1_3;
48 ctrl.buildCapabilitiesObject = buildCapabilitiesObject;
49 ctrl.isTestFlagged = isTestFlagged;
50 ctrl.getFlaggedReason = getFlaggedReason;
51 ctrl.isCapabilityShown = isCapabilityShown;
52 ctrl.isTestShown = isTestShown;
53 ctrl.getCapabilityTestCount = getCapabilityTestCount;
54 ctrl.getStatusTestCount = getStatusTestCount;
55 ctrl.openFullTestListModal = openFullTestListModal;
56 ctrl.openEditTestModal = openEditTestModal;
58 /** The testID extracted from the URL route. */
59 ctrl.testId = $stateParams.testID;
61 /** The target OpenStack marketing program to compare against. */
62 ctrl.target = 'platform';
64 /** Mappings of Interop WG components to marketing program names. */
65 ctrl.targetMappings = {
66 'platform': 'Openstack Powered Platform',
67 'compute': 'OpenStack Powered Compute',
68 'object': 'OpenStack Powered Object Storage'
71 /** The schema version of the currently selected guideline data. */
72 ctrl.schemaVersion = null;
74 /** The selected test status used for test filtering. */
75 ctrl.testStatus = 'total';
77 /** The HTML template that all accordian groups will use. */
78 ctrl.detailsTemplate = 'components/results-report/partials/' +
82 * Retrieve an array of available guideline files from the TestAPI
83 * API server, sort this array reverse-alphabetically, and store it in
84 * a scoped variable. The scope's selected version is initialized to
85 * the latest (i.e. first) version here as well. After a successful API
86 * call, the function to update the capabilities is called.
87 * Sample API return array: ["2015.03.json", "2015.04.json"]
89 function getVersionList() {
90 var content_url = testapiApiUrl + '/guidelines';
91 ctrl.versionsRequest =
92 $http.get(content_url).success(function (data) {
93 ctrl.versionList = data.sort().reverse();
95 // Default to the first approved guideline which is
96 // expected to be at index 1.
97 ctrl.version = ctrl.versionList[1];
99 ctrl.updateGuidelines();
100 }).error(function (error) {
101 ctrl.showError = true;
102 ctrl.error = 'Error retrieving version list: ' +
103 angular.toJson(error);
108 * Retrieve results from the TestAPI API server based on the test
109 * run id in the URL. This function is the first function that will
110 * be called from the controller. Upon successful retrieval of results,
111 * the function that gets the version list will be called.
113 function getResults() {
114 var content_url = testapiApiUrl + '/results/' + ctrl.testId;
115 ctrl.resultsRequest =
116 $http.get(content_url).success(function (data) {
117 ctrl.resultsData = data;
118 ctrl.version = ctrl.resultsData.meta.guideline;
119 ctrl.isVerified = ctrl.resultsData.verification_status;
120 if (ctrl.resultsData.meta.target) {
121 ctrl.target = ctrl.resultsData.meta.target;
124 }).error(function (error) {
125 ctrl.showError = true;
126 ctrl.resultsData = null;
127 ctrl.error = 'Error retrieving results from server: ' +
128 angular.toJson(error);
133 * This tells you whether the current user has administrative
134 * privileges for the test result.
135 * @returns {Boolean} true if the user has admin privileges.
137 function isResultAdmin() {
138 return Boolean(ctrl.resultsData &&
139 (ctrl.resultsData.user_role === 'owner' ||
140 ctrl.resultsData.user_role === 'foundation'));
143 * This tells you whether the current results are shared with the
145 * @returns {Boolean} true if the results are shared
147 function isShared() {
148 return Boolean(ctrl.resultsData &&
149 'shared' in ctrl.resultsData.meta);
153 * This will send an API request in order to share or unshare the
154 * current results based on the passed in shareState.
155 * @param {Boolean} shareState - Whether to share or unshare results.
157 function shareTestRun(shareState) {
159 testapiApiUrl, '/results/', ctrl.testId, '/meta/shared'
163 $http.post(content_url, 'true').success(function () {
164 ctrl.resultsData.meta.shared = 'true';
165 raiseAlert('success', '', 'Test run shared!');
166 }).error(function (error) {
167 raiseAlert('danger', error.title, error.detail);
171 $http.delete(content_url).success(function () {
172 delete ctrl.resultsData.meta.shared;
173 raiseAlert('success', '', 'Test run unshared!');
174 }).error(function (error) {
175 raiseAlert('danger', error.title, error.detail);
181 * This will send a request to the API to delete the current
184 function deleteTestRun() {
186 testapiApiUrl, '/results/', ctrl.testId
189 $http.delete(content_url).success(function () {
190 $window.history.back();
191 }).error(function (error) {
192 raiseAlert('danger', error.title, error.detail);
197 * This will send a request to the API to delete the current
200 function updateVerificationStatus() {
202 testapiApiUrl, '/results/', ctrl.testId
204 var data = {'verification_status': ctrl.isVerified};
206 $http.put(content_url, data).success(
208 ctrl.resultsData.verification_status = ctrl.isVerified;
209 raiseAlert('success', '',
210 'Verification status changed!');
211 }).error(function (error) {
212 ctrl.isVerified = ctrl.resultsData.verification_status;
213 raiseAlert('danger', error.title, error.detail);
218 * This will contact the TestAPI API server to retrieve the JSON
219 * content of the guideline file corresponding to the selected
220 * version. A function to construct an object from the capability
221 * data will be called upon successful retrieval.
223 function updateGuidelines() {
224 ctrl.guidelineData = null;
225 ctrl.showError = false;
226 var content_url = testapiApiUrl + '/guidelines/' +
229 $http.get(content_url).success(function (data) {
230 ctrl.guidelineData = data;
231 ctrl.schemaVersion = data.schema;
232 ctrl.buildCapabilitiesObject();
233 }).error(function (error) {
234 ctrl.showError = true;
235 ctrl.guidelineData = null;
236 ctrl.error = 'Error retrieving guideline date: ' +
237 angular.toJson(error);
242 * This will get all the capabilities relevant to the target and
243 * their corresponding statuses.
244 * @returns {Object} Object containing each capability and their status
246 function getTargetCapabilities() {
247 var components = ctrl.guidelineData.components;
250 // The 'platform' target is comprised of multiple components, so
251 // we need to get the capabilities belonging to each of its
253 if (ctrl.target === 'platform') {
254 var platform_components =
255 ctrl.guidelineData.platform.required;
257 // This will contain status priority values, where lower
258 // values mean higher priorities.
266 // For each component required for the platform program.
267 angular.forEach(platform_components, function (component) {
268 // Get each capability list belonging to each status.
269 angular.forEach(components[component],
270 function (caps, status) {
271 // For each capability.
272 angular.forEach(caps, function(cap) {
273 // If the capability has already been added.
274 if (cap in targetCaps) {
275 // If the status priority value is less
276 // than the saved priority value, update
278 if (statusMap[status] <
279 statusMap[targetCaps[cap]]) {
280 targetCaps[cap] = status;
284 targetCaps[cap] = status;
291 angular.forEach(components[ctrl.target],
292 function (caps, status) {
293 angular.forEach(caps, function(cap) {
294 targetCaps[cap] = status;
302 * This will build the a capability object for schema version 1.2.
303 * This object will contain the information needed to form a report in
305 * @param {String} capId capability ID
307 function buildCapabilityV1_2(capId) {
311 'notPassedTests': [],
313 'notPassedFlagged': []
315 var capDetails = ctrl.guidelineData.capabilities[capId];
316 // Loop through each test belonging to the capability.
317 angular.forEach(capDetails.tests,
319 // If the test ID is in the results' test list, add
320 // it to the passedTests array.
321 if (ctrl.resultsData.results.indexOf(testId) > -1) {
322 cap.passedTests.push(testId);
323 if (capDetails.flagged.indexOf(testId) > -1) {
324 cap.passedFlagged.push(testId);
328 cap.notPassedTests.push(testId);
329 if (capDetails.flagged.indexOf(testId) > -1) {
330 cap.notPassedFlagged.push(testId);
338 * This will build the a capability object for schema version 1.3 and
339 * above. This object will contain the information needed to form a
340 * report in the HTML template.
341 * @param {String} capId capability ID
343 function buildCapabilityV1_3(capId) {
347 'notPassedTests': [],
349 'notPassedFlagged': []
352 // For cases where a capability listed in components is not
353 // in the capabilities object.
354 if (!(capId in ctrl.guidelineData.capabilities)) {
358 // Loop through each test belonging to the capability.
359 angular.forEach(ctrl.guidelineData.capabilities[capId].tests,
360 function (details, testId) {
363 // If the test ID is in the results' test list.
364 if (ctrl.resultsData.results.indexOf(testId) > -1) {
367 else if ('aliases' in details) {
368 var len = details.aliases.length;
369 for (var i = 0; i < len; i++) {
370 var alias = details.aliases[i];
371 if (ctrl.resultsData.results.indexOf(alias) > -1) {
378 // Add to correct array based on whether the test was
381 cap.passedTests.push(testId);
382 if ('flagged' in details) {
383 cap.passedFlagged.push(testId);
387 cap.notPassedTests.push(testId);
388 if ('flagged' in details) {
389 cap.notPassedFlagged.push(testId);
397 * This will check the schema version of the current capabilities file,
398 * and will call the correct method to build an object based on the
399 * capability data retrieved from the TestAPI API server.
401 function buildCapabilitiesObject() {
402 // This is the object template where 'count' is the number of
403 // total tests that fall under the given status, and 'passedCount'
404 // is the number of tests passed. The 'caps' array will contain
405 // objects with details regarding each capability.
407 'required': {'caps': [], 'count': 0, 'passedCount': 0,
408 'flagFailCount': 0, 'flagPassCount': 0},
409 'advisory': {'caps': [], 'count': 0, 'passedCount': 0,
410 'flagFailCount': 0, 'flagPassCount': 0},
411 'deprecated': {'caps': [], 'count': 0, 'passedCount': 0,
412 'flagFailCount': 0, 'flagPassCount': 0},
413 'removed': {'caps': [], 'count': 0, 'passedCount': 0,
414 'flagFailCount': 0, 'flagPassCount': 0}
417 switch (ctrl.schemaVersion) {
419 var capMethod = 'buildCapabilityV1_2';
425 capMethod = 'buildCapabilityV1_3';
428 ctrl.showError = true;
429 ctrl.guidelineData = null;
430 ctrl.error = 'The schema version for the guideline ' +
431 'file selected (' + ctrl.schemaVersion +
432 ') is currently not supported.';
436 // Get test details for each relevant capability and store
437 // them in the scope's 'caps' object.
438 var targetCaps = ctrl.getTargetCapabilities();
439 angular.forEach(targetCaps, function(status, capId) {
440 var cap = ctrl[capMethod](capId);
441 ctrl.caps[status].count +=
442 cap.passedTests.length + cap.notPassedTests.length;
443 ctrl.caps[status].passedCount += cap.passedTests.length;
444 ctrl.caps[status].flagPassCount += cap.passedFlagged.length;
445 ctrl.caps[status].flagFailCount +=
446 cap.notPassedFlagged.length;
447 ctrl.caps[status].caps.push(cap);
450 ctrl.requiredPassPercent = (ctrl.caps.required.passedCount *
451 100 / ctrl.caps.required.count);
453 ctrl.totalRequiredFailCount = ctrl.caps.required.count -
454 ctrl.caps.required.passedCount;
455 ctrl.totalRequiredFlagCount =
456 ctrl.caps.required.flagFailCount +
457 ctrl.caps.required.flagPassCount;
458 ctrl.totalNonFlagCount = ctrl.caps.required.count -
459 ctrl.totalRequiredFlagCount;
460 ctrl.nonFlagPassCount = ctrl.totalNonFlagCount -
461 (ctrl.totalRequiredFailCount -
462 ctrl.caps.required.flagFailCount);
464 ctrl.nonFlagRequiredPassPercent = (ctrl.nonFlagPassCount *
465 100 / ctrl.totalNonFlagCount);
469 * This will check if a given test is flagged.
470 * @param {String} test ID of the test to check
471 * @param {Object} capObj capability that test is under
472 * @returns {Boolean} truthy value if test is flagged
474 function isTestFlagged(test, capObj) {
478 return (((ctrl.schemaVersion === '1.2') &&
479 (capObj.flagged.indexOf(test) > -1)) ||
480 ((ctrl.schemaVersion >= '1.3') &&
481 (capObj.tests[test].flagged)));
485 * This will return the reason a test is flagged. An empty string
486 * will be returned if the passed in test is not flagged.
487 * @param {String} test ID of the test to check
488 * @param {String} capObj capability that test is under
489 * @returns {String} reason
491 function getFlaggedReason(test, capObj) {
492 if ((ctrl.schemaVersion === '1.2') &&
493 (ctrl.isTestFlagged(test, capObj))) {
495 // Return a generic message since schema 1.2 does not
496 // provide flag reasons.
497 return 'Interop Working Group has flagged this test.';
499 else if ((ctrl.schemaVersion >= '1.3') &&
500 (ctrl.isTestFlagged(test, capObj))) {
502 return capObj.tests[test].flagged.reason;
510 * This will check the if a capability should be shown based on the
511 * test filter selected. If a capability does not have any tests
512 * belonging under the given filter, it should not be shown.
513 * @param {Object} capability Built object for capability
514 * @returns {Boolean} true if capability should be shown
516 function isCapabilityShown(capability) {
517 return ((ctrl.testStatus === 'total') ||
518 (ctrl.testStatus === 'passed' &&
519 capability.passedTests.length > 0) ||
520 (ctrl.testStatus === 'not passed' &&
521 capability.notPassedTests.length > 0) ||
522 (ctrl.testStatus === 'flagged' &&
523 (capability.passedFlagged.length +
524 capability.notPassedFlagged.length > 0)));
528 * This will check the if a test should be shown based on the test
530 * @param {String} test ID of the test
531 * @param {Object} capability Built object for capability
532 * @return {Boolean} true if test should be shown
534 function isTestShown(test, capability) {
535 return ((ctrl.testStatus === 'total') ||
536 (ctrl.testStatus === 'passed' &&
537 capability.passedTests.indexOf(test) > -1) ||
538 (ctrl.testStatus === 'not passed' &&
539 capability.notPassedTests.indexOf(test) > -1) ||
540 (ctrl.testStatus === 'flagged' &&
541 (capability.passedFlagged.indexOf(test) > -1 ||
542 capability.notPassedFlagged.indexOf(test) > -1)));
546 * This will give the number of tests belonging under the selected
547 * test filter for a given capability.
548 * @param {Object} capability Built object for capability
549 * @returns {Number} number of tests under filter
551 function getCapabilityTestCount(capability) {
552 if (ctrl.testStatus === 'total') {
553 return capability.passedTests.length +
554 capability.notPassedTests.length;
556 else if (ctrl.testStatus === 'passed') {
557 return capability.passedTests.length;
559 else if (ctrl.testStatus === 'not passed') {
560 return capability.notPassedTests.length;
562 else if (ctrl.testStatus === 'flagged') {
563 return capability.passedFlagged.length +
564 capability.notPassedFlagged.length;
572 * This will give the number of tests belonging under the selected
573 * test filter for a given status.
574 * @param {String} capability status
575 * @returns {Number} number of tests for status under filter
577 function getStatusTestCount(status) {
581 else if (ctrl.testStatus === 'total') {
582 return ctrl.caps[status].count;
584 else if (ctrl.testStatus === 'passed') {
585 return ctrl.caps[status].passedCount;
587 else if (ctrl.testStatus === 'not passed') {
588 return ctrl.caps[status].count -
589 ctrl.caps[status].passedCount;
591 else if (ctrl.testStatus === 'flagged') {
592 return ctrl.caps[status].flagFailCount +
593 ctrl.caps[status].flagPassCount;
601 * This will open the modal that will show the full list of passed
602 * tests for the current results.
604 function openFullTestListModal() {
606 templateUrl: '/components/results-report/partials' +
607 '/fullTestListModal.html',
609 windowClass: 'modal',
611 controller: 'FullTestListModalController as modal',
615 return ctrl.resultsData.results;
622 * This will open the modal that will all a user to edit test run
625 function openEditTestModal() {
627 templateUrl: '/components/results-report/partials' +
628 '/editTestModal.html',
630 windowClass: 'modal',
632 controller: 'EditTestModalController as modal',
635 resultsData: function () {
636 return ctrl.resultsData;
646 .module('testapiApp')
647 .controller('FullTestListModalController', FullTestListModalController);
649 FullTestListModalController.$inject = ['$uibModalInstance', 'tests'];
652 * Full Test List Modal Controller
653 * This controller is for the modal that appears if a user wants to see the
654 * full list of passed tests on a report page.
656 function FullTestListModalController($uibModalInstance, tests) {
662 * This function will close/dismiss the modal.
664 ctrl.close = function () {
665 $uibModalInstance.dismiss('exit');
669 * This function will return a string representing the sorted
670 * tests list separated by newlines.
672 ctrl.getTestListString = function () {
673 return ctrl.tests.sort().join('\n');
678 .module('testapiApp')
679 .controller('EditTestModalController', EditTestModalController);
681 EditTestModalController.$inject = [
682 '$uibModalInstance', '$http', '$state', 'raiseAlert',
683 'testapiApiUrl', 'resultsData'
687 * Edit Test Modal Controller
688 * This controller is for the modal that appears if a user wants to edit
691 function EditTestModalController($uibModalInstance, $http, $state,
692 raiseAlert, testapiApiUrl, resultsData) {
696 ctrl.getVersionList = getVersionList;
697 ctrl.getUserProducts = getUserProducts;
698 ctrl.associateProductVersion = associateProductVersion;
699 ctrl.getProductVersions = getProductVersions;
700 ctrl.saveChanges = saveChanges;
702 ctrl.resultsData = resultsData;
703 ctrl.metaCopy = angular.copy(resultsData.meta);
704 ctrl.prodVersionCopy = angular.copy(resultsData.product_version);
706 ctrl.getVersionList();
707 ctrl.getUserProducts();
710 * Retrieve an array of available capability files from the TestAPI
711 * API server, sort this array reverse-alphabetically, and store it in
713 * Sample API return array: ["2015.03.json", "2015.04.json"]
715 function getVersionList() {
716 if (ctrl.versionList) {
719 var content_url = testapiApiUrl + '/guidelines';
720 ctrl.versionsRequest =
721 $http.get(content_url).success(function (data) {
722 ctrl.versionList = data.sort().reverse();
723 }).error(function (error) {
724 raiseAlert('danger', error.title,
725 'Unable to retrieve version list');
730 * Get products user has management rights to or all products depending
731 * on the passed in parameter value.
733 function getUserProducts() {
734 var contentUrl = testapiApiUrl + '/products';
735 ctrl.productsRequest =
736 $http.get(contentUrl).success(function (data) {
738 angular.forEach(data.products, function(prod) {
739 if (prod.can_manage) {
740 ctrl.products[prod.id] = prod;
743 if (ctrl.prodVersionCopy) {
744 ctrl.selectedProduct = ctrl.products[
745 ctrl.prodVersionCopy.product_info.id
748 ctrl.getProductVersions();
749 }).error(function (error) {
750 ctrl.products = null;
751 ctrl.showError = true;
753 'Error retrieving Products listing from server: ' +
754 angular.toJson(error);
759 * Send a PUT request to the API server to associate a product with
762 function associateProductVersion() {
763 var verId = (ctrl.selectedVersion ?
764 ctrl.selectedVersion.id : null);
765 var testId = resultsData.id;
766 var url = testapiApiUrl + '/results/' + testId;
767 ctrl.associateRequest = $http.put(url, {'product_version_id':
769 .error(function (error) {
770 ctrl.showError = true;
771 ctrl.showSuccess = false;
773 'Error associating product version with test run: ' +
774 angular.toJson(error);
779 * Get all versions for a product.
781 function getProductVersions() {
782 if (!ctrl.selectedProduct) {
783 ctrl.productVersions = [];
784 ctrl.selectedVersion = null;
788 var url = testapiApiUrl + '/products/' +
789 ctrl.selectedProduct.id + '/versions';
790 ctrl.getVersionsRequest = $http.get(url)
791 .success(function (data) {
792 ctrl.productVersions = data;
793 if (ctrl.prodVersionCopy &&
794 ctrl.prodVersionCopy.product_info.id ==
795 ctrl.selectedProduct.id) {
796 ctrl.selectedVersion = ctrl.prodVersionCopy;
799 angular.forEach(data, function(ver) {
801 ctrl.selectedVersion = ver;
805 }).error(function (error) {
806 raiseAlert('danger', error.title, error.detail);
811 * Send a PUT request to the server with the changes.
813 function saveChanges() {
814 ctrl.showError = false;
815 ctrl.showSuccess = false;
817 testapiApiUrl, '/results/', resultsData.id, '/meta/'
819 var metaFields = ['target', 'guideline', 'shared'];
820 var meta = ctrl.metaCopy;
821 angular.forEach(metaFields, function(field) {
822 var oldMetaValue = (field in ctrl.resultsData.meta) ?
823 ctrl.resultsData.meta[field] : '';
824 if (field in meta && oldMetaValue != meta[field]) {
825 var metaUrl = metaBaseUrl + field;
827 ctrl.assocRequest = $http.post(metaUrl, meta[field])
828 .success(function(data) {
829 ctrl.resultsData.meta[field] = meta[field];
831 .error(function (error) {
832 ctrl.showError = true;
833 ctrl.showSuccess = false;
835 'Error associating metadata with ' +
836 'test run: ' + angular.toJson(error);
840 ctrl.unassocRequest = $http.delete(metaUrl)
841 .success(function (data) {
842 delete ctrl.resultsData.meta[field];
845 .error(function (error) {
846 ctrl.showError = true;
847 ctrl.showSuccess = false;
849 'Error associating metadata with ' +
850 'test run: ' + angular.toJson(error);
855 ctrl.associateProductVersion();
856 if (!ctrl.showError) {
857 ctrl.showSuccess = true;
863 * This function will close/dismiss the modal.
865 ctrl.close = function () {
866 $uibModalInstance.dismiss('exit');