mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 09:58:04 +08:00
fix(workflow): derive plugin install state in render
Remove useEffect-based sync of _pluginInstallLocked/_dimmed in workflow nodes to avoid render-update loops.\n\nMove plugin-missing checks to pure utilities and use them in checklist.\nOptimize node installation hooks by enabling only relevant queries and narrowing memo dependencies.
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import type { CommonNodeType, Node } from '../../types'
|
||||
import type { ChecklistItem } from '../use-checklist'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { createEdge, createNode, resetFixtureCounters } from '../../__tests__/fixtures'
|
||||
import { resetReactFlowMockState, rfState } from '../../__tests__/reactflow-mock-state'
|
||||
import { renderWorkflowHook } from '../../__tests__/workflow-test-env'
|
||||
@ -217,7 +218,9 @@ describe('useChecklist', () => {
|
||||
data: {
|
||||
type: BlockEnum.Tool,
|
||||
title: 'My Tool',
|
||||
_pluginInstallLocked: true,
|
||||
provider_type: CollectionType.builtIn,
|
||||
provider_id: 'missing-provider',
|
||||
plugin_unique_identifier: 'plugin/tool@0.0.1',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ import { useStrategyProviders } from '@/service/use-strategy'
|
||||
import {
|
||||
useAllBuiltInTools,
|
||||
useAllCustomTools,
|
||||
useAllMCPTools,
|
||||
useAllWorkflowTools,
|
||||
} from '@/service/use-tools'
|
||||
import { useAllTriggerPlugins } from '@/service/use-triggers'
|
||||
@ -59,6 +60,7 @@ import {
|
||||
getValidTreeNodes,
|
||||
} from '../utils'
|
||||
import { extractPluginId } from '../utils/plugin'
|
||||
import { isNodePluginMissing } from '../utils/plugin-install-check'
|
||||
import { getTriggerCheckParams } from '../utils/trigger'
|
||||
import useNodesAvailableVarList, { useGetNodesAvailableVarList } from './use-nodes-available-var-list'
|
||||
|
||||
@ -82,13 +84,6 @@ const START_NODE_TYPES: BlockEnum[] = [
|
||||
BlockEnum.TriggerPlugin,
|
||||
]
|
||||
|
||||
// Node types that depend on plugins
|
||||
const PLUGIN_DEPENDENT_TYPES: BlockEnum[] = [
|
||||
BlockEnum.Tool,
|
||||
BlockEnum.DataSource,
|
||||
BlockEnum.TriggerPlugin,
|
||||
]
|
||||
|
||||
export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
@ -96,6 +91,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
const { data: buildInTools } = useAllBuiltInTools()
|
||||
const { data: customTools } = useAllCustomTools()
|
||||
const { data: workflowTools } = useAllWorkflowTools()
|
||||
const { data: mcpTools } = useAllMCPTools()
|
||||
const dataSourceList = useStore(s => s.dataSourceList)
|
||||
const { data: strategyProviders } = useStrategyProviders()
|
||||
const { data: triggerPlugins } = useAllTriggerPlugins()
|
||||
@ -174,7 +170,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
if (node.type === CUSTOM_NODE) {
|
||||
const checkData = getCheckData(node.data)
|
||||
const validator = nodesExtraData?.[node.data.type as BlockEnum]?.checkValid
|
||||
const isPluginMissing = PLUGIN_DEPENDENT_TYPES.includes(node.data.type as BlockEnum) && node.data._pluginInstallLocked
|
||||
const isPluginMissing = isNodePluginMissing(node.data, { builtInTools: buildInTools, customTools, workflowTools, mcpTools, triggerPlugins, dataSourceList })
|
||||
|
||||
const errorMessages: string[] = []
|
||||
|
||||
@ -264,7 +260,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
|
||||
|
||||
workflowStore.setState({ checklistItems: list })
|
||||
return list
|
||||
}, [nodes, edges, shouldCheckStartNode, nodesExtraData, buildInTools, customTools, workflowTools, language, dataSourceList, triggerPlugins, getToolIcon, strategyProviders, getCheckData, t, map, modelProviders, workflowStore])
|
||||
}, [nodes, edges, shouldCheckStartNode, nodesExtraData, buildInTools, customTools, workflowTools, mcpTools, language, dataSourceList, triggerPlugins, getToolIcon, strategyProviders, getCheckData, t, map, modelProviders, workflowStore])
|
||||
|
||||
return needWarningNodes
|
||||
}
|
||||
|
||||
@ -16,11 +16,15 @@ import {
|
||||
useAllTriggerPlugins,
|
||||
useInvalidateAllTriggerPlugins,
|
||||
} from '@/service/use-triggers'
|
||||
import { canFindTool } from '@/utils'
|
||||
import { useStore } from '../store'
|
||||
import { BlockEnum } from '../types'
|
||||
import {
|
||||
matchDataSource,
|
||||
matchToolInCollection,
|
||||
matchTriggerProvider,
|
||||
} from '../utils/plugin-install-check'
|
||||
|
||||
type InstallationState = {
|
||||
export type InstallationState = {
|
||||
isChecking: boolean
|
||||
isMissing: boolean
|
||||
uniqueIdentifier?: string
|
||||
@ -29,14 +33,31 @@ type InstallationState = {
|
||||
shouldDim: boolean
|
||||
}
|
||||
|
||||
const useToolInstallation = (data: ToolNodeType): InstallationState => {
|
||||
const builtInQuery = useAllBuiltInTools()
|
||||
const customQuery = useAllCustomTools()
|
||||
const workflowQuery = useAllWorkflowTools()
|
||||
const mcpQuery = useAllMCPTools()
|
||||
const invalidateTools = useInvalidToolsByType(data.provider_type)
|
||||
const NOOP_INSTALLATION: InstallationState = {
|
||||
isChecking: false,
|
||||
isMissing: false,
|
||||
uniqueIdentifier: undefined,
|
||||
canInstall: false,
|
||||
onInstallSuccess: () => undefined,
|
||||
shouldDim: false,
|
||||
}
|
||||
|
||||
const useToolInstallation = (data: ToolNodeType, enabled: boolean): InstallationState => {
|
||||
const isBuiltIn = enabled && data.provider_type === CollectionType.builtIn
|
||||
const isCustom = enabled && data.provider_type === CollectionType.custom
|
||||
const isWorkflow = enabled && data.provider_type === CollectionType.workflow
|
||||
const isMcp = enabled && data.provider_type === CollectionType.mcp
|
||||
|
||||
const builtInQuery = useAllBuiltInTools(isBuiltIn)
|
||||
const customQuery = useAllCustomTools(isCustom)
|
||||
const workflowQuery = useAllWorkflowTools(isWorkflow)
|
||||
const mcpQuery = useAllMCPTools(isMcp)
|
||||
const invalidateTools = useInvalidToolsByType(enabled ? data.provider_type : undefined)
|
||||
|
||||
const collectionInfo = useMemo(() => {
|
||||
if (!enabled)
|
||||
return undefined
|
||||
|
||||
switch (data.provider_type) {
|
||||
case CollectionType.builtIn:
|
||||
return {
|
||||
@ -62,6 +83,7 @@ const useToolInstallation = (data: ToolNodeType): InstallationState => {
|
||||
return undefined
|
||||
}
|
||||
}, [
|
||||
enabled,
|
||||
builtInQuery.data,
|
||||
builtInQuery.isLoading,
|
||||
customQuery.data,
|
||||
@ -77,20 +99,13 @@ const useToolInstallation = (data: ToolNodeType): InstallationState => {
|
||||
const isLoading = collectionInfo?.isLoading ?? false
|
||||
const isResolved = !!collectionInfo && !isLoading
|
||||
|
||||
const { plugin_id, provider_id, provider_name } = data
|
||||
const matchedCollection = useMemo(() => {
|
||||
if (!collection || !collection.length)
|
||||
return undefined
|
||||
|
||||
return collection.find((toolWithProvider) => {
|
||||
if (data.plugin_id && toolWithProvider.plugin_id === data.plugin_id)
|
||||
return true
|
||||
if (canFindTool(toolWithProvider.id, data.provider_id))
|
||||
return true
|
||||
if (toolWithProvider.name === data.provider_name)
|
||||
return true
|
||||
return false
|
||||
})
|
||||
}, [collection, data.plugin_id, data.provider_id, data.provider_name])
|
||||
return matchToolInCollection(collection, { plugin_id, provider_id, provider_name })
|
||||
}, [collection, plugin_id, provider_id, provider_name])
|
||||
|
||||
const uniqueIdentifier = data.plugin_unique_identifier || data.plugin_id || data.provider_id
|
||||
const canInstall = Boolean(data.plugin_unique_identifier)
|
||||
@ -112,28 +127,20 @@ const useToolInstallation = (data: ToolNodeType): InstallationState => {
|
||||
}
|
||||
}
|
||||
|
||||
const useTriggerInstallation = (data: PluginTriggerNodeType): InstallationState => {
|
||||
const triggerPluginsQuery = useAllTriggerPlugins()
|
||||
const useTriggerInstallation = (data: PluginTriggerNodeType, enabled: boolean): InstallationState => {
|
||||
const triggerPluginsQuery = useAllTriggerPlugins(enabled)
|
||||
const invalidateTriggers = useInvalidateAllTriggerPlugins()
|
||||
|
||||
const triggerProviders = triggerPluginsQuery.data
|
||||
const isLoading = triggerPluginsQuery.isLoading
|
||||
|
||||
const { plugin_id, provider_id, provider_name } = data
|
||||
const matchedProvider = useMemo(() => {
|
||||
if (!triggerProviders || !triggerProviders.length)
|
||||
return undefined
|
||||
|
||||
return triggerProviders.find(provider =>
|
||||
provider.name === data.provider_name
|
||||
|| provider.id === data.provider_id
|
||||
|| (data.plugin_id && provider.plugin_id === data.plugin_id),
|
||||
)
|
||||
}, [
|
||||
data.plugin_id,
|
||||
data.provider_id,
|
||||
data.provider_name,
|
||||
triggerProviders,
|
||||
])
|
||||
return matchTriggerProvider(triggerProviders, { plugin_id, provider_id, provider_name })
|
||||
}, [plugin_id, provider_id, provider_name, triggerProviders])
|
||||
|
||||
const uniqueIdentifier = data.plugin_unique_identifier || data.plugin_id || data.provider_id
|
||||
const canInstall = Boolean(data.plugin_unique_identifier)
|
||||
@ -154,24 +161,17 @@ const useTriggerInstallation = (data: PluginTriggerNodeType): InstallationState
|
||||
}
|
||||
}
|
||||
|
||||
const useDataSourceInstallation = (data: DataSourceNodeType): InstallationState => {
|
||||
const useDataSourceInstallation = (data: DataSourceNodeType, _enabled: boolean): InstallationState => {
|
||||
const dataSourceList = useStore(s => s.dataSourceList)
|
||||
const invalidateDataSourceList = useInvalidDataSourceList()
|
||||
|
||||
const { plugin_unique_identifier, plugin_id, provider_name } = data
|
||||
const matchedPlugin = useMemo(() => {
|
||||
if (!dataSourceList || !dataSourceList.length)
|
||||
return undefined
|
||||
|
||||
return dataSourceList.find((item) => {
|
||||
if (data.plugin_unique_identifier && item.plugin_unique_identifier === data.plugin_unique_identifier)
|
||||
return true
|
||||
if (data.plugin_id && item.plugin_id === data.plugin_id)
|
||||
return true
|
||||
if (data.provider_name && item.provider === data.provider_name)
|
||||
return true
|
||||
return false
|
||||
})
|
||||
}, [data.plugin_id, data.plugin_unique_identifier, data.provider_name, dataSourceList])
|
||||
return matchDataSource(dataSourceList, { plugin_unique_identifier, plugin_id, provider_name })
|
||||
}, [dataSourceList, plugin_id, plugin_unique_identifier, provider_name])
|
||||
|
||||
const uniqueIdentifier = data.plugin_unique_identifier || data.plugin_id
|
||||
const canInstall = Boolean(data.plugin_unique_identifier)
|
||||
@ -195,25 +195,20 @@ const useDataSourceInstallation = (data: DataSourceNodeType): InstallationState
|
||||
}
|
||||
|
||||
export const useNodePluginInstallation = (data: CommonNodeType): InstallationState => {
|
||||
const toolInstallation = useToolInstallation(data as ToolNodeType)
|
||||
const triggerInstallation = useTriggerInstallation(data as PluginTriggerNodeType)
|
||||
const dataSourceInstallation = useDataSourceInstallation(data as DataSourceNodeType)
|
||||
const isTool = data.type === BlockEnum.Tool
|
||||
const isTrigger = data.type === BlockEnum.TriggerPlugin
|
||||
const isDataSource = data.type === BlockEnum.DataSource
|
||||
|
||||
switch (data.type as BlockEnum) {
|
||||
case BlockEnum.Tool:
|
||||
return toolInstallation
|
||||
case BlockEnum.TriggerPlugin:
|
||||
return triggerInstallation
|
||||
case BlockEnum.DataSource:
|
||||
return dataSourceInstallation
|
||||
default:
|
||||
return {
|
||||
isChecking: false,
|
||||
isMissing: false,
|
||||
uniqueIdentifier: undefined,
|
||||
canInstall: false,
|
||||
onInstallSuccess: () => undefined,
|
||||
shouldDim: false,
|
||||
}
|
||||
}
|
||||
const toolInstallation = useToolInstallation(data as ToolNodeType, isTool)
|
||||
const triggerInstallation = useTriggerInstallation(data as PluginTriggerNodeType, isTrigger)
|
||||
const dataSourceInstallation = useDataSourceInstallation(data as DataSourceNodeType, isDataSource)
|
||||
|
||||
if (isTool)
|
||||
return toolInstallation
|
||||
if (isTrigger)
|
||||
return triggerInstallation
|
||||
if (isDataSource)
|
||||
return dataSourceInstallation
|
||||
|
||||
return NOOP_INSTALLATION
|
||||
}
|
||||
|
||||
@ -420,8 +420,6 @@ export const useNodesInteractions = () => {
|
||||
return
|
||||
if (node.data.type === BlockEnum.DataSourceEmpty)
|
||||
return
|
||||
if (node.data._pluginInstallLocked)
|
||||
return
|
||||
handleNodeSelect(node.id)
|
||||
},
|
||||
[handleNodeSelect],
|
||||
|
||||
Reference in New Issue
Block a user