feat: introduce trigger functionality (#27644)
Signed-off-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: Stream <Stream_2@qq.com> Co-authored-by: lyzno1 <92089059+lyzno1@users.noreply.github.com> Co-authored-by: zhsama <torvalds@linux.do> Co-authored-by: Harry <xh001x@hotmail.com> Co-authored-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: yessenia <yessenia.contact@gmail.com> Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: WTW0313 <twwu@dify.ai> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -97,13 +97,14 @@ export function setupScrollToNodeListener(
|
||||
const node = nodes.find(n => n.id === nodeId)
|
||||
if (node) {
|
||||
// Use ReactFlow's fitView API to scroll to the node
|
||||
reactflow.fitView({
|
||||
nodes: [node],
|
||||
padding: 0.2,
|
||||
duration: 800,
|
||||
minZoom: 0.5,
|
||||
maxZoom: 1,
|
||||
})
|
||||
const nodePosition = { x: node.position.x, y: node.position.y }
|
||||
|
||||
// Calculate position to place node in top-left area
|
||||
// Move the center point right and down to show node in top-left
|
||||
const targetX = nodePosition.x + window.innerWidth * 0.25
|
||||
const targetY = nodePosition.y + window.innerHeight * 0.25
|
||||
|
||||
reactflow.setCenter(targetX, targetY, { zoom: 1, duration: 800 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
web/app/components/workflow/utils/trigger.ts
Normal file
52
web/app/components/workflow/utils/trigger.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
|
||||
import type { PluginTriggerNodeType } from '@/app/components/workflow/nodes/trigger-plugin/types'
|
||||
|
||||
export type TriggerCheckParams = {
|
||||
triggerInputsSchema: Array<{
|
||||
variable: string
|
||||
label: string
|
||||
required?: boolean
|
||||
}>
|
||||
isReadyForCheckValid: boolean
|
||||
}
|
||||
|
||||
export const getTriggerCheckParams = (
|
||||
triggerData: PluginTriggerNodeType,
|
||||
triggerProviders: TriggerWithProvider[] | undefined,
|
||||
language: string,
|
||||
): TriggerCheckParams => {
|
||||
if (!triggerProviders) {
|
||||
return {
|
||||
triggerInputsSchema: [],
|
||||
isReadyForCheckValid: false,
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
provider_id,
|
||||
provider_name,
|
||||
event_name,
|
||||
} = triggerData
|
||||
|
||||
const provider = triggerProviders.find(item =>
|
||||
item.name === provider_name
|
||||
|| item.id === provider_id
|
||||
|| (provider_id && item.plugin_id === provider_id),
|
||||
)
|
||||
|
||||
const currentEvent = provider?.events.find(event => event.name === event_name)
|
||||
|
||||
const triggerInputsSchema = (currentEvent?.parameters || []).map((parameter) => {
|
||||
const label = parameter.label?.[language] || parameter.label?.en_US || parameter.name
|
||||
return {
|
||||
variable: parameter.name,
|
||||
label,
|
||||
required: parameter.required,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
triggerInputsSchema,
|
||||
isReadyForCheckValid: true,
|
||||
}
|
||||
}
|
||||
26
web/app/components/workflow/utils/workflow-entry.ts
Normal file
26
web/app/components/workflow/utils/workflow-entry.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { BlockEnum, type Node, isTriggerNode } from '../types'
|
||||
|
||||
/**
|
||||
* Get the workflow entry node
|
||||
* Priority: trigger nodes > start node
|
||||
*/
|
||||
export function getWorkflowEntryNode(nodes: Node[]): Node | undefined {
|
||||
const triggerNode = nodes.find(node => isTriggerNode(node.data.type))
|
||||
if (triggerNode) return triggerNode
|
||||
|
||||
return nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a node type is a workflow entry node
|
||||
*/
|
||||
export function isWorkflowEntryNode(nodeType: BlockEnum): boolean {
|
||||
return nodeType === BlockEnum.Start || isTriggerNode(nodeType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if workflow is in trigger mode
|
||||
*/
|
||||
export function isTriggerWorkflow(nodes: Node[]): boolean {
|
||||
return nodes.some(node => isTriggerNode(node.data.type))
|
||||
}
|
||||
@@ -34,6 +34,9 @@ export const canRunBySingle = (nodeType: BlockEnum, isChildNode: boolean) => {
|
||||
|| nodeType === BlockEnum.VariableAggregator
|
||||
|| nodeType === BlockEnum.Assigner
|
||||
|| nodeType === BlockEnum.DataSource
|
||||
|| nodeType === BlockEnum.TriggerSchedule
|
||||
|| nodeType === BlockEnum.TriggerWebhook
|
||||
|| nodeType === BlockEnum.TriggerPlugin
|
||||
}
|
||||
|
||||
export const isSupportCustomRunForm = (nodeType: BlockEnum) => {
|
||||
@@ -92,18 +95,29 @@ export const getNodesConnectedSourceOrTargetHandleIdsMap = (changes: ConnectedSo
|
||||
return nodesConnectedSourceOrTargetHandleIdsMap
|
||||
}
|
||||
|
||||
export const getValidTreeNodes = (startNode: Node, nodes: Node[], edges: Edge[]) => {
|
||||
if (!startNode) {
|
||||
export const getValidTreeNodes = (nodes: Node[], edges: Edge[]) => {
|
||||
// Find all start nodes (Start and Trigger nodes)
|
||||
const startNodes = nodes.filter(node =>
|
||||
node.data.type === BlockEnum.Start
|
||||
|| node.data.type === BlockEnum.TriggerSchedule
|
||||
|| node.data.type === BlockEnum.TriggerWebhook
|
||||
|| node.data.type === BlockEnum.TriggerPlugin,
|
||||
)
|
||||
|
||||
if (startNodes.length === 0) {
|
||||
return {
|
||||
validNodes: [],
|
||||
maxDepth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const list: Node[] = [startNode]
|
||||
let maxDepth = 1
|
||||
const list: Node[] = []
|
||||
let maxDepth = 0
|
||||
|
||||
const traverse = (root: Node, depth: number) => {
|
||||
// Add the current node to the list
|
||||
list.push(root)
|
||||
|
||||
if (depth > maxDepth)
|
||||
maxDepth = depth
|
||||
|
||||
@@ -111,19 +125,19 @@ export const getValidTreeNodes = (startNode: Node, nodes: Node[], edges: Edge[])
|
||||
|
||||
if (outgoers.length) {
|
||||
outgoers.forEach((outgoer) => {
|
||||
list.push(outgoer)
|
||||
// Only traverse if we haven't processed this node yet (avoid cycles)
|
||||
if (!list.find(n => n.id === outgoer.id)) {
|
||||
if (outgoer.data.type === BlockEnum.Iteration)
|
||||
list.push(...nodes.filter(node => node.parentId === outgoer.id))
|
||||
if (outgoer.data.type === BlockEnum.Loop)
|
||||
list.push(...nodes.filter(node => node.parentId === outgoer.id))
|
||||
|
||||
if (outgoer.data.type === BlockEnum.Iteration)
|
||||
list.push(...nodes.filter(node => node.parentId === outgoer.id))
|
||||
if (outgoer.data.type === BlockEnum.Loop)
|
||||
list.push(...nodes.filter(node => node.parentId === outgoer.id))
|
||||
|
||||
traverse(outgoer, depth + 1)
|
||||
traverse(outgoer, depth + 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
list.push(root)
|
||||
|
||||
// Leaf node - add iteration/loop children if any
|
||||
if (root.data.type === BlockEnum.Iteration)
|
||||
list.push(...nodes.filter(node => node.parentId === root.id))
|
||||
if (root.data.type === BlockEnum.Loop)
|
||||
@@ -131,7 +145,11 @@ export const getValidTreeNodes = (startNode: Node, nodes: Node[], edges: Edge[])
|
||||
}
|
||||
}
|
||||
|
||||
traverse(startNode, maxDepth)
|
||||
// Start traversal from all start nodes
|
||||
startNodes.forEach((startNode) => {
|
||||
if (!list.find(n => n.id === startNode.id))
|
||||
traverse(startNode, 1)
|
||||
})
|
||||
|
||||
return {
|
||||
validNodes: uniqBy(list, 'id'),
|
||||
|
||||
Reference in New Issue
Block a user