Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/sim/app/api/workspaces/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ async function createWorkspace(userId: string, name: string) {
updatedAt: now,
})

// Create "Workflow 1" for the workspace with start block
// Create initial workflow for the workspace with start block
const starterId = crypto.randomUUID()
const initialState = {
blocks: {
Expand Down Expand Up @@ -170,7 +170,7 @@ async function createWorkspace(userId: string, name: string) {
userId,
workspaceId,
folderId: null,
name: 'Workflow 1',
name: 'default-agent',
description: 'Your first workflow - start building here!',
state: initialState,
color: '#3972F6',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ interface ScheduleConfigProps {

export function ScheduleConfig({
blockId,
subBlockId,
subBlockId: _subBlockId,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are unused -- but I wasn't sure why they were ever passed in and didn't want to remove them rn. So using underscore to indicate unused and not get a linter unused warning.

isConnecting,
isPreview = false,
previewValue,
previewValue: _previewValue,
disabled = false,
}: ScheduleConfigProps) {
const [error, setError] = useState<string | null>(null)
Expand All @@ -56,13 +56,7 @@ export function ScheduleConfig({

// Get the startWorkflow value to determine if scheduling is enabled
// and expose the setter so we can update it
const [startWorkflow, setStartWorkflow] = useSubBlockValue(blockId, 'startWorkflow')
const isScheduleEnabled = startWorkflow === 'schedule'

const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)

// Use preview value when in preview mode, otherwise use store value
const value = isPreview ? previewValue : storeValue
const [_startWorkflow, setStartWorkflow] = useSubBlockValue(blockId, 'startWorkflow')

// Function to check if schedule exists in the database
const checkSchedule = async () => {
Expand Down Expand Up @@ -110,10 +104,17 @@ export function ScheduleConfig({

// Check for schedule on mount and when relevant dependencies change
useEffect(() => {
// Always check for schedules regardless of the UI setting
// This ensures we detect schedules even when the UI is set to manual
checkSchedule()
}, [workflowId, scheduleType, isModalOpen, refreshCounter])
// Only check for schedules when workflowId changes or modal opens
// Avoid checking on every scheduleType change to prevent excessive API calls
if (workflowId && (isModalOpen || refreshCounter > 0)) {
checkSchedule()
}

// Cleanup function to reset loading state
return () => {
setIsLoading(false)
}
}, [workflowId, isModalOpen, refreshCounter])

// Format the schedule information for display
const getScheduleInfo = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react'
import { BookOpen, Code, Info, RectangleHorizontal, RectangleVertical } from 'lucide-react'
import { useParams } from 'next/navigation'
import { Handle, type NodeProps, Position, useUpdateNodeInternals } from 'reactflow'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
Expand Down Expand Up @@ -83,6 +84,11 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
const isActiveBlock = useExecutionStore((state) => state.activeBlockIds.has(id))
const isActive = dataIsActive || isActiveBlock

// Get the current workflow ID from URL params instead of global state
// This prevents race conditions when switching workflows rapidly
const params = useParams()
const currentWorkflowId = params.workflowId as string

const reactivateSchedule = async (scheduleId: string) => {
try {
const response = await fetch(`/api/schedules/${scheduleId}`, {
Expand All @@ -94,7 +100,10 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
})

if (response.ok) {
fetchScheduleInfo()
// Use the current workflow ID from params instead of global state
if (currentWorkflowId) {
fetchScheduleInfo(currentWorkflowId)
}
} else {
console.error('Failed to reactivate schedule')
}
Expand All @@ -103,11 +112,11 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
}
}

const fetchScheduleInfo = async () => {
const fetchScheduleInfo = async (workflowId: string) => {
if (!workflowId) return

try {
setIsLoadingScheduleInfo(true)
const workflowId = useWorkflowRegistry.getState().activeWorkflowId
if (!workflowId) return

const response = await fetch(`/api/schedules?workflowId=${workflowId}&mode=schedule`, {
cache: 'no-store',
Expand Down Expand Up @@ -176,12 +185,18 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
}

useEffect(() => {
if (type === 'starter') {
fetchScheduleInfo()
if (type === 'starter' && currentWorkflowId) {
fetchScheduleInfo(currentWorkflowId)
} else {
setScheduleInfo(null)
setIsLoadingScheduleInfo(false) // Reset loading state when not a starter block
}

// Cleanup function to reset loading state when component unmounts or workflow changes
return () => {
setIsLoadingScheduleInfo(false)
}
}, [type])
}, [type, currentWorkflowId])

// Get webhook information for the tooltip
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ import { useFolderStore } from '@/stores/folders/store'
interface CreateMenuProps {
onCreateWorkflow: (folderId?: string) => void
isCollapsed?: boolean
isCreatingWorkflow?: boolean
}

export function CreateMenu({ onCreateWorkflow, isCollapsed }: CreateMenuProps) {
export function CreateMenu({
onCreateWorkflow,
isCollapsed,
isCreatingWorkflow = false,
}: CreateMenuProps) {
const [showFolderDialog, setShowFolderDialog] = useState(false)
const [folderName, setFolderName] = useState('')
const [isCreating, setIsCreating] = useState(false)
Expand Down Expand Up @@ -73,6 +78,7 @@ export function CreateMenu({ onCreateWorkflow, isCollapsed }: CreateMenuProps) {
onClick={handleCreateWorkflow}
onMouseEnter={() => setIsHoverOpen(true)}
onMouseLeave={() => setIsHoverOpen(false)}
disabled={isCreatingWorkflow}
>
<Plus
className={cn(
Expand Down Expand Up @@ -101,11 +107,17 @@ export function CreateMenu({ onCreateWorkflow, isCollapsed }: CreateMenuProps) {
onCloseAutoFocus={(e) => e.preventDefault()}
>
<button
className='flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground'
className={cn(
'flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors',
isCreatingWorkflow
? 'cursor-not-allowed opacity-50'
: 'hover:bg-accent hover:text-accent-foreground'
)}
onClick={handleCreateWorkflow}
disabled={isCreatingWorkflow}
>
<File className='h-4 w-4' />
New Workflow
{isCreatingWorkflow ? 'Creating...' : 'New Workflow'}
</button>
<button
className='flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export function Sidebar() {
const { isPending: sessionLoading } = useSession()
const userPermissions = useUserPermissionsContext()
const isLoading = workflowsLoading || sessionLoading

// Add state to prevent multiple simultaneous workflow creations
const [isCreatingWorkflow, setIsCreatingWorkflow] = useState(false)
const router = useRouter()
const params = useParams()
const workspaceId = params.workspaceId as string
Expand Down Expand Up @@ -108,14 +111,23 @@ export function Sidebar() {

// Create workflow handler
const handleCreateWorkflow = async (folderId?: string) => {
// Prevent multiple simultaneous workflow creations
if (isCreatingWorkflow) {
logger.info('Workflow creation already in progress, ignoring request')
return
}

try {
setIsCreatingWorkflow(true)
const id = await createWorkflow({
workspaceId: workspaceId || undefined,
folderId: folderId || undefined,
})
router.push(`/workspace/${workspaceId}/w/${id}`)
} catch (error) {
logger.error('Error creating workflow:', error)
} finally {
setIsCreatingWorkflow(false)
}
}

Expand Down Expand Up @@ -173,7 +185,11 @@ export function Sidebar() {
{isLoading ? <Skeleton className='h-4 w-16' /> : 'Workflows'}
</h2>
{!isCollapsed && !isLoading && (
<CreateMenu onCreateWorkflow={handleCreateWorkflow} isCollapsed={false} />
<CreateMenu
onCreateWorkflow={handleCreateWorkflow}
isCollapsed={false}
isCreatingWorkflow={isCreatingWorkflow}
/>
)}
</div>
<FolderTree
Expand Down
Loading