Flavors panel can switch to Angular
This patch enables all of the features for the Flavors panel to use Angular but disables it, so that it is easy to switch on/off. Note that we add integration test switches since it can't read the Django conf. Note that I changed the common tests to allow for testing of api calls that don't produce error toasts, because we needed better branch coverage, and the deleteFlavor api wasn't fully branch-tested. Change-Id: I92b1b57bd486e5eb87179cb8d44b7551e9de2e0f Partially-Implements: blueprint ng-flavors
This commit is contained in:
parent
dd6c644755
commit
e8e84c2db4
@ -432,7 +432,8 @@ This example sorts flavors by vcpus in descending order::
|
||||
Default::
|
||||
|
||||
{
|
||||
'images_panel': True
|
||||
'images_panel': True,
|
||||
'flavors_panel': False,
|
||||
}
|
||||
|
||||
A dictionary of currently available AngularJS features. This allows simple
|
||||
|
@ -16,13 +16,24 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
|
||||
from openstack_dashboard.dashboards.admin.flavors import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||
url(r'^(?P<id>[^/]+)/update/$', views.UpdateView.as_view(), name='update'),
|
||||
]
|
||||
if settings.ANGULAR_FEATURES['flavors_panel']:
|
||||
# New angular panel
|
||||
urlpatterns = [
|
||||
url(r'^$', views.AngularIndexView.as_view(), name='index'),
|
||||
url(r'^create/$', views.AngularIndexView.as_view(), name='create'),
|
||||
url(r'^(?P<id>[^/]+)/update/$', views.AngularIndexView.as_view(),
|
||||
name='index'),
|
||||
]
|
||||
else:
|
||||
urlpatterns = [
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||
url(r'^(?P<id>[^/]+)/update/$',
|
||||
views.UpdateView.as_view(), name='update'),
|
||||
]
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views import generic
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
@ -34,6 +35,10 @@ from openstack_dashboard.dashboards.admin.flavors \
|
||||
INDEX_URL = "horizon:admin:flavors:index"
|
||||
|
||||
|
||||
class AngularIndexView(generic.TemplateView):
|
||||
template_name = 'angular.html'
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = project_tables.FlavorsTable
|
||||
template_name = 'admin/flavors/index.html'
|
||||
|
@ -1,46 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* 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 horizon.dashboard.admin
|
||||
* @ngModule
|
||||
*
|
||||
* @description
|
||||
* Dashboard module to host various admin panels.
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.admin', [
|
||||
'horizon.dashboard.admin.flavors'
|
||||
])
|
||||
.config(config);
|
||||
|
||||
config.$inject = [
|
||||
'$provide',
|
||||
'$windowProvider'
|
||||
];
|
||||
|
||||
/**
|
||||
* @name horizon.dashboard.admin.basePath
|
||||
* @description Base path for the admin dashboard
|
||||
*/
|
||||
function config($provide, $windowProvider) {
|
||||
var path = $windowProvider.$get().STATIC_URL + 'dashboard/admin/';
|
||||
$provide.constant('horizon.dashboard.admin.basePath', path);
|
||||
}
|
||||
|
||||
})();
|
@ -1,38 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* 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.dashboard.admin', function() {
|
||||
var staticUrl, basePath;
|
||||
|
||||
beforeEach(module('horizon.dashboard.admin'));
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
staticUrl = $injector.get('$window').STATIC_URL;
|
||||
basePath = $injector.get('horizon.dashboard.admin.basePath');
|
||||
}));
|
||||
|
||||
it('should exist', function() {
|
||||
expect(angular.module('horizon.dashboard.admin')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should set path properly', function () {
|
||||
expect(basePath).toEqual(staticUrl + 'dashboard/admin/');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})();
|
@ -1,44 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* 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.dashboard.admin.flavors')
|
||||
.filter('hasExtras', hasExtrasFilter);
|
||||
|
||||
hasExtrasFilter.$inject = [];
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name hasExtrasFilter
|
||||
* @description
|
||||
* If input is defined and has more than one property return 'Yes' else return 'No'
|
||||
*
|
||||
*/
|
||||
function hasExtrasFilter() {
|
||||
return function check(input) {
|
||||
if (input &&
|
||||
angular.isObject(input) &&
|
||||
!angular.isArray(input) &&
|
||||
Object.keys(input).length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
})();
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* 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.dashboard.admin.flavors.hasExtras', function() {
|
||||
|
||||
var hasExtras;
|
||||
|
||||
beforeEach(module('horizon.framework.util.i18n'));
|
||||
beforeEach(module('horizon.dashboard.admin.flavors'));
|
||||
|
||||
beforeEach(inject(function(_hasExtrasFilter_) {
|
||||
hasExtras = _hasExtrasFilter_;
|
||||
}));
|
||||
|
||||
it('returns Yes when key is present', function() {
|
||||
var input = { 1: 'test' };
|
||||
expect(hasExtras(input)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns No when object is undefined or has no properties', function() {
|
||||
expect(hasExtras()).not.toBeTruthy();
|
||||
expect(hasExtras({})).not.toBeTruthy();
|
||||
expect(hasExtras('string')).not.toBeTruthy();
|
||||
expect(hasExtras(1)).not.toBeTruthy();
|
||||
expect(hasExtras([1])).not.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* 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 horizon.dashboard.admin.flavors
|
||||
* @ngModule
|
||||
*
|
||||
* @description
|
||||
* Provides all of the services and widgets required
|
||||
* to support and display the flavors panel.
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.admin.flavors', []);
|
||||
|
||||
})();
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* 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.dashboard.admin.flavors', function () {
|
||||
it('should exist', function () {
|
||||
expect(angular.module('horizon.dashboard.admin.flavors')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
@ -1,87 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* 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.dashboard.admin.flavors')
|
||||
.controller('FlavorsTableController', FlavorsTableController);
|
||||
|
||||
FlavorsTableController.$inject = [
|
||||
'horizon.app.core.openstack-service-api.nova'
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngdoc FlavorsTableController
|
||||
* @ngController
|
||||
*
|
||||
* @description
|
||||
* Controller for the flavors panel.
|
||||
* Serves as the focal point for table actions.
|
||||
*/
|
||||
function FlavorsTableController(
|
||||
nova
|
||||
) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.flavors = [];
|
||||
ctrl.iflavors = [];
|
||||
|
||||
ctrl.searchFacets = getSearchFacets();
|
||||
|
||||
init();
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
function init() {
|
||||
nova.getFlavors(true, true).then(onGetFlavors);
|
||||
}
|
||||
|
||||
function onGetFlavors(response) {
|
||||
ctrl.flavors = response.data.items;
|
||||
}
|
||||
|
||||
function getSearchFacets() {
|
||||
return [
|
||||
{
|
||||
label: gettext('Name'),
|
||||
name: 'name',
|
||||
singleton: true
|
||||
},
|
||||
{
|
||||
label: gettext('VCPUs'),
|
||||
name: 'vcpus',
|
||||
singleton: true
|
||||
},
|
||||
{
|
||||
label: gettext('RAM'),
|
||||
name: 'ram',
|
||||
singleton: true
|
||||
},
|
||||
{
|
||||
label: gettext('Public'),
|
||||
name: 'os-flavor-access:is_public',
|
||||
singleton: true,
|
||||
options: [
|
||||
{label: gettext('Yes'), key: 'true'},
|
||||
{label: gettext('No'), key: 'false'}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
})();
|
@ -1,77 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* 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.dashboard.admin.flavors.controller.FlavorsTableController', function () {
|
||||
|
||||
var flavors = [{id: '1'}, {id: '2'}];
|
||||
|
||||
var novaAPI = {
|
||||
getFlavors: function() {
|
||||
var deferred = $q.defer();
|
||||
deferred.resolve({data: {items: flavors}});
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
var controller, $q, $scope;
|
||||
|
||||
beforeEach(module('horizon.framework'));
|
||||
|
||||
beforeEach(module('horizon.app.core.openstack-service-api', function($provide) {
|
||||
$provide.value('horizon.app.core.openstack-service-api.nova', novaAPI);
|
||||
}));
|
||||
|
||||
beforeEach(module('horizon.dashboard.admin', function($provide) {
|
||||
$provide.constant('horizon.dashboard.admin.basePath', '/a/sample/path/');
|
||||
}));
|
||||
|
||||
beforeEach(module('horizon.dashboard.admin.flavors'));
|
||||
|
||||
beforeEach(inject(function ($injector, _$rootScope_) {
|
||||
$scope = _$rootScope_.$new();
|
||||
$q = $injector.get('$q');
|
||||
controller = $injector.get('$controller');
|
||||
}));
|
||||
|
||||
function createController() {
|
||||
return controller('FlavorsTableController', {});
|
||||
}
|
||||
|
||||
it('should set facets for search', function () {
|
||||
var ctrl = createController();
|
||||
expect(ctrl.searchFacets).toBeDefined();
|
||||
expect(ctrl.searchFacets.length).toEqual(4);
|
||||
expect(ctrl.searchFacets[0].name).toEqual('name');
|
||||
expect(ctrl.searchFacets[1].name).toEqual('vcpus');
|
||||
expect(ctrl.searchFacets[2].name).toEqual('ram');
|
||||
expect(ctrl.searchFacets[3].name).toEqual('os-flavor-access:is_public');
|
||||
});
|
||||
|
||||
it('should invoke nova apis', function() {
|
||||
spyOn(novaAPI, 'getFlavors').and.callThrough();
|
||||
|
||||
var ctrl = createController();
|
||||
$scope.$apply();
|
||||
|
||||
expect(novaAPI.getFlavors).toHaveBeenCalled();
|
||||
expect(ctrl.flavors).toEqual(flavors);
|
||||
expect(ctrl.iflavors).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})();
|
@ -1,63 +0,0 @@
|
||||
<div ng-controller="FlavorsTableController as table">
|
||||
|
||||
<hz-magic-search-context filter-facets="table.searchFacets">
|
||||
<hz-magic-search-bar>
|
||||
</hz-magic-search-bar>
|
||||
|
||||
<table st-magic-search
|
||||
hz-table ng-cloak
|
||||
st-table="table.iflavors"
|
||||
st-safe-src="table.flavors"
|
||||
class="table table-striped table-rsp table-detail">
|
||||
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<th class="select-col">
|
||||
<input type="checkbox" hz-select-all="table.iflavors">
|
||||
</th>
|
||||
<th class="rsp-p1" st-sort="name" translate>Flavor Name</th>
|
||||
<th class="rsp-p1" st-sort="vcpus" translate>VCPUs</th>
|
||||
<th class="rsp-p1" st-sort="ram" st-sort-default="ram" translate>RAM</th>
|
||||
<th class="rsp-p2" st-sort="disk" translate>Root Disk</th>
|
||||
<th class="rsp-p2" st-sort="ephermal" translate>Ephemeral Disk</th>
|
||||
<th class="rsp-p2" st-sort="swap" translate>Swap Disk</th>
|
||||
<th class="rsp-p2" st-sort="rxtx_factor" translate>RX/TX factor</th>
|
||||
<th class="rsp-p2" st-sort="id" translate>ID</th>
|
||||
<th class="rsp-p2" st-sort="is_public" translate>Public</th>
|
||||
<th class="rsp-p2" st-sort="metadata" translate>Metadata</th>
|
||||
<th class="rsp-p2"></th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="flavor in table.iflavors track by flavor.id">
|
||||
<td class="select-col">
|
||||
<input type="checkbox"
|
||||
ng-model="tCtrl.selections[flavor.id].checked"
|
||||
hz-select="flavor">
|
||||
</td>
|
||||
<td class="rsp-p1">{$ flavor.name $}</td>
|
||||
<td class="rsp-p1">{$ flavor.vcpus $}</td>
|
||||
<td class="rsp-p1">{$ flavor.ram | mb $}</td>
|
||||
<td class="rsp-p2">{$ flavor.disk | gb $}</td>
|
||||
<td class="rsp-p2">{$ flavor.ephemeral | gb $}</td>
|
||||
<td class="rsp-p2">{$ flavor.swap | mb $}</td>
|
||||
<td class="rsp-p2">{$ flavor.rxtx_factor $}</td>
|
||||
<td class="rsp-p2">{$ flavor.id $}</td>
|
||||
<td class="rsp-p2">{$ flavor.is_public | yesno $}</td>
|
||||
<td class="rsp-p2">{$ flavor.extras | hasExtras | yesno $}</td>
|
||||
<td class="rsp-p2">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr hz-no-items items="table.iflavors"></tr>
|
||||
|
||||
</tbody>
|
||||
|
||||
<tfoot hz-table-footer items="table.iflavors"></tfoot>
|
||||
|
||||
</table>
|
||||
</hz-magic-search-context>
|
||||
</div>
|
@ -21,7 +21,6 @@ ADD_INSTALLED_APPS = [
|
||||
]
|
||||
|
||||
ADD_ANGULAR_MODULES = [
|
||||
'horizon.dashboard.admin',
|
||||
]
|
||||
|
||||
AUTO_DISCOVER_STATIC_FILES = True
|
||||
|
@ -1,3 +1,18 @@
|
||||
# (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.
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'flavors'
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
|
@ -1,31 +0,0 @@
|
||||
# (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
# (c) Copyright 2015 ThoughtWorks, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# The slug of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'admin'
|
||||
|
||||
# The slug of the panel group the PANEL is associated with.
|
||||
# If you want the panel to show up without a panel group,
|
||||
# use the panel group "default".
|
||||
PANEL_GROUP = 'admin'
|
||||
|
||||
# The slug of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'ngflavors'
|
||||
|
||||
# If set to True, this settings file will not be added to the settings.
|
||||
DISABLED = True
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'openstack_dashboard.dashboards.admin.ngflavors.panel.NGFlavors'
|
@ -2,4 +2,4 @@
|
||||
# wanting to only test legacy panels. Since 'local' modules are evaluated
|
||||
# after settings.py, these configurations will override the default settings.
|
||||
|
||||
ANGULAR_FEATURES.update({"images_panel": False})
|
||||
ANGULAR_FEATURES.update({"images_panel": False, "flavors_panel": False})
|
||||
|
@ -305,6 +305,7 @@ COMPRESS_OFFLINE_CONTEXT = 'horizon.themes.offline_context'
|
||||
# Dictionary of currently available angular features
|
||||
ANGULAR_FEATURES = {
|
||||
'images_panel': True,
|
||||
'flavors_panel': False,
|
||||
}
|
||||
|
||||
# Notice all customizable configurations should be above this line
|
||||
|
@ -33,6 +33,7 @@
|
||||
.module('horizon.app.core', [
|
||||
'horizon.app.core.conf',
|
||||
'horizon.app.core.cloud-services',
|
||||
'horizon.app.core.flavors',
|
||||
'horizon.app.core.images',
|
||||
'horizon.app.core.metadata',
|
||||
'horizon.app.core.openstack-service-api',
|
||||
|
121
openstack_dashboard/static/app/core/flavors/flavors.module.js
Normal file
121
openstack_dashboard/static/app/core/flavors/flavors.module.js
Normal file
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* (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';
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @ngname horizon.app.core.flavors
|
||||
*
|
||||
* @description
|
||||
* Provides all of the services and widgets required
|
||||
* to support and display flavors related content.
|
||||
*/
|
||||
angular
|
||||
.module('horizon.app.core.flavors', [
|
||||
'ngRoute',
|
||||
'horizon.framework.conf',
|
||||
'horizon.app.core'
|
||||
])
|
||||
.constant('horizon.app.core.flavors.resourceType', 'OS::Nova::Flavor')
|
||||
.run(run)
|
||||
.config(config);
|
||||
|
||||
run.$inject = [
|
||||
'horizon.framework.conf.resource-type-registry.service',
|
||||
'horizon.app.core.flavors.basePath',
|
||||
'horizon.app.core.flavors.service',
|
||||
'horizon.app.core.flavors.resourceType'
|
||||
];
|
||||
|
||||
function run(registry, basePath, flavorsService, flavorResourceType) {
|
||||
registry.getResourceType(flavorResourceType)
|
||||
.setNames(gettext('Flavor'), gettext('Flavors'))
|
||||
.setSummaryTemplateUrl(basePath + 'summary.html')
|
||||
.setProperties(flavorProperties())
|
||||
.setListFunction(flavorsService.getFlavorsPromise)
|
||||
.tableColumns
|
||||
.append({
|
||||
id: 'name',
|
||||
priority: 1
|
||||
})
|
||||
.append({
|
||||
id: 'vcpus',
|
||||
priority: 2
|
||||
})
|
||||
.append({
|
||||
id: 'ram',
|
||||
priority: 1,
|
||||
sortDefault: true
|
||||
})
|
||||
.append({
|
||||
id: 'disk',
|
||||
priority: 2
|
||||
})
|
||||
.append({
|
||||
id: 'id',
|
||||
priority: 1
|
||||
})
|
||||
.append({
|
||||
id: 'os-flavor-access:is_public',
|
||||
priority: 2
|
||||
});
|
||||
|
||||
/**
|
||||
* @name roleProperties
|
||||
* @description resource properties for flavor module
|
||||
*/
|
||||
function flavorProperties() {
|
||||
return {
|
||||
name: gettext('Flavor Name'),
|
||||
vcpus: gettext('VCPUs'),
|
||||
ram: {label: gettext('RAM'), filters: ['mb']},
|
||||
disk: {label: gettext('Root Disk'), filters: ['gb']},
|
||||
'OS-FLV-EXT-DATA:ephemeral': {label: gettext('Ephmeral Disk'), filters: ['gb']},
|
||||
swap: {label: gettext('Swap Disk'), filters: ['gb']},
|
||||
rxtx_factor: gettext('RX/TX Factor'),
|
||||
id: gettext('ID'),
|
||||
'os-flavor-access:is_public': {label: gettext('Public'), filters: ['yesno']},
|
||||
metadata: gettext('Metadata')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
config.$inject = [
|
||||
'$provide',
|
||||
'$windowProvider',
|
||||
'$routeProvider'
|
||||
];
|
||||
|
||||
/**
|
||||
* @name config
|
||||
* @param {Object} $provide
|
||||
* @param {Object} $windowProvider
|
||||
* @param {Object} $routeProvider
|
||||
* @description Routes used by this module.
|
||||
* @returns {undefined} Returns nothing
|
||||
*/
|
||||
function config($provide, $windowProvider, $routeProvider) {
|
||||
var path = $windowProvider.$get().STATIC_URL + 'app/core/flavors/';
|
||||
$provide.constant('horizon.app.core.flavors.basePath', path);
|
||||
|
||||
$routeProvider.when('/admin/flavors', {
|
||||
templateUrl: path + 'panel.html'
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* (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.flavors')
|
||||
.factory('horizon.app.core.flavors.service', flavorsService);
|
||||
|
||||
flavorsService.$inject = [
|
||||
'horizon.app.core.openstack-service-api.nova'
|
||||
];
|
||||
|
||||
function flavorsService(nova) {
|
||||
return {
|
||||
getFlavorsPromise: getFlavorsPromise
|
||||
};
|
||||
|
||||
function getFlavorsPromise(params) {
|
||||
var fullParams = angular.extend({}, params, {get_extras: true});
|
||||
return nova.getFlavors(fullParams);
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* (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('flavors service', function() {
|
||||
var service;
|
||||
beforeEach(module('horizon.framework.util'));
|
||||
beforeEach(module('horizon.framework.conf'));
|
||||
beforeEach(module('horizon.app.core.flavors'));
|
||||
beforeEach(inject(function($injector) {
|
||||
service = $injector.get('horizon.app.core.flavors.service');
|
||||
}));
|
||||
|
||||
describe('getFlavorsPromise', function() {
|
||||
it("provides a promise that gets translated", inject(function($q, $injector) {
|
||||
var glance = $injector.get('horizon.app.core.openstack-service-api.nova');
|
||||
var deferred = $q.defer();
|
||||
spyOn(glance, 'getFlavors').and.returnValue(deferred.promise);
|
||||
service.getFlavorsPromise({});
|
||||
deferred.resolve({data: {items: [{id: 1, updated_at: 'jul1'}]}});
|
||||
expect(glance.getFlavors).toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
})();
|
3
openstack_dashboard/static/app/core/flavors/panel.html
Normal file
3
openstack_dashboard/static/app/core/flavors/panel.html
Normal file
@ -0,0 +1,3 @@
|
||||
<hz-resource-panel resource-type-name="OS::Nova::Flavor">
|
||||
<hz-resource-table resource-type-name="OS::Nova::Flavor"></hz-resource-table>
|
||||
</hz-resource-panel>
|
19
openstack_dashboard/static/app/core/flavors/summary.html
Normal file
19
openstack_dashboard/static/app/core/flavors/summary.html
Normal file
@ -0,0 +1,19 @@
|
||||
<hz-resource-property-list
|
||||
resource-type-name="OS::Nova::Flavor"
|
||||
item="item"
|
||||
property-groups="[
|
||||
['name', 'id'],
|
||||
['vcpus', 'ram'],
|
||||
['disk', 'OS-FLV-EXT-DATA:ephemeral', 'swap'],
|
||||
['rxtx_factor']]">
|
||||
</hz-resource-property-list>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<dl>
|
||||
<div ng-repeat="(key, value) in item.extras">
|
||||
<dt>{$ key $}</dt>
|
||||
<dd>{$ value $}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
@ -468,14 +468,8 @@
|
||||
* call per flavor).
|
||||
* @returns {Object} The result of the API call
|
||||
*/
|
||||
function getFlavors(isPublic, getExtras) {
|
||||
var config = {'params': {}};
|
||||
if (isPublic) {
|
||||
config.params.is_public = 'true';
|
||||
}
|
||||
if (getExtras) {
|
||||
config.params.get_extras = 'true';
|
||||
}
|
||||
function getFlavors(params) {
|
||||
var config = params ? { 'params' : params} : { 'params' : {} };
|
||||
return apiService.get('/api/nova/flavors/', config)
|
||||
.success(function (data) {
|
||||
// The colon character ':' in the flavor data causes problems when used
|
||||
|
@ -286,8 +286,6 @@
|
||||
},
|
||||
"error": "Unable to retrieve the flavors.",
|
||||
"testInput": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -301,8 +299,7 @@
|
||||
},
|
||||
"error": "Unable to retrieve the flavors.",
|
||||
"testInput": [
|
||||
true,
|
||||
false
|
||||
{is_public: "true"}
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -316,8 +313,7 @@
|
||||
},
|
||||
"error": "Unable to retrieve the flavors.",
|
||||
"testInput": [
|
||||
false,
|
||||
true
|
||||
{get_extras: "true"}
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -332,8 +328,7 @@
|
||||
},
|
||||
"error": "Unable to retrieve the flavors.",
|
||||
"testInput": [
|
||||
true,
|
||||
true
|
||||
{is_public: "true", get_extras: "true"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -102,6 +102,12 @@ SeleniumGroup = [
|
||||
help="Is the browser size maximized for each test?"),
|
||||
]
|
||||
|
||||
FlavorsGroup = [
|
||||
cfg.StrOpt('panel_type',
|
||||
default='legacy',
|
||||
help='type/version of flavors panel'),
|
||||
]
|
||||
|
||||
ScenarioGroup = [
|
||||
cfg.StrOpt('ssh_user',
|
||||
default='cirros',
|
||||
@ -163,6 +169,7 @@ def get_config():
|
||||
cfg.CONF.register_opts(NetworkGroup, group="network")
|
||||
cfg.CONF.register_opts(AvailableServiceGroup, group="service_available")
|
||||
cfg.CONF.register_opts(SeleniumGroup, group="selenium")
|
||||
cfg.CONF.register_opts(FlavorsGroup, group="flavors")
|
||||
cfg.CONF.register_opts(ImageGroup, group="image")
|
||||
cfg.CONF.register_opts(ScenarioGroup, group="scenario")
|
||||
cfg.CONF.register_opts(InstancesGroup, group="launch_instances")
|
||||
|
@ -33,6 +33,9 @@ explicit_wait=90
|
||||
# (boolean)
|
||||
maximize_browser=yes
|
||||
|
||||
[flavors]
|
||||
panel_type=legacy
|
||||
|
||||
[image]
|
||||
# http accessible image (string value)
|
||||
panel_type=angular
|
||||
|
@ -15,6 +15,8 @@ from openstack_dashboard.test.integration_tests.regions import forms
|
||||
from openstack_dashboard.test.integration_tests.regions import menus
|
||||
from openstack_dashboard.test.integration_tests.regions import tables
|
||||
|
||||
from selenium.webdriver.common import by
|
||||
|
||||
|
||||
class FlavorsTable(tables.TableRegion):
|
||||
name = "flavors"
|
||||
@ -144,3 +146,12 @@ class FlavorsPage(basepage.BaseNavigationPage):
|
||||
def is_flavor_public(self, name):
|
||||
row = self._get_flavor_row(name)
|
||||
return row.cells[self.FLAVORS_TABLE_PUBLIC_COLUMN].text == "Yes"
|
||||
|
||||
|
||||
class FlavorsPageNG(FlavorsPage):
|
||||
_resource_page_header_locator = (by.By.CSS_SELECTOR,
|
||||
'hz-resource-panel hz-page-header h1')
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return self._get_element(*self._resource_page_header_locator)
|
||||
|
@ -12,10 +12,28 @@
|
||||
|
||||
import random
|
||||
|
||||
from openstack_dashboard.test.integration_tests import decorators
|
||||
from openstack_dashboard.test.integration_tests import helpers
|
||||
from openstack_dashboard.test.integration_tests.regions import messages
|
||||
|
||||
|
||||
@decorators.config_option_required('flavors.panel_type', 'angular',
|
||||
message="Legacy Panels not tested")
|
||||
class TestFlavorAngular(helpers.AdminTestCase):
|
||||
@property
|
||||
def flavors_page(self):
|
||||
from openstack_dashboard.test.integration_tests.pages.admin.\
|
||||
system.flavorspage import FlavorsPageNG
|
||||
self.home_pg.go_to_system_flavorspage()
|
||||
return FlavorsPageNG(self.driver, self.CONFIG)
|
||||
|
||||
def test_basic_flavors_browse(self):
|
||||
flavors_page = self.flavors_page
|
||||
self.assertEqual(flavors_page.header.text, 'Flavors')
|
||||
|
||||
|
||||
@decorators.config_option_required('flavors.panel_type', 'legacy',
|
||||
message="Angular Panels not tested")
|
||||
class TestFlavors(helpers.AdminTestCase):
|
||||
FLAVOR_NAME = helpers.gen_random_resource_name("flavor")
|
||||
|
||||
@ -48,6 +66,18 @@ class TestFlavors(helpers.AdminTestCase):
|
||||
self.assertFalse(
|
||||
self.flavors_page.is_flavor_present(self.FLAVOR_NAME))
|
||||
|
||||
def test_flavor_header(self):
|
||||
header_text = self.driver.find_element_by_tag_name('h1').text
|
||||
self.assertEqual(header_text, 'Flavors')
|
||||
|
||||
def test_flavor_module_exists(self):
|
||||
js_cmd = "$('html').append('<div id=\"testonly\">'"\
|
||||
" + angular.module('horizon.app.core.flavors').name"\
|
||||
" + '</div>');"
|
||||
self.driver.execute_script(js_cmd)
|
||||
value = self.driver.find_element_by_id('testonly').text
|
||||
self.assertEqual(value, 'horizon.app.core.flavors')
|
||||
|
||||
def test_flavor_create(self):
|
||||
"""tests the flavor creation and deletion functionalities:
|
||||
* creates a new flavor
|
||||
|
@ -99,7 +99,8 @@ HORIZON_CONFIG = {
|
||||
}
|
||||
|
||||
ANGULAR_FEATURES = {
|
||||
'images_panel': False # Use the legacy panel so unit tests are still run
|
||||
'images_panel': False, # Use the legacy panel so unit tests are still run
|
||||
'flavors_panel': False,
|
||||
}
|
||||
|
||||
STATICFILES_DIRS = settings_utils.get_xstatic_dirs(
|
||||
|
13
releasenotes/notes/flavor-panel-switch-6b5cd5f0964f4ba3.yaml
Normal file
13
releasenotes/notes/flavor-panel-switch-6b5cd5f0964f4ba3.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
prelude: >
|
||||
The Flavor panel now may be configured to use
|
||||
either the legacy or Angular code.
|
||||
features:
|
||||
- ANGULAR_FEATURES now allows for a key 'flavors_panel' to be
|
||||
specified as True or False indicating whether the Angular
|
||||
version of the panel is enabled.
|
||||
- Integration tests for Flavor features may also be toggled
|
||||
in openstack_dashboard/test/integration_tests/horizon.conf
|
||||
using the 'panel_type' feature in the 'flavors' setting,
|
||||
either set to 'legacy' or 'angular' to match the enabled
|
||||
panel type.
|
Loading…
Reference in New Issue
Block a user