Skip to content

Commit 8dad4d4

Browse files
waleedlatif1claude
andcommitted
refactor(workflow): extend block protection utilities for edge protection
Add isEdgeProtected, filterUnprotectedEdges, and hasProtectedBlocks utilities. Refactor workflow.tsx to use these helpers for: - onEdgesChange edge removal filtering - onConnect connection prevention - onNodeDragStart drag prevention - Keyboard edge deletion - Block menu disableEdit calculation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c987b6f commit 8dad4d4

File tree

2 files changed

+69
-67
lines changed

2 files changed

+69
-67
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/block-protection-utils.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ export interface FilterProtectedBlocksResult {
1313
}
1414

1515
/**
16-
* Checks if a block is protected from deletion.
16+
* Checks if a block is protected from editing/deletion.
1717
* A block is protected if it is locked or if its parent container is locked.
1818
*
1919
* @param blockId - The ID of the block to check
2020
* @param blocks - Record of all blocks in the workflow
21-
* @returns True if the block is protected from deletion
21+
* @returns True if the block is protected
2222
*/
2323
export function isBlockProtected(blockId: string, blocks: Record<string, BlockState>): boolean {
2424
const block = blocks[blockId]
@@ -34,6 +34,21 @@ export function isBlockProtected(blockId: string, blocks: Record<string, BlockSt
3434
return false
3535
}
3636

37+
/**
38+
* Checks if an edge is protected from modification.
39+
* An edge is protected if either its source or target block is protected.
40+
*
41+
* @param edge - The edge to check (must have source and target)
42+
* @param blocks - Record of all blocks in the workflow
43+
* @returns True if the edge is protected
44+
*/
45+
export function isEdgeProtected(
46+
edge: { source: string; target: string },
47+
blocks: Record<string, BlockState>
48+
): boolean {
49+
return isBlockProtected(edge.source, blocks) || isBlockProtected(edge.target, blocks)
50+
}
51+
3752
/**
3853
* Filters out protected blocks from a list of block IDs for deletion.
3954
* Protected blocks are those that are locked or inside a locked container.
@@ -55,3 +70,32 @@ export function filterProtectedBlocks(
5570
allProtected: protectedIds.length === blockIds.length && blockIds.length > 0,
5671
}
5772
}
73+
74+
/**
75+
* Filters edges to only include those that are not protected.
76+
*
77+
* @param edges - Array of edges to filter
78+
* @param blocks - Record of all blocks in the workflow
79+
* @returns Array of edges that can be modified (not protected)
80+
*/
81+
export function filterUnprotectedEdges<T extends { source: string; target: string }>(
82+
edges: T[],
83+
blocks: Record<string, BlockState>
84+
): T[] {
85+
return edges.filter((edge) => !isEdgeProtected(edge, blocks))
86+
}
87+
88+
/**
89+
* Checks if any blocks in the selection are protected.
90+
* Useful for determining if edit actions should be disabled.
91+
*
92+
* @param blockIds - Array of block IDs to check
93+
* @param blocks - Record of all blocks in the workflow
94+
* @returns True if any block is protected
95+
*/
96+
export function hasProtectedBlocks(
97+
blockIds: string[],
98+
blocks: Record<string, BlockState>
99+
): boolean {
100+
return blockIds.some((id) => isBlockProtected(id, blocks))
101+
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 23 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ import {
5757
estimateBlockDimensions,
5858
filterProtectedBlocks,
5959
getClampedPositionForNode,
60+
hasProtectedBlocks,
61+
isBlockProtected,
62+
isEdgeProtected,
6063
isInEditableElement,
6164
resolveParentChildSelectionConflicts,
6265
validateTriggerPaste,
@@ -2519,21 +2522,10 @@ const WorkflowContent = React.memo(() => {
25192522
.filter((change: any) => change.type === 'remove')
25202523
.map((change: any) => change.id)
25212524
.filter((edgeId: string) => {
2522-
// Prevent removing edges connected to locked blocks or blocks inside locked containers
2525+
// Prevent removing edges connected to protected blocks
25232526
const edge = edges.find((e) => e.id === edgeId)
25242527
if (!edge) return true
2525-
const sourceBlock = blocks[edge.source]
2526-
const targetBlock = blocks[edge.target]
2527-
const sourceParentLocked =
2528-
sourceBlock?.data?.parentId && blocks[sourceBlock.data.parentId]?.locked
2529-
const targetParentLocked =
2530-
targetBlock?.data?.parentId && blocks[targetBlock.data.parentId]?.locked
2531-
return (
2532-
!sourceBlock?.locked &&
2533-
!targetBlock?.locked &&
2534-
!sourceParentLocked &&
2535-
!targetParentLocked
2536-
)
2528+
return !isEdgeProtected(edge, blocks)
25372529
})
25382530

25392531
if (edgeIdsToRemove.length > 0) {
@@ -2602,19 +2594,8 @@ const WorkflowContent = React.memo(() => {
26022594

26032595
if (!sourceNode || !targetNode) return
26042596

2605-
// Prevent connections to/from locked blocks or blocks inside locked containers
2606-
const sourceBlock = blocks[connection.source]
2607-
const targetBlock = blocks[connection.target]
2608-
const sourceParentLocked =
2609-
sourceBlock?.data?.parentId && blocks[sourceBlock.data.parentId]?.locked
2610-
const targetParentLocked =
2611-
targetBlock?.data?.parentId && blocks[targetBlock.data.parentId]?.locked
2612-
if (
2613-
sourceBlock?.locked ||
2614-
targetBlock?.locked ||
2615-
sourceParentLocked ||
2616-
targetParentLocked
2617-
) {
2597+
// Prevent connections to/from protected blocks
2598+
if (isEdgeProtected(connection, blocks)) {
26182599
addNotification({
26192600
level: 'info',
26202601
message: 'Cannot connect to locked blocks or blocks inside locked containers',
@@ -2875,9 +2856,8 @@ const WorkflowContent = React.memo(() => {
28752856
/** Captures initial parent ID and position when drag starts. */
28762857
const onNodeDragStart = useCallback(
28772858
(_event: React.MouseEvent, node: any) => {
2878-
// Prevent dragging locked blocks
2879-
const block = blocks[node.id]
2880-
if (block?.locked) {
2859+
// Prevent dragging protected blocks
2860+
if (isBlockProtected(node.id, blocks)) {
28812861
return
28822862
}
28832863

@@ -3386,28 +3366,15 @@ const WorkflowContent = React.memo(() => {
33863366
/** Stable delete handler to avoid creating new function references per edge. */
33873367
const handleEdgeDelete = useCallback(
33883368
(edgeId: string) => {
3389-
// Prevent removing edges connected to locked blocks or blocks inside locked containers
3369+
// Prevent removing edges connected to protected blocks
33903370
const edge = edges.find((e) => e.id === edgeId)
3391-
if (edge) {
3392-
const sourceBlock = blocks[edge.source]
3393-
const targetBlock = blocks[edge.target]
3394-
const sourceParentLocked =
3395-
sourceBlock?.data?.parentId && blocks[sourceBlock.data.parentId]?.locked
3396-
const targetParentLocked =
3397-
targetBlock?.data?.parentId && blocks[targetBlock.data.parentId]?.locked
3398-
if (
3399-
sourceBlock?.locked ||
3400-
targetBlock?.locked ||
3401-
sourceParentLocked ||
3402-
targetParentLocked
3403-
) {
3404-
addNotification({
3405-
level: 'info',
3406-
message: 'Cannot remove connections from locked blocks',
3407-
workflowId: activeWorkflowId || undefined,
3408-
})
3409-
return
3410-
}
3371+
if (edge && isEdgeProtected(edge, blocks)) {
3372+
addNotification({
3373+
level: 'info',
3374+
message: 'Cannot remove connections from locked blocks',
3375+
workflowId: activeWorkflowId || undefined,
3376+
})
3377+
return
34113378
}
34123379
removeEdge(edgeId)
34133380
// Remove this edge from selection (find by edge ID value)
@@ -3462,22 +3429,11 @@ const WorkflowContent = React.memo(() => {
34623429

34633430
// Handle edge deletion first (edges take priority if selected)
34643431
if (selectedEdges.size > 0) {
3465-
// Get all selected edge IDs and filter out edges connected to locked blocks or blocks inside locked containers
3432+
// Get all selected edge IDs and filter out edges connected to protected blocks
34663433
const edgeIds = Array.from(selectedEdges.values()).filter((edgeId) => {
34673434
const edge = edges.find((e) => e.id === edgeId)
34683435
if (!edge) return true
3469-
const sourceBlock = blocks[edge.source]
3470-
const targetBlock = blocks[edge.target]
3471-
const sourceParentLocked =
3472-
sourceBlock?.data?.parentId && blocks[sourceBlock.data.parentId]?.locked
3473-
const targetParentLocked =
3474-
targetBlock?.data?.parentId && blocks[targetBlock.data.parentId]?.locked
3475-
return (
3476-
!sourceBlock?.locked &&
3477-
!targetBlock?.locked &&
3478-
!sourceParentLocked &&
3479-
!targetParentLocked
3480-
)
3436+
return !isEdgeProtected(edge, blocks)
34813437
})
34823438
if (edgeIds.length > 0) {
34833439
collaborativeBatchRemoveEdges(edgeIds)
@@ -3657,8 +3613,10 @@ const WorkflowContent = React.memo(() => {
36573613
canRunFromBlock={runFromBlockState.canRun}
36583614
disableEdit={
36593615
!effectivePermissions.canEdit ||
3660-
contextMenuBlocks.some((b) => b.locked) ||
3661-
contextMenuBlocks.some((b) => b.parentId && blocks[b.parentId]?.locked)
3616+
hasProtectedBlocks(
3617+
contextMenuBlocks.map((b) => b.id),
3618+
blocks
3619+
)
36623620
}
36633621
userCanEdit={effectivePermissions.canEdit}
36643622
isExecuting={isExecuting}

0 commit comments

Comments
 (0)