Add ability to select subset of tests

In the results report page, a user may only want to view failed
or flagged tests. This patch gives that functionality.

Closes-Bug: #1479959
Change-Id: Id91c4881d982cfacbfcffdf0be4a8fccb8d10489
This commit is contained in:
Paul Van Eck 2015-08-13 21:07:14 -07:00
parent bc427fb976
commit 1ca17dde52
8 changed files with 343 additions and 89 deletions

View File

@ -170,3 +170,7 @@ h1, h2, h3, h4, h5, h6 {
padding-left: .5em;
padding-right: .5em;
}
.button-margin {
margin-bottom: 1em;
}

View File

@ -19,11 +19,7 @@ refstackApp.controller('capabilitiesController',
/** The target OpenStack marketing program to show capabilities for. */
$scope.target = 'platform';
/**
* The various possible capability statuses. The true value for each
* status is the name of the key, so by default only required
* capabilities will be shown.
*/
/** The various possible capability statuses. */
$scope.status = {
required: true,
advisory: false,

View File

@ -0,0 +1,64 @@
<!--
HTML for each accordion group that separates the status types on the results
report page.
-->
<accordion-group is-open="isOpen" is-disabled="caps[status].caps.length == 0">
<accordion-heading>
{{status | capitalize}} <small>({{caps[status].caps.length}} total capabilities)</small>
<i class="pull-right glyphicon"
ng-class="{'glyphicon-chevron-down': isOpen, 'glyphicon-chevron-right': !isOpen}"></i>
</accordion-heading>
<ol class="capabilities">
<li ng-repeat="capability in caps[status].caps | orderBy:'id'"
ng-if="isCapabilityShown(capability)">
<a ng-click="hideTests = !hideTests"
title="{{capabilityData.capabilities[capability.id].description}}">
{{capability.id}}
</a>
<span ng-class="{'text-success': testStatus === 'passed',
'text-danger': testStatus === 'failed',
'text-warning': testStatus === 'flagged'}"
ng-if="testStatus !== 'all'">
[{{getTestCount(capability)}}]
</span>
<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)}"
ng-if="testStatus === 'all'">
[{{capability.passedTests.length}}/{{capability.passedTests.length +
capability.notPassedTests.length}}]
</span>
<ul class="list-unstyled" collapse="hideTests">
<li ng-repeat="test in capability.passedTests | orderBy:'toString()'"
ng-if="isTestShown(test, capability)">
<span class="glyphicon glyphicon-ok text-success"
aria-hidden="true">
</span>
<span ng-class="{'glyphicon glyphicon-flag text-warning':
isTestFlagged(test, capabilityData.capabilities[capability.id])}"
title="{{getFlaggedReason(test, capabilityData.capabilities[capability.id])}}">
</span>
{{test}}
</li>
<li ng-repeat="test in capability.notPassedTests | orderBy:'toString()'"
ng-if="isTestShown(test, capability)">
<span class="glyphicon glyphicon-remove text-danger" aria-hidden="true"></span>
<span ng-class="{'glyphicon glyphicon-flag text-warning':
isTestFlagged(test, capabilityData.capabilities[capability.id])}"
title="{{getFlaggedReason(test, capabilityData.capabilities[capability.id])}}">
</span>
{{test}}
</li>
</ul>
</li>
</ol>
</accordion-group>

View File

@ -1,34 +0,0 @@
<!--
HTML for each accordion group that separates the status types on the results
report page. This template is for DefCore capabilities schema version 1.2.
-->
<accordion-group is-open="isOpen" is-disabled="caps[status].caps.length == 0">
<accordion-heading>
{{status | capitalize}} <small>({{caps[status].caps.length}} capabilities)</small>
<i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': isOpen, 'glyphicon-chevron-right': !isOpen}"></i>
</accordion-heading>
<ol class="capabilities">
<li ng-repeat="capability in caps[status].caps">
<a ng-click="hideTests = !hideTests" title="{{capabilityData.capabilities[capability.id].description}}">{{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>

View File

@ -1,39 +0,0 @@
<!--
HTML for each accordion group that separates the status types on the results
report page. This template is for DefCore capabilities schema version 1.3.
-->
<accordion-group is-open="isOpen" is-disabled="caps[status].caps.length == 0">
<accordion-heading>
{{status | capitalize}} <small>({{caps[status].caps.length}} capabilities)</small>
<i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': isOpen, 'glyphicon-chevron-right': !isOpen}"></i>
</accordion-heading>
<ol class="capabilities">
<li ng-repeat="capability in caps[status].caps">
<a ng-click="hideTests = !hideTests" title="{{capabilityData.capabilities[capability.id].description}}">{{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].tests[test].flag}"
title="{{capabilityData.capabilities[capability.id].tests[test].flag.reason}}">
</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].tests[test].flag}"
title="{{capabilityData.capabilities[capability.id].tests[test].flag.reason}}">
</span>
{{test}}
</li>
</ul>
</li>
</ol>
</accordion-group>

View File

@ -79,6 +79,26 @@
<hr>
<h4>Capability Overview</h4>
Test Filters:<br />
<div class="btn-group button-margin" data-toggle="buttons">
<label class="btn btn-default" ng-class="{'active': testStatus === 'all'}">
<input type="radio" ng-model="testStatus" value="all">
<span class="text-primary">All</span>
</label>
<label class="btn btn-default" ng-class="{'active': testStatus === 'passed'}">
<input type="radio" ng-model="testStatus" value="passed">
<span class="text-success">Passed</span>
</label>
<label class="btn btn-default" ng-class="{'active': testStatus === 'failed'}">
<input type="radio" ng-model="testStatus" value="failed">
<span class="text-danger">Failed</span>
</label>
<label class="btn btn-default" ng-class="{'active': testStatus === 'flagged'}">
<input type="radio" ng-model="testStatus" value="flagged">
<span class="text-warning">Flagged</span>
</label>
</div>
<accordion close-others=false>
<!-- The ng-repeat is used to pass in a local variable to the template. -->
<ng-include

View File

@ -31,6 +31,13 @@ refstackApp.controller('resultsReportController',
/** The schema version of the currently selected capabilities data. */
$scope.schemaVersion = null;
/** The selected test status used for test filtering. */
$scope.testStatus = 'all';
/** The HTML template that all accordian groups will use. */
$scope.detailsTemplate = 'components/results-report/partials/' +
'reportDetails.html';
/**
* Retrieve an array of available capability files from the Refstack
* API server, sort this array reverse-alphabetically, and store it in
@ -137,9 +144,6 @@ refstackApp.controller('resultsReportController',
$http.get(content_url).success(function (data) {
$scope.capabilityData = data;
$scope.schemaVersion = data.schema;
$scope.detailsTemplate = 'components/results-report/' +
'partials/reportDetailsV' +
data.schema + '.html';
$scope.buildCapabilitiesObject();
}).error(function (error) {
$scope.showError = true;
@ -154,7 +158,7 @@ refstackApp.controller('resultsReportController',
* their corresponding statuses.
* @returns {Object} Object containing each capability and their status
*/
$scope.getTargetCapabilitites = function () {
$scope.getTargetCapabilities = function () {
var components = $scope.capabilityData.components;
var targetCaps = {};
@ -213,7 +217,7 @@ refstackApp.controller('resultsReportController',
* 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
* @param {String} capId capability ID
*/
$scope.buildCapabilityV1_2 = function (capId) {
var cap = {
@ -249,7 +253,7 @@ refstackApp.controller('resultsReportController',
* This will build the a capability object for schema version 1.3.
* This object will contain the information needed to form a report in
* the HTML template.
* @param {String} capID capability ID
* @param {String} capId capability ID
*/
$scope.buildCapabilityV1_3 = function (capId) {
var cap = {
@ -319,7 +323,7 @@ refstackApp.controller('resultsReportController',
// Get test details for each relevant capability and store
// them in the scope's 'caps' object.
var targetCaps = $scope.getTargetCapabilitites();
var targetCaps = $scope.getTargetCapabilities();
angular.forEach(targetCaps, function(status, capId) {
var cap = $scope[capMethod](capId);
$scope.caps[status].count +=
@ -349,6 +353,109 @@ refstackApp.controller('resultsReportController',
100 / $scope.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
*/
$scope.isTestFlagged = function (test, capObj) {
if (!capObj) {
return false;
}
return ((($scope.schemaVersion === '1.2') &&
(capObj.flagged.indexOf(test) > -1)) ||
(($scope.schemaVersion === '1.3') &&
(capObj.tests[test].flag)));
};
/**
* 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
*/
$scope.getFlaggedReason = function (test, capObj) {
if (($scope.schemaVersion === '1.2') &&
($scope.isTestFlagged(test, capObj))){
// Return a generic message since schema 1.2 does not
// provide flag reasons.
return 'DefCore has flagged this test.';
}
else if (($scope.schemaVersion === '1.3') &&
($scope.isTestFlagged(test, capObj))){
return capObj.tests[test].flag.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
*/
$scope.isCapabilityShown = function (capability) {
return (($scope.testStatus === 'all') ||
($scope.testStatus === 'passed' &&
capability.passedTests.length > 0) ||
($scope.testStatus === 'failed' &&
capability.notPassedTests.length > 0) ||
($scope.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
*/
$scope.isTestShown = function (test, capability) {
return (($scope.testStatus === 'all') ||
($scope.testStatus === 'passed' &&
capability.passedTests.indexOf(test) > -1) ||
($scope.testStatus === 'failed' &&
capability.notPassedTests.indexOf(test) > -1) ||
($scope.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.
* @param {Object} capability Built object for capability
* @returns {Number} number of tests under filter
*/
$scope.getTestCount = function (capability) {
if ($scope.testStatus === 'all') {
return capability.passedTests.length +
capability.notPassedTests.length;
}
else if ($scope.testStatus === 'passed') {
return capability.passedTests.length;
}
else if ($scope.testStatus === 'failed') {
return capability.notPassedTests.length;
}
else if ($scope.testStatus === 'flagged') {
return capability.passedFlagged.length +
capability.notPassedFlagged.length;
}
else {
return 0;
}
};
getResults();
}
]

View File

@ -248,9 +248,6 @@ describe('Refstack controllers', function () {
'2015.03.json']);
expect(scope.capabilityData).toEqual(fakeCapabilityResponse);
expect(scope.schemaVersion).toEqual('1.2');
expect(scope.detailsTemplate).toEqual('components/results-' +
'report/partials/' +
'reportDetailsV1.2.html');
});
it('should have a method that creates an object containing each ' +
@ -279,7 +276,7 @@ describe('Refstack controllers', function () {
'cap_id_2': 'required',
'cap_id_3': 'advisory'
};
expect(scope.getTargetCapabilitites()).toEqual(expected);
expect(scope.getTargetCapabilities()).toEqual(expected);
});
it('should be able to sort the results into a capability object for ' +
@ -371,5 +368,144 @@ describe('Refstack controllers', function () {
expect(scope.requiredPassPercent).toEqual(50);
expect(scope.nonFlagPassCount).toEqual(0);
});
it('should have a method to determine if a test is flagged',
function () {
var capObj = {'flagged': [ 'test1'],
'tests': ['test1', 'test2']};
scope.schemaVersion = '1.2';
expect(scope.isTestFlagged('test1', capObj)).toEqual(true);
expect(scope.isTestFlagged('test2', capObj)).toEqual(false);
capObj = {'tests': {
'test1': {
'flag': {
'action': 'foo',
'date': '2015-03-24',
'reason': 'bar'
},
'idempotent_id': 'id-1234'
},
'test2': {
'idempotent_id': 'id-5678'
}
}
};
scope.schemaVersion = '1.3';
expect(scope.isTestFlagged('test1', capObj)).toBeTruthy();
expect(scope.isTestFlagged('test2', capObj)).toBeFalsy();
expect(scope.isTestFlagged('test2', null)).toEqual(false);
});
it('should have a method to get the reason a flagged test is flagged',
function () {
var capObj = {'flagged': [ 'test1'],
'tests': ['test1', 'test2']};
scope.schemaVersion = '1.2';
expect(scope.getFlaggedReason('test1', capObj)).toEqual(
'DefCore has flagged this test.');
// Check that non-flagged test returns empty string.
expect(scope.getFlaggedReason('test2', capObj)).toEqual('');
capObj = {'tests': {
'test1': {
'flag': {
'action': 'foo',
'date': '2015-03-24',
'reason': 'bar'
},
'idempotent_id': 'id-1234'
}
}
};
scope.schemaVersion = '1.3';
expect(scope.getFlaggedReason('test1', capObj)).toEqual('bar');
});
it('should have a method to determine whether a capability should ' +
'be shown',
function () {
var caps = [{'id': 'cap_id_1',
'passedTests': ['test_id_1'],
'notPassedTests': [],
'passedFlagged': ['test_id_1'],
'notPassedFlagged': []
},
{'id': 'cap_id_2',
'passedTests': [],
'notPassedTests': ['test_id_4'],
'passedFlagged': [],
'notPassedFlagged': []
}];
// Check that all capabilities are shown by default.
expect(scope.isCapabilityShown(caps[0])).toEqual(true);
expect(scope.isCapabilityShown(caps[1])).toEqual(true);
// Check that only capabilities with passed tests are shown.
scope.testStatus = 'passed';
expect(scope.isCapabilityShown(caps[0])).toEqual(true);
expect(scope.isCapabilityShown(caps[1])).toEqual(false);
// Check that only capabilities with passed tests are shown.
scope.testStatus = 'failed';
expect(scope.isCapabilityShown(caps[0])).toEqual(false);
expect(scope.isCapabilityShown(caps[1])).toEqual(true);
// Check that only capabilities with flagged tests are shown.
scope.testStatus = 'flagged';
expect(scope.isCapabilityShown(caps[0])).toEqual(true);
expect(scope.isCapabilityShown(caps[1])).toEqual(false);
});
it('should have a method to determine whether a test should be shown',
function () {
var cap = {'id': 'cap_id_1',
'passedTests': ['test_id_1'],
'notPassedTests': [],
'passedFlagged': ['test_id_1'],
'notPassedFlagged': []
};
expect(scope.isTestShown('test_id_1', cap)).toEqual(true);
scope.testStatus = 'passed';
expect(scope.isTestShown('test_id_1', cap)).toEqual(true);
scope.testStatus = 'failed';
expect(scope.isTestShown('test_id_1', cap)).toEqual(false);
scope.testStatus = 'flagged';
expect(scope.isTestShown('test_id_1', cap)).toEqual(true);
});
it('should have a method to determine how many tests belong under ' +
'the current test filter',
function () {
var cap = {'id': 'cap_id_1',
'passedTests': ['t1', 't2', 't3'],
'notPassedTests': ['t4', 't5', 't6', 't7'],
'passedFlagged': ['t1'],
'notPassedFlagged': ['t3', 't4']
};
// Should return the count of all tests.
expect(scope.getTestCount(cap)).toEqual(7);
// Should return the count of passed tests.
scope.testStatus = 'passed';
expect(scope.getTestCount(cap)).toEqual(3);
// Should return the count of failed tests.
scope.testStatus = 'failed';
expect(scope.getTestCount(cap)).toEqual(4);
// Should return the count of flagged tests.
scope.testStatus = 'flagged';
expect(scope.getTestCount(cap)).toEqual(3);
});
});
});