A ActionType is a class which defines how to do
, undo
and redo
a particular action
in Baserow. It can freely use Handlers to do the logic, but it almost certainly
shouldn’t call any other ActionType’s unless it is some sort of meta
ActionAction if
we ever have one. ActionTypes will be retrieved from a registry given a type and
triggered by API
methods (
e.g. action_type_registry.get_by_type(DeleteWorkspaceAction).do(user, workspace_to_delete)
).
backend/src/baserow/core/actions/registries.py
there is a action_type_registry
which can be used to register ActionType
’sActionType
must implement do
/undo
/redo
methods.
do
Performs the action when a user requests it to happen, it must also save
a Action
model using cls.register_action
undo
Must undo the action done by do
. It must not save any Action
models.redo
Must redo the action after it has been undone by undo
. It must not save
any Action
models.ActionType
must implement a Params
dataclass which it will store any
parameters it needs to undo
or redo
the action in. An instance of this dataclass
must be provided to cls.register_action
in the do
method, and it will be
serialized to JSON and stored in the Action
table. When redo
or undo
is called
this dataclass
will be created again from the json in the Action
row and provided
to the function.See baserow.core.action.models.Action for more details.
id (serial) | user_id (fk to user table, nullable) | session (text nullable) | category (text) | created_on (auto_now_add DateTimeField) | type (text) | params (JSONB) | undone_at (nullable DateTimeField) | error (text nullable) |
---|---|---|---|---|---|---|---|---|
1 | 2 | ‘some-uuid-from-client’ | ‘root’ | datetime | ‘workspace_created’ | ‘{created_workspace_id:10}’ | null | null |
The ActionHandler
has undo
and redo
methods which can be used to trigger an
undo/redo for a user. There are two corresponding endpoints in /api/user/undo
and /api/user/redo
which call the ActionHandler
. To trigger an undo
/ redo
we
need three pieces of information:
client session id
. Every time a user does an action in Baserow we check the
ClientSessionId
header. If set we associate the action with that ClientSessionId
.
When a user then goes to undo or redo they also provide this header and we only let
them undo/redo actions with a matching ClientSessionId
. This lets us have different
undo/redo histories per tab the user has open as each tab will generate a
unique ClientSessionId
.category
. Every time an action is performed in Baserow we associate it with a
particular category. This is literally just a text column on the Action
model with
values like root
or table10
or workspace20
. An actions category describes in which
logical part of Baserow the action was performed. The ActionType
implementation
decides what to set its category to when calling cls.register_action
. When an
undo/redo occurs the web-frontend sends the categories the user is currently looking
at. For example if I have table 20 open, with workspace 6 in the side bar and I press
undo/redo the category sent will be:{
root: true,
table: 20,
workspace: 6
}
By sending this category to the undo/redo endpoint we are telling it to undo any actions which were done in:
For example, if I renamed table 20, then the table_update action would be in workspace 6 category. If I was then looking at table 20 in the UI and pressed undo, the UI would send the workspace 6 category as one of the active categories as table 20 is in workspace 6. Meaning I could then undo this rename. If i was to first switch to workspace 5 and press undo, the UI would send workspace 5 as the category and I wouldn’t be able to undo the rename of table 20 until I switched back into a part of the UI where the workspace 6 category is active.
example_client_session_id
is generated and
stored in the auth
store. (its a uuid normally).undoRedo
store to
be: {root: true, table_id:10, application_id:2, workspace_id:1}
ClientSessionId
header is set on the request
to example_client_session_id
action_type_registry.get(UpdateTableAction).do(user, ...)
category
of the action to be workspace1
ClientSessionId
is found from the request and the session of the action
is set to example_client_session_id
user
of the action is set to User A
action.params
JSONField to facilitate
undos and redos.Undo
undo
endpoint with the category
request data value
set to the current category of the page the user has open obtained from
the undoRedo
store (see above).
ClientSessionId
header is set on the request
to example_client_session_id
ActionHandler.undo
is called.
User A
in
session example_client_session_id
and in any of the following
categories ["root", "workspace1", "application2", "table10"]
. These were
calculated from the category parameter provided to the endpoint.workspace
, it was done by User A
and it has not yet been undone (
the undone_at
column is null).Params
dataclassdatetime.now(tz=timezone.utc)
indicating it has now been undoneImagine a situation when two users are working on a table at the same time, in order they: 1 User A changes a cell in a field called ‘date’
We cannot undo User A’s latest action as it was to a cell in the now deleted field ’ name’. What will happen when is:
undone
by setting it’s undone_at
datetime field
to datetime.now(tz=timezone.utc)
Interestingly, if the user then presses redo twice we will:
can't redo due to error, skipping.