fix(triggers): fix triggers in subflows (#1883)

This commit is contained in:
Siddharth Ganesan
2025-11-10 20:51:07 -08:00
committed by GitHub
parent cd48cd4de4
commit 991b0e31ad
5 changed files with 120 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ import {
export enum TriggerWarningType {
DUPLICATE_TRIGGER = 'duplicate_trigger',
LEGACY_INCOMPATIBILITY = 'legacy_incompatibility',
TRIGGER_IN_SUBFLOW = 'trigger_in_subflow',
}
interface TriggerWarningDialogProps {
@@ -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'
}
}
@@ -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.'
}
}

View File

@@ -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) => {
@@ -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
@@ -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

View File

@@ -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',

View File

@@ -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'
}
}

View File

@@ -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) {