Merge "Replaces the delete button with a disabling version"

This commit is contained in:
Zuul 2018-10-25 18:54:57 +00:00 committed by Gerrit Code Review
commit 235d4c5e9e
5 changed files with 189 additions and 5 deletions

View File

@ -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"
}
});
}
})();

View File

@ -0,0 +1 @@
<delete-image-selected selected="tCtrl.selected"></delete-image-selected>

View File

@ -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';
}
})();

View File

@ -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 = '<div>Mock</div>';
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(
'<delete-image-selected selected="selected" callback="callback">' +
'</delete-image-selected>'
);
$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);
});
});
})();

View File

@ -0,0 +1,7 @@
<action action-classes="'btn btn-danger'"
disabled="$ctrl.disabled"
item="$ctrl.selected"
callback="$ctrl.callback">
<span class="fa fa-trash"></span>
{{ $ctrl.text }}
</action>