Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,36 @@ export function CredentialSelector({
.setDisplayNames('credentials', effectiveProviderId, credentialMap)
}

// Do not auto-select or reset. We only show what's persisted.
// Check if the currently selected credential still exists
const selectedCredentialStillExists = (creds || []).some(
(cred: Credential) => cred.id === selectedId
)
const shouldClearPersistedSelection =
!isPreview && selectedId && !selectedCredentialStillExists && !foreignMetaFound

if (shouldClearPersistedSelection) {
logger.info('Clearing invalid credential selection - credential was disconnected', {
selectedId,
provider: effectiveProviderId,
})

// Clear via setStoreValue to trigger cascade
setStoreValue('')
setSelectedId('')

if (effectiveProviderId) {
useDisplayNamesStore
.getState()
.removeDisplayName('credentials', effectiveProviderId, selectedId)
}
}
}
} catch (error) {
logger.error('Error fetching credentials:', { error })
} finally {
setIsLoading(false)
}
}, [effectiveProviderId, selectedId, activeWorkflowId])
}, [effectiveProviderId, selectedId, activeWorkflowId, isPreview, setStoreValue])

// Fetch credentials on initial mount and whenever the subblock value changes externally
useEffect(() => {
Expand Down Expand Up @@ -204,6 +226,24 @@ export function CredentialSelector({
}
}, [fetchCredentials])

// Listen for credential disconnection events from settings modal
useEffect(() => {
const handleCredentialDisconnected = (event: Event) => {
const customEvent = event as CustomEvent
const { providerId } = customEvent.detail
// Re-fetch if this disconnection affects our provider
if (providerId && (providerId === effectiveProviderId || providerId.startsWith(provider))) {
fetchCredentials()
}
}

window.addEventListener('credential-disconnected', handleCredentialDisconnected)

return () => {
window.removeEventListener('credential-disconnected', handleCredentialDisconnected)
}
}, [fetchCredentials, effectiveProviderId, provider])

// Handle popover open to fetch fresh credentials
const handleOpenChange = (isOpen: boolean) => {
setOpen(isOpen)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ export function GoogleDrivePicker({
if (data.file) {
setSelectedFile(data.file)
onFileInfoChange?.(data.file)

// Cache the file name
if (selectedCredentialId && data.file.id && data.file.name) {
useDisplayNamesStore.getState().setDisplayNames('files', selectedCredentialId, {
[data.file.id]: data.file.name,
})
}

return data.file
}
} else {
Expand Down Expand Up @@ -335,6 +343,13 @@ export function GoogleDrivePicker({
setSelectedFile(fileInfo)
onChange(file.id, fileInfo)
onFileInfoChange?.(fileInfo)

// Cache the selected file name
if (selectedCredentialId) {
useDisplayNamesStore
.getState()
.setDisplayNames('files', selectedCredentialId, { [file.id]: file.name })
}
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AlertTriangle } from 'lucide-react'
import { Label, Tooltip } from '@/components/emcn/components'
import { cn } from '@/lib/utils'
import type { FieldDiffStatus } from '@/lib/workflows/diff/types'
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-depends-on-gate'
import type { SubBlockConfig } from '@/blocks/types'
import {
ChannelSelectorInput,
Expand Down Expand Up @@ -157,7 +158,15 @@ function SubBlockComponent({
| string[]
| null
| undefined
const isDisabled = disabled || isPreview

// Use dependsOn gating to compute final disabled state
const { finalDisabled: gatedDisabled } = useDependsOnGate(blockId, config, {
disabled,
isPreview,
previewContextValues: subBlockValues,
})

const isDisabled = gatedDisabled

/**
* Selects and renders the appropriate input component for the current sub-block `config.type`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
BLOCK_DIMENSIONS,
useBlockDimensions,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-block-dimensions'
import type { SubBlockConfig } from '@/blocks/types'
import { SELECTOR_TYPES_HYDRATION_REQUIRED, type SubBlockConfig } from '@/blocks/types'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useCredentialDisplay } from '@/hooks/use-credential-display'
import { useDisplayName } from '@/hooks/use-display-name'
Expand Down Expand Up @@ -237,7 +237,10 @@ const SubBlockRow = ({

const isPasswordField = subBlock?.password === true
const maskedValue = isPasswordField && value && value !== '-' ? '•••' : null
const displayValue = maskedValue || credentialName || dropdownLabel || genericDisplayName || value

const isSelectorType = subBlock?.type && SELECTOR_TYPES_HYDRATION_REQUIRED.includes(subBlock.type)
const hydratedName = credentialName || dropdownLabel || genericDisplayName
const displayValue = maskedValue || hydratedName || (isSelectorType && value ? '-' : value)

return (
<div className='flex items-center gap-[8px]'>
Expand Down
14 changes: 14 additions & 0 deletions apps/sim/blocks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ export type SubBlockType =
| 'variables-input' // Variable assignments for updating workflow variables
| 'text' // Read-only text display

/**
* Selector types that require display name hydration
* These show IDs/keys that need to be resolved to human-readable names
*/
export const SELECTOR_TYPES_HYDRATION_REQUIRED: SubBlockType[] = [
'oauth-input',
'channel-selector',
'file-selector',
'folder-selector',
'project-selector',
'knowledge-base-selector',
'document-selector',
] as const

export type ExtractToolOutput<T> = T extends ToolResponse ? T['output'] : never

export type ToolOutputToValueType<T> = T extends Record<string, any>
Expand Down
22 changes: 22 additions & 0 deletions apps/sim/hooks/use-display-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,28 @@ export function useDisplayName(
})
.catch(() => {})
.finally(() => setIsFetching(false))
}
// Google Drive files/folders (fetch by ID since no list endpoint via Picker API)
else if (
(provider === 'google-drive' || subBlock.serviceId === 'google-drive') &&
typeof value === 'string' &&
value
) {
const queryParams = new URLSearchParams({
credentialId: context.credentialId,
fileId: value,
})
fetch(`/api/tools/drive/file?${queryParams.toString()}`)
.then((res) => res.json())
.then((data) => {
if (data.file?.id && data.file.name) {
useDisplayNamesStore
.getState()
.setDisplayNames('files', context.credentialId!, { [data.file.id]: data.file.name })
}
})
.catch(() => {})
.finally(() => setIsFetching(false))
} else {
setIsFetching(false)
}
Expand Down
21 changes: 21 additions & 0 deletions apps/sim/stores/display-names/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ interface DisplayNamesStore {
*/
getDisplayName: (type: keyof DisplayNamesCache, context: string, id: string) => string | null

/**
* Remove a single display name
*/
removeDisplayName: (type: keyof DisplayNamesCache, context: string, id: string) => void

/**
* Clear all cached display names for a type/context
*/
Expand Down Expand Up @@ -103,6 +108,22 @@ export const useDisplayNamesStore = create<DisplayNamesStore>((set, get) => ({
return contextCache?.[id] || null
},

removeDisplayName: (type, context, id) => {
set((state) => {
const contextCache = { ...state.cache[type][context] }
delete contextCache[id]
return {
cache: {
...state.cache,
[type]: {
...state.cache[type],
[context]: contextCache,
},
},
}
})
},

clearContext: (type, context) => {
set((state) => {
const newTypeCache = { ...state.cache[type] }
Expand Down
5 changes: 3 additions & 2 deletions apps/sim/triggers/gmail/poller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export const gmailPollingTrigger: TriggerConfig = {
| string
| null
if (!credentialId) {
return []
// Return a sentinel to prevent infinite retry loops when credential is missing
throw new Error('No Gmail credential selected')
}
try {
const response = await fetch(`/api/tools/gmail/labels?credentialId=${credentialId}`)
Expand All @@ -55,7 +56,7 @@ export const gmailPollingTrigger: TriggerConfig = {
return []
} catch (error) {
logger.error('Error fetching Gmail labels:', error)
return []
throw error
}
},
dependsOn: ['triggerCredentials'],
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/triggers/outlook/poller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const outlookPollingTrigger: TriggerConfig = {
| string
| null
if (!credentialId) {
return []
throw new Error('No Outlook credential selected')
}
try {
const response = await fetch(`/api/tools/outlook/folders?credentialId=${credentialId}`)
Expand All @@ -55,7 +55,7 @@ export const outlookPollingTrigger: TriggerConfig = {
return []
} catch (error) {
logger.error('Error fetching Outlook folders:', error)
return []
throw error
}
},
dependsOn: ['triggerCredentials'],
Expand Down