Skip to content

Commit 991b0e3

Browse files
authored
fix(triggers): fix triggers in subflows (#1883)
1 parent cd48cd4 commit 991b0e3

File tree

5 files changed

+120
-0
lines changed

5 files changed

+120
-0
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/trigger-warning-dialog/trigger-warning-dialog.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
export enum TriggerWarningType {
1212
DUPLICATE_TRIGGER = 'duplicate_trigger',
1313
LEGACY_INCOMPATIBILITY = 'legacy_incompatibility',
14+
TRIGGER_IN_SUBFLOW = 'trigger_in_subflow',
1415
}
1516

1617
interface TriggerWarningDialogProps {
@@ -32,6 +33,8 @@ export function TriggerWarningDialog({
3233
return 'Cannot mix trigger types'
3334
case TriggerWarningType.DUPLICATE_TRIGGER:
3435
return `Only one ${triggerName} trigger allowed`
36+
case TriggerWarningType.TRIGGER_IN_SUBFLOW:
37+
return 'Triggers not allowed in subflows'
3538
}
3639
}
3740

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

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,24 @@ const WorkflowContent = React.memo(() => {
731731
prevDiffReadyRef.current = isDiffReady
732732
}, [isDiffReady, diffAnalysis, fitView])
733733

734+
// Listen for trigger warning events
735+
useEffect(() => {
736+
const handleShowTriggerWarning = (event: CustomEvent) => {
737+
const { type, triggerName } = event.detail
738+
setTriggerWarning({
739+
open: true,
740+
triggerName: triggerName || 'trigger',
741+
type: type === 'trigger_in_subflow' ? TriggerWarningType.TRIGGER_IN_SUBFLOW : type,
742+
})
743+
}
744+
745+
window.addEventListener('show-trigger-warning', handleShowTriggerWarning as EventListener)
746+
747+
return () => {
748+
window.removeEventListener('show-trigger-warning', handleShowTriggerWarning as EventListener)
749+
}
750+
}, [setTriggerWarning])
751+
734752
// Handler for trigger selection from list
735753
const handleTriggerSelect = useCallback(
736754
(triggerId: string, enableTriggerMode?: boolean) => {
@@ -849,6 +867,22 @@ const WorkflowContent = React.memo(() => {
849867
const name = getUniqueBlockName(baseName, blocks)
850868

851869
if (containerInfo) {
870+
// Check if this is a trigger block or has trigger mode enabled
871+
const isTriggerBlock =
872+
blockConfig.category === 'triggers' ||
873+
blockConfig.triggers?.enabled ||
874+
data.enableTriggerMode === true
875+
876+
if (isTriggerBlock) {
877+
const triggerName = TriggerUtils.getDefaultTriggerName(data.type) || 'trigger'
878+
setTriggerWarning({
879+
open: true,
880+
triggerName,
881+
type: TriggerWarningType.TRIGGER_IN_SUBFLOW,
882+
})
883+
return
884+
}
885+
852886
// Calculate position relative to the container's content area
853887
// Account for header (50px), left padding (16px), and top padding (16px)
854888
const headerHeight = 50
@@ -1714,6 +1748,28 @@ const WorkflowContent = React.memo(() => {
17141748
return
17151749
}
17161750

1751+
// Trigger blocks cannot be placed inside loop or parallel subflows
1752+
if (potentialParentId) {
1753+
const block = blocks[node.id]
1754+
if (block && TriggerUtils.isTriggerBlock(block)) {
1755+
const triggerName = TriggerUtils.getDefaultTriggerName(block.type) || 'trigger'
1756+
setTriggerWarning({
1757+
open: true,
1758+
triggerName,
1759+
type: TriggerWarningType.TRIGGER_IN_SUBFLOW,
1760+
})
1761+
logger.warn('Prevented trigger block from being placed inside a container', {
1762+
blockId: node.id,
1763+
blockType: block.type,
1764+
attemptedParentId: potentialParentId,
1765+
})
1766+
// Reset state without updating parent
1767+
setDraggedNodeId(null)
1768+
setPotentialParentId(null)
1769+
return
1770+
}
1771+
}
1772+
17171773
// Update the node's parent relationship
17181774
if (potentialParentId) {
17191775
// Compute relative position BEFORE updating parent to avoid stale state

apps/sim/hooks/use-collaborative-workflow.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,23 @@ export function useCollaborativeWorkflow() {
971971

972972
const newTriggerMode = !currentBlock.triggerMode
973973

974+
// When enabling trigger mode, check if block is inside a subflow
975+
if (newTriggerMode && currentBlock.data?.parentId) {
976+
const parent = workflowStore.blocks[currentBlock.data.parentId]
977+
if (parent && (parent.type === 'loop' || parent.type === 'parallel')) {
978+
// Dispatch custom event to show warning modal
979+
window.dispatchEvent(
980+
new CustomEvent('show-trigger-warning', {
981+
detail: {
982+
type: 'trigger_in_subflow',
983+
triggerName: 'trigger',
984+
},
985+
})
986+
)
987+
return
988+
}
989+
}
990+
974991
executeQueuedOperation(
975992
'update-trigger-mode',
976993
'block',

apps/sim/lib/workflows/triggers.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,4 +564,32 @@ export class TriggerUtils {
564564

565565
return `Multiple ${triggerName} Trigger blocks found. Keep only one.`
566566
}
567+
568+
/**
569+
* Check if a block is inside a loop or parallel subflow
570+
* @param blockId - ID of the block to check
571+
* @param blocks - Record of all blocks in the workflow
572+
* @returns true if the block is inside a loop or parallel, false otherwise
573+
*/
574+
static isBlockInSubflow<T extends { id: string; data?: { parentId?: string } }>(
575+
blockId: string,
576+
blocks: T[] | Record<string, T>
577+
): boolean {
578+
const blockArray = Array.isArray(blocks) ? blocks : Object.values(blocks)
579+
const block = blockArray.find((b) => b.id === blockId)
580+
581+
if (!block || !block.data?.parentId) {
582+
return false
583+
}
584+
585+
// Check if the parent is a loop or parallel block
586+
const parent = blockArray.find((b) => b.id === block.data?.parentId)
587+
if (!parent) {
588+
return false
589+
}
590+
591+
// Type-safe check: parent must have a 'type' property
592+
const parentWithType = parent as T & { type?: string }
593+
return parentWithType.type === 'loop' || parentWithType.type === 'parallel'
594+
}
567595
}

apps/sim/stores/workflows/workflow/store.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,20 @@ export const useWorkflowStore = create<WorkflowStore>()(
10511051

10521052
const newTriggerMode = !block.triggerMode
10531053

1054+
// When switching TO trigger mode, check if block is inside a subflow
1055+
if (newTriggerMode && block.data?.parentId) {
1056+
const parent = get().blocks[block.data.parentId]
1057+
if (parent && (parent.type === 'loop' || parent.type === 'parallel')) {
1058+
logger.warn('Cannot enable trigger mode for block inside loop or parallel subflow', {
1059+
blockId: id,
1060+
blockType: block.type,
1061+
parentId: block.data.parentId,
1062+
parentType: parent.type,
1063+
})
1064+
return
1065+
}
1066+
}
1067+
10541068
// When switching TO trigger mode, remove all incoming connections
10551069
let filteredEdges = [...get().edges]
10561070
if (newTriggerMode) {

0 commit comments

Comments
 (0)