Skip to content

Commit 92fe353

Browse files
authored
fix(subflow): fixed subflow execution regardless of path decision (#707)
* fix typo in docs file * fix(subflows): fixed subflows executing irrespective of active path * added routing strategy * reorganized executor * brought folder renaming inline * cleanup
1 parent 4c6c727 commit 92fe353

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2203
-394
lines changed

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-context-menu/folder-context-menu.tsx

Lines changed: 6 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,29 @@ interface FolderContextMenuProps {
2424
folderId: string
2525
folderName: string
2626
onCreateWorkflow: (folderId: string) => void
27-
onRename?: (folderId: string, newName: string) => void
2827
onDelete?: (folderId: string) => void
28+
onStartEdit?: () => void
2929
level: number
3030
}
3131

3232
export function FolderContextMenu({
3333
folderId,
3434
folderName,
3535
onCreateWorkflow,
36-
onRename,
3736
onDelete,
37+
onStartEdit,
3838
level,
3939
}: FolderContextMenuProps) {
4040
const [showSubfolderDialog, setShowSubfolderDialog] = useState(false)
41-
const [showRenameDialog, setShowRenameDialog] = useState(false)
4241
const [subfolderName, setSubfolderName] = useState('')
43-
const [renameName, setRenameName] = useState(folderName)
4442
const [isCreating, setIsCreating] = useState(false)
45-
const [isRenaming, setIsRenaming] = useState(false)
4643
const params = useParams()
4744
const workspaceId = params.workspaceId as string
4845

4946
// Get user permissions for the workspace
5047
const userPermissions = useUserPermissionsContext()
5148

52-
const { createFolder, updateFolder, deleteFolder } = useFolderStore()
49+
const { createFolder, deleteFolder } = useFolderStore()
5350

5451
const handleCreateWorkflow = () => {
5552
onCreateWorkflow(folderId)
@@ -60,8 +57,9 @@ export function FolderContextMenu({
6057
}
6158

6259
const handleRename = () => {
63-
setRenameName(folderName)
64-
setShowRenameDialog(true)
60+
if (onStartEdit) {
61+
onStartEdit()
62+
}
6563
}
6664

6765
const handleDelete = async () => {
@@ -98,31 +96,9 @@ export function FolderContextMenu({
9896
}
9997
}
10098

101-
const handleRenameSubmit = async (e: React.FormEvent) => {
102-
e.preventDefault()
103-
if (!renameName.trim()) return
104-
105-
setIsRenaming(true)
106-
try {
107-
if (onRename) {
108-
onRename(folderId, renameName.trim())
109-
} else {
110-
// Default rename behavior
111-
await updateFolder(folderId, { name: renameName.trim() })
112-
}
113-
setShowRenameDialog(false)
114-
} catch (error) {
115-
console.error('Failed to rename folder:', error)
116-
} finally {
117-
setIsRenaming(false)
118-
}
119-
}
120-
12199
const handleCancel = () => {
122100
setSubfolderName('')
123101
setShowSubfolderDialog(false)
124-
setRenameName(folderName)
125-
setShowRenameDialog(false)
126102
}
127103

128104
return (
@@ -230,37 +206,6 @@ export function FolderContextMenu({
230206
</form>
231207
</DialogContent>
232208
</Dialog>
233-
234-
{/* Rename dialog */}
235-
<Dialog open={showRenameDialog} onOpenChange={setShowRenameDialog}>
236-
<DialogContent className='sm:max-w-[425px]' onClick={(e) => e.stopPropagation()}>
237-
<DialogHeader>
238-
<DialogTitle>Rename Folder</DialogTitle>
239-
</DialogHeader>
240-
<form onSubmit={handleRenameSubmit} className='space-y-4'>
241-
<div className='space-y-2'>
242-
<Label htmlFor='rename-folder'>Folder Name</Label>
243-
<Input
244-
id='rename-folder'
245-
value={renameName}
246-
onChange={(e) => setRenameName(e.target.value)}
247-
placeholder='Enter folder name...'
248-
maxLength={50}
249-
autoFocus
250-
required
251-
/>
252-
</div>
253-
<div className='flex justify-end space-x-2'>
254-
<Button type='button' variant='outline' onClick={handleCancel}>
255-
Cancel
256-
</Button>
257-
<Button type='submit' disabled={!renameName.trim() || isRenaming}>
258-
{isRenaming ? 'Renaming...' : 'Rename'}
259-
</Button>
260-
</div>
261-
</form>
262-
</DialogContent>
263-
</Dialog>
264209
</>
265210
)
266211
}

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/components/folder-item.tsx

Lines changed: 99 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
AlertDialogHeader,
1515
AlertDialogTitle,
1616
} from '@/components/ui/alert-dialog'
17+
import { Input } from '@/components/ui/input'
1718
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
1819
import { createLogger } from '@/lib/logs/console-logger'
1920
import { type FolderTreeNode, useFolderStore } from '@/stores/folders/store'
@@ -48,14 +49,33 @@ export function FolderItem({
4849
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
4950
const [isDeleting, setIsDeleting] = useState(false)
5051
const [isDragging, setIsDragging] = useState(false)
52+
const [isEditing, setIsEditing] = useState(false)
53+
const [editValue, setEditValue] = useState(folder.name)
54+
const [isRenaming, setIsRenaming] = useState(false)
5155
const dragStartedRef = useRef(false)
56+
const inputRef = useRef<HTMLInputElement>(null)
5257
const params = useParams()
5358
const workspaceId = params.workspaceId as string
5459
const isExpanded = expandedFolders.has(folder.id)
5560
const updateTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
5661
const pendingStateRef = useRef<boolean | null>(null)
5762

63+
// Update editValue when folder name changes
64+
useEffect(() => {
65+
setEditValue(folder.name)
66+
}, [folder.name])
67+
68+
// Focus input when entering edit mode
69+
useEffect(() => {
70+
if (isEditing && inputRef.current) {
71+
inputRef.current.focus()
72+
inputRef.current.select()
73+
}
74+
}, [isEditing])
75+
5876
const handleToggleExpanded = useCallback(() => {
77+
if (isEditing) return // Don't toggle when editing
78+
5979
const newExpandedState = !isExpanded
6080
toggleExpanded(folder.id)
6181
pendingStateRef.current = newExpandedState
@@ -73,9 +93,11 @@ export function FolderItem({
7393
})
7494
}
7595
}, 300)
76-
}, [folder.id, isExpanded, toggleExpanded, updateFolderAPI])
96+
}, [folder.id, isExpanded, toggleExpanded, updateFolderAPI, isEditing])
7797

7898
const handleDragStart = (e: React.DragEvent) => {
99+
if (isEditing) return
100+
79101
dragStartedRef.current = true
80102
setIsDragging(true)
81103

@@ -101,7 +123,7 @@ export function FolderItem({
101123
}
102124

103125
const handleClick = (e: React.MouseEvent) => {
104-
if (dragStartedRef.current) {
126+
if (dragStartedRef.current || isEditing) {
105127
e.preventDefault()
106128
return
107129
}
@@ -116,15 +138,57 @@ export function FolderItem({
116138
}
117139
}, [])
118140

119-
const handleRename = async (folderId: string, newName: string) => {
141+
const handleStartEdit = () => {
142+
setIsEditing(true)
143+
setEditValue(folder.name)
144+
}
145+
146+
const handleSaveEdit = async () => {
147+
if (!editValue.trim() || editValue.trim() === folder.name) {
148+
setIsEditing(false)
149+
setEditValue(folder.name)
150+
return
151+
}
152+
153+
setIsRenaming(true)
120154
try {
121-
await updateFolderAPI(folderId, { name: newName })
155+
await updateFolderAPI(folder.id, { name: editValue.trim() })
156+
logger.info(`Successfully renamed folder from "${folder.name}" to "${editValue.trim()}"`)
157+
setIsEditing(false)
122158
} catch (error) {
123-
logger.error('Failed to rename folder:', { error })
159+
logger.error('Failed to rename folder:', {
160+
error,
161+
folderId: folder.id,
162+
oldName: folder.name,
163+
newName: editValue.trim(),
164+
})
165+
// Reset to original name on error
166+
setEditValue(folder.name)
167+
} finally {
168+
setIsRenaming(false)
169+
}
170+
}
171+
172+
const handleCancelEdit = () => {
173+
setIsEditing(false)
174+
setEditValue(folder.name)
175+
}
176+
177+
const handleKeyDown = (e: React.KeyboardEvent) => {
178+
if (e.key === 'Enter') {
179+
e.preventDefault()
180+
handleSaveEdit()
181+
} else if (e.key === 'Escape') {
182+
e.preventDefault()
183+
handleCancelEdit()
124184
}
125185
}
126186

127-
const handleDelete = async (folderId: string) => {
187+
const handleInputBlur = () => {
188+
handleSaveEdit()
189+
}
190+
191+
const handleDelete = async () => {
128192
setShowDeleteDialog(true)
129193
}
130194

@@ -154,7 +218,7 @@ export function FolderItem({
154218
onDragLeave={onDragLeave}
155219
onDrop={onDrop}
156220
onClick={handleClick}
157-
draggable={true}
221+
draggable={!isEditing}
158222
onDragStart={handleDragStart}
159223
onDragEnd={handleDragEnd}
160224
>
@@ -222,7 +286,7 @@ export function FolderItem({
222286
maxWidth: isFirstItem ? `${164 - level * 20}px` : `${206 - level * 20}px`,
223287
}}
224288
onClick={handleClick}
225-
draggable={true}
289+
draggable={!isEditing}
226290
onDragStart={handleDragStart}
227291
onDragEnd={handleDragEnd}
228292
>
@@ -234,18 +298,34 @@ export function FolderItem({
234298
)}
235299
</div>
236300

237-
<span className='flex-1 select-none truncate text-muted-foreground'>{folder.name}</span>
238-
239-
<div className='flex items-center justify-center' onClick={(e) => e.stopPropagation()}>
240-
<FolderContextMenu
241-
folderId={folder.id}
242-
folderName={folder.name}
243-
onCreateWorkflow={onCreateWorkflow}
244-
onRename={handleRename}
245-
onDelete={handleDelete}
246-
level={level}
301+
{isEditing ? (
302+
<Input
303+
ref={inputRef}
304+
value={editValue}
305+
onChange={(e) => setEditValue(e.target.value)}
306+
onKeyDown={handleKeyDown}
307+
onBlur={handleInputBlur}
308+
className='h-6 flex-1 border-0 bg-transparent p-0 text-muted-foreground text-sm outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
309+
maxLength={50}
310+
disabled={isRenaming}
311+
onClick={(e) => e.stopPropagation()} // Prevent folder toggle when clicking input
247312
/>
248-
</div>
313+
) : (
314+
<span className='flex-1 select-none truncate text-muted-foreground'>{folder.name}</span>
315+
)}
316+
317+
{!isEditing && (
318+
<div className='flex items-center justify-center' onClick={(e) => e.stopPropagation()}>
319+
<FolderContextMenu
320+
folderId={folder.id}
321+
folderName={folder.name}
322+
onCreateWorkflow={onCreateWorkflow}
323+
onDelete={handleDelete}
324+
onStartEdit={handleStartEdit}
325+
level={level}
326+
/>
327+
</div>
328+
)}
249329
</div>
250330
</div>
251331

apps/sim/blocks/blocks/google_calendar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const GoogleCalendarBlock: BlockConfig<GoogleCalendarResponse> = {
2121
description: 'Manage Google Calendar events',
2222
longDescription:
2323
"Integrate Google Calendar functionality to create, read, update, and list calendar events within your workflow. Automate scheduling, check availability, and manage events using OAuth authentication. Email invitations are sent asynchronously and delivery depends on recipients' Google Calendar settings.",
24-
docsLink: 'https://docs.simstudio.ai/tools/google-calendar',
24+
docsLink: 'https://docs.simstudio.ai/tools/google_calendar',
2525
category: 'tools',
2626
bgColor: '#E0E0E0',
2727
icon: GoogleCalendarIcon,

apps/sim/executor/consts.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Enum defining all supported block types in the executor.
3+
* This centralizes block type definitions and eliminates magic strings.
4+
*/
5+
export enum BlockType {
6+
PARALLEL = 'parallel',
7+
LOOP = 'loop',
8+
ROUTER = 'router',
9+
CONDITION = 'condition',
10+
FUNCTION = 'function',
11+
AGENT = 'agent',
12+
API = 'api',
13+
EVALUATOR = 'evaluator',
14+
RESPONSE = 'response',
15+
WORKFLOW = 'workflow',
16+
STARTER = 'starter',
17+
}
18+
19+
/**
20+
* Array of all block types for iteration and validation
21+
*/
22+
export const ALL_BLOCK_TYPES = Object.values(BlockType) as string[]
23+
24+
/**
25+
* Type guard to check if a string is a valid block type
26+
*/
27+
export function isValidBlockType(type: string): type is BlockType {
28+
return ALL_BLOCK_TYPES.includes(type)
29+
}

0 commit comments

Comments
 (0)