mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(selectors): resolve env var references at design time for selector context (#3446)
* fix(selectors): resolve env var references at design time for selector context
Selectors now resolve {{ENV_VAR}} references before building context and
returning dependency values to consumers, enabling env-var-based credentials
(e.g. {{SLACK_BOT_TOKEN}}) to work with selector dropdowns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): prevent unresolved env var templates from leaking into context
- Fall back to undefined instead of raw template string when env var is
missing from store, so the null-check in the context loop discards it
- Use resolvedDetailId in query cache key so React Query refetches when
the underlying env var value changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(selectors): use || for consistent empty-string env var handling
Align use-selector-setup.ts with use-selector-query.ts by using || instead
of ?? so empty-string env var values are treated as unset.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,9 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { isEnvVarReference, isReference } from '@/executor/constants'
|
||||
import { extractEnvVarName, isEnvVarReference, isReference } from '@/executor/constants'
|
||||
import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types'
|
||||
import { useEnvironmentStore } from '@/stores/settings/environment'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useDependsOnGate } from './use-depends-on-gate'
|
||||
|
||||
@@ -30,23 +31,43 @@ export function useSelectorSetup(
|
||||
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
|
||||
const workflowId = (params?.workflowId as string) || activeWorkflowId || ''
|
||||
|
||||
const envVariables = useEnvironmentStore((s) => s.variables)
|
||||
|
||||
const { finalDisabled, dependencyValues, canonicalIndex } = useDependsOnGate(
|
||||
blockId,
|
||||
subBlock,
|
||||
opts
|
||||
)
|
||||
|
||||
const resolvedDependencyValues = useMemo(() => {
|
||||
const resolved: Record<string, unknown> = {}
|
||||
for (const [key, value] of Object.entries(dependencyValues)) {
|
||||
if (value === null || value === undefined) {
|
||||
resolved[key] = value
|
||||
continue
|
||||
}
|
||||
const str = String(value)
|
||||
if (isEnvVarReference(str)) {
|
||||
const varName = extractEnvVarName(str)
|
||||
resolved[key] = envVariables[varName]?.value || undefined
|
||||
} else {
|
||||
resolved[key] = value
|
||||
}
|
||||
}
|
||||
return resolved
|
||||
}, [dependencyValues, envVariables])
|
||||
|
||||
const selectorContext = useMemo<SelectorContext>(() => {
|
||||
const context: SelectorContext = {
|
||||
workflowId,
|
||||
mimeType: subBlock.mimeType,
|
||||
}
|
||||
|
||||
for (const [depKey, value] of Object.entries(dependencyValues)) {
|
||||
for (const [depKey, value] of Object.entries(resolvedDependencyValues)) {
|
||||
if (value === null || value === undefined) continue
|
||||
const strValue = String(value)
|
||||
if (!strValue) continue
|
||||
if (isReference(strValue) || isEnvVarReference(strValue)) continue
|
||||
if (isReference(strValue)) continue
|
||||
|
||||
const canonicalParamId = canonicalIndex.canonicalIdBySubBlockId[depKey] ?? depKey
|
||||
|
||||
@@ -58,14 +79,14 @@ export function useSelectorSetup(
|
||||
}
|
||||
|
||||
return context
|
||||
}, [dependencyValues, canonicalIndex, workflowId, subBlock.mimeType])
|
||||
}, [resolvedDependencyValues, canonicalIndex, workflowId, subBlock.mimeType])
|
||||
|
||||
return {
|
||||
selectorKey: (subBlock.selectorKey ?? null) as SelectorKey | null,
|
||||
selectorContext,
|
||||
allowSearch: subBlock.selectorAllowSearch ?? true,
|
||||
disabled: finalDisabled || !subBlock.selectorKey,
|
||||
dependencyValues,
|
||||
dependencyValues: resolvedDependencyValues,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { isEnvVarReference, isReference } from '@/executor/constants'
|
||||
import { extractEnvVarName, isEnvVarReference, isReference } from '@/executor/constants'
|
||||
import { getSelectorDefinition, mergeOption } from '@/hooks/selectors/registry'
|
||||
import type { SelectorKey, SelectorOption, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
import { useEnvironmentStore } from '@/stores/settings/environment'
|
||||
|
||||
interface SelectorHookArgs extends Omit<SelectorQueryArgs, 'key'> {
|
||||
search?: string
|
||||
@@ -30,14 +31,25 @@ export function useSelectorOptionDetail(
|
||||
key: SelectorKey,
|
||||
args: SelectorHookArgs & { detailId?: string }
|
||||
) {
|
||||
const envVariables = useEnvironmentStore((s) => s.variables)
|
||||
const definition = getSelectorDefinition(key)
|
||||
|
||||
const resolvedDetailId = useMemo(() => {
|
||||
if (!args.detailId) return undefined
|
||||
if (isReference(args.detailId)) return undefined
|
||||
if (isEnvVarReference(args.detailId)) {
|
||||
const varName = extractEnvVarName(args.detailId)
|
||||
return envVariables[varName]?.value || undefined
|
||||
}
|
||||
return args.detailId
|
||||
}, [args.detailId, envVariables])
|
||||
|
||||
const queryArgs: SelectorQueryArgs = {
|
||||
key,
|
||||
context: args.context,
|
||||
detailId: args.detailId,
|
||||
detailId: resolvedDetailId,
|
||||
}
|
||||
const hasRealDetailId =
|
||||
Boolean(args.detailId) && !isReference(args.detailId!) && !isEnvVarReference(args.detailId!)
|
||||
const hasRealDetailId = Boolean(resolvedDetailId)
|
||||
const baseEnabled =
|
||||
hasRealDetailId && definition.fetchById !== undefined
|
||||
? definition.enabled
|
||||
@@ -47,7 +59,7 @@ export function useSelectorOptionDetail(
|
||||
const enabled = args.enabled ?? baseEnabled
|
||||
|
||||
const query = useQuery<SelectorOption | null>({
|
||||
queryKey: [...definition.getQueryKey(queryArgs), 'detail', args.detailId ?? 'none'],
|
||||
queryKey: [...definition.getQueryKey(queryArgs), 'detail', resolvedDetailId ?? 'none'],
|
||||
queryFn: () => definition.fetchById!(queryArgs),
|
||||
enabled,
|
||||
staleTime: definition.staleTime ?? 300_000,
|
||||
|
||||
Reference in New Issue
Block a user