Skip to content

Commit

Permalink
fix: Notes nodes width resets to default on page reload (#6025)
Browse files Browse the repository at this point in the history
* fix note node size

* ♻️ (NoteNode/index.tsx): remove commented out code for debounced resize handler to improve code readability and maintainability

* feat: update NoteNode to resize and persist dimensions in state

* 🐛 (generalBugs-shard-10.spec.ts): fix clicking on the last element with the text "openai" instead of the first one to match the intended behavior
✨ (generalBugs-shard-10.spec.ts): refactor test to use forEach loop for better readability and accuracy in comparing text contents

* 🐛 (general-bugs-save-changes-on-node.spec.ts): add 500ms delay to fix flakiness in tests related to clicking elements and waiting for selectors to appear

* ✨ (appHeaderComponent/index.tsx): add data-testid attribute to app header for testing purposes
📝 (general-bugs-save-changes-on-node.spec.ts): update verifyTextareaValue function to accept an additional parameter flowName for better test coverage and clarity
📝 (general-bugs-save-changes-on-node.spec.ts): add functionality to input and fill a random flow name in the test scenario for improved test coverage and reliability

---------

Co-authored-by: anovazzi1 <[email protected]>
Co-authored-by: Gabriel Luiz Freitas Almeida <[email protected]>
  • Loading branch information
3 people authored Feb 5, 2025
1 parent 974cf2e commit 8a7d7ef
Show file tree
Hide file tree
Showing 20 changed files with 2,357 additions and 4,133 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

346 changes: 123 additions & 223 deletions src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

107 changes: 91 additions & 16 deletions src/frontend/src/CustomNodes/NoteNode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ import {
NOTE_NODE_MIN_WIDTH,
} from "@/constants/constants";
import { useAlternate } from "@/shared/hooks/use-alternate";
import useFlowStore from "@/stores/flowStore";
import { NoteDataType } from "@/types/flow";
import { cn } from "@/utils/utils";
import { NodeResizer } from "@xyflow/react";
import { debounce } from "lodash";
import { useEffect, useMemo, useRef, useState } from "react";
import NodeDescription from "../GenericNode/components/NodeDescription";
import NoteToolbarComponent from "./NoteToolbarComponent";

const NOTE_NODE_PADDING = 25;
const CHAR_LIMIT = 2500;
const DEFAULT_WIDTH = 324;
const DEFAULT_HEIGHT = 324;

function NoteNode({
data,
selected,
Expand All @@ -22,16 +30,68 @@ function NoteNode({
(key) => key === data.node?.template.backgroundColor,
) ?? Object.keys(COLOR_OPTIONS)[0];
const nodeDiv = useRef<HTMLDivElement>(null);
const [size, setSize] = useState({ width: 0, height: 0 });
//tricky to start the description with the right size
const [size, setSize] = useState({
width: DEFAULT_WIDTH - NOTE_NODE_PADDING,
height: DEFAULT_HEIGHT - NOTE_NODE_PADDING,
});
const [resizedNote, setResizedNote] = useState(false);
const currentFlow = useFlowStore((state) => state.currentFlow);
const setNode = useFlowStore((state) => state.setNode);
const [isResizing, setIsResizing] = useState(false);

const nodeData = useMemo(
() => currentFlow?.data?.nodes.find((node) => node.id === data.id),
[currentFlow, data.id],
);

const nodeDataWidth = useMemo(
() => nodeData?.width ?? DEFAULT_WIDTH,
[nodeData?.width],
);
const nodeDataHeight = useMemo(
() => nodeData?.height ?? DEFAULT_HEIGHT,
[nodeData?.height],
);

const dataId = useMemo(() => data.id, [data.id]);
const dataDescription = useMemo(
() => data.node?.description,
[data.node?.description],
);

const debouncedResize = useMemo(
() =>
debounce((width: number, height: number) => {
setSize({
width: width - NOTE_NODE_PADDING,
height: height - NOTE_NODE_PADDING,
});
setNode(data.id, (node) => {
return {
...node,
width: width,
height: height,
};
});
}, 5),
[],
);

useEffect(() => {
if (nodeDiv.current) {
if (nodeData && !resizedNote && nodeDataWidth > 0 && nodeDataHeight > 0) {
setSize({
width: nodeDiv.current.offsetWidth - 25,
height: nodeDiv.current.offsetHeight - 25,
width: nodeDataWidth - NOTE_NODE_PADDING,
height: nodeDataHeight - NOTE_NODE_PADDING,
});
} else if (!nodeData && nodeDiv.current) {
const currentWidth = nodeDiv.current.offsetWidth || DEFAULT_WIDTH;
const currentHeight = nodeDiv.current.offsetHeight || DEFAULT_HEIGHT;
setSize({
width: Math.max(currentWidth, DEFAULT_WIDTH) - NOTE_NODE_PADDING,
height: Math.max(currentHeight, DEFAULT_HEIGHT) - NOTE_NODE_PADDING,
});
}
}, []);
}, [nodeData, nodeDataWidth, nodeDataHeight, resizedNote]);

const [editNameDescription, set] = useAlternate(false);

Expand All @@ -49,25 +109,35 @@ function NoteNode({
return (
<>
<NodeResizer
minWidth={NOTE_NODE_MIN_WIDTH}
minHeight={NOTE_NODE_MIN_HEIGHT}
minWidth={Math.max(DEFAULT_WIDTH, NOTE_NODE_MIN_WIDTH)}
minHeight={Math.max(DEFAULT_HEIGHT, NOTE_NODE_MIN_HEIGHT)}
onResize={(_, params) => {
const { width, height } = params;
setSize({ width: width - 25, height: height - 25 });
debouncedResize(width, height);
}}
isVisible={selected}
lineClassName="!border !border-muted-foreground"
onResizeStart={() => {
setResizedNote(true);
setIsResizing(true);
}}
onResizeEnd={() => {
setIsResizing(false);
debouncedResize.flush();
}}
/>
<div
data-testid="note_node"
style={{
minWidth: NOTE_NODE_MIN_WIDTH,
minHeight: NOTE_NODE_MIN_HEIGHT,
minWidth: Math.max(DEFAULT_WIDTH, NOTE_NODE_MIN_WIDTH),
minHeight: Math.max(DEFAULT_HEIGHT, NOTE_NODE_MIN_HEIGHT),
backgroundColor: COLOR_OPTIONS[bgColor] ?? "#00000000",
}}
ref={nodeDiv}
className={cn(
"relative flex h-full w-full flex-col gap-3 rounded-xl p-3 transition-all",
"relative flex h-full w-full flex-col gap-3 rounded-xl p-3",
"transition-all duration-200 ease-in-out",
!isResizing && "transition-transform",
COLOR_OPTIONS[bgColor] !== null &&
`border ${!selected && "-z-50 shadow-sm"}`,
)}
Expand All @@ -76,9 +146,14 @@ function NoteNode({
<div
style={{
width: size.width,
height: size.height,
height: "100%",
display: "flex",
overflow: "hidden",
}}
className={cn(
"transition-all duration-200 ease-in-out",
!isResizing && "transition-[width,height]",
)}
>
<NodeDescription
inputClassName={cn(
Expand All @@ -93,10 +168,10 @@ function NoteNode({
: "dark:!text-background",
)}
style={{ backgroundColor: COLOR_OPTIONS[bgColor] ?? "#00000000" }}
charLimit={2500}
nodeId={data.id}
charLimit={CHAR_LIMIT}
nodeId={dataId}
selected={selected}
description={data.node?.description}
description={dataDescription}
emptyPlaceholder="Double-click to start typing or enter Markdown..."
placeholderClassName={
COLOR_OPTIONS[bgColor] === null ? "" : "dark:!text-background"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ export default function AppHeader(): JSX.Element {
useResetDismissUpdateAll();

return (
<div className="flex h-[62px] w-full items-center justify-between gap-2 border-b px-5 py-2.5 dark:bg-background">
<div
className="flex h-[62px] w-full items-center justify-between gap-2 border-b px-5 py-2.5 dark:bg-background"
data-testid="app-header"
>
{/* Left Section */}
<div
className={`flex items-center gap-2`}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import { expect, Page, test } from "@playwright/test";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";

async function verifyTextareaValue(page: Page, value: string) {
async function verifyTextareaValue(
page: Page,
value: string,
flowName: string,
) {
await page
.getByTestId("textarea_str_input_value")
.waitFor({ state: "visible" });
await page.getByTestId("textarea_str_input_value").fill(value);

await expect(page.getByTestId("textarea_str_input_value")).toHaveValue(value);

await page.waitForTimeout(500);

await page.getByTestId("icon-ChevronLeft").first().click();

await page.waitForSelector('[data-testid="list-card"]', {
timeout: 5000,
state: "visible",
});

await page.getByTestId("list-card").first().click();
await page.waitForTimeout(500);
await page.getByText(flowName).first().click();

await page.waitForSelector('[data-testid="textarea_str_input_value"]', {
timeout: 5000,
state: "visible",
});

await page.waitForTimeout(500);
const inputValue = await page
.getByTestId("textarea_str_input_value")
.inputValue();
Expand All @@ -37,6 +45,8 @@ test(
Math.random().toString(36).substring(2, 8),
);

const randomFlowName = Math.random().toString(36).substring(2, 8);

await awaitBootstrapTest(page);
await page.getByTestId("blank-flow").click();

Expand All @@ -45,6 +55,12 @@ test(
state: "visible",
});

await page.getByTestId("input-flow-name").click();

await page.getByTestId("input-flow-name").fill(randomFlowName);

await page.keyboard.press("Enter");

await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("text output");

Expand All @@ -56,9 +72,11 @@ test(
state: "visible",
});

await page.getByTestId("app-header").first().click();

for (const value of randomValues) {
try {
await verifyTextareaValue(page, value);
await verifyTextareaValue(page, value, randomFlowName);
} catch (error) {
console.error(`Failed to verify value: ${value}`, error);
throw error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test(

await page.getByTestId("fit_view").click();

await page.getByText("openai").first().click();
await page.getByText("openai").last().click();
await page.keyboard.press("Delete");

//connection 1
Expand Down Expand Up @@ -94,8 +94,8 @@ test(
.getByTestId("div-chat-message")
.allTextContents();

const concatAllText2 = textContents2.join(" ");

expect(concatAllText2).toBe(concatAllText);
textContents2.forEach((text) => {
expect(text).toBe(concatAllText);
});
},
);

0 comments on commit 8a7d7ef

Please sign in to comment.