The ability to undo

This is something which has been available in desktop applications since the dawn of time, but very few web applications implement this functionality. As a result, when a user, who is familiar with the use of desktop applications makes a mistake in a web application and can’t undo it, they feel lost.

An Example: Google Mail

GMail implements this really well. If you delete an email, you get a notification saying it was successfully deleted and the option to undo it if you made a mistake. The same applied for archiving a message. There’s even an option in the labs section to enable undo for sending the message, which is useful if you hit send and realise you forgot to add an attatchment or missed that vital point from your email.

The aim of GMails undo feature is to allow users to undo an action straight away, so it wouldn’t work if you realised you deleted a message a week later (although you could still manually move it from the bin to your inbox).

How would it work in Project Management

So how should undo work in a project management system? Well, there are several options:

  1. Only have the option to undo an action immediately after the action was carried out
  2. Allow users to undo their actions at a later moment in time
  3. Allow them to undo their actions later, but have a time restriction (such as only being able to undo something within 30 minutes of doing it)

However each of these options has some restrictions or complications

Option 1

This would be by far the easiest to implement as it wouldn’t have the complications of 2 and 3 (which will be discussed shortly). You just need to save the state of the data which the action is being carried out against before and after, and if they want to undo, simple swap the data round again. This method will also mean we store the least amount of data about users actions, as we just need to keep each users last action as they can’t undo anything prior to that.

The only thing that needs to be taken into account is any action which uses notifications (such as an email notification) to other, the notifications need to be delayed incase the user rolls back.

Option 2

This is the oppose of option 1, it will keep a log of all your actions and allow you to undo them at a later point in time. The problem with this, is that over time, other users will have carried out actions which build on the action you wish to undo. In this case, we can either:

  1. Not allow you to undo the action if it will affect other actions
  2. Allow the undo and lose (either by undoing them as well or orphaning the actions which where linked to this one)
  3. Anly allowing the admins/project manager undo the action (and lose the linked actions)

This method would also be the most expensive in terms of storing all this history as for each action it would again need the before and after state of affected items.

Option 3

This is a half way point between options 1 & 2 and was suggested by Martin Smith. Users should only be allowed to undo their actions within X minutes of doing the action and admins may be able to undo actions without there time estrictions. This would hopefully also take care of the issue of dependencies between actions.

Should it be possible to undo all actions?

Do all actions need the ability to undo? If you create a project by mistake, would it be better to undo it or to delete it? Should only edit and delete actions have an undo option tied to them? The fewer actions which need to be supported by undo, the easier it will be to implement and maintain, and the fewer complications there will be between dependencies.

Implementation

As mentioned earlier, the basis of how this would work, is that every action which is some way manipulates the state of the database would need to be logged. The 3 types of actions this applies to is:

  1. The creation of new objects
  2. The editing of existing objects
  3. The deletion of existing objects

So lets see how this would work in real life using the example of a message. You write your message out, select the people to notify and then submit the form. You then realised you forgot to add a vital piece of information to the message so you undo the submission which stops email notifications being sent, change the content of the message and submit again. This is the first type of undo where the commiting the action to the database and sending out email notifications was delayed.

A few days later, the scope of the project changes so you decide to update the message to include some extra details and submit the form. A snapshot of the old message is saved (by serialising the row of the old message) to a seperate table in the database with a link back to the new message. Your project manager then realizes you done this (changed the scope of the project without his knowledge/permission) so he rolls back the changes you made (which is done by retrieving the old row, unserializing it and replacing the new message with it).

A few days later, you want to make some more changes to the message (with permission this time) and instead of hitting edit you hit the delete button (and blindly accept the confirmation message). Before the message was actually deleted, it was serialized into the undo table. You then hit the undo button, the message is unserialized and out back into the messages table.

Table structure

The schema for the table isn’t finished yet, but in essence it has

  • id – The auto incrementing primary key
  • account_id – A foreign key mapping to the account this item is from
  • project_id – A foreign key mapping to the project this item is from
  • user_id – A foreign key mapping to the user which carried out the action
  • table – The name of the table this item was originally from
  • item_id – The id of the item within that (the original) table
  • data – The serialized data
  • action – Whether this was an insert, delete or update as how these are undone is handled differantly
  • time – A timestamp of when this action was done
  • undone – A boolean stating if the action has already been undone (to prevent it being undone multiple times)

Example

And here is an example of how the code to use this functionality will look. This example is a very simplified example of the update function in the message model.

public function update($message_id, $message) {
 // First we need to get the existing row
 // This is so we can roll back with an undo
 $this->db->where('id', $message_id);
 $query = $this->db->get('messages');
 $row = $query->num_rows() == 0 ? false : $query->row();

 // If we don't acually have this row, we'll add the item
 if($row === false) return $this->add($message);

 // Now we can do the update
 $this->db->where('id', $message_id);
 $this->db->update('messages', $message);

 // And finally the logic for undo
 // We return the ID of the undo item instead of the message
 // as we already know the message ID (we passed it in)
 // but we need the undo ID so we can have a link to undo it
 return $this->undoable->set('messages', $message_id, 'update', $row);
}

So what do you think? Any thoughts or comments you have are much appreciated.

Bookmark and Share

Leave a Reply