Add service account credential block prompting for google service account

This commit is contained in:
Theodore Li
2026-04-04 18:54:37 -07:00
parent bef786cd5b
commit fac34dc658
4 changed files with 75 additions and 63 deletions

View File

@@ -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.

View File

@@ -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/panel'
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,
])

View 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])
}

View File

@@ -5,12 +5,12 @@ 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'
import { PROVIDER_DEFINITIONS } from '@/providers/models'
import { tools as toolsRegistry } from '@/tools/registry'
import { getServiceAccountProviderForProviderId } from '@/lib/oauth/utils'
import { getTrigger, isTriggerValid } from '@/triggers'
import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants'
@@ -354,8 +354,7 @@ function transformBlockMetadata(metadata: CopilotBlockMetadata): any {
const serviceAccountProviderId = getServiceAccountProviderForProviderId(serviceId)
if (serviceAccountProviderId) {
transformed.requiredCredentials.serviceAccountType = serviceAccountProviderId
transformed.requiredCredentials.description =
`OAuth or service account authentication supported for ${metadata.name}`
transformed.requiredCredentials.description = `OAuth or service account authentication supported for ${metadata.name}`
}
} else if (metadata.authType === 'API Key') {
transformed.requiredCredentials = {