mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
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:
committed by
GitHub
parent
cadfcdbfbd
commit
ebb8cf8bf9
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user