You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Applications that use graph databases have no way to launch Graph Explorer pre-connected to their database. Users must manually open Graph Explorer, create a connection, and configure it — friction that discourages cross-app integration.
Solution
Graph Explorer accepts a dedicated #/connect route whose query parameters specify a database connection. On entry it either activates a matching existing Connection or prompts the user to create a new one, then redirects to the graph view ready to explore.
Note: The parameters belong to the #/connect route, so they go after the #. Graph Explorer uses hash-based routing, so integrators build the link the same way they would any in-app link. The graphDbUrl value must be URL-encoded (e.g., https://host:8182 becomes https%3A%2F%2Fhost%3A8182). Most languages provide this via encodeURIComponent() (JavaScript), urllib.parse.quote() (Python), URLEncoder.encode() (Java), etc.
User Stories
As an external app developer, I want to link users to Graph Explorer with a database connection pre-activated, so that users can explore their graph data without manual configuration.
As an external app developer, I want to specify the database URL, query language, AWS region, service type, and display name in the link, so that the connection is configured correctly for my database.
As a Graph Explorer user, I want to be prompted before a new connection is created from a link, so that arbitrary links cannot silently modify my saved connections.
As a Graph Explorer user, I want to edit the connection name in the creation prompt, so that I can personalize how the connection appears in my list.
As a Graph Explorer user, I want my existing graph Session to be preserved when a link targets the connection I am already connected to, so that my exploration work is not lost when nothing actually changes.
As a Graph Explorer user, I want the connect URL to leave no trace in my history, so that refreshing the page or pressing back behaves normally.
As a Graph Explorer user, I want only graphDbUrl to be required in the link, so that simple integrations need minimal configuration (query language defaults to Gremlin, IAM auth assumed off when region is absent).
As a Graph Explorer user, I want duplicate connections to not be created when I click the same external link multiple times, so that my connection list stays clean.
As a Graph Explorer user, I want the link processed after default connections load, so that a link matching a default connection does not prompt me to create a duplicate.
As an external app developer, I want the matching logic to find my connection even if the user has renamed it in Graph Explorer, so that name changes don't break integration links.
As a Graph Explorer user with multiple connections to the same database, I want the name parameter to disambiguate which connection activates, so that the correct one is chosen.
As a Graph Explorer user, I want a link requesting IAM auth to never silently reuse a plaintext connection to the same endpoint (or the reverse), so that I always know how a connection authenticates.
As a Graph Explorer user, I want a link carrying values Graph Explorer cannot honor to be rejected outright rather than corrected for me, so that a link never quietly connects me with settings it did not ask for.
As a Graph Explorer user, I want to be told which part of a bad link was wrong, so that I can correct it or report it to whoever sent it.
Implementation Decisions
URL parameter location: Query parameters on a dedicated #/connect route, so they sit after the # like every other route. This keeps the parameters inside the router (no window.location reads, no manual param stripping) and makes the link format the same shape integrators already use for in-app links. This replaces an earlier decision to put the parameters before the #; see the ADR for the reasoning and the trade it makes.
Accepted parameters:graphDbUrl (required, must be URL-encoded, must be http/https, must not carry a username or password), queryEngine (optional, default "gremlin"), awsRegion (optional), serviceType (optional), name (optional). Providing awsRegion enables IAM auth; without it IAM auth is off.
Invalid values are rejected, not coerced: An unsupported queryEngine or serviceType invalidates the link rather than falling back to a default. Answering queryEngine=sql with Gremlin would build a connection that queries the database in a language the caller never asked for. A rejected link is ignored and the user is shown which parameter was at fault and what it requires.
Credentials in the URL are rejected:fetch itself refuses a URL carrying userinfo (the Request constructor throws), so such a link could only ever produce a connection that fails every query, after persisting the password to IndexedDB and into any exported connection file. Graph Explorer authenticates with IAM, never userinfo.
Processing location: The #/connect route resolves the link once on entry and acts on the result. It still runs after default connections have loaded, because AppStatusLoader gates the route behind a loading state while the connection store is empty. Order: preload IndexedDB → fetch default connections → resolve the link → redirect.
Matching logic: Filter connections by graphDbUrl (case-insensitive) + queryEngine + auth posture. Auth posture is identity-bearing: whether IAM is on, and when on, the region and service type. If multiple match, prefer the already-active connection, then tiebreak by name, then fall back to first found. If zero match, prompt for creation.
Activation behavior: A link matching a different connection switches to it silently, with no prompt. This is the same operation as clicking that connection in the connections list, which has never asked for confirmation, and the link only ever activates a connection the user already created. There is also no session data at risk: Sessions are stored per connection, so switching swaps which Session is displayed rather than destroying the previous one. A link cannot change an already-open window's state either, since following one opens a new tab or the user pastes it deliberately.
Creation requires user consent: When no match is found, the create-connection form opens pre-filled and fully editable. Saving creates the connection and activates it; closing or cancelling creates nothing. Either way the user ends on the graph view. This is the trust gate for the untrusted endpoint details a link can carry, and the only path that can introduce a new database.
Connection naming: Use the name parameter when given, otherwise derive one from the graphDbUrl hostname. The derived name is part of the link's identity, not just a display fallback: a nameless link identifies the connection it would have created, so reopening it returns to that connection even after the user adds a second connection to the same endpoint under a name of their own. The name is editable in the create form.
No parameter stripping: The route redirects with replace, so the #/connect entry never enters history. This replaces the earlier window.history.replaceState approach.
Scope: New tab/window navigation only. No iframe/postMessage support.
Subsequent navigations: Opening another link with different parameters is a hash change, which the router handles as a normal navigation rather than a full page reload.
Modules
Connection link reader — Pure function that reads a route's search string and returns one of three outcomes: no link present, an invalid link carrying a problem per offending parameter, or a valid link with typed parameters. Handles URL decoding, required vs optional fields, and defaults.
Connection matcher — Pure function over the parsed parameters, the existing connections, and the active connection id, returning the matched connection or null. Encapsulates the full priority logic (filter by graphDbUrl + queryEngine + auth posture → prefer active → tiebreak by name → first found).
Intent resolver — Pure function that folds a link plus the current connections into one of four intents: do nothing (no link, or it targets the active connection), activate a matching connection, create a new one seeded from the link, or reject an invalid link. Callers dispatch on the intent rather than juggling match and pending flags.
Connect route — Resolves the link once on entry via the resolver, then acts: activates silently, notifies and ignores an invalid link, or renders the create form. Every outcome except the create form redirects immediately.
Create form prefill — The existing create-connection form gains the ability to open pre-filled with fully editable values, without entering its "edit an existing connection" mode.
Testing Decisions
Good tests for this feature verify external behavior (given this link and this connection state, what connection is activated, what is shown) rather than implementation details (which atoms were set, which internal functions were called).
Modules to test
Connection link reader — Valid parameter combinations, missing required parameters, defaults applied, URL decoding, and each rejection rule with the problem it reports. Prior art: defaultConnection.test.ts (schema validation pattern with DefaultConnectionDataSchema).
Connection matcher — Single match, multiple-match disambiguation (active preferred, then name tiebreak, then first), auth posture mismatch falling through to no match, zero matches returning null, case-insensitive URL comparison. Prior art: exportedGraph.test.ts (isMatchingConnection tests).
Intent resolver — Each of the four intents, including a link matching the active connection resolving to a no-op and an auth-posture mismatch resolving to create.
Connect route — Integration tests for each intent: silent activation with redirect, create form pre-filled, no-op redirect, and an invalid link that notifies and redirects without prompting. Also the ordering case: a link matching a default connection that is still loading must not prompt to create a duplicate.
Create form prefill — Renders with the given values, stays in "add" mode, and creates the connection on submit.
The isMatchingConnection function in exportedGraph.ts is prior art for matching logic, but the new matcher module should be independent since post-Unify Docker image by removing SageMaker variant #1773 the connection model changes.
Link-driven activation reuses today's manual connection-switching behavior. There is no special "activate without reset" path: the in-memory graph is cleared on switch exactly as it is when switching by hand, and the previous connection's Session remains persisted under its own key.
External apps must URL-encode parameter values. The graphDbUrl in particular contains characters (:, /) that are invalid in query string values without encoding.
Design decisions and the reasoning behind them live in docs/adr/20260612-connection-links.md.
Problem Statement
Applications that use graph databases have no way to launch Graph Explorer pre-connected to their database. Users must manually open Graph Explorer, create a connection, and configure it — friction that discourages cross-app integration.
Solution
Graph Explorer accepts a dedicated
#/connectroute whose query parameters specify a database connection. On entry it either activates a matching existing Connection or prompts the user to create a new one, then redirects to the graph view ready to explore.Example link from an external app:
User Stories
graphDbUrlto be required in the link, so that simple integrations need minimal configuration (query language defaults to Gremlin, IAM auth assumed off when region is absent).nameparameter to disambiguate which connection activates, so that the correct one is chosen.Implementation Decisions
#/connectroute, so they sit after the#like every other route. This keeps the parameters inside the router (nowindow.locationreads, no manual param stripping) and makes the link format the same shape integrators already use for in-app links. This replaces an earlier decision to put the parameters before the#; see the ADR for the reasoning and the trade it makes.graphDbUrl(required, must be URL-encoded, must behttp/https, must not carry a username or password),queryEngine(optional, default "gremlin"),awsRegion(optional),serviceType(optional),name(optional). ProvidingawsRegionenables IAM auth; without it IAM auth is off.queryEngineorserviceTypeinvalidates the link rather than falling back to a default. AnsweringqueryEngine=sqlwith Gremlin would build a connection that queries the database in a language the caller never asked for. A rejected link is ignored and the user is shown which parameter was at fault and what it requires.fetchitself refuses a URL carrying userinfo (theRequestconstructor throws), so such a link could only ever produce a connection that fails every query, after persisting the password to IndexedDB and into any exported connection file. Graph Explorer authenticates with IAM, never userinfo.#/connectroute resolves the link once on entry and acts on the result. It still runs after default connections have loaded, becauseAppStatusLoadergates the route behind a loading state while the connection store is empty. Order: preload IndexedDB → fetch default connections → resolve the link → redirect.graphDbUrl(case-insensitive) +queryEngine+ auth posture. Auth posture is identity-bearing: whether IAM is on, and when on, the region and service type. If multiple match, prefer the already-active connection, then tiebreak byname, then fall back to first found. If zero match, prompt for creation.nameparameter when given, otherwise derive one from thegraphDbUrlhostname. The derived name is part of the link's identity, not just a display fallback: a nameless link identifies the connection it would have created, so reopening it returns to that connection even after the user adds a second connection to the same endpoint under a name of their own. The name is editable in the create form.replace, so the#/connectentry never enters history. This replaces the earlierwindow.history.replaceStateapproach.Modules
null. Encapsulates the full priority logic (filter by graphDbUrl + queryEngine + auth posture → prefer active → tiebreak by name → first found).Testing Decisions
Good tests for this feature verify external behavior (given this link and this connection state, what connection is activated, what is shown) rather than implementation details (which atoms were set, which internal functions were called).
Modules to test
defaultConnection.test.ts(schema validation pattern withDefaultConnectionDataSchema).exportedGraph.test.ts(isMatchingConnectiontests).Out of Scope
postMessage-based communication with parent applicationsgraphDbUrl(security risk)fetchTimeoutMsornodeExpansionLimitas URL parameters (too implementation-specific for external callers)Further Notes
graphDbUrlas the single required field.isMatchingConnectionfunction inexportedGraph.tsis prior art for matching logic, but the new matcher module should be independent since post-Unify Docker image by removing SageMaker variant #1773 the connection model changes.graphDbUrlin particular contains characters (:,/) that are invalid in query string values without encoding.docs/adr/20260612-connection-links.md.Related Issues