diff -r 76ab3c71aff2 -r c67bcee93248 doc/book/additionnal_services/undo.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/additionnal_services/undo.rst Thu Jan 08 22:11:06 2015 +0100 @@ -0,0 +1,337 @@ +Undoing changes in CubicWeb +--------------------------- + +Many desktop applications offer the possibility for the user to +undo its last changes : this *undo feature* has now been +integrated into the CubicWeb framework. This document will +introduce you to the *undo feature* both from the end-user and the +application developer point of view. + +But because a semantic web application and a common desktop +application are not the same thing at all, especially as far as +undoing is concerned, we will first introduce *what* is the *undo +feature* for now. + +What's *undoing* in a CubicWeb application +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +What is an *undo feature* is quite intuitive in the context of a +desktop application. But it is a bit subtler in the context of a +Semantic Web application. This section introduces some of the main +differences between a classical desktop and a Semantic Web +applications to keep in mind in order to state precisely *what we +want*. + +The notion transactions +``````````````````````` + +A CubicWeb application acts upon an *Entity-Relationship* model, +described by a schema. This allows to ensure some data integrity +properties. It also implies that changes are made by all-or-none +groups called *transactions*, such that the data integrity is +preserved whether the transaction is completely applied *or* none +of it is applied. + +A transaction can thus include more actions than just those +directly required by the main purpose of the user. For example, +when a user *just* writes a new blog entry, the underlying +*transaction* holds several *actions* as illustrated below : + +* By admin on 2012/02/17 15:18 - Created Blog entry : Torototo + + #. Created Blog entry : Torototo + #. Added relation : Torototo owned by admin + #. Added relation : Torototo blog entry of Undo Blog + #. Added relation : Torototo in state draft (draft) + #. Added relation : Torototo created by admin + +Because of the very nature (all-or-none) of the transactions, the +"undoable stuff" are the transactions and not the actions ! + +Public and private actions within a transaction +``````````````````````````````````````````````` + +Actually, within the *transaction* "Created Blog entry : +Torototo", two of those *actions* are said to be *public* and +the others are said to be *private*. *Public* here means that the +public actions (1 and 3) were directly requested by the end user ; +whereas *private* means that the other actions (2, 4, 5) were +triggered "under the hood" to fulfill various requirements for the +user operation (ensuring integrity, security, ... ). + +And because quite a lot of actions can be triggered by a "simple" +end-user request, most of which the end-user is not (and does not +need or wish to be) aware, only the so-called public actions will +appear [1]_ in the description of the an undoable transaction. + +* By admin on 2012/02/17 15:18 - Created Blog entry : Torototo + + #. Created Blog entry : Torototo + #. Added relation : Torototo blog entry of Undo Blog + +But note that both public and private actions will be undone +together when the transaction is undone. + +(In)dependent transactions : the simple case +```````````````````````````````````````````` + +A CubicWeb application can be used *simultaneously* by different users +(whereas a single user works on an given office document at a +given time), so that there is not always a single history +time-line in the CubicWeb case. Moreover CubicWeb provides +security through the mechanism of *permissions* granted to each +user. This can lead to some transactions *not* being undoable in +some contexts. + +In the simple case two (unprivileged) users Alice and Bob make +relatively independent changes : then both Alice and Bob can undo +their changes. But in some case there is a clean dependency +between Alice's and Bob's actions or between actions of one of +them. For example let's suppose that : + +- Alice has created a blog, +- then has published a first post inside, +- then Bob has published a second post in the same blog, +- and finally Alice has updated its post contents. + +Then it is clear that Alice can undo her contents changes and Bob +can undo his post creation independently. But Alice can not undo +her post creation while she has not first undone her changes. +It is also clear that Bob should *not* have the +permissions to undo any of Alice's transactions. + + +More complex dependencies between transactions +`````````````````````````````````````````````` + +But more surprising things can quickly happen. Going back to the +previous example, Alice *can* undo the creation of the blog after +Bob has published its post in it ! But this is possible only +because the schema does not *require* for a post to be in a +blog. Would the *blog entry of* relation have been mandatory, then +Alice could not have undone the blog creation because it would +have broken integrity constraint for Bob's post. + +When a user attempts to undo a transaction the system will check +whether a later transaction has explicit dependency on the +would-be-undone transaction. In this case the system will not even +attempt the undo operation and inform the user. + +If no such dependency is detected the system will attempt the undo +operation but it can fail, typically because of integrity +constraint violations. In such a case the undo operation is +completely [3]_ rollbacked. + + +The *undo feature* for CubicWeb end-users +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The exposition of the undo feature to the end-user through a Web +interface is still quite basic and will be improved toward a +greater usability. But it is already fully functional. For now +there are two ways to access the *undo feature* as long as the it +has been activated in the instance configuration file with the +option *undo-support=yes*. + +Immediately after having done the change to be canceled through +the **undo** link in the message. This allows to undo an +hastily action immediately. For example, just after having +validated the creation of the blog entry *A second blog entry* we +get the following message, allowing to undo the creation. + +.. image:: /images/undo_mesage_w600.png + :width: 600px + :alt: Screenshot of the undo link in the message + :align: center + +At any time we can access the **undo-history view** accessible from the +start-up page. + +.. image:: /images/undo_startup-link_w600.png + :width: 600px + :alt: Screenshot of the startup menu with access to the history view + :align: center + +This view will provide inspection of the transaction and their (public) +actions. Each transaction provides its own **undo** link. Only the +transactions the user has permissions to see and undo will be shown. + +.. image:: /images/undo_history-view_w600.png + :width: 600px + :alt: Screenshot of the undo history main view + :align: center + +If the user attempts to undo a transaction which can't be undone or +whose undoing fails, then a message will explain the situation and +no partial undoing will be left behind. + +This is all for the end-user side of the undo mechanism : this is +quite simple indeed ! Now, in the following section, we are going +to introduce the developer side of the undo mechanism. + +The *undo feature* for CubicWeb application developers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A word of warning : this section is intended for developers, +already having some knowledge of what's under CubicWeb's hood. If +it is not *yet* the case, please refer to CubicWeb documentation +http://docs.cubicweb.org/ . + +Overview +```````` + +The core of the undo mechanisms is at work in the *native source*, +beyond the RQL. This does mean that *transactions* and *actions* +are *no entities*. Instead they are represented at the SQL level +and exposed through the *DB-API* supported by the repository +*Connection* objects. + +Once the *undo feature* has been activated in the instance +configuration file with the option *undo-support=yes*, each +mutating operation (cf. [2]_) will be recorded in some special SQL +table along with its associated transaction. Transaction are +identified by a *txuuid* through which the functions of the +*DB-API* handle them. + +On the web side the last commited transaction *txuuid* is +remembered in the request's data to allow for imediate undoing +whereas the *undo-history view* relies upon the *DB-API* to list +the accessible transactions. The actual undoing is performed by +the *UndoController* accessible at URL of the form +`www.my.host/my/instance/undo?txuuid=...` + +The repository side +``````````````````` + +Please refer to the file `cubicweb/server/sources/native.py` and +`cubicweb/transaction.py` for the details. + +The undoing information is mainly stored in three SQL tables: + +`transactions` + Stores the txuuid, the user eid and the date-and-time of + the transaction. This table is referenced by the two others. + +`tx_entity_actions` + Stores the undo information for actions on entities. + +`tx_relation_actions` + Stores the undo information for the actions on relations. + +When the undo support is activated, entries are added to those +tables for each mutating operation on the data repository, and are +deleted on each transaction undoing. + +Those table are accessible through the following methods of the +repository `Connection` object : + +`undoable_transactions` + Returns a list of `Transaction` objects accessible to the user + and according to the specified filter(s) if any. + +`tx_info` + Returns a `Transaction` object from a `txuuid` + +`undo_transaction` + Returns the list of `Action` object for the given `txuuid`. + + NB: By default it only return *public* actions. + +The web side +```````````` + +The exposure of the *undo feature* to the end-user through the Web +interface relies on the *DB-API* introduced above. This implies +that the *transactions* and *actions* are not *entities* linked by +*relations* on which the usual views can be applied directly. + +That's why the file `cubicweb/web/views/undohistory.py` defines +some dedicated views to access the undo information : + +`UndoHistoryView` + This is a *StartupView*, the one accessible from the home + page of the instance which list all transactions. + +`UndoableTransactionView` + This view handles the display of a single `Transaction` object. + +`UndoableActionBaseView` + This (abstract) base class provides private methods to build + the display of actions whatever their nature. + +`Undoable[Add|Remove|Create|Delete|Update]ActionView` + Those views all inherit from `UndoableActionBaseView` and + each handles a specific kind of action. + +`UndoableActionPredicate` + This predicate is used as a *selector* to pick the appropriate + view for actions. + +Apart from this main *undo-history view* a `txuuid` is stored in +the request's data `last_undoable_transaction` in order to allow +immediate undoing of a hastily validated operation. This is +handled in `cubicweb/web/application.py` in the `main_publish` and +`add_undo_link_to_msg` methods for the storing and displaying +respectively. + +Once the undo information is accessible, typically through a +`txuuid` in an *undo* URL, the actual undo operation can be +performed by the `UndoController` defined in +`cubicweb/web/views/basecontrollers.py`. This controller basically +extracts the `txuuid` and performs a call to `undo_transaction` and +in case of an undo-specific error, lets the top level publisher +handle it as a validation error. + + +Conclusion +~~~~~~~~~~ + +The undo mechanism relies upon a low level recording of the +mutating operation on the repository. Those records are accessible +through some method added to the *DB-API* and exposed to the +end-user either through a whole history view of through an +immediate undoing link in the message box. + +The undo feature is functional but the interface and configuration +options are still quite reduced. One major improvement would be to +be able to filter with a finer grain which transactions or actions +one wants to see in the *undo-history view*. Another critical +improvement would be to enable the undo feature on a part only of +the entity-relationship schema to avoid storing too much useless +data and reduce the underlying overhead. + +But both functionality are related to the strong design choice not +to represent transactions and actions as entities and +relations. This has huge benefits in terms of safety and conceptual +simplicity but prevents from using lots of convenient CubicWeb +features such as *facets* to access undo information. + +Before developing further the undo feature or eventually revising +this design choice, it appears that some return of experience is +strongly needed. So don't hesitate to try the undo feature in your +application and send us some feedback. + + +Notes +~~~~~ + +.. [1] The end-user Web interface could be improved to enable + user to choose whether he wishes to see private actions. + +.. [2] There is only five kind of elementary actions (beyond + merely accessing data for reading): + + * **C** : creating an entity + * **D** : deleting an entity + * **U** : updating an entity attributes + * **A** : adding a relation + * **R** : removing a relation + +.. [3] Meaning none of the actions in the transaction is + undone. Depending upon the application, it might make sense + to enable *partial* undo. That is to say undo in which some + actions could not be undo without preventing to undo the + others actions in the transaction (as long as it does not + break schema integrity). This is not forbidden by the + back-end but is deliberately not supported by the front-end + (for now at least).