Skip to content

Commit 98f824c

Browse files
committed
Merge branch 'resolve-realtime-node-replacement-undo-redo' into 'develop'
Resolve automation node replacement undo/redo See merge request baserow/baserow!3748
2 parents 8abf923 + 9048e68 commit 98f824c

File tree

24 files changed

+602
-47
lines changed

24 files changed

+602
-47
lines changed

backend/src/baserow/contrib/automation/apps.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def ready(self):
4848
UpdateAutomationNodeOperationType,
4949
)
5050
from baserow.contrib.automation.nodes.registries import (
51+
ReplaceAutomationNodeTrashOperationType,
5152
automation_node_type_registry,
5253
)
5354
from baserow.contrib.automation.nodes.trash_types import (
@@ -169,6 +170,12 @@ def ready(self):
169170
)
170171
automation_node_type_registry.register(CorePeriodicTriggerNodeType())
171172

173+
from baserow.core.trash.registries import trash_operation_type_registry
174+
175+
trash_operation_type_registry.register(
176+
ReplaceAutomationNodeTrashOperationType()
177+
)
178+
172179
from baserow.contrib.automation.data_providers.data_provider_types import (
173180
PreviousNodeProviderType,
174181
)

backend/src/baserow/contrib/automation/nodes/actions.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
from baserow.contrib.automation.nodes.handler import AutomationNodeHandler
1313
from baserow.contrib.automation.nodes.models import AutomationNode
1414
from baserow.contrib.automation.nodes.node_types import AutomationNodeType
15+
from baserow.contrib.automation.nodes.registries import (
16+
ReplaceAutomationNodeTrashOperationType,
17+
)
1518
from baserow.contrib.automation.nodes.service import AutomationNodeService
19+
from baserow.contrib.automation.nodes.signals import automation_node_replaced
1620
from baserow.contrib.automation.nodes.trash_types import AutomationNodeTrashableItemType
1721
from baserow.contrib.automation.nodes.types import NextAutomationNodeValues
1822
from baserow.contrib.automation.workflows.models import AutomationWorkflow
@@ -432,12 +436,25 @@ def undo(
432436
action_to_undo: Action,
433437
):
434438
# Restore the node to its original type.
435-
TrashHandler.restore_item(
439+
restored_node = TrashHandler.restore_item(
436440
user,
437441
AutomationNodeTrashableItemType.type,
438442
params.original_node_id,
439443
)
440-
AutomationNodeService().delete_node(user, params.node_id)
444+
# Trash the node of the new type, and pass its operation type so that its
445+
# trash entry is flagged as managed to prevent users from restoring it.
446+
deleted_node = AutomationNodeService().delete_node(
447+
user,
448+
params.node_id,
449+
trash_operation_type=ReplaceAutomationNodeTrashOperationType.type,
450+
)
451+
automation_node_replaced.send(
452+
cls,
453+
workflow=restored_node.workflow,
454+
deleted_node=deleted_node,
455+
restored_node=restored_node.specific,
456+
user=user,
457+
)
441458

442459
@classmethod
443460
def redo(
@@ -447,9 +464,22 @@ def redo(
447464
action_to_redo: Action,
448465
):
449466
# Restore the node to its new type again.
450-
TrashHandler.restore_item(
467+
restored_node = TrashHandler.restore_item(
451468
user,
452469
AutomationNodeTrashableItemType.type,
453470
params.node_id,
454471
)
455-
AutomationNodeService().delete_node(user, params.original_node_id)
472+
# Trash the node of the original type, and pass its operation type so that its
473+
# trash entry is flagged as managed to prevent users from restoring it.
474+
deleted_node = AutomationNodeService().delete_node(
475+
user,
476+
params.original_node_id,
477+
trash_operation_type=ReplaceAutomationNodeTrashOperationType.type,
478+
)
479+
automation_node_replaced.send(
480+
cls,
481+
workflow=restored_node.workflow,
482+
restored_node=restored_node.specific,
483+
deleted_node=deleted_node,
484+
user=user,
485+
)

backend/src/baserow/contrib/automation/nodes/handler.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,15 @@ def create_node(
223223
"previous_node_output"
224224
] = before.previous_node_output
225225

226-
# Find any nodes which have a previous node ID and output
227-
# that match the before node's previous node ID and output.
226+
# Find the nodes that are using `before` as their previous node.
227+
# If `before` has a `previous_node_id`, then we get `before.previous_node`'s
228+
# next nodes. If there's no `previous_node_id`, then `before` is a trigger,
229+
# so we want the nodes that come after this trigger.
228230
node_previous_ids_to_update = list(
229231
workflow.automation_workflow_nodes.filter(
230-
previous_node_id=before.previous_node_id,
232+
previous_node_id=before.id
233+
if before.previous_node_id is None
234+
else before.previous_node_id,
231235
previous_node_output=before.previous_node_output,
232236
)
233237
)

backend/src/baserow/contrib/automation/nodes/registries.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from baserow.core.services.exceptions import InvalidServiceTypeDispatchSource
2626
from baserow.core.services.handler import ServiceHandler
2727
from baserow.core.services.registries import ServiceTypeSubClass, service_type_registry
28+
from baserow.core.trash.registries import TrashOperationType
2829

2930

3031
class AutomationNodeType(
@@ -303,4 +304,28 @@ class AutomationNodeTypeRegistry(
303304
name = "automation_node_type"
304305

305306

307+
class ReplaceAutomationNodeTrashOperationType(TrashOperationType):
308+
"""
309+
The replace-automation-node trash operation is used when an automation node is
310+
replaced with another node type. This operation type exists to ensure that extra
311+
steps are followed when the node is restored from its trashed state.
312+
"""
313+
314+
type = "replace_automation_node"
315+
316+
"""
317+
This trash operation type is 'managed'. We don't want users to interact with
318+
it in the workspace trash, the system is responsible for it.
319+
"""
320+
managed = True
321+
322+
"""
323+
In this trash operation type we don't want to send any created or deleted signals.
324+
We need to be precise with our realtime signals, so at a strategic time we use
325+
the `replace` signal instead.
326+
"""
327+
send_post_restore_created_signal = False
328+
send_post_trash_deleted_signal = False
329+
330+
306331
automation_node_type_registry = AutomationNodeTypeRegistry()

backend/src/baserow/contrib/automation/nodes/service.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
ReadAutomationNodeOperationType,
2020
UpdateAutomationNodeOperationType,
2121
)
22-
from baserow.contrib.automation.nodes.registries import automation_node_type_registry
22+
from baserow.contrib.automation.nodes.registries import (
23+
ReplaceAutomationNodeTrashOperationType,
24+
automation_node_type_registry,
25+
)
2326
from baserow.contrib.automation.nodes.signals import (
2427
automation_node_created,
2528
automation_node_deleted,
29+
automation_node_replaced,
2630
automation_node_updated,
2731
automation_nodes_reordered,
2832
)
@@ -186,12 +190,20 @@ def update_node(
186190

187191
return updated_node
188192

189-
def delete_node(self, user: AbstractUser, node_id: int) -> AutomationNode:
193+
def delete_node(
194+
self,
195+
user: AbstractUser,
196+
node_id: int,
197+
trash_operation_type: Optional[str] = None,
198+
) -> AutomationNode:
190199
"""
191200
Deletes the specified automation node.
192201
193202
:param user: The user trying to delete the node.
194203
:param node_id: The ID of the node to delete.
204+
:param trash_operation_type: The trash operation type to use when trashing
205+
the node.
206+
:return: The deleted node.
195207
:raises AutomationTriggerModificationDisallowed: If the node is a trigger.
196208
"""
197209

@@ -205,15 +217,22 @@ def delete_node(self, user: AbstractUser, node_id: int) -> AutomationNode:
205217
)
206218

207219
automation = node.workflow.automation
208-
TrashHandler.trash(user, automation.workspace, automation, node)
209-
210-
automation_node_deleted.send(
211-
self,
212-
workflow=node.workflow,
213-
node_id=node.id,
214-
user=user,
220+
trash_entry = TrashHandler.trash(
221+
user,
222+
automation.workspace,
223+
automation,
224+
node,
225+
trash_operation_type=trash_operation_type,
215226
)
216227

228+
if trash_entry.get_operation_type().send_post_trash_deleted_signal:
229+
automation_node_deleted.send(
230+
self,
231+
workflow=node.workflow,
232+
node_id=node.id,
233+
user=user,
234+
)
235+
217236
return node
218237

219238
def order_nodes(
@@ -331,6 +350,7 @@ def replace_node(
331350
workflow=node.workflow,
332351
before=node,
333352
order=node.order,
353+
previous_node_id=node.previous_node_id,
334354
previous_node_output=node.previous_node_output,
335355
**prepared_values,
336356
)
@@ -339,8 +359,24 @@ def replace_node(
339359
# After the node creation, the replaced node has changed
340360
node.refresh_from_db()
341361

362+
# Trash the old node, assigning it a specific trash operation
363+
# type so that we know it was replaced when restoring it.
342364
automation = node.workflow.automation
343-
TrashHandler.trash(user, automation.workspace, automation, node)
365+
TrashHandler.trash(
366+
user,
367+
automation.workspace,
368+
automation,
369+
node,
370+
trash_operation_type=ReplaceAutomationNodeTrashOperationType.type,
371+
)
372+
373+
automation_node_replaced.send(
374+
self,
375+
workflow=new_node.workflow,
376+
restored_node=new_node,
377+
deleted_node=node,
378+
user=user,
379+
)
344380

345381
return ReplacedAutomationNode(
346382
node=new_node,

backend/src/baserow/contrib/automation/nodes/signals.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
automation_node_created = Signal()
44
automation_node_updated = Signal()
55
automation_node_deleted = Signal()
6+
automation_node_replaced = Signal()
67
automation_nodes_reordered = Signal()

backend/src/baserow/contrib/automation/nodes/trash_types.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
from baserow.contrib.automation.nodes.operations import (
66
RestoreAutomationNodeOperationType,
77
)
8-
from baserow.contrib.automation.nodes.signals import (
9-
automation_node_created,
10-
automation_node_deleted,
11-
)
8+
from baserow.contrib.automation.nodes.signals import automation_node_created
129
from baserow.contrib.automation.workflows.models import AutomationWorkflow
1310
from baserow.core.models import TrashEntry
1411
from baserow.core.trash.exceptions import TrashItemRestorationDisallowed
@@ -51,14 +48,11 @@ def trash(
5148
item_to_trash.previous_node, next_nodes, item_to_trash.previous_node_output
5249
)
5350

54-
automation_node_deleted.send(
55-
self,
56-
workflow=item_to_trash.workflow,
57-
node_id=item_to_trash.id,
58-
user=requesting_user,
59-
)
60-
61-
def restore(self, trashed_item: AutomationActionNode, trash_entry: TrashEntry):
51+
def restore(
52+
self,
53+
trashed_item: AutomationActionNode,
54+
trash_entry: TrashEntry,
55+
):
6256
workflow = trashed_item.workflow
6357
next_nodes = list(
6458
AutomationNodeHandler().get_next_nodes(workflow, trashed_item.previous_node)
@@ -106,7 +100,8 @@ def restore(self, trashed_item: AutomationActionNode, trash_entry: TrashEntry):
106100
updates.append(next_node)
107101
AutomationNode.objects.bulk_update(updates, ["previous_node_output"])
108102

109-
automation_node_created.send(self, node=trashed_item, user=None)
103+
if trash_entry.get_operation_type().send_post_restore_created_signal:
104+
automation_node_created.send(self, node=trashed_item, user=None)
110105

111106
def permanently_delete_item(
112107
self, trashed_item: AutomationNode, trash_item_lookup_cache=None

backend/src/baserow/contrib/automation/nodes/ws/signals.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
from baserow.contrib.automation.nodes.operations import (
1212
ListAutomationNodeOperationType,
1313
ReadAutomationNodeOperationType,
14+
UpdateAutomationNodeOperationType,
1415
)
1516
from baserow.contrib.automation.nodes.signals import (
1617
automation_node_created,
1718
automation_node_deleted,
19+
automation_node_replaced,
1820
automation_node_updated,
1921
automation_nodes_reordered,
2022
)
@@ -98,3 +100,29 @@ def nodes_reordered(
98100
getattr(user, "web_socket_id", None),
99101
)
100102
)
103+
104+
105+
@receiver(automation_node_replaced)
106+
def node_replaced(
107+
sender,
108+
workflow: AutomationWorkflow,
109+
deleted_node: AutomationNode,
110+
restored_node: AutomationNode,
111+
user: AbstractUser,
112+
**kwargs,
113+
):
114+
transaction.on_commit(
115+
lambda: broadcast_to_permitted_users.delay(
116+
workflow.automation.workspace_id,
117+
UpdateAutomationNodeOperationType.type,
118+
AutomationWorkflowObjectScopeType.type,
119+
workflow.id,
120+
{
121+
"type": "automation_node_replaced",
122+
"workflow_id": workflow.id,
123+
"deleted_node": AutomationNodeSerializer(deleted_node).data,
124+
"restored_node": AutomationNodeSerializer(restored_node).data,
125+
},
126+
getattr(user, "web_socket_id", None),
127+
)
128+
)

backend/src/baserow/contrib/automation/trash_types.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ def trash(
3030

3131
super().trash(item_to_trash, requesting_user, trash_entry)
3232

33-
def restore(self, trashed_item: Automation, trash_entry: TrashEntry):
33+
def restore(
34+
self,
35+
trashed_item: Automation,
36+
trash_entry: TrashEntry,
37+
):
3438
super().restore(trashed_item, trash_entry)
3539
application_created.send(self, application=trashed_item, user=None)
3640

backend/src/baserow/contrib/automation/workflows/trash_types.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ def trash(
3434
user=None,
3535
)
3636

37-
def restore(self, trashed_item: AutomationWorkflow, trash_entry: TrashEntry):
37+
def restore(
38+
self,
39+
trashed_item: AutomationWorkflow,
40+
trash_entry: TrashEntry,
41+
):
3842
super().restore(trashed_item, trash_entry)
3943
automation_workflow_created.send(self, workflow=trashed_item, user=None)
4044

0 commit comments

Comments
 (0)