diff options
11 files changed, 902 insertions, 1 deletions
diff --git a/testapi/opnfv_testapi/handlers/deploy_result_handlers.py b/testapi/opnfv_testapi/handlers/deploy_result_handlers.py index 973bfef..a8fcd88 100644 --- a/testapi/opnfv_testapi/handlers/deploy_result_handlers.py +++ b/testapi/opnfv_testapi/handlers/deploy_result_handlers.py @@ -1,6 +1,7 @@ from opnfv_testapi.handlers import result_handlers from opnfv_testapi.models import deploy_result_models from opnfv_testapi.tornado_swagger import swagger +from bson import objectid class GenericDeployResultHandler(result_handlers.GenericResultHandler): @@ -113,3 +114,17 @@ class DeployResultsHandler(GenericDeployResultHandler): self._create(miss_fields=miss_fields, carriers=carriers, values_check=values_check) + + +class DeployResultHandler(GenericDeployResultHandler): + @swagger.operation(nickname='getTestDeployResultById') + def get(self, result_id): + """ + @description: get a single deploy result by result_id + @rtype: L{DeployResult} + @return 200: Deploy result exist + @raise 404: Deploy result not exist + """ + query = dict() + query["_id"] = objectid.ObjectId(result_id) + self._get_one(query=query) diff --git a/testapi/opnfv_testapi/router/url_mappings.py b/testapi/opnfv_testapi/router/url_mappings.py index a857725..b9dd231 100644 --- a/testapi/opnfv_testapi/router/url_mappings.py +++ b/testapi/opnfv_testapi/router/url_mappings.py @@ -52,6 +52,7 @@ mappings = [ (r'/api/v1/results/upload', result_handlers.ResultsUploadHandler), (r"/api/v1/results/([^/]+)", result_handlers.ResultsGURHandler), (r"/api/v1/deployresults", deploy_handlers.DeployResultsHandler), + (r"/api/v1/deployresults/([^/]+)", deploy_handlers.DeployResultHandler), # scenarios (r"/api/v1/scenarios", scenario_handlers.ScenariosCLHandler), diff --git a/testapi/opnfv_testapi/tests/UI/e2e/deployResultsControllerSpec.js b/testapi/opnfv_testapi/tests/UI/e2e/deployResultsControllerSpec.js new file mode 100644 index 0000000..e00243b --- /dev/null +++ b/testapi/opnfv_testapi/tests/UI/e2e/deployResultsControllerSpec.js @@ -0,0 +1,397 @@ +'use strict'; + +var mock = require('protractor-http-mock'); +var baseURL = "http://localhost:8000/" + +describe('testing the result page for anonymous user', function () { + beforeEach(function(){ + mock([ + { + request: { + path: '/api/v1/pods', + method: 'GET' + }, + response: { + data: { + pods: [{role: "community-ci", name: "test", owner: "testUser", + details: "DemoDetails", mode: "metal", _id: "59f02f099a07c84bfc5c7aed", + creation_date: "2017-10-25 11:58:25.926168"}] + } + } + }, + { + request: { + path: '/api/v1/deployresults', + method: 'GET', + queryString: { + page: '1' + } + }, + response: { + data: { + "pagination": { + "current_page": 1, + "total_pages": 1 + }, + "deployresults": [ + { + "build_id": 411, + "upstream_build_id": 184, + "scenario": "os-nosdn-nofeature-ha", + "stop_date": "2018-01-2723:21:31.3N", + "start_date": "2018-01-2723:21:28.3N", + "upstream_job_name": "daisy-os-nosdn-nofeature-ha-baremetal-daily-master", + "version": "master", + "pod_name": "zte-pod", + "criteria": "PASS", + "installer": "daisy", + "_id": "5a6dc1089a07c80f3c9f8d62", + "job_name": "daisy-deploy-baremetal-daily-master", + "details": null + } + ] + } + } + }, + { + request: { + path: '/api/v1/deployresults', + method: 'GET', + queryString: { + page: '1', + installer: 'daisy' + } + }, + response: { + data: { + "pagination": { + "current_page": 1, + "total_pages": 1 + }, + "deployresults": [ + { + "build_id": 411, + "upstream_build_id": 184, + "scenario": "os-nosdn-nofeature-ha", + "stop_date": "2018-01-2723:21:31.3N", + "start_date": "2018-01-2723:21:28.3N", + "upstream_job_name": "daisy-os-nosdn-nofeature-ha-baremetal-daily-master", + "version": "master", + "pod_name": "zte-pod", + "criteria": "PASS", + "installer": "daisy", + "_id": "5a6dc1089a07c80f3c9f8d63", + "job_name": "daisy-deploy-baremetal-daily-master", + "details": null + } + ] + } + } + }, + { + request: { + path: '/api/v1/deployresults', + method: 'GET', + queryString: { + page: '1', + installer: 'daisy', + job_name: 'daisy-deploy-baremetal-daily-master' + } + }, + response: { + data: { + "pagination": { + "current_page": 1, + "total_pages": 1 + }, + "deployresults": [ + { + "build_id": 411, + "upstream_build_id": 184, + "scenario": "os-nosdn-nofeature-ha", + "stop_date": "2018-01-2723:21:31.3N", + "start_date": "2018-01-2723:21:28.3N", + "upstream_job_name": "daisy-os-nosdn-nofeature-ha-baremetal-daily-master", + "version": "master", + "pod_name": "zte-pod", + "criteria": "PASS", + "installer": "daisy", + "_id": "5a6dc1089a07c80f3c9f8d64", + "job_name": "daisy-deploy-baremetal-daily-master", + "details": null + } + ] + } + } + } + ]); + }); + + afterEach(function(){ + mock.teardown(); + }); + + it( 'should show the deploy results page for anonymous user', function() { + browser.get(baseURL+"#/deployresults"); + expect(element(by.cssContainingText(".ng-binding.ng-scope","Deploy Results")).isDisplayed()).toBe(true); + }); + + it( 'navigate anonymous user to results page', function() { + browser.get(baseURL); + var resultLink = element(by.linkText('Deploy Results')).click(); + var EC = browser.ExpectedConditions; + browser.wait(EC.urlContains(baseURL+ '#/deployresults'), 10000); + }); + + it('Should show the results in results page for anonymous user ', function () { + browser.get(baseURL+"#/deployresults"); + var row = element.all(by.repeater('(index, result) in ctrl.data.deployresults')).first(); + var cells = row.all(by.tagName('td')); + expect(cells.get(0).getText()).toContain("3c9f8d62"); + }); + + it('Should show the results in results page related to the filters for anonymous user ', function () { + browser.get(baseURL+"#/deployresults"); + var filter = element(by.model('ctrl.filter')); + var filterText = element(by.model('ctrl.filterText')); + filter.sendKeys('installer'); + filterText.sendKeys('daisy'); + var buttonFilter = element(by.buttonText('Filter')); + buttonFilter.click(); + var row = element.all(by.repeater('(index, result) in ctrl.data.deployresults')).first(); + var cells = row.all(by.tagName('td')); + expect(cells.get(0).getText()).toContain("3c9f8d63"); + filter.sendKeys('job_name'); + filterText.sendKeys('daisy-deploy-baremetal-daily-master') + buttonFilter.click(); + expect(cells.get(0).getText()).toContain("3c9f8d64"); + }); + it('Should not show the results in results page related to the filters for anonymous user ', function () { + browser.get(baseURL+"#/deployresults"); + var filter = element(by.model('ctrl.filter')); + var filterText = element(by.model('ctrl.filterText')); + filter.sendKeys('installer'); + filterText.sendKeys('daisyl'); + var buttonFilter = element(by.buttonText('Filter')); + buttonFilter.click(); + expect(element(by.css('.alert.alert-danger.ng-binding.ng-scope')) + .isDisplayed()).toBe(true); + }); + +}); + +describe('testing the result page for user', function () { + beforeEach(function(){ + mock([ + { + request: { + path: '/api/v1/pods', + method: 'GET' + }, + response: { + data: { + pods: [{role: "community-ci", name: "test", owner: "testUser", + details: "DemoDetails", mode: "metal", _id: "59f02f099a07c84bfc5c7aed", + creation_date: "2017-10-25 11:58:25.926168"}] + } + } + }, + { + request: { + path: '/api/v1/profile', + method: 'GET' + }, + response: { + data: { + "fullname": "Test User", "_id": "79f82eey9a00c84bfhc7aed", + "user": "testUser", "groups": ["opnfv-testapi-users"], + "email": "testuser@test.com" + } + } + }, + { + request: { + path: '/api/v1/deployresults', + method: 'GET', + queryString: { + page: '1' + } + }, + response: { + data: { + "pagination": { + "current_page": 1, + "total_pages": 1 + }, + "deployresults": [ + { + "build_id": 411, + "upstream_build_id": 184, + "scenario": "os-nosdn-nofeature-ha", + "stop_date": "2018-01-2723:21:31.3N", + "start_date": "2018-01-2723:21:28.3N", + "upstream_job_name": "daisy-os-nosdn-nofeature-ha-baremetal-daily-master", + "version": "master", + "pod_name": "zte-pod", + "criteria": "PASS", + "installer": "daisy", + "_id": "5a6dc1089a07c80f3c9f8d62", + "job_name": "daisy-deploy-baremetal-daily-master", + "details": null + } + ] + } + } + }, + { + request: { + path: '/api/v1/deployresults', + method: 'GET', + queryString: { + page: '1', + installer: 'daisy' + } + }, + response: { + data: { + "pagination": { + "current_page": 1, + "total_pages": 1 + }, + "deployresults": [ + { + "build_id": 411, + "upstream_build_id": 184, + "scenario": "os-nosdn-nofeature-ha", + "stop_date": "2018-01-2723:21:31.3N", + "start_date": "2018-01-2723:21:28.3N", + "upstream_job_name": "daisy-os-nosdn-nofeature-ha-baremetal-daily-master", + "version": "master", + "pod_name": "zte-pod", + "criteria": "PASS", + "installer": "daisy", + "_id": "5a6dc1089a07c80f3c9f8d63", + "job_name": "daisy-deploy-baremetal-daily-master", + "details": null + } + ] + } + } + }, + { + request: { + path: '/api/v1/deployresults', + method: 'GET', + queryString: { + page: '1', + installer: 'daisy', + job_name : 'daisy-deploy-baremetal-daily-master' + } + }, + response: { + data: { + "pagination": { + "current_page": 1, + "total_pages": 1 + }, + "deployresults": [ + { + "build_id": 411, + "upstream_build_id": 184, + "scenario": "os-nosdn-nofeature-ha", + "stop_date": "2018-01-2723:21:31.3N", + "start_date": "2018-01-2723:21:28.3N", + "upstream_job_name": "daisy-os-nosdn-nofeature-ha-baremetal-daily-master", + "version": "master", + "pod_name": "zte-pod", + "criteria": "PASS", + "installer": "daisy", + "_id": "5a6dc1089a07c80f3c9f8d64", + "job_name": "daisy-deploy-baremetal-daily-master", + "details": null + } + ] + } + } + } + ]); + }); + + afterEach(function(){ + mock.teardown(); + }); + + it( 'should show the deploy results page for user', function() { + browser.get(baseURL+"#/deployresults"); + expect(element(by.cssContainingText(".ng-binding.ng-scope","Deploy Results")).isDisplayed()).toBe(true); + }); + + it( 'navigate user to results page', function() { + browser.get(baseURL); + var resultLink = element(by.linkText('Deploy Results')).click(); + var EC = browser.ExpectedConditions; + browser.wait(EC.urlContains(baseURL+ '#/deployresults'), 10000); + }); + + it('Should show the results in results page for user ', function () { + browser.get(baseURL+"#/deployresults"); + var row = element.all(by.repeater('(index, result) in ctrl.data.deployresults')).first(); + var cells = row.all(by.tagName('td')); + expect(cells.get(0).getText()).toContain("3c9f8d62"); + }); + + it('Should show the results in results page related to the filters for user ', function () { + browser.get(baseURL+"#/deployresults"); + var filter = element(by.model('ctrl.filter')); + var filterText = element(by.model('ctrl.filterText')); + filter.sendKeys('installer'); + filterText.sendKeys('daisy'); + var buttonFilter = element(by.buttonText('Filter')); + buttonFilter.click(); + var row = element.all(by.repeater('(index, result) in ctrl.data.deployresults')).first(); + var cells = row.all(by.tagName('td')); + expect(cells.get(0).getText()).toContain("3c9f8d63"); + filter.sendKeys('job_name'); + filterText.sendKeys('daisy-deploy-baremetal-daily-master') + buttonFilter.click(); + expect(cells.get(0).getText()).toContain("3c9f8d64"); + }); + it('Should not show the results in results page related to the filters for user ', function () { + browser.get(baseURL+"#/deployresults"); + var filter = element(by.model('ctrl.filter')); + var filterText = element(by.model('ctrl.filterText')); + filter.sendKeys('installer'); + filterText.sendKeys('daisy1'); + var buttonFilter = element(by.buttonText('Filter')); + buttonFilter.click(); + expect(element(by.css('.alert.alert-danger.ng-binding.ng-scope')) + .isDisplayed()).toBe(true); + }); + + it('Clear the filter', function () { + browser.get(baseURL+"#/deployresults"); + var filter = element(by.model('ctrl.filter')); + var filterText = element(by.model('ctrl.filterText')); + filter.sendKeys('installer'); + filterText.sendKeys('daisy'); + var buttonFilter = element(by.buttonText('Filter')); + buttonFilter.click(); + var row = element.all(by.repeater('(index, result) in ctrl.data.deployresults')).first(); + var cells = row.all(by.tagName('td')); + expect(cells.get(0).getText()).toContain("3c9f8d63"); + var buttonClear = element(by.buttonText('Clear')); + buttonClear.click(); + var row = element.all(by.repeater('(index, result) in ctrl.data.deployresults')).first(); + var cells = row.all(by.tagName('td')); + expect(cells.get(0).getText()).toContain("3c9f8d62"); + }); + + it('view the deploy results ', function () { + browser.get(baseURL+"#/deployresults"); + var viewOperation = element(by.linkText('3c9f8d62')) + viewOperation.click(); + var EC = browser.ExpectedConditions; + browser.wait(EC.urlContains('#/deployresults/5a6dc1089a07c80f3c9f8d62'), 10000); + }); + +});
\ No newline at end of file diff --git a/testapi/opnfv_testapi/ui/Gruntfile.js b/testapi/opnfv_testapi/ui/Gruntfile.js index e335356..41a2ec5 100644 --- a/testapi/opnfv_testapi/ui/Gruntfile.js +++ b/testapi/opnfv_testapi/ui/Gruntfile.js @@ -122,7 +122,8 @@ module.exports = function (grunt) { '../tests/UI/e2e/testCasesControllerSpec.js', '../tests/UI/e2e/resultsControllerSpec.js', '../tests/UI/e2e/scenariosControllerSpec.js', - '../tests/UI/e2e/scenarioControllerSpec.js' + '../tests/UI/e2e/scenarioControllerSpec.js', + '../tests/UI/e2e/deployResultsControllerSpec.js' ] } }, diff --git a/testapi/opnfv_testapi/ui/app.js b/testapi/opnfv_testapi/ui/app.js index c2fa774..ae50166 100644 --- a/testapi/opnfv_testapi/ui/app.js +++ b/testapi/opnfv_testapi/ui/app.js @@ -104,6 +104,16 @@ templateUrl: 'testapi-ui/components/results/result/result.html', controller: 'ResultController as ctrl' }). + state('deployresults', { + url: '/deployresults', + templateUrl: 'testapi-ui/components/deploy-results/deployResults.html', + controller: 'DeployResultsController as ctrl' + }). + state('deployresult', { + url: '/deployresults/:_id', + templateUrl: 'testapi-ui/components/deploy-results/deploy-result/deployResult.html', + controller: 'DeployResultController as ctrl' + }). state('profile', { url: '/profile', templateUrl: 'testapi-ui/components/profile/profile.html', diff --git a/testapi/opnfv_testapi/ui/components/deploy-results/deploy-result/deployResult.html b/testapi/opnfv_testapi/ui/components/deploy-results/deploy-result/deployResult.html new file mode 100644 index 0000000..ba9bee7 --- /dev/null +++ b/testapi/opnfv_testapi/ui/components/deploy-results/deploy-result/deployResult.html @@ -0,0 +1,93 @@ +<legend>Result</legend> +<div style="padding-right:0px"> + <div class="table-responsive"> + <table class="table" ng-data="ctrl.data.pods"> + <tbody> + <tr style="padding:9px"> + <td class="podsTableTd">Id :</td> + <td class="podsTableLeftTd">{{ctrl.data._id}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Installer :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.installer}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Scenario :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.scenario}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Pod Name :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.pod_name}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Criteria :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.criteria}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Start Date :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.start_date}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Stop Date :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.stop_date}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Job Name :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.job_name}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Build ID :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data['build_id']}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Upstream Job Name :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.upstream_job_name}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Upstream Build ID :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.upstream_build_id}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Details :</td> + <td width="90%" class="podsTableLeftTd"> + <a ng-click="ctrl.showDetails()"> + <p ng-if="ctrl.details">Hide</p> + <p ng-if="!ctrl.details">Show</p> + </a> + <table class="table" ng-class="{'hidden' : !ctrl.details}" style="margin:10px"> + <tbody> + <tr style="padding:9px"></tr> + <tr style="padding:9px"> + <td class="podsTableTd">Failures :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.details.failures}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Details :</td> + <td width="90%" class="podsTableLeftTd">{{ctrl.data.details.errors}}</td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">Stream :</td> + <td width="90%" class="podsTableLeftTd"><p>{{ctrl.data.details.stream}}</p></td> + </tr> + <tr style="padding:9px"> + <td class="podsTableTd">TestsRun :</td> + <td width="90%" class="podsTableLeftTd"><p>{{ctrl.data.details.testsRun}}</p></td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + </div> +</div> +<div class="col-md-12"> + <div ng-show="ctrl.showError" class="col-md-12 alert alert-danger" role="alert"> + <span class="pull-right"> {{ctrl.error}}</span> + <span class="glyphicon glyphicon-exclamation-sign pull-right" aria-hidden="true" >Error:</span> + </div> + <div ng-show="ctrl.showSuccess" class="col-md-12 alert alert-success" role="alert"> + <span class="pull-right"> {{ctrl.success}}</span> + <span class="glyphicon glyphicon-ok pull-right" aria-hidden="true"></span> + </div> + </div>
\ No newline at end of file diff --git a/testapi/opnfv_testapi/ui/components/deploy-results/deploy-result/deployResultController.js b/testapi/opnfv_testapi/ui/components/deploy-results/deploy-result/deployResultController.js new file mode 100644 index 0000000..40cf1cb --- /dev/null +++ b/testapi/opnfv_testapi/ui/components/deploy-results/deploy-result/deployResultController.js @@ -0,0 +1,65 @@ +/* + * 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('DeployResultController', DeployResultController); + + DeployResultController.$inject = [ + '$scope', '$http', '$filter', '$state', '$window', '$uibModal', 'testapiApiUrl','raiseAlert', + 'confirmModal' + ]; + + /** + * TestAPI DeployResultController + * This controller is for the '/result/:_id' page where a user can browse + * through result declared in TestAPI. + */ + function DeployResultController($scope, $http, $filter, $state, $window, $uibModal, testapiApiUrl, + raiseAlert, confirmModal) { + var ctrl = this; + ctrl.url = testapiApiUrl + '/deployresults'; + ctrl._id = $state.params['_id']; + ctrl.loadDetails = loadDetails + ctrl.showDetails = showDetails + + /** + *Contact the testapi and retrevie the result details + */ + function loadDetails(){ + var resultUrl = ctrl.url + '/' + ctrl._id; + ctrl.showError = false; + ctrl.podsRequest = + $http.get(resultUrl).success(function (data) { + ctrl.data = data; + }).catch(function (error) { + ctrl.data = null; + ctrl.showError = true; + ctrl.error = error.statusText; + }); + } + + function showDetails(){ + if(ctrl.details){ + ctrl.details = false + }else{ + ctrl.details = true + } + } + ctrl.loadDetails(); + } +})();
\ No newline at end of file diff --git a/testapi/opnfv_testapi/ui/components/deploy-results/deployResults.html b/testapi/opnfv_testapi/ui/components/deploy-results/deployResults.html new file mode 100644 index 0000000..6fbaaea --- /dev/null +++ b/testapi/opnfv_testapi/ui/components/deploy-results/deployResults.html @@ -0,0 +1,126 @@ +<h3>{{ctrl.pageHeader}}</h3> +<p>{{ctrl.pageParagraph}}</p> +<div class="row" style="margin-bottom:24px;"></div> +<div class="result-filters" style="border-top: none;"> + <div class="row podTable" style="vertical-align:middle"> + <div class="col-sm-1 pull-right"> + <button type="button" class="btn btn-danger" ng-click="ctrl.clearFilters()"> + <i class="fa fa-search"></i> Clear + </button> + </div> + <div class="col-sm-1 pull-right"> + <button type="button" class="btn btn-success" ng-click="ctrl.filterList()"> + <i class="fa fa-search"></i> Filter</button> + </div> + <div class="col-sm-2 pull-right" ng-class="{'hidden': ctrl.filter=='start_date' || ctrl.filter=='end_date'}"> + <span style="margin-top:6px">Search: </span> + <input list="filter" name="filter" class="form-control search" style="display:inline;width:105px;padding-left:6px;" + ng-Model="ctrl.filterText" placeholder="Search String"> + <datalist id="filter" ng-class="{ 'hidden' : ctrl.filterOption.length<0}"> + <option ng-repeat="(index, filterValue) in ctrl.filterOption " value="{{filterValue}}">{{filterValue}}</option> + </datalist> + </div> + <div class="col-sm-3 pull-right" style="width:20%" ng-class="{'hidden': ctrl.filter!='start_date'}"> + <span style="margin-top:6px">Start Date: </span> + <p class="input-group" style="width:48%;display:inline-flex;"> + <input type="text" class="form-control" + uib-datepicker-popup="{{ctrl.format}}" + ng-model="ctrl.filterText" is-open="ctrl.startOpen" + close-text="Close" /> + <span class="input-group-btn"> + <button type="button" class="btn btn-default" ng-click="ctrl.open($event, 'startOpen')"> + <i class="glyphicon glyphicon-calendar"></i> + </button> + </span> + </p> + </div> + <div class="col-sm-3 pull-right" style="width:20%" ng-class="{'hidden': ctrl.filter!='end_date'}"> + <span style="margin-top:6px">End Date: </span> + <p class="input-group" style="width:48%;display:inline-flex;"> + <input type="text" class="form-control" + uib-datepicker-popup="{{ctrl.format}}" + ng-model="ctrl.filterText" is-open="ctrl.endOpen" + close-text="Close" /> + <span class="input-group-btn"> + <button type="button" class="btn btn-default" ng-click="ctrl.open($event, 'endOpen')"> + <i class="glyphicon glyphicon-calendar"></i> + </button> + </span> + </p> + </div> + <div class="col-md-2 row pull-right" style="width: 20%;"> + <span style="margin-top:6px">Filter: </span> + <select ng-model="ctrl.filter" ng-change="ctrl.encodeFilter()" class="form-control" style="display:inline; width:150px;"> + <option value="pod" ng-disabled="ctrl.testFilter('pod')" >Pod Name</option> + <option value="job_name" ng-disabled="ctrl.testFilter('job_name')" >Job Name</option> + <option value="installer" ng-disabled="ctrl.testFilter('installer')">Installer</option> + <option value="version" ng-disabled="ctrl.testFilter('version')">Version</option> + <option value="scenario" ng-disabled="ctrl.testFilter('scenario')">Scenario</option> + <option value="build_id" ng-disabled="ctrl.testFilter('build_id')">Build ID</option> + <option value="criteria" ng-disabled="ctrl.testFilter('criteria')">Criteria</option> + <option value="start_date" ng-disabled="ctrl.testFilter('start_date')">Start Date</option> + <option value="end_date" ng-disabled="ctrl.testFilter('end_date')">End Date</option> + </select> + </div> + <div class='filter-box'> + <div class='filter-tag col-md-1' ng-repeat="(key, tag) in ctrl.tagArray" style="background-color: #f5f5f5;border: 1px solid #e3e3e3;/* border: 1px; */margin-top: 3px;padding: 4px;margin-left: 15px;width: 13%;"> + {{key}} : {{tag}} + <div class='delete-tag btn btn-danger btn-xs' ng-click='ctrl.deleteTag(key)'> + × + </div> + </div> + </div> + </div> +</div> + +<div cg-busy="{promise:ctrl.authRequest,message:'Loading'}"></div> +<div cg-busy="{promise:ctrl.resultsRequest,message:'Loading'}"></div> +<div ng-show="ctrl.data" class="results-table"> + <table ng-data="ctrl.data.deployresults" ng-show="ctrl.data" class="table table-striped table-hover"> + <thead> + <tr> + <th>ID</th> + <th>Installer</th> + <th>Scenario</th> + <th>Pod</th> + <th>Criteria</th> + <th>Start Date</th> + <th>Stop Date</th> + </tr> + </thead> + + <tbody> + <tr ng-repeat-start="(index, result) in ctrl.data.deployresults"> + <td><a ng-click="ctrl.viewDeployResult(result._id)">{{ result._id.substr(-8) }}</a></td> + <td>{{ result.installer }}</td> + <td>{{ result.scenario }}</td> + <td>{{ result.pod_name }}</td> + <td>{{ result.criteria }}</td> + <td>{{ result.start_date }}</td> + <td>{{ result.stop_date }}</td> + </tr> + <tr ng-repeat-end=> + </tr> + </tbody> + </table> + + <div class="pages"> + <uib-pagination + total-items="ctrl.totalItems" + ng-model="ctrl.currentPage" + items-per-page="ctrl.itemsPerPage" + max-size="ctrl.maxSize" + class="pagination-sm" + boundary-links="true" + rotate="false" + num-pages="ctrl.numPages" + ng-change="ctrl.filterList()"> + </uib-pagination> + </div> +</div> + +<div ng-show="ctrl.showError" class="alert alert-danger" role="alert"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> + <span class="sr-only">Error:</span> + {{ctrl.error}} +</div> diff --git a/testapi/opnfv_testapi/ui/components/deploy-results/deployResultsController.js b/testapi/opnfv_testapi/ui/components/deploy-results/deployResultsController.js new file mode 100644 index 0000000..5230a75 --- /dev/null +++ b/testapi/opnfv_testapi/ui/components/deploy-results/deployResultsController.js @@ -0,0 +1,190 @@ +/* + * 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('DeployResultsController', DeployResultsController); + + DeployResultsController.$inject = [ + '$scope', '$http', '$filter', '$state', 'testapiApiUrl','raiseAlert' + ]; + + /** + * TestAPI Deploy Results Controller + * This controller is for the '/deployresults' page where a user can browse + * a listing of community uploaded results. + */ + function DeployResultsController($scope, $http, $filter, $state, testapiApiUrl, + raiseAlert) { + var ctrl = this; + + ctrl.open = open; + ctrl.clearFilters = clearFilters; + ctrl.deleteTag = deleteTag; + ctrl.filterList= filterList; + ctrl.testFilter = testFilter + ctrl.filter = "pod" + ctrl.filterValue = "pod_name" + ctrl.encodeFilter = encodeFilter + ctrl.viewDeployResult = viewDeployResult + ctrl.tagArray = {} + ctrl.filterOption=[] + + /** 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'; + + ctrl.pageHeader = "Deploy Results" + + ctrl.pageParagraph = 'Your most recently uploaded deploy results are listed here.' + ctrl.isPublic = false; + + function encodeFilter(){ + ctrl.filterText = '' + ctrl.filterOption=[] + if(ctrl.filter=="pod" || ctrl.filter=="scenario"){ + var reqURL = testapiApiUrl +"/" + ctrl.filter + "s" + ctrl.datasRequest = + $http.get(reqURL).success(function (data) { + ctrl.filterData = data; + for(var index in ctrl.filterData[ctrl.filter + "s"]){ + if( ctrl.filterOption.indexOf(ctrl.filterData[ctrl.filter + "s"][index]["name"]) < 0){ + ctrl.filterOption.push(ctrl.filterData[ctrl.filter + "s"][index]["name"]) + } + } + }).catch(function (data) { + ctrl.data = null; + ctrl.showError = true; + ctrl.error = data.statusText; + }); + + } + } + + function deleteTag(index){ + delete ctrl.tagArray[index]; + ctrl.filterList(); + } + + function testFilter(text){ + for (var filter in ctrl.tagArray){ + if(text==filter){ + return true; + } + } + return false; + } + + function viewDeployResult(_id){ + $state.go('deployresult', {'_id':_id}, {reload: true}); + } + + /** + * This will contact the TestAPI API to get a listing of deploy + * results. + */ + function filterList(){ + if(ctrl.filter && ctrl.filterText!="" && ctrl.filterText!=undefined){ + ctrl.tagArray[ctrl.filter] = ctrl.filterText; + } + ctrl.showError = false; + var content_url = testapiApiUrl + '/deployresults' + + '?page=' + ctrl.currentPage; + for(var key in ctrl.tagArray){ + if(key=="start_date"){ + var start = $filter('date')(ctrl.tagArray[key], 'yyyy-MM-dd'); + if (start) { + content_url = + content_url + '&from=' + start + ' 00:00:00'; + } + } + else if(key=="end_date"){ + var end = $filter('date')(ctrl.tagArray[key], 'yyyy-MM-dd'); + if (end) { + content_url = content_url + '&to=' + end + ' 23:59:59'; + } + } + else{ + content_url = content_url + "&" + key + "=" + ctrl.tagArray[key] + } + 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; + ctrl.encodeFilter(); + }).catch(function (error) { + ctrl.data = null; + ctrl.totalItems = 0; + ctrl.showError = true; + ctrl.error = error.statusText + }); + ctrl.filterText = '' + } + ctrl.filterList(); + + + /** + * 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.tagArray = {} + ctrl.filter = undefined + ctrl.filterList(); + } + } +})(); diff --git a/testapi/opnfv_testapi/ui/index.html b/testapi/opnfv_testapi/ui/index.html index beec61d..68c6cc5 100644 --- a/testapi/opnfv_testapi/ui/index.html +++ b/testapi/opnfv_testapi/ui/index.html @@ -51,6 +51,8 @@ <script src="testapi-ui/components/projects/project/projectController.js"></script> <script src="testapi-ui/components/results/resultsController.js"></script> <script src="testapi-ui/components/results/result/resultController.js"></script> + <script src="testapi-ui/components/deploy-results/deployResultsController.js"></script> + <script src="testapi-ui/components/deploy-results/deploy-result/deployResultController.js"></script> <script src="testapi-ui/components/profile/profileController.js"></script> <script src="testapi-ui/components/auth-failure/authFailureController.js"></script> <script src="testapi-ui/components/logout/logoutController.js"></script> diff --git a/testapi/opnfv_testapi/ui/shared/header/header.html b/testapi/opnfv_testapi/ui/shared/header/header.html index 3b2aba1..2b7be2f 100644 --- a/testapi/opnfv_testapi/ui/shared/header/header.html +++ b/testapi/opnfv_testapi/ui/shared/header/header.html @@ -20,6 +20,7 @@ TestAPI <li ng-class="{ active: header.isActive('/pods')}"><a ui-sref="pods">Pods</a></li> <li ng-class="{ active: header.isActive('/projects')}"><a ui-sref="projects">Projects</a></li> <li ng-class="{ active: header.isActive('/results')}"><a ui-sref="results">Results</a></li> + <li ng-class="{ active: header.isActive('/deployresults')}"><a ui-sref="deployresults">Deploy Results</a></li> <li ng-class="{ active: header.isActive('/scenarios')}"><a ui-sref="scenarios">Scenarios</a></li> </ul> <ul class="nav navbar-nav navbar-right" ng-class="{'hidden' : !authenticate}"> |