doc/book/additionnal_services/undo.rst
changeset 10491 c67bcee93248
parent 8698 b2443e266661
equal deleted inserted replaced
10490:76ab3c71aff2 10491:c67bcee93248
       
     1 Undoing changes in CubicWeb
       
     2 ---------------------------
       
     3 
       
     4 Many desktop applications offer the possibility for the user to
       
     5 undo its last changes : this *undo feature* has now been
       
     6 integrated into the CubicWeb framework. This document will
       
     7 introduce you to the *undo feature* both from the end-user and the
       
     8 application developer point of view.
       
     9 
       
    10 But because a semantic web application and a common desktop
       
    11 application are not the same thing at all, especially as far as
       
    12 undoing is concerned, we will first introduce *what* is the *undo
       
    13 feature* for now.
       
    14 
       
    15 What's *undoing* in a CubicWeb application
       
    16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
    17 
       
    18 What is an *undo feature* is quite intuitive in the context of a
       
    19 desktop application. But it is a bit subtler in the context of a
       
    20 Semantic Web application. This section introduces some of the main
       
    21 differences between a classical desktop and a Semantic Web
       
    22 applications to keep in mind in order to state precisely *what we
       
    23 want*.
       
    24 
       
    25 The notion transactions
       
    26 ```````````````````````
       
    27 
       
    28 A CubicWeb application acts upon an *Entity-Relationship* model,
       
    29 described by a schema. This allows to ensure some data integrity
       
    30 properties. It also implies that changes are made by all-or-none
       
    31 groups called *transactions*, such that the data integrity is
       
    32 preserved whether the transaction is completely applied *or* none
       
    33 of it is applied.
       
    34 
       
    35 A transaction can thus include more actions than just those
       
    36 directly required by the main purpose of the user.  For example,
       
    37 when a user *just* writes a new blog entry, the underlying
       
    38 *transaction* holds several *actions* as illustrated below :
       
    39 
       
    40 * By admin on 2012/02/17 15:18 - Created Blog entry : Torototo
       
    41 
       
    42   #. Created Blog entry : Torototo
       
    43   #. Added relation : Torototo owned by admin
       
    44   #. Added relation : Torototo blog entry of Undo Blog
       
    45   #. Added relation : Torototo in state draft (draft)
       
    46   #. Added relation : Torototo created by admin
       
    47 
       
    48 Because of the very nature (all-or-none) of the transactions, the
       
    49 "undoable stuff" are the transactions and not the actions !
       
    50 
       
    51 Public and private actions within a transaction
       
    52 ```````````````````````````````````````````````
       
    53 
       
    54 Actually, within the *transaction* "Created Blog entry :
       
    55 Torototo", two of those *actions* are said to be *public* and
       
    56 the others are said to be *private*. *Public* here means that the
       
    57 public actions (1 and 3) were directly requested by the end user ;
       
    58 whereas *private* means that the other actions (2, 4, 5) were
       
    59 triggered "under the hood" to fulfill various requirements for the
       
    60 user operation (ensuring integrity, security, ... ).
       
    61 
       
    62 And because quite a lot of actions can be triggered by a "simple"
       
    63 end-user request, most of which the end-user is not (and does not
       
    64 need or wish to be) aware, only the so-called public actions will
       
    65 appear [1]_ in the description of the an undoable transaction.
       
    66 
       
    67 * By admin on 2012/02/17 15:18 - Created Blog entry : Torototo
       
    68 
       
    69   #. Created Blog entry : Torototo
       
    70   #. Added relation : Torototo blog entry of Undo Blog
       
    71 
       
    72 But note that both public and private actions will be undone
       
    73 together when the transaction is undone.
       
    74 
       
    75 (In)dependent transactions : the simple case
       
    76 ````````````````````````````````````````````
       
    77 
       
    78 A CubicWeb application can be used *simultaneously* by different users
       
    79 (whereas a single user works on an given office document at a
       
    80 given time), so that there is not always a single history
       
    81 time-line in the CubicWeb case. Moreover CubicWeb provides
       
    82 security through the mechanism of *permissions* granted to each
       
    83 user. This can lead to some transactions *not* being undoable in
       
    84 some contexts.
       
    85 
       
    86 In the simple case two (unprivileged) users Alice and Bob make
       
    87 relatively independent changes : then both Alice and Bob can undo
       
    88 their changes. But in some case there is a clean dependency
       
    89 between Alice's and Bob's actions or between actions of one of
       
    90 them. For example let's suppose that :
       
    91 
       
    92 - Alice has created a blog,
       
    93 - then has published a first post inside,
       
    94 - then Bob has published a second post in the same blog,
       
    95 - and finally Alice has updated its post contents.
       
    96 
       
    97 Then it is clear that Alice can undo her contents changes and Bob
       
    98 can undo his post creation independently. But Alice can not undo
       
    99 her post creation while she has not first undone her changes.
       
   100 It is also clear that Bob should *not* have the
       
   101 permissions to undo any of Alice's transactions.
       
   102 
       
   103 
       
   104 More complex dependencies between transactions
       
   105 ``````````````````````````````````````````````
       
   106 
       
   107 But more surprising things can quickly happen. Going back to the
       
   108 previous example, Alice *can* undo the creation of the blog after
       
   109 Bob has published its post in it ! But this is possible only
       
   110 because the schema does not *require* for a post to be in a
       
   111 blog. Would the *blog entry of* relation have been mandatory, then
       
   112 Alice could not have undone the blog creation because it would
       
   113 have broken integrity constraint for Bob's post.
       
   114 
       
   115 When a user attempts to undo a transaction the system will check
       
   116 whether a later transaction has explicit dependency on the
       
   117 would-be-undone transaction. In this case the system will not even
       
   118 attempt the undo operation and inform the user.
       
   119 
       
   120 If no such dependency is detected the system will attempt the undo
       
   121 operation but it can fail, typically because of integrity
       
   122 constraint violations. In such a case the undo operation is
       
   123 completely [3]_ rollbacked.
       
   124 
       
   125 
       
   126 The *undo feature* for CubicWeb end-users
       
   127 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   128 
       
   129 The exposition of the undo feature to the end-user through a Web
       
   130 interface is still quite basic and will be improved toward a
       
   131 greater usability. But it is already fully functional.  For now
       
   132 there are two ways to access the *undo feature* as long as the it
       
   133 has been activated in the instance configuration file with the
       
   134 option *undo-support=yes*.
       
   135 
       
   136 Immediately after having done the change to be canceled through
       
   137 the **undo** link in the message. This allows to undo an
       
   138 hastily action immediately. For example, just after having
       
   139 validated the creation of the blog entry *A second blog entry* we
       
   140 get the following message, allowing to undo the creation.
       
   141 
       
   142 .. image:: /images/undo_mesage_w600.png
       
   143    :width: 600px
       
   144    :alt: Screenshot of the undo link in the message
       
   145    :align: center
       
   146 
       
   147 At any time we can access the **undo-history view** accessible from the
       
   148 start-up page.
       
   149 
       
   150 .. image:: /images/undo_startup-link_w600.png
       
   151    :width: 600px
       
   152    :alt: Screenshot of the startup menu with access to the history view
       
   153    :align: center
       
   154 
       
   155 This view will provide inspection of the transaction and their (public)
       
   156 actions. Each transaction provides its own **undo** link. Only the
       
   157 transactions the user has permissions to see and undo will be shown.
       
   158 
       
   159 .. image:: /images/undo_history-view_w600.png
       
   160    :width: 600px
       
   161    :alt: Screenshot of the undo history main view
       
   162    :align: center
       
   163 
       
   164 If the user attempts to undo a transaction which can't be undone or
       
   165 whose undoing fails, then a message will explain the situation and
       
   166 no partial undoing will be left behind.
       
   167 
       
   168 This is all for the end-user side of the undo mechanism : this is
       
   169 quite simple indeed ! Now, in the following section, we are going
       
   170 to introduce the developer side of the undo mechanism.
       
   171 
       
   172 The *undo feature* for CubicWeb application developers
       
   173 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   174 
       
   175 A word of warning : this section is intended for developers,
       
   176 already having some knowledge of what's under CubicWeb's hood. If
       
   177 it is not *yet* the case, please refer to CubicWeb documentation
       
   178 http://docs.cubicweb.org/ .
       
   179 
       
   180 Overview
       
   181 ````````
       
   182 
       
   183 The core of the undo mechanisms is at work in the *native source*,
       
   184 beyond the RQL. This does mean that *transactions* and *actions*
       
   185 are *no entities*. Instead they are represented at the SQL level
       
   186 and exposed through the *DB-API* supported by the repository
       
   187 *Connection* objects.
       
   188 
       
   189 Once the *undo feature* has been activated in the instance
       
   190 configuration file with the option *undo-support=yes*, each
       
   191 mutating operation (cf. [2]_) will be recorded in some special SQL
       
   192 table along with its associated transaction. Transaction are
       
   193 identified by a *txuuid* through which the functions of the
       
   194 *DB-API* handle them.
       
   195 
       
   196 On the web side the last commited transaction *txuuid* is
       
   197 remembered in the request's data to allow for imediate undoing
       
   198 whereas the *undo-history view* relies upon the *DB-API* to list
       
   199 the accessible transactions. The actual undoing is performed by
       
   200 the *UndoController* accessible at URL of the form
       
   201 `www.my.host/my/instance/undo?txuuid=...`
       
   202 
       
   203 The repository side
       
   204 ```````````````````
       
   205 
       
   206 Please refer to the file `cubicweb/server/sources/native.py` and
       
   207 `cubicweb/transaction.py` for the details.
       
   208 
       
   209 The undoing information is mainly stored in three SQL tables:
       
   210 
       
   211 `transactions`
       
   212     Stores the txuuid, the user eid and the date-and-time of
       
   213     the transaction. This table is referenced by the two others.
       
   214 
       
   215 `tx_entity_actions`
       
   216     Stores the undo information for actions on entities.
       
   217 
       
   218 `tx_relation_actions`
       
   219     Stores the undo information for the actions on relations.
       
   220 
       
   221 When the undo support is activated, entries are added to those
       
   222 tables for each mutating operation on the data repository, and are
       
   223 deleted on each transaction undoing.
       
   224 
       
   225 Those table are accessible through the following methods of the
       
   226 repository `Connection` object :
       
   227 
       
   228 `undoable_transactions`
       
   229     Returns a list of `Transaction` objects accessible to the user
       
   230     and according to the specified filter(s) if any.
       
   231 
       
   232 `tx_info`
       
   233     Returns a `Transaction` object from a `txuuid`
       
   234 
       
   235 `undo_transaction`
       
   236     Returns the list of `Action` object for the given `txuuid`.
       
   237 
       
   238     NB:  By default it only return *public* actions.
       
   239 
       
   240 The web side
       
   241 ````````````
       
   242 
       
   243 The exposure of the *undo feature* to the end-user through the Web
       
   244 interface relies on the *DB-API* introduced above. This implies
       
   245 that the *transactions* and *actions* are not *entities* linked by
       
   246 *relations* on which the usual views can be applied directly.
       
   247 
       
   248 That's why the file `cubicweb/web/views/undohistory.py` defines
       
   249 some dedicated views to access the undo information :
       
   250 
       
   251 `UndoHistoryView`
       
   252     This is a *StartupView*, the one accessible from the home
       
   253     page of the instance which list all transactions.
       
   254 
       
   255 `UndoableTransactionView`
       
   256     This view handles the display of a single `Transaction` object.
       
   257 
       
   258 `UndoableActionBaseView`
       
   259     This (abstract) base class provides private methods to build
       
   260     the display of actions whatever their nature.
       
   261 
       
   262 `Undoable[Add|Remove|Create|Delete|Update]ActionView`
       
   263     Those views all inherit from `UndoableActionBaseView` and
       
   264     each handles a specific kind of action.
       
   265 
       
   266 `UndoableActionPredicate`
       
   267     This predicate is used as a *selector* to pick the appropriate
       
   268     view for actions.
       
   269 
       
   270 Apart from this main *undo-history view* a `txuuid` is stored in
       
   271 the request's data `last_undoable_transaction` in order to allow
       
   272 immediate undoing of a hastily validated operation. This is
       
   273 handled in `cubicweb/web/application.py` in the `main_publish` and
       
   274 `add_undo_link_to_msg` methods for the storing and displaying
       
   275 respectively.
       
   276 
       
   277 Once the undo information is accessible, typically through a
       
   278 `txuuid` in an *undo* URL, the actual undo operation can be
       
   279 performed by the `UndoController` defined in
       
   280 `cubicweb/web/views/basecontrollers.py`. This controller basically
       
   281 extracts the `txuuid` and performs a call to `undo_transaction` and
       
   282 in case of an undo-specific error, lets the top level publisher
       
   283 handle it as a validation error.
       
   284 
       
   285 
       
   286 Conclusion
       
   287 ~~~~~~~~~~
       
   288 
       
   289 The undo mechanism relies upon a low level recording of the
       
   290 mutating operation on the repository. Those records are accessible
       
   291 through some method added to the *DB-API* and exposed to the
       
   292 end-user either through a whole history view of through an
       
   293 immediate undoing link in the message box.
       
   294 
       
   295 The undo feature is functional but the interface and configuration
       
   296 options are still quite reduced. One major improvement would be to
       
   297 be able to filter with a finer grain which transactions or actions
       
   298 one wants to see in the *undo-history view*. Another critical
       
   299 improvement would be to enable the undo feature on a part only of
       
   300 the entity-relationship schema to avoid storing too much useless
       
   301 data and reduce the underlying overhead.
       
   302 
       
   303 But both functionality are related to the strong design choice not
       
   304 to represent transactions and actions as entities and
       
   305 relations. This has huge benefits in terms of safety and conceptual
       
   306 simplicity but prevents from using lots of convenient CubicWeb
       
   307 features such as *facets* to access undo information.
       
   308 
       
   309 Before developing further the undo feature or eventually revising
       
   310 this design choice, it appears that some return of experience is
       
   311 strongly needed. So don't hesitate to try the undo feature in your
       
   312 application and send us some feedback.
       
   313 
       
   314 
       
   315 Notes
       
   316 ~~~~~
       
   317 
       
   318 .. [1] The end-user Web interface could be improved to enable
       
   319        user to choose whether he wishes to see private actions.
       
   320 
       
   321 .. [2] There is only five kind of elementary actions (beyond
       
   322        merely accessing data for reading):
       
   323 
       
   324        * **C** : creating an entity
       
   325        * **D** : deleting an entity
       
   326        * **U** : updating an entity attributes
       
   327        * **A** : adding a relation
       
   328        * **R** : removing a relation
       
   329 
       
   330 .. [3] Meaning none of the actions in the transaction is
       
   331        undone. Depending upon the application, it might make sense
       
   332        to enable *partial* undo. That is to say undo in which some
       
   333        actions could not be undo without preventing to undo the
       
   334        others actions in the transaction (as long as it does not
       
   335        break schema integrity). This is not forbidden by the
       
   336        back-end but is deliberately not supported by the front-end
       
   337        (for now at least).