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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
export enum TriggerWarningType {
DUPLICATE_TRIGGER = 'duplicate_trigger',
LEGACY_INCOMPATIBILITY = 'legacy_incompatibility',
TRIGGER_IN_SUBFLOW = 'trigger_in_subflow',
}

interface TriggerWarningDialogProps {
Expand All @@ -32,6 +33,8 @@ export function TriggerWarningDialog({
return 'Cannot mix trigger types'
case TriggerWarningType.DUPLICATE_TRIGGER:
return `Only one ${triggerName} trigger allowed`
case TriggerWarningType.TRIGGER_IN_SUBFLOW:
return 'Triggers not allowed in subflows'
}
}

Expand All @@ -41,6 +44,8 @@ export function TriggerWarningDialog({
return 'Cannot add new trigger blocks when a legacy Start block exists. Available in newer workflows.'
case TriggerWarningType.DUPLICATE_TRIGGER:
return `A workflow can only have one ${triggerName} trigger block. Please remove the existing one before adding a new one.`
case TriggerWarningType.TRIGGER_IN_SUBFLOW:
return 'Triggers cannot be placed inside loop or parallel subflows.'
}
}

Expand Down
56 changes: 56 additions & 0 deletions apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,24 @@ const WorkflowContent = React.memo(() => {
prevDiffReadyRef.current = isDiffReady
}, [isDiffReady, diffAnalysis, fitView])

// Listen for trigger warning events
useEffect(() => {
const handleShowTriggerWarning = (event: CustomEvent) => {
const { type, triggerName } = event.detail
setTriggerWarning({
open: true,
triggerName: triggerName || 'trigger',
type: type === 'trigger_in_subflow' ? TriggerWarningType.TRIGGER_IN_SUBFLOW : type,
})
}

window.addEventListener('show-trigger-warning', handleShowTriggerWarning as EventListener)

return () => {
window.removeEventListener('show-trigger-warning', handleShowTriggerWarning as EventListener)
}
}, [setTriggerWarning])

// Handler for trigger selection from list
const handleTriggerSelect = useCallback(
(triggerId: string, enableTriggerMode?: boolean) => {
Expand Down Expand Up @@ -849,6 +867,22 @@ const WorkflowContent = React.memo(() => {
const name = getUniqueBlockName(baseName, blocks)

if (containerInfo) {
// Check if this is a trigger block or has trigger mode enabled
const isTriggerBlock =
blockConfig.category === 'triggers' ||
blockConfig.triggers?.enabled ||
data.enableTriggerMode === true

if (isTriggerBlock) {
const triggerName = TriggerUtils.getDefaultTriggerName(data.type) || 'trigger'
setTriggerWarning({
open: true,
triggerName,
type: TriggerWarningType.TRIGGER_IN_SUBFLOW,
})
return
}

// Calculate position relative to the container's content area
// Account for header (50px), left padding (16px), and top padding (16px)
const headerHeight = 50
Expand Down Expand Up @@ -1714,6 +1748,28 @@ const WorkflowContent = React.memo(() => {
return
}

// Trigger blocks cannot be placed inside loop or parallel subflows
if (potentialParentId) {
const block = blocks[node.id]
if (block && TriggerUtils.isTriggerBlock(block)) {
const triggerName = TriggerUtils.getDefaultTriggerName(block.type) || 'trigger'
setTriggerWarning({
open: true,
triggerName,
type: TriggerWarningType.TRIGGER_IN_SUBFLOW,
})
logger.warn('Prevented trigger block from being placed inside a container', {
blockId: node.id,
blockType: block.type,
attemptedParentId: potentialParentId,
})
// Reset state without updating parent
setDraggedNodeId(null)
setPotentialParentId(null)
return
}
}

// Update the node's parent relationship
if (potentialParentId) {
// Compute relative position BEFORE updating parent to avoid stale state
Expand Down
17 changes: 17 additions & 0 deletions apps/sim/hooks/use-collaborative-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,23 @@ export function useCollaborativeWorkflow() {

const newTriggerMode = !currentBlock.triggerMode

// When enabling trigger mode, check if block is inside a subflow
if (newTriggerMode && currentBlock.data?.parentId) {
const parent = workflowStore.blocks[currentBlock.data.parentId]
if (parent && (parent.type === 'loop' || parent.type === 'parallel')) {
// Dispatch custom event to show warning modal
window.dispatchEvent(
new CustomEvent('show-trigger-warning', {
detail: {
type: 'trigger_in_subflow',
triggerName: 'trigger',
},
})
)
return
}
}

executeQueuedOperation(
'update-trigger-mode',
'block',
Expand Down
28 changes: 28 additions & 0 deletions apps/sim/lib/workflows/triggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,4 +564,32 @@ export class TriggerUtils {

return `Multiple ${triggerName} Trigger blocks found. Keep only one.`
}

/**
* Check if a block is inside a loop or parallel subflow
* @param blockId - ID of the block to check
* @param blocks - Record of all blocks in the workflow
* @returns true if the block is inside a loop or parallel, false otherwise
*/
static isBlockInSubflow<T extends { id: string; data?: { parentId?: string } }>(
blockId: string,
blocks: T[] | Record<string, T>
): boolean {
const blockArray = Array.isArray(blocks) ? blocks : Object.values(blocks)
const block = blockArray.find((b) => b.id === blockId)

if (!block || !block.data?.parentId) {
return false
}

// Check if the parent is a loop or parallel block
const parent = blockArray.find((b) => b.id === block.data?.parentId)
if (!parent) {
return false
}

// Type-safe check: parent must have a 'type' property
const parentWithType = parent as T & { type?: string }
return parentWithType.type === 'loop' || parentWithType.type === 'parallel'
}
Comment on lines +574 to +594
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Unused utility - isBlockInSubflow is never called. The same logic is duplicated inline in use-collaborative-workflow.ts:977 and store.ts:1057. Consider either using this utility or removing it to reduce code duplication.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/sim/lib/workflows/triggers.ts
Line: 574:594

Comment:
**style:** Unused utility - `isBlockInSubflow` is never called. The same logic is duplicated inline in `use-collaborative-workflow.ts:977` and `store.ts:1057`. Consider either using this utility or removing it to reduce code duplication.

How can I resolve this? If you propose a fix, please make it concise.

}
14 changes: 14 additions & 0 deletions apps/sim/stores/workflows/workflow/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,20 @@ export const useWorkflowStore = create<WorkflowStore>()(

const newTriggerMode = !block.triggerMode

// When switching TO trigger mode, check if block is inside a subflow
if (newTriggerMode && block.data?.parentId) {
const parent = get().blocks[block.data.parentId]
if (parent && (parent.type === 'loop' || parent.type === 'parallel')) {
logger.warn('Cannot enable trigger mode for block inside loop or parallel subflow', {
blockId: id,
blockType: block.type,
parentId: block.data.parentId,
parentType: parent.type,
})
return
}
}

// When switching TO trigger mode, remove all incoming connections
let filteredEdges = [...get().edges]
if (newTriggerMode) {
Expand Down