From 426aa0231af6cb1911ac7b6fbd36062e338af05a Mon Sep 17 00:00:00 2001 From: SerenaFeng Date: Fri, 27 Oct 2017 16:58:49 +0800 Subject: remove components out side of 3rd_party components are implemented by TestAPI team, they are not the third party code, put them under opnfv_testapi/ui directory Change-Id: Ia0f5e2afe4bcb12bcb74d3a8d78fb28fe1432bec Signed-off-by: SerenaFeng --- testapi/opnfv_testapi/ui/about/about.html | 32 + .../ui/auth-failure/authFailureController.js | 33 + testapi/opnfv_testapi/ui/home/home.html | 23 + testapi/opnfv_testapi/ui/logout/logout.html | 1 + .../opnfv_testapi/ui/logout/logoutController.js | 44 ++ testapi/opnfv_testapi/ui/pods/pods.html | 76 ++ testapi/opnfv_testapi/ui/pods/podsController.js | 122 +++ .../ui/profile/importPubKeyModal.html | 27 + testapi/opnfv_testapi/ui/profile/profile.html | 44 ++ .../opnfv_testapi/ui/profile/profileController.js | 219 ++++++ .../opnfv_testapi/ui/profile/showPubKeyModal.html | 11 + .../ui/results-report/partials/editTestModal.html | 65 ++ .../results-report/partials/fullTestListModal.html | 13 + .../ui/results-report/partials/reportDetails.html | 87 +++ .../ui/results-report/resultsReport.html | 185 +++++ .../ui/results-report/resultsReportController.js | 869 +++++++++++++++++++++ testapi/opnfv_testapi/ui/results/results.html | 115 +++ .../opnfv_testapi/ui/results/resultsController.js | 370 +++++++++ 18 files changed, 2336 insertions(+) create mode 100644 testapi/opnfv_testapi/ui/about/about.html create mode 100644 testapi/opnfv_testapi/ui/auth-failure/authFailureController.js create mode 100644 testapi/opnfv_testapi/ui/home/home.html create mode 100644 testapi/opnfv_testapi/ui/logout/logout.html create mode 100644 testapi/opnfv_testapi/ui/logout/logoutController.js create mode 100644 testapi/opnfv_testapi/ui/pods/pods.html create mode 100644 testapi/opnfv_testapi/ui/pods/podsController.js create mode 100644 testapi/opnfv_testapi/ui/profile/importPubKeyModal.html create mode 100644 testapi/opnfv_testapi/ui/profile/profile.html create mode 100644 testapi/opnfv_testapi/ui/profile/profileController.js create mode 100644 testapi/opnfv_testapi/ui/profile/showPubKeyModal.html create mode 100644 testapi/opnfv_testapi/ui/results-report/partials/editTestModal.html create mode 100644 testapi/opnfv_testapi/ui/results-report/partials/fullTestListModal.html create mode 100644 testapi/opnfv_testapi/ui/results-report/partials/reportDetails.html create mode 100644 testapi/opnfv_testapi/ui/results-report/resultsReport.html create mode 100644 testapi/opnfv_testapi/ui/results-report/resultsReportController.js create mode 100644 testapi/opnfv_testapi/ui/results/results.html create mode 100644 testapi/opnfv_testapi/ui/results/resultsController.js (limited to 'testapi/opnfv_testapi') diff --git a/testapi/opnfv_testapi/ui/about/about.html b/testapi/opnfv_testapi/ui/about/about.html new file mode 100644 index 0000000..65860a8 --- /dev/null +++ b/testapi/opnfv_testapi/ui/about/about.html @@ -0,0 +1,32 @@ +

TestAPI Documentation

+ +

TestAPI is a source of tools for test results collection of OPNFV clouds.

+

To learn more about TestAPI, visit the links below.

+ +
    +
  1. + + About TestAPI +
  2. +
  3. + + How do I declare project in TestAPI +
  4. +
  5. + + How do I declare test case in TestAPI +
  6. +
  7. + + How do I push my results to TestAPI +
  8. +
+ diff --git a/testapi/opnfv_testapi/ui/auth-failure/authFailureController.js b/testapi/opnfv_testapi/ui/auth-failure/authFailureController.js new file mode 100644 index 0000000..29d1d70 --- /dev/null +++ b/testapi/opnfv_testapi/ui/auth-failure/authFailureController.js @@ -0,0 +1,33 @@ +/* + * 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('AuthFailureController', AuthFailureController); + + AuthFailureController.$inject = ['$location', '$state', 'raiseAlert']; + /** + * TestAPI Auth Failure Controller + * This controller handles messages from TestAPI API if user auth fails. + */ + function AuthFailureController($location, $state, raiseAlert) { + var ctrl = this; + ctrl.message = $location.search().message; + raiseAlert('danger', 'Authentication Failure:', ctrl.message); + $state.go('home'); + } +})(); diff --git a/testapi/opnfv_testapi/ui/home/home.html b/testapi/opnfv_testapi/ui/home/home.html new file mode 100644 index 0000000..47d747f --- /dev/null +++ b/testapi/opnfv_testapi/ui/home/home.html @@ -0,0 +1,23 @@ +
+ +
+

Results Collection

+

TestAPI is a source of tools for OPNFV test results collection

+
+
+
+ +
+
+

What is TestAPI?

+
    +
  • Toolset for testing interoperability between OPNFV test projects.
  • +
  • Database backed website supporting collection and publication of + community test results for OPNFV.
  • +
  • User interface to display individual test run results.
  • +
+
+
+ diff --git a/testapi/opnfv_testapi/ui/logout/logout.html b/testapi/opnfv_testapi/ui/logout/logout.html new file mode 100644 index 0000000..38a5c36 --- /dev/null +++ b/testapi/opnfv_testapi/ui/logout/logout.html @@ -0,0 +1 @@ +
diff --git a/testapi/opnfv_testapi/ui/logout/logoutController.js b/testapi/opnfv_testapi/ui/logout/logoutController.js new file mode 100644 index 0000000..1b6d78c --- /dev/null +++ b/testapi/opnfv_testapi/ui/logout/logoutController.js @@ -0,0 +1,44 @@ +/* + * 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('LogoutController', LogoutController); + + LogoutController.$inject = [ + '$location', '$window', '$timeout' + ]; + + /** + * TestAPI Logout Controller + * This controller handles logging out. In order to fully logout, the + * openstackid_session cookie must also be removed. The way to do that + * is to have the user's browser make a request to the openstackid logout + * page. We do this by placing the logout link as the src for an html + * image. After some time, the user is redirected home. + */ + function LogoutController($location, $window, $timeout) { + var ctrl = this; + + ctrl.openid_logout_url = $location.search().openid_logout; + var img = new Image(0, 0); + img.src = ctrl.openid_logout_url; + ctrl.redirectWait = $timeout(function() { + $window.location.href = '/'; + }, 500); + } +})(); diff --git a/testapi/opnfv_testapi/ui/pods/pods.html b/testapi/opnfv_testapi/ui/pods/pods.html new file mode 100644 index 0000000..22f2934 --- /dev/null +++ b/testapi/opnfv_testapi/ui/pods/pods.html @@ -0,0 +1,76 @@ +

Pods

+

This page is used to create or query pods.
+ Querying pods is open to everybody.
+ But only login users are granted the privilege to create the new pod. +

+ +
+ +
+ +
+

Filters

+
+
+ + +
+
+
+ +
+
+ +
+ + + + + + + + +
+ {{pod.name}} +
+

+ owner: {{pod.owner}}
+ role: {{pod.role}}
+ mode: {{pod.mode}}
+ create_date: {{pod.creation_date}}
+ details: {{pod.details}} +

+
+
+
+
+ diff --git a/testapi/opnfv_testapi/ui/pods/podsController.js b/testapi/opnfv_testapi/ui/pods/podsController.js new file mode 100644 index 0000000..489fa8a --- /dev/null +++ b/testapi/opnfv_testapi/ui/pods/podsController.js @@ -0,0 +1,122 @@ +/* + * 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('PodsController', PodsController); + + PodsController.$inject = [ + '$scope', '$http', '$filter', '$state', 'testapiApiUrl','raiseAlert' + ]; + + /** + * TestAPI Pods Controller + * This controller is for the '/pods' page where a user can browse + * through pods declared in TestAPI. + */ + function PodsController($scope, $http, $filter, $state, testapiApiUrl, + raiseAlert) { + var ctrl = this; + ctrl.url = testapiApiUrl + '/pods'; + + ctrl.create = create; + ctrl.update = update; + ctrl.open = open; + ctrl.clearFilters = clearFilters; + + ctrl.roles = ['community-ci', 'production-ci']; + ctrl.modes = ['metal', 'virtual']; + ctrl.createRequirements = [ + {label: 'name', type: 'text', required: true}, + {label: 'mode', type: 'select', selects: ctrl.modes}, + {label: 'role', type: 'select', selects: ctrl.roles}, + {label: 'details', type: 'textarea', required: false} + ]; + + ctrl.name = ''; + ctrl.role = 'community-ci'; + ctrl.mode = 'metal'; + ctrl.details = ''; + + /** + * 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.update(); + } + + /** + * This will contact the TestAPI to create a new pod. + */ + function create() { + ctrl.showError = false; + + if(ctrl.name != ""){ + var pods_url = ctrl.url; + var body = { + name: ctrl.name, + mode: ctrl.mode, + role: ctrl.role, + details: ctrl.details + }; + ctrl.podsRequest = + $http.post(pods_url, body).error(function (data, status) { + ctrl.showError = true; + if(status == 403){ + ctrl.error = + 'Error creating the new pod from server: Pod\'s name already exists' + } + }); + } + else{ + ctrl.showError = true; + ctrl.error = 'Name is missing.' + } + } + + /** + * This will contact the TestAPI to get a listing of declared pods. + */ + function update() { + ctrl.showError = false; + ctrl.podsRequest = + $http.get(ctrl.url).success(function (data) { + ctrl.data = data; + }).error(function (error) { + ctrl.data = null; + ctrl.showError = true; + ctrl.error = + 'Error retrieving pods from server: ' + + angular.toJson(error); + }); + } + } +})(); diff --git a/testapi/opnfv_testapi/ui/profile/importPubKeyModal.html b/testapi/opnfv_testapi/ui/profile/importPubKeyModal.html new file mode 100644 index 0000000..0f55c27 --- /dev/null +++ b/testapi/opnfv_testapi/ui/profile/importPubKeyModal.html @@ -0,0 +1,27 @@ + + diff --git a/testapi/opnfv_testapi/ui/profile/profile.html b/testapi/opnfv_testapi/ui/profile/profile.html new file mode 100644 index 0000000..763f5d1 --- /dev/null +++ b/testapi/opnfv_testapi/ui/profile/profile.html @@ -0,0 +1,44 @@ +

User profile

+
+
+ + + + + + + + + +
User {{auth.currentUser.user}}
Fullname {{auth.currentUser.fullname}}
Email {{auth.currentUser.email}}
Groups +
+ {{group}}
+
+
+
+
+
+
+
+

User Public Keys

+
+
+ +
+
+
+ +
+ + + + + + + + +
{{pubKey.format}}{{pubKey.shortKey}}{{pubKey.comment}}
+
+
diff --git a/testapi/opnfv_testapi/ui/profile/profileController.js b/testapi/opnfv_testapi/ui/profile/profileController.js new file mode 100644 index 0000000..5dbdf7b --- /dev/null +++ b/testapi/opnfv_testapi/ui/profile/profileController.js @@ -0,0 +1,219 @@ +/* + * + * 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') + .factory('PubKeys', PubKeys); + + PubKeys.$inject = ['$resource', 'testapiApiUrl']; + + /** + * This is a provider for the user's uploaded public keys. + */ + function PubKeys($resource, testapiApiUrl) { + return $resource(testapiApiUrl + '/user/pubkeys/:id', null, null); + } + + angular + .module('testapiApp') + .controller('ProfileController', ProfileController); + + ProfileController.$inject = [ + '$scope', '$http', 'testapiApiUrl', 'PubKeys', + '$uibModal', 'raiseAlert', '$state' + ]; + + /** + * TestAPI Profile Controller + * This controller handles user's profile page, where a user can view + * account-specific information. + */ + function ProfileController($scope, $http, testapiApiUrl, + PubKeys, $uibModal, raiseAlert, $state) { + + var ctrl = this; + + ctrl.updatePubKeys = updatePubKeys; + ctrl.openImportPubKeyModal = openImportPubKeyModal; + ctrl.openShowPubKeyModal = openShowPubKeyModal; + + // Must be authenticated to view this page. + if (!$scope.auth.isAuthenticated) { + $state.go('home'); + } + + /** + * This function will fetch all the user's public keys from the + * server and store them in an array. + */ + function updatePubKeys() { + var keys = PubKeys.query(function() { + ctrl.pubkeys = []; + angular.forEach(keys, function (key) { + ctrl.pubkeys.push({ + 'resource': key, + 'format': key.format, + 'shortKey': [ + key.pubkey.slice(0, 10), + '.', + key.pubkey.slice(-10) + ].join('.'), + 'pubkey': key.pubkey, + 'comment': key.comment + }); + }); + }); + } + + /** + * This function will open the modal that will give the user a form + * for importing a public key. + */ + function openImportPubKeyModal() { + $uibModal.open({ + templateUrl: '/components/profile/importPubKeyModal.html', + backdrop: true, + windowClass: 'modal', + controller: 'ImportPubKeyModalController as modal' + }).result.finally(function() { + ctrl.updatePubKeys(); + }); + } + + /** + * This function will open the modal that will give the full + * information regarding a specific public key. + * @param {Object} pubKey resource + */ + function openShowPubKeyModal(pubKey) { + $uibModal.open({ + templateUrl: '/components/profile/showPubKeyModal.html', + backdrop: true, + windowClass: 'modal', + controller: 'ShowPubKeyModalController as modal', + resolve: { + pubKey: function() { + return pubKey; + } + } + }).result.finally(function() { + ctrl.updatePubKeys(); + }); + } + + ctrl.authRequest = $scope.auth.doSignCheck().then(ctrl.updatePubKeys); + } + + angular + .module('testapiApp') + .controller('ImportPubKeyModalController', ImportPubKeyModalController); + + ImportPubKeyModalController.$inject = [ + '$uibModalInstance', 'PubKeys', 'raiseAlert' + ]; + + /** + * Import Pub Key Modal Controller + * This controller is for the modal that appears if a user wants to import + * a public key. + */ + function ImportPubKeyModalController($uibModalInstance, + PubKeys, raiseAlert) { + + var ctrl = this; + + ctrl.importPubKey = importPubKey; + ctrl.cancel = cancel; + + /** + * This function will save a new public key resource to the API server. + */ + function importPubKey() { + var newPubKey = new PubKeys( + {raw_key: ctrl.raw_key, self_signature: ctrl.self_signature} + ); + newPubKey.$save( + function(newPubKey_) { + raiseAlert('success', '', 'Public key saved successfully'); + $uibModalInstance.close(newPubKey_); + }, + function(httpResp) { + raiseAlert('danger', + httpResp.statusText, httpResp.data.title); + ctrl.cancel(); + } + ); + } + + /** + * This function will dismiss the modal. + */ + function cancel() { + $uibModalInstance.dismiss('cancel'); + } + } + + angular + .module('testapiApp') + .controller('ShowPubKeyModalController', ShowPubKeyModalController); + + ShowPubKeyModalController.$inject = [ + '$uibModalInstance', 'raiseAlert', 'pubKey' + ]; + + /** + * Show Pub Key Modal Controller + * This controller is for the modal that appears if a user wants to see the + * full details of one of their public keys. + */ + function ShowPubKeyModalController($uibModalInstance, raiseAlert, pubKey) { + var ctrl = this; + + ctrl.deletePubKey = deletePubKey; + ctrl.cancel = cancel; + + ctrl.pubKey = pubKey.resource; + ctrl.rawKey = [pubKey.format, pubKey.pubkey, pubKey.comment].join('\n'); + + /** + * This function will delete a public key resource. + */ + function deletePubKey() { + ctrl.pubKey.$remove( + {id: ctrl.pubKey.id}, + function() { + raiseAlert('success', + '', 'Public key deleted successfully'); + $uibModalInstance.close(ctrl.pubKey.id); + }, + function(httpResp) { + raiseAlert('danger', + httpResp.statusText, httpResp.data.title); + ctrl.cancel(); + } + ); + } + + /** + * This method will dismiss the modal. + */ + function cancel() { + $uibModalInstance.dismiss('cancel'); + } + } +})(); diff --git a/testapi/opnfv_testapi/ui/profile/showPubKeyModal.html b/testapi/opnfv_testapi/ui/profile/showPubKeyModal.html new file mode 100644 index 0000000..5f63a5e --- /dev/null +++ b/testapi/opnfv_testapi/ui/profile/showPubKeyModal.html @@ -0,0 +1,11 @@ + + diff --git a/testapi/opnfv_testapi/ui/results-report/partials/editTestModal.html b/testapi/opnfv_testapi/ui/results-report/partials/editTestModal.html new file mode 100644 index 0000000..583c9b9 --- /dev/null +++ b/testapi/opnfv_testapi/ui/results-report/partials/editTestModal.html @@ -0,0 +1,65 @@ + diff --git a/testapi/opnfv_testapi/ui/results-report/partials/fullTestListModal.html b/testapi/opnfv_testapi/ui/results-report/partials/fullTestListModal.html new file mode 100644 index 0000000..6db198b --- /dev/null +++ b/testapi/opnfv_testapi/ui/results-report/partials/fullTestListModal.html @@ -0,0 +1,13 @@ + diff --git a/testapi/opnfv_testapi/ui/results-report/partials/reportDetails.html b/testapi/opnfv_testapi/ui/results-report/partials/reportDetails.html new file mode 100644 index 0000000..517e569 --- /dev/null +++ b/testapi/opnfv_testapi/ui/results-report/partials/reportDetails.html @@ -0,0 +1,87 @@ + + + + + {{status | capitalize}} + + (Total: {{ctrl.caps[status].caps.length}} capabilities, {{ctrl.caps[status].count}} tests) + + ({{ctrl.testStatus | capitalize}}: {{ctrl.getStatusTestCount(status)}} tests) + + + + + +
    +
  1. + + + {{capability.id}} + + + [{{ctrl.getCapabilityTestCount(capability)}}] + + + [{{capability.passedTests.length}}/{{capability.passedTests.length + + capability.notPassedTests.length}}] + + +
      + +
    • + + + + + {{test}} + — + [Aliases] +
      +
      • {{alias}}
      +
      +
      +
    • + + + +
    • + + + + + {{test}} + — + [Aliases] +
      +
      • {{alias}}
      +
      +
      +
    • + +
    +
  2. +
+
diff --git a/testapi/opnfv_testapi/ui/results-report/resultsReport.html b/testapi/opnfv_testapi/ui/results-report/resultsReport.html new file mode 100644 index 0000000..5527121 --- /dev/null +++ b/testapi/opnfv_testapi/ui/results-report/resultsReport.html @@ -0,0 +1,185 @@ +

Test Run Results

+ +
+
+
+
+ Test ID: {{ctrl.testId}}
+
Cloud ID: {{ctrl.resultsData.cpid}}
+ Upload Date: {{ctrl.resultsData.created_at}} UTC
+ Duration: {{ctrl.resultsData.duration_seconds}} seconds
+ Total Number of Passed Tests: + + {{ctrl.resultsData.results.length}} + +
+
+
+ Publicly Shared: + Yes + No +
+
+
+ Product: + {{ctrl.resultsData.product_version.product_info.name}} + + ({{ctrl.resultsData.product_version.version}}) +
+
+
+ Associated Guideline: + {{ctrl.resultsData.meta.guideline.slice(0, -5)}} +
+
+ Associated Target Program: + {{ctrl.targetMappings[ctrl.resultsData.meta.target]}} +
+
+ Verified: + YES +
+
+
+ +
+
+ + +
+
+
+
+ +
+
+
+
+
+ +
+

See how these results stack up against Interop Working Group capabilities and OpenStack + target marketing programs. +

+ + +
+
+ Guideline Version: + + +
+
+ Target Program: + +
+
+ + +
+
+ Guideline Status: + {{ctrl.guidelineData.status | capitalize}} +
+ + Corresponding OpenStack Releases: +
    +
  • + {{release | capitalize}} +
  • +
+
+ +
+ Status: +

This cloud passes {{ctrl.requiredPassPercent | number:1}}% + ({{ctrl.caps.required.passedCount}}/{{ctrl.caps.required.count}}) + of the tests in the {{ctrl.version.slice(0, -5)}} required capabilities for the + {{ctrl.targetMappings[target]}} program.
+ Excluding flagged tests, this cloud passes + {{ctrl.nonFlagRequiredPassPercent | number:1}}% + ({{ctrl.nonFlagPassCount}}/{{ctrl.totalNonFlagCount}}) + of the required tests. +

+ +

Compliance with {{ctrl.version.slice(0, -5)}}: + + YES + NO + +

+ +
+

Capability Overview

+ + Test Filters:
+
+ + + + +
+ + + + + +
+ + +
+ + +
+ + +
+
+
+ +
+
+
+
+
+ + diff --git a/testapi/opnfv_testapi/ui/results-report/resultsReportController.js b/testapi/opnfv_testapi/ui/results-report/resultsReportController.js new file mode 100644 index 0000000..591ad40 --- /dev/null +++ b/testapi/opnfv_testapi/ui/results-report/resultsReportController.js @@ -0,0 +1,869 @@ +/* + * 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('ResultsReportController', ResultsReportController); + + ResultsReportController.$inject = [ + '$http', '$stateParams', '$window', + '$uibModal', 'testapiApiUrl', 'raiseAlert' + ]; + + /** + * TestAPI 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, testapiApiUrl, raiseAlert) { + + var ctrl = this; + + ctrl.getVersionList = getVersionList; + ctrl.getResults = getResults; + ctrl.isResultAdmin = isResultAdmin; + ctrl.isShared = isShared; + ctrl.shareTestRun = shareTestRun; + ctrl.deleteTestRun = deleteTestRun; + ctrl.updateVerificationStatus = updateVerificationStatus; + 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; + ctrl.openEditTestModal = openEditTestModal; + + /** The testID extracted from the URL route. */ + ctrl.testId = $stateParams.testID; + + /** The target OpenStack marketing program to compare against. */ + ctrl.target = 'platform'; + + /** Mappings of Interop WG 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 TestAPI + * 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 = testapiApiUrl + '/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 TestAPI 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 = testapiApiUrl + '/results/' + ctrl.testId; + ctrl.resultsRequest = + $http.get(content_url).success(function (data) { + ctrl.resultsData = data; + ctrl.version = ctrl.resultsData.meta.guideline; + ctrl.isVerified = ctrl.resultsData.verification_status; + 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 user has administrative + * privileges for the test result. + * @returns {Boolean} true if the user has admin privileges. + */ + function isResultAdmin() { + return Boolean(ctrl.resultsData && + (ctrl.resultsData.user_role === 'owner' || + ctrl.resultsData.user_role === 'foundation')); + } + /** + * 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 = [ + testapiApiUrl, '/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 = [ + testapiApiUrl, '/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 send a request to the API to delete the current + * test results set. + */ + function updateVerificationStatus() { + var content_url = [ + testapiApiUrl, '/results/', ctrl.testId + ].join(''); + var data = {'verification_status': ctrl.isVerified}; + ctrl.updateRequest = + $http.put(content_url, data).success( + function () { + ctrl.resultsData.verification_status = ctrl.isVerified; + raiseAlert('success', '', + 'Verification status changed!'); + }).error(function (error) { + ctrl.isVerified = ctrl.resultsData.verification_status; + raiseAlert('danger', error.title, error.detail); + }); + } + + /** + * This will contact the TestAPI 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 = testapiApiUrl + '/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': [] + }; + + // For cases where a capability listed in components is not + // in the capabilities object. + if (!(capId in ctrl.guidelineData.capabilities)) { + return cap; + } + + // 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 TestAPI 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 'Interop Working Group 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; + } + } + }); + } + + /** + * This will open the modal that will all a user to edit test run + * metadata. + */ + function openEditTestModal() { + $uibModal.open({ + templateUrl: '/components/results-report/partials' + + '/editTestModal.html', + backdrop: true, + windowClass: 'modal', + animation: true, + controller: 'EditTestModalController as modal', + size: 'lg', + resolve: { + resultsData: function () { + return ctrl.resultsData; + } + } + }); + } + + getResults(); + } + + angular + .module('testapiApp') + .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'); + }; + } + + angular + .module('testapiApp') + .controller('EditTestModalController', EditTestModalController); + + EditTestModalController.$inject = [ + '$uibModalInstance', '$http', '$state', 'raiseAlert', + 'testapiApiUrl', 'resultsData' + ]; + + /** + * Edit Test Modal Controller + * This controller is for the modal that appears if a user wants to edit + * test run metadata. + */ + function EditTestModalController($uibModalInstance, $http, $state, + raiseAlert, testapiApiUrl, resultsData) { + + var ctrl = this; + + ctrl.getVersionList = getVersionList; + ctrl.getUserProducts = getUserProducts; + ctrl.associateProductVersion = associateProductVersion; + ctrl.getProductVersions = getProductVersions; + ctrl.saveChanges = saveChanges; + + ctrl.resultsData = resultsData; + ctrl.metaCopy = angular.copy(resultsData.meta); + ctrl.prodVersionCopy = angular.copy(resultsData.product_version); + + ctrl.getVersionList(); + ctrl.getUserProducts(); + + /** + * 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() { + 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; + } + }); + if (ctrl.prodVersionCopy) { + ctrl.selectedProduct = ctrl.products[ + ctrl.prodVersionCopy.product_info.id + ]; + } + ctrl.getProductVersions(); + }).error(function (error) { + ctrl.products = null; + ctrl.showError = true; + ctrl.error = + 'Error retrieving Products listing from server: ' + + angular.toJson(error); + }); + } + + /** + * Send a PUT request to the API server to associate a product with + * a test result. + */ + function associateProductVersion() { + var verId = (ctrl.selectedVersion ? + ctrl.selectedVersion.id : null); + var testId = resultsData.id; + var url = testapiApiUrl + '/results/' + testId; + ctrl.associateRequest = $http.put(url, {'product_version_id': + verId}) + .error(function (error) { + ctrl.showError = true; + ctrl.showSuccess = false; + ctrl.error = + 'Error associating product version with test run: ' + + angular.toJson(error); + }); + } + + /** + * Get all versions for a product. + */ + function getProductVersions() { + if (!ctrl.selectedProduct) { + ctrl.productVersions = []; + ctrl.selectedVersion = null; + return; + } + + var url = testapiApiUrl + '/products/' + + ctrl.selectedProduct.id + '/versions'; + ctrl.getVersionsRequest = $http.get(url) + .success(function (data) { + ctrl.productVersions = data; + if (ctrl.prodVersionCopy && + ctrl.prodVersionCopy.product_info.id == + ctrl.selectedProduct.id) { + ctrl.selectedVersion = ctrl.prodVersionCopy; + } + else { + angular.forEach(data, function(ver) { + if (!ver.version) { + ctrl.selectedVersion = ver; + } + }); + } + }).error(function (error) { + raiseAlert('danger', error.title, error.detail); + }); + } + + /** + * Send a PUT request to the server with the changes. + */ + function saveChanges() { + ctrl.showError = false; + ctrl.showSuccess = false; + var metaBaseUrl = [ + testapiApiUrl, '/results/', resultsData.id, '/meta/' + ].join(''); + var metaFields = ['target', 'guideline', 'shared']; + var meta = ctrl.metaCopy; + angular.forEach(metaFields, function(field) { + var oldMetaValue = (field in ctrl.resultsData.meta) ? + ctrl.resultsData.meta[field] : ''; + if (field in meta && oldMetaValue != meta[field]) { + var metaUrl = metaBaseUrl + field; + if (meta[field]) { + ctrl.assocRequest = $http.post(metaUrl, meta[field]) + .success(function(data) { + ctrl.resultsData.meta[field] = meta[field]; + }) + .error(function (error) { + ctrl.showError = true; + ctrl.showSuccess = false; + ctrl.error = + 'Error associating metadata with ' + + 'test run: ' + angular.toJson(error); + }); + } + else { + ctrl.unassocRequest = $http.delete(metaUrl) + .success(function (data) { + delete ctrl.resultsData.meta[field]; + delete meta[field]; + }) + .error(function (error) { + ctrl.showError = true; + ctrl.showSuccess = false; + ctrl.error = + 'Error associating metadata with ' + + 'test run: ' + angular.toJson(error); + }); + } + } + }); + ctrl.associateProductVersion(); + if (!ctrl.showError) { + ctrl.showSuccess = true; + $state.reload(); + } + } + + /** + * This function will close/dismiss the modal. + */ + ctrl.close = function () { + $uibModalInstance.dismiss('exit'); + }; + } +})(); diff --git a/testapi/opnfv_testapi/ui/results/results.html b/testapi/opnfv_testapi/ui/results/results.html new file mode 100644 index 0000000..2ae5339 --- /dev/null +++ b/testapi/opnfv_testapi/ui/results/results.html @@ -0,0 +1,115 @@ +

{{ctrl.pageHeader}}

+

{{ctrl.pageParagraph}}

+
+

Upload Results

+
+ +
+
+ +
+
+ +
+
+{{ctrl.uploadState}} +
+
+
+
+

Filters

+
+
+ +

+ + + + +

+
+
+ +

+ + + + +

+
+
+ + +
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDPodProjectTest CaseInstallerVersionScenarioCriteriaStart DateStop Date
{{ result._id }}{{ result.pod_name }}{{ result.project_name }}{{ result.case_name }}{{ result.installer }}{{ result.version }}{{ result.scenario }}{{ result.criteria }}{{ result.start_date }}{{ result.stop_date }}
+ +
+ + +
+
+ + diff --git a/testapi/opnfv_testapi/ui/results/resultsController.js b/testapi/opnfv_testapi/ui/results/resultsController.js new file mode 100644 index 0000000..cc6cc0b --- /dev/null +++ b/testapi/opnfv_testapi/ui/results/resultsController.js @@ -0,0 +1,370 @@ +/* + * 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); + + angular + .module('testapiApp') + .directive('fileModel', ['$parse', function ($parse) { + return { + restrict: 'A', + link: function(scope, element, attrs) { + var model = $parse(attrs.fileModel); + var modelSetter = model.assign; + + element.bind('change', function(){ + scope.$apply(function(){ + modelSetter(scope, element[0].files[0]); + }); + }); + } + }; + }]); + + 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.uploadFile=uploadFile; + ctrl.update = update; + ctrl.open = open; + ctrl.clearFilters = clearFilters; + ctrl.associateMeta = associateMeta; + ctrl.getVersionList = getVersionList; + ctrl.getUserProducts = getUserProducts; + 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'; + // need auth to browse + 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.'; + + ctrl.uploadState = ''; + + ctrl.isPublic = false; + + if (ctrl.isUserResults) { + ctrl.authRequest = $scope.auth.doSignCheck() + .then(ctrl.update); + // ctrl.getUserProducts(); + } else { + ctrl.update(); + } + + + function uploadFileToUrl(file, uploadUrl){ + var fd = new FormData(); + fd.append('file', file); + fd.append('public', ctrl.isPublic) + + $http.post(uploadUrl, fd, { + transformRequest: angular.identity, + headers: {'Content-Type': undefined} + }) + + .success(function(data){ + var id = data.href.substr(data.href.lastIndexOf('/')+1); + ctrl.uploadState = "Upload succeed. Result id is " + id; + ctrl.update(); + }) + + .error(function(data, status){ + ctrl.uploadState = "Upload failed. Error code is " + status; + }); + } + + function uploadFile(){ + var file = $scope.resultFile; + console.log('file is ' ); + console.dir(file); + + var uploadUrl = testapiApiUrl + "/results/upload"; + uploadFileToUrl(file, uploadUrl); + }; + + /** + * 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 + '&from=' + start + ' 00:00:00'; + } + var end = $filter('date')(ctrl.endDate, 'yyyy-MM-dd'); + if (end) { + content_url = content_url + '&to=' + 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); + }); + } + + /** + * 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); + } + + } +})(); -- cgit 1.2.3-korg