fix(slack): set depends on for slack channel channel subblock (#1177)

* fix(slack): set depends on for slack channel

* use foreign credential check

* fix

* fix clearing of block
This commit is contained in:
Vikhyath Mondreti
2025-08-28 20:11:30 -07:00
committed by GitHub
parent cadfcdbfbd
commit ebb8cf8bf9
4 changed files with 50 additions and 55 deletions

View File

@@ -1,12 +1,14 @@
'use client' 'use client'
import { useEffect, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useParams } from 'next/navigation'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { import {
type SlackChannelInfo, type SlackChannelInfo,
SlackChannelSelector, SlackChannelSelector,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/channel-selector/components/slack-channel-selector' } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/channel-selector/components/slack-channel-selector'
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-depends-on-gate' import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-depends-on-gate'
import { useForeignCredential } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-foreign-credential'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value' import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value'
import type { SubBlockConfig } from '@/blocks/types' import type { SubBlockConfig } from '@/blocks/types'
@@ -15,7 +17,6 @@ interface ChannelSelectorInputProps {
subBlock: SubBlockConfig subBlock: SubBlockConfig
disabled?: boolean disabled?: boolean
onChannelSelect?: (channelId: string) => void onChannelSelect?: (channelId: string) => void
credential?: string
isPreview?: boolean isPreview?: boolean
previewValue?: any | null previewValue?: any | null
} }
@@ -25,10 +26,11 @@ export function ChannelSelectorInput({
subBlock, subBlock,
disabled = false, disabled = false,
onChannelSelect, onChannelSelect,
credential: providedCredential,
isPreview = false, isPreview = false,
previewValue, previewValue,
}: ChannelSelectorInputProps) { }: ChannelSelectorInputProps) {
const params = useParams()
const workflowIdFromUrl = (params?.workflowId as string) || ''
// Use the proper hook to get the current value and setter (same as file-selector) // Use the proper hook to get the current value and setter (same as file-selector)
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id) const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id)
// Reactive upstream fields // Reactive upstream fields
@@ -42,20 +44,22 @@ export function ChannelSelectorInput({
const provider = subBlock.provider || 'slack' const provider = subBlock.provider || 'slack'
const isSlack = provider === 'slack' const isSlack = provider === 'slack'
// Central dependsOn gating // Central dependsOn gating
const { finalDisabled } = useDependsOnGate(blockId, subBlock, { disabled, isPreview }) const { finalDisabled, dependsOn, dependencyValues } = useDependsOnGate(blockId, subBlock, {
disabled,
isPreview,
})
// Get the credential for the provider - use provided credential or fall back to reactive values // Choose credential strictly based on auth method
let credential: string const credential: string =
if (providedCredential) { (authMethod as string) === 'bot_token'
credential = providedCredential ? (botToken as string) || ''
} else if ((authMethod as string) === 'bot_token' && (botToken as string)) { : (connectedCredential as string) || ''
credential = botToken as string
} else {
credential = (connectedCredential as string) || ''
}
// Use preview value when in preview mode, otherwise use store value // Determine if connected OAuth credential is foreign (not applicable for bot tokens)
const value = isPreview ? previewValue : storeValue const { isForeignCredential } = useForeignCredential(
'slack',
(authMethod as string) === 'bot_token' ? '' : (connectedCredential as string) || ''
)
// Get the current value from the store or prop value if in preview mode (same pattern as file-selector) // Get the current value from the store or prop value if in preview mode (same pattern as file-selector)
useEffect(() => { useEffect(() => {
@@ -65,6 +69,21 @@ export function ChannelSelectorInput({
} }
}, [isPreview, previewValue, storeValue]) }, [isPreview, previewValue, storeValue])
// Clear channel when any declared dependency changes (e.g., authMethod/credential)
const prevDepsSigRef = useRef<string>('')
useEffect(() => {
if (dependsOn.length === 0) return
const currentSig = JSON.stringify(dependencyValues)
if (prevDepsSigRef.current && prevDepsSigRef.current !== currentSig) {
if (!isPreview) {
setSelectedChannelId('')
setChannelInfo(null)
setStoreValue('')
}
}
prevDepsSigRef.current = currentSig
}, [dependsOn, dependencyValues, isPreview, setStoreValue])
// Handle channel selection (same pattern as file-selector) // Handle channel selection (same pattern as file-selector)
const handleChannelChange = (channelId: string, info?: SlackChannelInfo) => { const handleChannelChange = (channelId: string, info?: SlackChannelInfo) => {
setSelectedChannelId(channelId) setSelectedChannelId(channelId)
@@ -90,6 +109,8 @@ export function ChannelSelectorInput({
credential={credential} credential={credential}
label={subBlock.placeholder || 'Select Slack channel'} label={subBlock.placeholder || 'Select Slack channel'}
disabled={finalDisabled} disabled={finalDisabled}
workflowId={workflowIdFromUrl}
isForeignCredential={isForeignCredential}
/> />
</div> </div>
</TooltipTrigger> </TooltipTrigger>

View File

@@ -24,6 +24,8 @@ interface SlackChannelSelectorProps {
credential: string credential: string
label?: string label?: string
disabled?: boolean disabled?: boolean
workflowId?: string
isForeignCredential?: boolean
} }
export function SlackChannelSelector({ export function SlackChannelSelector({
@@ -32,6 +34,8 @@ export function SlackChannelSelector({
credential, credential,
label = 'Select Slack channel', label = 'Select Slack channel',
disabled = false, disabled = false,
workflowId,
isForeignCredential = false,
}: SlackChannelSelectorProps) { }: SlackChannelSelectorProps) {
const [channels, setChannels] = useState<SlackChannelInfo[]>([]) const [channels, setChannels] = useState<SlackChannelInfo[]>([])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
@@ -51,7 +55,7 @@ export function SlackChannelSelector({
const res = await fetch('/api/tools/slack/channels', { const res = await fetch('/api/tools/slack/channels', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential }), body: JSON.stringify({ credential, workflowId }),
}) })
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`) if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)
@@ -125,6 +129,7 @@ export function SlackChannelSelector({
aria-expanded={open} aria-expanded={open}
className='relative w-full justify-between' className='relative w-full justify-between'
disabled={disabled || !credential} disabled={disabled || !credential}
title={isForeignCredential ? 'Using a shared account' : undefined}
> >
<div className='flex max-w-[calc(100%-20px)] items-center gap-2 overflow-hidden'> <div className='flex max-w-[calc(100%-20px)] items-center gap-2 overflow-hidden'>
<SlackIcon className='h-4 w-4 text-[#611f69]' /> <SlackIcon className='h-4 w-4 text-[#611f69]' />
@@ -133,6 +138,11 @@ export function SlackChannelSelector({
{getChannelIcon(selectedChannel)} {getChannelIcon(selectedChannel)}
<span className='truncate font-normal'>{formatChannelName(selectedChannel)}</span> <span className='truncate font-normal'>{formatChannelName(selectedChannel)}</span>
</> </>
) : value ? (
<>
<Hash className='h-1.5 w-1.5' />
<span className='truncate font-normal'>{value}</span>
</>
) : ( ) : (
<span className='truncate text-muted-foreground'>{label}</span> <span className='truncate text-muted-foreground'>{label}</span>
)} )}

View File

@@ -876,44 +876,6 @@ export function ToolInput({
return result return result
} }
// Helper function to get credential for channel selector
const getCredentialForChannelSelector = (paramId: string): string => {
// Look for the tool that contains this parameter
const currentToolIndex = selectedTools.findIndex((tool) => {
const toolParams = getToolParametersConfig(tool.toolId)
return toolParams?.userInputParameters.some((p) => p.id === paramId)
})
if (currentToolIndex === -1) return ''
const currentTool = selectedTools[currentToolIndex]
// Enhanced credential detection logic from legacy implementation
// Check for bot token first, then OAuth credential
const botToken =
currentTool.params.botToken || (subBlockStore.getValue(blockId, 'botToken') as string)
const oauthCredential =
currentTool.params.credential || (subBlockStore.getValue(blockId, 'credential') as string)
if (botToken?.trim()) {
return botToken
}
if (oauthCredential?.trim()) {
return oauthCredential
}
// Fallback: check for other common credential parameter names
const credentialKeys = ['accessToken', 'token', 'apiKey', 'authToken']
for (const key of credentialKeys) {
const value = currentTool.params[key] || (subBlockStore.getValue(blockId, key) as string)
if (value?.trim()) {
return value
}
}
return ''
}
// Render the appropriate UI component based on parameter configuration // Render the appropriate UI component based on parameter configuration
const renderParameterInput = ( const renderParameterInput = (
param: ToolParameterConfig, param: ToolParameterConfig,
@@ -1023,8 +985,9 @@ export function ToolInput({
placeholder: uiComponent.placeholder, placeholder: uiComponent.placeholder,
}} }}
onChannelSelect={onChange} onChannelSelect={onChange}
credential={getCredentialForChannelSelector(param.id)}
disabled={disabled} disabled={disabled}
isPreview={true}
previewValue={value}
/> />
) )

View File

@@ -81,6 +81,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
provider: 'slack', provider: 'slack',
placeholder: 'Select Slack channel', placeholder: 'Select Slack channel',
mode: 'basic', mode: 'basic',
dependsOn: ['credential', 'authMethod'],
}, },
// Manual channel ID input (advanced mode) // Manual channel ID input (advanced mode)
{ {