diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index 95534f38a..efce7df2e 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -1,6 +1,6 @@ import { usePostValidateCode } from "@/controllers/API/queries/nodes/use-post-validate-code"; import { usePostValidateComponentCode } from "@/controllers/API/queries/nodes/use-post-validate-component-code"; -import useFlowStore from "@/stores/flowStore"; +import { clearHandlesFromAdvancedFields } from "@/utils/reactflowUtils"; import "ace-builds/src-noconflict/ace"; import "ace-builds/src-noconflict/ext-language_tools"; import "ace-builds/src-noconflict/ext-searchbox"; @@ -83,7 +83,6 @@ export default function CodeAreaModal({ }); setOpen(false); setValue(code); - // setValue(code); } else { if (funcErrors.length !== 0) { setErrorData({ @@ -121,6 +120,8 @@ export default function CodeAreaModal({ onSuccess: ({ data, type }) => { if (data && type) { setValue(code); + clearHandlesFromAdvancedFields(componentId!, data); + setNodeClass(data, type); setError({ detail: { error: undefined, traceback: undefined } }); setOpen(false); @@ -255,6 +256,7 @@ export default function CodeAreaModal({ type="submit" id="checkAndSaveBtn" disabled={readonly} + data-testid="checkAndSaveBtn" > Check & Save diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 0c0a2cddb..ee38c8012 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -176,6 +176,45 @@ export function cleanEdges(nodes: AllNodeType[], edges: EdgeType[]) { return newEdges; } +export function clearHandlesFromAdvancedFields( + componentId: string, + data: APIClassType, +): void { + if (!componentId || !data?.template) { + return; + } + + try { + const flowStore = useFlowStore.getState(); + const { edges, deleteEdge } = flowStore; + + const connectedEdges = edges.filter((edge) => edge.target === componentId); + + if (connectedEdges.length === 0) { + return; + } + + const edgeIdsToDelete: string[] = []; + + for (const edge of connectedEdges) { + const fieldName = edge.data?.targetHandle?.fieldName; + + if (fieldName && isAdvancedField(data, fieldName)) { + edgeIdsToDelete.push(edge.id); + } + } + + edgeIdsToDelete.forEach(deleteEdge); + } catch (error) { + console.error("Error clearing handles from advanced fields:", error); + } +} + +const isAdvancedField = (data: APIClassType, fieldName: string): boolean => { + const field = data.template[fieldName]; + return field && "advanced" in field && field.advanced === true; +}; + export function filterHiddenFieldsEdges( edge: EdgeType, newEdges: EdgeType[], diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index c1c38a068..a6658f4e3 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -229,7 +229,6 @@ export const SIDEBAR_CATEGORIES = [ ]; export const SIDEBAR_BUNDLES = [ - { display_name: "Outlook", name: "outlook", icon: "Outlook" }, { display_name: "Language Models", name: "languagemodels", diff --git a/src/frontend/tests/extended/regression/general-bugs-delete-handle-advanced-input.spec.ts b/src/frontend/tests/extended/regression/general-bugs-delete-handle-advanced-input.spec.ts new file mode 100644 index 000000000..10dcf3341 --- /dev/null +++ b/src/frontend/tests/extended/regression/general-bugs-delete-handle-advanced-input.spec.ts @@ -0,0 +1,89 @@ +import { expect, test } from "@playwright/test"; +import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { extractAndCleanCode } from "../../utils/extract-and-clean-code"; +import { zoomOut } from "../../utils/zoom-out"; + +test( + "the system must delete the handles from advanced fields when the code is updated", + { tag: ["@release"] }, + async ({ page }) => { + await awaitBootstrapTest(page); + + await page.getByTestId("blank-flow").click(); + + await page.waitForSelector('[data-testid="fit_view"]', { + timeout: 100000, + }); + await page.getByTestId("sidebar-search-input").click(); + await page.getByTestId("sidebar-search-input").fill("if else"); + + await page + .getByTestId("logicIf-Else") + .hover() + .then(async () => { + await page.getByTestId("add-component-button-if-else").click(); + }); + + await page.getByTestId("fit_view").click(); + await zoomOut(page, 3); + + await page.getByTestId("edit-button-modal").click(); + + await page.getByTestId("showmessage").click(); + + await page.getByText("Close").last().click(); + + await page.getByTestId("sidebar-search-input").click(); + await page.getByTestId("sidebar-search-input").fill("text input"); + await page.waitForSelector('[data-testid="input_outputText Input"]', { + timeout: 2000, + }); + await page + .getByTestId("input_outputText Input") + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 200, y: 100 }, + }); + + await page + .getByTestId("handle-textinput-shownode-output text-right") + .click(); + + await page + .getByTestId("handle-conditionalrouter-shownode-alternative output-left") + .click(); + + await page.getByTestId("title-If-Else").click(); + + await page.getByTestId("edit-button-modal").click(); + + const numberOfDisabledInputs = await page + .getByPlaceholder("Receiving input") + .count(); + + expect(numberOfDisabledInputs).toBe(2); + + const numberOfLockIcons = await page.getByTestId("icon-lock").count(); + + expect(numberOfLockIcons).toBe(2); + + await page.getByText("Close").last().click(); + + await page.getByTestId("title-If-Else").click(); + + await page.getByTestId("code-button-modal").click(); + + await page.getByTestId("checkAndSaveBtn").last().click(); + + await page.getByTestId("edit-button-modal").click(); + + const numberOfDisabledInputsAfter = await page + .getByPlaceholder("Receiving input") + .count(); + + expect(numberOfDisabledInputsAfter).toBe(0); + + const numberOfLockIconsAfter = await page.getByTestId("icon-lock").count(); + + expect(numberOfLockIconsAfter).toBe(0); + }, +);