Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ interface WorkflowNodeDetailPanelProps {
parentPath?: string[];
}

const LOGIC_NODE_DESCRIPTIONS: Record<string, string> = {
map: "Executes a node for each item in a collection. Items are processed in parallel by default.",
switch: "Routes execution based on conditions. Cases are evaluated in order; the first match wins.",
loop: "Repeats a node until a condition becomes false. The first iteration always runs; the condition is checked before subsequent iterations.",
};

/**
* WorkflowNodeDetailPanel - Shows details for the selected workflow node
* Includes input/output schemas, code view toggle, and agent information
Expand Down Expand Up @@ -268,6 +274,14 @@ const WorkflowNodeDetailPanel: React.FC<WorkflowNodeDetailPanelProps> = ({ node,
</div>
)}

{/* Description (for logic nodes) */}
{(node.type === "map" || node.type === "switch" || node.type === "loop") && LOGIC_NODE_DESCRIPTIONS[node.type] && (
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not a big deal, but could reduce the number of checks, removing LOGIC_NODE_DESCRIPTIONS[node.type] or something from the condition since we know it's there?

<div className="mb-4">
<label className="mb-1 block text-sm font-medium text-(--color-secondary-text-wMain)">Description</label>
<div className="text-sm">{LOGIC_NODE_DESCRIPTIONS[node.type]}</div>
</div>
)}

{/* Description (from agent card) */}
{agentDescription && (
<div className="mb-4">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,37 @@ const createLoopNode = (delay?: string): LayoutNode => ({
},
});

const createMapNode = (): LayoutNode => ({
id: "process_items",
type: "map",
x: 0,
y: 0,
width: 200,
height: 60,
children: [],
data: {
label: "Process Items",
items: "{{input.items}}",
},
});

const createSwitchNode = (): LayoutNode => ({
id: "route_request",
type: "switch",
x: 0,
y: 0,
width: 200,
height: 60,
children: [],
data: {
label: "Route Request",
cases: [
{ condition: "{{input.type}} == 'A'", node: "handle_a" },
{ condition: "{{input.type}} == 'B'", node: "handle_b" },
],
},
});

export const LoopNodeWithDelay: Story = {
args: {
node: createLoopNode("5s"),
Expand Down Expand Up @@ -64,8 +95,50 @@ export const LoopNodeWithoutDelay: Story = {
expect(screen.getByText("10")).toBeInTheDocument();
expect(screen.getByText("Condition")).toBeInTheDocument();

// Verify description for loop nodes is rendered
expect(screen.getByText("Repeats a node until a condition becomes false. The first iteration always runs; the condition is checked before subsequent iterations.")).toBeInTheDocument();

// Verify delay is NOT rendered
const delayLabel = screen.queryByText("Delay");
expect(delayLabel).not.toBeInTheDocument();
},
};

export const MapNode: Story = {
args: {
node: createMapNode(),
workflowConfig: null,
agents: [],
},
play: async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The pattern pre-exists in this file, but I think it would be slightly beneficial to use the canvas, like this:

    play: async ({ canvas }) => {
        // Verify node ID appears twice: once in title header, once in Node ID section
        const pollingLoopElements = canvas.getAllByText("polling_loop");

It just makes the tests slightly more efficient since they are pre-scoped to the story container (we do need screen for things like dialogs or toasts since they are outside the canvas).

// Verify node ID appears in the panel
const processItemsElements = screen.getAllByText("process_items");
expect(processItemsElements.length).toBeGreaterThanOrEqual(2);

// Verify description for map nodes is rendered
expect(screen.getByText("Executes a node for each item in a collection. Items are processed in parallel by default.")).toBeInTheDocument();

// Verify items property is rendered
expect(screen.getByText("Items")).toBeInTheDocument();
expect(screen.getByText("{{input.items}}")).toBeInTheDocument();
},
};

export const SwitchNode: Story = {
args: {
node: createSwitchNode(),
workflowConfig: null,
agents: [],
},
play: async () => {
// Verify node ID appears in the panel
const routeRequestElements = screen.getAllByText("route_request");
expect(routeRequestElements.length).toBeGreaterThanOrEqual(2);

// Verify description for switch nodes is rendered
expect(screen.getByText("Routes execution based on conditions. Cases are evaluated in order; the first match wins.")).toBeInTheDocument();

// Verify cases are rendered
expect(screen.getByText("Cases")).toBeInTheDocument();
},
};
Loading