Image uses hz-property for its drawer information

The Image drawer should use the hz-property so it is kept in sync with all other
uses of the properties (in tables, etc.).

Gets rid of filters in favor of simple mappings or functions.

Moves several functions out of the module into a basic images-service so
it's easy to access/test functions.

Change-Id: I4d881744a398f9aa076830b576cb9f66335d8ba9
Partially-Implements: blueprint angular-registry
This commit is contained in:
Matt Borland 2016-06-24 14:04:49 -06:00
parent 9e4e8e00d5
commit d6c0cdc6d3
9 changed files with 216 additions and 275 deletions

View File

@ -1,34 +1,14 @@
<div ng-controller="horizon.app.core.images.DrawerController as drawerCtrl">
<div class="row">
<dl class="col-md-2">
<dt translate>Is Public</dt>
<dd>{$ item.is_public | yesno $}</dd>
</dl>
<dl class="col-md-2">
<dt translate>Protected</dt>
<dd>{$ item.protected | yesno $}</dd>
</dl>
<dl class="col-md-2">
<dt translate>Format</dt>
<dd>{$ item.disk_format | noValue | uppercase $}</dd>
</dl>
<dl class="col-md-2">
<dt translate>Size</dt>
<dd>{$ item.size | bytes $}</dd>
</dl>
</div>
<div class="row">
<dl class="col-md-2">
<dt translate>Min. Disk</dt>
<dd>{$ item.min_disk | gb | noValue $}</dd>
</dl>
<dl class="col-md-2">
<dt translate>Min. RAM</dt>
<dd>{$ item.min_ram | mb | noValue $}</dd>
</dl>
</div>
<hz-resource-property-list
resource-type-name="OS::Glance::Image"
item="item"
property-groups="[
['name', 'id'],
['is_public', 'protected'],
['disk_format', 'size'],
['min_disk', 'min_ram']]">
</hz-resource-property-list>
<div class="row" ng-if="drawerCtrl.metadataDefs">
<div class="col-sm-12">

View File

@ -3,29 +3,21 @@
<div class="col-md-6 detail">
<h3 translate>Image</h3>
<hr>
<dl class="dl-horizontal">
<dt translate>Type</dt>
<dd>{$ ctrl.image | imageType $}</dd>
<dt translate>Filename</dt>
<dd>{$ ctrl.image.properties.filename $}</dd>
<dt translate>Status</dt>
<dd>{$ ctrl.image.status $}</dd>
<dt translate>Size</dt>
<dd>{$ ctrl.image.size | bytes $}</dd>
<dt translate>Min. Disk</dt>
<dd>{$ ctrl.image.min_disk | gb $}</dd>
<dt translate>Min. RAM</dt>
<dd>{$ ctrl.image.min_ram | mb $}</dd>
<dt translate>Disk Format</dt>
<dd>{$ ctrl.image.disk_format | uppercase $}</dd>
<dt translate>Container Format</dt>
<dd>{$ ctrl.image.container_format | uppercase $}</dd>
</dl>
<hz-resource-property-list
resource-type-name="OS::Glance::Image"
cls="dl-horizontal"
item="ctrl.image"
property-groups="[[
'type', 'status', 'size', 'min_disk', 'min_ram', 'disk_format',
'container_format']]">
</hz-resource-property-list>
</div>
<div class="col-md-6 detail">
<h3>{$ 'Security' | translate $}</h3>
<hr>
<dl class="dl-horizontal">
<dt translate>Filename</dt>
<dd>{$ ctrl.image.properties.filename $}</dd>
<dt translate>Visibility</dt>
<dd>{$ ctrl.image | imageVisibility $}</dd>
<dt translate>Protected</dt>

View File

@ -1,53 +0,0 @@
/**
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.images')
.filter('imageStatus', imageStatusFilter);
imageStatusFilter.$inject = [
'horizon.framework.util.i18n.gettext'
];
function imageStatusFilter(gettext) {
var imageStatuses = {
'active': gettext('Active'),
'saving': gettext('Saving'),
'queued': gettext('Queued'),
'pending_delete': gettext('Pending Delete'),
'killed': gettext('Killed'),
'deleted': gettext('Deleted')
};
return filter;
/**
* @ngdoc filter
* @name imageStatusFilter
* @param {string} input - The status code
* @description
* Takes raw image status from the API and returns the user friendly status.
* @returns {string} The user-friendly status
*/
function filter(input) {
var result = imageStatuses[input];
return angular.isDefined(result) ? result : input;
}
}
}());

View File

@ -1,40 +0,0 @@
/**
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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('horizon.app.core.images.imageStatus Filter', function () {
beforeEach(module('horizon.framework.util.i18n'));
beforeEach(module('horizon.app.core.images'));
describe('iumageStatus', function () {
var imageStatusFilter;
beforeEach(inject(function (_imageStatusFilter_) {
imageStatusFilter = _imageStatusFilter_;
}));
it('Returns value when key is present', function () {
expect(imageStatusFilter('active')).toBe('Active');
});
it('Returns input when key is not present', function () {
expect(imageStatusFilter('unknown')).toBe('unknown');
});
});
});
})();

View File

@ -1,50 +0,0 @@
/**
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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.images')
.filter('imageType', imageTypeFilter);
imageTypeFilter.$inject = [
'horizon.framework.util.i18n.gettext'
];
function imageTypeFilter(gettext) {
return filter;
/**
* @ngdoc filter
* @name imageTypeFilter
* @param {Object} input - An image object
* @description
* Takes a raw image object from the API and returns the user friendly type.
* @returns {Function} The filter
*/
function filter(input) {
if (null !== input &&
angular.isDefined(input) &&
angular.isDefined(input.properties) &&
input.properties.image_type === 'snapshot') {
return gettext('Snapshot');
} else {
return gettext('Image');
}
}
}
}());

View File

@ -1,48 +0,0 @@
/**
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
*
* 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('horizon.app.core.images.imageType Filter', function () {
beforeEach(module('horizon.framework.util.i18n'));
beforeEach(module('horizon.app.core.images'));
describe('imageType', function () {
var imageTypeFilter;
beforeEach(inject(function (_imageTypeFilter_) {
imageTypeFilter = _imageTypeFilter_;
}));
it('returns Snapshot for snapshot', function () {
expect(imageTypeFilter({properties:{image_type:'snapshot'}})).toBe('Snapshot');
});
it('returns Image for image', function () {
expect(imageTypeFilter({properties:{image_type:'image'}})).toBe('Image');
});
it('returns Image for null', function () {
expect(imageTypeFilter(null)).toBe('Image');
});
it('returns Image for undefined', function () {
expect(imageTypeFilter()).toBe('Image');
});
});
});
})();

View File

@ -36,17 +36,26 @@
.constant('horizon.app.core.images.validationRules', validationRules())
.constant('horizon.app.core.images.imageFormats', imageFormats())
.constant('horizon.app.core.images.resourceType', 'OS::Glance::Image')
.constant('horizon.app.core.images.statuses', {
'active': gettext('Active'),
'saving': gettext('Saving'),
'queued': gettext('Queued'),
'pending_delete': gettext('Pending Delete'),
'killed': gettext('Killed'),
'deleted': gettext('Deleted')
})
.run(run)
.config(config);
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.app.core.openstack-service-api.glance',
'horizon.app.core.images.basePath',
'horizon.app.core.images.service',
'horizon.app.core.images.statuses',
'horizon.app.core.images.resourceType'
];
function run(registry, glance, basePath, imageResourceType) {
function run(registry, basePath, imagesService, statuses, imageResourceType) {
registry.getResourceType(imageResourceType)
.setNames(gettext('Image'), gettext('Images'))
.setSummaryTemplateUrl(basePath + 'details/drawer.html')
@ -54,19 +63,26 @@
label: gettext('Checksum')
})
.setProperty('container_format', {
label: gettext('Container Format')
label: gettext('Container Format'),
filters: ['uppercase']
})
.setProperty('created_at', {
label: gettext('Created At')
})
.setProperty('disk_format', {
label: gettext('Disk Format')
label: gettext('Disk Format'),
filters: ['noValue', 'uppercase']
})
.setProperty('id', {
label: gettext('ID')
})
.setProperty('is_public', {
label: gettext('Is Public'),
filters: ['yesno']
})
.setProperty('type', {
label: gettext('Type')
label: gettext('Type'),
filters: [imagesService.imageType]
})
.setProperty('members', {
label: gettext('Members')
@ -84,13 +100,16 @@
label: gettext('Owner')
})
.setProperty('protected', {
label: gettext('Protected')
label: gettext('Protected'),
filters: ['yesno']
})
.setProperty('size', {
label: gettext('Size')
label: gettext('Size'),
filters: ['bytes']
})
.setProperty('status', {
label: gettext('Status')
label: gettext('Status'),
values: statuses
})
.setProperty('tags', {
label: gettext('Tags')
@ -116,38 +135,33 @@
.setProperty('ramdisk_id', {
label: gettext('Ramdisk ID')
})
.setListFunction(listFunction)
.setListFunction(imagesService.getImagesPromise)
.tableColumns
.append({
id: 'name',
priority: 1,
sortDefault: true,
urlFunction: urlFunction
urlFunction: imagesService.getDetailsPath
})
.append({
id: 'type',
priority: 1,
filters: ['imageType']
priority: 1
})
.append({
id: 'status',
priority: 1,
filters: ['imageStatus']
priority: 1
})
.append({
id: 'protected',
priority: 1,
filters: ['yesno']
priority: 1
})
.append({
id: 'disk_format',
priority: 2,
filters: ['noValue', 'uppercase']
priority: 2
})
.append({
id: 'size',
priority: 2,
filters: ['bytes']
priority: 2
});
registry.getResourceType(imageResourceType).filterFacets
@ -214,23 +228,6 @@
isServer: true,
singleton: true
});
function listFunction(params) {
return glance.getImages(params).then(modifyResponse);
function modifyResponse(response) {
return {data: {items: response.data.items.map(addTrackBy)}};
function addTrackBy(image) {
image.trackBy = image.id + image.updated_at;
return image;
}
}
}
function urlFunction(item) {
return 'project/ngdetails/OS::Glance::Image/' + item.id;
}
}
/**

View File

@ -0,0 +1,97 @@
/*
* (c) Copyright 2016 Hewlett Packard Enterprise Development LP
*
* 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.images')
.factory('horizon.app.core.images.service', imageService);
imageService.$inject = [
'horizon.app.core.openstack-service-api.glance'
];
/*
* @ngdoc factory
* @name horizon.app.core.images.service
*
* @description
* This service provides functions that are used through the Images
* features. These are primarily used in the module registrations
* but do not need to be restricted to such use. Each exposed function
* is documented below.
*/
function imageService(glance) {
return {
getDetailsPath: getDetailsPath,
getImagesPromise: getImagesPromise,
imageType: imageType
};
/*
* @ngdoc function
* @name getDetailsPath
* @param item {Object} - The image object
* @description
* Given an Image object, returns the relative path to the details
* view.
*/
function getDetailsPath(item) {
return 'project/ngdetails/OS::Glance::Image/' + item.id;
}
/*
* @ngdoc function
* @name imageType
* @param item {Object} - The image object
* @description
* Given an Image object, returns a name describing the type of image,
* either an 'Image' or a 'Snapshot' type.
*/
function imageType(item) {
if (null !== item &&
angular.isDefined(item) &&
angular.isDefined(item.properties) &&
item.properties.image_type === 'snapshot') {
return gettext('Snapshot');
} else {
return gettext('Image');
}
}
/*
* @ngdoc function
* @name getImagesPromise
* @description
* Given filter/query parameters, returns a promise for the matching
* images. This is used in displaying lists of Images. In this case,
* we need to modify the API's response by adding a composite value called
* 'trackBy' to assist the display mechanism when updating rows.
*/
function getImagesPromise(params) {
return glance.getImages(params).then(modifyResponse);
function modifyResponse(response) {
return {data: {items: response.data.items.map(addTrackBy)}};
function addTrackBy(image) {
image.trackBy = image.id + image.updated_at;
return image;
}
}
}
}
})();

View File

@ -0,0 +1,66 @@
/*
* (c) Copyright 2016 Hewlett Packard Enterprise Development LP
*
* 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('images service', function() {
var service;
beforeEach(module('horizon.app.core.images'));
beforeEach(inject(function($injector) {
service = $injector.get('horizon.app.core.images.service');
}));
it("getDetailsPath creates urls using the item's ID", function() {
var myItem = {id: "1234"};
expect(service.getDetailsPath(myItem)).toBe('project/ngdetails/OS::Glance::Image/1234');
});
describe('imageType', function() {
it("imageType returns Snapshot when appropriate", function() {
var myItem = {properties: {image_type: 'snapshot'}};
expect(service.imageType(myItem)).toBe('Snapshot');
});
it("imageType returns Image when no item", function() {
var myItem;
expect(service.imageType(myItem)).toBe('Image');
});
it("imageType returns Image when no properties", function() {
var myItem = {};
expect(service.imageType(myItem)).toBe('Image');
});
it("imageType returns Image when properties but not type 'snapshot'", function() {
var myItem = {properties: {image_type: 'unknown'}};
expect(service.imageType(myItem)).toBe('Image');
});
});
describe('getImagesPromise', function() {
it("provides a promise that gets translated", inject(function($q, $injector, $timeout) {
var glance = $injector.get('horizon.app.core.openstack-service-api.glance');
var deferred = $q.defer();
spyOn(glance, 'getImages').and.returnValue(deferred.promise);
var result = service.getImagesPromise({});
deferred.resolve({data: {items: [{id: 1, updated_at: 'jul1'}]}});
$timeout.flush();
expect(result.$$state.value.data.items[0].trackBy).toBe('1jul1');
}));
});
});
})();