RefStack should allow associating specific guideline/target.

Since a user may have tested against a specific guideline version
and target program, we should allow users to associate these specifics
to their test results.

Closes-Bug: #1477392
Co-Authored-By: Paul Van Eck <pvaneck@us.ibm.com>
Change-Id: If0bf28f79e11b1ba83465d19e3d10953386d8864
This commit is contained in:
david liu 2015-11-27 14:58:02 +08:00
parent 3f08865327
commit f41721e069
7 changed files with 220 additions and 12 deletions

View File

@ -188,3 +188,7 @@ h1, h2, h3, h4, h5, h6 {
.test-detail ul {
padding-left: 20px;
}
a.glyphicon {
text-decoration: none;
}

View File

@ -34,9 +34,11 @@
<div class="row">
<div class="col-md-3">
<strong>Capabilities Version:</strong>
<select ng-model="ctrl.version" ng-change="ctrl.updateCapabilities()" class="form-control">
<!-- Slicing the version file name here gets rid of the '.json' file extension -->
<option ng-repeat="versionFile in ctrl.versionList" value="{{versionFile}}">{{versionFile.slice(0, -5)}}</option>
<!-- Slicing the version file name here gets rid of the '.json' file extension -->
<select ng-model="ctrl.version"
ng-change="ctrl.updateCapabilities()"
class="form-control"
ng-options="versionFile.slice(0,-5) for versionFile in ctrl.versionList">
</select>
</div>
<div class="col-md-4">

View File

@ -89,7 +89,9 @@
ctrl.versionsRequest =
$http.get(content_url).success(function (data) {
ctrl.versionList = data.sort().reverse();
ctrl.version = ctrl.versionList[0];
if (!ctrl.version) {
ctrl.version = ctrl.versionList[0];
}
ctrl.updateCapabilities();
}).error(function (error) {
ctrl.showError = true;
@ -109,6 +111,10 @@
ctrl.resultsRequest =
$http.get(content_url).success(function (data) {
ctrl.resultsData = data;
ctrl.version = ctrl.resultsData.meta.guideline;
if (ctrl.resultsData.meta.target) {
ctrl.target = ctrl.resultsData.meta.target;
}
getVersionList();
}).error(function (error) {
ctrl.showError = true;

View File

@ -38,12 +38,15 @@
</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-show="ctrl.data" class="table table-striped table-hover">
<thead>
<tr>
<th ng-if="ctrl.isUserResults"></th>
<th>Upload Date</th>
<th>Test Run ID</th>
<th ng-if="ctrl.isUserResults">Shared</th>
@ -51,10 +54,96 @@
</thead>
<tbody>
<tr ng-repeat="result in ctrl.data.results">
<tr ng-repeat-start="(index, result) in ctrl.data.results">
<td ng-if="ctrl.isUserResults">
<a ng-if="!result.expanded"
class="glyphicon glyphicon-plus"
ng-click="result.expanded = true">
</a>
<a ng-if="result.expanded"
class="glyphicon glyphicon-minus"
ng-click="result.expanded = false">
</a>
</td>
<td>{{result.created_at}}</td>
<td><a ui-sref="resultsDetail({testID: result.id})">{{result.id}}</a></td>
<td ng-if="ctrl.isUserResults"><span ng-show="result.meta.shared" class="glyphicon glyphicon-share"></span></td>
<td ng-if="ctrl.isUserResults">
<span ng-show="result.meta.shared" class="glyphicon glyphicon-share"></span>
</td>
</tr>
<tr ng-if="result.expanded" ng-repeat-end>
<td></td>
<td colspan="3">
<strong>Publicly Shared:</strong>
<span ng-if="result.meta.shared == 'true' && !result.sharedEdit">Yes</span>
<span ng-if="!result.meta.shared && !result.sharedEdit">
<em>No</em>
</span>
<select ng-if="result.sharedEdit"
ng-model="result.meta.shared"
class="form-inline">
<option value="true">Share</option>
<option value="">Unshare</option>
</select>
<a ng-if="!result.sharedEdit"
ng-click="result.sharedEdit = true"
title="Edit"
class="glyphicon glyphicon-pencil"></a>
<a ng-if="result.sharedEdit"
ng-click="ctrl.associateMeta(index,'shared',result.meta.shared)"
title="Save"
class="glyphicon glyphicon-floppy-disk"></a>
<br />
<strong>Associated Guideline:</strong>
<span ng-if="!result.meta.guideline && !result.guidelineEdit">
<em>None</em>
</span>
<span ng-if="result.meta.guideline && !result.guidelineEdit">
{{result.meta.guideline.slice(0, -5)}}
</span>
<select ng-if="result.guidelineEdit"
ng-model="result.meta.guideline"
ng-options="o as o.slice(0, -5) for o in ctrl.versionList"
class="form-inline">
<option value="">None</option>
</select>
<a ng-if="!result.guidelineEdit"
ng-click="ctrl.getVersionList();result.guidelineEdit = true"
title="Edit"
class="glyphicon glyphicon-pencil"></a>
<a ng-if="result.guidelineEdit"
ng-click="ctrl.associateMeta(index, 'guideline', result.meta.guideline)"
title="Save"
class="glyphicon glyphicon-floppy-disk">
</a>
<br />
<strong>Associated Target Program:</strong>
<span ng-if="!result.meta.target && !result.targetEdit">
<em>None</em>
</span>
<span ng-if="result.meta.target && !result.targetEdit">
{{ctrl.targetMappings[result.meta.target]}}</span>
<select ng-if="result.targetEdit"
ng-model="result.meta.target"
class="form-inline">
<option value="">None</option>
<option value="platform">OpenStack Powered Platform</option>
<option value="compute">OpenStack Powered Compute</option>
<option value="object">OpenStack Powered Object Storage</option>
</select>
<a ng-if="!result.targetEdit"
ng-click="result.targetEdit = true"
title="Edit"
class="glyphicon glyphicon-pencil">
</a>
<a ng-if="result.targetEdit"
ng-click="ctrl.associateMeta(index, 'target', result.meta.target)"
title="Save"
class="glyphicon glyphicon-floppy-disk">
</a>
</td>
</tr>
</tbody>
</table>
@ -79,4 +168,3 @@
<span class="sr-only">Error:</span>
{{ctrl.error}}
</div>

View File

@ -20,7 +20,7 @@
.controller('ResultsController', ResultsController);
ResultsController.$inject = [
'$scope', '$http', '$filter', '$state', 'refstackApiUrl'
'$scope', '$http', '$filter', '$state', 'refstackApiUrl','raiseAlert'
];
/**
@ -28,12 +28,22 @@
* This controller is for the '/results' page where a user can browse
* a listing of community uploaded results.
*/
function ResultsController($scope, $http, $filter, $state, refstackApiUrl) {
function ResultsController($scope, $http, $filter, $state, refstackApiUrl,
raiseAlert) {
var ctrl = this;
ctrl.update = update;
ctrl.open = open;
ctrl.clearFilters = clearFilters;
ctrl.associateMeta = associateMeta;
ctrl.getVersionList = getVersionList;
/** Mappings of DefCore 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;
@ -143,5 +153,58 @@
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 = [
refstackApiUrl, '/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) {
raiseAlert('danger', error.title, error.detail);
});
}
}
/**
* Retrieve an array of available capability files from the Refstack
* 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 = refstackApiUrl + '/capabilities';
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');
});
}
}
})();

View File

@ -160,7 +160,7 @@ describe('Refstack controllers', function () {
'pagination': {'current_page': 1, 'total_pages': 2},
'results': [{
'created_at': '2015-03-09 01:23:45',
'test_id': 'some-id',
'id': 'some-id',
'cpid': 'some-cpid'
}]
};
@ -212,11 +212,56 @@ describe('Refstack controllers', function () {
expect(ctrl.startDate).toBe(null);
expect(ctrl.endDate).toBe(null);
});
it('should have a function to associate metadata to a test run',
function () {
$httpBackend.expectGET(fakeApiUrl + '/results?page=1')
.respond(fakeResponse);
ctrl.data = fakeResponse;
ctrl.data.results[0].targetEdit = true;
ctrl.associateMeta(0, 'target', 'platform');
$httpBackend.expectPOST(
fakeApiUrl + '/results/some-id/meta/target',
'platform')
.respond(201, '');
$httpBackend.flush();
expect(ctrl.data.results[0].targetEdit).toBe(false);
});
it('should have a function to delete metadata from a test run',
function () {
$httpBackend.expectGET(fakeApiUrl + '/results?page=1')
.respond(fakeResponse);
ctrl.data = fakeResponse;
ctrl.data.results[0].targetEdit = true;
ctrl.associateMeta(0, 'target', '');
$httpBackend.expectDELETE(
fakeApiUrl + '/results/some-id/meta/target')
.respond(200, '');
$httpBackend.flush();
expect(ctrl.data.results[0].targetEdit).toBe(false);
});
it('should have a function to get guideline versions',
function () {
$httpBackend.expectGET(fakeApiUrl + '/results?page=1')
.respond(fakeResponse);
$httpBackend.expectGET(fakeApiUrl +
'/capabilities').respond(['2015.03.json', '2015.04.json']);
ctrl.getVersionList();
$httpBackend.flush();
// Expect the list to have the latest guideline first.
expect(ctrl.versionList).toEqual(['2015.04.json',
'2015.03.json']);
});
});
describe('ResultsReportController', function () {
var stateparams, ctrl;
var fakeResultResponse = {'results': ['test_id_1']};
var fakeResultResponse = {'results': ['test_id_1'], 'meta': {
'public_key': 'ssh-rsa', 'guideline': '2015.04.json', 'target':
'object'
}};
var fakeCapabilityResponse = {
'platform': {'required': ['compute']},
'schema': '1.2',

View File

@ -36,7 +36,7 @@ CONF = cfg.CONF
class MetadataController(rest.RestController):
"""/v1/results/<test_id>/meta handler."""
rw_access_keys = ('shared',)
rw_access_keys = ('shared', 'guideline', 'target',)
@pecan.expose('json')
def get(self, test_id):