Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions packages-answers/utils/src/extractAllCredentials.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,31 @@
import { INodeParams } from '@flowise/components'
import { CredentialInfo } from 'types'
import { getCredentialCategory } from './getCredentialCategory'

/**
* Extract all credentials (assigned and unassigned) from flow data
* Extract all credentials (assigned and unassigned) from flow data for QuickSetup mode
* @param {string|object} flowData - Flow data as JSON string or object
* @returns {CredentialInfo[]} Array of all credentials with assignment status
* @returns {object} Object containing all credentials info
*/
export const extractAllCredentials = (flowData: string | any): CredentialInfo[] => {
export const extractAllCredentials = (flowData: string | any) => {
try {
// Parse flow data if it's a string
const flow = typeof flowData === 'string' ? JSON.parse(flowData) : flowData

if (!flow.nodes || !Array.isArray(flow.nodes)) {
return []
return { allCredentials: [], hasCredentials: false }
}

const allCredentials: CredentialInfo[] = []
const allCredentials: any[] = []
const credentialTypes = new Set<string>()

// Define commonly needed optional credentials that should be offered
const importantOptionalCredentials = ['redisCacheApi', 'redisCacheUrlApi', 'upstashRedisApi', 'upstashRedisMemoryApi']

// Iterate through all nodes
flow.nodes.forEach((node: any) => {
if (node.data && node.data.inputParams) {
// Find credential input parameters (required OR important optional ones)
const credentialParams = node.data.inputParams.filter((param: INodeParams) => {
if (param.type !== 'credential') return false

// Include required credentials
if (!param.optional) return true

// Include important optional credentials
if (
param.credentialNames &&
param.credentialNames.some((name: string) => importantOptionalCredentials.includes(name))
) {
return true
}

return false
// Find all credential input parameters
const credentialParams = node.data.inputParams.filter((param: any) => {
return param.type === 'credential'
})

credentialParams.forEach((credentialParam: INodeParams) => {
credentialParams.forEach((credentialParam: any) => {
// Check if credential is assigned
const hasCredential =
node.data.credential ||
Expand All @@ -55,13 +38,27 @@ export const extractAllCredentials = (flowData: string | any): CredentialInfo[]
credentialNames.forEach((credentialName: string) => {
credentialTypes.add(credentialName)

// Get category information to determine if credential is core
const category = getCredentialCategory(node.data.category, credentialName)

// Credential is required if:
// 1. Category is core (Chat Models, Agents, etc), OR
// 2. Parameter explicitly marked as NOT optional (optional: false)
// Default to optional if not explicitly marked
const isRequired = category.isCore || (credentialParam.optional === false)
Comment on lines +41 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Treat undefined optional flags as required credentials

The new isRequired calculation only marks a credential as required when credentialParam.optional === false or the category is core. In Flowise nodes a credential parameter is required when optional is omitted (undefined) and optional only when the flag is explicitly set to true. With the current check, all non‑core credentials that leave optional undefined will be classified as optional, so the quick setup UI will let users skip API keys that are in fact mandatory and the flow will fail at runtime. Consider using credentialParam.optional !== true so undefined values still count as required.

Useful? React with πŸ‘Β / πŸ‘Ž.


const credInfo = {
nodeId: node.id,
nodeName: node.data.name || 'Unknown Node',
nodeCategory: node.data.category || 'Unknown',
nodeType: node.data.type || node.type || 'Unknown',
credentialType: credentialName,
parameterName: credentialParam.name,
label: credentialParam.label || credentialParam.name,
isOptional: !!credentialParam.optional,
isRequired: isRequired,
isCore: category.isCore,
categoryType: category.type,
categoryDisplayName: category.displayName,
isAssigned: !!hasCredential,
assignedCredentialId: hasCredential || null
}
Expand All @@ -72,9 +69,12 @@ export const extractAllCredentials = (flowData: string | any): CredentialInfo[]
}
})

return allCredentials
return {
allCredentials,
hasCredentials: credentialTypes.size > 0
}
} catch (error) {
console.error('Error in extractAllCredentials:', error)
return []
return { allCredentials: [], hasCredentials: false }
}
}
51 changes: 27 additions & 24 deletions packages-answers/utils/src/extractMissingCredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,31 @@
* Utility functions for detecting and managing missing credentials in flows
*/

import { getCredentialCategory } from './getCredentialCategory'

/**
* Extract required credentials from flow data and identify missing ones
* @param {string|object} flowData - Flow data as JSON string or object
* @returns {any[]} Array of missing credentials
* @returns {object} Object containing missing credentials info
*/
export const extractMissingCredentials = (flowData: string | any) => {
try {
// Parse flow data if it's a string
const flow = typeof flowData === 'string' ? JSON.parse(flowData) : flowData

if (!flow.nodes || !Array.isArray(flow.nodes)) {
return []
return { missingCredentials: [], hasCredentials: false }
}

const missingCredentials: any[] = []
const credentialTypes = new Set<string>()

// Define commonly needed optional credentials that should be offered
const importantOptionalCredentials = ['redisCacheApi', 'redisCacheUrlApi', 'upstashRedisApi', 'upstashRedisMemoryApi']

// Iterate through all nodes
flow.nodes.forEach((node: any, index: number) => {
flow.nodes.forEach((node: any) => {
if (node.data && node.data.inputParams) {
// Find credential input parameters (required OR important optional ones)
// Find all credential input parameters
const credentialParams = node.data.inputParams.filter((param: any) => {
if (param.type !== 'credential') return false

// Include required credentials
if (!param.optional) return true

// Include important optional credentials
if (
param.credentialNames &&
param.credentialNames.some((name: string) => importantOptionalCredentials.includes(name))
) {
return true
}

return false
return param.type === 'credential'
})

credentialParams.forEach((credentialParam: any) => {
Expand All @@ -57,13 +43,27 @@ export const extractMissingCredentials = (flowData: string | any) => {
credentialNames.forEach((credentialName: string) => {
credentialTypes.add(credentialName)

// Get category information to determine if credential is core
const category = getCredentialCategory(node.data.category, credentialName)

// Credential is required if:
// 1. Category is core (Chat Models, Agents, etc), OR
// 2. Parameter explicitly marked as NOT optional (optional: false)
// Default to optional if not explicitly marked
const isRequired = category.isCore || (credentialParam.optional === false)
Comment on lines +46 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Required credential detection uses incorrect optional check

Similar to extractAllCredentials, missing-credential detection now treats a credential as required only when credentialParam.optional === false. For many nodes the optional property is left undefined to indicate a required credential. Those credentials will now be recorded as optional, so the modal will not warn about missing mandatory credentials and users can save an unusable flow. Use credentialParam.optional !== true (or a falsy check) to continue treating undefined flags as required.

Useful? React with πŸ‘Β / πŸ‘Ž.


const missingCred = {
nodeId: node.id,
nodeName: node.data.name || 'Unknown Node',
nodeCategory: node.data.category || 'Unknown',
nodeType: node.data.type || node.type || 'Unknown',
credentialType: credentialName,
parameterName: credentialParam.name,
label: credentialParam.label || credentialParam.name,
isOptional: !!credentialParam.optional
isRequired: isRequired,
isCore: category.isCore,
categoryType: category.type,
categoryDisplayName: category.displayName
}

missingCredentials.push(missingCred)
Expand All @@ -73,9 +73,12 @@ export const extractMissingCredentials = (flowData: string | any) => {
}
})

return missingCredentials
return {
missingCredentials,
hasCredentials: credentialTypes.size > 0
}
} catch (error) {
console.error('Error in extractMissingCredentials:', error)
return []
return { missingCredentials: [], hasCredentials: false }
}
}
2 changes: 1 addition & 1 deletion packages-answers/utils/src/findSidekickById.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export async function findSidekickById(user: User, id: string) {
...chatflow,
canEdit: (chatflow.isOwner && user.permissions?.includes('chatflow:manage')) || user.permissions?.includes('org:manage')
}
const allCredentials = extractAllCredentials(chatflow.flowData)
const { allCredentials } = extractAllCredentials(chatflow.flowData)
const needsSetup = allCredentials.some((cred) => !cred.isAssigned)

return {
Expand Down
55 changes: 55 additions & 0 deletions packages-answers/utils/src/getCredentialCategory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Determine credential category based on node category and type
* @param {string} nodeCategory - Node category (e.g., "Chat Models", "Tools", "MCP Servers")
* @param {string} credentialType - Credential type name
* @returns {object} Category info with type, display name, and isCore flag
*/
export const getCredentialCategory = (nodeCategory?: string, credentialType?: string) => {
// Normalize category
const category = (nodeCategory || '').toLowerCase()
const credType = (credentialType || '').toLowerCase()

// Chat Models - always required for basic functionality
if (category.includes('chat model') || category.includes('llm')) {
return { type: 'chatModel', displayName: 'Chat Model', isCore: true }
}

// MCP Servers - optional tools
if (category.includes('mcp server') || credType.includes('mcp')) {
return { type: 'mcpServer', displayName: 'MCP Server', isCore: false }
}

// Tools - optional enhancements
if (category.includes('tool')) {
return { type: 'tool', displayName: 'Tool', isCore: false }
}

// Agents - required for agent flows
if (category.includes('agent')) {
return { type: 'agent', displayName: 'Agent', isCore: true }
}

// Chains - required for chain flows
if (category.includes('chain')) {
return { type: 'chain', displayName: 'Chain', isCore: true }
}

// Document Loaders
if (category.includes('document loader')) {
return { type: 'documentLoader', displayName: 'Document Loader', isCore: false }
}

// Vector Stores
if (category.includes('vector store')) {
return { type: 'vectorStore', displayName: 'Vector Store', isCore: false }
}

// Cache - optional but important
if (credType.includes('redis') || credType.includes('cache')) {
return { type: 'cache', displayName: 'Cache', isCore: false }
}

// Default
return { type: 'other', displayName: 'Other', isCore: false }
}

2 changes: 2 additions & 0 deletions packages/server/src/aai-utils/billing/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export const BILLING_CONFIG = {
PAGE_BATCH_SIZE: parseInt(process.env.BILLING_SYNC_PAGE_BATCH_SIZE || '3'), // Reduced from 15 to 3 pages
RATE_LIMIT_DELAY_MS: parseInt(process.env.BILLING_SYNC_RATE_LIMIT_MS || '2000'), // Increased from 1000 to 2000ms
TRACE_BATCH_SIZE: parseInt(process.env.BILLING_SYNC_TRACE_BATCH_SIZE || '5'), // Reduced from 15 to 5 traces
CHUNK_SIZE_DAYS: parseInt(process.env.BILLING_SYNC_CHUNK_SIZE_DAYS || '30'), // Time window size for historical processing
PAGE_FETCH_DELAY_MS: parseInt(process.env.BILLING_PAGE_FETCH_DELAY_MS || '500'), // Delay between sequential page fetches
MAX_RETRIES: 3,
RETRY_DELAY_MS: 1000,
EXPONENTIAL_BACKOFF: true
Expand Down
Loading
Loading