Skip to content

Commit 8c8c627

Browse files
TheodoreSpeaksTheodore Li
andauthored
feat(block): Conditionally hide impersonateUser field from block, add service account prompting (#3966)
* Add credential prompting for google service accounts * Add service account credential block prompting for google service account * Revert requiredCredentials change * Fix lint --------- Co-authored-by: Theodore Li <theo@sim.ai>
1 parent 2164cef commit 8c8c627

File tree

4 files changed

+88
-60
lines changed

4 files changed

+88
-60
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,18 @@
11
import { useCallback, useMemo } from 'react'
2-
import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibility'
32
import {
43
buildCanonicalIndex,
54
evaluateSubBlockCondition,
65
isSubBlockFeatureEnabled,
76
isSubBlockHidden,
87
isSubBlockVisibleForMode,
9-
resolveDependencyValue,
108
} from '@/lib/workflows/subblocks/visibility'
119
import type { BlockConfig, SubBlockConfig, SubBlockType } from '@/blocks/types'
12-
import { useWorkspaceCredential } from '@/hooks/queries/credentials'
1310
import { usePermissionConfig } from '@/hooks/use-permission-config'
11+
import { useReactiveConditions } from '@/hooks/use-reactive-conditions'
1412
import { useWorkflowDiffStore } from '@/stores/workflow-diff'
15-
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
1613
import { mergeSubblockState } from '@/stores/workflows/utils'
1714
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1815

19-
/**
20-
* Evaluates reactive conditions for subblocks. Always calls the same hooks
21-
* regardless of whether a reactive condition exists (Rules of Hooks).
22-
*
23-
* Returns a Set of subblock IDs that should be hidden.
24-
*/
25-
function useReactiveConditions(
26-
subBlocks: SubBlockConfig[],
27-
blockId: string,
28-
activeWorkflowId: string | null,
29-
canonicalModeOverrides?: CanonicalModeOverrides
30-
): Set<string> {
31-
const reactiveSubBlock = useMemo(() => subBlocks.find((sb) => sb.reactiveCondition), [subBlocks])
32-
const reactiveCond = reactiveSubBlock?.reactiveCondition
33-
34-
const canonicalIndex = useMemo(() => buildCanonicalIndex(subBlocks), [subBlocks])
35-
36-
// Resolve watchFields through canonical index to get the active credential value
37-
const watchedCredentialId = useSubBlockStore(
38-
useCallback(
39-
(state) => {
40-
if (!reactiveCond || !activeWorkflowId) return ''
41-
const blockValues = state.workflowValues[activeWorkflowId]?.[blockId] ?? {}
42-
for (const field of reactiveCond.watchFields) {
43-
const val = resolveDependencyValue(
44-
field,
45-
blockValues,
46-
canonicalIndex,
47-
canonicalModeOverrides
48-
)
49-
if (val && typeof val === 'string') return val
50-
}
51-
return ''
52-
},
53-
[reactiveCond, activeWorkflowId, blockId, canonicalIndex, canonicalModeOverrides]
54-
)
55-
)
56-
57-
// Always call useWorkspaceCredential (stable hook count), disable when not needed
58-
const { data: credential } = useWorkspaceCredential(
59-
watchedCredentialId || undefined,
60-
Boolean(reactiveCond && watchedCredentialId)
61-
)
62-
63-
return useMemo(() => {
64-
const hidden = new Set<string>()
65-
if (!reactiveSubBlock || !reactiveCond) return hidden
66-
67-
const conditionMet = credential?.type === reactiveCond.requiredType
68-
if (!conditionMet) {
69-
hidden.add(reactiveSubBlock.id)
70-
}
71-
return hidden
72-
}, [reactiveSubBlock, reactiveCond, credential?.type])
73-
}
74-
7516
/**
7617
* Custom hook for computing subblock layout in the editor panel.
7718
* Determines which subblocks should be visible based on mode, conditions, and feature flags.

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { useReactivateSchedule, useScheduleInfo } from '@/hooks/queries/schedule
4747
import { useSkills } from '@/hooks/queries/skills'
4848
import { useTablesList } from '@/hooks/queries/tables'
4949
import { useWorkflowMap } from '@/hooks/queries/workflows'
50+
import { useReactiveConditions } from '@/hooks/use-reactive-conditions'
5051
import { useSelectorDisplayName } from '@/hooks/use-selector-display-name'
5152
import { useVariablesStore } from '@/stores/variables/store'
5253
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
@@ -942,6 +943,13 @@ export const WorkflowBlock = memo(function WorkflowBlock({
942943
const canonicalIndex = useMemo(() => buildCanonicalIndex(config.subBlocks), [config.subBlocks])
943944
const canonicalModeOverrides = currentStoreBlock?.data?.canonicalModes
944945

946+
const hiddenByReactiveCondition = useReactiveConditions(
947+
config.subBlocks,
948+
id,
949+
activeWorkflowId,
950+
canonicalModeOverrides
951+
)
952+
945953
const subBlockRowsData = useMemo(() => {
946954
const rows: SubBlockConfig[][] = []
947955
let currentRow: SubBlockConfig[] = []
@@ -979,6 +987,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({
979987
const visibleSubBlocks = config.subBlocks.filter((block) => {
980988
if (block.hidden) return false
981989
if (block.hideFromPreview) return false
990+
if (hiddenByReactiveCondition.has(block.id)) return false
982991
if (!isSubBlockFeatureEnabled(block)) return false
983992
if (isSubBlockHidden(block)) return false
984993

@@ -1047,6 +1056,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({
10471056
canonicalModeOverrides,
10481057
userPermissions.canEdit,
10491058
canonicalIndex,
1059+
hiddenByReactiveCondition,
10501060
blockSubBlockValues,
10511061
activeWorkflowId,
10521062
])
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { useCallback, useMemo } from 'react'
2+
import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibility'
3+
import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility'
4+
import type { SubBlockConfig } from '@/blocks/types'
5+
import { useWorkspaceCredential } from '@/hooks/queries/credentials'
6+
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
7+
8+
/**
9+
* Evaluates reactive conditions for subblocks. Always calls the same hooks
10+
* regardless of whether a reactive condition exists (Rules of Hooks).
11+
*
12+
* Returns a Set of subblock IDs that should be hidden.
13+
*/
14+
export function useReactiveConditions(
15+
subBlocks: SubBlockConfig[],
16+
blockId: string,
17+
activeWorkflowId: string | null,
18+
canonicalModeOverrides?: CanonicalModeOverrides
19+
): Set<string> {
20+
const reactiveSubBlock = useMemo(() => subBlocks.find((sb) => sb.reactiveCondition), [subBlocks])
21+
const reactiveCond = reactiveSubBlock?.reactiveCondition
22+
23+
const canonicalIndex = useMemo(() => buildCanonicalIndex(subBlocks), [subBlocks])
24+
25+
// Resolve watchFields through canonical index to get the active credential value
26+
const watchedCredentialId = useSubBlockStore(
27+
useCallback(
28+
(state) => {
29+
if (!reactiveCond || !activeWorkflowId) return ''
30+
const blockValues = state.workflowValues[activeWorkflowId]?.[blockId] ?? {}
31+
for (const field of reactiveCond.watchFields) {
32+
const val = resolveDependencyValue(
33+
field,
34+
blockValues,
35+
canonicalIndex,
36+
canonicalModeOverrides
37+
)
38+
if (val && typeof val === 'string') return val
39+
}
40+
return ''
41+
},
42+
[reactiveCond, activeWorkflowId, blockId, canonicalIndex, canonicalModeOverrides]
43+
)
44+
)
45+
46+
// Always call useWorkspaceCredential (stable hook count), disable when not needed
47+
const { data: credential } = useWorkspaceCredential(
48+
watchedCredentialId || undefined,
49+
Boolean(reactiveCond && watchedCredentialId)
50+
)
51+
52+
return useMemo(() => {
53+
const hidden = new Set<string>()
54+
if (!reactiveSubBlock || !reactiveCond) return hidden
55+
56+
const conditionMet = credential?.type === reactiveCond.requiredType
57+
if (!conditionMet) {
58+
hidden.add(reactiveSubBlock.id)
59+
}
60+
return hidden
61+
}, [reactiveSubBlock, reactiveCond, credential?.type])
62+
}

apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions'
55
import type { BaseServerTool } from '@/lib/copilot/tools/server/base-tool'
66
import { GetBlocksMetadataInput, GetBlocksMetadataResult } from '@/lib/copilot/tools/shared/schemas'
77
import { getAllowedIntegrationsFromEnv, isHosted } from '@/lib/core/config/feature-flags'
8+
import { getServiceAccountProviderForProviderId } from '@/lib/oauth/utils'
89
import { registry as blockRegistry } from '@/blocks/registry'
910
import { AuthMode, type BlockConfig, isHiddenFromDisplay } from '@/blocks/types'
1011
import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
@@ -342,6 +343,20 @@ function transformBlockMetadata(metadata: CopilotBlockMetadata): any {
342343
service: metadata.id, // e.g., 'gmail', 'slack', etc.
343344
description: `OAuth authentication required for ${metadata.name}`,
344345
}
346+
347+
// Check if this service also supports service account credentials
348+
const oauthSubBlock = metadata.inputSchema?.find(
349+
(sb: CopilotSubblockMetadata) => sb.type === 'oauth-input' && sb.serviceId
350+
)
351+
if (oauthSubBlock?.serviceId) {
352+
const serviceAccountProviderId = getServiceAccountProviderForProviderId(
353+
oauthSubBlock.serviceId
354+
)
355+
if (serviceAccountProviderId) {
356+
transformed.requiredCredentials.serviceAccountType = serviceAccountProviderId
357+
transformed.requiredCredentials.description = `OAuth or service account authentication supported for ${metadata.name}`
358+
}
359+
}
345360
} else if (metadata.authType === 'API Key') {
346361
transformed.requiredCredentials = {
347362
type: 'api_key',

0 commit comments

Comments
 (0)