diff --git a/openstack_dashboard/api/rest/swift.py b/openstack_dashboard/api/rest/swift.py
index d0fbf571e2..1dd4ee1580 100644
--- a/openstack_dashboard/api/rest/swift.py
+++ b/openstack_dashboard/api/rest/swift.py
@@ -154,7 +154,7 @@ class Object(generic.View):
# note: not an AJAX request - the body will be raw file content
@csrf_exempt
def post(self, request, container, object_name):
- """Create a new object or pseudo-folder
+ """Create or replace an object or pseudo-folder
:param request:
:param container:
@@ -176,23 +176,19 @@ class Object(generic.View):
data = form.clean()
- try:
- if object_name[-1] == '/':
- result = api.swift.swift_create_pseudo_folder(
- request,
- container,
- object_name
- )
- else:
- result = api.swift.swift_upload_object(
- request,
- container,
- object_name,
- data['file']
- )
- except exceptions.AlreadyExists as e:
- # 409 Conflict
- return rest_utils.JSONResponse(str(e), 409)
+ if object_name[-1] == '/':
+ result = api.swift.swift_create_pseudo_folder(
+ request,
+ container,
+ object_name
+ )
+ else:
+ result = api.swift.swift_upload_object(
+ request,
+ container,
+ object_name,
+ data['file']
+ )
return rest_utils.CreatedResponse(
u'/api/swift/containers/%s/object/%s' % (container, result.name)
diff --git a/openstack_dashboard/api/swift.py b/openstack_dashboard/api/swift.py
index 49dd46a84b..53c9c6ed9a 100644
--- a/openstack_dashboard/api/swift.py
+++ b/openstack_dashboard/api/swift.py
@@ -272,8 +272,6 @@ def swift_copy_object(request, orig_container_name, orig_object_name,
def swift_upload_object(request, container_name, object_name,
object_file=None):
- if swift_object_exists(request, container_name, object_name):
- raise exceptions.AlreadyExists(object_name, 'object')
headers = {}
size = 0
if object_file:
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-controller.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-controller.js
new file mode 100644
index 0000000000..d71250ae17
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-controller.js
@@ -0,0 +1,48 @@
+/*
+ * (c) Copyright 2015 Rackspace US, 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.
+ */
+(function () {
+ 'use strict';
+
+ angular
+ .module('horizon.dashboard.project.containers')
+ .controller(
+ 'horizon.dashboard.project.containers.EditObjectModalController',
+ EditObjectModalController
+ );
+
+ EditObjectModalController.$inject = ['fileDetails'];
+
+ function EditObjectModalController(fileDetails) {
+ var ctrl = this;
+
+ ctrl.model = {
+ container: fileDetails.container,
+ path: fileDetails.path,
+ view_file: null, // file object managed by angular form ngModel
+ edit_file: null // file object from the DOM element with the actual upload
+ };
+ ctrl.changeFile = changeFile;
+
+ ///////////
+
+ function changeFile(files) {
+ if (files.length) {
+ // record the file selected for upload for use in the action that invoked this modal
+ ctrl.model.edit_file = files[0];
+ }
+ }
+ }
+})();
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-controller.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-controller.spec.js
new file mode 100644
index 0000000000..0d0b031708
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-controller.spec.js
@@ -0,0 +1,55 @@
+/**
+ * (c) Copyright 2016 Rackspace US, 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.
+ */
+
+(function() {
+ 'use strict';
+
+ describe('horizon.dashboard.project.containers edit-object controller', function() {
+ var controller, ctrl;
+
+ beforeEach(module('horizon.framework'));
+ beforeEach(module('horizon.dashboard.project.containers'));
+
+ beforeEach(module(function ($provide) {
+ $provide.value('fileDetails', {
+ container: 'spam',
+ path: 'ham/eggs'
+ });
+ }));
+
+ beforeEach(inject(function ($injector) {
+ controller = $injector.get('$controller');
+ ctrl = controller('horizon.dashboard.project.containers.EditObjectModalController');
+ }));
+
+ it('should initialise the controller model when created', function test() {
+ expect(ctrl.model.path).toEqual('ham/eggs');
+ expect(ctrl.model.container).toEqual('spam');
+ });
+
+ it('should respond to file changes correctly', function test() {
+ var file = {name: 'eggs'};
+ ctrl.changeFile([file]);
+ expect(ctrl.model.edit_file).toEqual(file);
+ });
+
+ it('should not respond to file changes if no files are present', function test() {
+ ctrl.model.edit_file = 'spam';
+ ctrl.changeFile([]);
+ expect(ctrl.model.edit_file).toEqual('spam');
+ });
+ });
+})();
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-modal.html b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-modal.html
new file mode 100644
index 0000000000..1b8ee65a08
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-modal.html
@@ -0,0 +1,40 @@
+
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/file-change-directive.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/file-change-directive.js
index 67318716a8..1e1f5f04a8 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/file-change-directive.js
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/file-change-directive.js
@@ -16,6 +16,31 @@
(function () {
'use strict';
+ /**
+ * @ngdoc directive
+ * @name on-file-change
+ * @element
+ * @description
+ * The `on-file-change` directive watches a file input and fires
+ * a callback when the file input is changed.
+ *
+ * The callback will be passed the "files" property from the
+ * browser event.
+ *
+ * @example
+ * ```
+ *
+ *
+ *
+ * function changeFile(files) {
+ * if (files.length) {
+ * // update the upload file & its name
+ * ctrl.upload_file = files[0];
+ * ctrl.file_name = files[0].name;
+ * }
+ * }
+ * ```
+ */
angular
.module('horizon.dashboard.project.containers')
.directive('onFileChange', OnFileChange);
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/object-name-exists.directive.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/object-name-exists.directive.js
new file mode 100644
index 0000000000..af79d8db7a
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/object-name-exists.directive.js
@@ -0,0 +1,74 @@
+/*
+ * (c) Copyright 2015 Rackspace US, 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.
+ */
+(function () {
+ 'use strict';
+
+ /**
+ * @ngdoc directive
+ * @name object-name-exists
+ * @element
+ * @description
+ * The `object-name-exists` directive is used on an angular form
+ * element to verify whether a Swift object name in the current context
+ * already exists or not. The current context (container name and
+ * folder) is taken from the container model service.
+ *
+ * If the name is taken, the ngModel will have $error.exists set
+ * (and all the other usual validation properties).
+ *
+ * Additionally since the check is asynchronous the ngModel
+ * will also have $pending.exists set while the lookup is being
+ * performed.
+ *
+ * @example
+ * ```
+ *
+ * Checking if this name is used...
+ * This name already exists!
+ * ```
+ */
+ angular
+ .module('horizon.dashboard.project.containers')
+ .directive('objectNameExists', ObjectNameExists);
+
+ ObjectNameExists.$inject = [
+ 'horizon.app.core.openstack-service-api.swift',
+ 'horizon.dashboard.project.containers.containers-model',
+ '$q'
+ ];
+
+ function ObjectNameExists(swiftAPI, model, $q) {
+ return {
+ restrict: 'A',
+ require: 'ngModel',
+ link: function(scope, element, attrs, ngModel) {
+ ngModel.$asyncValidators.exists = function exists(modelValue) {
+ if (ngModel.$isEmpty(modelValue)) {
+ // consider empty model valid
+ return $q.when();
+ }
+
+ var def = $q.defer();
+ // reverse the sense here - successful lookup == error
+ swiftAPI
+ .getObjectDetails(model.container.name, model.fullPath(modelValue), true)
+ .then(def.reject, def.resolve);
+ return def.promise;
+ };
+ }
+ };
+ }
+})();
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/object-name-exists.directive.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/object-name-exists.directive.spec.js
new file mode 100644
index 0000000000..d2667aa27e
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/object-name-exists.directive.spec.js
@@ -0,0 +1,69 @@
+/*
+ * (c) Copyright 2016 Rackspace US, 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.
+ */
+(function() {
+ 'use strict';
+
+ describe('horizon.dashboard.project.containers model', function() {
+ beforeEach(module('horizon.app.core.openstack-service-api'));
+ beforeEach(module('horizon.dashboard.project.containers'));
+ beforeEach(module('horizon.framework'));
+
+ var $compile, $scope, model, element, swiftAPI, apiDeferred;
+
+ beforeEach(inject(function inject($injector, _$q_, _$rootScope_) {
+ $scope = _$rootScope_.$new();
+ $compile = $injector.get('$compile');
+ model = $injector.get('horizon.dashboard.project.containers.containers-model');
+ swiftAPI = $injector.get('horizon.app.core.openstack-service-api.swift');
+ apiDeferred = _$q_.defer();
+ spyOn(swiftAPI, 'getObjectDetails').and.returnValue(apiDeferred.promise);
+ model.container = {name: 'spam'};
+ model.folder = 'ham';
+ $scope.model = '';
+ element = angular.element(
+ '
' +
+ '' +
+ 'EXISTS' +
+ '
'
+ );
+ element = $compile(element)($scope);
+ $scope.$apply();
+ }));
+
+ it('should reject names that exist', function test() {
+ // edit the field
+ element.find('input').val('exists.txt').trigger('input');
+ expect(swiftAPI.getObjectDetails).toHaveBeenCalledWith('spam', 'ham/exists.txt', true);
+
+ // cause the lookup to complete successfully (file exists)
+ apiDeferred.resolve();
+ $scope.$apply();
+
+ expect(element.find('span').hasClass('ng-hide')).toEqual(false);
+ });
+
+ it('should accept names that do not exist', function test() {
+ // edit the field
+ element.find('input').val('not-exists.txt').trigger('input');
+ expect(swiftAPI.getObjectDetails).toHaveBeenCalledWith('spam', 'ham/not-exists.txt', true);
+
+ // cause the lookup to complete successfully (file exists)
+ apiDeferred.resolve();
+ $scope.$apply();
+ expect(element.find('span').hasClass('ng-hide')).toEqual(false);
+ });
+ });
+})();
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-batch-actions.service.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-batch-actions.service.js
index eb6f55be38..008403c07c 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-batch-actions.service.js
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-batch-actions.service.js
@@ -68,7 +68,7 @@
function uploadModal(html, $modal) {
var localSpec = {
backdrop: 'static',
- controller: 'UploadObjectModalController as ctrl',
+ controller: 'horizon.dashboard.project.containers.UploadObjectModalController as ctrl',
templateUrl: html
};
return $modal.open(localSpec).result;
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-row-actions.service.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-row-actions.service.js
index d127778889..5976c98da0 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-row-actions.service.js
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-row-actions.service.js
@@ -22,25 +22,25 @@
.factory('horizon.dashboard.project.containers.objects-row-actions', rowActions)
.factory('horizon.dashboard.project.containers.objects-actions.delete', deleteService)
.factory('horizon.dashboard.project.containers.objects-actions.download', downloadService)
+ .factory('horizon.dashboard.project.containers.objects-actions.edit', editService)
.factory('horizon.dashboard.project.containers.objects-actions.view', viewService);
rowActions.$inject = [
- 'horizon.dashboard.project.containers.basePath',
'horizon.dashboard.project.containers.objects-actions.delete',
'horizon.dashboard.project.containers.objects-actions.download',
+ 'horizon.dashboard.project.containers.objects-actions.edit',
'horizon.dashboard.project.containers.objects-actions.view',
'horizon.framework.util.i18n.gettext'
];
-
/**
* @ngdoc factory
* @name horizon.app.core.images.table.row-actions.service
* @description A list of row actions.
*/
function rowActions(
- basePath,
deleteService,
downloadService,
+ editService,
viewService,
gettext
) {
@@ -56,6 +56,10 @@
service: downloadService,
template: {text: gettext('Download')}
},
+ {
+ service: editService,
+ template: {text: gettext('Edit')}
+ },
{
service: viewService,
template: {text: gettext('View Details')}
@@ -75,13 +79,19 @@
function downloadService($qExtensions, $window) {
return {
- allowed: function allowed(file) { return $qExtensions.booleanAsPromise(file.is_object); },
- // remove leading url slash to ensure uses relative link/base path
- // thus using webroot.
- perform: function perform(file) {
- $window.location.href = file.url.replace(/^\//, '');
- }
+ allowed: allowed,
+ perform: perform
};
+
+ function allowed(file) {
+ return $qExtensions.booleanAsPromise(file.is_object);
+ }
+
+ // remove leading url slash to ensure uses relative link/base path
+ // thus using webroot.
+ function perform(file) {
+ $window.location.href = file.url.replace(/^\//, '');
+ }
}
viewService.$inject = [
@@ -94,30 +104,99 @@
function viewService(swiftAPI, basePath, model, $qExtensions, $modal) {
return {
- allowed: function allowed(file) {
- return $qExtensions.booleanAsPromise(file.is_object);
- },
- perform: function perform(file) {
- var objectPromise = swiftAPI.getObjectDetails(
- model.container.name,
- model.fullPath(file.name)
- ).then(
- function received(response) {
- return response.data;
- }
- );
- var localSpec = {
- backdrop: 'static',
- controller: 'SimpleModalController as ctrl',
- templateUrl: basePath + 'object-details-modal.html',
- resolve: {
- context: function context() { return objectPromise; }
- }
- };
-
- $modal.open(localSpec);
- }
+ allowed: allowed,
+ perform: perform
};
+
+ function allowed(file) {
+ return $qExtensions.booleanAsPromise(file.is_object);
+ }
+
+ function perform(file) {
+ var objectPromise = swiftAPI.getObjectDetails(
+ model.container.name,
+ model.fullPath(file.name)
+ ).then(
+ function received(response) {
+ return response.data;
+ }
+ );
+ var localSpec = {
+ backdrop: 'static',
+ controller: 'SimpleModalController as ctrl',
+ templateUrl: basePath + 'object-details-modal.html',
+ resolve: {
+ context: function context() { return objectPromise; }
+ }
+ };
+
+ $modal.open(localSpec);
+ }
+ }
+
+ editService.$inject = [
+ 'horizon.app.core.openstack-service-api.swift',
+ 'horizon.dashboard.project.containers.basePath',
+ 'horizon.dashboard.project.containers.containers-model',
+ 'horizon.framework.util.q.extensions',
+ 'horizon.framework.widgets.modal-wait-spinner.service',
+ 'horizon.framework.widgets.toast.service',
+ '$modal'
+ ];
+
+ function editService(swiftAPI, basePath, model, $qExtensions, modalWaitSpinnerService,
+ toastService, $modal) {
+ return {
+ allowed: allowed,
+ perform: perform
+ };
+
+ function allowed(file) {
+ return $qExtensions.booleanAsPromise(file.is_object);
+ }
+
+ function perform(file) {
+ var localSpec = {
+ backdrop: 'static',
+ controller: 'horizon.dashboard.project.containers.EditObjectModalController as ctrl',
+ templateUrl: basePath + 'edit-object-modal.html',
+ resolve: {
+ fileDetails: function fileDetails() {
+ return {
+ path: file.path,
+ container: model.container.name
+ };
+ }
+ }
+ };
+ return $modal.open(localSpec).result.then(editObjectCallback);
+ }
+
+ function editObjectCallback(uploadInfo) {
+ modalWaitSpinnerService.showModalSpinner(gettext("Uploading"));
+ swiftAPI.uploadObject(
+ model.container.name,
+ uploadInfo.path,
+ uploadInfo.edit_file
+ ).then(success, error);
+
+ function success() {
+ modalWaitSpinnerService.hideModalSpinner();
+ toastService.add(
+ 'success',
+ interpolate(gettext('File %(path)s uploaded.'), uploadInfo, true)
+ );
+ model.updateContainer();
+ model.selectContainer(
+ model.container.name,
+ model.folder
+ );
+ }
+
+ function error() {
+ modalWaitSpinnerService.hideModalSpinner();
+ }
+ }
}
deleteService.$inject = [
@@ -129,27 +208,31 @@
function deleteService(basePath, actionResultService, $qExtensions, $modal) {
return {
- allowed: function allowed() {
- return $qExtensions.booleanAsPromise(true);
- },
- perform: function perform(file) {
- var localSpec = {
- backdrop: 'static',
- controller: 'DeleteObjectsModalController as ctrl',
- templateUrl: basePath + 'delete-objects-modal.html',
- resolve: {
- selected: function () {
- return [file];
- }
- }
- };
-
- return $modal.open(localSpec).result.then(function finished() {
- return actionResultService.getActionResult().deleted(
- 'OS::Swift::Object', file.name
- ).result;
- });
- }
+ allowed: allowed,
+ perform: perform
};
+
+ function allowed() {
+ return $qExtensions.booleanAsPromise(true);
+ }
+
+ function perform(file) {
+ var localSpec = {
+ backdrop: 'static',
+ controller: 'DeleteObjectsModalController as ctrl',
+ templateUrl: basePath + 'delete-objects-modal.html',
+ resolve: {
+ selected: function () {
+ return [file];
+ }
+ }
+ };
+
+ return $modal.open(localSpec).result.then(function finished() {
+ return actionResultService.getActionResult().deleted(
+ 'OS::Swift::Object', file.name
+ ).result;
+ });
+ }
}
})();
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-row-actions.service.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-row-actions.service.spec.js
index 93509007f6..ad2591329c 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-row-actions.service.spec.js
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-row-actions.service.spec.js
@@ -41,7 +41,7 @@
it('should create an actions list', function test() {
expect(rowActions.actions).toBeDefined();
var actions = rowActions.actions();
- expect(actions.length).toEqual(3);
+ expect(actions.length).toEqual(4);
angular.forEach(actions, function check(action) {
expect(action.service).toBeDefined();
expect(action.template).toBeDefined();
@@ -171,6 +171,97 @@
});
});
+ describe('editService', function test() {
+ var swiftAPI, editService, modalWaitSpinnerService, toastService, $q;
+
+ beforeEach(inject(function inject($injector, _$q_) {
+ swiftAPI = $injector.get('horizon.app.core.openstack-service-api.swift');
+ editService = $injector.get('horizon.dashboard.project.containers.objects-actions.edit');
+ modalWaitSpinnerService = $injector.get(
+ 'horizon.framework.widgets.modal-wait-spinner.service'
+ );
+ toastService = $injector.get('horizon.framework.widgets.toast.service');
+ $q = _$q_;
+ }));
+
+ it('should have an allowed and perform', function test() {
+ expect(editService.allowed).toBeDefined();
+ expect(editService.perform).toBeDefined();
+ });
+
+ it('should only allow files', function test() {
+ expectAllowed(editService.allowed({is_object: true}));
+ });
+
+ it('should only now allow folders', function test() {
+ expectNotAllowed(editService.allowed({is_object: false}));
+ });
+
+ it('should handle upload success correctly', function() {
+ var modalDeferred = $q.defer();
+ var apiDeferred = $q.defer();
+ var result = { result: modalDeferred.promise };
+ spyOn($modal, 'open').and.returnValue(result);
+ spyOn(modalWaitSpinnerService, 'showModalSpinner');
+ spyOn(modalWaitSpinnerService, 'hideModalSpinner');
+ spyOn(swiftAPI, 'uploadObject').and.returnValue(apiDeferred.promise);
+ spyOn(toastService, 'add').and.callThrough();
+ spyOn(model,'updateContainer');
+ spyOn(model,'selectContainer');
+
+ editService.perform();
+ model.container = {name: 'spam'};
+ $rootScope.$apply();
+
+ // Close the modal, make sure API call succeeds
+ modalDeferred.resolve({name: 'ham', path: '/folder/ham'});
+ apiDeferred.resolve();
+ $rootScope.$apply();
+
+ // Check the string of functions called by this code path succeed
+ expect($modal.open).toHaveBeenCalled();
+ expect(modalWaitSpinnerService.showModalSpinner).toHaveBeenCalled();
+ expect(swiftAPI.uploadObject).toHaveBeenCalled();
+ expect(toastService.add).toHaveBeenCalledWith('success', 'File /folder/ham uploaded.');
+ expect(modalWaitSpinnerService.hideModalSpinner).toHaveBeenCalled();
+ expect(model.updateContainer).toHaveBeenCalled();
+ expect(model.selectContainer).toHaveBeenCalled();
+ });
+
+ it('should handle upload error correctly', function() {
+ var modalDeferred = $q.defer();
+ var apiDeferred = $q.defer();
+ var result = { result: modalDeferred.promise };
+ spyOn($modal, 'open').and.returnValue(result);
+ spyOn(modalWaitSpinnerService, 'showModalSpinner');
+ spyOn(modalWaitSpinnerService, 'hideModalSpinner');
+ spyOn(swiftAPI, 'uploadObject').and.returnValue(apiDeferred.promise);
+ spyOn(toastService, 'add').and.callThrough();
+ spyOn(model,'updateContainer');
+ spyOn(model,'selectContainer');
+
+ editService.perform();
+ model.container = {name: 'spam'};
+ $rootScope.$apply();
+
+ // Close the modal, make sure API call is rejected
+ modalDeferred.resolve({name: 'ham', path: '/'});
+ apiDeferred.reject();
+ $rootScope.$apply();
+
+ // Check the string of functions called by this code path succeed
+ expect(modalWaitSpinnerService.showModalSpinner).toHaveBeenCalled();
+ expect(swiftAPI.uploadObject).toHaveBeenCalled();
+ expect(modalWaitSpinnerService.hideModalSpinner).toHaveBeenCalled();
+ expect($modal.open).toHaveBeenCalled();
+
+ // Check the success branch is not called
+ expect(model.updateContainer).not.toHaveBeenCalled();
+ expect(model.selectContainer).not.toHaveBeenCalled();
+ expect(toastService.add).not.toHaveBeenCalledWith('success');
+ });
+ });
+
function exerciseAllowedPromise(promise) {
var handler = jasmine.createSpyObj('handler', ['success', 'error']);
promise.then(handler.success, handler.error);
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects.controller.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects.controller.spec.js
index 2e4c5ab357..f1a4bc8b0c 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects.controller.spec.js
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects.controller.spec.js
@@ -91,5 +91,44 @@
expect(model.selectContainer).toHaveBeenCalledWith('spam', 'ham');
});
+
+ it('should handle action results when result is undefined', function test() {
+ var ctrl = createController();
+
+ spyOn(model, 'updateContainer');
+ spyOn($scope, '$broadcast');
+ ctrl.actionResultHandler();
+
+ expect($scope.$broadcast).not.toHaveBeenCalled();
+ expect(model.updateContainer).not.toHaveBeenCalled();
+ expect(model.selectContainer).not.toHaveBeenCalled();
+ });
+
+ it('should handle action results with an empty deleted list', function test() {
+ var ctrl = createController();
+ var result = { deleted: [] };
+
+ spyOn(model, 'updateContainer');
+ spyOn($scope, '$broadcast');
+ ctrl.actionResultHandler(result);
+
+ expect($scope.$broadcast).not.toHaveBeenCalled();
+ expect(model.updateContainer).not.toHaveBeenCalled();
+ expect(model.selectContainer).not.toHaveBeenCalled();
+ });
+
+ it('should handle action results', function test() {
+ var ctrl = createController();
+ spyOn($scope, '$broadcast');
+ spyOn(model, 'updateContainer');
+
+ var d = $q.defer();
+ ctrl.actionResultHandler(d.promise);
+ d.resolve({deleted: [1]});
+ $scope.$apply();
+
+ expect(model.updateContainer).toHaveBeenCalled();
+ expect(model.selectContainer).toHaveBeenCalledWith('spam', undefined);
+ });
});
})();
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-controller.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-controller.js
index 30be321012..62ff5c0512 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-controller.js
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-controller.js
@@ -18,24 +18,27 @@
angular
.module('horizon.dashboard.project.containers')
- .controller('UploadObjectModalController', UploadObjectModalController);
+ .controller(
+ 'horizon.dashboard.project.containers.UploadObjectModalController',
+ UploadObjectModalController
+ );
UploadObjectModalController.$inject = [
- 'horizon.dashboard.project.containers.containers-model',
- '$scope'
+ 'horizon.dashboard.project.containers.containers-model'
];
- function UploadObjectModalController(model, $scope) {
+ function UploadObjectModalController(model) {
var ctrl = this;
ctrl.model = {
- name:'',
+ name: '',
container: model.container,
folder: model.folder,
view_file: null, // file object managed by angular form ngModel
upload_file: null, // file object from the DOM element with the actual upload
DELIMETER: model.DELIMETER
};
+ ctrl.form = null; // set by the HTML
ctrl.changeFile = changeFile;
///////////
@@ -45,9 +48,12 @@
// update the upload file & its name
ctrl.model.upload_file = files[0];
ctrl.model.name = files[0].name;
+ ctrl.form.name.$setDirty();
- // we're modifying the model value from a DOM event so we need to manually $digest
- $scope.$digest();
+ // Note that a $scope.$digest() is now needed for the change to the ngModel to be
+ // reflected in the page (since this callback is fired from inside a DOM event)
+ // but the on-file-changed directive currently does a digest after this callback
+ // is invoked.
}
}
}
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-controller.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-controller.spec.js
index 27b4940ee7..2a4d0c1d29 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-controller.spec.js
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-controller.spec.js
@@ -18,7 +18,7 @@
'use strict';
describe('horizon.dashboard.project.containers upload-object controller', function() {
- var controller, $scope;
+ var controller, ctrl;
beforeEach(module('horizon.framework'));
beforeEach(module('horizon.dashboard.project.containers'));
@@ -30,42 +30,31 @@
});
}));
- beforeEach(inject(function ($injector, _$rootScope_) {
+ beforeEach(inject(function ($injector) {
controller = $injector.get('$controller');
- $scope = _$rootScope_.$new(true);
+ ctrl = controller('horizon.dashboard.project.containers.UploadObjectModalController');
+ ctrl.form = {name: {$setDirty: angular.noop}};
+ spyOn(ctrl.form.name, '$setDirty');
}));
- function createController() {
- return controller('UploadObjectModalController', {$scope: $scope});
- }
-
it('should initialise the controller model when created', function test() {
- var ctrl = createController();
expect(ctrl.model.name).toEqual('');
expect(ctrl.model.container.name).toEqual('spam');
expect(ctrl.model.folder).toEqual('ham');
});
it('should respond to file changes correctly', function test() {
- var ctrl = createController();
- spyOn($scope, '$digest');
var file = {name: 'eggs'};
-
ctrl.changeFile([file]);
-
expect(ctrl.model.name).toEqual('eggs');
expect(ctrl.model.upload_file).toEqual(file);
- expect($scope.$digest).toHaveBeenCalled();
+ expect(ctrl.form.name.$setDirty).toHaveBeenCalled();
});
- it('should respond to file changes correctly if no files are present', function test() {
- var ctrl = createController();
- spyOn($scope, '$digest');
-
+ it('should not respond to file changes if no files are present', function test() {
ctrl.changeFile([]);
-
expect(ctrl.model.name).toEqual('');
- expect($scope.$digest).not.toHaveBeenCalled();
+ expect(ctrl.form.name.$setDirty).not.toHaveBeenCalled();
});
});
})();
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-modal.html b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-modal.html
index b7c4556bab..e88a4f6655 100644
--- a/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-modal.html
+++ b/openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-modal.html
@@ -8,7 +8,7 @@
-