Skip to content

Commit

Permalink
[stable10] Backport of Replace notification with confirm dialog for u…
Browse files Browse the repository at this point in the history
…ser deletion

Replace notification with confirm dialog for user
deletion from UI.

Signed-off-by: Sujith H <sharidasan@owncloud.com>
  • Loading branch information
sharidas committed Nov 23, 2018
1 parent 1c5b520 commit 81e0228
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 205 deletions.
74 changes: 3 additions & 71 deletions settings/js/users/deleteHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,6 @@ DeleteHandler.TIMEOUT_MS = 7000;
*/
DeleteHandler.prototype._timeout = null;

/**
* The function to be called after successfully marking the object for deletion
* @callback markCallback
* @param {string} oid the ID of the specific user or group
*/

/**
* The function to be called after successful delete. The id of the object will
* be passed as argument. Unsuccessful operations will display an error using
* OC.dialogs, no callback is fired.
* @callback removeCallback
* @param {string} oid the ID of the specific user or group
*/

/**
* This callback is fired after "undo" was clicked so the consumer can update
* the web interface
* @callback undoCallback
* @param {string} oid the ID of the specific user or group
*/

/**
* enabled the notification system. Required for undo UI.
*
Expand Down Expand Up @@ -111,55 +90,14 @@ DeleteHandler.prototype.showNotification = function() {
}
};

/**
* hides the Undo Notification
*/
DeleteHandler.prototype.hideNotification = function() {
if(this.notifier !== false) {
$('#notification').removeData(this.notificationDataID);
this.notifier.hide();
}
};

/**
* initializes the delete operation for a given object id
*
* @param {string} oid the object id
*/
DeleteHandler.prototype.mark = function(oid) {
if(this.oidToDelete !== false) {
// passing true to avoid hiding the notification
// twice and causing the second notification
// to disappear immediately
this.deleteEntry(true);
}
this.oidToDelete = oid;
this.canceled = false;
this.markCallback(oid);
this.showNotification();
if (this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if (DeleteHandler.TIMEOUT_MS > 0) {
this._timeout = window.setTimeout(
_.bind(this.deleteEntry, this),
DeleteHandler.TIMEOUT_MS
);
}
};

/**
* cancels a delete operation
*/
DeleteHandler.prototype.cancel = function() {
if (this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}

this.canceled = true;
this.oidToDelete = false;
this.markCallback(decodeURIComponent(oid));
};

/**
Expand All @@ -173,7 +111,7 @@ DeleteHandler.prototype.cancel = function() {
*/
DeleteHandler.prototype.deleteEntry = function(keepNotification) {
var deferred = $.Deferred();
if(this.canceled || this.oidToDelete === false) {
if(this.oidToDelete === false) {
return deferred.resolve().promise();
}

Expand All @@ -182,11 +120,6 @@ DeleteHandler.prototype.deleteEntry = function(keepNotification) {
dh.hideNotification();
}

if (this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}

var payload = {};
payload[dh.ajaxParamID] = dh.oidToDelete;
return $.ajax({
Expand All @@ -199,10 +132,9 @@ DeleteHandler.prototype.deleteEntry = function(keepNotification) {

//TODO: following line
dh.removeCallback(dh.oidToDelete);
dh.canceled = true;
},
error: function (jqXHR) {
OC.dialogs.alert(jqXHR.responseJSON.data.message, t('settings', 'Unable to delete {objName}', {objName: dh.oidToDelete}));
OC.dialogs.alert(jqXHR.responseJSON.data.message, t('settings', 'Unable to delete {objName}', {objName: decodeURIComponent(dh.oidToDelete)}));
dh.undoCallback(dh.oidToDelete);

}
Expand Down
21 changes: 12 additions & 9 deletions settings/js/users/groups.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,22 +269,25 @@ GroupList = {
GroupDeleteHandler = new DeleteHandler('/settings/users/groups', 'groupname',
GroupList.hide, GroupList.remove);

//configure undo
OC.Notification.hide();
var msg = escapeHTML(t('settings', 'deleted {groupName}', {groupName: '%oid'})) + '<span class="undo">' +
escapeHTML(t('settings', 'undo')) + '</span>';
GroupDeleteHandler.setNotification(OC.Notification, 'deletegroup', msg,
GroupList.show);
GroupList.remove);

//when to mark user for delete
$userGroupList.on('click', '.delete', function () {
// Call function for handling delete/undo
GroupDeleteHandler.mark(encodeURIComponent(GroupList.getElementGID(this)).toString());
});

//delete a marked user when leaving the page
$(window).on('beforeunload', function () {
GroupDeleteHandler.deleteEntry();
var groupName = encodeURIComponent(GroupList.getElementGID(this)).toString();
OC.dialogs.confirm(
t('settings', 'You are about to delete a group. This action can\'t be undone and is permanent. Are you sure that you want to permanently delete {groupName}?', {groupName:decodeURIComponent(groupName)}),
t('settings', 'Delete group'),
function (confirmation) {
if (confirmation) {
GroupDeleteHandler.mark(groupName);
GroupDeleteHandler.deleteEntry();
}
}
);
});
},

Expand Down
23 changes: 11 additions & 12 deletions settings/js/users/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,12 +377,16 @@ var UserList = {
$userListBody.on('click', '.delete', function () {
// Call function for handling delete/undo
var uid = UserList.getUID(this);
UserDeleteHandler.mark(uid);
});

//delete a marked user when leaving the page
$(window).on('beforeunload', function () {
UserDeleteHandler.deleteEntry();
OC.dialogs.confirm(
t('settings', 'You are about to delete a user. This action can\'t be undone and is permanent. All user data, files and shares will be deleted. Are you sure that you want to permanently delete {userName}?', {userName: uid}),
t('settings', 'Delete user'),
function (confirmation) {
if (confirmation) {
UserDeleteHandler.mark(uid);
UserDeleteHandler.deleteEntry();
}
}
);
});
},
update: function (gid, limit) {
Expand Down Expand Up @@ -912,12 +916,7 @@ $(document).ready(function () {
}
}

var promise;
if (UserDeleteHandler) {
promise = UserDeleteHandler.deleteEntry();
} else {
promise = $.Deferred().resolve().promise();
}
var promise = $.Deferred().resolve().promise();

promise.then(function() {
var groups = $('#newuser .groups').data('groups') || [];
Expand Down
96 changes: 0 additions & 96 deletions settings/tests/js/users/deleteHandlerSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,77 +48,6 @@ describe('DeleteHandler tests', function() {
hideNotificationSpy.restore();
clock.restore();
});
it('shows a notification when marking for delete', function() {
var handler = init(markCallback, removeCallback, undoCallback);
handler.mark('some_uid');

expect(showNotificationSpy.calledOnce).toEqual(true);
expect(showNotificationSpy.getCall(0).args[0]).toEqual('removed some_uid entry');

expect(markCallback.calledOnce).toEqual(true);
expect(markCallback.getCall(0).args[0]).toEqual('some_uid');
expect(removeCallback.notCalled).toEqual(true);
expect(undoCallback.notCalled).toEqual(true);

expect(fakeServer.requests.length).toEqual(0);
});
it('deletes first entry and reshows notification on second delete', function() {
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
204,
{ 'Content-Type': 'application/json' },
JSON.stringify({status: 'success'})
]);
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_other_uid/, [
204,
{ 'Content-Type': 'application/json' },
JSON.stringify({status: 'success'})
]);

var handler = init(markCallback, removeCallback, undoCallback);
handler.mark('some_uid');

expect(showNotificationSpy.calledOnce).toEqual(true);
expect(showNotificationSpy.getCall(0).args[0]).toEqual('removed some_uid entry');
showNotificationSpy.restore();
showNotificationSpy = sinon.spy(OC.Notification, 'showHtml');

handler.mark('some_other_uid');

expect(hideNotificationSpy.calledOnce).toEqual(true);
expect(showNotificationSpy.calledOnce).toEqual(true);
expect(showNotificationSpy.getCall(0).args[0]).toEqual('removed some_other_uid entry');

expect(markCallback.calledTwice).toEqual(true);
expect(markCallback.getCall(0).args[0]).toEqual('some_uid');
expect(markCallback.getCall(1).args[0]).toEqual('some_other_uid');
// called only once, because it is called once the second user is deleted
expect(removeCallback.calledOnce).toEqual(true);
expect(undoCallback.notCalled).toEqual(true);

// previous one was delete
expect(fakeServer.requests.length).toEqual(1);
var request = fakeServer.requests[0];
expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid');
});
it('automatically deletes after timeout', function() {
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
204,
{ 'Content-Type': 'application/json' },
JSON.stringify({status: 'success'})
]);

var handler = init(markCallback, removeCallback, undoCallback);
handler.mark('some_uid');

clock.tick(5000);
// nothing happens yet
expect(fakeServer.requests.length).toEqual(0);

clock.tick(3000);
expect(fakeServer.requests.length).toEqual(1);
var request = fakeServer.requests[0];
expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid');
});
it('deletes when deleteEntry is called', function() {
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
200,
Expand Down Expand Up @@ -147,31 +76,6 @@ describe('DeleteHandler tests', function() {
var request = fakeServer.requests[0];
expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid%3C%3E%2F%22..%5C');
});
it('cancels deletion when undo is clicked', function() {
var handler = init(markCallback, removeCallback, undoCallback);
handler.setNotification(OC.Notification, 'dataid', 'removed %oid entry <span class="undo">Undo</span>', undoCallback);
handler.mark('some_uid');
$('#notification .undo').click();

expect(undoCallback.calledOnce).toEqual(true);

// timer was cancelled
clock.tick(10000);
expect(fakeServer.requests.length).toEqual(0);
});
it('cancels deletion when cancel method is called', function() {
var handler = init(markCallback, removeCallback, undoCallback);
handler.setNotification(OC.Notification, 'dataid', 'removed %oid entry <span class="undo">Undo</span>', undoCallback);
handler.mark('some_uid');
handler.cancel();

// not sure why, seems to be by design
expect(undoCallback.notCalled).toEqual(true);

// timer was cancelled
clock.tick(10000);
expect(fakeServer.requests.length).toEqual(0);
});
it('calls removeCallback after successful server side deletion', function() {
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
200,
Expand Down
48 changes: 43 additions & 5 deletions tests/acceptance/features/bootstrap/WebUIUsersContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,19 +149,31 @@ public function theAdminCreatesAUserUsingWithoutAPasswordTheWebUI(

/**
*
* @When the administrator deletes the group named :name using the webUI
* @When the administrator deletes the group named :name using the webUI and confirms the deletion using the webUI
*
* @param string $name
*
* @return void
*/
public function theAdminDeletesTheGroupUsingTheWebUI($name) {
$this->usersPage->deleteGroup($name, $this->getSession());
$this->usersPage->deleteGroup($name, $this->getSession(), true);
$this->featureContext->rememberThatGroupIsNotExpectedToExist($name);
}

/**
* @When the administrator deletes these groups using the webUI:
* @When the administrator deletes the group named :name using the webUI and cancels the deletion using the webUI
*
* @param string $name
*
* @return void
*/
public function theAdminDeletesDoesNotDeleteGroupUsingWebUI($name) {
$this->usersPage->deleteGroup($name, $this->getSession(), false);
$this->featureContext->rememberThatGroupIsNotExpectedToExist($name);
}

/**
* @When the administrator deletes these groups and confirms the deletion using the webUI:
* expects a table of groups with the heading "groupname"
*
* @param TableNode $table
Expand All @@ -174,6 +186,20 @@ public function theAdminDeletesTheseGroupsUsingTheWebUI(TableNode $table) {
}
}

/**
* @When the administrator deletes these groups and and cancels the deletion using the webUI:
* expects a table of groups with the heading "groupname"
*
* @param TableNode $table
*
* @return void
*/
public function theAdminDeletesDoesNotTheseGroupsUsingTheWebUI(TableNode $table) {
foreach ($table as $row) {
$this->theAdminDeletesDoesNotDeleteGroupUsingWebUI($row['groupname']);
}
}

/**
* @Then the group name :groupName should be listed on the webUI
*
Expand Down Expand Up @@ -269,14 +295,26 @@ public function theDisabledUserTriesToLogin($username, $password) {
}

/**
* @When the administrator deletes user :username using the webUI
* @When the administrator deletes user :username using the webUI and confirms the deletion using the webUI
*
* @param string $username
*
* @return void
*/
public function theAdministratorDeletesTheUser($username) {
$this->usersPage->deleteUser($username);
$this->usersPage->deleteUser($username, true);
$this->featureContext->rememberThatUserIsNotExpectedToExist($username);
}

/**
* @When the administrator deletes user :username using the webUI and cancels the deletion using the webUI
*
* @param string $username
*
* @return void
*/
public function theAdministratorDoesNotDeleteTheUser($username) {
$this->usersPage->deleteUser($username, false);
$this->featureContext->rememberThatUserIsNotExpectedToExist($username);
}

Expand Down
Loading

0 comments on commit 81e0228

Please sign in to comment.