-
Notifications
You must be signed in to change notification settings - Fork 0
variable-panel done with looping gmail execution #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 🤖 Prompt for AI Agents |
||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: missing dot separator in child variable path — produces broken references like Line 142 concatenates Proposed fix onClick={() => handleClick(
node.nodeName.toLowerCase().replace(/\s+/g, '_'),
- { ...child, path: `${variable.path}${child.path}` }
+ { ...child, path: `${variable.path}.${child.path}` }
)}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| 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; | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error handling for failed executions is unreachable. When the backend returns a non-2xx status (e.g., 403 on execution failure at 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's locate and read the gmail.action.ts file
find . -name "gmail.action.ts" -type fRepository: 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.tsRepository: 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 gmailRepository: 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 fRepository: 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 -30Repository: 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 -100Repository: 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 -40Repository: 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 fRepository: Dev-Pross/BuildFlow Length of output: 162 🏁 Script executed: # Look for backend or server code
fd -e ts "server|backend|executor" . --type f | head -20Repository: Dev-Pross/BuildFlow Length of output: 205 🏁 Script executed: # Check if there's a packages or server directory
ls -la . | head -20Repository: 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 -30Repository: Dev-Pross/BuildFlow Length of output: 2751 🏁 Script executed: cat -n ./packages/nodes/src/gmail/gmail.executor.tsRepository: 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.tsRepository: Dev-Pross/BuildFlow Length of output: 3754 Fix The Gmail Correct mapping:🤖 Prompt for AI Agents |
||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The Google Sheets executor ( 🤖 Prompt for AI Agents |
||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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