Merge "Angularize Key Pair Details"

This commit is contained in:
Jenkins 2017-10-07 04:47:12 +00:00 committed by Gerrit Code Review
commit 631df03dfe
16 changed files with 287 additions and 16 deletions

View File

@ -20,6 +20,7 @@
.module('horizon.framework.util.filters')
.filter('yesno', yesNoFilter)
.filter('simpleDate', simpleDateFilter)
.filter('mediumDate', mediumDateFilter)
.filter('gb', gbFilter)
.filter('mb', mbFilter)
.filter('title', titleFilter)
@ -59,6 +60,19 @@
};
}
/**
* @ngdoc filter
* @name mediumDate
* @description
* Evaluates given for display as a medium date, returning '-' if empty.
*/
mediumDateFilter.$inject = ['$filter'];
function mediumDateFilter($filter) {
return function (input) {
return $filter('noValue')($filter('date')(input, 'medium'));
};
}
/**
* @ngdoc filter
* @name gb

View File

@ -67,6 +67,21 @@
});
});
describe('mediumDate', function () {
var mediumDateFilter;
beforeEach(inject(function (_mediumDateFilter_) {
mediumDateFilter = _mediumDateFilter_;
}));
it('returns blank if nothing', function () {
expect(mediumDateFilter()).toBe('-');
});
it('returns the expected time', function() {
expect(mediumDateFilter('2001-02-03T16:05:06')).toBe('Feb 3, 2001 4:05:06 PM');
});
});
describe('gb', function () {
var gbFilter;
beforeEach(inject(function (_gbFilter_) {

View File

@ -80,6 +80,17 @@ class Keypairs(generic.View):
)
@urls.register
class Keypair(generic.View):
"""API for retrieving a single keypair."""
url_regex = r'nova/keypairs/(?P<name>[^/]+)$'
@rest_utils.ajax()
def get(self, request, name):
"""Get a specific keypair."""
return api.nova.keypair_get(request, name).to_dict()
@urls.register
class Services(generic.View):
"""API for nova services."""

View File

@ -28,6 +28,9 @@ if settings.ANGULAR_FEATURES.get('key_pairs_panel'):
title = _("Key Pairs")
urlpatterns = [
url('', views.AngularIndexView.as_view(title=title), name='index'),
url(r'^(?P<keypair_name>[^/]+)/$',
views.AngularIndexView.as_view(title=title),
name='detail'),
]
else:
urlpatterns = [

View File

@ -1,9 +1,23 @@
$details-content-fold-width: ($screen-md-min - $sidebar-width) !default;
hz-resource-property-list[resource-type-name="OS::Nova::Keypair"] {
hz-resource-property[prop-name="public_key"] dd {
overflow-wrap: break-word;
width: calc(100vw - 61px);
@media (min-width: 992px) {
width: calc(100vw - 281px);
width: calc(100vw - calc(#{$padding-large-horizontal} * 4));
@media (min-width: $details-content-fold-width) {
width: calc(100vw - #{$sidebar-width} - calc(#{$padding-large-horizontal} * 4));
}
}
}
hz-details {
hz-resource-property-list[resource-type-name="OS::Nova::Keypair"] {
hz-resource-property[prop-name="public_key"] dd {
overflow-wrap: break-word;
width: calc(100vw - #{$dl-horizontal-offset} - calc(#{$padding-large-horizontal} * 4));
@media (min-width: $details-content-fold-width) {
width: calc(100vw - #{$sidebar-width} - #{$dl-horizontal-offset} - calc(#{$padding-large-horizontal} * 4));
}
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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('horizon.app.core.keypairs')
.controller('horizon.app.core.keypairs.DetailsController', KeypairDetailsController);
KeypairDetailsController.$inject = ['$scope'];
function KeypairDetailsController($scope) {
var ctrl = this;
ctrl.keypair = {};
$scope.context.loadPromise.then(onGetKeypair);
function onGetKeypair(response) {
ctrl.keypair = response.data;
}
}
})();

View File

@ -0,0 +1,42 @@
/*
* 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';
describe('keypair details controller', function() {
var ctrl, deferred, nova;
beforeEach(module('horizon.app.core.keypairs'));
beforeEach(inject(function($controller, $q, $injector) {
nova = $injector.get('horizon.app.core.openstack-service-api.nova');
deferred = $q.defer();
deferred.resolve({data: {name: 1}});
spyOn(nova, 'getKeypair').and.returnValue(deferred.promise);
ctrl = $controller('horizon.app.core.keypairs.DetailsController',
{
'$scope' : {context : {loadPromise: deferred.promise}}
}
);
}));
it('sets ctrl', inject(function($timeout) {
$timeout.flush();
expect(ctrl.keypair).toBeDefined();
}));
});
})();

View File

@ -0,0 +1,12 @@
<div ng-controller="horizon.app.core.keypairs.DetailsController as ctrl">
<div class="row">
<div class="col-md-12 detail">
<hz-resource-property-list
resource-type-name="OS::Nova::Keypair"
cls="dl-horizontal"
item="ctrl.keypair"
property-groups="[['id', 'name', 'fingerprint', 'created_at', 'user_id', 'public_key']]">
</hz-resource-property-list>
</div>
</div>
</div>

View File

@ -0,0 +1,51 @@
/*
* 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';
/**
* @ngdoc overview
* @ngname horizon.app.core.keypairs.details
*
* @description
* Provides details features for keypair.
*/
angular.module('horizon.app.core.keypairs.details',
['horizon.framework.conf', 'horizon.app.core'])
.run(registerKeypairDetails);
registerKeypairDetails.$inject = [
'horizon.app.core.keypairs.basePath',
'horizon.app.core.keypairs.resourceType',
'horizon.app.core.keypairs.service',
'horizon.framework.conf.resource-type-registry.service'
];
function registerKeypairDetails(
basePath,
resourceType,
keypairService,
registry
) {
registry.getResourceType(resourceType)
.setLoadFunction(keypairService.getKeypairPromise)
.detailsViews.append({
id: 'keypairDetails',
name: gettext('Details'),
template: basePath + 'details/details.html'
});
}
})();

View File

@ -28,8 +28,7 @@
angular
.module('horizon.app.core.keypairs', [
'ngRoute',
'horizon.app.core',
'horizon.framework.conf'
'horizon.app.core.keypairs.details'
])
.constant('horizon.app.core.keypairs.resourceType', 'OS::Nova::Keypair')
.run(run)
@ -54,7 +53,8 @@
.append({
id: 'name',
priority: 1,
sortDefault: true
sortDefault: true,
urlFunction: keypairsService.urlFunction
})
.append({
id: 'fingerprint',
@ -75,7 +75,7 @@
'id': {label: gettext('ID'), filters: ['noValue'] },
'name': {label: gettext('Name'), filters: ['noName'] },
'fingerprint': {label: gettext('Fingerprint'), filters: ['noValue'] },
'created_at': {label: gettext('Created'), filters: ['simpleDate'] },
'created_at': {label: gettext('Created'), filters: ['mediumDate'] },
'user_id': {label: gettext('User ID'), filters: ['noValue'] },
'public_key': {label: gettext('Public Key'), filters: ['noValue'] }
};
@ -84,7 +84,8 @@
config.$inject = [
'$provide',
'$windowProvider',
'$routeProvider'
'$routeProvider',
'horizon.app.core.detailRoute'
];
/**
@ -92,14 +93,22 @@
* @param {Object} $provide
* @param {Object} $windowProvider
* @param {Object} $routeProvider
* @param {Object} detailRoute
* @description Routes used by this module.
* @returns {undefined} Returns nothing
*/
function config($provide, $windowProvider, $routeProvider) {
function config($provide, $windowProvider, $routeProvider, detailRoute) {
var path = $windowProvider.$get().STATIC_URL + 'app/core/keypairs/';
$provide.constant('horizon.app.core.keypairs.basePath', path);
$routeProvider.when('/project/key_pairs', {
templateUrl: path + 'panel.html'
})
.when('/project/key_pairs/:id', {
redirectTo: goToAngularDetails
});
function goToAngularDetails(params) {
return detailRoute + 'OS::Nova::Keypair/' + params.id;
}
}
})();

View File

@ -40,7 +40,6 @@
expect(names).toContain('name');
function getName(x) {
// underscore.js and .pluck() would be great here.
return x.name;
}
});

View File

@ -20,6 +20,7 @@
keypairsService.$inject = [
'$filter',
'horizon.app.core.detailRoute',
'horizon.app.core.openstack-service-api.nova'
];
@ -33,9 +34,11 @@
* but do not need to be restricted to such use. Each exposed function
* is documented below.
*/
function keypairsService($filter, nova) {
function keypairsService($filter, detailRoute, nova) {
return {
getKeypairsPromise: getKeypairsPromise
getKeypairsPromise: getKeypairsPromise,
getKeypairPromise: getKeypairPromise,
urlFunction: urlFunction
};
/*
@ -61,6 +64,18 @@
}
}
}
/*
* @ngdoc function
* @name getKeypairPromise
* @description
* Given a name, returns a promise for the keypair data.
*/
function getKeypairPromise(name) {
return nova.getKeypair(name);
}
function urlFunction(item) {
return detailRoute + 'OS::Nova::Keypair/' + item.name;
}
}
})();

View File

@ -21,10 +21,11 @@
});
describe('keypairsService', function() {
var service;
var service, detailRoute;
beforeEach(module('horizon.app.core'));
beforeEach(inject(function($injector) {
service = $injector.get('horizon.app.core.keypairs.service');
detailRoute = $injector.get('horizon.app.core.detailRoute');
}));
describe('getKeypairsPromise', function() {
@ -49,6 +50,23 @@
}));
});
});
describe('getKeypairPromise', function() {
it("provides a promise", inject(function($q, $injector) {
var nova = $injector.get('horizon.app.core.openstack-service-api.nova');
var deferred = $q.defer();
spyOn(nova, 'getKeypair').and.returnValue(deferred.promise);
var result = service.getKeypairPromise('keypair1');
deferred.resolve({data: {keypair: {name: 'keypair1'}}});
expect(nova.getKeypair).toHaveBeenCalled();
expect(result.$$state.value.data.keypair.name).toBe('keypair1');
}));
});
describe('urlFunction', function() {
it("get url", inject(function() {
var result = service.urlFunction({name: "123abc"});
expect(result).toBe(detailRoute + "OS::Nova::Keypair/123abc");
}));
});
});
})();

View File

@ -44,6 +44,7 @@
getServerSecurityGroups: getServerSecurityGroups,
getKeypairs: getKeypairs,
createKeypair: createKeypair,
getKeypair: getKeypair,
getAvailabilityZones: getAvailabilityZones,
getLimits: getLimits,
createServer: createServer,
@ -141,6 +142,23 @@
});
}
/**
* @name getKeypair
* @description
* Get a single keypair by name.
*
* @param {string} name
* The name of the keypair. Required.
*
* @returns {Object} The result of the API call.
*/
function getKeypair(name) {
return apiService.get('/api/nova/keypairs/' + name)
.error(function () {
toastService.add('error', gettext('Unable to retrieve the keypair.'));
});
}
// Availability Zones
/**

View File

@ -133,6 +133,13 @@
{}
]
},
{
"func": "getKeypair",
"method": "get",
"path": "/api/nova/keypairs/19",
"error": "Unable to retrieve the keypair.",
"testInput": [19]
},
{
"func": "deleteServer",
"method": "delete",

View File

@ -172,7 +172,7 @@ class NovaRestTestCase(test.TestCase):
# Keypairs
#
@mock.patch.object(nova.api, 'nova')
def test_keypair_get(self, nc):
def test_keypair_list(self, nc):
request = self.mock_rest_request()
nc.keypair_list.return_value = [
mock.Mock(**{'to_dict.return_value': {'id': 'one'}}),
@ -214,6 +214,16 @@ class NovaRestTestCase(test.TestCase):
self.assertEqual('/api/nova/keypairs/Ni%21', response['location'])
nc.keypair_import.assert_called_once_with(request, 'Ni!', 'hi')
@mock.patch.object(nova.api, 'nova')
def test_keypair_get(self, nc):
request = self.mock_rest_request()
nc.keypair_get.return_value.to_dict.return_value = {'name': '1'}
response = nova.Keypair().get(request, '1')
self.assertStatusCode(response, 200)
self.assertEqual({"name": "1"},
response.json)
nc.keypair_get.assert_called_once_with(request, "1")
#
# Availability Zones
#