Skip to content

Commit 409672c

Browse files
committed
improvement(tools): use react query to fetch child workflow schema, avoid refetch and duplicated utils
1 parent 5e44357 commit 409672c

File tree

11 files changed

+153
-279
lines changed

11 files changed

+153
-279
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
extractPathFromOutputId,
3030
parseOutputContentSafely,
3131
} from '@/lib/core/utils/response-format'
32-
import { normalizeInputFormatValue } from '@/lib/workflows/input-format-utils'
32+
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
3333
import { StartBlockPath, TriggerUtils } from '@/lib/workflows/triggers/triggers'
3434
import { START_BLOCK_RESERVED_FIELDS } from '@/lib/workflows/types'
3535
import {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/a2a/a2a.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
import { Skeleton } from '@/components/ui'
2323
import type { AgentAuthentication, AgentCapabilities } from '@/lib/a2a/types'
2424
import { getBaseUrl } from '@/lib/core/utils/urls'
25-
import { normalizeInputFormatValue } from '@/lib/workflows/input-format-utils'
25+
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
2626
import { StartBlockPath, TriggerUtils } from '@/lib/workflows/triggers/triggers'
2727
import {
2828
useA2AAgentByWorkflow,

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from '@/components/emcn'
1515
import { Skeleton } from '@/components/ui'
1616
import { generateToolInputSchema, sanitizeToolName } from '@/lib/mcp/workflow-tool-schema'
17-
import { normalizeInputFormatValue } from '@/lib/workflows/input-format-utils'
17+
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
1818
import { isValidStartBlockType } from '@/lib/workflows/triggers/trigger-utils'
1919
import type { InputFormatField } from '@/lib/workflows/types'
2020
import {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/input-mapping/input-mapping.tsx

Lines changed: 4 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useRef, useState } from 'react'
1+
import { useMemo, useRef, useState } from 'react'
22
import { Badge, Input } from '@/components/emcn'
33
import { Label } from '@/components/ui/label'
44
import { cn } from '@/lib/core/utils/cn'
@@ -7,39 +7,7 @@ import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/compon
77
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
88
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
99
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
10-
11-
/**
12-
* Represents a field in the input format configuration
13-
*/
14-
interface InputFormatField {
15-
name: string
16-
type?: string
17-
}
18-
19-
/**
20-
* Represents an input trigger block structure
21-
*/
22-
interface InputTriggerBlock {
23-
type: 'input_trigger' | 'start_trigger'
24-
subBlocks?: {
25-
inputFormat?: { value?: InputFormatField[] }
26-
}
27-
}
28-
29-
/**
30-
* Represents a legacy starter block structure
31-
*/
32-
interface StarterBlockLegacy {
33-
type: 'starter'
34-
subBlocks?: {
35-
inputFormat?: { value?: InputFormatField[] }
36-
}
37-
config?: {
38-
params?: {
39-
inputFormat?: InputFormatField[]
40-
}
41-
}
42-
}
10+
import { useWorkflowInputFields } from '@/hooks/queries/workflows'
4311

4412
/**
4513
* Props for the InputMappingField component
@@ -70,73 +38,6 @@ interface InputMappingProps {
7038
disabled?: boolean
7139
}
7240

73-
/**
74-
* Type guard to check if a value is an InputTriggerBlock
75-
* @param value - The value to check
76-
* @returns True if the value is an InputTriggerBlock
77-
*/
78-
function isInputTriggerBlock(value: unknown): value is InputTriggerBlock {
79-
const type = (value as { type?: unknown }).type
80-
return (
81-
!!value && typeof value === 'object' && (type === 'input_trigger' || type === 'start_trigger')
82-
)
83-
}
84-
85-
/**
86-
* Type guard to check if a value is a StarterBlockLegacy
87-
* @param value - The value to check
88-
* @returns True if the value is a StarterBlockLegacy
89-
*/
90-
function isStarterBlock(value: unknown): value is StarterBlockLegacy {
91-
return !!value && typeof value === 'object' && (value as { type?: unknown }).type === 'starter'
92-
}
93-
94-
/**
95-
* Type guard to check if a value is an InputFormatField
96-
* @param value - The value to check
97-
* @returns True if the value is an InputFormatField
98-
*/
99-
function isInputFormatField(value: unknown): value is InputFormatField {
100-
if (typeof value !== 'object' || value === null) return false
101-
if (!('name' in value)) return false
102-
const { name, type } = value as { name: unknown; type?: unknown }
103-
if (typeof name !== 'string' || name.trim() === '') return false
104-
if (type !== undefined && typeof type !== 'string') return false
105-
return true
106-
}
107-
108-
/**
109-
* Extracts input format fields from workflow blocks
110-
* @param blocks - The workflow blocks to extract from
111-
* @returns Array of input format fields or null if not found
112-
*/
113-
function extractInputFormatFields(blocks: Record<string, unknown>): InputFormatField[] | null {
114-
const triggerEntry = Object.entries(blocks).find(([, b]) => isInputTriggerBlock(b))
115-
if (triggerEntry && isInputTriggerBlock(triggerEntry[1])) {
116-
const inputFormat = triggerEntry[1].subBlocks?.inputFormat?.value
117-
if (Array.isArray(inputFormat)) {
118-
return (inputFormat as unknown[])
119-
.filter(isInputFormatField)
120-
.map((f) => ({ name: f.name, type: f.type }))
121-
}
122-
}
123-
124-
const starterEntry = Object.entries(blocks).find(([, b]) => isStarterBlock(b))
125-
if (starterEntry && isStarterBlock(starterEntry[1])) {
126-
const starter = starterEntry[1]
127-
const subBlockFormat = starter.subBlocks?.inputFormat?.value
128-
const legacyParamsFormat = starter.config?.params?.inputFormat
129-
const chosen = Array.isArray(subBlockFormat) ? subBlockFormat : legacyParamsFormat
130-
if (Array.isArray(chosen)) {
131-
return (chosen as unknown[])
132-
.filter(isInputFormatField)
133-
.map((f) => ({ name: f.name, type: f.type }))
134-
}
135-
}
136-
137-
return null
138-
}
139-
14041
/**
14142
* InputMapping component displays and manages input field mappings for workflow execution
14243
* @param props - The component props
@@ -168,62 +69,10 @@ export function InputMapping({
16869
const inputRefs = useRef<Map<string, HTMLInputElement>>(new Map())
16970
const overlayRefs = useRef<Map<string, HTMLDivElement>>(new Map())
17071

171-
const [childInputFields, setChildInputFields] = useState<InputFormatField[]>([])
172-
const [isLoading, setIsLoading] = useState(false)
72+
const workflowId = typeof selectedWorkflowId === 'string' ? selectedWorkflowId : undefined
73+
const { data: childInputFields = [], isLoading } = useWorkflowInputFields(workflowId)
17374
const [collapsedFields, setCollapsedFields] = useState<Record<string, boolean>>({})
17475

175-
useEffect(() => {
176-
let isMounted = true
177-
const controller = new AbortController()
178-
179-
async function fetchChildSchema() {
180-
if (!selectedWorkflowId) {
181-
if (isMounted) {
182-
setChildInputFields([])
183-
setIsLoading(false)
184-
}
185-
return
186-
}
187-
188-
try {
189-
if (isMounted) setIsLoading(true)
190-
191-
const res = await fetch(`/api/workflows/${selectedWorkflowId}`, {
192-
signal: controller.signal,
193-
})
194-
195-
if (!res.ok) {
196-
if (isMounted) {
197-
setChildInputFields([])
198-
setIsLoading(false)
199-
}
200-
return
201-
}
202-
203-
const { data } = await res.json()
204-
const blocks = (data?.state?.blocks as Record<string, unknown>) || {}
205-
const fields = extractInputFormatFields(blocks)
206-
207-
if (isMounted) {
208-
setChildInputFields(fields || [])
209-
setIsLoading(false)
210-
}
211-
} catch (error) {
212-
if (isMounted) {
213-
setChildInputFields([])
214-
setIsLoading(false)
215-
}
216-
}
217-
}
218-
219-
fetchChildSchema()
220-
221-
return () => {
222-
isMounted = false
223-
controller.abort()
224-
}
225-
}, [selectedWorkflowId])
226-
22776
const valueObj: Record<string, string> = useMemo(() => {
22877
if (isPreview && previewValue && typeof previewValue === 'object') {
22978
return previewValue as Record<string, string>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type React from 'react'
22
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
33
import { createLogger } from '@sim/logger'
4-
import { useQuery } from '@tanstack/react-query'
54
import { Loader2, WrenchIcon, XIcon } from 'lucide-react'
65
import { useParams } from 'next/navigation'
76
import {
@@ -61,7 +60,7 @@ import {
6160
useCustomTools,
6261
} from '@/hooks/queries/custom-tools'
6362
import { useForceRefreshMcpTools, useMcpServers, useStoredMcpTools } from '@/hooks/queries/mcp'
64-
import { useWorkflows } from '@/hooks/queries/workflows'
63+
import { useWorkflowInputFields, useWorkflows } from '@/hooks/queries/workflows'
6564
import { usePermissionConfig } from '@/hooks/use-permission-config'
6665
import { getProviderFromModel, supportsToolUsageControl } from '@/providers/utils'
6766
import { useSettingsModalStore } from '@/stores/modals/settings/store'
@@ -645,56 +644,7 @@ function WorkflowInputMapperSyncWrapper({
645644
disabled: boolean
646645
workflowId: string
647646
}) {
648-
const { data: workflowData, isLoading } = useQuery({
649-
queryKey: ['workflow-input-fields', workflowId],
650-
queryFn: async () => {
651-
const response = await fetch(`/api/workflows/${workflowId}`)
652-
if (!response.ok) throw new Error('Failed to fetch workflow')
653-
const { data } = await response.json()
654-
return data
655-
},
656-
enabled: Boolean(workflowId),
657-
staleTime: 60 * 1000,
658-
})
659-
660-
const inputFields = useMemo(() => {
661-
if (!workflowData?.state?.blocks) return []
662-
663-
const blocks = workflowData.state.blocks as Record<string, any>
664-
665-
const triggerEntry = Object.entries(blocks).find(
666-
([, block]) =>
667-
block.type === 'start_trigger' || block.type === 'input_trigger' || block.type === 'starter'
668-
)
669-
670-
if (!triggerEntry) return []
671-
672-
const triggerBlock = triggerEntry[1]
673-
674-
const inputFormat = triggerBlock.subBlocks?.inputFormat?.value
675-
676-
if (Array.isArray(inputFormat)) {
677-
return inputFormat
678-
.filter((field: any) => field.name && typeof field.name === 'string')
679-
.map((field: any) => ({
680-
name: field.name,
681-
type: field.type || 'string',
682-
}))
683-
}
684-
685-
const legacyFormat = triggerBlock.config?.params?.inputFormat
686-
687-
if (Array.isArray(legacyFormat)) {
688-
return legacyFormat
689-
.filter((field: any) => field.name && typeof field.name === 'string')
690-
.map((field: any) => ({
691-
name: field.name,
692-
type: field.type || 'string',
693-
}))
694-
}
695-
696-
return []
697-
}, [workflowData])
647+
const { data: inputFields = [], isLoading } = useWorkflowInputFields(workflowId)
698648

699649
const parsedValue = useMemo(() => {
700650
try {

apps/sim/hooks/queries/workflows.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createLogger } from '@sim/logger'
33
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
44
import { getNextWorkflowColor } from '@/lib/workflows/colors'
55
import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults'
6+
import { extractInputFieldsFromBlocks, type WorkflowInputField } from '@/lib/workflows/input-format'
67
import {
78
createOptimisticMutationHandlers,
89
generateTempId,
@@ -22,6 +23,34 @@ export const workflowKeys = {
2223
deploymentVersions: () => [...workflowKeys.all, 'deploymentVersion'] as const,
2324
deploymentVersion: (workflowId: string | undefined, version: number | undefined) =>
2425
[...workflowKeys.deploymentVersions(), workflowId ?? '', version ?? 0] as const,
26+
inputFields: (workflowId: string | undefined) =>
27+
[...workflowKeys.all, 'inputFields', workflowId ?? ''] as const,
28+
}
29+
30+
/**
31+
* Fetches workflow input fields from the workflow state.
32+
*/
33+
async function fetchWorkflowInputFields(workflowId: string): Promise<WorkflowInputField[]> {
34+
const response = await fetch(`/api/workflows/${workflowId}`)
35+
if (!response.ok) throw new Error('Failed to fetch workflow')
36+
const { data } = await response.json()
37+
return extractInputFieldsFromBlocks(data?.state?.blocks)
38+
}
39+
40+
/**
41+
* Hook to fetch workflow input fields for configuration.
42+
* Uses React Query for caching and deduplication.
43+
*
44+
* @param workflowId - The workflow ID to fetch input fields for
45+
* @returns Query result with input fields array
46+
*/
47+
export function useWorkflowInputFields(workflowId: string | undefined) {
48+
return useQuery({
49+
queryKey: workflowKeys.inputFields(workflowId),
50+
queryFn: () => fetchWorkflowInputFields(workflowId!),
51+
enabled: Boolean(workflowId),
52+
staleTime: 60 * 1000, // 1 minute cache
53+
})
2554
}
2655

2756
function mapWorkflow(workflow: any): WorkflowMetadata {

apps/sim/lib/mcp/workflow-tool-schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod'
2-
import { normalizeInputFormatValue } from '@/lib/workflows/input-format-utils'
2+
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
33
import { isValidStartBlockType } from '@/lib/workflows/triggers/trigger-utils'
44
import type { InputFormatField } from '@/lib/workflows/types'
55
import type { McpToolSchema } from './types'

apps/sim/lib/workflows/blocks/block-outputs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createLogger } from '@sim/logger'
2-
import { normalizeInputFormatValue } from '@/lib/workflows/input-format-utils'
2+
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
33
import {
44
classifyStartBlockType,
55
StartBlockPath,

apps/sim/lib/workflows/input-format-utils.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)