From b042fbdfe2a336d2d908edc7dde881261ef82ef6 Mon Sep 17 00:00:00 2001 From: Andy Kwok Date: Mon, 8 Jun 2026 13:55:01 -0700 Subject: [PATCH 1/5] Phase 1 --- .../src/core/AppStatusLoader.tsx | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/graph-explorer/src/core/AppStatusLoader.tsx b/packages/graph-explorer/src/core/AppStatusLoader.tsx index 36535788f..89dbce11b 100644 --- a/packages/graph-explorer/src/core/AppStatusLoader.tsx +++ b/packages/graph-explorer/src/core/AppStatusLoader.tsx @@ -1,3 +1,5 @@ +import type { QueryEngine, NeptuneServiceType } from "@shared/types"; + import { useQuery } from "@tanstack/react-query"; import { useAtom } from "jotai"; import { @@ -10,6 +12,11 @@ import { import { PanelEmptyState, Spinner } from "@/components"; import { logger } from "@/utils"; +import type { + ConfigurationId, + RawConfiguration, +} from "./ConfigurationProvider"; + import { fetchDefaultConnection } from "./defaultConnection"; import { activeConfigurationAtom, configurationAtom } from "./StateProvider"; @@ -65,6 +72,52 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { defaultConnectionConfigs, ]); + // Process URL connection parameters + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const graphDbUrl = params.get("graphDbUrl"); + if (!graphDbUrl) return; + + const queryEngine = params.get("queryEngine") ?? "gremlin"; + const awsRegion = params.get("awsRegion") ?? ""; + const serviceType = params.get("serviceType") ?? ""; + const name = params.get("name") ?? graphDbUrl; + + const id = `url-${graphDbUrl}-${queryEngine}` as ConfigurationId; + const newConnection: RawConfiguration = { + id, + displayLabel: name, + connection: { + url: window.location.origin, + queryEngine: queryEngine as QueryEngine, + proxyConnection: true, + graphDbUrl, + awsAuthEnabled: !!(awsRegion && serviceType), + awsRegion, + serviceType: (serviceType || undefined) as + | NeptuneServiceType + | undefined, + }, + }; + + startTransition(() => { + logger.debug("Adding connection from URL params", newConnection); + setConfiguration(prev => { + const updated = new Map(prev); + updated.set(id, newConnection); + return updated; + }); + setActiveConfig(id); + }); + + // Strip URL params + window.history.replaceState( + {}, + "", + window.location.pathname + window.location.hash, + ); + }, [setConfiguration, setActiveConfig]); + if (configuration.size === 0 && defaultConfigQuery.isLoading) { return ( Date: Mon, 8 Jun 2026 14:27:35 -0700 Subject: [PATCH 2/5] Matching logic --- .../src/core/AppStatusLoader.tsx | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/packages/graph-explorer/src/core/AppStatusLoader.tsx b/packages/graph-explorer/src/core/AppStatusLoader.tsx index 89dbce11b..86f633dd8 100644 --- a/packages/graph-explorer/src/core/AppStatusLoader.tsx +++ b/packages/graph-explorer/src/core/AppStatusLoader.tsx @@ -83,32 +83,49 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { const serviceType = params.get("serviceType") ?? ""; const name = params.get("name") ?? graphDbUrl; - const id = `url-${graphDbUrl}-${queryEngine}` as ConfigurationId; - const newConnection: RawConfiguration = { - id, - displayLabel: name, - connection: { - url: window.location.origin, - queryEngine: queryEngine as QueryEngine, - proxyConnection: true, - graphDbUrl, - awsAuthEnabled: !!(awsRegion && serviceType), - awsRegion, - serviceType: (serviceType || undefined) as - | NeptuneServiceType - | undefined, - }, - }; + // Check for existing connection with same graphDbUrl + queryEngine + const existingMatch = Array.from(configuration.values()).find( + c => + c.connection?.graphDbUrl?.toLowerCase() === graphDbUrl.toLowerCase() && + c.connection?.queryEngine === queryEngine, + ); - startTransition(() => { - logger.debug("Adding connection from URL params", newConnection); - setConfiguration(prev => { - const updated = new Map(prev); - updated.set(id, newConnection); - return updated; + if (existingMatch) { + logger.debug( + "Found matching connection from URL params", + existingMatch.id, + ); + startTransition(() => { + setActiveConfig(existingMatch.id); }); - setActiveConfig(id); - }); + } else { + const id = `url-${graphDbUrl}-${queryEngine}` as ConfigurationId; + const newConnection: RawConfiguration = { + id, + displayLabel: name, + connection: { + url: window.location.origin, + queryEngine: queryEngine as QueryEngine, + proxyConnection: true, + graphDbUrl, + awsAuthEnabled: !!(awsRegion && serviceType), + awsRegion, + serviceType: (serviceType || undefined) as + | NeptuneServiceType + | undefined, + }, + }; + + startTransition(() => { + logger.debug("Adding connection from URL params", newConnection); + setConfiguration(prev => { + const updated = new Map(prev); + updated.set(id, newConnection); + return updated; + }); + setActiveConfig(id); + }); + } // Strip URL params window.history.replaceState( @@ -116,7 +133,7 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { "", window.location.pathname + window.location.hash, ); - }, [setConfiguration, setActiveConfig]); + }, [configuration, setConfiguration, setActiveConfig]); if (configuration.size === 0 && defaultConfigQuery.isLoading) { return ( From f6a77dc592b93af8d9a94939ca0b96d53bc91d35 Mon Sep 17 00:00:00 2001 From: Andy Kwok Date: Mon, 8 Jun 2026 15:11:05 -0700 Subject: [PATCH 3/5] Update dialog --- .../src/core/AppStatusLoader.tsx | 195 ++++++++++++------ .../src/core/urlConnectionParams.test.ts | 185 +++++++++++++++++ .../src/core/urlConnectionParams.ts | 72 +++++++ 3 files changed, 387 insertions(+), 65 deletions(-) create mode 100644 packages/graph-explorer/src/core/urlConnectionParams.test.ts create mode 100644 packages/graph-explorer/src/core/urlConnectionParams.ts diff --git a/packages/graph-explorer/src/core/AppStatusLoader.tsx b/packages/graph-explorer/src/core/AppStatusLoader.tsx index 86f633dd8..102c620f5 100644 --- a/packages/graph-explorer/src/core/AppStatusLoader.tsx +++ b/packages/graph-explorer/src/core/AppStatusLoader.tsx @@ -1,5 +1,3 @@ -import type { QueryEngine, NeptuneServiceType } from "@shared/types"; - import { useQuery } from "@tanstack/react-query"; import { useAtom } from "jotai"; import { @@ -7,18 +5,31 @@ import { startTransition, Suspense, useEffect, + useState, } from "react"; import { PanelEmptyState, Spinner } from "@/components"; +import { Button } from "@/components/Button/Button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogBody, + DialogFooter, +} from "@/components/Dialog"; import { logger } from "@/utils"; -import type { - ConfigurationId, - RawConfiguration, -} from "./ConfigurationProvider"; - import { fetchDefaultConnection } from "./defaultConnection"; import { activeConfigurationAtom, configurationAtom } from "./StateProvider"; +import { + parseUrlConnectionParams, + findMatchingConnection, + buildConnectionFromParams, +} from "./urlConnectionParams"; + +// Read URL params once at module level (before any render) +const initialUrlParams = parseUrlConnectionParams(window.location.search); function AppStatusLoader({ children }: PropsWithChildren) { return ( @@ -31,12 +42,12 @@ function AppStatusLoader({ children }: PropsWithChildren) { function LoadDefaultConfig({ children }: PropsWithChildren) { const [activeConfig, setActiveConfig] = useAtom(activeConfigurationAtom); const [configuration, setConfiguration] = useAtom(configurationAtom); + const [urlHandled, setUrlHandled] = useState(false); const defaultConfigQuery = useQuery({ queryKey: ["default-connection"], queryFn: fetchDefaultConnection, staleTime: Infinity, - // Run the query only if the store is loaded and there are no configs enabled: configuration.size === 0, }); @@ -44,7 +55,6 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { useEffect(() => { if (!defaultConnectionConfigs) { - // Query hasn't run yet return; } @@ -72,68 +82,36 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { defaultConnectionConfigs, ]); - // Process URL connection parameters + // Handle existing match activation via effect useEffect(() => { - const params = new URLSearchParams(window.location.search); - const graphDbUrl = params.get("graphDbUrl"); - if (!graphDbUrl) return; - - const queryEngine = params.get("queryEngine") ?? "gremlin"; - const awsRegion = params.get("awsRegion") ?? ""; - const serviceType = params.get("serviceType") ?? ""; - const name = params.get("name") ?? graphDbUrl; - - // Check for existing connection with same graphDbUrl + queryEngine - const existingMatch = Array.from(configuration.values()).find( - c => - c.connection?.graphDbUrl?.toLowerCase() === graphDbUrl.toLowerCase() && - c.connection?.queryEngine === queryEngine, - ); + if (urlHandled || !initialUrlParams) return; - if (existingMatch) { - logger.debug( - "Found matching connection from URL params", - existingMatch.id, - ); - startTransition(() => { - setActiveConfig(existingMatch.id); - }); - } else { - const id = `url-${graphDbUrl}-${queryEngine}` as ConfigurationId; - const newConnection: RawConfiguration = { - id, - displayLabel: name, - connection: { - url: window.location.origin, - queryEngine: queryEngine as QueryEngine, - proxyConnection: true, - graphDbUrl, - awsAuthEnabled: !!(awsRegion && serviceType), - awsRegion, - serviceType: (serviceType || undefined) as - | NeptuneServiceType - | undefined, - }, - }; - - startTransition(() => { - logger.debug("Adding connection from URL params", newConnection); - setConfiguration(prev => { - const updated = new Map(prev); - updated.set(id, newConnection); - return updated; - }); - setActiveConfig(id); - }); - } + const existingMatch = findMatchingConnection( + configuration, + initialUrlParams, + ); + if (!existingMatch) return; - // Strip URL params + logger.debug("Found matching connection from URL params", existingMatch.id); + startTransition(() => { + setUrlHandled(true); + setActiveConfig(existingMatch.id); + }); window.history.replaceState( {}, "", window.location.pathname + window.location.hash, ); - }, [configuration, setConfiguration, setActiveConfig]); + }, [urlHandled, configuration, setActiveConfig]); + + // Determine if we need to show the dialog + const existingMatch = initialUrlParams + ? findMatchingConnection(configuration, initialUrlParams) + : null; + const pendingConnection = + initialUrlParams && !existingMatch && !urlHandled + ? buildConnectionFromParams(initialUrlParams, window.location.origin) + : null; if (configuration.size === 0 && defaultConfigQuery.isLoading) { return ( @@ -145,7 +123,6 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { ); } - // Loading from config file if exists if ( configuration.size === 0 && defaultConnectionConfigs && @@ -160,7 +137,95 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { ); } - return <>{children}; + return ( + <> + {children} + { + if (!open) { + setUrlHandled(true); + window.history.replaceState( + {}, + "", + window.location.pathname + window.location.hash, + ); + } + }} + > + + + Create Connection + + + {pendingConnection && ( +
+

+ Name:{" "} + {pendingConnection.displayLabel} +

+

+ Endpoint:{" "} + {pendingConnection.connection?.graphDbUrl} +

+

+ Query Engine:{" "} + {pendingConnection.connection?.queryEngine} +

+ {pendingConnection.connection?.awsRegion && ( +

+ Region:{" "} + {pendingConnection.connection.awsRegion} +

+ )} +
+ )} +
+ + + + +
+
+ + ); } function PreparingEnvironment() { diff --git a/packages/graph-explorer/src/core/urlConnectionParams.test.ts b/packages/graph-explorer/src/core/urlConnectionParams.test.ts new file mode 100644 index 000000000..e2e13b742 --- /dev/null +++ b/packages/graph-explorer/src/core/urlConnectionParams.test.ts @@ -0,0 +1,185 @@ +import type { + ConfigurationId, + RawConfiguration, +} from "./ConfigurationProvider"; + +import { + parseUrlConnectionParams, + findMatchingConnection, + buildConnectionFromParams, +} from "./urlConnectionParams"; + +describe("parseUrlConnectionParams", () => { + test("returns null when graphDbUrl is missing", () => { + expect(parseUrlConnectionParams("")).toBeNull(); + expect(parseUrlConnectionParams("?queryEngine=openCypher")).toBeNull(); + }); + + test("parses graphDbUrl with defaults", () => { + const result = parseUrlConnectionParams( + "?graphDbUrl=https%3A%2F%2Fg-xxx.us-west-2.neptune-graph.amazonaws.com", + ); + expect(result).toEqual({ + graphDbUrl: "https://g-xxx.us-west-2.neptune-graph.amazonaws.com", + queryEngine: "gremlin", + awsRegion: "", + serviceType: "", + name: "https://g-xxx.us-west-2.neptune-graph.amazonaws.com", + }); + }); + + test("parses all parameters", () => { + const result = parseUrlConnectionParams( + "?graphDbUrl=https%3A%2F%2Fg-xxx.neptune-graph.amazonaws.com&queryEngine=openCypher&awsRegion=us-west-2&serviceType=neptune-graph&name=My+Graph", + ); + expect(result).toEqual({ + graphDbUrl: "https://g-xxx.neptune-graph.amazonaws.com", + queryEngine: "openCypher", + awsRegion: "us-west-2", + serviceType: "neptune-graph", + name: "My Graph", + }); + }); +}); + +describe("findMatchingConnection", () => { + const configs = new Map([ + [ + "conn-1" as ConfigurationId, + { + id: "conn-1" as ConfigurationId, + displayLabel: "Test", + connection: { + url: "https://localhost", + queryEngine: "openCypher", + graphDbUrl: "https://g-abc.us-west-2.neptune-graph.amazonaws.com", + }, + }, + ], + [ + "conn-2" as ConfigurationId, + { + id: "conn-2" as ConfigurationId, + displayLabel: "Gremlin DB", + connection: { + url: "https://localhost", + queryEngine: "gremlin", + graphDbUrl: "https://my-cluster.neptune.amazonaws.com", + }, + }, + ], + ]); + + test("finds match by graphDbUrl and queryEngine", () => { + const match = findMatchingConnection(configs, { + graphDbUrl: "https://g-abc.us-west-2.neptune-graph.amazonaws.com", + queryEngine: "openCypher", + awsRegion: "", + serviceType: "", + name: "", + }); + expect(match?.id).toBe("conn-1"); + }); + + test("matches case-insensitively on graphDbUrl", () => { + const match = findMatchingConnection(configs, { + graphDbUrl: "https://G-ABC.US-WEST-2.NEPTUNE-GRAPH.AMAZONAWS.COM", + queryEngine: "openCypher", + awsRegion: "", + serviceType: "", + name: "", + }); + expect(match?.id).toBe("conn-1"); + }); + + test("returns null when queryEngine differs", () => { + const match = findMatchingConnection(configs, { + graphDbUrl: "https://g-abc.us-west-2.neptune-graph.amazonaws.com", + queryEngine: "gremlin", + awsRegion: "", + serviceType: "", + name: "", + }); + expect(match).toBeNull(); + }); + + test("returns null when no match", () => { + const match = findMatchingConnection(configs, { + graphDbUrl: "https://unknown.neptune.amazonaws.com", + queryEngine: "gremlin", + awsRegion: "", + serviceType: "", + name: "", + }); + expect(match).toBeNull(); + }); +}); + +describe("buildConnectionFromParams", () => { + test("builds connection with IAM enabled", () => { + const connection = buildConnectionFromParams( + { + graphDbUrl: "https://g-xxx.neptune-graph.amazonaws.com", + queryEngine: "openCypher", + awsRegion: "us-west-2", + serviceType: "neptune-graph", + name: "My Graph", + }, + "https://localhost", + ); + + expect(connection.id).toBe( + "url-https://g-xxx.neptune-graph.amazonaws.com-openCypher", + ); + expect(connection.displayLabel).toBe("My Graph"); + expect(connection.connection).toEqual({ + url: "https://localhost", + queryEngine: "openCypher", + proxyConnection: true, + graphDbUrl: "https://g-xxx.neptune-graph.amazonaws.com", + awsAuthEnabled: true, + awsRegion: "us-west-2", + serviceType: "neptune-graph", + }); + }); + + test("builds connection with IAM disabled when region/serviceType missing", () => { + const connection = buildConnectionFromParams( + { + graphDbUrl: "https://g-xxx.neptune-graph.amazonaws.com", + queryEngine: "gremlin", + awsRegion: "", + serviceType: "", + name: "No IAM", + }, + "https://localhost", + ); + + expect(connection.connection?.awsAuthEnabled).toBe(false); + expect(connection.connection?.serviceType).toBeUndefined(); + }); + + test("generates deterministic ID", () => { + const a = buildConnectionFromParams( + { + graphDbUrl: "https://g-xxx.neptune-graph.amazonaws.com", + queryEngine: "openCypher", + awsRegion: "", + serviceType: "", + name: "A", + }, + "https://localhost", + ); + const b = buildConnectionFromParams( + { + graphDbUrl: "https://g-xxx.neptune-graph.amazonaws.com", + queryEngine: "openCypher", + awsRegion: "", + serviceType: "", + name: "B", + }, + "https://localhost", + ); + expect(a.id).toBe(b.id); + }); +}); diff --git a/packages/graph-explorer/src/core/urlConnectionParams.ts b/packages/graph-explorer/src/core/urlConnectionParams.ts new file mode 100644 index 000000000..11ce8bb98 --- /dev/null +++ b/packages/graph-explorer/src/core/urlConnectionParams.ts @@ -0,0 +1,72 @@ +import type { QueryEngine, NeptuneServiceType } from "@shared/types"; + +import type { + ConfigurationId, + RawConfiguration, +} from "./ConfigurationProvider"; + +export interface UrlConnectionParams { + graphDbUrl: string; + queryEngine: QueryEngine; + awsRegion: string; + serviceType: string; + name: string; +} + +/** Parse URL search params into connection params. Returns null if graphDbUrl is missing. */ +export function parseUrlConnectionParams( + search: string, +): UrlConnectionParams | null { + const params = new URLSearchParams(search); + const graphDbUrl = params.get("graphDbUrl"); + if (!graphDbUrl) return null; + + return { + graphDbUrl, + queryEngine: (params.get("queryEngine") ?? "gremlin") as QueryEngine, + awsRegion: params.get("awsRegion") ?? "", + serviceType: params.get("serviceType") ?? "", + name: params.get("name") ?? graphDbUrl, + }; +} + +/** Find an existing connection matching graphDbUrl + queryEngine. */ +export function findMatchingConnection( + configurations: Map, + params: UrlConnectionParams, +): RawConfiguration | null { + for (const config of configurations.values()) { + if ( + config.connection?.graphDbUrl?.toLowerCase() === + params.graphDbUrl.toLowerCase() && + config.connection?.queryEngine === params.queryEngine + ) { + return config; + } + } + return null; +} + +/** Build a RawConfiguration from URL params. */ +export function buildConnectionFromParams( + params: UrlConnectionParams, + origin: string, +): RawConfiguration { + const id = + `url-${params.graphDbUrl}-${params.queryEngine}` as ConfigurationId; + return { + id, + displayLabel: params.name, + connection: { + url: origin, + queryEngine: params.queryEngine, + proxyConnection: true, + graphDbUrl: params.graphDbUrl, + awsAuthEnabled: !!(params.awsRegion && params.serviceType), + awsRegion: params.awsRegion, + serviceType: (params.serviceType || undefined) as + | NeptuneServiceType + | undefined, + }, + }; +} From c40f4f22e8e305fc81fe7e202ee347db4d2fdc97 Mon Sep 17 00:00:00 2001 From: Andy Kwok Date: Mon, 8 Jun 2026 15:17:10 -0700 Subject: [PATCH 4/5] Refactor --- .../src/core/AppStatusLoader.tsx | 167 +++++------------- .../src/core/UrlConnectionDialog.tsx | 71 ++++++++ 2 files changed, 120 insertions(+), 118 deletions(-) create mode 100644 packages/graph-explorer/src/core/UrlConnectionDialog.tsx diff --git a/packages/graph-explorer/src/core/AppStatusLoader.tsx b/packages/graph-explorer/src/core/AppStatusLoader.tsx index 102c620f5..4a3f718eb 100644 --- a/packages/graph-explorer/src/core/AppStatusLoader.tsx +++ b/packages/graph-explorer/src/core/AppStatusLoader.tsx @@ -4,31 +4,23 @@ import { type PropsWithChildren, startTransition, Suspense, - useEffect, useState, + useEffect, } from "react"; import { PanelEmptyState, Spinner } from "@/components"; -import { Button } from "@/components/Button/Button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogBody, - DialogFooter, -} from "@/components/Dialog"; import { logger } from "@/utils"; import { fetchDefaultConnection } from "./defaultConnection"; import { activeConfigurationAtom, configurationAtom } from "./StateProvider"; +import { UrlConnectionDialog } from "./UrlConnectionDialog"; import { parseUrlConnectionParams, findMatchingConnection, buildConnectionFromParams, } from "./urlConnectionParams"; -// Read URL params once at module level (before any render) +// Read URL params once at module level const initialUrlParams = parseUrlConnectionParams(window.location.search); function AppStatusLoader({ children }: PropsWithChildren) { @@ -42,7 +34,7 @@ function AppStatusLoader({ children }: PropsWithChildren) { function LoadDefaultConfig({ children }: PropsWithChildren) { const [activeConfig, setActiveConfig] = useAtom(activeConfigurationAtom); const [configuration, setConfiguration] = useAtom(configurationAtom); - const [urlHandled, setUrlHandled] = useState(false); + const [urlHandled, setUrlHandled] = useState(!initialUrlParams); const defaultConfigQuery = useQuery({ queryKey: ["default-connection"], @@ -82,36 +74,52 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { defaultConnectionConfigs, ]); - // Handle existing match activation via effect - useEffect(() => { - if (urlHandled || !initialUrlParams) return; + // Compute dialog state during render + const existingMatch = + !urlHandled && initialUrlParams + ? findMatchingConnection(configuration, initialUrlParams) + : null; + const pendingConnection = + !urlHandled && initialUrlParams && !existingMatch + ? buildConnectionFromParams(initialUrlParams, window.location.origin) + : null; - const existingMatch = findMatchingConnection( - configuration, - initialUrlParams, + function handleConfirm() { + if (existingMatch) { + logger.debug( + "Activating matching connection from URL params", + existingMatch.id, + ); + startTransition(() => { + setActiveConfig(existingMatch.id); + }); + } else if (pendingConnection) { + logger.debug("Adding connection from URL params", pendingConnection); + startTransition(() => { + setConfiguration(prev => { + const updated = new Map(prev); + updated.set(pendingConnection.id, pendingConnection); + return updated; + }); + setActiveConfig(pendingConnection.id); + }); + } + setUrlHandled(true); + window.history.replaceState( + {}, + "", + window.location.pathname + window.location.hash, ); - if (!existingMatch) return; + } - logger.debug("Found matching connection from URL params", existingMatch.id); - startTransition(() => { - setUrlHandled(true); - setActiveConfig(existingMatch.id); - }); + function handleCancel() { + setUrlHandled(true); window.history.replaceState( {}, "", window.location.pathname + window.location.hash, ); - }, [urlHandled, configuration, setActiveConfig]); - - // Determine if we need to show the dialog - const existingMatch = initialUrlParams - ? findMatchingConnection(configuration, initialUrlParams) - : null; - const pendingConnection = - initialUrlParams && !existingMatch && !urlHandled - ? buildConnectionFromParams(initialUrlParams, window.location.origin) - : null; + } if (configuration.size === 0 && defaultConfigQuery.isLoading) { return ( @@ -140,90 +148,13 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { return ( <> {children} - { - if (!open) { - setUrlHandled(true); - window.history.replaceState( - {}, - "", - window.location.pathname + window.location.hash, - ); - } - }} - > - - - Create Connection - - - {pendingConnection && ( -
-

- Name:{" "} - {pendingConnection.displayLabel} -

-

- Endpoint:{" "} - {pendingConnection.connection?.graphDbUrl} -

-

- Query Engine:{" "} - {pendingConnection.connection?.queryEngine} -

- {pendingConnection.connection?.awsRegion && ( -

- Region:{" "} - {pendingConnection.connection.awsRegion} -

- )} -
- )} -
- - - - -
-
+ ); } diff --git a/packages/graph-explorer/src/core/UrlConnectionDialog.tsx b/packages/graph-explorer/src/core/UrlConnectionDialog.tsx new file mode 100644 index 000000000..a1f77aba3 --- /dev/null +++ b/packages/graph-explorer/src/core/UrlConnectionDialog.tsx @@ -0,0 +1,71 @@ +import { Button } from "@/components/Button/Button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogBody, + DialogFooter, +} from "@/components/Dialog"; + +import type { RawConfiguration } from "./ConfigurationProvider"; + +interface UrlConnectionDialogProps { + open: boolean; + connection: RawConfiguration | null; + isExisting: boolean; + onConfirm: () => void; + onCancel: () => void; +} + +export function UrlConnectionDialog({ + open, + connection, + isExisting, + onConfirm, + onCancel, +}: UrlConnectionDialogProps) { + return ( + !o && onCancel()}> + + + + {isExisting ? "Activate Connection" : "Create Connection"} + + + + {connection && ( +
+

+ Name:{" "} + {connection.displayLabel} +

+

+ Endpoint:{" "} + {connection.connection?.graphDbUrl} +

+

+ Query Engine:{" "} + {connection.connection?.queryEngine} +

+ {connection.connection?.awsRegion && ( +

+ Region:{" "} + {connection.connection.awsRegion} +

+ )} +
+ )} +
+ + + + +
+
+ ); +} From fae8606634350953e85a69dbe92d6766fa6fab82 Mon Sep 17 00:00:00 2001 From: Andy Kwok Date: Mon, 8 Jun 2026 16:37:20 -0700 Subject: [PATCH 5/5] Minimise diff --- packages/graph-explorer/src/core/AppStatusLoader.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/graph-explorer/src/core/AppStatusLoader.tsx b/packages/graph-explorer/src/core/AppStatusLoader.tsx index 4a3f718eb..d51972307 100644 --- a/packages/graph-explorer/src/core/AppStatusLoader.tsx +++ b/packages/graph-explorer/src/core/AppStatusLoader.tsx @@ -40,6 +40,7 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { queryKey: ["default-connection"], queryFn: fetchDefaultConnection, staleTime: Infinity, + // Run the query only if the store is loaded and there are no configs enabled: configuration.size === 0, }); @@ -47,6 +48,7 @@ function LoadDefaultConfig({ children }: PropsWithChildren) { useEffect(() => { if (!defaultConnectionConfigs) { + // Query hasn't run yet return; }