|
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). |