mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
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>
This commit is contained in:
@@ -1,77 +1,18 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibility'
|
||||
import {
|
||||
buildCanonicalIndex,
|
||||
evaluateSubBlockCondition,
|
||||
isSubBlockFeatureEnabled,
|
||||
isSubBlockHidden,
|
||||
isSubBlockVisibleForMode,
|
||||
resolveDependencyValue,
|
||||
} from '@/lib/workflows/subblocks/visibility'
|
||||
import type { BlockConfig, SubBlockConfig, SubBlockType } from '@/blocks/types'
|
||||
import { useWorkspaceCredential } from '@/hooks/queries/credentials'
|
||||
import { usePermissionConfig } from '@/hooks/use-permission-config'
|
||||
import { useReactiveConditions } from '@/hooks/use-reactive-conditions'
|
||||
import { useWorkflowDiffStore } from '@/stores/workflow-diff'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { mergeSubblockState } from '@/stores/workflows/utils'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
|
||||
/**
|
||||
* Evaluates reactive conditions for subblocks. Always calls the same hooks
|
||||
* regardless of whether a reactive condition exists (Rules of Hooks).
|
||||
*
|
||||
* Returns a Set of subblock IDs that should be hidden.
|
||||
*/
|
||||
function useReactiveConditions(
|
||||
subBlocks: SubBlockConfig[],
|
||||
blockId: string,
|
||||
activeWorkflowId: string | null,
|
||||
canonicalModeOverrides?: CanonicalModeOverrides
|
||||
): Set<string> {
|
||||
const reactiveSubBlock = useMemo(() => subBlocks.find((sb) => sb.reactiveCondition), [subBlocks])
|
||||
const reactiveCond = reactiveSubBlock?.reactiveCondition
|
||||
|
||||
const canonicalIndex = useMemo(() => buildCanonicalIndex(subBlocks), [subBlocks])
|
||||
|
||||
// Resolve watchFields through canonical index to get the active credential value
|
||||
const watchedCredentialId = useSubBlockStore(
|
||||
useCallback(
|
||||
(state) => {
|
||||
if (!reactiveCond || !activeWorkflowId) return ''
|
||||
const blockValues = state.workflowValues[activeWorkflowId]?.[blockId] ?? {}
|
||||
for (const field of reactiveCond.watchFields) {
|
||||
const val = resolveDependencyValue(
|
||||
field,
|
||||
blockValues,
|
||||
canonicalIndex,
|
||||
canonicalModeOverrides
|
||||
)
|
||||
if (val && typeof val === 'string') return val
|
||||
}
|
||||
return ''
|
||||
},
|
||||
[reactiveCond, activeWorkflowId, blockId, canonicalIndex, canonicalModeOverrides]
|
||||
)
|
||||
)
|
||||
|
||||
// Always call useWorkspaceCredential (stable hook count), disable when not needed
|
||||
const { data: credential } = useWorkspaceCredential(
|
||||
watchedCredentialId || undefined,
|
||||
Boolean(reactiveCond && watchedCredentialId)
|
||||
)
|
||||
|
||||
return useMemo(() => {
|
||||
const hidden = new Set<string>()
|
||||
if (!reactiveSubBlock || !reactiveCond) return hidden
|
||||
|
||||
const conditionMet = credential?.type === reactiveCond.requiredType
|
||||
if (!conditionMet) {
|
||||
hidden.add(reactiveSubBlock.id)
|
||||
}
|
||||
return hidden
|
||||
}, [reactiveSubBlock, reactiveCond, credential?.type])
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook for computing subblock layout in the editor panel.
|
||||
* Determines which subblocks should be visible based on mode, conditions, and feature flags.
|
||||
|
||||
@@ -47,6 +47,7 @@ import { useReactivateSchedule, useScheduleInfo } from '@/hooks/queries/schedule
|
||||
import { useSkills } from '@/hooks/queries/skills'
|
||||
import { useTablesList } from '@/hooks/queries/tables'
|
||||
import { useWorkflowMap } from '@/hooks/queries/workflows'
|
||||
import { useReactiveConditions } from '@/hooks/use-reactive-conditions'
|
||||
import { useSelectorDisplayName } from '@/hooks/use-selector-display-name'
|
||||
import { useVariablesStore } from '@/stores/variables/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
@@ -942,6 +943,13 @@ export const WorkflowBlock = memo(function WorkflowBlock({
|
||||
const canonicalIndex = useMemo(() => buildCanonicalIndex(config.subBlocks), [config.subBlocks])
|
||||
const canonicalModeOverrides = currentStoreBlock?.data?.canonicalModes
|
||||
|
||||
const hiddenByReactiveCondition = useReactiveConditions(
|
||||
config.subBlocks,
|
||||
id,
|
||||
activeWorkflowId,
|
||||
canonicalModeOverrides
|
||||
)
|
||||
|
||||
const subBlockRowsData = useMemo(() => {
|
||||
const rows: SubBlockConfig[][] = []
|
||||
let currentRow: SubBlockConfig[] = []
|
||||
@@ -979,6 +987,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({
|
||||
const visibleSubBlocks = config.subBlocks.filter((block) => {
|
||||
if (block.hidden) return false
|
||||
if (block.hideFromPreview) return false
|
||||
if (hiddenByReactiveCondition.has(block.id)) return false
|
||||
if (!isSubBlockFeatureEnabled(block)) return false
|
||||
if (isSubBlockHidden(block)) return false
|
||||
|
||||
@@ -1047,6 +1056,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({
|
||||
canonicalModeOverrides,
|
||||
userPermissions.canEdit,
|
||||
canonicalIndex,
|
||||
hiddenByReactiveCondition,
|
||||
blockSubBlockValues,
|
||||
activeWorkflowId,
|
||||
])
|
||||
|
||||
62
apps/sim/hooks/use-reactive-conditions.ts
Normal file
62
apps/sim/hooks/use-reactive-conditions.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibility'
|
||||
import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useWorkspaceCredential } from '@/hooks/queries/credentials'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
|
||||
/**
|
||||
* Evaluates reactive conditions for subblocks. Always calls the same hooks
|
||||
* regardless of whether a reactive condition exists (Rules of Hooks).
|
||||
*
|
||||
* Returns a Set of subblock IDs that should be hidden.
|
||||
*/
|
||||
export function useReactiveConditions(
|
||||
subBlocks: SubBlockConfig[],
|
||||
blockId: string,
|
||||
activeWorkflowId: string | null,
|
||||
canonicalModeOverrides?: CanonicalModeOverrides
|
||||
): Set<string> {
|
||||
const reactiveSubBlock = useMemo(() => subBlocks.find((sb) => sb.reactiveCondition), [subBlocks])
|
||||
const reactiveCond = reactiveSubBlock?.reactiveCondition
|
||||
|
||||
const canonicalIndex = useMemo(() => buildCanonicalIndex(subBlocks), [subBlocks])
|
||||
|
||||
// Resolve watchFields through canonical index to get the active credential value
|
||||
const watchedCredentialId = useSubBlockStore(
|
||||
useCallback(
|
||||
(state) => {
|
||||
if (!reactiveCond || !activeWorkflowId) return ''
|
||||
const blockValues = state.workflowValues[activeWorkflowId]?.[blockId] ?? {}
|
||||
for (const field of reactiveCond.watchFields) {
|
||||
const val = resolveDependencyValue(
|
||||
field,
|
||||
blockValues,
|
||||
canonicalIndex,
|
||||
canonicalModeOverrides
|
||||
)
|
||||
if (val && typeof val === 'string') return val
|
||||
}
|
||||
return ''
|
||||
},
|
||||
[reactiveCond, activeWorkflowId, blockId, canonicalIndex, canonicalModeOverrides]
|
||||
)
|
||||
)
|
||||
|
||||
// Always call useWorkspaceCredential (stable hook count), disable when not needed
|
||||
const { data: credential } = useWorkspaceCredential(
|
||||
watchedCredentialId || undefined,
|
||||
Boolean(reactiveCond && watchedCredentialId)
|
||||
)
|
||||
|
||||
return useMemo(() => {
|
||||
const hidden = new Set<string>()
|
||||
if (!reactiveSubBlock || !reactiveCond) return hidden
|
||||
|
||||
const conditionMet = credential?.type === reactiveCond.requiredType
|
||||
if (!conditionMet) {
|
||||
hidden.add(reactiveSubBlock.id)
|
||||
}
|
||||
return hidden
|
||||
}, [reactiveSubBlock, reactiveCond, credential?.type])
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions'
|
||||
import type { BaseServerTool } from '@/lib/copilot/tools/server/base-tool'
|
||||
import { GetBlocksMetadataInput, GetBlocksMetadataResult } from '@/lib/copilot/tools/shared/schemas'
|
||||
import { getAllowedIntegrationsFromEnv, isHosted } from '@/lib/core/config/feature-flags'
|
||||
import { getServiceAccountProviderForProviderId } from '@/lib/oauth/utils'
|
||||
import { registry as blockRegistry } from '@/blocks/registry'
|
||||
import { AuthMode, type BlockConfig, isHiddenFromDisplay } from '@/blocks/types'
|
||||
import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
|
||||
@@ -342,6 +343,20 @@ function transformBlockMetadata(metadata: CopilotBlockMetadata): any {
|
||||
service: metadata.id, // e.g., 'gmail', 'slack', etc.
|
||||
description: `OAuth authentication required for ${metadata.name}`,
|
||||
}
|
||||
|
||||
// Check if this service also supports service account credentials
|
||||
const oauthSubBlock = metadata.inputSchema?.find(
|
||||
(sb: CopilotSubblockMetadata) => sb.type === 'oauth-input' && sb.serviceId
|
||||
)
|
||||
if (oauthSubBlock?.serviceId) {
|
||||
const serviceAccountProviderId = getServiceAccountProviderForProviderId(
|
||||
oauthSubBlock.serviceId
|
||||
)
|
||||
if (serviceAccountProviderId) {
|
||||
transformed.requiredCredentials.serviceAccountType = serviceAccountProviderId
|
||||
transformed.requiredCredentials.description = `OAuth or service account authentication supported for ${metadata.name}`
|
||||
}
|
||||
}
|
||||
} else if (metadata.authType === 'API Key') {
|
||||
transformed.requiredCredentials = {
|
||||
type: 'api_key',
|
||||
|
||||
Reference in New Issue
Block a user