99 RefreshCw ,
1010 Trash2 ,
1111} from "lucide-react" ;
12- import { useMemo , useRef , useState } from "react" ;
12+ import { useCallback , useMemo , useRef , useState } from "react" ;
1313import { toast } from "sonner" ;
1414import {
1515 AlertDialog ,
@@ -38,6 +38,7 @@ import {
3838 edgesAtom ,
3939 isGeneratingAtom ,
4040 nodesAtom ,
41+ pendingIntegrationNodesAtom ,
4142 propertiesPanelActiveTabAtom ,
4243 selectedEdgeAtom ,
4344 selectedNodeAtom ,
@@ -62,6 +63,11 @@ import { WorkflowRuns } from "./workflow-runs";
6263const NON_ALPHANUMERIC_REGEX = / [ ^ a - z A - Z 0 - 9 \s ] / g;
6364const WORD_SPLIT_REGEX = / \s + / ;
6465
66+ // System actions that need integrations (not in plugin registry)
67+ const SYSTEM_ACTION_INTEGRATIONS : Record < string , IntegrationType > = {
68+ "Database Query" : "database" ,
69+ } ;
70+
6571// Multi-selection panel component
6672const MultiSelectionPanel = ( {
6773 selectedNodes,
@@ -154,13 +160,17 @@ export const PanelInner = () => {
154160 const setShowClearDialog = useSetAtom ( showClearDialogAtom ) ;
155161 const setShowDeleteDialog = useSetAtom ( showDeleteDialogAtom ) ;
156162 const clearNodeStatuses = useSetAtom ( clearNodeStatusesAtom ) ;
163+ const setPendingIntegrationNodes = useSetAtom ( pendingIntegrationNodesAtom ) ;
157164 const [ showDeleteNodeAlert , setShowDeleteNodeAlert ] = useState ( false ) ;
158165 const [ showDeleteEdgeAlert , setShowDeleteEdgeAlert ] = useState ( false ) ;
159166 const [ showDeleteRunsAlert , setShowDeleteRunsAlert ] = useState ( false ) ;
160167 const [ showIntegrationsDialog , setShowIntegrationsDialog ] = useState ( false ) ;
161168 const [ isRefreshing , setIsRefreshing ] = useState ( false ) ;
162169 const [ activeTab , setActiveTab ] = useAtom ( propertiesPanelActiveTabAtom ) ;
163170 const refreshRunsRef = useRef < ( ( ) => Promise < void > ) | null > ( null ) ;
171+ const autoSelectAbortControllersRef = useRef < Record < string , AbortController > > (
172+ { }
173+ ) ;
164174 const selectedNode = nodes . find ( ( node ) => node . id === selectedNodeId ) ;
165175 const selectedEdge = edges . find ( ( edge ) => edge . id === selectedEdgeId ) ;
166176
@@ -252,11 +262,99 @@ export const PanelInner = () => {
252262 updateNodeData ( { id : selectedNode . id , data : { description } } ) ;
253263 }
254264 } ;
265+ const autoSelectIntegration = useCallback (
266+ async (
267+ nodeId : string ,
268+ actionType : string ,
269+ currentConfig : Record < string , unknown > ,
270+ abortSignal : AbortSignal
271+ ) => {
272+ // Get integration type - check plugin registry first, then system actions
273+ const action = findActionById ( actionType ) ;
274+ const integrationType : IntegrationType | undefined =
275+ ( action ?. integration as IntegrationType | undefined ) ||
276+ SYSTEM_ACTION_INTEGRATIONS [ actionType ] ;
277+
278+ if ( ! integrationType ) {
279+ // No integration needed, remove from pending
280+ setPendingIntegrationNodes ( ( prev : Set < string > ) => {
281+ const next = new Set ( prev ) ;
282+ next . delete ( nodeId ) ;
283+ return next ;
284+ } ) ;
285+ return ;
286+ }
287+
288+ try {
289+ const all = await api . integration . getAll ( ) ;
290+
291+ // Check if this operation was aborted (actionType changed)
292+ if ( abortSignal . aborted ) {
293+ return ;
294+ }
295+
296+ const filtered = all . filter ( ( i ) => i . type === integrationType ) ;
297+
298+ // Auto-select if only one integration exists
299+ if ( filtered . length === 1 && ! abortSignal . aborted ) {
300+ const newConfig = {
301+ ...currentConfig ,
302+ actionType,
303+ integrationId : filtered [ 0 ] . id ,
304+ } ;
305+ updateNodeData ( { id : nodeId , data : { config : newConfig } } ) ;
306+ }
307+ } catch ( error ) {
308+ console . error ( "Failed to auto-select integration:" , error ) ;
309+ } finally {
310+ // Always remove from pending set when done (unless aborted)
311+ if ( ! abortSignal . aborted ) {
312+ setPendingIntegrationNodes ( ( prev : Set < string > ) => {
313+ const next = new Set ( prev ) ;
314+ next . delete ( nodeId ) ;
315+ return next ;
316+ } ) ;
317+ }
318+ }
319+ } ,
320+ [ updateNodeData , setPendingIntegrationNodes ]
321+ ) ;
255322
256323 const handleUpdateConfig = ( key : string , value : string ) => {
257324 if ( selectedNode ) {
258- const newConfig = { ...selectedNode . data . config , [ key ] : value } ;
325+ let newConfig = { ...selectedNode . data . config , [ key ] : value } ;
326+
327+ // When action type changes, clear the integrationId since it may not be valid for the new action
328+ if ( key === "actionType" && selectedNode . data . config ?. integrationId ) {
329+ newConfig = { ...newConfig , integrationId : undefined } ;
330+ }
331+
259332 updateNodeData ( { id : selectedNode . id , data : { config : newConfig } } ) ;
333+
334+ // When action type changes, auto-select integration if only one exists
335+ if ( key === "actionType" ) {
336+ // Cancel any pending auto-select operation for this node
337+ const existingController =
338+ autoSelectAbortControllersRef . current [ selectedNode . id ] ;
339+ if ( existingController ) {
340+ existingController . abort ( ) ;
341+ }
342+
343+ // Create new AbortController for this operation
344+ const newController = new AbortController ( ) ;
345+ autoSelectAbortControllersRef . current [ selectedNode . id ] = newController ;
346+
347+ // Add to pending set before starting async check
348+ setPendingIntegrationNodes ( ( prev : Set < string > ) =>
349+ new Set ( prev ) . add ( selectedNode . id )
350+ ) ;
351+ autoSelectIntegration (
352+ selectedNode . id ,
353+ value ,
354+ newConfig ,
355+ newController . signal
356+ ) ;
357+ }
260358 }
261359 } ;
262360
0 commit comments