Skip to content

feat: Implement Gmail node executor and introduce workflow testing an…#75

Merged
Vamsi-o merged 1 commit into
mainfrom
config-model
Mar 17, 2026
Merged

feat: Implement Gmail node executor and introduce workflow testing an…#75
Vamsi-o merged 1 commit into
mainfrom
config-model

Conversation

@TejaBudumuru3

@TejaBudumuru3 TejaBudumuru3 commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

…d configuration UI components.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added test panel for displaying node test outputs in multiple formats, including spreadsheet tables and JSON views
    • Enabled on-demand testing of individual workflow nodes directly from the configuration interface
  • Improvements

    • Enhanced email send response with structured metadata including message ID, thread ID, and status
    • Improved algorithm for identifying related workflow nodes in your workflow configuration

@TejaBudumuru3 TejaBudumuru3 requested a review from Vamsi-o as a code owner March 16, 2026 16:56
@coderabbitai

coderabbitai Bot commented Mar 16, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A testing feature is introduced that allows users to run and display test results for workflow nodes. A new TestPanel component displays formatted test outputs, VariablePanel gains testing capabilities, ConfigModal integrates the testing workflow with result storage, and the Gmail executor's success output is restructured to include metadata.

Changes

Cohort / File(s) Summary
Testing UI Components
apps/web/app/components/ui/TestPanel.tsx, apps/web/app/components/ui/variable-panel.tsx
New TestPanel component renders test results in multiple formats (spreadsheet tables, object tables, JSON). VariablePanel enhanced with onTestNode callback, accordion state management, and "Test Node" button for triggering node tests with loading/retake states.
Testing Workflow Integration
apps/web/app/workflows/[id]/components/ConfigModal.tsx, apps/web/app/workflows/[id]/page.tsx
ConfigModal integrates TestPanel and adds handleTestPreviousNode to execute nodes on-demand with interpolation context, store results in Redux, and display errors via toast. Page.tsx updates node gathering from direct edges to backward BFS for ancestor collection and adjusts test invocation for action nodes.
Gmail Executor Updates
packages/nodes/src/gmail/gmail.executor.ts
Success output structure changed to return organized metadata (status, messageId, threadId, recipient, subject, summary) instead of raw result.data.
Build Configuration
.gitignore, packages/nodes/tsconfig.tsbuildinfo
Added tsconfig.tsbuildinfo to .gitignore; deleted the build artifact file.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ConfigModal
    participant VariablePanel
    participant Workflow Engine
    participant Redux Store
    participant TestPanel

    User->>ConfigModal: Selects node to test
    User->>VariablePanel: Clicks "Test Node" button
    VariablePanel->>ConfigModal: onTestNode(nodeId)
    ConfigModal->>Workflow Engine: handleTestPreviousNode:<br/>resolve config + execute node
    Workflow Engine-->>ConfigModal: testResult + metadata
    ConfigModal->>Redux Store: Store testResult<br/>(nodeName, nodeIcon, output)
    ConfigModal->>TestPanel: Render with testResult
    TestPanel->>TestPanel: Route rendering logic:<br/>strings → preformatted<br/>arrays → tables<br/>objects → formatted display
    TestPanel-->>User: Display formatted test output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Vamsi-o

Poem

🐰 A testing suite hops into view,
With panels displaying what nodes can do—
Results rendered fine, in tables or text,
Our workflows now tested, what comes next?
bounces excitedly

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title adequately summarizes the main changes: implementing Gmail node executor and introducing workflow testing components. However, it is truncated and incomplete.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch config-model
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can use your project's `biome` configuration to improve the quality of JS/TS/CSS/JSON code reviews.

Add a configuration file to your project to customize how CodeRabbit runs biome.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/app/components/ui/TestPanel.tsx`:
- Around line 51-80: renderSpreadsheetTable currently maps every returned row
which can freeze the UI for large Sheets results; limit the preview by slicing
rows before rendering (e.g., keep headers plus at most N data rows) or implement
virtualization. Specifically, in renderSpreadsheetTable, compute a boundedRows
or previewRows from rows (use the first row as headers and slice dataRows to a
MAX_PREVIEW_ROWS constant consistent with VariablePanel /
renderArrayOfObjectsTable), then use those bounded values in the table rendering
and optionally show a count/truncation indicator.

In `@apps/web/app/components/ui/variable-panel.tsx`:
- Around line 80-98: The interactive mapping headers/cells (the div using
activeField, isTested, formattedNodeName, strictConcatPath and invoking
handleInsert) are currently mouse-only; replace the non-focusable divs/ths/tds
with semantic <button> elements where possible or, if structural constraints
prevent that, add tabIndex=0, role="button", aria-disabled when !isTested or
!activeField, and an onKeyDown handler that invokes the same insertion/expansion
logic when Enter or Space is pressed (call the same handleInsert logic and
stopPropagation). Ensure the title text and visual disabled styling remain, and
apply the same changes to the other similar blocks referenced (around the usages
at lines you noted) so keyboard users can focus and activate these controls.
- Around line 80-97: The UI currently blocks insertion whenever isTested is
false, which prevents schema-backed variables from being inserted; change the
click/hover/title checks to allow insertion when the node has a schema fallback
(e.g., variables or outputSchema present) even if not isTested. Concretely,
compute a boolean like hasSchemaFallback = Boolean(node.variables?.length ||
node.outputSchema) and replace occurrences of checks that require isTested (used
with activeField, handleInsert, and the title logic around
formattedNodeName/strictConcatPath) with (isTested || hasSchemaFallback); apply
the same change to the other similar conditional at the referenced block (lines
~403-405) so schema-backed rows remain insertable for untested nodes.
- Around line 249-279: The drag-and-drop and click handlers build dot-access
expressions like `{{${formattedNodeName}.map(item => item.${header})}}` which
break for keys that are not valid identifiers; update the construction in the
onDragStart handler and the onClick/handleInsert call to use bracket notation
when needed (e.g., use item[headerString] or `${formattedNodeName}.map(item =>
item[${quotedHeader}])`) by testing header against a JS identifier regex and
quoting/escaping it when using bracket form; adjust the title string generation
similarly so it shows a valid expression; touch the same logic around the other
similar block (the block referenced at lines ~298-309) so both drag click and
title use bracket notation for non-identifier keys.

In `@apps/web/app/workflows/`[id]/components/ConfigModal.tsx:
- Around line 42-47: The useEffect that currently only clears testResult when
selectedNode?.id changes should also reset other node-scoped modal state (clear
activeField and dynamicOptions) so no stale field or options from the previous
node remain; locate the state variables testResult, activeField, dynamicOptions
and update the effect that depends on selectedNode?.id to set them all to their
initial values, and change TestPanel usage to derive its displayed status from
nodeTestOutput (from Redux) instead of local testResult so the panel reflects
Redux updates correctly.
- Around line 433-435: The option value expression in ConfigModal's render
(inside options.map) is using incorrect precedence so items with both value and
id pick id; change the expression used for the <option value=...> to use a
nullish/coalescing chain (e.g., prefer opt.value, then opt.id, then the raw opt)
so the saved config matches the selected option; update the value expression in
the options.map callback in ConfigModal.tsx accordingly.

In `@apps/web/app/workflows/`[id]/page.tsx:
- Line 617: The test callbacks currently pass stale identifier properties (e.g.,
Trigger.TriggerId / node.NodeId) which breaks after rehydration; update the
callbacks that call testNodeFromCanvas to derive a single canonical testId
(e.g., const testId = actionId ?? action.id ?? trigger.id ?? node.id) and pass
that testId into testNodeFromCanvas (instead of Trigger.TriggerId/node.NodeId).
Locate all occurrences where testNodeFromCanvas is invoked (including the
existing onTest: ()=> testNodeFromCanvas(actionId, action.name, "action") usage
and the other callbacks that reference Trigger.TriggerId or node.NodeId) and
replace them to use the canonical testId so reopened workflows use the correct
id path.

In `@packages/nodes/src/gmail/gmail.executor.ts`:
- Around line 60-71: The current return always sets output.status to "sent" even
when result.success is false; update the return so output is only populated on
success (using result.success) and on failure either omit output or set a
failure-aware object (e.g., status: "failed" plus error details). Locate the
return block that references result.success, output, messageId, threadId, to,
and subject in gmail.executor.ts and wrap the output population behind a
conditional: when result.success === true include the sent metadata (status:
"sent", messageId, threadId, to, subject, summary), otherwise set output to null
or a small failure object containing status: "failed" and result.error to avoid
misleading downstream nodes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e916fe66-4469-4578-a003-461976dbe061

📥 Commits

Reviewing files that changed from the base of the PR and between cd56ebc and e44cc91.

📒 Files selected for processing (7)
  • .gitignore
  • apps/web/app/components/ui/TestPanel.tsx
  • apps/web/app/components/ui/variable-panel.tsx
  • apps/web/app/workflows/[id]/components/ConfigModal.tsx
  • apps/web/app/workflows/[id]/page.tsx
  • packages/nodes/src/gmail/gmail.executor.ts
  • packages/nodes/tsconfig.tsbuildinfo
💤 Files with no reviewable changes (1)
  • packages/nodes/tsconfig.tsbuildinfo

Comment on lines +51 to +80
const renderSpreadsheetTable = (rows: any[][]) => {
if (rows.length === 0) return <EmptyState message="No rows in response" />;
const headers = rows[0] as string[];
const dataRows = rows.slice(1);

return (
<div className="w-full border border-[#2a2f3e] rounded-lg overflow-hidden overflow-x-auto">
<table className="w-full text-left text-xs text-gray-300 min-w-max border-collapse">
<thead className="bg-[#161b22] sticky top-0">
<tr>
<th className="px-3 py-2.5 w-10 text-center border-r border-b border-[#2a2f3e] text-gray-500 font-normal text-[10px]">#</th>
{headers.map((h, i) => (
<th key={i} className="px-4 py-2.5 border-r border-b border-[#2a2f3e] font-medium text-blue-300 truncate max-w-[160px]">
{h || `Column ${i + 1}`}
</th>
))}
</tr>
</thead>
<tbody>
{dataRows.map((row, ri) => (
<tr key={ri} className="border-b border-[#1a1f2e]/50 hover:bg-[#1f2536] transition-colors">
<td className="px-3 py-2 text-center border-r border-[#1a1f2e]/50 text-gray-600 bg-[#161b26] text-[10px]">{ri + 1}</td>
{headers.map((_, ci) => (
<td key={ci} className="px-4 py-2 border-r border-[#1a1f2e]/50 truncate max-w-[200px] text-gray-300">
{String(row[ci] ?? '')}
</td>
))}
</tr>
))}
</tbody>

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

Limit spreadsheet previews before rendering.

renderSpreadsheetTable maps every returned row, unlike renderArrayOfObjectsTable and the preview limits in VariablePanel. A large Sheets test result can lock the modal as soon as it opens; slice or virtualize this branch before rendering.

♻️ Suggested fix
-        const dataRows = rows.slice(1);
+        const PREVIEW_ROW_LIMIT = 100;
+        const dataRows = rows.slice(1, PREVIEW_ROW_LIMIT + 1);
📝 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
const renderSpreadsheetTable = (rows: any[][]) => {
if (rows.length === 0) return <EmptyState message="No rows in response" />;
const headers = rows[0] as string[];
const dataRows = rows.slice(1);
return (
<div className="w-full border border-[#2a2f3e] rounded-lg overflow-hidden overflow-x-auto">
<table className="w-full text-left text-xs text-gray-300 min-w-max border-collapse">
<thead className="bg-[#161b22] sticky top-0">
<tr>
<th className="px-3 py-2.5 w-10 text-center border-r border-b border-[#2a2f3e] text-gray-500 font-normal text-[10px]">#</th>
{headers.map((h, i) => (
<th key={i} className="px-4 py-2.5 border-r border-b border-[#2a2f3e] font-medium text-blue-300 truncate max-w-[160px]">
{h || `Column ${i + 1}`}
</th>
))}
</tr>
</thead>
<tbody>
{dataRows.map((row, ri) => (
<tr key={ri} className="border-b border-[#1a1f2e]/50 hover:bg-[#1f2536] transition-colors">
<td className="px-3 py-2 text-center border-r border-[#1a1f2e]/50 text-gray-600 bg-[#161b26] text-[10px]">{ri + 1}</td>
{headers.map((_, ci) => (
<td key={ci} className="px-4 py-2 border-r border-[#1a1f2e]/50 truncate max-w-[200px] text-gray-300">
{String(row[ci] ?? '')}
</td>
))}
</tr>
))}
</tbody>
const renderSpreadsheetTable = (rows: any[][]) => {
if (rows.length === 0) return <EmptyState message="No rows in response" />;
const headers = rows[0] as string[];
const PREVIEW_ROW_LIMIT = 100;
const dataRows = rows.slice(1, PREVIEW_ROW_LIMIT + 1);
return (
<div className="w-full border border-[`#2a2f3e`] rounded-lg overflow-hidden overflow-x-auto">
<table className="w-full text-left text-xs text-gray-300 min-w-max border-collapse">
<thead className="bg-[`#161b22`] sticky top-0">
<tr>
<th className="px-3 py-2.5 w-10 text-center border-r border-b border-[`#2a2f3e`] text-gray-500 font-normal text-[10px]">#</th>
{headers.map((h, i) => (
<th key={i} className="px-4 py-2.5 border-r border-b border-[`#2a2f3e`] font-medium text-blue-300 truncate max-w-[160px]">
{h || `Column ${i + 1}`}
</th>
))}
</tr>
</thead>
<tbody>
{dataRows.map((row, ri) => (
<tr key={ri} className="border-b border-[`#1a1f2e`]/50 hover:bg-[`#1f2536`] transition-colors">
<td className="px-3 py-2 text-center border-r border-[`#1a1f2e`]/50 text-gray-600 bg-[`#161b26`] text-[10px]">{ri + 1}</td>
{headers.map((_, ci) => (
<td key={ci} className="px-4 py-2 border-r border-[`#1a1f2e`]/50 truncate max-w-[200px] text-gray-300">
{String(row[ci] ?? '')}
</td>
))}
</tr>
))}
</tbody>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/components/ui/TestPanel.tsx` around lines 51 - 80,
renderSpreadsheetTable currently maps every returned row which can freeze the UI
for large Sheets results; limit the preview by slicing rows before rendering
(e.g., keep headers plus at most N data rows) or implement virtualization.
Specifically, in renderSpreadsheetTable, compute a boundedRows or previewRows
from rows (use the first row as headers and slice dataRows to a MAX_PREVIEW_ROWS
constant consistent with VariablePanel / renderArrayOfObjectsTable), then use
those bounded values in the table rendering and optionally show a
count/truncation indicator.

Comment on lines +80 to +98
<div
className={`
flex items-center group transition-colors border-b border-[#1a1f2e]/50
${activeField && isTested ? "hover:bg-[#202737] cursor-pointer" : "cursor-not-allowed opacity-50"}
`}
onClick={(e) => {
e.stopPropagation();
if (activeField && isTested) {
handleInsert(`{{${formattedNodeName}.${strictConcatPath}}}`);
}
}}
title={
!isTested
? "Test node first to map data"
: activeField
? `Insert {{${formattedNodeName}.${strictConcatPath}}}`
: "Select a field first"
}
>

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

The new mapping interactions are mouse-only.

These div/th/td click targets are not focusable and have no keyboard equivalent, so keyboard users cannot expand nodes or insert variables. Please use real buttons where possible, or add tabIndex, role, and onKeyDown handling to the interactive headers and cells.

Also applies to: 165-170, 183-189, 249-279, 298-309, 380-385

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/components/ui/variable-panel.tsx` around lines 80 - 98, The
interactive mapping headers/cells (the div using activeField, isTested,
formattedNodeName, strictConcatPath and invoking handleInsert) are currently
mouse-only; replace the non-focusable divs/ths/tds with semantic <button>
elements where possible or, if structural constraints prevent that, add
tabIndex=0, role="button", aria-disabled when !isTested or !activeField, and an
onKeyDown handler that invokes the same insertion/expansion logic when Enter or
Space is pressed (call the same handleInsert logic and stopPropagation). Ensure
the title text and visual disabled styling remain, and apply the same changes to
the other similar blocks referenced (around the usages at lines you noted) so
keyboard users can focus and activate these controls.

Comment on lines +80 to +97
<div
className={`
flex items-center group transition-colors border-b border-[#1a1f2e]/50
${activeField && isTested ? "hover:bg-[#202737] cursor-pointer" : "cursor-not-allowed opacity-50"}
`}
onClick={(e) => {
e.stopPropagation();
if (activeField && isTested) {
handleInsert(`{{${formattedNodeName}.${strictConcatPath}}}`);
}
}}
title={
!isTested
? "Test node first to map data"
: activeField
? `Insert {{${formattedNodeName}.${strictConcatPath}}}`
: "Select a field first"
}

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

Keep schema-backed variables insertable.

previousNodes still arrives with variables populated from each node's static outputSchema, and this card now says "Using sample schema", but the new isTested checks make every row read-only until the node has been executed once. That turns the schema fallback into dead UI for untested nodes. Please only require testing when a node has no schema to insert from.

Also applies to: 403-405

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/components/ui/variable-panel.tsx` around lines 80 - 97, The UI
currently blocks insertion whenever isTested is false, which prevents
schema-backed variables from being inserted; change the click/hover/title checks
to allow insertion when the node has a schema fallback (e.g., variables or
outputSchema present) even if not isTested. Concretely, compute a boolean like
hasSchemaFallback = Boolean(node.variables?.length || node.outputSchema) and
replace occurrences of checks that require isTested (used with activeField,
handleInsert, and the title logic around formattedNodeName/strictConcatPath)
with (isTested || hasSchemaFallback); apply the same change to the other similar
conditional at the referenced block (lines ~403-405) so schema-backed rows
remain insertable for untested nodes.

Comment on lines +249 to +279
draggable={true}
onDragStart={(e) => {
const variableText = `{{${formattedNodeName}.map(item => item.${header})}}`;

e.dataTransfer.setData('text/plain', variableText);

e.dataTransfer.setData('application/buildflow-variable', JSON.stringify({
nodeName: formattedNodeName,
path: header,
display: `${formattedNodeName}.${header}`
}))

e.dataTransfer.effectAllowed = 'copy';

}}
key={header}
className={`px-3 py-2 border-r border-b border-[#2a2f3e] font-medium text-gray-400 truncate max-w-[150px] transition-colors
${activeField && isTested ? "hover:bg-blue-500/20 hover:text-blue-300 cursor-grab active:cursor-grabbing" : "cursor-not-allowed opacity-50"}
`}
onClick={() => {
if (activeField && isTested) {
handleInsert(`{{${formattedNodeName}.map(item => item.${header})}}`);
}
}}
title={
!isTested
? "Test node first to map data"
: activeField
? `Insert entire column as array: {{${formattedNodeName}.map(item => item.${header})}}`
: "Select a field first"
}

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

Use bracket notation for non-identifier keys.

These templates assume every object key is dot-safe. Keys like message-id, from name, or 1stValue will generate invalid expressions such as item.message-id or {{node[0].from name}}, so users end up inserting broken variables. Build the accessor with bracket notation when the key is not a valid identifier.

♻️ Suggested fix
                   onDragStart={(e) => {
-                    const variableText = `{{${formattedNodeName}.map(item => item.${header})}}`;
+                    const accessor = /^[A-Za-z_$][\w$]*$/.test(header)
+                      ? `.${header}`
+                      : `[${JSON.stringify(header)}]`;
+                    const variableText = `{{${formattedNodeName}.map(item => item${accessor})}}`;
@@
                       onClick={() => {
                         if (activeField && isTested) {
-                          handleInsert(`{{${formattedNodeName}[${rowIndex}].${header}}}`);
+                          const accessor = /^[A-Za-z_$][\w$]*$/.test(header)
+                            ? `.${header}`
+                            : `[${JSON.stringify(header)}]`;
+                          handleInsert(`{{${formattedNodeName}[${rowIndex}]${accessor}}}`);
                         }
                       }}

Also applies to: 298-309

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/components/ui/variable-panel.tsx` around lines 249 - 279, The
drag-and-drop and click handlers build dot-access expressions like
`{{${formattedNodeName}.map(item => item.${header})}}` which break for keys that
are not valid identifiers; update the construction in the onDragStart handler
and the onClick/handleInsert call to use bracket notation when needed (e.g., use
item[headerString] or `${formattedNodeName}.map(item => item[${quotedHeader}])`)
by testing header against a JS identifier regex and quoting/escaping it when
using bracket form; adjust the title string generation similarly so it shows a
valid expression; touch the same logic around the other similar block (the block
referenced at lines ~298-309) so both drag click and title use bracket notation
for non-identifier keys.

Comment on lines 42 to +47
const [testResult, setTestResult] = useState<any>(null);


// Reset test result when switching to a different node
useEffect(() => {
setTestResult(null);
}, [selectedNode?.id]);

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

Reset all node-scoped modal state together.

Only testResult is cleared when selectedNode changes. activeField and dynamicOptions survive the switch, so variable insertion can still target a field from the previous node, while TestPanel ignores the nodeTestOutput already in Redux and falls back to "Not tested yet". Reopening a tested node—or failing a retest that only updates Redux—can therefore show the wrong state. I'd derive the panel from nodeTestOutput and reset the other node-specific state here as well.

Also applies to: 56-62, 685-685

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/workflows/`[id]/components/ConfigModal.tsx around lines 42 - 47,
The useEffect that currently only clears testResult when selectedNode?.id
changes should also reset other node-scoped modal state (clear activeField and
dynamicOptions) so no stale field or options from the previous node remain;
locate the state variables testResult, activeField, dynamicOptions and update
the effect that depends on selectedNode?.id to set them all to their initial
values, and change TestPanel usage to derive its displayed status from
nodeTestOutput (from Redux) instead of local testResult so the panel reflects
Redux updates correctly.

Comment on lines 433 to 435
{options.map((opt: any) => (
<option key={opt.value || opt.id || opt} value={opt.value || opt.id !== undefined ? opt.id : opt }>
<option key={opt.value || opt.id || opt} value={opt.value || opt.id !== undefined ? opt.id : opt}>
{opt.label || opt.name || opt}

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:

find . -type f -name "ConfigModal.tsx" | head -5

Repository: Dev-Pross/BuildFlow

Length of output: 119


🏁 Script executed:

if [ -f "apps/web/app/workflows/[id]/components/ConfigModal.tsx" ]; then
  wc -l "apps/web/app/workflows/[id]/components/ConfigModal.tsx"
else
  fd ConfigModal.tsx
fi

Repository: Dev-Pross/BuildFlow

Length of output: 121


🏁 Script executed:

# Check the exact lines and surrounding context
sed -n '430,440p' apps/web/app/workflows/[id]/components/ConfigModal.tsx

Repository: Dev-Pross/BuildFlow

Length of output: 467


🏁 Script executed:

node <<'EOF'
// Test the actual parsing of: opt.value || opt.id !== undefined ? opt.id : opt

// Case 1: opt.value is defined, opt.id is also defined
let opt = { value: "A", id: "B" };
let result1 = opt.value || opt.id !== undefined ? opt.id : opt;
console.log("Case 1 - both defined:");
console.log("  opt.value:", opt.value);
console.log("  opt.id:", opt.id);
console.log("  Result:", result1);
console.log("  Expected (value):", opt.value);
console.log("  Match:", result1 === opt.value ? "✓" : "✗ BUG");

// Case 2: opt.value is undefined, opt.id is defined
opt = { value: undefined, id: "B" };
result1 = opt.value || opt.id !== undefined ? opt.id : opt;
console.log("\nCase 2 - only id defined:");
console.log("  opt.value:", opt.value);
console.log("  opt.id:", opt.id);
console.log("  Result:", result1);
console.log("  Expected (id):", opt.id);
console.log("  Match:", result1 === opt.id ? "✓" : "✗");

// Now test the suggested fix
console.log("\n--- Testing suggested fix: opt?.value ?? opt?.id ?? opt ---");

opt = { value: "A", id: "B" };
let result2 = opt?.value ?? opt?.id ?? opt;
console.log("Case 1 - both defined:");
console.log("  Result:", result2);
console.log("  Expected (value):", opt.value);
console.log("  Match:", result2 === opt.value ? "✓" : "✗");

opt = { value: undefined, id: "B" };
result2 = opt?.value ?? opt?.id ?? opt;
console.log("\nCase 2 - only id defined:");
console.log("  Result:", result2);
console.log("  Expected (id):", opt.id);
console.log("  Match:", result2 === opt.id ? "✓" : "✗");
EOF

Repository: Dev-Pross/BuildFlow

Length of output: 463


Fix the dropdown option value expression.

The expression opt.value || opt.id !== undefined ? opt.id : opt is parsed as (opt.value || opt.id !== undefined) ? opt.id : opt due to operator precedence. This causes an option with both value and id to submit opt.id instead of opt.value. Use a nullish coalescing chain to ensure the saved config matches the selected option:

{options.map((opt: any) => {
  const optionValue = opt?.value ?? opt?.id ?? opt;
  return (
    <option key={String(optionValue)} value={optionValue}>
      {opt.label || opt.name || opt}
    </option>
  );
})}
📝 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
{options.map((opt: any) => (
<option key={opt.value || opt.id || opt} value={opt.value || opt.id !== undefined ? opt.id : opt }>
<option key={opt.value || opt.id || opt} value={opt.value || opt.id !== undefined ? opt.id : opt}>
{opt.label || opt.name || opt}
{options.map((opt: any) => {
const optionValue = opt?.value ?? opt?.id ?? opt;
return (
<option key={String(optionValue)} value={optionValue}>
{opt.label || opt.name || opt}
</option>
);
})}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/workflows/`[id]/components/ConfigModal.tsx around lines 433 -
435, The option value expression in ConfigModal's render (inside options.map) is
using incorrect precedence so items with both value and id pick id; change the
expression used for the <option value=...> to use a nullish/coalescing chain
(e.g., prefer opt.value, then opt.id, then the raw opt) so the saved config
matches the selected option; update the value expression in the options.map
callback in ConfigModal.tsx accordingly.

icon: action.icon
}),
onTest: ()=> testNodeFromCanvas(action.NodeId, action.name, "action")
onTest: ()=> testNodeFromCanvas(actionId, action.name, "action")

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

Carry the corrected test id through the rehydration paths too.

This callback is correct for freshly created actions, but the callbacks around Line 391, Line 417, and Line 709 still call testNodeFromCanvas with Trigger.TriggerId / node.NodeId on objects that are otherwise read via id. Fresh nodes will test fine; reopened workflows can still hit api.execute.node(undefined, ...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/workflows/`[id]/page.tsx at line 617, The test callbacks
currently pass stale identifier properties (e.g., Trigger.TriggerId /
node.NodeId) which breaks after rehydration; update the callbacks that call
testNodeFromCanvas to derive a single canonical testId (e.g., const testId =
actionId ?? action.id ?? trigger.id ?? node.id) and pass that testId into
testNodeFromCanvas (instead of Trigger.TriggerId/node.NodeId). Locate all
occurrences where testNodeFromCanvas is invoked (including the existing onTest:
()=> testNodeFromCanvas(actionId, action.name, "action") usage and the other
callbacks that reference Trigger.TriggerId or node.NodeId) and replace them to
use the canonical testId so reopened workflows use the correct id path.

Comment on lines 60 to 71
return {
success: result.success,
output: result.data,
output: {
status: "sent",
messageId: result.data?.id,
threadId: result.data?.threadId,
to: to,
subject: subject,
summary: `Email sent to ${to} with subject "${subject}"`,
},
error: result.error,
};

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

status: "sent" is returned even when the email fails to send.

When result.success is false, the output still contains status: "sent", which is misleading for downstream nodes and logging. The structured output should either be omitted on failure or reflect the actual status.

🐛 Proposed fix to conditionally set output on success
       return {
         success: result.success,
-        output: {
-          status: "sent",
-          messageId: result.data?.id,
-          threadId: result.data?.threadId,
-          to: to,
-          subject: subject,
-          summary: `Email sent to ${to} with subject "${subject}"`,
-        },
+        output: result.success
+          ? {
+              status: "sent",
+              messageId: result.data?.id,
+              threadId: result.data?.threadId,
+              to: to,
+              subject: subject,
+              summary: `Email sent to ${to} with subject "${subject}"`,
+            }
+          : undefined,
         error: result.error,
       };
📝 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
return {
success: result.success,
output: result.data,
output: {
status: "sent",
messageId: result.data?.id,
threadId: result.data?.threadId,
to: to,
subject: subject,
summary: `Email sent to ${to} with subject "${subject}"`,
},
error: result.error,
};
return {
success: result.success,
output: result.success
? {
status: "sent",
messageId: result.data?.id,
threadId: result.data?.threadId,
to: to,
subject: subject,
summary: `Email sent to ${to} with subject "${subject}"`,
}
: undefined,
error: result.error,
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nodes/src/gmail/gmail.executor.ts` around lines 60 - 71, The current
return always sets output.status to "sent" even when result.success is false;
update the return so output is only populated on success (using result.success)
and on failure either omit output or set a failure-aware object (e.g., status:
"failed" plus error details). Locate the return block that references
result.success, output, messageId, threadId, to, and subject in
gmail.executor.ts and wrap the output population behind a conditional: when
result.success === true include the sent metadata (status: "sent", messageId,
threadId, to, subject, summary), otherwise set output to null or a small failure
object containing status: "failed" and result.error to avoid misleading
downstream nodes.

@Vamsi-o Vamsi-o left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Potential issue | 🟠 Major

Limit spreadsheet previews before rendering.

renderSpreadsheetTable maps every returned row, unlike renderArrayOfObjectsTable and the preview limits in VariablePanel. A large Sheets test result can lock the modal as soon as it opens; slice or virtualize this branch before rendering

@Vamsi-o Vamsi-o merged commit 01079dd into main Mar 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants