From 719624a62f8277eb7d7c53d866b918e8f3dab48d Mon Sep 17 00:00:00 2001 From: Kris McGinnes Date: Fri, 25 Sep 2026 16:57:53 -0500 Subject: [PATCH] Put the advanced connection settings behind a disclosure --- docs/features/connections.md | 3 + docs/guides/troubleshooting.md | 2 +- .../CreateConnection.test.tsx | 74 ++++++++- .../CreateConnection/CreateConnection.tsx | 146 +++++++++++------- .../src/utils/createDisplayError.test.ts | 2 +- .../src/utils/createDisplayError.ts | 2 +- 6 files changed, 165 insertions(+), 64 deletions(-) diff --git a/docs/features/connections.md b/docs/features/connections.md index b877c8c9c..cc489735c 100644 --- a/docs/features/connections.md +++ b/docs/features/connections.md @@ -17,6 +17,9 @@ For guides on connecting to specific databases, see [Connecting to databases](.. - **AWS IAM Auth Enabled:** Check this box if connecting to Amazon Neptune using IAM Auth and SigV4 signed requests - **Service Type:** Choose the service type - **AWS Region:** Specify the AWS region where the Neptune cluster is hosted (e.g., us-east-1) + +The next two settings are grouped under an **Advanced options** section that you expand to reach. It starts expanded when the connection you are editing already overrides one of them, so an existing override is never hidden from you. + - **Fetch Timeout:** Specify the timeout for the fetch request - **Neighbor Expansion Limit:** Specify the default limit for neighbor expansion. This will override the app setting for neighbor expansion. diff --git a/docs/guides/troubleshooting.md b/docs/guides/troubleshooting.md index fc95ed182..eb07e8a44 100644 --- a/docs/guides/troubleshooting.md +++ b/docs/guides/troubleshooting.md @@ -117,7 +117,7 @@ There are multiple sources of timeouts. Graph Explorer distinguishes two kinds of timeout and shows a different message for each. -**Fetch timeout exceeded** means the request didn't finish within the connection's own Fetch Timeout setting. This is a client-side limit you configure yourself in the connection's settings, under "Enable Fetch Timeout". Increase the Fetch Timeout value, or retry the request. +**Fetch timeout exceeded** means the request didn't finish within the connection's own Fetch Timeout setting. This is a client-side limit you configure yourself in this connection's advanced options, under "Enable Fetch Timeout". Increase the Fetch Timeout value, or retry the request. **Database query timed out** means the database itself stopped the query because it ran longer than the database's configured query timeout. For Neptune, this is controlled by the DB cluster parameter group. Increase the query timeout there, or retry the request. diff --git a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.test.tsx b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.test.tsx index a86aa9b7e..6be61b9b6 100644 --- a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.test.tsx +++ b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.test.tsx @@ -1,10 +1,18 @@ // @vitest-environment happy-dom + +import type { ConnectionConfig } from "@shared/types"; + import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, expect, test, vi } from "vitest"; import { TooltipProvider } from "@/components"; -import { configurationAtom, getAppStore } from "@/core"; +import { + type ConfigurationContextProps, + configurationAtom, + createNewConfigurationId, + getAppStore, +} from "@/core"; import { createQueryClient } from "@/core/queryClient"; import { mergeConfiguration } from "@/core/StateProvider/configuration"; import { createRandomRawConfiguration, TestProvider } from "@/utils/testing"; @@ -26,6 +34,14 @@ function renderCreateConnection(ui: React.ReactElement) { return store; } +/** The advanced settings are behind a disclosure, so their content is unmounted until it opens. */ +async function openAdvancedOptions(user: ReturnType) { + const trigger = screen.getByRole("button", { name: "Advanced options" }); + expect(trigger).toHaveAttribute("aria-expanded", "false"); + await user.click(trigger); + expect(trigger).toHaveAttribute("aria-expanded", "true"); +} + describe("CreateConnection", () => { test("removes newlines and surrounding whitespace from URL fields", async () => { const user = userEvent.setup(); @@ -62,6 +78,7 @@ describe("CreateConnection", () => { test("labels the override field Neighbor Expansion Limit", async () => { const user = userEvent.setup(); renderCreateConnection(); + await openAdvancedOptions(user); await user.click( screen.getByRole("checkbox", { @@ -74,6 +91,61 @@ describe("CreateConnection", () => { ).toBeInTheDocument(); }); + test("keeps the advanced options collapsed until the user expands them", async () => { + const user = userEvent.setup(); + renderCreateConnection(); + + expect( + screen.queryByRole("checkbox", { name: /Enable Fetch Timeout/ }), + ).not.toBeInTheDocument(); + + await openAdvancedOptions(user); + + expect( + screen.getByRole("checkbox", { name: /Enable Fetch Timeout/ }), + ).toBeInTheDocument(); + expect( + screen.getByRole("checkbox", { + name: /Override Default Neighbor Expansion Limit/, + }), + ).toBeInTheDocument(); + }); + + test("opens the advanced options when the connection already overrides one", () => { + const configId = createNewConfigurationId(); + const store = getAppStore(); + const connection: ConnectionConfig = { + url: "https://proxy.example.com", + graphDbUrl: "", + queryEngine: "gremlin", + fetchTimeoutMs: 30000, + }; + store.set( + configurationAtom, + new Map([[configId, { id: configId, connection }]]), + ); + + render( + + + + + , + ); + + expect( + screen.getByRole("button", { name: "Advanced options" }), + ).toHaveAttribute("aria-expanded", "true"); + expect( + screen.getByRole("checkbox", { name: /Enable Fetch Timeout/ }), + ).toBeChecked(); + }); + test("rejects a URL that is empty after normalization", async () => { const user = userEvent.setup(); const store = renderCreateConnection( diff --git a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx index 87cf907c9..a6ab603b1 100644 --- a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx +++ b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx @@ -6,11 +6,15 @@ import type { import { useQueryClient } from "@tanstack/react-query"; import { useAtomCallback } from "jotai/utils"; +import { ChevronRightIcon } from "lucide-react"; import { useCallback, useState } from "react"; import { Button, Checkbox, + Collapsible, + CollapsibleContent, + CollapsibleTrigger, FormItem, InfoTooltip, InputField, @@ -90,6 +94,15 @@ function mapToConnection(data: Required): ConnectionConfig { }; } +/** + * Whether the advanced disclosure should start open. A connection that already + * overrides one of these settings would otherwise hide that fact behind a + * collapsed section, so editing it looks like the defaults are in force. + */ +function hasAdvancedOverrides(form: ConnectionForm): boolean { + return form.fetchTimeoutEnabled || form.nodeExpansionLimitEnabled; +} + /** * Maps a connection into form values under the given name. The caller picks the * name because only it knows the fallback: a stored connection falls back to @@ -395,66 +408,79 @@ const CreateConnection = ({ )} - - - - {form.fetchTimeoutEnabled && ( - - - - - )} - - - - {form.nodeExpansionLimitEnabled && ( - - - - - )} + + {/* Renders its own button rather than `asChild` onto a div, so the + disclosure stays keyboard operable and announces its expanded state. */} + + + Advanced options + + + + + + {form.fetchTimeoutEnabled && ( + + + + + )} + + + + {form.nodeExpansionLimitEnabled && ( + + + + + )} + +