From 4dd5bf30093432d1f6efdb75f5069141cb336cd7 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Thu, 9 Oct 2025 10:02:37 -0300 Subject: [PATCH] fix: Prevent crash on invalid replacement components (#10110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ (NodeLegacyComponent): Improve rendering logic to only display components with valid replacements 🐛 (use-get-replacement-components): Update data access to handle potential undefined values 📝 (general-bugs-frontend-crashing-on-invalid-replace.spec.ts): Add test case for handling undefined replacements in custom components ♻️ (local_db.py): Remove unnecessary replacement values from LocalDBComponent * ✅ (fileUploadComponent.spec.ts): update test for file types visibility to use async/await syntax for better readability and maintainability * ✨ (userSettings.spec.ts): refactor tests to use a helper function awaitBootstrapTest for code reusability and to skip modal popups before interacting with the page. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: Carlos Coelho <80289056+carlosrcoelho@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../components/NodeLegacyComponent/index.tsx | 28 ++++--- .../hooks/use-get-replacement-components.ts | 2 +- .../extended/features/userSettings.spec.ts | 16 +++- ...ontend-crashing-on-invalid-replace.spec.ts | 75 +++++++++++++++++++ .../lfx/components/vectorstores/local_db.py | 1 - 5 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 src/frontend/tests/extended/regression/general-bugs-frontend-crashing-on-invalid-replace.spec.ts diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeLegacyComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeLegacyComponent/index.tsx index c1710765aa..03c49e6bc1 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeLegacyComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeLegacyComponent/index.tsx @@ -23,6 +23,7 @@ export default function NodeLegacyComponent({ }; const foundComponents = useGetReplacementComponents(replacement); + return (
- {replacement && Array.isArray(replacement) && replacement.length > 0 ? ( + {replacement && + Array.isArray(replacement) && + replacement.length > 0 && + foundComponents.some((component) => component) ? ( Use{" "} {foundComponents.map((component, index) => ( <> - {index > 0 && ", "} - + {component && ( + <> + {index > 0 && ", "} + + + )} ))} . diff --git a/src/frontend/src/CustomNodes/GenericNode/hooks/use-get-replacement-components.ts b/src/frontend/src/CustomNodes/GenericNode/hooks/use-get-replacement-components.ts index efbe7c52de..1e28572802 100644 --- a/src/frontend/src/CustomNodes/GenericNode/hooks/use-get-replacement-components.ts +++ b/src/frontend/src/CustomNodes/GenericNode/hooks/use-get-replacement-components.ts @@ -11,7 +11,7 @@ export const useGetReplacementComponents = (replacement?: string[]) => { return ( categoryName && componentName && - data[categoryName][componentName].display_name + data[categoryName]?.[componentName]?.display_name ); }) : []; diff --git a/src/frontend/tests/extended/features/userSettings.spec.ts b/src/frontend/tests/extended/features/userSettings.spec.ts index 9d71da385d..a68ef71b46 100644 --- a/src/frontend/tests/extended/features/userSettings.spec.ts +++ b/src/frontend/tests/extended/features/userSettings.spec.ts @@ -14,7 +14,9 @@ test( { tag: ["@release"] }, async ({ page }) => { - await page.goto("/"); + await awaitBootstrapTest(page, { + skipModal: true, + }); await page.waitForSelector('[data-testid="mainpage_title"]', { timeout: 30000, }); @@ -42,7 +44,9 @@ test( const randomName2 = Math.random().toString(36).substring(2); const randomName3 = Math.random().toString(36).substring(2); - await page.goto("/"); + await awaitBootstrapTest(page, { + skipModal: true, + }); await page.getByTestId("user-profile-settings").click(); await page.getByText("Settings").click(); await page.getByText("Global Variables").click(); @@ -134,7 +138,9 @@ test( ); test("should see shortcuts", { tag: ["@release"] }, async ({ page }) => { - await page.goto("/"); + await awaitBootstrapTest(page, { + skipModal: true, + }); await page.waitForSelector('[data-testid="mainpage_title"]', { timeout: 30000, }); @@ -172,7 +178,9 @@ test( "should interact with API Keys", { tag: ["@release", "@api"] }, async ({ page }) => { - await page.goto("/"); + await awaitBootstrapTest(page, { + skipModal: true, + }); await page.getByTestId("user-profile-settings").click(); await page.getByText("Settings").click(); await page.getByText("Langflow API").first().click(); diff --git a/src/frontend/tests/extended/regression/general-bugs-frontend-crashing-on-invalid-replace.spec.ts b/src/frontend/tests/extended/regression/general-bugs-frontend-crashing-on-invalid-replace.spec.ts new file mode 100644 index 0000000000..aac55443f2 --- /dev/null +++ b/src/frontend/tests/extended/regression/general-bugs-frontend-crashing-on-invalid-replace.spec.ts @@ -0,0 +1,75 @@ +import { expect, test } from "../../fixtures"; +import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; + +test( + "user must be able to use a component with undefined replacement", + { + tag: ["@release"], + }, + async ({ page }) => { + await awaitBootstrapTest(page); + + await page.waitForSelector('[data-testid="blank-flow"]', { + timeout: 30000, + }); + + await page.getByTestId("blank-flow").click(); + + await page.getByTestId("sidebar-custom-component-button").click(); + + await page.getByTestId("title-Custom Component").click(); + + await page.getByTestId("code-button-modal").last().click(); + + const problematicCode = ` +# from lfx.field_typing import Data +from lfx.custom.custom_component.component import Component +from lfx.io import MessageTextInput, Output +from lfx.schema.data import Data + + +class CustomComponent(Component): + display_name = "Custom Component" + description = "Use as a template to create your own component." + documentation: str = "https://docs.langflow.org/components-custom-components" + icon = "code" + name = "CustomComponent" + replacement = ["knowledgebases.KnowledgeRetrieval", "knowledgebases.KnowledgeIngestion", "THISISNOTEXISTING.COMPONENT"] # This line was causing the crash + inputs = [ + MessageTextInput( + name="input_value", + display_name="Input Value", + info="This is a custom component Input", + value="Hello, World!", + tool_mode=True, + ), + ] + + outputs = [ + Output(display_name="Output", name="output", method="build_output"), + ] + + def build_output(self) -> Data: + data = Data(value=self.input_value) + self.status = data + return data + + `; + + await page.locator(".ace_content").click(); + await page.keyboard.press(`ControlOrMeta+A`); + await page.locator("textarea").fill(problematicCode); + + await page.getByText("Check & Save").last().click(); + + await page.waitForTimeout(1000); + await page.waitForSelector("text=No direct replacement", { + timeout: 30000, + }); + + const numberOfDirectReplacementText = await page + .getByText("No direct replacement") + .count(); + expect(numberOfDirectReplacementText).toBe(1); + }, +); diff --git a/src/lfx/src/lfx/components/vectorstores/local_db.py b/src/lfx/src/lfx/components/vectorstores/local_db.py index 8ed50b36f9..3245d70920 100644 --- a/src/lfx/src/lfx/components/vectorstores/local_db.py +++ b/src/lfx/src/lfx/components/vectorstores/local_db.py @@ -22,7 +22,6 @@ class LocalDBComponent(LCVectorStoreComponent): name = "LocalDB" icon = "database" legacy = True - replacement = ["knowledgebases.KnowledgeRetrieval", "knowledgebases.KnowledgeIngestion"] inputs = [ TabInput(