Skip to content

Commit 2a3fdc9

Browse files
authored
Add node duplication
1 parent 14e029f commit 2a3fdc9

File tree

12 files changed

+148
-11
lines changed

12 files changed

+148
-11
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,9 @@ def undo(
264264
action_to_undo: Action,
265265
):
266266
# Trash the duplicated node.
267-
AutomationNodeService().delete_node(user, params.duplicated_node_id)
267+
AutomationNodeService().delete_node(
268+
user, params.duplicated_node_id, ignore_user_for_signal=True
269+
)
268270

269271
@classmethod
270272
def redo(

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,7 @@ def update_node(
241241
)
242242

243243
def delete_node(
244-
self,
245-
user: AbstractUser,
246-
node_id: int,
244+
self, user: AbstractUser | None, node_id: int, ignore_user_for_signal=False
247245
) -> AutomationNode:
248246
"""
249247
Deletes the specified automation node.
@@ -268,7 +266,7 @@ def delete_node(
268266
node.get_type().before_delete(node.specific)
269267

270268
TrashHandler.trash(
271-
user,
269+
user if not ignore_user_for_signal else None,
272270
automation.workspace,
273271
automation,
274272
node,
@@ -278,7 +276,7 @@ def delete_node(
278276
self,
279277
workflow=workflow,
280278
node_id=node.id,
281-
user=user,
279+
user=user if not ignore_user_for_signal else None,
282280
)
283281

284282
return node

web-frontend/modules/automation/components/workflow/WorkflowEdge.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
@remove-node="emit('remove-node', $event)"
5151
@replace-node="emit('replace-node', $event)"
5252
@move-node="emit('move-node', $event)"
53+
@duplicate-node="emit('duplicate-node', $event)"
5354
/>
5455
</div>
5556
</template>
@@ -90,7 +91,14 @@ const props = defineProps({
9091
},
9192
})
9293
93-
const emit = defineEmits(['add-node', 'select-node', 'move-node'])
94+
const emit = defineEmits([
95+
'add-node',
96+
'select-node',
97+
'move-node',
98+
'remove-node',
99+
'replace-node',
100+
'duplicate-node',
101+
])
94102
95103
const store = useStore()
96104
const workflow = inject('workflow')

web-frontend/modules/automation/components/workflow/WorkflowEditor.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
@replace-node="emit('replace-node', $event)"
3030
@select-node="emit('input', $event.id)"
3131
@move-node="emit('move-node', $event)"
32+
@duplicate-node="emit('duplicate-node', $event)"
3233
/>
3334
<template v-else>
3435
<div class="workflow-editor__trigger-selector" @scroll.stop>
@@ -72,7 +73,13 @@ const props = defineProps({
7273
})
7374
7475
const vueFlowEdges = []
75-
const emit = defineEmits(['add-node', 'remove-node', 'input', 'move-node'])
76+
const emit = defineEmits([
77+
'add-node',
78+
'remove-node',
79+
'input',
80+
'move-node',
81+
'duplicate-node',
82+
])
7683
7784
const { onPaneClick } = useVueFlow()
7885

web-frontend/modules/automation/components/workflow/WorkflowNode.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
@select-node="emit('select-node', $event)"
1515
@remove-node="emit('remove-node', $event)"
1616
@replace-node="emit('replace-node', $event)"
17+
@duplicate-node="emit('duplicate-node', $event)"
1718
/>
1819
<div v-if="nodeType.isContainer" class="workflow-node__children">
1920
<div ref="children" class="workflow-node__children-wrapper">
@@ -67,6 +68,7 @@
6768
@remove-node="emit('remove-node', $event)"
6869
@replace-node="emit('replace-node', $event)"
6970
@move-node="emit('move-node', $event)"
71+
@duplicate-node="emit('duplicate-node', $event)"
7072
/>
7173
</div>
7274
</div>
@@ -111,6 +113,7 @@ const emit = defineEmits([
111113
'remove-node',
112114
'replace-node',
113115
'move-node',
116+
'duplicate-node',
114117
])
115118
116119
const { app } = useContext()

web-frontend/modules/automation/components/workflow/WorkflowNodeContent.vue

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,17 @@
7575
{{ $t('workflowNode.moreReplace') }}
7676
</a>
7777
</li>
78-
<li class="context__menu-item">
78+
<li v-if="canBeDuplicated" class="context__menu-item">
79+
<a
80+
role="button"
81+
class="context__menu-item-link"
82+
@click="emit('duplicate-node', node.id)"
83+
>
84+
<i class="context__menu-item-icon iconoir-copy"></i>
85+
{{ $t('workflowNode.moreDuplicate') }}
86+
</a>
87+
</li>
88+
<li class="context__menu-item context__menu-item--with-separator">
7989
<a
8090
:key="getDeleteErrorMessage"
8191
v-tooltip="getDeleteErrorMessage || null"
@@ -131,7 +141,12 @@ const props = defineProps({
131141
},
132142
})
133143
134-
const emit = defineEmits(['remove-node', 'replace-node', 'select-node'])
144+
const emit = defineEmits([
145+
'remove-node',
146+
'replace-node',
147+
'select-node',
148+
'duplicate-node',
149+
])
135150
136151
const isDragging = ref(false)
137152
@@ -277,4 +292,11 @@ const getDataBeforeLabel = computed(() => {
277292
output,
278293
})
279294
})
295+
296+
const canBeDuplicated = computed(() => {
297+
return nodeType.value.isDuplicable({
298+
workflow: workflow.value,
299+
node: props.node,
300+
})
301+
})
280302
</script>

web-frontend/modules/automation/locales/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@
132132
"beforeLabelRepeat": "For each item",
133133
"moreEdit": "Edit",
134134
"moreReplace": "Replace",
135-
"nodeOptions": "Node options"
135+
"nodeOptions": "Node options",
136+
"displayLabelDebug": "ID: {id} | Prev: {previousNodeId} | {outputUid}",
137+
"moreDuplicate": "Duplicate"
136138
},
137139
"workflowAddNode": {
138140
"displayTitle": "Create automation node"

web-frontend/modules/automation/nodeTypeMixins.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export const TriggerNodeTypeMixin = (Base) =>
2020
getDeleteErrorMessage({ workflow, node }) {
2121
return this.app.i18n.t('nodeType.triggerDeletionError')
2222
}
23+
24+
isDuplicable({ workflow, node }) {
25+
return false
26+
}
2327
}
2428

2529
export const ActionNodeTypeMixin = (Base) =>

web-frontend/modules/automation/nodeTypes.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ export class NodeType extends Registerable {
158158
return Boolean(this.getDeleteErrorMessage({ workflow, node }))
159159
}
160160

161+
/**
162+
* Returns whether the given node can be duplicated.
163+
*/
164+
isDuplicable({ workflow, node }) {
165+
return true
166+
}
167+
161168
/**
162169
* Returns the error message we should show when a node cannot be deleted.
163170
* By default, this method is empty, but can be overridden by the node type
@@ -655,6 +662,10 @@ export class CoreIteratorNodeType extends containerNodeTypeMixin(
655662
}
656663
return ''
657664
}
665+
666+
isDuplicable({ workflow, node }) {
667+
return false
668+
}
658669
}
659670

660671
export class CoreSMTPEmailNodeType extends ActionNodeTypeMixin(NodeType) {
@@ -805,6 +816,10 @@ export class CoreRouterNodeType extends ActionNodeTypeMixin(
805816
},
806817
]
807818
}
819+
820+
isDuplicable({ workflow, node }) {
821+
return false
822+
}
808823
}
809824

810825
export class AIAgentActionNodeType extends ActionNodeTypeMixin(NodeType) {

web-frontend/modules/automation/pages/automationWorkflow.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
@remove-node="handleRemoveNode"
2828
@replace-node="handleReplaceNode"
2929
@move-node="handleMoveNode"
30+
@duplicate-node="handleDuplicateNode"
3031
/>
3132
</client-only>
3233
</div>
@@ -265,6 +266,12 @@ export default {
265266
notifyIf(err, 'automation')
266267
}
267268
},
269+
async handleDuplicateNode(nodeId) {
270+
await this.$store.dispatch('automationWorkflowNode/duplicate', {
271+
workflow: this.workflow,
272+
nodeId,
273+
})
274+
},
268275
},
269276
}
270277
</script>

0 commit comments

Comments
 (0)