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'
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 {
type SlackChannelInfo,
SlackChannelSelector,
} 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 { 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 type { SubBlockConfig } from '@/blocks/types'
@@ -15,7 +17,6 @@ interface ChannelSelectorInputProps {
subBlock: SubBlockConfig
disabled?: boolean
onChannelSelect?: (channelId: string) => void
credential?: string
isPreview?: boolean
previewValue?: any | null
}
@@ -25,10 +26,11 @@ export function ChannelSelectorInput({
subBlock,
disabled = false,
onChannelSelect,
credential: providedCredential,
isPreview = false,
previewValue,
}: 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)
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id)
// Reactive upstream fields
@@ -42,20 +44,22 @@ export function ChannelSelectorInput({
const provider = subBlock.provider || 'slack'
const isSlack = provider === 'slack'
// 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
let credential: string
if (providedCredential) {
credential = providedCredential
} else if ((authMethod as string) === 'bot_token' && (botToken as string)) {
credential = botToken as string
} else {
credential = (connectedCredential as string) || ''
}
// Choose credential strictly based on auth method
const credential: string =
(authMethod as string) === 'bot_token'
? (botToken as string) || ''
: (connectedCredential as string) || ''
// Use preview value when in preview mode, otherwise use store value
const value = isPreview ? previewValue : storeValue
// Determine if connected OAuth credential is foreign (not applicable for bot tokens)
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)
useEffect(() => {
@@ -65,6 +69,21 @@ export function ChannelSelectorInput({
}
}, [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)
const handleChannelChange = (channelId: string, info?: SlackChannelInfo) => {
setSelectedChannelId(channelId)
@@ -90,6 +109,8 @@ export function ChannelSelectorInput({
credential={credential}
label={subBlock.placeholder || 'Select Slack channel'}
disabled={finalDisabled}
workflowId={workflowIdFromUrl}
isForeignCredential={isForeignCredential}
/>
</div>
</TooltipTrigger>

View File

@@ -24,6 +24,8 @@ interface SlackChannelSelectorProps {
credential: string
label?: string
disabled?: boolean
workflowId?: string
isForeignCredential?: boolean
}
export function SlackChannelSelector({
@@ -32,6 +34,8 @@ export function SlackChannelSelector({
credential,
label = 'Select Slack channel',
disabled = false,
workflowId,
isForeignCredential = false,
}: SlackChannelSelectorProps) {
const [channels, setChannels] = useState<SlackChannelInfo[]>([])
const [loading, setLoading] = useState(false)
@@ -51,7 +55,7 @@ export function SlackChannelSelector({
const res = await fetch('/api/tools/slack/channels', {
method: 'POST',
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}`)
@@ -125,6 +129,7 @@ export function SlackChannelSelector({
aria-expanded={open}
className='relative w-full justify-between'
disabled={disabled || !credential}
title={isForeignCredential ? 'Using a shared account' : undefined}
>
<div className='flex max-w-[calc(100%-20px)] items-center gap-2 overflow-hidden'>
<SlackIcon className='h-4 w-4 text-[#611f69]' />
@@ -133,6 +138,11 @@ export function SlackChannelSelector({
{getChannelIcon(selectedChannel)}
<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>
)}

View File

@@ -876,44 +876,6 @@ export function ToolInput({
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
const renderParameterInput = (
param: ToolParameterConfig,
@@ -1023,8 +985,9 @@ export function ToolInput({
placeholder: uiComponent.placeholder,
}}
onChannelSelect={onChange}
credential={getCredentialForChannelSelector(param.id)}
disabled={disabled}
isPreview={true}
previewValue={value}
/>
)

View File

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