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
8 changes: 5 additions & 3 deletions apps/http-backend/src/routes/userRoutes/executionRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ execRouter.post('/node', userMiddleware, async(req: AuthRequest, res: Response)
const type = nodeData.AvailableNode.type
const config = dataSafe.data.Config ? dataSafe.data.Config : nodeData.config // for test api data prefered fist then config in db
console.log(`config and type: ${JSON.stringify(config)} & ${type}`)
// if(nodeData.CredentialsID)

const context = {
userId: req.user.sub,
config: config ,
// credentialsId: nodeData.CredentialsID || ""
config: config,
credentialId: nodeData.CredentialsID || config?.credentialId || ""
}

console.log(`Execution context: ${JSON.stringify(context)}`)
Comment on lines +36 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid logging credential identifiers in production.

Line 40 logs the full execution context including credentialId. This is fine for development but should be removed or guarded behind a debug flag before shipping to production to avoid leaking internal identifiers in logs.

🤖 Prompt for AI Agents
In `@apps/http-backend/src/routes/userRoutes/executionRoutes.ts` around lines 36 -
40, The console.log that prints the full execution context (including
credentialId derived from nodeData.CredentialsID / config?.credentialId) should
not leak identifiers in production; either remove the console.log in
executionRoutes.ts or replace it with a guarded debug-level logger (check an
isDebug/isProd flag or logger.level) and ensure credentialId is redacted before
logging (e.g., omit or mask context.credentialId) so sensitive identifiers are
never emitted in non-debug environments.

const executionResult = await ExecutionRegister.execute(type, context)

console.log(`Execution result: ${executionResult}`)
Expand Down
180 changes: 180 additions & 0 deletions apps/web/app/components/ui/variable-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"use client";

import { PreviousNodeOutput, VariableDefinition } from "@/app/lib/types/node.types";
import { useAppSelector } from "@/app/hooks/redux";
import { selectAllOutputs, NodeTestOutput } from "@/store/slices/nodeOutputSlice";

interface VariablePanelProps {
previousNodes: PreviousNodeOutput[];
onInsert: (variableSyntax: string) => void;
activeField: string | null;
}

export function VariablePanel({ previousNodes, onInsert, activeField }: VariablePanelProps) {
// Get tested outputs from Redux
const testedOutputs = useAppSelector(selectAllOutputs);

// Debug logging
console.log('[VariablePanel] previousNodes:', previousNodes);
console.log('[VariablePanel] testedOutputs from Redux:', testedOutputs);

// Merge static outputSchema with dynamic tested data
const enrichedNodes: PreviousNodeOutput[] = previousNodes.map(node => {
const testedData = testedOutputs[node.nodeId];

console.log(`[VariablePanel] Node ${node.nodeName} (${node.nodeId}):`, {
hasTestedData: !!testedData,
testedSuccess: testedData?.success,
testedVariablesCount: testedData?.variables?.length,
staticVariablesCount: node.variables?.length
});

// If node was tested, use the dynamic variables from real output
if (testedData?.success && testedData.variables.length > 0) {
console.log(`[VariablePanel] Using DYNAMIC variables for ${node.nodeName}:`, testedData.variables);
return {
...node,
variables: testedData.variables,
// Mark as dynamically discovered
_tested: true
} as PreviousNodeOutput & { _tested?: boolean };
}

// Otherwise use the static outputSchema
console.log(`[VariablePanel] Using STATIC variables for ${node.nodeName}:`, node.variables);
Comment on lines +17 to +44

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove verbose debug logging.

Lines 18–19, 25–30, 34, and 44 log every render's previousNodes, per-node tested data details, and full variable lists to the console. This is noisy in development and leaks internal data structure details in production.

🤖 Prompt for AI Agents
In `@apps/web/app/components/ui/variable-panel.tsx` around lines 17 - 44, Remove
the verbose console.log calls in the VariablePanel render/processing flow:
delete the top-level logs of previousNodes and testedOutputs and the per-node
logs inside the enrichedNodes mapping (the logs that print testedData details
and full variable lists), or guard them behind a debug flag (e.g., only log when
process.env.NODE_ENV === 'development' or a dedicated isDebug prop) so
VariablePanel no longer emits sensitive/internal structures; update the
enrichedNodes mapping that uses previousNodes, testedOutputs,
PreviousNodeOutput, and the _tested marker accordingly to avoid leaving stray
debug statements.

return node;
});

const handleClick = (nodeName: string, variable: VariableDefinition) => {
// Build the variable syntax: {{nodeName.path}}
const syntax = `{{${nodeName}.${variable.path}}}`;
onInsert(syntax);
};

if (enrichedNodes.length === 0) {
return (
<div className="w-64 bg-gray-900 border-r border-gray-800 p-4 text-gray-400 text-sm">
<p>No previous nodes available.</p>
<p className="mt-2 text-xs">Add a trigger or action before this node to use its output data.</p>
</div>
);
}

return (
<div className="w-64 bg-gray-900 border-r border-gray-800 p-4 space-y-4 overflow-y-auto max-h-[80vh]">
<div className="text-sm font-medium text-white mb-2">
📦 Available Variables
</div>

{!activeField && (
<div className="text-xs text-yellow-400 bg-yellow-400/10 p-2 rounded">
Click on a field first, then click a variable to insert it.
</div>
)}

{enrichedNodes.map((node) => {
const isTested = testedOutputs[node.nodeId]?.success;

return (
<div key={node.nodeId} className="space-y-2">
{/* Node Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-sm font-medium text-gray-300">
<span>{node.icon || "⚙️"}</span>
<span>{node.nodeName}</span>
</div>
{isTested && (
<span className="text-xs bg-green-500/20 text-green-400 px-2 py-0.5 rounded">
✓ Tested
</span>
)}
</div>

{/* No variables message */}
{node.variables.length === 0 && (
<p className="text-xs text-gray-500 italic">
Test this node to discover available variables
</p>
)}

{/* Variable Pills */}
<div className="flex flex-wrap gap-2">
{node.variables.map((variable) => (
<button
key={variable.path}
onClick={() => handleClick(node.nodeName.toLowerCase().replace(/\s+/g, '_'), variable)}
disabled={!activeField}
className={`
px-3 py-1.5 rounded-full text-xs font-medium
transition-all duration-150
${activeField
? isTested
? "bg-green-500/20 text-green-300 hover:bg-green-500/40 cursor-pointer border border-green-500/30"
: "bg-blue-500/20 text-blue-300 hover:bg-blue-500/40 cursor-pointer border border-blue-500/30"
: "bg-gray-700/50 text-gray-500 cursor-not-allowed border border-gray-600/30"
}
`}
title={`${variable.sampleValue ? `Sample: ${variable.sampleValue}\n` : ''}Insert {{${node.nodeName.toLowerCase().replace(/\s+/g, '_')}.${variable.path}}}`}
>
<span className="mr-1">{getTypeIcon(variable.type)}</span>
{variable.name}
{variable.sampleValue && (
<span className="ml-1 text-gray-500 max-w-16 truncate">
({String(variable.sampleValue).substring(0, 15)}{String(variable.sampleValue).length > 15 ? '...' : ''})
</span>
)}
</button>
))}
</div>

{/* Render nested children if any */}
{node.variables
.filter(v => v.children && v.children.length > 0)
.map(variable => (
<div key={`${variable.path}-children`} className="ml-4 mt-1">
<div className="text-xs text-gray-500 mb-1">└ {variable.name} fields:</div>
<div className="flex flex-wrap gap-1">
{variable.children?.map(child => (
<button
key={child.path}
onClick={() => handleClick(
node.nodeName.toLowerCase().replace(/\s+/g, '_'),
{ ...child, path: `${variable.path}${child.path}` }
)}
Comment on lines +140 to +143

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: missing dot separator in child variable path — produces broken references like rows[0]email.

Line 142 concatenates variable.path and child.path without a . between them. For a parent path "rows[0]" and child path "email", the resulting interpolation reference becomes {{node.rows[0]email}} instead of {{node.rows[0].email}}, which will fail to resolve at runtime.

Proposed fix
                      onClick={() => handleClick(
                        node.nodeName.toLowerCase().replace(/\s+/g, '_'), 
-                        { ...child, path: `${variable.path}${child.path}` }
+                        { ...child, path: `${variable.path}.${child.path}` }
                      )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onClick={() => handleClick(
node.nodeName.toLowerCase().replace(/\s+/g, '_'),
{ ...child, path: `${variable.path}${child.path}` }
)}
onClick={() => handleClick(
node.nodeName.toLowerCase().replace(/\s+/g, '_'),
{ ...child, path: `${variable.path}.${child.path}` }
)}
🤖 Prompt for AI Agents
In `@apps/web/app/components/ui/variable-panel.tsx` around lines 140 - 143, The
click handler builds a child variable path by naively concatenating
variable.path and child.path (in the onClick that calls handleClick), which
produces broken references like "rows[0]email"; fix by inserting a dot between
parent and child when both are present and not already separated — e.g. compute
a combinedPath (for use in the handleClick call) that uses child.path when
variable.path is empty, otherwise uses `${variable.path}.${child.path}` while
avoiding duplicate dots if either side already contains a trailing/leading dot;
update the onClick invocation that currently passes `{ ...child, path:
`${variable.path}${child.path}` }` to use this combinedPath.

disabled={!activeField}
className={`
px-2 py-1 rounded text-xs
${activeField
? "bg-purple-500/20 text-purple-300 hover:bg-purple-500/40 cursor-pointer"
: "bg-gray-700/50 text-gray-500 cursor-not-allowed"
}
`}
>
{child.name}
</button>
))}
</div>
</div>
))}
</div>
);
})}
</div>
);
}

// Helper function for type icons
function getTypeIcon(type: string): string {
const icons: Record<string, string> = {
string: "📝",
number: "🔢",
boolean: "✓",
date: "📅",
array: "📋",
object: "{}",
any: "•",
};
return icons[type] || "•";
}

export default VariablePanel;
22 changes: 22 additions & 0 deletions apps/web/app/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,27 @@ export const api = {
name: tab.name
}))
},
},
execute: {
// Execute a single node for testing
node: async (nodeId: string, config?: any) => {
const res = await axios.post(`${BACKEND_URL}/execute/node`,
{ NodeId: nodeId, Config: config },
{
withCredentials: true,
headers: { "Content-Type": "application/json" },
}
);
console.log('[api.execute.node] Response:', res);

// Check if execution was successful
if (res.data?.data?.success) {
return res.data.data.output;
}

// If not successful, throw error with message
const errorMessage = res.data?.data?.error || res.data?.message || "Execution failed";
throw new Error(errorMessage);
}
Comment on lines +123 to +141

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Error handling for failed executions is unreachable.

When the backend returns a non-2xx status (e.g., 403 on execution failure at executionRoutes.ts line 51), axios throws before reaching line 134. The res.data?.data?.success check and the custom error at line 140 are effectively dead code for the failure path. You'd need a try/catch around the axios call (or use validateStatus) to handle the backend's structured error response.

Proposed fix
     node: async (nodeId: string, config?: any) => {
-      const res = await axios.post(`${BACKEND_URL}/execute/node`, 
-        { NodeId: nodeId, Config: config },
-        {
-          withCredentials: true,
-          headers: { "Content-Type": "application/json" },
-        }
-      );
-      console.log('[api.execute.node] Response:', res);
-      
-      // Check if execution was successful
-      if (res.data?.data?.success) {
-        return res.data.data.output;
+      try {
+        const res = await axios.post(`${BACKEND_URL}/execute/node`, 
+          { NodeId: nodeId, Config: config },
+          {
+            withCredentials: true,
+            headers: { "Content-Type": "application/json" },
+          }
+        );
+        
+        if (res.data?.data?.success) {
+          return res.data.data.output;
+        }
+        
+        throw new Error(res.data?.data?.error || res.data?.message || "Execution failed");
+      } catch (error: any) {
+        const message = error.response?.data?.message 
+          || error.response?.data?.data?.error 
+          || error.message 
+          || "Execution failed";
+        throw new Error(message);
       }
-      
-      // If not successful, throw error with message
-      const errorMessage = res.data?.data?.error || res.data?.message || "Execution failed";
-      throw new Error(errorMessage);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
node: async (nodeId: string, config?: any) => {
const res = await axios.post(`${BACKEND_URL}/execute/node`,
{ NodeId: nodeId, Config: config },
{
withCredentials: true,
headers: { "Content-Type": "application/json" },
}
);
console.log('[api.execute.node] Response:', res);
// Check if execution was successful
if (res.data?.data?.success) {
return res.data.data.output;
}
// If not successful, throw error with message
const errorMessage = res.data?.data?.error || res.data?.message || "Execution failed";
throw new Error(errorMessage);
}
node: async (nodeId: string, config?: any) => {
try {
const res = await axios.post(`${BACKEND_URL}/execute/node`,
{ NodeId: nodeId, Config: config },
{
withCredentials: true,
headers: { "Content-Type": "application/json" },
}
);
if (res.data?.data?.success) {
return res.data.data.output;
}
throw new Error(res.data?.data?.error || res.data?.message || "Execution failed");
} catch (error: any) {
const message = error.response?.data?.message
|| error.response?.data?.data?.error
|| error.message
|| "Execution failed";
throw new Error(message);
}
}
🤖 Prompt for AI Agents
In `@apps/web/app/lib/api.ts` around lines 123 - 141, The axios.post call inside
the node async function currently throws on non-2xx responses so the subsequent
res.data?.data?.success check and custom error are never reached; wrap the axios
call in a try/catch (or use axios validateStatus to treat the backend's
structured error responses as resolved) and in the catch extract the backend
payload (res?.response?.data or the error response body) to build and throw the
same custom Error used when res.data indicates failure, ensuring you still log
the response (console.log('[api.execute.node] Response:', ...)) and return
res.data.data.output when success.

}
};
8 changes: 7 additions & 1 deletion apps/web/app/lib/nodeConfigs/gmail.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,11 @@ export const gmailActionConfig: NodeConfig = {
],

summary: "Send emails via Gmail", // ✅ Correct description
helpUrl: "https://docs.example.com/gmail-action"
helpUrl: "https://docs.example.com/gmail-action",

outputSchema: [
{ name: "Message ID", path: "messageId", type: "string" },
{ name: "Status", path: "status", type: "string" },
{ name: "Thread ID", path: "threadId", type: "string" },
],
Comment on lines +51 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and read the gmail.action.ts file
find . -name "gmail.action.ts" -type f

Repository: Dev-Pross/BuildFlow

Length of output: 109


🏁 Script executed:

# Search for gmail-related files
fd -e ts "gmail" apps/web/app/lib/

Repository: Dev-Pross/BuildFlow

Length of output: 107


🏁 Script executed:

# Also check the executor to understand what output is being returned
rg "sendEmail" -A 10 -B 2 apps/web/app/lib/

Repository: Dev-Pross/BuildFlow

Length of output: 45


🏁 Script executed:

cat -n ./apps/web/app/lib/nodeConfigs/gmail.action.ts

Repository: Dev-Pross/BuildFlow

Length of output: 2102


🏁 Script executed:

# Search for the executor or service that processes Gmail
fd -e ts "gmail" apps/web/app/lib/ --exec cat -n {}

Repository: Dev-Pross/BuildFlow

Length of output: 2102


🏁 Script executed:

# Search for executor files related to gmail
fd -e ts "executor" apps/web/app/lib/ | grep -i gmail

Repository: Dev-Pross/BuildFlow

Length of output: 45


🏁 Script executed:

# Search more broadly for where this action is executed
rg "gmailActionConfig" -A 5 -B 5 apps/web/app/lib/

Repository: Dev-Pross/BuildFlow

Length of output: 2415


🏁 Script executed:

# Look for sendEmail implementation
rg "sendEmail|gmailService" -A 10 apps/web/app/lib/

Repository: Dev-Pross/BuildFlow

Length of output: 45


🏁 Script executed:

# Find files that might handle action execution
fd -e ts "action|executor" apps/web/app/lib/ --type f

Repository: Dev-Pross/BuildFlow

Length of output: 158


🏁 Script executed:

# Look for Google API or Gmail service integration
fd -e ts "" apps/web/app/lib/ | head -30

Repository: Dev-Pross/BuildFlow

Length of output: 305


🏁 Script executed:

# Search for how output is generated for actions
rg "outputSchema" -B 5 -A 5 apps/web/app/

Repository: Dev-Pross/BuildFlow

Length of output: 5069


🏁 Script executed:

cat -n apps/web/app/lib/api.ts | head -100

Repository: Dev-Pross/BuildFlow

Length of output: 4142


🏁 Script executed:

# Search for where Gmail service or Google API is used
rg "google|gmail|Gmail" -i apps/web/app/ --type ts --type tsx | head -40

Repository: Dev-Pross/BuildFlow

Length of output: 90


🏁 Script executed:

# Look for any API handlers or routes that might execute actions
fd -e ts "route|handler|api" apps/web/app/ --type f

Repository: Dev-Pross/BuildFlow

Length of output: 162


🏁 Script executed:

# Look for backend or server code
fd -e ts "server|backend|executor" . --type f | head -20

Repository: Dev-Pross/BuildFlow

Length of output: 205


🏁 Script executed:

# Check if there's a packages or server directory
ls -la . | head -20

Repository: Dev-Pross/BuildFlow

Length of output: 1241


🏁 Script executed:

# Search the entire repo for Gmail API calls or sendEmail
rg "sendEmail|users\.messages\.send|gmail" -i --type ts | head -30

Repository: Dev-Pross/BuildFlow

Length of output: 2751


🏁 Script executed:

cat -n ./packages/nodes/src/gmail/gmail.executor.ts

Repository: Dev-Pross/BuildFlow

Length of output: 2567


🏁 Script executed:

# Also check the gmail service to understand the API call
cat -n ./packages/nodes/src/gmail/gmail.service.ts

Repository: Dev-Pross/BuildFlow

Length of output: 3754


Fix outputSchema paths to match Gmail API response fields.

The Gmail users.messages.send() API returns { id, threadId, labelIds }. The outputSchema paths are incorrect: messageId should be id, and the status field does not exist in the Gmail API response. Users referencing these variables will encounter undefined values.

Correct mapping:
outputSchema: [
  { name: "Message ID", path: "id", type: "string" },
  { name: "Thread ID", path: "threadId", type: "string" },
],
🤖 Prompt for AI Agents
In `@apps/web/app/lib/nodeConfigs/gmail.action.ts` around lines 51 - 55, The
outputSchema in gmail.action.ts is using incorrect Gmail API field names: update
the outputSchema array (symbol: outputSchema) to map the Gmail response fields
returned by users.messages.send() by replacing the "messageId" path with "id"
and removing the non-existent "status" entry so that only valid fields like "id"
and "threadId" are returned (i.e., keep entries for "Message ID" -> path "id"
and "Thread ID" -> path "threadId").

};
17 changes: 16 additions & 1 deletion apps/web/app/lib/nodeConfigs/googleSheet.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,20 @@ export const googleSheetActionConfig: NodeConfig = {
],

summary: "Interact with Google Sheets spreadsheets",
helpUrl: "https://docs.example.com/google-sheets-action"
helpUrl: "https://docs.example.com/google-sheets-action",

outputSchema: [
{
name: "Rows",
path: "rows",
type: "array",
description: "All rows from the sheet",
children: [
{ name: "Row Index", path: "[*].index", type: "number" },
// Dynamic columns added at runtime based on sheet headers
]
},
{ name: "Row Count", path: "rowCount", type: "number" },
{ name: "Sheet Name", path: "sheetName", type: "string" },
],
Comment on lines +61 to +74

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

outputSchema path "sheetName" doesn't match executor output.

The Google Sheets executor (google-sheets.executor.ts, lines 276-281) returns sheetId (which is actually the spreadsheetId), not sheetName. The schema should align with the actual output keys, otherwise variable interpolation will resolve sheetName to undefined.

🤖 Prompt for AI Agents
In `@apps/web/app/lib/nodeConfigs/googleSheet.action.ts` around lines 61 - 74, The
outputSchema in googleSheet.action.ts lists a "Sheet Name" path of "sheetName"
but the executor (google-sheets.executor.ts) returns sheetId (the spreadsheetId)
instead; update the schema entry in outputSchema so the path matches the
executor output (e.g., change the path from "sheetName" to "sheetId" or
"spreadsheetId" and update the name/description accordingly) to ensure variable
interpolation resolves correctly—verify the exact key used in
google-sheets.executor.ts and make the schema match that key.

};
26 changes: 26 additions & 0 deletions apps/web/app/lib/types/node.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
// What information does a node config need?

export type VariableType = 'string' | 'number' | 'boolean' | 'date' | 'array' | 'object' | 'any';

export interface VariableDefinition {
name: string; // "Email Address"
path: string; // "rows[0].email"
type: VariableType;
description?: string;
sampleValue?: any; // "john@example.com"
children?: VariableDefinition[]; // For nested objects/arrays
}
// What gets passed to VariablePanel
export interface PreviousNodeOutput {
nodeId: string; // "node_abc123"
nodeName: string; // "Google Sheets"
nodeType: string; // "google_sheet"
icon?: string; // "📊"
variables: VariableDefinition[];
}
export interface NodeConfig {
id: string; // Unique identifier for the node, e.g., "google_sheet"
type: "trigger" | "action"; // Node category
Expand All @@ -19,6 +37,10 @@ export interface NodeConfig {
tags?: string[]; // Searchable tags
// Any extra raw config data
data?: Record<string, any>; // Allow extra arbitrary config as needed
// NEW: What this node outputs (for next nodes to use)
outputSchema?: VariableDefinition[];
// NEW: Sample output for UI preview
sampleOutput?: Record<string, any>;
}

export interface ConfigField {
Expand All @@ -37,4 +59,8 @@ export interface ConfigField {
min?: number; // For number fields: min value
max?: number; // For number fields: max value
// You could add validation, inputMask, or other metadata as needed here
// NEW: Allow variable insertion
acceptsVariables?: boolean; // default true for text/textarea
// NEW: Restrict which variable types can be dropped
acceptTypes?: VariableType[];
}
Loading