Skip to content

Commit ab50c2d

Browse files
author
Theodore Li
committed
create credentialCondition
1 parent f7b5055 commit ab50c2d

5 files changed

Lines changed: 57 additions & 34 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { createElement, useCallback, useEffect, useMemo, useState } from 'react'
3+
import { createElement, useCallback, useMemo, useState } from 'react'
44
import { ExternalLink, Users } from 'lucide-react'
55
import { useParams } from 'next/navigation'
66
import { Button, Combobox } from '@/components/emcn/components'
@@ -128,14 +128,6 @@ export function CredentialSelector({
128128
[selectedCredential]
129129
)
130130

131-
const [, setIsServiceAccount] = useSubBlockValue<string>(blockId, 'isServiceAccount')
132-
133-
const isAdvancedMode = subBlock.mode === 'advanced'
134-
135-
useEffect(() => {
136-
setIsServiceAccount(isAdvancedMode || isServiceAccount ? 'true' : '')
137-
}, [isServiceAccount, isAdvancedMode, setIsServiceAccount])
138-
139131
const selectedCredentialSet = useMemo(
140132
() => credentialSets.find((cs) => cs.id === selectedCredentialSetId),
141133
[credentialSets, selectedCredentialSetId]

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ import {
5252
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components'
5353
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
5454
import type { SubBlockConfig } from '@/blocks/types'
55+
import { useWorkspaceCredential } from '@/hooks/queries/credentials'
56+
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
57+
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
5558
import { useWebhookManagement } from '@/hooks/use-webhook-management'
5659

5760
const SLACK_OVERRIDES: SelectorOverrides = {
@@ -615,6 +618,36 @@ function SubBlockComponent({
615618
previewContextValues: contextValues,
616619
})
617620

621+
// Credential-type visibility gate: reactively fetches the credential by ID
622+
// and hides the field unless it matches the required type.
623+
const credTypeCond = config.credentialTypeCondition
624+
const activeWfId = useWorkflowRegistry((s) => (credTypeCond ? s.activeWorkflowId : null))
625+
626+
const watchedCredentialId = useSubBlockStore(
627+
useCallback(
628+
(state) => {
629+
if (!credTypeCond || !activeWfId) return ''
630+
const blockValues = state.workflowValues[activeWfId]?.[blockId] ?? {}
631+
const merged = { ...(dependencyContext ?? {}), ...blockValues }
632+
for (const field of credTypeCond.watchFields) {
633+
const val = merged[field]
634+
if (val && typeof val === 'string') return val
635+
}
636+
return ''
637+
},
638+
[credTypeCond, activeWfId, blockId, dependencyContext]
639+
)
640+
)
641+
642+
const { data: watchedCredential } = useWorkspaceCredential(
643+
watchedCredentialId || undefined,
644+
Boolean(credTypeCond && watchedCredentialId)
645+
)
646+
647+
if (credTypeCond && watchedCredential?.type !== credTypeCond.requiredType) {
648+
return <></> as unknown as JSX.Element
649+
}
650+
618651
const isDisabled = gatedDisabled
619652

620653
/**

apps/sim/blocks/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,19 @@ export interface SubBlockConfig {
356356
not?: boolean
357357
}
358358
})
359+
/**
360+
* Declarative credential-type visibility gate. The SubBlock watches the specified
361+
* fields for a credential ID, fetches the credential via React Query, and hides
362+
* the field unless the credential type matches.
363+
*
364+
* Works in both block editor and tool-input contexts without side effects.
365+
*/
366+
credentialTypeCondition?: {
367+
/** Subblock IDs (or canonical param IDs) to read the credential ID from, in priority order. */
368+
watchFields: string[]
369+
/** Required credential type for the field to be visible (e.g. 'service_account'). */
370+
requiredType: string
371+
}
359372
// Props specific to 'code' sub-block type
360373
language?: 'javascript' | 'json' | 'python'
361374
generationType?: GenerationType

apps/sim/blocks/utils.ts

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,20 @@ import { useProvidersStore } from '@/stores/providers/store'
1010

1111
/**
1212
* Standard subblocks for Google service account impersonation.
13-
* The credential-selector writes `isServiceAccount` to the store when a service account
14-
* credential is selected. The `impersonateUserEmail` field conditionally appears for
15-
* domain-wide delegation to Google Workspace APIs.
13+
* The `impersonateUserEmail` field fetches the credential by ID to check if it's
14+
* a service account, using `asyncCondition` so it works in both block editor and
15+
* agent tool-input contexts without cross-subblock state propagation.
1616
*/
1717
export const SERVICE_ACCOUNT_SUBBLOCKS: SubBlockConfig[] = [
18-
{
19-
id: 'isServiceAccount',
20-
title: 'Is Service Account',
21-
type: 'short-input',
22-
hidden: true,
23-
mode: 'both',
24-
},
2518
{
2619
id: 'impersonateUserEmail',
2720
title: 'Impersonated Account',
2821
type: 'short-input',
2922
placeholder: 'Email to impersonate (for service accounts)',
3023
paramVisibility: 'user-only',
31-
condition: (values) => {
32-
// In tool-input context (agent tools), __canonicalModes and isServiceAccount
33-
// are absent — always show so users can configure impersonation.
34-
const hasBlockEditorContext =
35-
values?.__canonicalModes !== undefined || values?.isServiceAccount !== undefined
36-
if (!hasBlockEditorContext) {
37-
return { field: 'isServiceAccount', value: 'does-not-match', not: true }
38-
}
39-
const modes = values.__canonicalModes as Record<string, string> | undefined
40-
if (modes?.oauthCredential === 'advanced') {
41-
return { field: 'isServiceAccount', value: 'does-not-match', not: true }
42-
}
43-
return { field: 'isServiceAccount', value: 'true' }
24+
credentialTypeCondition: {
25+
watchFields: ['credential', 'oauthCredential', 'manualCredential'],
26+
requiredType: 'service_account',
4427
},
4528
mode: 'both',
4629
},

apps/sim/tools/params.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -989,8 +989,10 @@ export function getSubBlocksForToolInput(
989989
// Filter by visibility: exclude hidden and llm-only
990990
if (visibility === 'hidden' || visibility === 'llm-only') continue
991991

992-
// Evaluate condition against current values
993-
if (sb.condition) {
992+
// Evaluate condition against current values.
993+
// Skip sync condition check for subblocks with credentialTypeCondition — visibility
994+
// is handled at render time by the SubBlock component via React Query.
995+
if (sb.condition && !sb.credentialTypeCondition) {
994996
const conditionMet = evaluateSubBlockCondition(
995997
sb.condition as SubBlockCondition,
996998
valuesWithOperation

0 commit comments

Comments
 (0)