From 71cac4a145d6726703d76ce66e1787df80c3f3cf Mon Sep 17 00:00:00 2001 From: mareklycka Date: Mon, 23 Jul 2018 09:45:55 +0200 Subject: [PATCH] Replaces the delete button with a disabling version Replaces the default delete button template for the images batch delete action with a custom one. This template contains a new component, which changes the disabled/enabled state of the underlying action based on the selected images allowed value as specified by the delete-image service used to evaluate the allowed value for individual rows/images and their respective delete actions. Closes-Bug: 1549544 Change-Id: Idd5f59e32268cf4ceefe3639c607fb6c3520e538 --- .../app/core/images/actions/actions.module.js | 15 ++- ...delete-image-selected-button.template.html | 1 + .../delete-image-selected.component.js | 74 ++++++++++++++ .../delete-image-selected.component.spec.js | 97 +++++++++++++++++++ .../delete-image-selected.template.html | 7 ++ 5 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 openstack_dashboard/static/app/core/images/actions/delete-image-selected-button.template.html create mode 100644 openstack_dashboard/static/app/core/images/actions/delete-image-selected.component.js create mode 100644 openstack_dashboard/static/app/core/images/actions/delete-image-selected.component.spec.js create mode 100644 openstack_dashboard/static/app/core/images/actions/delete-image-selected.template.html diff --git a/openstack_dashboard/static/app/core/images/actions/actions.module.js b/openstack_dashboard/static/app/core/images/actions/actions.module.js index 6fb6bcb64e..a49af8c263 100644 --- a/openstack_dashboard/static/app/core/images/actions/actions.module.js +++ b/openstack_dashboard/static/app/core/images/actions/actions.module.js @@ -38,7 +38,8 @@ 'horizon.app.core.images.actions.delete-image.service', 'horizon.app.core.images.actions.launch-instance.service', 'horizon.app.core.images.actions.update-metadata.service', - 'horizon.app.core.images.resourceType' + 'horizon.app.core.images.resourceType', + 'horizon.app.core.images.basePath' ]; function registerImageActions( @@ -49,7 +50,8 @@ deleteImageService, launchInstanceService, updateMetadataService, - imageResourceTypeCode + imageResourceTypeCode, + basePath ) { var imageResourceType = registry.getResourceType(imageResourceTypeCode); imageResourceType.itemActions @@ -100,15 +102,18 @@ } }); + // A custom template is provided instead of the 'standard' definition + // to customize when the rendered button is disabled + // + // The template contains a new angular component which controls the + // disabled/enabled state of the rendered button. imageResourceType.batchActions .append({ id: 'batchDeleteImageAction', service: deleteImageService, template: { - type: 'delete-selected', - text: gettext('Delete Images') + url: basePath + "/actions/delete-image-selected-button.template.html" } }); } - })(); diff --git a/openstack_dashboard/static/app/core/images/actions/delete-image-selected-button.template.html b/openstack_dashboard/static/app/core/images/actions/delete-image-selected-button.template.html new file mode 100644 index 0000000000..517db0cab3 --- /dev/null +++ b/openstack_dashboard/static/app/core/images/actions/delete-image-selected-button.template.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/openstack_dashboard/static/app/core/images/actions/delete-image-selected.component.js b/openstack_dashboard/static/app/core/images/actions/delete-image-selected.component.js new file mode 100644 index 0000000000..e6745abbd5 --- /dev/null +++ b/openstack_dashboard/static/app/core/images/actions/delete-image-selected.component.js @@ -0,0 +1,74 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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 component generally renders the same content as the default batch action +// button with the added complexity of changing the buttons enabled/disabled +// stated based on the 'allowed' state of the passed selected images. +(function() { + 'use strict'; + + angular + .module('horizon.app.core.images.actions') + .component('deleteImageSelected', { + controller: controller, + templateUrl: templateUrl, + bindings: { + callback: '=?', + selected: '<' + } + }); + + controller.$inject = [ + 'horizon.app.core.images.actions.delete-image.service', + '$q' + ]; + + function controller(deleteImageService, $q) { + var ctrl = this; + + ctrl.$onInit = function() { + ctrl.text = gettext('Delete Images'); + ctrl._disable(); + }; + + ctrl.$onChanges = function() { + ctrl._disable(); + }; + + ctrl._disable = function() { + if (ctrl.selected.length === 0) { + ctrl.disabled = true; + } else { + var promises = $.map(ctrl.selected, function(image) { + return deleteImageService.allowed(image); + }); + + $q.all(promises).then( + function() { + ctrl.disabled = false; + }, + function() { + ctrl.disabled = true; + } + ); + } + }; + } + + templateUrl.$inject = ['horizon.app.core.images.basePath']; + + function templateUrl(basePath) { + return basePath + 'actions/delete-image-selected.template.html'; + } + +})(); diff --git a/openstack_dashboard/static/app/core/images/actions/delete-image-selected.component.spec.js b/openstack_dashboard/static/app/core/images/actions/delete-image-selected.component.spec.js new file mode 100644 index 0000000000..dd6639a060 --- /dev/null +++ b/openstack_dashboard/static/app/core/images/actions/delete-image-selected.component.spec.js @@ -0,0 +1,97 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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('delete-image-selected component', function() { + var $scope, $element, $controller, $q; + // Mock image data + var mockAllowed = { allowed: true }; + var mockDisallowed = { allowed: false }; + + beforeEach(module('templates')); + beforeEach(module('horizon.app.core.images.actions', function($provide) { + // Injects a mock 'action' directive for unit testing + $provide.decorator('actionDirective', function($delegate) { + var component = $delegate[0]; + + component.template = '
Mock
'; + component.templateUrl = null; + + return $delegate; + }); + + // Mock delete-image.service. The disabling mechanism uses the allowed + // function from that service using the promises API, which is mocked + // here. + $provide.service( + 'horizon.app.core.images.actions.delete-image.service', function() { + return { + allowed: function(mockImage) { + var deferred = $q.defer(); + if (mockImage.allowed) { + deferred.resolve(); + } else { + deferred.reject(); + } + return deferred.promise; + } + }; + } + ); + })); + beforeEach(inject(function(_$rootScope_, _$compile_, _$q_) { + $q = _$q_; + $scope = _$rootScope_.$new(); + var tag = angular.element( + '' + + '' + ); + + $scope.selected = []; + + $element = _$compile_(tag)($scope); + $scope.$apply(); + + $controller = $element.controller('deleteImageSelected'); + })); + + it('disables for empty list', function() { + expect($controller.disabled).toBe(true); + }); + + it('enables for all allowed images', function() { + // Selections change the object; just pushing in new values wouldn't + // trigger disable recalculations + $scope.selected = [$.extend({}, mockAllowed)]; + $scope.$apply(); + expect($controller.disabled).toBe(false); + }); + + it('disables for all disallowed images', function() { + $scope.selected = [$.extend({}, mockDisallowed)]; + $scope.$apply(); + expect($controller.disabled).toBe(true); + }); + + it('disables for mixed images', function() { + $scope.selected = [ + $.extend({}, mockDisallowed), + $.extend({}, mockDisallowed) + ]; + $scope.$apply(); + expect($controller.disabled).toBe(true); + }); + }); +})(); diff --git a/openstack_dashboard/static/app/core/images/actions/delete-image-selected.template.html b/openstack_dashboard/static/app/core/images/actions/delete-image-selected.template.html new file mode 100644 index 0000000000..3ec8c8bfb6 --- /dev/null +++ b/openstack_dashboard/static/app/core/images/actions/delete-image-selected.template.html @@ -0,0 +1,7 @@ + + + {{ $ctrl.text }} +