-
Notifications
You must be signed in to change notification settings - Fork 8.2k
refactor(frontend): McpServerTab #10396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
95bd2b5
refactor(frontend): improve McpServerTab with separation of concerns …
566853c
fix(frontend): fix sidebar cookie state and use-mobile event listener…
86d5621
refactor(frontend): extract McpServerTab sections into focused compon…
2bb84e4
refactor(frontend): merge McpCodeDisplay into McpJsonContent for bett…
890b642
refactor(frontend): organize types and interfaces at the top of McpJs…
9d983b4
refactor(frontend): organize types at the top of mcpServerUtils
64bfa8f
test(frontend): add critical user flow tests for McpServerTab refacto…
30d4879
refactor(frontend): remove all 'any' types from tests and components
9d4095e
refactor(test): clarify useMcpServer test scope
0669698
fix(frontend): fix WSL platform detection in buildMcpServerJson
6d63543
refactor(test): remove unnecessary comments and extra test files
19db5e3
test(frontend): add comprehensive unit tests for MCP section components
621baf6
fix(test): make message-sorting performance test more robust for CI
1aa5612
test(frontend): add tests for generateApiKey in authStore
bbc0181
test(frontend): improve authStore generateApiKey test coverage
eba279a
docs(test): add comment explaining useMcpServer integration test cove…
2ee5d2a
test(frontend): add explicit tests for api_key condition branches
f918f8e
refactor: move API key generation from authStore to useMcpServer hook
e92b817
fix: reset package-lock.json to match main branch
4780193
test(frontend): add tests for use-mobile and sidebar bug fixes
4e722bf
refactor(frontend): cleanup unused imports and improve code quality
655c8d4
test(frontend): add direct unit tests for useMcpServer hook
128b45f
refactor(test): remove all 'any' and 'unknown' types from tests
835281b
fix(test): add explicit testids for copy/check icons for E2E compatib…
345af75
test(frontend): add tests for copy/check icon states
fbb55d5
test(frontend): improve icon tests to verify both name and testId
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
122 changes: 122 additions & 0 deletions
122
src/frontend/src/components/ui/__tests__/sidebar.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import { fireEvent, render } from "@testing-library/react"; | ||
| import { SidebarProvider, useSidebar } from "../sidebar"; | ||
|
|
||
| // Mock component to test useSidebar hook | ||
| const TestComponent = ({ onToggle }: { onToggle?: () => void }) => { | ||
| const { setOpen, open } = useSidebar(); | ||
|
|
||
| return ( | ||
| <div> | ||
| <div data-testid="sidebar-state">{open ? "open" : "closed"}</div> | ||
| <button | ||
| data-testid="toggle-btn" | ||
| onClick={() => { | ||
| setOpen((prev) => !prev); | ||
| onToggle?.(); | ||
| }} | ||
| > | ||
| Toggle | ||
| </button> | ||
| <button data-testid="set-open-btn" onClick={() => setOpen(true)}> | ||
| Set Open | ||
| </button> | ||
| <button data-testid="set-closed-btn" onClick={() => setOpen(false)}> | ||
| Set Closed | ||
| </button> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| describe("Sidebar", () => { | ||
| let cookieStore: Record<string, string> = {}; | ||
|
|
||
| beforeEach(() => { | ||
| // Reset cookie store | ||
| cookieStore = {}; | ||
|
|
||
| // Mock document.cookie | ||
| Object.defineProperty(document, "cookie", { | ||
| get: jest.fn(() => { | ||
| return Object.entries(cookieStore) | ||
| .map(([key, value]) => `${key}=${value}`) | ||
| .join("; "); | ||
| }), | ||
| set: jest.fn((cookieString: string) => { | ||
| const [keyValue] = cookieString.split(";"); | ||
| const [key, value] = keyValue.split("="); | ||
| if (key && value !== undefined) { | ||
| cookieStore[key.trim()] = value.trim(); | ||
| } | ||
| }), | ||
| configurable: true, | ||
| }); | ||
| }); | ||
|
|
||
| it("should use computed nextOpen value in cookie, not stale open state", () => { | ||
| const { getByTestId } = render( | ||
| <SidebarProvider> | ||
| <TestComponent /> | ||
| </SidebarProvider>, | ||
| ); | ||
|
|
||
| // Initial state should be open (default) | ||
| expect(getByTestId("sidebar-state")).toHaveTextContent("open"); | ||
|
|
||
| // Toggle to closed using function updater | ||
| fireEvent.click(getByTestId("toggle-btn")); | ||
|
|
||
| // Cookie should reflect the NEW state (closed), not the old state | ||
| // This verifies the bug fix: using nextOpen instead of open | ||
| expect(cookieStore["sidebar:state"]).toBe("false"); | ||
| }); | ||
|
|
||
| it("should update cookie when setOpen is called with boolean", () => { | ||
| const { getByTestId } = render( | ||
| <SidebarProvider defaultOpen={false}> | ||
| <TestComponent /> | ||
| </SidebarProvider>, | ||
| ); | ||
|
|
||
| // Set to open | ||
| fireEvent.click(getByTestId("set-open-btn")); | ||
| expect(cookieStore["sidebar:state"]).toBe("true"); | ||
|
|
||
| // Set to closed | ||
| fireEvent.click(getByTestId("set-closed-btn")); | ||
| expect(cookieStore["sidebar:state"]).toBe("false"); | ||
| }); | ||
|
|
||
| it("should handle function updater correctly", () => { | ||
| const { getByTestId } = render( | ||
| <SidebarProvider defaultOpen={true}> | ||
| <TestComponent /> | ||
| </SidebarProvider>, | ||
| ); | ||
|
|
||
| // Toggle from true to false | ||
| fireEvent.click(getByTestId("toggle-btn")); | ||
| expect(cookieStore["sidebar:state"]).toBe("false"); | ||
|
|
||
| // Toggle from false to true | ||
| fireEvent.click(getByTestId("toggle-btn")); | ||
| expect(cookieStore["sidebar:state"]).toBe("true"); | ||
| }); | ||
|
|
||
| it("should persist state across multiple toggles", () => { | ||
| const { getByTestId } = render( | ||
| <SidebarProvider defaultOpen={false}> | ||
| <TestComponent /> | ||
| </SidebarProvider>, | ||
| ); | ||
|
|
||
| // Multiple toggles | ||
| fireEvent.click(getByTestId("toggle-btn")); // -> true | ||
| expect(cookieStore["sidebar:state"]).toBe("true"); | ||
|
|
||
| fireEvent.click(getByTestId("toggle-btn")); // -> false | ||
| expect(cookieStore["sidebar:state"]).toBe("false"); | ||
|
|
||
| fireEvent.click(getByTestId("toggle-btn")); // -> true | ||
| expect(cookieStore["sidebar:state"]).toBe("true"); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
src/frontend/src/pages/MainPage/pages/homePage/components/McpAuthSection.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| import { ForwardedIconComponent } from "@/components/common/genericIconComponent"; | ||
| import ShadTooltip from "@/components/common/shadTooltipComponent"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import type { AuthSettingsType } from "@/types/mcp"; | ||
| import { AUTH_METHODS } from "@/utils/mcpUtils"; | ||
| import { cn } from "@/utils/utils"; | ||
|
|
||
| interface McpAuthSectionProps { | ||
| hasAuthentication: boolean; | ||
| composerUrlData?: { error_message?: string }; | ||
| isLoading: boolean; | ||
| currentAuthSettings?: AuthSettingsType; | ||
| setAuthModalOpen: (open: boolean) => void; | ||
| } | ||
|
|
||
| export const McpAuthSection = ({ | ||
| hasAuthentication, | ||
| composerUrlData, | ||
| isLoading, | ||
| currentAuthSettings, | ||
| setAuthModalOpen, | ||
| }: McpAuthSectionProps) => ( | ||
| <div className="flex justify-between"> | ||
| <span className="flex gap-2 items-center text-sm cursor-default"> | ||
| <span className=" font-medium">Auth:</span> | ||
| {!hasAuthentication ? ( | ||
| <span className="text-accent-amber-foreground flex gap-2 text-mmd items-center"> | ||
| <ForwardedIconComponent | ||
| name="AlertTriangle" | ||
| className="h-4 w-4 shrink-0" | ||
| /> | ||
| None (public) | ||
| </span> | ||
| ) : ( | ||
| <ShadTooltip | ||
| content={ | ||
| !composerUrlData?.error_message | ||
| ? undefined | ||
| : `MCP Server is not running: ${composerUrlData?.error_message}` | ||
| } | ||
| > | ||
| <span | ||
| className={cn( | ||
| "flex gap-2 text-mmd items-center", | ||
| isLoading | ||
| ? "text-muted-foreground" | ||
| : !composerUrlData?.error_message | ||
| ? "text-accent-emerald-foreground" | ||
| : "text-accent-amber-foreground", | ||
| )} | ||
| > | ||
| <ForwardedIconComponent | ||
| name={ | ||
| isLoading | ||
| ? "Loader2" | ||
| : !composerUrlData?.error_message | ||
| ? "Check" | ||
| : "AlertTriangle" | ||
| } | ||
| className={cn("h-4 w-4 shrink-0", isLoading && "animate-spin")} | ||
| /> | ||
| {isLoading | ||
| ? "Loading..." | ||
| : AUTH_METHODS[ | ||
| currentAuthSettings?.auth_type as keyof typeof AUTH_METHODS | ||
| ]?.label || currentAuthSettings?.auth_type} | ||
| </span> | ||
| </ShadTooltip> | ||
| )} | ||
| </span> | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| className="!text-mmd !font-normal" | ||
| onClick={() => setAuthModalOpen(true)} | ||
| > | ||
| <ForwardedIconComponent name="Fingerprint" className="h-4 w-4 shrink-0" /> | ||
| {hasAuthentication ? "Edit Auth" : "Add Auth"} | ||
| </Button> | ||
| </div> | ||
| ); |
101 changes: 101 additions & 0 deletions
101
src/frontend/src/pages/MainPage/pages/homePage/components/McpAutoInstallContent.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import { ForwardedIconComponent } from "@/components/common/genericIconComponent"; | ||
| import ShadTooltip from "@/components/common/shadTooltipComponent"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { toSpaceCase } from "@/utils/stringManipulation"; | ||
| import { cn } from "@/utils/utils"; | ||
| import { autoInstallers } from "../utils/mcpServerUtils"; | ||
|
|
||
| interface McpAutoInstallContentProps { | ||
| isLocalConnection: boolean; | ||
| installedMCPData?: Array<{ name?: string; available?: boolean }>; | ||
| loadingMCP: string[]; | ||
| installClient: (name: string, title?: string) => void; | ||
| installedClients?: string[]; | ||
| } | ||
|
|
||
| export const McpAutoInstallContent = ({ | ||
| isLocalConnection, | ||
| installedMCPData, | ||
| loadingMCP, | ||
| installClient, | ||
| installedClients, | ||
| }: McpAutoInstallContentProps) => ( | ||
| <div className="flex flex-col gap-1 mt-4"> | ||
| {!isLocalConnection && ( | ||
| <div className="mb-2 rounded-md bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:bg-amber-950 dark:text-amber-200"> | ||
| <div className="flex items-center gap-3"> | ||
| <ForwardedIconComponent | ||
| name="AlertTriangle" | ||
| className="h-4 w-4 shrink-0" | ||
| /> | ||
| <span> | ||
| One-click install is disabled because the Langflow server is not | ||
| running on your local machine. Use the JSON tab to configure your | ||
| client manually. | ||
| </span> | ||
| </div> | ||
| </div> | ||
| )} | ||
| {autoInstallers.map((installer) => ( | ||
| <ShadTooltip | ||
| key={installer.name} | ||
| content={ | ||
| !installedMCPData?.find((client) => client.name === installer.name) | ||
| ?.available | ||
| ? `Install ${toSpaceCase(installer.name)} to enable auto-install.` | ||
| : "" | ||
| } | ||
| side="left" | ||
| > | ||
| <div className="w-full flex"> | ||
| <Button | ||
| variant="ghost" | ||
| className="group flex flex-1 items-center justify-between disabled:text-foreground disabled:opacity-50" | ||
| disabled={ | ||
| loadingMCP.includes(installer.name) || | ||
| !isLocalConnection || | ||
| !installedMCPData?.find( | ||
| (client) => client.name === installer.name, | ||
| )?.available | ||
| } | ||
| onClick={() => installClient(installer.name, installer.title)} | ||
| > | ||
| <div className="flex items-center gap-4 text-sm font-medium"> | ||
| <ForwardedIconComponent | ||
| name={installer.icon} | ||
| className={cn("h-5 w-5")} | ||
| aria-hidden="true" | ||
| /> | ||
| {installer.title} | ||
| </div> | ||
| <div className="relative h-4 w-4"> | ||
| <ForwardedIconComponent | ||
| name={ | ||
| installedClients?.includes(installer.name) | ||
| ? "Check" | ||
| : loadingMCP.includes(installer.name) | ||
| ? "Loader2" | ||
| : "Plus" | ||
| } | ||
| className={cn( | ||
| "h-4 w-4 absolute top-0 left-0 opacity-100", | ||
| loadingMCP.includes(installer.name) && "animate-spin", | ||
| installedClients?.includes(installer.name) && | ||
| "group-hover:opacity-0", | ||
| )} | ||
| /> | ||
| {installedClients?.includes(installer.name) && ( | ||
| <ForwardedIconComponent | ||
| name={"RefreshCw"} | ||
| className={cn( | ||
| "h-4 w-4 absolute top-0 left-0 opacity-0 group-hover:opacity-100", | ||
| )} | ||
| /> | ||
| )} | ||
| </div> | ||
| </Button> | ||
| </div> | ||
| </ShadTooltip> | ||
| ))} | ||
| </div> | ||
| ); |
46 changes: 46 additions & 0 deletions
46
src/frontend/src/pages/MainPage/pages/homePage/components/McpFlowsSection.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { ForwardedIconComponent } from "@/components/common/genericIconComponent"; | ||
| import ShadTooltip from "@/components/common/shadTooltipComponent"; | ||
| import ToolsComponent from "@/components/core/parameterRenderComponent/components/ToolsComponent"; | ||
| import type { InputFieldType } from "@/types/api"; | ||
| import type { ToolFlow } from "../utils/mcpServerUtils"; | ||
|
|
||
| interface McpFlowsSectionProps { | ||
| flowsMCPData: ToolFlow[]; | ||
| handleOnNewValue: (changes: Partial<InputFieldType>) => void; | ||
| } | ||
|
|
||
| export const McpFlowsSection = ({ | ||
| flowsMCPData, | ||
| handleOnNewValue, | ||
| }: McpFlowsSectionProps) => ( | ||
| <div className="w-full xl:w-2/5"> | ||
| <div className="flex flex-row justify-between pt-1"> | ||
| <ShadTooltip | ||
| content="Flows in this project can be exposed as callable MCP tools." | ||
| side="right" | ||
| > | ||
| <div className="flex items-center text-sm font-medium hover:cursor-help"> | ||
| Flows/Tools | ||
| <ForwardedIconComponent | ||
| name="info" | ||
| className="ml-1.5 h-4 w-4 text-muted-foreground" | ||
| aria-hidden="true" | ||
| /> | ||
| </div> | ||
| </ShadTooltip> | ||
| </div> | ||
| <div className="flex flex-row flex-wrap gap-2 pt-2"> | ||
| <ToolsComponent | ||
| value={flowsMCPData} | ||
| title="MCP Server Tools" | ||
| description="Select tools to add to this server" | ||
| handleOnNewValue={handleOnNewValue} | ||
| id="mcp-server-tools" | ||
| button_description="Edit Tools" | ||
| editNode={false} | ||
| isAction | ||
| disabled={false} | ||
| /> | ||
| </div> | ||
| </div> | ||
| ); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.