Merge "Remove compass4nfv weekly danube job"
[releng.git] / utils / test / testapi / opnfv_testapi / ui / results-report / resultsReportController.js
1 /*
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
5  *
6  * http://www.apache.org/licenses/LICENSE-2.0
7  *
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.
13  */
14
15 (function () {
16     'use strict';
17
18     angular
19         .module('testapiApp')
20         .controller('ResultsReportController', ResultsReportController);
21
22     ResultsReportController.$inject = [
23         '$http', '$stateParams', '$window',
24         '$uibModal', 'testapiApiUrl', 'raiseAlert'
25     ];
26
27     /**
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.
31      */
32     function ResultsReportController($http, $stateParams, $window,
33         $uibModal, testapiApiUrl, raiseAlert) {
34
35         var ctrl = this;
36
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;
57
58         /** The testID extracted from the URL route. */
59         ctrl.testId = $stateParams.testID;
60
61         /** The target OpenStack marketing program to compare against. */
62         ctrl.target = 'platform';
63
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'
69         };
70
71         /** The schema version of the currently selected guideline data. */
72         ctrl.schemaVersion = null;
73
74         /** The selected test status used for test filtering. */
75         ctrl.testStatus = 'total';
76
77         /** The HTML template that all accordian groups will use. */
78         ctrl.detailsTemplate = 'components/results-report/partials/' +
79                                'reportDetails.html';
80
81         /**
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"]
88          */
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();
94                     if (!ctrl.version) {
95                         // Default to the first approved guideline which is
96                         // expected to be at index 1.
97                         ctrl.version = ctrl.versionList[1];
98                     }
99                     ctrl.updateGuidelines();
100                 }).error(function (error) {
101                     ctrl.showError = true;
102                     ctrl.error = 'Error retrieving version list: ' +
103                         angular.toJson(error);
104                 });
105         }
106
107         /**
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.
112          */
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;
122                     }
123                     getVersionList();
124                 }).error(function (error) {
125                     ctrl.showError = true;
126                     ctrl.resultsData = null;
127                     ctrl.error = 'Error retrieving results from server: ' +
128                         angular.toJson(error);
129                 });
130         }
131
132         /**
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.
136          */
137         function isResultAdmin() {
138             return Boolean(ctrl.resultsData &&
139                 (ctrl.resultsData.user_role === 'owner' ||
140                  ctrl.resultsData.user_role === 'foundation'));
141         }
142         /**
143          * This tells you whether the current results are shared with the
144          * community or not.
145          * @returns {Boolean} true if the results are shared
146          */
147         function isShared() {
148             return Boolean(ctrl.resultsData &&
149                 'shared' in ctrl.resultsData.meta);
150         }
151
152         /**
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.
156          */
157         function shareTestRun(shareState) {
158             var content_url = [
159                 testapiApiUrl, '/results/', ctrl.testId, '/meta/shared'
160             ].join('');
161             if (shareState) {
162                 ctrl.shareRequest =
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);
168                     });
169             } else {
170                 ctrl.shareRequest =
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);
176                     });
177             }
178         }
179
180         /**
181          * This will send a request to the API to delete the current
182          * test results set.
183          */
184         function deleteTestRun() {
185             var content_url = [
186                 testapiApiUrl, '/results/', ctrl.testId
187             ].join('');
188             ctrl.deleteRequest =
189                 $http.delete(content_url).success(function () {
190                     $window.history.back();
191                 }).error(function (error) {
192                     raiseAlert('danger', error.title, error.detail);
193                 });
194         }
195
196         /**
197          * This will send a request to the API to delete the current
198          * test results set.
199          */
200         function updateVerificationStatus() {
201             var content_url = [
202                 testapiApiUrl, '/results/', ctrl.testId
203             ].join('');
204             var data = {'verification_status': ctrl.isVerified};
205             ctrl.updateRequest =
206                 $http.put(content_url, data).success(
207                     function () {
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);
214                     });
215         }
216
217         /**
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.
222          */
223         function updateGuidelines() {
224             ctrl.guidelineData = null;
225             ctrl.showError = false;
226             var content_url = testapiApiUrl + '/guidelines/' +
227                 ctrl.version;
228             ctrl.capsRequest =
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);
238                 });
239         }
240
241         /**
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
245          */
246         function getTargetCapabilities() {
247             var components = ctrl.guidelineData.components;
248             var targetCaps = {};
249
250             // The 'platform' target is comprised of multiple components, so
251             // we need to get the capabilities belonging to each of its
252             // components.
253             if (ctrl.target === 'platform') {
254                 var platform_components =
255                     ctrl.guidelineData.platform.required;
256
257                 // This will contain status priority values, where lower
258                 // values mean higher priorities.
259                 var statusMap = {
260                     required: 1,
261                     advisory: 2,
262                     deprecated: 3,
263                     removed: 4
264                 };
265
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
277                                     // the value.
278                                     if (statusMap[status] <
279                                         statusMap[targetCaps[cap]]) {
280                                         targetCaps[cap] = status;
281                                     }
282                                 }
283                                 else {
284                                     targetCaps[cap] = status;
285                                 }
286                             });
287                         });
288                 });
289             }
290             else {
291                 angular.forEach(components[ctrl.target],
292                     function (caps, status) {
293                         angular.forEach(caps, function(cap) {
294                             targetCaps[cap] = status;
295                         });
296                     });
297             }
298             return targetCaps;
299         }
300
301         /**
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
304          * the HTML template.
305          * @param {String} capId capability ID
306          */
307         function buildCapabilityV1_2(capId) {
308             var cap = {
309                 'id': capId,
310                 'passedTests': [],
311                 'notPassedTests': [],
312                 'passedFlagged': [],
313                 'notPassedFlagged': []
314             };
315             var capDetails = ctrl.guidelineData.capabilities[capId];
316             // Loop through each test belonging to the capability.
317             angular.forEach(capDetails.tests,
318                 function (testId) {
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);
325                         }
326                     }
327                     else {
328                         cap.notPassedTests.push(testId);
329                         if (capDetails.flagged.indexOf(testId) > -1) {
330                             cap.notPassedFlagged.push(testId);
331                         }
332                     }
333                 });
334             return cap;
335         }
336
337         /**
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
342          */
343         function buildCapabilityV1_3(capId) {
344             var cap = {
345                 'id': capId,
346                 'passedTests': [],
347                 'notPassedTests': [],
348                 'passedFlagged': [],
349                 'notPassedFlagged': []
350             };
351
352             // For cases where a capability listed in components is not
353             // in the capabilities object.
354             if (!(capId in ctrl.guidelineData.capabilities)) {
355                 return cap;
356             }
357
358             // Loop through each test belonging to the capability.
359             angular.forEach(ctrl.guidelineData.capabilities[capId].tests,
360                 function (details, testId) {
361                     var passed = false;
362
363                     // If the test ID is in the results' test list.
364                     if (ctrl.resultsData.results.indexOf(testId) > -1) {
365                         passed = true;
366                     }
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) {
372                                 passed = true;
373                                 break;
374                             }
375                         }
376                     }
377
378                     // Add to correct array based on whether the test was
379                     // passed or not.
380                     if (passed) {
381                         cap.passedTests.push(testId);
382                         if ('flagged' in details) {
383                             cap.passedFlagged.push(testId);
384                         }
385                     }
386                     else {
387                         cap.notPassedTests.push(testId);
388                         if ('flagged' in details) {
389                             cap.notPassedFlagged.push(testId);
390                         }
391                     }
392                 });
393             return cap;
394         }
395
396         /**
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.
400          */
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.
406             ctrl.caps = {
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}
415             };
416
417             switch (ctrl.schemaVersion) {
418                 case '1.2':
419                     var capMethod = 'buildCapabilityV1_2';
420                     break;
421                 case '1.3':
422                 case '1.4':
423                 case '1.5':
424                 case '1.6':
425                     capMethod = 'buildCapabilityV1_3';
426                     break;
427                 default:
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.';
433                     return;
434             }
435
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);
448             });
449
450             ctrl.requiredPassPercent = (ctrl.caps.required.passedCount *
451                 100 / ctrl.caps.required.count);
452
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);
463
464             ctrl.nonFlagRequiredPassPercent = (ctrl.nonFlagPassCount *
465                 100 / ctrl.totalNonFlagCount);
466         }
467
468         /**
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
473          */
474         function isTestFlagged(test, capObj) {
475             if (!capObj) {
476                 return false;
477             }
478             return (((ctrl.schemaVersion === '1.2') &&
479                 (capObj.flagged.indexOf(test) > -1)) ||
480                     ((ctrl.schemaVersion >= '1.3') &&
481                 (capObj.tests[test].flagged)));
482         }
483
484         /**
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
490          */
491         function getFlaggedReason(test, capObj) {
492             if ((ctrl.schemaVersion === '1.2') &&
493                 (ctrl.isTestFlagged(test, capObj))) {
494
495                 // Return a generic message since schema 1.2 does not
496                 // provide flag reasons.
497                 return 'Interop Working Group has flagged this test.';
498             }
499             else if ((ctrl.schemaVersion >= '1.3') &&
500                 (ctrl.isTestFlagged(test, capObj))) {
501
502                 return capObj.tests[test].flagged.reason;
503             }
504             else {
505                 return '';
506             }
507         }
508
509         /**
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
515          */
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)));
525         }
526
527         /**
528          * This will check the if a test should be shown based on the test
529          * filter selected.
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
533          */
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)));
543         }
544
545         /**
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
550          */
551         function getCapabilityTestCount(capability) {
552             if (ctrl.testStatus === 'total') {
553                 return capability.passedTests.length +
554                    capability.notPassedTests.length;
555             }
556             else if (ctrl.testStatus === 'passed') {
557                 return capability.passedTests.length;
558             }
559             else if (ctrl.testStatus === 'not passed') {
560                 return capability.notPassedTests.length;
561             }
562             else if (ctrl.testStatus === 'flagged') {
563                 return capability.passedFlagged.length +
564                    capability.notPassedFlagged.length;
565             }
566             else {
567                 return 0;
568             }
569         }
570
571         /**
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
576          */
577         function getStatusTestCount(status) {
578             if (!ctrl.caps) {
579                 return -1;
580             }
581             else if (ctrl.testStatus === 'total') {
582                 return ctrl.caps[status].count;
583             }
584             else if (ctrl.testStatus === 'passed') {
585                 return ctrl.caps[status].passedCount;
586             }
587             else if (ctrl.testStatus === 'not passed') {
588                 return ctrl.caps[status].count -
589                   ctrl.caps[status].passedCount;
590             }
591             else if (ctrl.testStatus === 'flagged') {
592                 return ctrl.caps[status].flagFailCount +
593                   ctrl.caps[status].flagPassCount;
594             }
595             else {
596                 return -1;
597             }
598         }
599
600         /**
601          * This will open the modal that will show the full list of passed
602          * tests for the current results.
603          */
604         function openFullTestListModal() {
605             $uibModal.open({
606                 templateUrl: '/components/results-report/partials' +
607                         '/fullTestListModal.html',
608                 backdrop: true,
609                 windowClass: 'modal',
610                 animation: true,
611                 controller: 'FullTestListModalController as modal',
612                 size: 'lg',
613                 resolve: {
614                     tests: function () {
615                         return ctrl.resultsData.results;
616                     }
617                 }
618             });
619         }
620
621         /**
622          * This will open the modal that will all a user to edit test run
623          * metadata.
624          */
625         function openEditTestModal() {
626             $uibModal.open({
627                 templateUrl: '/components/results-report/partials' +
628                         '/editTestModal.html',
629                 backdrop: true,
630                 windowClass: 'modal',
631                 animation: true,
632                 controller: 'EditTestModalController as modal',
633                 size: 'lg',
634                 resolve: {
635                     resultsData: function () {
636                         return ctrl.resultsData;
637                     }
638                 }
639             });
640         }
641
642         getResults();
643     }
644
645     angular
646         .module('testapiApp')
647         .controller('FullTestListModalController', FullTestListModalController);
648
649     FullTestListModalController.$inject = ['$uibModalInstance', 'tests'];
650
651     /**
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.
655      */
656     function FullTestListModalController($uibModalInstance, tests) {
657         var ctrl = this;
658
659         ctrl.tests = tests;
660
661         /**
662          * This function will close/dismiss the modal.
663          */
664         ctrl.close = function () {
665             $uibModalInstance.dismiss('exit');
666         };
667
668         /**
669          * This function will return a string representing the sorted
670          * tests list separated by newlines.
671          */
672         ctrl.getTestListString = function () {
673             return ctrl.tests.sort().join('\n');
674         };
675     }
676
677     angular
678         .module('testapiApp')
679         .controller('EditTestModalController', EditTestModalController);
680
681     EditTestModalController.$inject = [
682         '$uibModalInstance', '$http', '$state', 'raiseAlert',
683         'testapiApiUrl', 'resultsData'
684     ];
685
686     /**
687      * Edit Test Modal Controller
688      * This controller is for the modal that appears if a user wants to edit
689      * test run metadata.
690      */
691     function EditTestModalController($uibModalInstance, $http, $state,
692         raiseAlert, testapiApiUrl, resultsData) {
693
694         var ctrl = this;
695
696         ctrl.getVersionList = getVersionList;
697         ctrl.getUserProducts = getUserProducts;
698         ctrl.associateProductVersion = associateProductVersion;
699         ctrl.getProductVersions = getProductVersions;
700         ctrl.saveChanges = saveChanges;
701
702         ctrl.resultsData = resultsData;
703         ctrl.metaCopy = angular.copy(resultsData.meta);
704         ctrl.prodVersionCopy = angular.copy(resultsData.product_version);
705
706         ctrl.getVersionList();
707         ctrl.getUserProducts();
708
709         /**
710          * Retrieve an array of available capability files from the TestAPI
711          * API server, sort this array reverse-alphabetically, and store it in
712          * a scoped variable.
713          * Sample API return array: ["2015.03.json", "2015.04.json"]
714          */
715         function getVersionList() {
716             if (ctrl.versionList) {
717                 return;
718             }
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');
726                 });
727         }
728
729         /**
730          * Get products user has management rights to or all products depending
731          * on the passed in parameter value.
732          */
733         function getUserProducts() {
734             var contentUrl = testapiApiUrl + '/products';
735             ctrl.productsRequest =
736                 $http.get(contentUrl).success(function (data) {
737                     ctrl.products = {};
738                     angular.forEach(data.products, function(prod) {
739                         if (prod.can_manage) {
740                             ctrl.products[prod.id] = prod;
741                         }
742                     });
743                     if (ctrl.prodVersionCopy) {
744                         ctrl.selectedProduct = ctrl.products[
745                             ctrl.prodVersionCopy.product_info.id
746                         ];
747                     }
748                     ctrl.getProductVersions();
749                 }).error(function (error) {
750                     ctrl.products = null;
751                     ctrl.showError = true;
752                     ctrl.error =
753                         'Error retrieving Products listing from server: ' +
754                         angular.toJson(error);
755                 });
756         }
757
758         /**
759          * Send a PUT request to the API server to associate a product with
760          * a test result.
761          */
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':
768                                                     verId})
769                 .error(function (error) {
770                     ctrl.showError = true;
771                     ctrl.showSuccess = false;
772                     ctrl.error =
773                         'Error associating product version with test run: ' +
774                         angular.toJson(error);
775                 });
776         }
777
778         /**
779          * Get all versions for a product.
780          */
781         function getProductVersions() {
782             if (!ctrl.selectedProduct) {
783                 ctrl.productVersions = [];
784                 ctrl.selectedVersion = null;
785                 return;
786             }
787
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;
797                     }
798                     else {
799                         angular.forEach(data, function(ver) {
800                             if (!ver.version) {
801                                 ctrl.selectedVersion = ver;
802                             }
803                         });
804                     }
805                 }).error(function (error) {
806                     raiseAlert('danger', error.title, error.detail);
807                 });
808         }
809
810         /**
811          * Send a PUT request to the server with the changes.
812          */
813         function saveChanges() {
814             ctrl.showError = false;
815             ctrl.showSuccess = false;
816             var metaBaseUrl = [
817                 testapiApiUrl, '/results/', resultsData.id, '/meta/'
818             ].join('');
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;
826                     if (meta[field]) {
827                         ctrl.assocRequest = $http.post(metaUrl, meta[field])
828                             .success(function(data) {
829                                 ctrl.resultsData.meta[field] = meta[field];
830                             })
831                             .error(function (error) {
832                                 ctrl.showError = true;
833                                 ctrl.showSuccess = false;
834                                 ctrl.error =
835                                     'Error associating metadata with ' +
836                                     'test run: ' + angular.toJson(error);
837                             });
838                     }
839                     else {
840                         ctrl.unassocRequest = $http.delete(metaUrl)
841                             .success(function (data) {
842                                 delete ctrl.resultsData.meta[field];
843                                 delete meta[field];
844                             })
845                             .error(function (error) {
846                                 ctrl.showError = true;
847                                 ctrl.showSuccess = false;
848                                 ctrl.error =
849                                     'Error associating metadata with ' +
850                                     'test run: ' + angular.toJson(error);
851                             });
852                     }
853                 }
854             });
855             ctrl.associateProductVersion();
856             if (!ctrl.showError) {
857                 ctrl.showSuccess = true;
858                 $state.reload();
859             }
860         }
861
862         /**
863          * This function will close/dismiss the modal.
864          */
865         ctrl.close = function () {
866             $uibModalInstance.dismiss('exit');
867         };
868     }
869 })();