fix: Prevent crash on invalid replacement components (#10110)

*  (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>
This commit is contained in:
Cristhian Zanforlin Lousa
2025-10-09 10:02:37 -03:00
committed by GitHub
parent 67960cf044
commit 4dd5bf3009
5 changed files with 106 additions and 16 deletions

View File

@ -23,6 +23,7 @@ export default function NodeLegacyComponent({
};
const foundComponents = useGetReplacementComponents(replacement);
return (
<div
className={cn(
@ -48,20 +49,27 @@ export default function NodeLegacyComponent({
</Button>
</div>
<div className="text-mmd text-muted-foreground w-full">
{replacement && Array.isArray(replacement) && replacement.length > 0 ? (
{replacement &&
Array.isArray(replacement) &&
replacement.length > 0 &&
foundComponents.some((component) => component) ? (
<span className="block items-center">
Use{" "}
{foundComponents.map((component, index) => (
<>
{index > 0 && ", "}
<Button
variant="link"
className=" !text-accent-pink-foreground !text-mmd !inline-block"
size={null}
onClick={() => handleFilterComponent(replacement[index])}
>
<span>{component}</span>
</Button>
{component && (
<>
{index > 0 && ", "}
<Button
variant="link"
className=" !text-accent-pink-foreground !text-mmd !inline-block"
size={null}
onClick={() => handleFilterComponent(replacement[index])}
>
<span>{component}</span>
</Button>
</>
)}
</>
))}
.

View File

@ -11,7 +11,7 @@ export const useGetReplacementComponents = (replacement?: string[]) => {
return (
categoryName &&
componentName &&
data[categoryName][componentName].display_name
data[categoryName]?.[componentName]?.display_name
);
})
: [];

View File

@ -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();

View File

@ -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);
},
);

View File

@ -22,7 +22,6 @@ class LocalDBComponent(LCVectorStoreComponent):
name = "LocalDB"
icon = "database"
legacy = True
replacement = ["knowledgebases.KnowledgeRetrieval", "knowledgebases.KnowledgeIngestion"]
inputs = [
TabInput(