Add UI support for interop schema 2.0

New schema[1] will be released soon, so RefStack UI needs
to be able to handle it.

[1] https://review.openstack.org/#/c/430556

Change-Id: Ifdfe40c12a7a97ff742ed15aeb5d9ce399ee3cb1
This commit is contained in:
Paul Van Eck 2017-07-17 22:10:06 -07:00
parent c9bc0987af
commit 88e42fe9a3
8 changed files with 333 additions and 26 deletions

View File

@ -25,13 +25,13 @@
<br /> <br />
<div ng-if="ctrl.guidelines"> <div ng-if="ctrl.guidelines">
<strong>Guideline Status:</strong> <strong>Guideline Status:</strong>
{{ctrl.guidelines.status | capitalize}} {{ctrl.guidelineStatus | capitalize}}
</div> </div>
<div ng-show="ctrl.guidelines"> <div ng-show="ctrl.guidelines">
<strong>Corresponding OpenStack Releases:</strong> <strong>Corresponding OpenStack Releases:</strong>
<ul class="list-inline"> <ul class="list-inline">
<li ng-repeat="release in ctrl.guidelines.releases"> <li ng-repeat="release in ctrl.releases">
{{release | capitalize}} {{release | capitalize}}
</li> </li>
</ul> </ul>

View File

@ -87,7 +87,22 @@
ctrl.capsRequest = ctrl.capsRequest =
$http.get(content_url).success(function (data) { $http.get(content_url).success(function (data) {
ctrl.guidelines = data; ctrl.guidelines = data;
if ('metadata' in data && data.metadata.schema >= '2.0') {
ctrl.schema = data.metadata.schema;
ctrl.criteria = data.metadata.scoring.criteria;
ctrl.releases =
data.metadata.os_trademark_approval.releases;
ctrl.guidelineStatus =
data.metadata.os_trademark_approval.status;
}
else {
ctrl.schema = data.schema;
ctrl.criteria = data.criteria;
ctrl.releases = data.releases;
ctrl.guidelineStatus = data.status;
}
ctrl.updateTargetCapabilities(); ctrl.updateTargetCapabilities();
}).error(function (error) { }).error(function (error) {
ctrl.showError = true; ctrl.showError = true;
ctrl.guidelines = null; ctrl.guidelines = null;
@ -110,8 +125,24 @@
// The 'platform' target is comprised of multiple components, so // The 'platform' target is comprised of multiple components, so
// we need to get the capabilities belonging to each of its // we need to get the capabilities belonging to each of its
// components. // components.
if (ctrl.target === 'platform') { if (ctrl.target === 'platform' || ctrl.schema >= '2.0') {
var platform_components = ctrl.guidelines.platform.required; if (ctrl.schema >= '2.0') {
var platformsMap = {
'platform': 'OpenStack Powered Platform',
'compute': 'OpenStack Powered Compute',
'object': 'OpenStack Powered Storage'
};
var targetComponents = ctrl.guidelines.platforms[
platformsMap[ctrl.target]].components.map(
function(c) {
return c.name;
}
);
}
else {
var targetComponents = ctrl.guidelines.platform.required;
}
// This will contain status priority values, where lower // This will contain status priority values, where lower
// values mean higher priorities. // values mean higher priorities.
@ -123,9 +154,13 @@
}; };
// For each component required for the platform program. // For each component required for the platform program.
angular.forEach(platform_components, function (component) { angular.forEach(targetComponents, function (component) {
// Get each capability list belonging to each status. // Get each capability list belonging to each status.
angular.forEach(components[component], var componentList = components[component];
if (ctrl.schema >= '2.0') {
componentList = componentList.capabilities;
}
angular.forEach(componentList,
function (caps, status) { function (caps, status) {
// For each capability. // For each capability.
angular.forEach(caps, function(cap) { angular.forEach(caps, function(cap) {

View File

@ -19,11 +19,11 @@ variable 'guidelines'.
<a ng-click="showTests = !showTests">Tests ({{ctrl.getObjectLength(capability.tests)}})</a> <a ng-click="showTests = !showTests">Tests ({{ctrl.getObjectLength(capability.tests)}})</a>
<ul uib-collapse="!showTests"> <ul uib-collapse="!showTests">
<li ng-if="ctrl.guidelines.schema === '1.2'" ng-repeat="test in capability.tests"> <li ng-if="ctrl.schema === '1.2'" ng-repeat="test in capability.tests">
<span ng-class="{'glyphicon glyphicon-flag text-warning': capability.flagged.indexOf(test) > -1}"></span> <span ng-class="{'glyphicon glyphicon-flag text-warning': capability.flagged.indexOf(test) > -1}"></span>
{{test}} {{test}}
</li> </li>
<li ng-if="ctrl.guidelines.schema > '1.2'" ng-repeat="(testName, testDetails) in capability.tests"> <li ng-if="ctrl.schema > '1.2'" ng-repeat="(testName, testDetails) in capability.tests">
<span ng-class="{'glyphicon glyphicon-flag text-warning': testDetails.flagged}" title="{{testDetails.flagged.reason}}"></span> <span ng-class="{'glyphicon glyphicon-flag text-warning': testDetails.flagged}" title="{{testDetails.flagged.reason}}"></span>
{{testName}} {{testName}}
<div class="test-detail" ng-if="testDetails.aliases"> <div class="test-detail" ng-if="testDetails.aliases">
@ -35,14 +35,14 @@ variable 'guidelines'.
</li> </li>
</ol> </ol>
<div ng-show="ctrl.guidelines" class="criteria"> <div ng-show="ctrl.criteria" class="criteria">
<hr> <hr>
<h4><a ng-click="showCriteria = !showCriteria">Criteria</a></h4> <h4><a ng-click="showCriteria = !showCriteria">Criteria</a></h4>
<div uib-collapse="showCriteria"> <div uib-collapse="showCriteria">
<ul> <ul>
<li ng-repeat="(key, criterion) in ctrl.guidelines.criteria"> <li ng-repeat="(key, criterion) in ctrl.criteria">
<span class="criterion-name">{{criterion.name}}</span><br /> <span class="criterion-name">{{criterion.name}}</span><br />
<em>{{criterion.Description}}</em><br /> <em>{{criterion.Description || criterion.description}}</em><br />
Weight: {{criterion.weight}} Weight: {{criterion.weight}}
</li> </li>
</ul> </ul>

View File

@ -93,12 +93,12 @@
<br /> <br />
<div ng-if="ctrl.guidelineData"> <div ng-if="ctrl.guidelineData">
<strong>Guideline Status:</strong> <strong>Guideline Status:</strong>
{{ctrl.guidelineData.status | capitalize}} {{ctrl.guidelineStatus | capitalize}}
</div> </div>
<strong>Corresponding OpenStack Releases:</strong> <strong>Corresponding OpenStack Releases:</strong>
<ul class="list-inline"> <ul class="list-inline">
<li ng-repeat="release in ctrl.guidelineData.releases"> <li ng-repeat="release in ctrl.releases">
{{release | capitalize}} {{release | capitalize}}
</li> </li>
</ul> </ul>

View File

@ -228,7 +228,18 @@
ctrl.capsRequest = ctrl.capsRequest =
$http.get(content_url).success(function (data) { $http.get(content_url).success(function (data) {
ctrl.guidelineData = data; ctrl.guidelineData = data;
if ('metadata' in data && data.metadata.schema >= '2.0') {
ctrl.schemaVersion = data.metadata.schema;
ctrl.guidelineStatus =
data.metadata.os_trademark_approval.status;
ctrl.releases =
data.metadata.os_trademark_approval.releases;
}
else {
ctrl.schemaVersion = data.schema; ctrl.schemaVersion = data.schema;
ctrl.guidelineStatus = data.status;
ctrl.releases = data.releases;
}
ctrl.buildCapabilitiesObject(); ctrl.buildCapabilitiesObject();
}).error(function (error) { }).error(function (error) {
ctrl.showError = true; ctrl.showError = true;
@ -250,9 +261,24 @@
// The 'platform' target is comprised of multiple components, so // The 'platform' target is comprised of multiple components, so
// we need to get the capabilities belonging to each of its // we need to get the capabilities belonging to each of its
// components. // components.
if (ctrl.target === 'platform') { if (ctrl.target === 'platform' || ctrl.schemaVersion >= '2.0') {
var platform_components = if (ctrl.schemaVersion >= '2.0') {
ctrl.guidelineData.platform.required; var platformsMap = {
'platform': 'OpenStack Powered Platform',
'compute': 'OpenStack Powered Compute',
'object': 'OpenStack Powered Storage'
};
var targetComponents = ctrl.guidelineData.platforms[
platformsMap[ctrl.target]].components.map(
function(c) {
return c.name;
}
);
}
else {
var targetComponents = ctrl.guidelineData.platform.required;
}
// This will contain status priority values, where lower // This will contain status priority values, where lower
// values mean higher priorities. // values mean higher priorities.
@ -264,9 +290,13 @@
}; };
// For each component required for the platform program. // For each component required for the platform program.
angular.forEach(platform_components, function (component) { angular.forEach(targetComponents, function (component) {
var componentList = components[component];
if (ctrl.schemaVersion >= '2.0') {
componentList = componentList.capabilities;
}
// Get each capability list belonging to each status. // Get each capability list belonging to each status.
angular.forEach(components[component], angular.forEach(componentList,
function (caps, status) { function (caps, status) {
// For each capability. // For each capability.
angular.forEach(caps, function(cap) { angular.forEach(caps, function(cap) {
@ -422,6 +452,7 @@
case '1.4': case '1.4':
case '1.5': case '1.5':
case '1.6': case '1.6':
case '2.0':
capMethod = 'buildCapabilityV1_3'; capMethod = 'buildCapabilityV1_3';
break; break;
default: default:

View File

@ -146,7 +146,7 @@ describe('Refstack controllers', function () {
'2015.03.json']); '2015.03.json']);
expect(ctrl.guidelines).toEqual(fakeCaps); expect(ctrl.guidelines).toEqual(fakeCaps);
// The guideline status should be approved. // The guideline status should be approved.
expect(ctrl.guidelines.status).toEqual('approved'); expect(ctrl.guidelineStatus).toEqual('approved');
var expectedTargetCaps = { var expectedTargetCaps = {
'cap_id_1': 'required', 'cap_id_1': 'required',
'cap_id_2': 'advisory', 'cap_id_2': 'advisory',
@ -156,6 +156,75 @@ describe('Refstack controllers', function () {
expect(ctrl.targetCapabilities).toEqual(expectedTargetCaps); expect(ctrl.targetCapabilities).toEqual(expectedTargetCaps);
}); });
it('should be able to handle guidelines using schema 2.0',
function () {
var fakeCaps = {
'metadata': {
'id': '2017.08',
'schema': '2.0',
'scoring': {},
'os_trademark_approval': {
'target_approval': '2017.08',
'replaces': '2017.01',
'releases': ['newton', 'ocata', 'pike'],
'status': 'approved'
}
},
'platforms': {
'OpenStack Powered Platform': {
'description': 'foo bar',
'components': [
{ 'name': 'os_powered_compute' },
{ 'name': 'os_powered_storage' }
]
}
},
'components': {
'os_powered_compute': {
'capabilities': {
'required': ['cap_id_1'],
'advisory': ['cap_id_2'],
'deprecated': ['cap_id_3'],
'removed': ['cap_id_4']
}
},
'os_powered_storage': {
'capabilities': {
'required': ['cap_id_5'],
'advisory': ['cap_id_6'],
'deprecated': ['cap_id_7'],
'removed': ['cap_id_8']
}
}
}
};
$httpBackend.expectGET(fakeApiUrl +
'/guidelines').respond(['next.json', '2015.03.json',
'2017.08.json']);
// Should call request with latest version.
$httpBackend.expectGET(fakeApiUrl +
'/guidelines/2017.08.json').respond(fakeCaps);
$httpBackend.flush();
ctrl.update();
// The version list should be sorted latest first.
expect(ctrl.guidelines).toEqual(fakeCaps);
// The guideline status should be approved.
expect(ctrl.guidelineStatus).toEqual('approved');
var expectedTargetCaps = {
'cap_id_1': 'required',
'cap_id_2': 'advisory',
'cap_id_3': 'deprecated',
'cap_id_4': 'removed',
'cap_id_5': 'required',
'cap_id_6': 'advisory',
'cap_id_7': 'deprecated',
'cap_id_8': 'removed'
};
expect(ctrl.targetCapabilities).toEqual(expectedTargetCaps);
});
it('should have a function to check if a capability status is selected', it('should have a function to check if a capability status is selected',
function () { function () {
ctrl.targetCapabilities = { ctrl.targetCapabilities = {
@ -497,6 +566,63 @@ describe('Refstack controllers', function () {
expect(ctrl.getTargetCapabilities()).toEqual(expected); expect(ctrl.getTargetCapabilities()).toEqual(expected);
}); });
it('should be able create an object containing each relevant' +
'capability and its highest priority status for schema 2.0',
function () {
ctrl.schemaVersion = '2.0';
ctrl.guidelineData = {
'metadata': {
'id': '2017.08',
'schema': '2.0',
'scoring': {},
'os_trademark_approval': {
'target_approval': '2017.08',
'replaces': '2017.01',
'releases': ['newton', 'ocata', 'pike'],
'status': 'approved'
}
},
'platforms': {
'OpenStack Powered Platform': {
'description': 'foo bar',
'components': [
{ 'name': 'os_powered_compute' },
{ 'name': 'os_powered_storage' }
]
}
},
'components': {
'os_powered_compute': {
'capabilities': {
'required': ['cap_id_1'],
'advisory': ['cap_id_2'],
'deprecated': ['cap_id_3'],
'removed': ['cap_id_4']
}
},
'os_powered_storage': {
'capabilities': {
'required': ['cap_id_5'],
'advisory': ['cap_id_6'],
'deprecated': ['cap_id_7'],
'removed': ['cap_id_8']
}
}
}
};
var expected = {
'cap_id_1': 'required',
'cap_id_2': 'advisory',
'cap_id_3': 'deprecated',
'cap_id_4': 'removed',
'cap_id_5': 'required',
'cap_id_6': 'advisory',
'cap_id_7': 'deprecated',
'cap_id_8': 'removed'
};
expect(ctrl.getTargetCapabilities()).toEqual(expected);
});
it('should be able to sort the results into a capability object for ' + it('should be able to sort the results into a capability object for ' +
'schema version 1.2', 'schema version 1.2',
function () { function () {

View File

@ -110,6 +110,20 @@ class Guidelines:
are given. If not target is specified, then all capabilities are given. are given. If not target is specified, then all capabilities are given.
""" """
components = guideline_json['components'] components = guideline_json['components']
if ('metadata' in guideline_json and
guideline_json['metadata']['schema'] >= '2.0'):
schema = guideline_json['metadata']['schema']
platformsMap = {
'platform': 'OpenStack Powered Platform',
'compute': 'OpenStack Powered Compute',
'object': 'OpenStack Powered Storage'
}
comps = \
guideline_json['platforms'][platformsMap[target]]['components']
targets = (obj['name'] for obj in comps)
else:
schema = guideline_json['schema']
targets = set() targets = set()
if target != 'platform': if target != 'platform':
targets.add(target) targets.add(target)
@ -118,7 +132,10 @@ class Guidelines:
target_caps = set() target_caps = set()
for component in targets: for component in targets:
for status, capabilities in components[component].items(): complist = components[component]
if schema >= '2.0':
complist = complist['capabilities']
for status, capabilities in complist.items():
if types is None or status in types: if types is None or status in types:
target_caps.update(capabilities) target_caps.update(capabilities)
@ -134,6 +151,10 @@ class Guidelines:
included in the list. included in the list.
""" """
caps = guideline_json['capabilities'] caps = guideline_json['capabilities']
if ('metadata' in guideline_json and
guideline_json['metadata']['schema'] >= '2.0'):
schema = guideline_json['metadata']['schema']
else:
schema = guideline_json['schema'] schema = guideline_json['schema']
test_list = [] test_list = []
for cap, cap_details in caps.items(): for cap, cap_details in caps.items():

View File

@ -91,6 +91,67 @@ class GuidelinesTestCase(base.BaseTestCase):
def test_get_target_capabilities(self): def test_get_target_capabilities(self):
"""Test getting relevant capabilities.""" """Test getting relevant capabilities."""
# Schema version 2.0
json = {
'metadata': {
'id': '2017.08',
'schema': '2.0',
'scoring': {},
'os_trademark_approval': {
'target_approval': '2017.08',
'replaces': '2017.01',
'releases': ['newton', 'ocata', 'pike'],
'status': 'approved'
}
},
'platforms': {
'OpenStack Powered Platform': {
'description': 'foo platform',
'components': [
{'name': 'os_powered_compute'},
{'name': 'os_powered_storage'}
]
},
'OpenStack Powered Storage': {
'description': 'foo storage',
'components': [
{'name': 'os_powered_storage'}
]
},
},
'components': {
'os_powered_compute': {
'capabilities': {
'required': ['cap_id_1'],
'advisory': ['cap_id_2'],
'deprecated': ['cap_id_3'],
'removed': []
}
},
'os_powered_storage': {
'capabilities': {
'required': ['cap_id_5'],
'advisory': ['cap_id_6'],
'deprecated': [],
'removed': []
}
}
}
}
caps = self.guidelines.get_target_capabilities(json)
expected = sorted(['cap_id_1', 'cap_id_2', 'cap_id_3',
'cap_id_5', 'cap_id_6'])
self.assertEqual(expected, sorted(caps))
caps = self.guidelines.get_target_capabilities(json,
types=['required'],
target='object')
expected = ['cap_id_5']
self.assertEqual(expected, caps)
# Schema version 1.4
json = { json = {
'platform': {'required': ['compute', 'object']}, 'platform': {'required': ['compute', 'object']},
'schema': '1.4', 'schema': '1.4',
@ -124,6 +185,39 @@ class GuidelinesTestCase(base.BaseTestCase):
def test_get_test_list(self): def test_get_test_list(self):
"""Test when getting the guideline test list.""" """Test when getting the guideline test list."""
# Schema version 2.0
json = {
'metadata': {
'schema': '2.0',
},
'capabilities': {
'cap-1': {
'tests': {
'test_1': {'idempotent_id': 'id-1234'},
'test_2': {'idempotent_id': 'id-5678',
'aliases': ['test_2_1']},
'test_3': {'idempotent_id': 'id-1111',
'flagged': {'reason': 'foo'}}
}
},
'cap-2': {
'tests': {
'test_4': {'idempotent_id': 'id-1233'}
}
}
}
}
tests = self.guidelines.get_test_list(json, ['cap-1'])
expected = ['test_1[id-1234]', 'test_2[id-5678]',
'test_2_1[id-5678]', 'test_3[id-1111]']
self.assertEqual(expected, tests)
tests = self.guidelines.get_test_list(json, ['cap-1'],
alias=False, show_flagged=False)
expected = ['test_1[id-1234]', 'test_2[id-5678]']
self.assertEqual(expected, tests)
# Schema version 1.4 # Schema version 1.4
json = { json = {
'schema': '1.4', 'schema': '1.4',