Add results and test report page
This is the groundwork for the community results listing page and the test run report page. The report page is fairly basic, primarily showing how results stack up against defcore capabilities. Design is subject to change, but this gets the ball rolling. A config.json.sample was also added. The UI now expects a config.json in the root which will contain the Refstack API URL. Change-Id: Id7a376d0bccda5cbb5daf05e52a2c174ad40b497
This commit is contained in:
parent
8f972ce693
commit
c094c27eff
1
refstack-ui/.gitignore
vendored
1
refstack-ui/.gitignore
vendored
@ -7,3 +7,4 @@ dist
|
|||||||
node_modules
|
node_modules
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
app/assets/lib
|
app/assets/lib
|
||||||
|
app/config.json
|
||||||
|
@ -7,6 +7,10 @@ User interface for interacting with the Refstack API.
|
|||||||
Setup
|
Setup
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|
Create a config.json file and specify your API endpoint inside this file:
|
||||||
|
|
||||||
|
:code:`cp app/config.json.sample app/config.json`
|
||||||
|
|
||||||
You can start a development server by doing the following:
|
You can start a development server by doing the following:
|
||||||
|
|
||||||
Install NodeJS and NPM:
|
Install NodeJS and NPM:
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
/* App Module */
|
/* App Module */
|
||||||
|
|
||||||
var refstackApp = angular.module('refstackApp', [
|
var refstackApp = angular.module('refstackApp', [
|
||||||
'ui.router', 'ui.bootstrap']);
|
'ui.router', 'ui.bootstrap', 'cgBusy']);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle application routing.
|
||||||
|
*/
|
||||||
refstackApp.config(['$stateProvider', '$urlRouterProvider',
|
refstackApp.config(['$stateProvider', '$urlRouterProvider',
|
||||||
function($stateProvider, $urlRouterProvider) {
|
function($stateProvider, $urlRouterProvider) {
|
||||||
$urlRouterProvider.otherwise('/');
|
$urlRouterProvider.otherwise('/');
|
||||||
@ -24,7 +27,33 @@ refstackApp.config(['$stateProvider', '$urlRouterProvider',
|
|||||||
}).
|
}).
|
||||||
state('results', {
|
state('results', {
|
||||||
url: '/results',
|
url: '/results',
|
||||||
templateUrl: '/components/results/results.html'
|
templateUrl: '/components/results/results.html',
|
||||||
|
controller: 'resultsController'
|
||||||
|
}).
|
||||||
|
state('resultsDetail', {
|
||||||
|
url: '/results/:testID',
|
||||||
|
templateUrl: '/components/results-report/resultsReport.html',
|
||||||
|
controller: 'resultsReportController'
|
||||||
})
|
})
|
||||||
}]);
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load Config and start up the angular application.
|
||||||
|
*/
|
||||||
|
angular.element(document).ready(function () {
|
||||||
|
var $http = angular.injector(['ng']).get('$http');
|
||||||
|
function startApp(config) {
|
||||||
|
// Add config options as constants.
|
||||||
|
for (var key in config) {
|
||||||
|
angular.module('refstackApp').constant(key, config[key]);
|
||||||
|
}
|
||||||
|
angular.bootstrap(document, ['refstackApp']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$http.get('config.json').success(function(data) {
|
||||||
|
startApp(data);
|
||||||
|
}).error(function(error) {
|
||||||
|
startApp({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -100,11 +100,6 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
content: '\00BB';
|
content: '\00BB';
|
||||||
}
|
}
|
||||||
|
|
||||||
.flagged:before {
|
|
||||||
color: #E6A100;
|
|
||||||
content: '\2691';
|
|
||||||
}
|
|
||||||
|
|
||||||
.program-about {
|
.program-about {
|
||||||
font-size: .8em;
|
font-size: .8em;
|
||||||
}
|
}
|
||||||
@ -128,3 +123,10 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
width: 70%;
|
width: 70%;
|
||||||
height: 70%;
|
height: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.result-filters {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-top: 2px solid #C9C9C9;
|
||||||
|
border-bottom: 2px solid #C9C9C9;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
@ -49,7 +49,8 @@
|
|||||||
<a ng-click="hideTests = !hideTests">Tests ({{capability.tests.length}})</a>
|
<a ng-click="hideTests = !hideTests">Tests ({{capability.tests.length}})</a>
|
||||||
<ul collapse="hideTests">
|
<ul collapse="hideTests">
|
||||||
<li ng-repeat="test in capability.tests">
|
<li ng-repeat="test in capability.tests">
|
||||||
<span ng-class="{'flagged': capability.flagged.indexOf(test) > -1}"> {{test}}</span>
|
<span ng-class="{'glyphicon glyphicon-flag text-warning': capability.flagged.indexOf(test) > -1}"></span>
|
||||||
|
{{test}}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
179
refstack-ui/app/components/results-report/resultsReport.html
Normal file
179
refstack-ui/app/components/results-report/resultsReport.html
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<h3>Test Run Results</h3>
|
||||||
|
|
||||||
|
<div cg-busy="{promise:resultsRequest,message:'Loading'}"></div>
|
||||||
|
<div ng-show="resultsData" class="test-report">
|
||||||
|
<strong>Test ID:</strong> {{testId}} <br />
|
||||||
|
<strong>Upload Date:</strong> {{resultsData.created_at}} UTC<br />
|
||||||
|
<strong>Duration:</strong> {{resultsData.duration_seconds}} seconds<br />
|
||||||
|
<strong>Total Number of Passed Tests:</strong> {{resultsData.results.length}} <br />
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p>See how these results stack up against DefCore capabilities and OpenStack
|
||||||
|
<a target="_blank" href="http://www.openstack.org/brand/interop/">target marketing programs.</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<strong>Capabilities Version:</strong>
|
||||||
|
<select ng-model="version" ng-change="updateCapabilities()" class="form-control">
|
||||||
|
<option value="2015.03">2015.03</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<strong>Target Program:</strong>
|
||||||
|
<select ng-model="target" class="form-control" ng-change="buildCapabilityObject()">
|
||||||
|
<option value="platform">OpenStack Powered Platform</option>
|
||||||
|
<option value="compute">OpenStack Powered Compute</option>
|
||||||
|
<option value="object">OpenStack Powered Object Storage</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div ng-show="capabilityData && resultsData">
|
||||||
|
<strong>Status:</strong>
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar"
|
||||||
|
role="progressbar"
|
||||||
|
aria-valuenow="{{caps.required.passedCount*100/caps.required.count | number:1}}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
style="min-width: 3em; width: {{caps.required.passedCount*100/caps.required.count}}%;">
|
||||||
|
|
||||||
|
{{caps.required.passedCount*100/caps.required.count | number:1}}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>This cloud passes <strong>{{caps.required.passedCount*100/caps.required.count | number:1}}% </strong>({{caps.required.passedCount}}/{{caps.required.count}})
|
||||||
|
of the <strong>{{version}}</strong> capability tests required by the <strong>{{targetMappings[target]}}</strong> program.</p>
|
||||||
|
|
||||||
|
<h4>Capability Overview</h4>
|
||||||
|
|
||||||
|
<accordion close-others=false>
|
||||||
|
<accordion-group heading="Required" is-open="requiredOpen" is-disabled="caps.required.caps.length == 0">
|
||||||
|
<accordion-heading>
|
||||||
|
Required <small>({{caps.required.caps.length}} capabilities)</small>
|
||||||
|
<i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': requiredOpen, 'glyphicon-chevron-right': !requiredOpen}"></i>
|
||||||
|
</accordion-heading>
|
||||||
|
<ol class="capabilities">
|
||||||
|
<li ng-repeat="capability in caps.required.caps">
|
||||||
|
<a ng-click="hideTests = !hideTests">{{capability.id}} </a>
|
||||||
|
<span ng-class="{'text-success': (capability.passedTests.length > 0 && capability.notPassedTests.length == 0),
|
||||||
|
'text-danger': (capability.passedTests.length == 0 && capability.notPassedTests.length > 0),
|
||||||
|
'text-warning': (capability.passedTests.length > 0 && capability.notPassedTests.length > 0)}">
|
||||||
|
[{{capability.passedTests.length}}/{{capability.passedTests.length + capability.notPassedTests.length}}]
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ul class="list-unstyled" collapse="hideTests">
|
||||||
|
<li ng-repeat="test in capability.passedTests">
|
||||||
|
<span class="glyphicon glyphicon-ok text-success" aria-hidden="true"></span>
|
||||||
|
<span ng-class="{'glyphicon glyphicon-flag text-warning': capabilityData.capabilities[capability.id].flagged.indexOf(test) > -1}"></span>
|
||||||
|
{{test}}
|
||||||
|
</li>
|
||||||
|
<li ng-repeat="test in capability.notPassedTests">
|
||||||
|
<span class="glyphicon glyphicon-remove text-danger" aria-hidden="true"></span>
|
||||||
|
<span ng-class="{'glyphicon glyphicon-flag text-warning': capabilityData.capabilities[capability.id].flagged.indexOf(test) > -1}"></span>
|
||||||
|
{{test}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</accordion-group>
|
||||||
|
|
||||||
|
<accordion-group heading="Advisory" is-open="advisoryOpen" is-disabled="caps.advisory.caps.length == 0">
|
||||||
|
<accordion-heading>
|
||||||
|
Advisory <small>({{caps.advisory.caps.length}} capabilities)</small>
|
||||||
|
<i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': advisoryOpen, 'glyphicon-chevron-right': !advisoryOpen}"></i>
|
||||||
|
</accordion-heading>
|
||||||
|
<ol class="capabilities">
|
||||||
|
<li ng-repeat="capability in caps.advisory.caps">
|
||||||
|
<a ng-click="hideTests = !hideTests">{{capability.id}} </a>
|
||||||
|
<span ng-class="{'text-success': (capability.passedTests.length > 0 && capability.notPassedTests.length == 0),
|
||||||
|
'text-danger': (capability.passedTests.length == 0 && capability.notPassedTests.length > 0),
|
||||||
|
'text-warning': (capability.passedTests.length > 0 && capability.notPassedTests.length > 0)}">
|
||||||
|
[{{capability.passedTests.length}}/{{capability.passedTests.length + capability.notPassedTests.length}}]
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ul class="list-unstyled" collapse="hideTests">
|
||||||
|
<li ng-repeat="test in capability.passedTests">
|
||||||
|
<span class="glyphicon glyphicon-ok text-success" aria-hidden="true"></span>
|
||||||
|
<span ng-class="{'glyphicon glyphicon-flag text-warning': capabilityData.capabilities[capability.id].flagged.indexOf(test) > -1}"></span>
|
||||||
|
{{test}}
|
||||||
|
</li>
|
||||||
|
<li ng-repeat="test in capability.notPassedTests">
|
||||||
|
<span class="glyphicon glyphicon-remove text-danger" aria-hidden="true"></span>
|
||||||
|
<span ng-class="{'glyphicon glyphicon-flag text-warning': capabilityData.capabilities[capability.id].flagged.indexOf(test) > -1}"></span>
|
||||||
|
{{test}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</accordion-group>
|
||||||
|
|
||||||
|
<accordion-group heading="Deprecated" is-open="deprecatedOpen" is-disabled="caps.deprecated.caps.length == 0">
|
||||||
|
<accordion-heading>
|
||||||
|
Deprecated <small>({{caps.deprecated.caps.length}} capabilities)</small>
|
||||||
|
<i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': deprecatedOpen, 'glyphicon-chevron-right': !deprecatedOpen}"></i>
|
||||||
|
</accordion-heading>
|
||||||
|
<ol class="capabilities">
|
||||||
|
<li ng-repeat="capability in caps.deprecated.caps">
|
||||||
|
<a ng-click="hideTests = !hideTests">{{capability.id}} </a>
|
||||||
|
<span ng-class="{'text-success': (capability.passedTests.length > 0 && capability.notPassedTests.length == 0),
|
||||||
|
'text-danger': (capability.passedTests.length == 0 && capability.notPassedTests.length > 0),
|
||||||
|
'text-warning': (capability.passedTests.length > 0 && capability.notPassedTests.length > 0)}">
|
||||||
|
[{{capability.passedTests.length}}/{{capability.passedTests.length + capability.notPassedTests.length}}]
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ul class="list-unstyled" collapse="hideTests">
|
||||||
|
<li ng-repeat="test in capability.passedTests">
|
||||||
|
<span class="glyphicon glyphicon-ok text-success" aria-hidden="true"></span>
|
||||||
|
<span ng-class="{'glyphicon glyphicon-flag text-warning': capabilityData.capabilities[capability.id].flagged.indexOf(test) > -1}"></span>
|
||||||
|
{{test}}
|
||||||
|
</li>
|
||||||
|
<li ng-repeat="test in capability.notPassedTests">
|
||||||
|
<span class="glyphicon glyphicon-remove text-danger" aria-hidden="true"></span>
|
||||||
|
<span ng-class="{'glyphicon glyphicon-flag text-warning': capabilityData.capabilities[capability.id].flagged.indexOf(test) > -1}"></span>
|
||||||
|
{{test}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</accordion-group>
|
||||||
|
|
||||||
|
<accordion-group is-open="removedOpen" is-disabled="caps.removed.caps.length == 0">
|
||||||
|
<accordion-heading>
|
||||||
|
Removed <small>({{caps.removed.caps.length}} capabilities)</small>
|
||||||
|
<i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': removedOpen, 'glyphicon-chevron-right': !removedOpen}"></i>
|
||||||
|
</accordion-heading>
|
||||||
|
<ol class="capabilities">
|
||||||
|
<li ng-repeat="capability in caps.removed.caps">
|
||||||
|
<a ng-click="hideTests = !hideTests">{{capability.id}} </a>
|
||||||
|
<span ng-class="{'text-success': (capability.passedTests.length > 0 && capability.notPassedTests.length == 0),
|
||||||
|
'text-danger': (capability.passedTests.length == 0 && capability.notPassedTests.length > 0),
|
||||||
|
'text-warning': (capability.passedTests.length > 0 && capability.notPassedTests.length > 0)}">
|
||||||
|
[{{capability.passedTests.length}}/{{capability.passedTests.length + capability.notPassedTests.length}}]
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ul class="list-unstyled" collapse="hideTests">
|
||||||
|
<li ng-repeat="test in capability.passedTests">
|
||||||
|
<span class="glyphicon glyphicon-ok text-success" aria-hidden="true"></span>
|
||||||
|
<span ng-class="{'glyphicon glyphicon-flag text-warning': capabilityData.capabilities[capability.id].flagged.indexOf(test) > -1}"></span>
|
||||||
|
{{test}}
|
||||||
|
</li>
|
||||||
|
<li ng-repeat="test in capability.notPassedTests">
|
||||||
|
<span class="glyphicon glyphicon-remove text-danger" aria-hidden="true"></span>
|
||||||
|
<span ng-class="{'glyphicon glyphicon-flag text-warning': capabilityData.capabilities[capability.id].flagged.indexOf(test) > -1}"></span>
|
||||||
|
{{test}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</accordion-group>
|
||||||
|
</accordion>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="showError" class="alert alert-danger" role="alert">
|
||||||
|
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||||
|
<span class="sr-only">Error:</span>
|
||||||
|
{{error}}
|
||||||
|
</div>
|
@ -0,0 +1,91 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* Refstack Results Report Controller */
|
||||||
|
|
||||||
|
var refstackApp = angular.module('refstackApp');
|
||||||
|
|
||||||
|
refstackApp.controller('resultsReportController', ['$scope', '$http', '$stateParams', 'refstackApiUrl',
|
||||||
|
function($scope, $http, $stateParams, refstackApiUrl) {
|
||||||
|
$scope.testId = $stateParams.testID
|
||||||
|
$scope.version = '2015.03';
|
||||||
|
$scope.hideTests = true;
|
||||||
|
$scope.target = 'platform';
|
||||||
|
$scope.requiredOpen = true;
|
||||||
|
|
||||||
|
$scope.targetMappings = {
|
||||||
|
'platform': 'Openstack Powered Platform',
|
||||||
|
'compute': 'OpenStack Powered Compute',
|
||||||
|
'object': 'OpenStack Powered Object Storage'
|
||||||
|
}
|
||||||
|
|
||||||
|
var content_url = refstackApiUrl +'/results/' + $scope.testId;
|
||||||
|
$scope.resultsRequest = $http.get(content_url).success(function(data) {
|
||||||
|
$scope.resultsData = data;
|
||||||
|
$scope.updateCapabilities();
|
||||||
|
}).error(function(error) {
|
||||||
|
$scope.showError = true;
|
||||||
|
$scope.resultsData = null;
|
||||||
|
$scope.error = "Error retrieving results from server: " + JSON.stringify(error);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.updateCapabilities = function() {
|
||||||
|
$scope.showError = false;
|
||||||
|
var content_url = 'assets/capabilities/'.concat($scope.version, '.json');
|
||||||
|
$http.get(content_url).success(function(data) {
|
||||||
|
$scope.capabilityData = data;
|
||||||
|
$scope.buildCapabilityObject($scope.capabilityData, $scope.resultsData.results);
|
||||||
|
}).error(function(error) {
|
||||||
|
$scope.showError = true;
|
||||||
|
$scope.capabilityData = null;
|
||||||
|
$scope.error = 'Error retrieving capabilities: ' + JSON.stringify(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.buildCapabilityObject = function() {
|
||||||
|
var capabilities = $scope.capabilityData.capabilities;
|
||||||
|
var caps = {'required': {'caps': [], 'count': 0, 'passedCount': 0},
|
||||||
|
'advisory': {'caps': [], 'count': 0, 'passedCount': 0},
|
||||||
|
'deprecated': {'caps': [], 'count': 0, 'passedCount': 0},
|
||||||
|
'removed': {'caps': [], 'count': 0, 'passedCount': 0}};
|
||||||
|
var components = $scope.capabilityData.components;
|
||||||
|
var cap_array = [];
|
||||||
|
// First determine which capabilities are relevant to the target.
|
||||||
|
if ($scope.target === 'platform') {
|
||||||
|
var platform_components = $scope.capabilityData.platform.required;
|
||||||
|
// For each component required for the platform program.
|
||||||
|
angular.forEach(platform_components, function(component) {
|
||||||
|
// Get each capability belonging to each status.
|
||||||
|
angular.forEach(components[component], function(capabilities) {
|
||||||
|
cap_array = cap_array.concat(capabilities);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
angular.forEach(components[$scope.target], function(capabilities) {
|
||||||
|
cap_array = cap_array.concat(capabilities);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.forEach(capabilities, function(value, key) {
|
||||||
|
if (cap_array.indexOf(key) > -1) {
|
||||||
|
var cap = { "id": key,
|
||||||
|
"passedTests": [],
|
||||||
|
"notPassedTests": []};
|
||||||
|
caps[value.status].count += value.tests.length;
|
||||||
|
angular.forEach(value.tests, function(test_id) {
|
||||||
|
if ($scope.resultsData.results.indexOf(test_id) > -1) {
|
||||||
|
cap.passedTests.push(test_id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cap.notPassedTests.push(test_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
caps[value.status].passedCount += cap.passedTests.length;
|
||||||
|
caps[value.status].caps.push(cap);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$scope.caps = caps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
@ -1 +1,82 @@
|
|||||||
<p>Community results list here.</p>
|
<h3>Community Results</h3>
|
||||||
|
<p>The most recently uploaded community test results are listed here. Currently, these results are anonymous.</p>
|
||||||
|
|
||||||
|
<div class="result-filters">
|
||||||
|
<h4>Filters</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label for="cpid">Start Date</label>
|
||||||
|
<p class="input-group">
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
datepicker-popup="{{format}}"
|
||||||
|
ng-model="startDate" is-open="startOpen"
|
||||||
|
datepicker-options="dateOptions"
|
||||||
|
close-text="Close" />
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="button" class="btn btn-default" ng-click="open($event, 'startOpen')">
|
||||||
|
<i class="glyphicon glyphicon-calendar"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label for="cpid">End Date</label>
|
||||||
|
<p class="input-group">
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
datepicker-popup="{{format}}"
|
||||||
|
ng-model="endDate" is-open="endOpen"
|
||||||
|
datepicker-options="dateOptions"
|
||||||
|
close-text="Close" />
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="button" class="btn btn-default" ng-click="open($event, 'endOpen')">
|
||||||
|
<i class="glyphicon glyphicon-calendar"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3"style="margin-top:24px;">
|
||||||
|
<button type="submit" class="btn btn-primary" ng-click="update()">Filter</button>
|
||||||
|
<button type="submit" class="btn btn-primary btn-danger" ng-click="clearFilters()">Clear</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div cg-busy="{promise:resultsRequest,message:'Loading'}"></div>
|
||||||
|
<div ng-show="data" class="results-table">
|
||||||
|
<table ng-show="data" class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Upload Date</th>
|
||||||
|
<th>Test Run ID</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="result in data.results">
|
||||||
|
<td>{{result.created_at}}</td>
|
||||||
|
<td><a ui-sref="resultsDetail({testID: result.test_id})">{{result.test_id}}</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="pages">
|
||||||
|
<pagination
|
||||||
|
total-items="totalItems"
|
||||||
|
ng-model="currentPage"
|
||||||
|
items-per-page="itemsPerPage"
|
||||||
|
max-size="maxSize"
|
||||||
|
class="pagination-sm"
|
||||||
|
boundary-links="true"
|
||||||
|
rotate="false"
|
||||||
|
num-pages="numPages"
|
||||||
|
ng-change="update()">
|
||||||
|
</pagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-show="showError" class="alert alert-danger" role="alert">
|
||||||
|
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||||
|
<span class="sr-only">Error:</span>
|
||||||
|
{{error}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
51
refstack-ui/app/components/results/resultsController.js
Normal file
51
refstack-ui/app/components/results/resultsController.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* Refstack Results Controller */
|
||||||
|
|
||||||
|
var refstackApp = angular.module('refstackApp');
|
||||||
|
|
||||||
|
refstackApp.controller('resultsController', ['$scope', '$http', '$filter', 'refstackApiUrl', function($scope, $http, $filter, refstackApiUrl) {
|
||||||
|
$scope.currentPage = 1;
|
||||||
|
$scope.itemsPerPage = 20;
|
||||||
|
$scope.maxSize = 5;
|
||||||
|
$scope.startDate = "";
|
||||||
|
$scope.endDate = "";
|
||||||
|
$scope.update = function() {
|
||||||
|
$scope.showError = false;
|
||||||
|
var content_url = refstackApiUrl + '/results?page=' + $scope.currentPage;
|
||||||
|
var start = $filter('date')($scope.startDate, "yyyy-MM-dd");
|
||||||
|
if (start) {
|
||||||
|
content_url = content_url + "&start_date=" + start + " 00:00:00";
|
||||||
|
}
|
||||||
|
var end = $filter('date')($scope.endDate, "yyyy-MM-dd");
|
||||||
|
if (end) {
|
||||||
|
content_url = content_url + "&end_date=" + end + " 23:59:59";
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.resultsRequest = $http.get(content_url).success(function(data) {
|
||||||
|
$scope.data = data;
|
||||||
|
$scope.totalItems = $scope.data.pagination.total_pages * $scope.itemsPerPage;
|
||||||
|
$scope.currentPage = $scope.data.pagination.current_page;
|
||||||
|
}).error(function(error) {
|
||||||
|
$scope.data = null;
|
||||||
|
$scope.totalItems = 0
|
||||||
|
$scope.showError = true
|
||||||
|
$scope.error = "Error retrieving results listing from server: " + JSON.stringify(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.update();
|
||||||
|
|
||||||
|
// This is called when a date filter calendar is opened.
|
||||||
|
$scope.open = function($event, openVar) {
|
||||||
|
$event.preventDefault();
|
||||||
|
$event.stopPropagation();
|
||||||
|
$scope[openVar] = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.clearFilters = function() {
|
||||||
|
$scope.startDate = null;
|
||||||
|
$scope.endDate = null;
|
||||||
|
$scope.update();
|
||||||
|
};
|
||||||
|
}]);
|
1
refstack-ui/app/config.json.sample
Normal file
1
refstack-ui/app/config.json.sample
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"refstackApiUrl": "http://api.refstack.net/v1"}
|
@ -14,7 +14,7 @@
|
|||||||
License for the specific language governing permissions and limitations
|
License for the specific language governing permissions and limitations
|
||||||
under the License.
|
under the License.
|
||||||
-->
|
-->
|
||||||
<html ng-app="refstackApp">
|
<html id="ng-app">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="description" content="Refstack">
|
<meta name="description" content="Refstack">
|
||||||
@ -24,17 +24,22 @@
|
|||||||
<link rel="shorcut icon" href="favicon.ico">
|
<link rel="shorcut icon" href="favicon.ico">
|
||||||
|
|
||||||
<link rel="stylesheet" href="assets/lib/bootstrap/dist/css/bootstrap.min.css">
|
<link rel="stylesheet" href="assets/lib/bootstrap/dist/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/lib/angular-busy/dist/angular-busy.min.css">
|
||||||
<link rel="stylesheet" href="assets/css/style.css">
|
<link rel="stylesheet" href="assets/css/style.css">
|
||||||
|
|
||||||
<script src="assets/lib/angular/angular.js"></script>
|
<script src="assets/lib/angular/angular.js"></script>
|
||||||
<script src="assets/lib/angular-ui-router/release/angular-ui-router.js"></script>
|
<script src="assets/lib/angular-ui-router/release/angular-ui-router.js"></script>
|
||||||
<script src="assets/lib/angular-bootstrap/ui-bootstrap.min.js"></script>
|
<script src="assets/lib/angular-bootstrap/ui-bootstrap.min.js"></script>
|
||||||
|
<script src="assets/lib/angular-bootstrap/ui-bootstrap-tpls.min.js"></script>
|
||||||
|
<script src="assets/lib/angular-busy/dist/angular-busy.min.js"></script>
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
<script src="assets/js/refstack.js"></script>
|
<script src="assets/js/refstack.js"></script>
|
||||||
|
|
||||||
<!-- Controllers -->
|
<!-- Controllers -->
|
||||||
<script src="shared/header/headerController.js"></script>
|
<script src="shared/header/headerController.js"></script>
|
||||||
<script src="components/capabilities/capabilitiesController.js"></script>
|
<script src="components/capabilities/capabilitiesController.js"></script>
|
||||||
|
<script src="components/results/resultsController.js"></script>
|
||||||
|
<script src="components/results-report/resultsReportController.js"></script>
|
||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<script src="shared/filters.js"></script>
|
<script src="shared/filters.js"></script>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"angular-ui-router": "0.2.13",
|
"angular-ui-router": "0.2.13",
|
||||||
"angular-resource": "1.3.15",
|
"angular-resource": "1.3.15",
|
||||||
"angular-bootstrap": "0.12.1",
|
"angular-bootstrap": "0.12.1",
|
||||||
|
"angular-busy": "4.1.3",
|
||||||
"bootstrap": "3.3.2"
|
"bootstrap": "3.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -9,6 +9,8 @@ module.exports = function(config){
|
|||||||
'app/assets/lib/angular-ui-router/release/angular-ui-router.js',
|
'app/assets/lib/angular-ui-router/release/angular-ui-router.js',
|
||||||
'app/assets/lib/angular-bootstrap/ui-bootstrap.min.js',
|
'app/assets/lib/angular-bootstrap/ui-bootstrap.min.js',
|
||||||
'app/assets/lib/angular-mocks/angular-mocks.js',
|
'app/assets/lib/angular-mocks/angular-mocks.js',
|
||||||
|
'app/assets/lib/angular-bootstrap/ui-bootstrap-tpls.min.js',
|
||||||
|
'app/assets/lib/angular-busy/dist/angular-busy.min.js',
|
||||||
|
|
||||||
// JS files.
|
// JS files.
|
||||||
'app/app.js',
|
'app/app.js',
|
||||||
|
@ -91,4 +91,121 @@ describe('Refstack controllers', function() {
|
|||||||
expect(scope.filterProgram({'id': 'cap_id_5'})).toBe(false);
|
expect(scope.filterProgram({'id': 'cap_id_5'})).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('resultsController', function() {
|
||||||
|
var scope, ctrl, $httpBackend, refstackApiUrl;
|
||||||
|
var fakeResponse = {'pagination': {'current_page': 1, 'total_pages': 2},
|
||||||
|
'results': [{'created_at': '2015-03-09 01:23:45',
|
||||||
|
'test_id': 'some-id',
|
||||||
|
'cpid': 'some-cpid'}]};
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
module('refstackApp');
|
||||||
|
module(function($provide) {
|
||||||
|
$provide.constant('refstackApiUrl', 'http://foo.bar/v1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
|
||||||
|
$httpBackend = _$httpBackend_;
|
||||||
|
scope = $rootScope.$new();
|
||||||
|
ctrl = $controller('resultsController', {$scope: scope});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fetch the first page of results with proper URL args', function() {
|
||||||
|
// Initial results should be page 1 of all results.
|
||||||
|
$httpBackend.expectGET('http://foo.bar/v1/results?page=1').respond(fakeResponse);
|
||||||
|
$httpBackend.flush();
|
||||||
|
expect(scope.data).toEqual(fakeResponse);
|
||||||
|
expect(scope.currentPage).toBe(1);
|
||||||
|
|
||||||
|
// Simulate the user adding date filters.
|
||||||
|
scope.startDate = new Date('2015-03-10T11:51:00');
|
||||||
|
scope.endDate = new Date('2015-04-10T11:51:00');
|
||||||
|
scope.update();
|
||||||
|
$httpBackend.expectGET('http://foo.bar/v1/results?page=1&start_date=2015-03-10 00:00:00&end_date=2015-04-10 23:59:59').respond(fakeResponse);
|
||||||
|
$httpBackend.flush();
|
||||||
|
expect(scope.data).toEqual(fakeResponse);
|
||||||
|
expect(scope.currentPage).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set an error when results cannot be retrieved', function() {
|
||||||
|
$httpBackend.expectGET('http://foo.bar/v1/results?page=1').respond(404, {'detail': 'Not Found'});
|
||||||
|
$httpBackend.flush();
|
||||||
|
expect(scope.data).toBe(null);
|
||||||
|
expect(scope.error).toEqual('Error retrieving results listing from server: {"detail":"Not Found"}');
|
||||||
|
expect(scope.totalItems).toBe(0);
|
||||||
|
expect(scope.showError).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have an function to clear filters and update the view', function() {
|
||||||
|
$httpBackend.expectGET('http://foo.bar/v1/results?page=1').respond(fakeResponse);
|
||||||
|
scope.startDate = "some date";
|
||||||
|
scope.endDate = "some other date";
|
||||||
|
scope.clearFilters();
|
||||||
|
expect(scope.startDate).toBe(null);
|
||||||
|
expect(scope.endDate).toBe(null);
|
||||||
|
$httpBackend.expectGET('http://foo.bar/v1/results?page=1').respond(fakeResponse);
|
||||||
|
$httpBackend.flush();
|
||||||
|
expect(scope.data).toEqual(fakeResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resultsReportController', function() {
|
||||||
|
var scope, ctrl, $httpBackend, refstackApiUrl, stateparams;
|
||||||
|
var fakeResultResponse = {'results': ['test_id_1']}
|
||||||
|
var fakeCapabilityResponse = {'platform': {'required': ['compute']},
|
||||||
|
'components': {
|
||||||
|
'compute': {
|
||||||
|
'required': ['cap_id_1'],
|
||||||
|
'advisory': [],
|
||||||
|
'deprecated': [],
|
||||||
|
'removed': []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'capabilities': {
|
||||||
|
'cap_id_1': {
|
||||||
|
'status': 'required',
|
||||||
|
'flagged': [],
|
||||||
|
'tests': ['test_id_1', 'test_id_2']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
module('refstackApp');
|
||||||
|
module(function($provide) {
|
||||||
|
$provide.constant('refstackApiUrl', 'http://foo.bar/v1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
|
||||||
|
$httpBackend = _$httpBackend_;
|
||||||
|
stateparams = {testID: 1234};
|
||||||
|
scope = $rootScope.$new();
|
||||||
|
ctrl = $controller('resultsReportController', {$scope: scope, $stateParams: stateparams});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should get the results for a specific test ID and also the relevant capabilities', function() {
|
||||||
|
$httpBackend.expectGET('http://foo.bar/v1/results/1234').respond(fakeResultResponse);
|
||||||
|
$httpBackend.expectGET('assets/capabilities/2015.03.json').respond(fakeCapabilityResponse);
|
||||||
|
$httpBackend.flush();
|
||||||
|
expect(scope.resultsData).toEqual(fakeResultResponse);
|
||||||
|
expect(scope.capabilityData).toEqual(fakeCapabilityResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to sort the results into a capability object', function() {
|
||||||
|
scope.resultsData = fakeResultResponse;
|
||||||
|
scope.capabilityData = fakeCapabilityResponse;
|
||||||
|
scope.buildCapabilityObject();
|
||||||
|
var expectedCapsObject = {'required': {'caps': [{'id': 'cap_id_1',
|
||||||
|
'passedTests': ['test_id_1'],
|
||||||
|
'notPassedTests': ['test_id_2']}],
|
||||||
|
'count': 2, 'passedCount': 1},
|
||||||
|
'advisory': {'caps': [], 'count': 0, 'passedCount': 0},
|
||||||
|
'deprecated': {'caps': [], 'count': 0, 'passedCount': 0},
|
||||||
|
'removed': {'caps': [], 'count': 0, 'passedCount': 0}};
|
||||||
|
expect(scope.caps).toEqual(expectedCapsObject);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user