From 466a18c7449598b453576e45f578ee3435fbdec2 Mon Sep 17 00:00:00 2001 From: Deon Sanchez <69873175+deon-sanchez@users.noreply.github.com> Date: Tue, 1 Jul 2025 07:07:48 -0600 Subject: [PATCH] fix: enhance dropdown component with refresh button and clean up parameter render logic (#8493) * fix: enhance dropdown component with refresh button and clean up parameter render logic - Added a refresh button to the dropdown component, improving user interaction. - Refactored parameter render component to remove unnecessary wrapping around the render function. - Updated package-lock.json to remove extraneous dependencies. * [autofix.ci] apply automated fixes * refactor(OutputComponent): replace DropdownMenu with Popover and Command components - Updated OutputComponent to use Popover and Command components for improved UI interaction. - Refactored dropdown logic to enhance accessibility and user experience. - Added a reference for the button to manage focus visibility. * refactor: update Memory Chatbot configuration and remove unused RefreshParameterComponent - Changed display names and output methods in Memory Chatbot JSON configuration for clarity and consistency. - Introduced a new output method for retrieving messages as text. - Removed the RefreshParameterComponent and its references from the parameter render component to streamline the codebase. * refactor: update dropdown component layout for improved styling - Changed the layout classes in the dropdown component to enhance responsiveness and visual consistency. - Adjusted flex properties to ensure proper alignment and spacing based on the presence of filtered metadata. * refactor: streamline dropdown component structure and enhance button functionality - Removed redundant rendering functions for refresh and custom option dialogs, integrating them directly into the dropdown's main structure. - Improved layout and styling for better responsiveness and visual consistency. - Adjusted class names for better alignment and spacing, particularly in relation to filtered metadata. - Ensured the refresh button is consistently displayed based on dialog input conditions. * refactor: enhance dropdown component styling for better readability - Updated text size in dropdown options for improved visibility. - Increased padding in command items for better touch targets and visual consistency. * refactor: adjust dropdown component styling for improved usability - Reduced padding in the search input for a more compact design. - Updated text size in the search input for better readability. - Enhanced layout of filtered metadata display for clearer visibility. * refactor: enhance dropdown component rendering and styling - Added console log for filtered metadata to assist in debugging. - Adjusted class names in dropdown options for better responsiveness based on filtered metadata length. * feat: add data-testid attributes for refresh buttons and simplify memoization in ParameterRenderComponent - Added data-testid attributes to refresh buttons in Dropdown component for improved testability. - Removed unnecessary useMemo in ParameterRenderComponent to streamline rendering logic. - Introduced a new test for the refresh dropdown list functionality to ensure proper behavior. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../components/OutputComponent/index.tsx | 79 ++++--- .../core/dropdownComponent/index.tsx | 192 ++++++++++-------- .../refreshParameterComponent/index.tsx | 74 ------- .../core/parameterRenderComponent/index.tsx | 18 +- .../features/refresh-dropdown-list.spec.ts | 43 ++++ 5 files changed, 204 insertions(+), 202 deletions(-) delete mode 100644 src/frontend/src/components/core/parameterRenderComponent/components/refreshParameterComponent/index.tsx create mode 100644 src/frontend/tests/extended/features/refresh-dropdown-list.spec.ts diff --git a/src/frontend/src/CustomNodes/GenericNode/components/OutputComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/OutputComponent/index.tsx index 3b9fbdbd7..3bc98ab02 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/OutputComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/OutputComponent/index.tsx @@ -1,12 +1,19 @@ import { ForwardedIconComponent } from "@/components/common/genericIconComponent"; import { Button } from "@/components/ui/button"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; + Command, + CommandGroup, + CommandItem, + CommandList, +} from "@/components/ui/command"; + +import { + Popover, + PopoverContentWithoutPortal, + PopoverTrigger, +} from "@/components/ui/popover"; import useFlowStore from "@/stores/flowStore"; +import { useRef } from "react"; import ShadTooltip from "../../../../components/common/shadTooltipComponent"; import { outputComponentType } from "../../../../types/components"; import { cn } from "../../../../utils/utils"; @@ -56,6 +63,7 @@ export default function OutputComponent({ const hasGroupOutputs = outputs?.some?.((output) => output.group_outputs); const isConditionalRouter = nodeType === "ConditionalRouter"; const hasOutputs = outputs.length > 1; + const refButton = useRef(null); const shouldShowDropdown = hasOutputs && !hasLoopOutput && !hasGroupOutputs && !isConditionalRouter; @@ -63,11 +71,13 @@ export default function OutputComponent({ return (
{shouldShowDropdown ? ( - - + + - - - {outputs.map((output) => ( - { - handleSelectOutput && handleSelectOutput(output); - }} - > - - {output.display_name ?? output.name} - - - {output.types.join(", ")} - - - ))} - - + + + + + + {outputs.map((output) => ( + { + handleSelectOutput && handleSelectOutput(output); + }} + value={output.name} + > + + {output.display_name ?? output.name} + + + {output.types.join(", ")} + + + ))} + + + + + ) : ( singleOutput )} diff --git a/src/frontend/src/components/core/dropdownComponent/index.tsx b/src/frontend/src/components/core/dropdownComponent/index.tsx index 880dc06a4..9f16b73a7 100644 --- a/src/frontend/src/components/core/dropdownComponent/index.tsx +++ b/src/frontend/src/components/core/dropdownComponent/index.tsx @@ -96,7 +96,7 @@ export default function Dropdown({ const fuse = new Fuse(validOptions, { keys: ["name", "value"] }); const PopoverContentDropdown = children || editNode ? PopoverContent : PopoverContentWithoutPortal; - const { helperText } = baseInputProps; + const { helperText, hasRefreshButton } = baseInputProps; // API and store hooks const postTemplateValue = usePostTemplateValue({ @@ -357,7 +357,7 @@ export default function Dropdown({ ); const renderSearchInput = () => ( -
+
); - const renderCustomOptionDialog = () => ( - - - - - - - - { - setOpenDialog(false); - setOpen(false); - }} - nodeId={nodeId!} - name={name!} - nodeClass={nodeClass!} - /> - - ); - const renderOptionsList = () => ( - - + + {filteredOptions?.length > 0 ? ( filteredOptions?.map((option, index) => (
@@ -452,14 +402,18 @@ export default function Dropdown({ /> )}
0, - "w-full pl-2": !filteredMetadata?.[index]?.icon, + className={cn("flex w-full", { + "pl-2": !filteredMetadata?.[index]?.icon, })} > -
- {option}{" "} +
+ {option} +
+ {filteredMetadata?.[index]?.status && ( -
- {filteredMetadata && filteredMetadata?.length > 0 ? ( -
+ )} + + {filteredMetadata && filteredMetadata?.length > 0 && ( +
{Object.entries( filterMetadataKeys(filteredMetadata?.[index] || {}), ) @@ -495,25 +450,27 @@ export default function Dropdown({ className="mx-1 h-1 w-1 flex-shrink-0 overflow-visible fill-muted-foreground" /> )} -
{`${String(value)} ${key}`}
+
+ {`${String(value)} ${key}`} +
))}
- ) : ( -
- -
)} +
+ +
@@ -527,7 +484,56 @@ export default function Dropdown({ )}
- {dialogInputs && dialogInputs?.fields && renderCustomOptionDialog()} + {dialogInputs && dialogInputs?.fields && ( + + + + + + + + { + setOpenDialog(false); + setOpen(false); + }} + nodeId={nodeId!} + name={name!} + nodeClass={nodeClass!} + /> + + )}
); @@ -540,9 +546,31 @@ export default function Dropdown({ children ? {} : { minWidth: refButton?.current?.clientWidth ?? "200px" } } > - + {options?.length > 0 && renderSearchInput()} {renderOptionsList()} + {!dialogInputs?.fields && hasRefreshButton && ( +
+ + + +
+ )}
); diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/refreshParameterComponent/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/refreshParameterComponent/index.tsx deleted file mode 100644 index b7b35c4d2..000000000 --- a/src/frontend/src/components/core/parameterRenderComponent/components/refreshParameterComponent/index.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { RefreshButton } from "@/components/ui/refreshButton"; -import { FLEX_VIEW_TYPES } from "@/constants/constants"; -import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value"; -import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template"; -import useAlertStore from "@/stores/alertStore"; -import { APIClassType, InputFieldType } from "@/types/api"; -import { cn } from "@/utils/utils"; -import { InputProps } from "../../types"; - -export function RefreshParameterComponent({ - children, - templateData, - disabled, - nodeClass, - editNode, - handleNodeClass, - nodeId, - name, -}: { - children: React.ReactElement; - templateData: Partial; - disabled: boolean; - nodeClass: APIClassType; - editNode: boolean; - handleNodeClass: (value: any, code?: string, type?: string) => void; - nodeId: string; - name: string; -}) { - const postTemplateValue = usePostTemplateValue({ - parameterId: name, - nodeId: nodeId, - node: nodeClass, - }); - - const setErrorData = useAlertStore((state) => state.setErrorData); - const handleRefreshButtonPress = () => - mutateTemplate( - templateData.value, - nodeId, - nodeClass, - handleNodeClass, - postTemplateValue, - setErrorData, - ); - - const isFlexView = FLEX_VIEW_TYPES.includes(templateData.type ?? ""); - - return ( - (children || - (templateData.refresh_button && !templateData.dialog_inputs)) && ( -
- {children} - {templateData.refresh_button && - !templateData.dialog_inputs?.fields?.data?.node?.template && ( -
- -
- )} -
- ) - ); -} diff --git a/src/frontend/src/components/core/parameterRenderComponent/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/index.tsx index cbd1db2ca..ac58be58c 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/index.tsx @@ -20,7 +20,6 @@ import McpComponent from "./components/mcpComponent"; import MultiselectComponent from "./components/multiselectComponent"; import PromptAreaComponent from "./components/promptComponent"; import QueryComponent from "./components/queryComponent"; -import { RefreshParameterComponent } from "./components/refreshParameterComponent"; import SortableListComponent from "./components/sortableListComponent"; import { StrRenderComponent } from "./components/strRenderComponent"; import ToggleShadComponent from "./components/toggleShadComponent"; @@ -295,20 +294,5 @@ export function ParameterRenderComponent({ } }; - return useMemo( - () => ( - - {renderComponent()} - - ), - [templateData, disabled, nodeId, editNode, nodeClass, name, templateValue], - ); + return renderComponent(); } diff --git a/src/frontend/tests/extended/features/refresh-dropdown-list.spec.ts b/src/frontend/tests/extended/features/refresh-dropdown-list.spec.ts new file mode 100644 index 000000000..d09d7a77e --- /dev/null +++ b/src/frontend/tests/extended/features/refresh-dropdown-list.spec.ts @@ -0,0 +1,43 @@ +import { test } from "@playwright/test"; +import * as dotenv from "dotenv"; +import path from "path"; +import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { initialGPTsetup } from "../../utils/initialGPTsetup"; + +test( + "refresh dropdown list", + { tag: ["@release", "@components"] }, + async ({ page }) => { + test.skip( + !process?.env?.ANTHROPIC_API_KEY, + "ANTHROPIC_API_KEY required to run this test", + ); + + if (!process.env.CI) { + dotenv.config({ path: path.resolve(__dirname, "../../.env") }); + } + + await page.goto("/"); + await awaitBootstrapTest(page); + + await page.getByTestId("side_nav_options_all-templates").click(); + await page + .getByRole("heading", { name: "Portfolio Website Code Generator" }) + .click(); + + await page.waitForSelector('[data-testid="fit_view"]', { + timeout: 100000, + }); + + await initialGPTsetup(page, { + skipAdjustScreenView: true, + skipSelectGptModel: true, + }); + + await page.waitForTimeout(3000); + + await page.getByTestId("dropdown_str_model_name").first().click(); + await page.getByTestId("refresh-dropdown-list-model_name").first().click(); + await page.getByText("Loading Options").isVisible({ timeout: 5000 }); + }, +);