diff --git a/.github/workflows/build_docker.yml b/.github/workflows/build_docker.yml
index 270a358d4..c07d927e7 100644
--- a/.github/workflows/build_docker.yml
+++ b/.github/workflows/build_docker.yml
@@ -70,7 +70,7 @@ jobs:
IMAGE_TAG: ${{ steps.get-image-tag.outputs.image_tag }}
run: |
docker build -t $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:$IMAGE_TAG .
- docker build --build-arg NEPTUNE_NOTEBOOK=true -t $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:sagemaker-$IMAGE_TAG .
+ docker tag $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:$IMAGE_TAG $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:sagemaker-$IMAGE_TAG
docker push $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:$IMAGE_TAG
docker push $REGISTRY/$REGISTRY_ALIAS/$REPOSITORY:sagemaker-$IMAGE_TAG
diff --git a/.github/workflows/test_build_docker.yml b/.github/workflows/test_build_docker.yml
index a41d37674..bc95bada2 100644
--- a/.github/workflows/test_build_docker.yml
+++ b/.github/workflows/test_build_docker.yml
@@ -24,7 +24,6 @@ jobs:
- name: Build Docker image
run: |
docker build -t test-image .
- docker build -t test-image-neptune --build-arg NEPTUNE_NOTEBOOK=true .
- name: Scan Docker image for vulnerabilities
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
@@ -36,7 +35,6 @@ jobs:
- name: Ensure openSSL is installed
run: |
docker run --rm --entrypoint="" test-image openssl --version
- docker run --rm --entrypoint="" test-image-neptune openssl --version
- name: Verify unnecessary packages are removed
run: |
@@ -48,14 +46,6 @@ jobs:
(command -v yum && echo "FAIL: yum found" && exit 1) || echo "✓ yum removed"
(command -v dnf && echo "FAIL: dnf found" && exit 1) || echo "✓ dnf removed"
'
- docker run --rm --entrypoint="" test-image-neptune sh -c '
- (command -v npm && echo "FAIL: npm found" && exit 1) || echo "✓ npm removed"
- (command -v pnpm && echo "FAIL: pnpm found" && exit 1) || echo "✓ pnpm removed"
- (command -v corepack && echo "FAIL: corepack found" && exit 1) || echo "✓ corepack removed"
- (command -v python3 && echo "FAIL: python3 found" && exit 1) || echo "✓ python3 removed"
- (command -v yum && echo "FAIL: yum found" && exit 1) || echo "✓ yum removed"
- (command -v dnf && echo "FAIL: dnf found" && exit 1) || echo "✓ dnf removed"
- '
- name: Verify server starts and responds
run: |
diff --git a/CONTEXT.md b/CONTEXT.md
index 8c65c6fb8..865ed15c9 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -13,8 +13,8 @@ A directed relationship between two vertices (source → target), with a type an
_Avoid_: Relationship, link
**Connection**:
-A saved database profile — the URL, query engine, authentication settings, and proxy routing needed to reach a graph database. Users create and manage these in the UI.
-_Avoid_: Configuration (legacy term being phased out — previously bundled connection + schema + user preferences into one object)
+A saved database profile — the graph database endpoint URL (`graphDbUrl`), query language, and optional IAM authentication settings. The client always reaches the database through the same-origin **Proxy Server**, so no proxy endpoint is configured. Users create and manage these in the UI.
+_Avoid_: Configuration (legacy term being phased out — previously bundled connection + schema + user preferences into one object); proxy endpoint / `proxyConnection` (removed — see ADR `unify-docker-image-remove-sagemaker-variant`)
**Query Language**:
The graph query protocol a Connection uses — one of Gremlin, openCypher, or SPARQL. Determines which Explorer is instantiated and implies the graph type (Gremlin/openCypher → property graph, SPARQL → RDF).
@@ -70,6 +70,10 @@ _Avoid_: Model, structure
The on-disk JSON format a user gets when they export a Connection (`saveConfigurationToFile`), and which import consumes. It bundles the connection config with a snapshot of the Schema (`lastUpdate` is an ISO string on disk). A single Zod schema in `parseConnectionFile.ts` is the source of truth: both the writer and the importer target the same inferred type (`ExportedConnectionFile`). The writer assigns a `Date` for `lastUpdate` and `JSON.stringify` serializes it to the ISO string; the parser coerces it back via `z.coerce.date()`. The schema is lenient (every level is a `looseObject`), so unknown and legacy fields — styling, `__inferred`/`__matches` on prefixes, attribute `dataType` — pass through untouched. It is intentionally decoupled from the in-memory configuration and from the IndexedDB storage shape, so the wire format can evolve independently. On import it is split — the connection lands in `configurationAtom`, the schema in `schemaAtom`.
_Avoid_: Configuration file (the wire format is not the in-memory or persisted shape)
+**Proxy Server**:
+The Node.js server that serves the frontend (mounted at `/explorer`) and proxies all database requests (mounted at `/`). The client resolves API endpoints relative to its own origin via `apiUrl()` — `../endpoint` from the static mount — so the frontend and proxy are always same-origin. This means every database request routes through the Proxy Server, which has network access to the database and handles SigV4 signing. See ADR `unify-docker-image-remove-sagemaker-variant`.
+_Avoid_: proxy endpoint URL (no longer user-configured)
+
## Relationships
- A **Connection** has exactly one **Query Language**
@@ -100,3 +104,4 @@ _Avoid_: Configuration file (the wire format is not the in-memory or persisted s
- A Connection's data now has three distinct shapes that look similar but must not be conflated: the **Exported Connection File** (on-disk wire format), the in-memory configuration, and the IndexedDB storage shape. They are being separated into explicit types so each can evolve independently.
- "Node" means **Vertex** in code but is the preferred UI term for property graphs — resolved: use **Vertex** in code, "node" in UI copy.
- "Attribute" vs "Property" — resolved: **Property** is canonical, "attribute" is legacy code term being phased out.
+- Connection URL field — `url` and `proxyConnection` are legacy fields that survive only in `LegacyConnectionConfig` and are migrated at read time to the canonical `graphDbUrl` (see `migrateLegacyConnection`, ADR `unify-docker-image-remove-sagemaker-variant`). Use `graphDbUrl` in code.
diff --git a/Dockerfile b/Dockerfile
index 5644d408a..a9cc658ab 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -20,32 +20,9 @@ RUN yum update -y --releasever 2023.12.20260608 && \
rm -rf /var/cache/yum /var/cache/dnf
FROM base
-ARG NEPTUNE_NOTEBOOK
-ENV NEPTUNE_NOTEBOOK=$NEPTUNE_NOTEBOOK
ENV HOME=/graph-explorer
-# Conditionally set the following environment values using +/- variable expansion
-# https://docs.docker.com/reference/dockerfile/#environment-replacement
-#
-# If NEPTUNE_NOTEBOOK value is set then
-# - GRAPH_EXP_ENV_ROOT_FOLDER = /proxy/9250/explorer
-# - PROXY_SERVER_HTTP_PORT = 9250
-# - LOG_STYLE = cloudwatch
-# Else the values are the defaults
-# - GRAPH_EXP_ENV_ROOT_FOLDER = /explorer
-# - PROXY_SERVER_HTTP_PORT = 80
-# - LOG_STYLE = default
-
-ENV GRAPH_EXP_ENV_ROOT_FOLDER=${NEPTUNE_NOTEBOOK:+/proxy/9250/explorer}
-ENV GRAPH_EXP_ENV_ROOT_FOLDER=${GRAPH_EXP_ENV_ROOT_FOLDER:-/explorer}
-
-ENV PROXY_SERVER_HTTP_PORT=${NEPTUNE_NOTEBOOK:+9250}
-ENV PROXY_SERVER_HTTP_PORT=${PROXY_SERVER_HTTP_PORT:-80}
-
-ENV LOG_STYLE=${NEPTUNE_NOTEBOOK:+cloudwatch}
-ENV LOG_STYLE=${LOG_STYLE:-default}
-
WORKDIR /
COPY . /graph-explorer/
WORKDIR /graph-explorer
diff --git a/docs/adr/20260519-unify-docker-image-remove-sagemaker-variant.md b/docs/adr/20260519-unify-docker-image-remove-sagemaker-variant.md
new file mode 100644
index 000000000..4e6881ebe
--- /dev/null
+++ b/docs/adr/20260519-unify-docker-image-remove-sagemaker-variant.md
@@ -0,0 +1,144 @@
+# Unify Docker Image by Removing SageMaker Variant
+
+## Status
+
+Accepted
+
+## Context
+
+Graph Explorer ships two Docker images from the same Dockerfile: a main image and a
+SageMaker image built with `--build-arg NEPTUNE_NOTEBOOK=true`. The build argument
+bakes different environment variable defaults into the image (port 9250 instead of 80,
+cloudwatch logging, a different Vite `base` path for static assets). The SageMaker
+lifecycle script already passes all these values as runtime environment variables,
+making the build-time split unnecessary.
+
+Additionally, the client requires users to manually specify a "Public or Proxy
+Endpoint" URL even though the proxy server and UI are always served from the same
+origin. This creates confusion and an extra configuration step that can be derived
+automatically.
+
+## Decision
+
+Eliminate the separate SageMaker image by:
+
+1. **Using relative asset paths** — set Vite `base: "./"` and add ``
+ to `index.html`. This makes the compiled frontend work behind any reverse proxy
+ without build-time knowledge of the path prefix.
+
+2. **Using relative fetch paths** — the client fetches API routes (sparql, gremlin,
+ openCypher, defaultConnection, etc.) via `new URL("../sparql", document.baseURI)`
+ instead of constructing absolute URLs from a configured proxy endpoint. The server
+ mounts static files at `/explorer` and API routes at `/`, so `../` from the static
+ directory always resolves to the API root.
+
+3. **Always routing through the proxy** — remove the `proxyConnection` toggle and
+ `url` (proxy endpoint) from the connection model. The client always sends requests
+ to the same-origin proxy server. The connection config simplifies to: database
+ endpoint, query engine, and optional IAM settings.
+
+4. **Moving SageMaker defaults to runtime** — `process-environment.sh` reads
+ `NEPTUNE_NOTEBOOK=true` at container startup and writes port/log-style/SSL
+ settings to `.env`. The Dockerfile no longer sets these, allowing the app's
+ built-in defaults (port 80, default log style) to apply when the variable is absent.
+
+5. **Publishing dual tags during transition** — CI builds one image and publishes it
+ under both the regular tag and the `sagemaker-*` tag, so existing lifecycle scripts
+ continue working without modification.
+
+## Consequences
+
+### Positive
+
+- One image to build, test, scan, and publish — halves CI time for Docker.
+- Users no longer need to figure out or configure the proxy server URL.
+- The connection form simplifies to just the database endpoint and auth settings.
+- Deployments behind arbitrary reverse proxies (not just Jupyter) work without
+ build-time configuration.
+- Removes ~20 lines of conditional Dockerfile logic and the two-path defaultConnection
+ fallback hack in the client.
+
+### Negative
+
+- Relative paths create a fixed contract: the API root is always one directory above
+ the static files mount. This is enforced in one place (`server-config.ts`) so drift
+ is unlikely but possible.
+- Legacy stored connections (localStorage) need a read-time migration:
+ `graphDbUrl = old.proxyConnection ? old.graphDbUrl : old.url`.
+- The `sagemaker-*` tags must be published for several release cycles until existing
+ deployed lifecycle scripts are updated.
+- The proxy server must have network access to the target database. Deployments in
+ restricted networks (e.g., private subnets without a NAT gateway) cannot reach
+ databases outside that network — even if the user's browser previously could via
+ direct connections. Users in this scenario need to add network routing.
+
+### Neutral
+
+- `NEPTUNE_NOTEBOOK` remains as a runtime convenience preset (sets port, log style,
+ disables SSL). It is not written to `.env` itself — only its side effects are
+ applied.
+- Extra environment variables passed by old deployments (`PUBLIC_OR_PROXY_ENDPOINT`,
+ `USING_PROXY_SERVER`) are silently ignored — no errors.
+
+## Changes Required
+
+### Dockerfile
+
+- Remove `ARG NEPTUNE_NOTEBOOK` and all conditional ENV logic
+- Remove `GRAPH_EXP_ENV_ROOT_FOLDER`
+- Remove `ENV PROXY_SERVER_HTTP_PORT` and `ENV LOG_STYLE` (defaults from `env.ts`)
+- Keep `EXPOSE 80`, `EXPOSE 443`, `EXPOSE 9250`
+
+### Frontend Build
+
+- `vite.config.ts`: `base: "./"`
+- `index.html`: add ``
+
+### Client Code
+
+- Explorers use `new URL("../sparql", document.baseURI)` etc.
+- `defaultConnection.ts`: single relative fetch, remove SageMaker fallback
+- `RELOAD_URL` becomes `"."`
+- Remove `proxyConnection` and `url` from `ConnectionConfig`
+- `graphDbUrl` is the canonical database URL field
+- `fetchDatabaseRequest.ts`: always send `graph-db-connection-url` header
+- Read-time normalization for legacy data
+
+### Client UI
+
+- Connection form: remove proxy endpoint and proxy toggle, always show "Graph
+ Connection URL" field and IAM toggle
+- Connection display components: simplify to `graphDbUrl`
+
+### Server
+
+- `process-environment.sh`:
+ - Remove `PUBLIC_OR_PROXY_ENDPOINT` / `USING_PROXY_SERVER` handling
+ - When `NEPTUNE_NOTEBOOK=true`: write `PROXY_SERVER_HTTP_PORT=9250` and
+ `LOG_STYLE=cloudwatch` to `.env` (respecting explicit overrides), force SSL off
+ - Stop writing `NEPTUNE_NOTEBOOK` to `.env`
+
+### CI
+
+- Build one image, publish under regular + `sagemaker-*` tags
+- Remove `--build-arg NEPTUNE_NOTEBOOK=true` step
+
+### Lifecycle Script
+
+Existing lifecycle scripts continue working unchanged — they pull `sagemaker-*` tags
+(now an alias for the regular image) and pass `PUBLIC_OR_PROXY_ENDPOINT` /
+`USING_PROXY_SERVER` env vars which the unified image silently ignores.
+
+The bundled example script in this repo is updated to:
+
+- Pull regular tag instead of `sagemaker-*`
+- Remove `PUBLIC_OR_PROXY_ENDPOINT` and `USING_PROXY_SERVER` from `docker run`
+
+### Documentation
+
+- `docs/references/configuration.md`
+- `docs/guides/deploy-to-ecs-fargate.md`
+- `docs/guides/deploy-to-sagemaker.md`
+- `docs/guides/deploy-with-docker.md`
+- `docs/features/connections.md`
+- `docs/architecture.md`
diff --git a/docs/architecture.md b/docs/architecture.md
index e107ea4ec..a82f8c873 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -13,15 +13,16 @@ Graph Explorer is a client-heavy web application with a thin backend proxy. The
graph LR
Browser["Browser\n(React)"] -- HTTP --> Proxy["Proxy Server\n(Express)"]
Proxy -- HTTP --> DB["Graph Database\n(Neptune, etc.)"]
- Browser -. direct .-> DB
Browser -- persistence --> IDB["IndexedDB\n(localforage)"]
```
-The React client constructs queries and sends them through the proxy server, which forwards requests to the graph database. When connecting to Amazon Neptune, the proxy signs requests with AWS SigV4 credentials. For non-Neptune databases, the proxy is optional — the client can connect directly to a publicly accessible endpoint (shown as the dotted line above).
+The React client constructs queries and sends them through the proxy server using relative URLs, which forwards requests to the graph database. When connecting to Amazon Neptune, the proxy signs requests with AWS SigV4 credentials.
The proxy does not store any user data — all preferences, connections, and query history live in the browser's IndexedDB.
-This split exists because browsers cannot perform SigV4 signing directly (it requires AWS credentials that should not be exposed to the client), and because the proxy can run inside a VPC alongside the database while the browser runs outside it.
+This architecture allows the app to work behind any reverse proxy (SageMaker, custom paths) without build-time configuration, since the client resolves API endpoints relative to its own location. The proxy can run inside a VPC alongside the database while the browser runs outside it.
+
+Because all requests flow through the proxy, the server must have network access to the target database. If the server is in a restricted network (e.g., a private subnet with no NAT gateway), it will not be able to reach databases outside that network even if the user's browser could reach them directly.
## Monorepo Structure
diff --git a/docs/features/connections.md b/docs/features/connections.md
index 5c891db5b..ba4bc310b 100644
--- a/docs/features/connections.md
+++ b/docs/features/connections.md
@@ -10,14 +10,11 @@ For guides on connecting to specific databases, see [Connecting to databases](..
- **Name:** Enter a name for your connection (e.g., `MyNeptuneCluster`).
- **Query Language:** Choose a query language that corresponds to your graph database.
-- **Public or proxy endpoint:** Provide the publicly accessible endpoint URL for a graph database, e.g., Gremlin Server. If connecting to Amazon Neptune, then provide a proxy endpoint URL that is accessible from outside the VPC, e.g., EC2.
- - **Note:** For connecting to Amazon Neptune, ensure that the graph connection URL is in the format `https://[NEPTUNE_ENDPOINT]:8182`, and that the proxy endpoint URL is either `https://[EC2_PUBLIC_HOSTNAME]:443` or `http://[EC2_PUBLIC_HOSTNAME]:80`, depending on the protocol used. Ensure that you don't end either of the URLs with `/`.
-- **Using proxy server:** Check this box if using a proxy endpoint.
-- **Graph connection URL:** Provide the endpoint for the graph database
-- **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)
-- **Fetch Timeout:** Specify the timeout for the fetch request
+- **Graph Connection URL:** Provide the endpoint URL for your graph database (e.g., `https://[NEPTUNE_ENDPOINT]:8182`). Ensure that the URL does not end with `/`.
+- **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 (`neptune-db` or `neptune-graph`).
+- **AWS Region:** Specify the AWS region where the Neptune cluster is hosted (e.g., us-east-1).
+- **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.
## Available Connections
diff --git a/docs/guides/connecting-to-gremlin-server.md b/docs/guides/connecting-to-gremlin-server.md
index 9d03582a9..188a32eb5 100644
--- a/docs/guides/connecting-to-gremlin-server.md
+++ b/docs/guides/connecting-to-gremlin-server.md
@@ -15,8 +15,6 @@ Then open Graph Explorer and add a new connection with the following settings:
- Name: `Gremlin Server`
- Query Language: `Gremlin`
-- Public or Proxy Endpoint: `https://localhost`
-- Using Proxy Server: `true`
- Graph Connection URL: `http://localhost:8182`
## Enable REST
diff --git a/docs/guides/connecting-to-neptune.md b/docs/guides/connecting-to-neptune.md
index ae19a8099..498c3b96a 100644
--- a/docs/guides/connecting-to-neptune.md
+++ b/docs/guides/connecting-to-neptune.md
@@ -8,8 +8,6 @@ Graph Explorer connects to Amazon Neptune through its proxy server, which forwar
- Name: `My Neptune Cluster`
- Query Language: Choose the query language for your graph
-- Public or Proxy Endpoint: `https://localhost` (or wherever Graph Explorer is hosted)
-- Using Proxy Server: `true`
- Graph Connection URL: `https://{your-cluster-endpoint}:8182`
- AWS IAM Auth Enabled: `true` if IAM authentication is enabled on your cluster
- Service Type: `neptune-db` (or `neptune-graph` for Neptune Analytics)
diff --git a/docs/guides/deploy-to-ecs-fargate.md b/docs/guides/deploy-to-ecs-fargate.md
index 6d8158583..5cc8e2e72 100644
--- a/docs/guides/deploy-to-ecs-fargate.md
+++ b/docs/guides/deploy-to-ecs-fargate.md
@@ -107,14 +107,6 @@ After the request is processed, the console will return you to your certificate
"name": "IAM",
"value": "false"
},
- {
- "name": "USING_PROXY_SERVER",
- "value": "true"
- },
- {
- "name": "PUBLIC_OR_PROXY_ENDPOINT",
- "value": "https://{FQDN_from_step3}"
- },
{
"name": "HOST",
"value": "localhost"
@@ -164,7 +156,6 @@ After the request is processed, the console will return you to your certificate
- `GRAPH_TYPE`: The query language for your initial connection.
- `IAM`: Set this to `true` to use SigV4 signed requests, if your Neptune cluster has IAM db authentication enabled.
- `GRAPH_CONNECTION_URL`: Set this as `https://{NEPTUNE_ENDPOINT}:8182`.
- - `PUBLIC_OR_PROXY_ENDPOINT`: Set this as `https://{Domain name set in Step 5 of "Request an ACM Public Certificate"}`.
- `SERVICE_TYPE`: Set this as `neptune-db` for Neptune database or `neptune-graph` for Neptune Analytics.
6. Click **Create**.
diff --git a/docs/guides/deploy-to-sagemaker.md b/docs/guides/deploy-to-sagemaker.md
index 7898df176..96acff624 100644
--- a/docs/guides/deploy-to-sagemaker.md
+++ b/docs/guides/deploy-to-sagemaker.md
@@ -14,6 +14,12 @@ When the notebook has been started and is in "Ready" state, you can access Graph
https://graph-explorer-notebook-name.notebook.us-west-2.sagemaker.aws/proxy/9250/explorer/
```
+## Network Requirements
+
+All database requests from Graph Explorer are routed through the proxy server running on the SageMaker notebook instance. This means the instance must have network access to any database you want to explore.
+
+If your notebook instance is in a private subnet without a NAT gateway or internet gateway, it will not be able to reach databases outside the VPC. To connect to external databases, ensure the instance has the appropriate network routing (VPC peering, NAT gateway, transit gateway, etc.).
+
## Minimum Database Permissions
By default, the permission policy for the IAM role of the SageMaker instance will have full access to the Neptune Database or Neptune Analytics instance. This means queries executed within Graph Explorer could contain mutations.
diff --git a/docs/guides/deploy-to-sagemaker/install-graph-explorer-lc.sh b/docs/guides/deploy-to-sagemaker/install-graph-explorer-lc.sh
index 06fa3b31b..efd9b4563 100644
--- a/docs/guides/deploy-to-sagemaker/install-graph-explorer-lc.sh
+++ b/docs/guides/deploy-to-sagemaker/install-graph-explorer-lc.sh
@@ -130,17 +130,14 @@ LATEST_ECR_RELEASE=$(curl -k -H "Authorization: Bearer $ECR_TOKEN" https://publi
echo "Pulling and starting graph-explorer..."
if [[ ${EXPLORER_VERSION} == "" ]]; then
- EXPLORER_ECR_TAG=sagemaker-${LATEST_ECR_RELEASE}
+ EXPLORER_ECR_TAG=${LATEST_ECR_RELEASE}
else
- if [[ ${EXPLORER_VERSION//./} -ge 140 ]]; then
- EXPLORER_ECR_TAG=sagemaker-${EXPLORER_VERSION}
- elif [[ ${EXPLORER_VERSION} == *latest* ]]; then
- EXPLORER_ECR_TAG=sagemaker-latest-SNAPSHOT
+ if [[ ${EXPLORER_VERSION} == *latest* ]]; then
+ EXPLORER_ECR_TAG=latest-SNAPSHOT
elif [[ ${EXPLORER_VERSION} == *dev* ]]; then
- EXPLORER_ECR_TAG=sagemaker-dev
+ EXPLORER_ECR_TAG=dev
else
- echo "Specified Graph Explorer version does not support use on SageMaker. Defaulting to latest release."
- EXPLORER_ECR_TAG=sagemaker-${LATEST_ECR_RELEASE}
+ EXPLORER_ECR_TAG=${EXPLORER_VERSION}
fi
fi
echo "Using explorer image tag: ${EXPLORER_ECR_TAG}"
@@ -155,13 +152,10 @@ start_graph_explorer_with_cw_logs() {
--log-opt awslogs-multiline-pattern='^(INFO|DEBUG|ERROR|WARN|TRACE|FATAL)' \
--env LOG_LEVEL=debug \
--env HOST=127.0.0.1 \
- --env PUBLIC_OR_PROXY_ENDPOINT=${EXPLORER_URI} \
--env GRAPH_CONNECTION_URL=${NEPTUNE_URI} \
- --env USING_PROXY_SERVER=true \
--env IAM=${IAM} \
--env AWS_REGION=${AWS_REGION} \
--env SERVICE_TYPE=${SERVICE} \
- --env PROXY_SERVER_HTTPS_CONNECTION=false \
--env NEPTUNE_NOTEBOOK=true public.ecr.aws/neptune/graph-explorer:${EXPLORER_ECR_TAG}
}
@@ -170,13 +164,10 @@ start_graph_explorer_with_default_logs() {
--restart always \
--env LOG_LEVEL=debug \
--env HOST=127.0.0.1 \
- --env PUBLIC_OR_PROXY_ENDPOINT=${EXPLORER_URI} \
--env GRAPH_CONNECTION_URL=${NEPTUNE_URI} \
- --env USING_PROXY_SERVER=true \
--env IAM=${IAM} \
--env AWS_REGION=${AWS_REGION} \
--env SERVICE_TYPE=${SERVICE} \
- --env PROXY_SERVER_HTTPS_CONNECTION=false \
--env NEPTUNE_NOTEBOOK=true public.ecr.aws/neptune/graph-explorer:${EXPLORER_ECR_TAG}
}
diff --git a/docs/guides/deploy-with-docker.md b/docs/guides/deploy-with-docker.md
index cee4c4d24..c8116555a 100644
--- a/docs/guides/deploy-with-docker.md
+++ b/docs/guides/deploy-with-docker.md
@@ -9,7 +9,7 @@ You can find the latest version of the image on
> [!NOTE]
>
-> Make sure to use the version of the image that does not include `sagemaker` in the tag.
+> Both the regular and `sagemaker-*` image tags contain the same image. You can use either.
## Prerequisites
diff --git a/docs/references/configuration.md b/docs/references/configuration.md
index 812ea4f56..c542edae2 100644
--- a/docs/references/configuration.md
+++ b/docs/references/configuration.md
@@ -8,16 +8,6 @@ All environment variables for configuring Graph Explorer, organized by concern.
These variables control server behavior, networking, and security.
-### `GRAPH_EXP_ENV_ROOT_FOLDER`
-
-Base path used to serve the `graph-explorer` front end application.
-
-Example: `/explorer`
-
-- Optional
-- Default: `/`
-- Type: `string`
-
### `HOST`
The public hostname of the server. This is used to generate the self-signed SSL certificate at container startup.
@@ -115,6 +105,14 @@ Override path for the folder containing `.env` and `defaultConnection.json`. Whe
- Default: `` (`packages/graph-explorer`)
- Type: `string`
+### `NEPTUNE_NOTEBOOK`
+
+Runtime convenience preset for SageMaker/Jupyter deployments. When set to `true`, configures port 9250, cloudwatch logging, and disables SSL automatically.
+
+- Optional
+- Default: not set
+- Type: `boolean`
+
## Default Connection
To provide a default connection such that initial loads of Graph Explorer always result with the same starting connection, modify the `docker run ...` command to either take in a JSON configuration or runtime environment variables. If you provide both a JSON configuration and environmental variables, the JSON will be prioritized.
@@ -124,19 +122,16 @@ To provide a default connection such that initial loads of Graph Explorer always
These are the valid environment variables used for the default connection, their defaults, and their descriptions.
- Required:
- - `PUBLIC_OR_PROXY_ENDPOINT` - `None`
+ - `GRAPH_CONNECTION_URL` - `None` - The URL of the graph database endpoint.
- Optional
- `GRAPH_TYPE` - `None` - If not specified, multiple connections will be created for every available query language.
- - `USING_PROXY_SERVER` - `False`
- `IAM` - `False`
- `GRAPH_EXP_HTTPS_CONNECTION` - `True` - Controls whether Graph Explorer uses SSL or not
- `PROXY_SERVER_HTTPS_CONNECTION` - `True` - Controls whether the server uses SSL or not
- `GRAPH_EXP_FETCH_REQUEST_TIMEOUT` - `240000` - Controls the timeout for the fetch request. Measured in milliseconds (i.e. 240000 is 240 seconds or 4 minutes).
- `GRAPH_EXP_NODE_EXPANSION_LIMIT` - `None` - Controls the limit for node counts and expansion queries.
- Conditionally Required:
- - Required if `USING_PROXY_SERVER=True`
- - `GRAPH_CONNECTION_URL` - `None`
- - Required if `USING_PROXY_SERVER=True` and `IAM=True`
+ - Required if `IAM=True`
- `AWS_REGION` - `None`
- `SERVICE_TYPE` - `neptune-db`, Set this as `neptune-db` for Neptune database or `neptune-graph` for Neptune Analytics.
@@ -146,9 +141,7 @@ First, create a `config.json` file containing values for the connection attribut
```json
{
- "PUBLIC_OR_PROXY_ENDPOINT": "https://public-endpoint",
"GRAPH_CONNECTION_URL": "https://{your-cluster-id}.us-west-2.neptune.amazonaws.com:8182",
- "USING_PROXY_SERVER": true,
"IAM": true,
"SERVICE_TYPE": "neptune-db",
"AWS_REGION": "us-west-2",
@@ -178,11 +171,9 @@ Provide the desired connection variables directly to the `docker run` command, a
```bash
docker run -p 80:80 -p 443:443 \
--env HOST={hostname-or-ip-address} \
- --env PUBLIC_OR_PROXY_ENDPOINT=https://public-endpoint \
- --env GRAPH_TYPE=gremlin \
- --env USING_PROXY_SERVER=true \
- --env IAM=false \
--env GRAPH_CONNECTION_URL=https://{your-cluster-id}.us-west-2.neptune.amazonaws.com:8182 \
+ --env GRAPH_TYPE=gremlin \
+ --env IAM=true \
--env AWS_REGION=us-west-2 \
--env SERVICE_TYPE=neptune-db \
--env PROXY_SERVER_HTTPS_CONNECTION=true \
diff --git a/packages/graph-explorer-proxy-server/src/process-environment.test.ts b/packages/graph-explorer-proxy-server/src/process-environment.test.ts
index 3fb5552a5..828c8e068 100644
--- a/packages/graph-explorer-proxy-server/src/process-environment.test.ts
+++ b/packages/graph-explorer-proxy-server/src/process-environment.test.ts
@@ -84,8 +84,8 @@ describe("process-environment.sh", () => {
});
});
- describe("NEPTUNE_NOTEBOOK=true forces SSL off", () => {
- it("overrides both HTTPS vars to false", () => {
+ describe("NEPTUNE_NOTEBOOK=true", () => {
+ it("forces both HTTPS vars to false", () => {
const { envFile } = runScript(workDir, {
NEPTUNE_NOTEBOOK: "true",
});
@@ -93,11 +93,41 @@ describe("process-environment.sh", () => {
expect(envFile).toContain("GRAPH_EXP_HTTPS_CONNECTION=false");
});
- it("writes NEPTUNE_NOTEBOOK=true to .env", () => {
+ it("writes PROXY_SERVER_HTTP_PORT=9250 to .env", () => {
const { envFile } = runScript(workDir, {
NEPTUNE_NOTEBOOK: "true",
});
- expect(envFile).toContain("NEPTUNE_NOTEBOOK=true");
+ expect(envFile).toContain("PROXY_SERVER_HTTP_PORT=9250");
+ });
+
+ it("writes LOG_STYLE=cloudwatch to .env", () => {
+ const { envFile } = runScript(workDir, {
+ NEPTUNE_NOTEBOOK: "true",
+ });
+ expect(envFile).toContain("LOG_STYLE=cloudwatch");
+ });
+
+ it("does not write NEPTUNE_NOTEBOOK to .env", () => {
+ const { envFile } = runScript(workDir, {
+ NEPTUNE_NOTEBOOK: "true",
+ });
+ expect(envFile).not.toContain("NEPTUNE_NOTEBOOK=");
+ });
+
+ it("respects explicit PROXY_SERVER_HTTP_PORT override", () => {
+ const { envFile } = runScript(workDir, {
+ NEPTUNE_NOTEBOOK: "true",
+ PROXY_SERVER_HTTP_PORT: "8080",
+ });
+ expect(envFile).not.toContain("PROXY_SERVER_HTTP_PORT=9250");
+ });
+
+ it("respects explicit LOG_STYLE override", () => {
+ const { envFile } = runScript(workDir, {
+ NEPTUNE_NOTEBOOK: "true",
+ LOG_STYLE: "json",
+ });
+ expect(envFile).not.toContain("LOG_STYLE=cloudwatch");
});
});
@@ -106,16 +136,16 @@ describe("process-environment.sh", () => {
const { envFile } = runScript(workDir, {
NEPTUNE_NOTEBOOK: "false",
});
- expect(envFile).toContain("NEPTUNE_NOTEBOOK=false");
expect(envFile).toContain("PROXY_SERVER_HTTPS_CONNECTION=true");
expect(envFile).toContain("GRAPH_EXP_HTTPS_CONNECTION=true");
});
- });
- describe("NEPTUNE_NOTEBOOK unset", () => {
- it("defaults NEPTUNE_NOTEBOOK to false in .env", () => {
- const { envFile } = runScript(workDir);
- expect(envFile).toContain("NEPTUNE_NOTEBOOK=false");
+ it("does not write port or log style", () => {
+ const { envFile } = runScript(workDir, {
+ NEPTUNE_NOTEBOOK: "false",
+ });
+ expect(envFile).not.toContain("PROXY_SERVER_HTTP_PORT");
+ expect(envFile).not.toContain("LOG_STYLE");
});
});
@@ -124,9 +154,7 @@ describe("process-environment.sh", () => {
fs.writeFileSync(
path.join(workDir, "config.json"),
JSON.stringify({
- PUBLIC_OR_PROXY_ENDPOINT: "https://my-endpoint:8182",
GRAPH_TYPE: "sparql",
- USING_PROXY_SERVER: true,
IAM: true,
GRAPH_CONNECTION_URL: "https://my-db:8182",
AWS_REGION: "us-west-2",
@@ -140,62 +168,55 @@ describe("process-environment.sh", () => {
expect(envFile).toContain("PROXY_SERVER_HTTPS_CONNECTION=false");
expect(envFile).toContain("GRAPH_EXP_HTTPS_CONNECTION=false");
expect(defaultConnection).toMatchObject({
- GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT: "https://my-endpoint:8182",
GRAPH_EXP_GRAPH_TYPE: "sparql",
- GRAPH_EXP_USING_PROXY_SERVER: true,
GRAPH_EXP_IAM: true,
GRAPH_EXP_CONNECTION_URL: "https://my-db:8182",
GRAPH_EXP_AWS_REGION: "us-west-2",
});
});
- it("config.json overrides conflicting env vars", () => {
+ it("config.json values override conflicting env vars", () => {
fs.writeFileSync(
path.join(workDir, "config.json"),
JSON.stringify({
- PUBLIC_OR_PROXY_ENDPOINT: "https://from-config:8182",
GRAPH_TYPE: "sparql",
+ GRAPH_CONNECTION_URL: "https://from-config:8182",
+ AWS_REGION: "us-west-2",
}),
);
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://from-env:8182",
GRAPH_TYPE: "gremlin",
+ GRAPH_CONNECTION_URL: "https://from-env:8182",
+ AWS_REGION: "eu-west-1",
});
- expect(defaultConnection).toHaveProperty(
- "GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT",
- "https://from-config:8182",
- );
- expect(defaultConnection).toHaveProperty(
- "GRAPH_EXP_GRAPH_TYPE",
- "sparql",
- );
+ expect(defaultConnection).toMatchObject({
+ GRAPH_EXP_GRAPH_TYPE: "sparql",
+ GRAPH_EXP_CONNECTION_URL: "https://from-config:8182",
+ GRAPH_EXP_AWS_REGION: "us-west-2",
+ });
});
});
describe("defaultConnection.json generation", () => {
- it("creates defaultConnection.json with GRAPH_EXP_ prefixed fields", () => {
+ it("creates defaultConnection.json when GRAPH_CONNECTION_URL is set", () => {
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "https://db:8182",
GRAPH_TYPE: "gremlin",
- USING_PROXY_SERVER: "true",
IAM: "false",
- GRAPH_CONNECTION_URL: "https://db:8182",
AWS_REGION: "eu-west-1",
});
expect(defaultConnection).toMatchObject({
- GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
GRAPH_EXP_GRAPH_TYPE: "gremlin",
- GRAPH_EXP_USING_PROXY_SERVER: true,
GRAPH_EXP_IAM: false,
GRAPH_EXP_CONNECTION_URL: "https://db:8182",
GRAPH_EXP_AWS_REGION: "eu-west-1",
});
});
- it("does not create defaultConnection.json without PUBLIC_OR_PROXY_ENDPOINT", () => {
+ it("does not create defaultConnection.json without GRAPH_CONNECTION_URL", () => {
const { defaultConnection } = runScript(workDir, {
GRAPH_TYPE: "gremlin",
});
@@ -204,7 +225,7 @@ describe("process-environment.sh", () => {
it("defaults SERVICE_TYPE to neptune-db", () => {
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "https://db:8182",
});
expect(defaultConnection).toHaveProperty(
"GRAPH_EXP_SERVICE_TYPE",
@@ -212,19 +233,9 @@ describe("process-environment.sh", () => {
);
});
- it("defaults USING_PROXY_SERVER to false", () => {
- const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
- });
- expect(defaultConnection).toHaveProperty(
- "GRAPH_EXP_USING_PROXY_SERVER",
- false,
- );
- });
-
it("defaults IAM to false", () => {
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "https://db:8182",
});
expect(defaultConnection).toHaveProperty("GRAPH_EXP_IAM", false);
});
@@ -233,7 +244,7 @@ describe("process-environment.sh", () => {
describe("SERVICE_TYPE=neptune-graph auto-sets openCypher", () => {
it("sets GRAPH_TYPE to openCypher when SERVICE_TYPE is neptune-graph", () => {
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "https://db:8182",
SERVICE_TYPE: "neptune-graph",
});
expect(defaultConnection).toHaveProperty(
@@ -244,7 +255,7 @@ describe("process-environment.sh", () => {
it("does not set GRAPH_TYPE when SERVICE_TYPE is neptune-db and no GRAPH_TYPE given", () => {
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "https://db:8182",
SERVICE_TYPE: "neptune-db",
});
expect(defaultConnection).not.toHaveProperty("GRAPH_EXP_GRAPH_TYPE");
@@ -252,7 +263,7 @@ describe("process-environment.sh", () => {
it("explicit GRAPH_TYPE takes priority over neptune-graph auto-detection", () => {
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "https://db:8182",
SERVICE_TYPE: "neptune-graph",
GRAPH_TYPE: "sparql",
});
@@ -269,16 +280,14 @@ describe("process-environment.sh", () => {
runScript(workDir, {
CONFIGURATION_FOLDER_PATH: customFolder,
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "https://db:8182",
});
- // Files exist at the custom path
expect(fs.existsSync(path.join(customFolder, ".env"))).toBe(true);
expect(
fs.existsSync(path.join(customFolder, "defaultConnection.json")),
).toBe(true);
- // Files do not exist at the default path
const defaultFolder = path.join(workDir, "packages", "graph-explorer");
expect(fs.existsSync(path.join(defaultFolder, ".env"))).toBe(false);
expect(
@@ -288,16 +297,16 @@ describe("process-environment.sh", () => {
});
describe("default values for optional fields", () => {
- it("defaults GRAPH_CONNECTION_URL to empty string", () => {
+ it("defaults GRAPH_CONNECTION_URL to empty string in output", () => {
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "",
});
- expect(defaultConnection).toHaveProperty("GRAPH_EXP_CONNECTION_URL", "");
+ // No defaultConnection generated when GRAPH_CONNECTION_URL is empty
+ expect(defaultConnection).toBeNull();
});
it("preserves path in GRAPH_CONNECTION_URL", () => {
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "http://localhost:8080",
GRAPH_CONNECTION_URL: "http://blazegraph:9999/blazegraph/namespace/kb",
});
expect(defaultConnection).toHaveProperty(
@@ -306,27 +315,16 @@ describe("process-environment.sh", () => {
);
});
- it("preserves trailing slash in GRAPH_CONNECTION_URL", () => {
- const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "http://localhost:8080",
- GRAPH_CONNECTION_URL: "http://blazegraph:9999/blazegraph/namespace/kb/",
- });
- expect(defaultConnection).toHaveProperty(
- "GRAPH_EXP_CONNECTION_URL",
- "http://blazegraph:9999/blazegraph/namespace/kb/",
- );
- });
-
it("defaults AWS_REGION to empty string", () => {
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "https://db:8182",
});
expect(defaultConnection).toHaveProperty("GRAPH_EXP_AWS_REGION", "");
});
it("passes through custom SERVICE_TYPE value", () => {
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "https://db:8182",
SERVICE_TYPE: "neptune-graph",
});
expect(defaultConnection).toHaveProperty(
@@ -357,53 +355,14 @@ describe("process-environment.sh", () => {
);
const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
+ GRAPH_CONNECTION_URL: "https://db:8182",
});
expect(defaultConnection).not.toHaveProperty("OLD_KEY");
expect(defaultConnection).toHaveProperty(
- "GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT",
- "https://endpoint:8182",
- );
- });
- });
-
- describe("defaultConnection.json has all expected keys", () => {
- it("contains exactly the expected keys when all values provided", () => {
- const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
- SERVICE_TYPE: "neptune-db",
- GRAPH_TYPE: "gremlin",
- USING_PROXY_SERVER: "true",
- IAM: "true",
- GRAPH_CONNECTION_URL: "https://db:8182",
- AWS_REGION: "us-east-1",
- });
-
- expect(Object.keys(defaultConnection!).sort()).toEqual([
- "GRAPH_EXP_AWS_REGION",
"GRAPH_EXP_CONNECTION_URL",
- "GRAPH_EXP_GRAPH_TYPE",
- "GRAPH_EXP_IAM",
- "GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT",
- "GRAPH_EXP_SERVICE_TYPE",
- "GRAPH_EXP_USING_PROXY_SERVER",
- ]);
- });
-
- it("omits GRAPH_EXP_GRAPH_TYPE when neither GRAPH_TYPE nor neptune-graph", () => {
- const { defaultConnection } = runScript(workDir, {
- PUBLIC_OR_PROXY_ENDPOINT: "https://endpoint:8182",
- });
-
- expect(Object.keys(defaultConnection!).sort()).toEqual([
- "GRAPH_EXP_AWS_REGION",
- "GRAPH_EXP_CONNECTION_URL",
- "GRAPH_EXP_IAM",
- "GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT",
- "GRAPH_EXP_SERVICE_TYPE",
- "GRAPH_EXP_USING_PROXY_SERVER",
- ]);
+ "https://db:8182",
+ );
});
});
@@ -413,11 +372,6 @@ describe("process-environment.sh", () => {
expect(envFile).toMatch(/^PROXY_SERVER_HTTPS_CONNECTION=true$/m);
});
- it("does not produce commented-out PROXY_SERVER_HTTPS_CONNECTION", () => {
- const { envFile } = runScript(workDir);
- expect(envFile).not.toContain("# PROXY_SERVER_HTTPS_CONNECTION");
- });
-
it("value has no trailing whitespace", () => {
const { envFile } = runScript(workDir, {
PROXY_SERVER_HTTPS_CONNECTION: "false",
diff --git a/packages/graph-explorer/index.html b/packages/graph-explorer/index.html
index 08c10323e..b694c2655 100644
--- a/packages/graph-explorer/index.html
+++ b/packages/graph-explorer/index.html
@@ -1,6 +1,7 @@
+
diff --git a/packages/graph-explorer/src/connector/LoggerConnector.test.ts b/packages/graph-explorer/src/connector/LoggerConnector.test.ts
index b0fc46e6d..929dbef87 100644
--- a/packages/graph-explorer/src/connector/LoggerConnector.test.ts
+++ b/packages/graph-explorer/src/connector/LoggerConnector.test.ts
@@ -1,3 +1,4 @@
+// @vitest-environment happy-dom
import {
ClientLoggerConnector,
ServerLoggerConnector,
@@ -15,52 +16,72 @@ describe("ClientLoggerConnector", () => {
});
describe("ServerLoggerConnector", () => {
- test("should send logs to the server", () => {
+ test("should send logs to the server via relative URL", () => {
const mockFetch = vi.fn().mockResolvedValue({});
vi.stubGlobal("fetch", mockFetch);
+ document.head.innerHTML = '';
- const connector = new ServerLoggerConnector("https://example.com/");
+ const connector = new ServerLoggerConnector();
connector.error("error msg");
- expect(mockFetch).toHaveBeenCalledWith("https://example.com/logger", {
- method: "POST",
- headers: { level: "error", message: JSON.stringify("error msg") },
- });
+ expect(mockFetch).toHaveBeenCalledWith(
+ expect.objectContaining({ href: "https://example.com/logger" }),
+ {
+ method: "POST",
+ headers: { level: "error", message: JSON.stringify("error msg") },
+ },
+ );
connector.warn("warn msg");
- expect(mockFetch).toHaveBeenCalledWith("https://example.com/logger", {
- method: "POST",
- headers: { level: "warn", message: JSON.stringify("warn msg") },
- });
+ expect(mockFetch).toHaveBeenCalledWith(
+ expect.objectContaining({ href: "https://example.com/logger" }),
+ {
+ method: "POST",
+ headers: { level: "warn", message: JSON.stringify("warn msg") },
+ },
+ );
connector.info("info msg");
- expect(mockFetch).toHaveBeenCalledWith("https://example.com/logger", {
- method: "POST",
- headers: { level: "info", message: JSON.stringify("info msg") },
- });
+ expect(mockFetch).toHaveBeenCalledWith(
+ expect.objectContaining({ href: "https://example.com/logger" }),
+ {
+ method: "POST",
+ headers: { level: "info", message: JSON.stringify("info msg") },
+ },
+ );
connector.debug("debug msg");
- expect(mockFetch).toHaveBeenCalledWith("https://example.com/logger", {
- method: "POST",
- headers: { level: "debug", message: JSON.stringify("debug msg") },
- });
+ expect(mockFetch).toHaveBeenCalledWith(
+ expect.objectContaining({ href: "https://example.com/logger" }),
+ {
+ method: "POST",
+ headers: { level: "debug", message: JSON.stringify("debug msg") },
+ },
+ );
connector.trace("trace msg");
- expect(mockFetch).toHaveBeenCalledWith("https://example.com/logger", {
- method: "POST",
- headers: { level: "trace", message: JSON.stringify("trace msg") },
- });
+ expect(mockFetch).toHaveBeenCalledWith(
+ expect.objectContaining({ href: "https://example.com/logger" }),
+ {
+ method: "POST",
+ headers: { level: "trace", message: JSON.stringify("trace msg") },
+ },
+ );
});
- test("should strip trailing slash from connection URL", () => {
+ test("should resolve logger path relative to baseURI", () => {
const mockFetch = vi.fn().mockResolvedValue({});
vi.stubGlobal("fetch", mockFetch);
+ document.head.innerHTML =
+ '';
- const connector = new ServerLoggerConnector("https://example.com/");
+ const connector = new ServerLoggerConnector();
connector.info("test");
expect(mockFetch).toHaveBeenCalledWith(
- "https://example.com/logger",
+ expect.objectContaining({
+ href: "https://example.com/proxy/9250/logger",
+ }),
expect.any(Object),
);
});
diff --git a/packages/graph-explorer/src/connector/LoggerConnector.ts b/packages/graph-explorer/src/connector/LoggerConnector.ts
index 9edc2a4bb..fb192d5c3 100644
--- a/packages/graph-explorer/src/connector/LoggerConnector.ts
+++ b/packages/graph-explorer/src/connector/LoggerConnector.ts
@@ -1,5 +1,7 @@
import { logger } from "@/utils";
+import { apiUrl } from "./utils/apiUrl";
+
export type LogLevel = "error" | "warn" | "info" | "debug" | "trace";
export interface LoggerConnector {
@@ -10,14 +12,11 @@ export interface LoggerConnector {
trace(message: unknown): void;
}
-/** Sends log messages to the server in the connection configuration. */
+/** Sends log messages to the server via relative URL. */
export class ServerLoggerConnector implements LoggerConnector {
- private readonly _baseUrl: string;
private readonly _clientLogger: ClientLoggerConnector;
- constructor(connectionUrl: string) {
- const url = connectionUrl.replace(/\/$/, "");
- this._baseUrl = `${url}/logger`;
+ constructor() {
this._clientLogger = new ClientLoggerConnector();
}
@@ -47,7 +46,7 @@ export class ServerLoggerConnector implements LoggerConnector {
}
private _sendLog(level: LogLevel, message: unknown) {
- return fetch(this._baseUrl, {
+ return fetch(apiUrl("logger"), {
method: "POST",
headers: {
level,
diff --git a/packages/graph-explorer/src/connector/emptyExplorer.ts b/packages/graph-explorer/src/connector/emptyExplorer.ts
index fd4a28220..bb0dacad2 100644
--- a/packages/graph-explorer/src/connector/emptyExplorer.ts
+++ b/packages/graph-explorer/src/connector/emptyExplorer.ts
@@ -6,10 +6,8 @@ import type { Explorer } from "./useGEFetchTypes";
*/
export const emptyExplorer: Explorer = {
connection: {
- url: "",
graphDbUrl: "",
queryEngine: "gremlin",
- proxyConnection: false,
awsAuthEnabled: false,
},
fetchSchema: async () => {
diff --git a/packages/graph-explorer/src/connector/fetchDatabaseRequest.test.ts b/packages/graph-explorer/src/connector/fetchDatabaseRequest.test.ts
index 4a73383aa..b512b05ee 100644
--- a/packages/graph-explorer/src/connector/fetchDatabaseRequest.test.ts
+++ b/packages/graph-explorer/src/connector/fetchDatabaseRequest.test.ts
@@ -8,10 +8,8 @@ function createConnection(
overrides?: Partial,
): NormalizedConnection {
return {
- url: "http://localhost:8182",
queryEngine: "gremlin",
graphDbUrl: "",
- proxyConnection: false,
awsAuthEnabled: false,
...overrides,
};
@@ -104,10 +102,9 @@ describe("fetchDatabaseRequest", () => {
});
describe("header construction", () => {
- it("sets proxy headers when proxyConnection is true", async () => {
+ it("sets graph-db-connection-url header", async () => {
mockFetch.mockResolvedValue(jsonResponse({}));
const conn = createConnection({
- proxyConnection: true,
graphDbUrl: "https://my-neptune:8182",
});
@@ -124,7 +121,7 @@ describe("fetchDatabaseRequest", () => {
it("sets db-query-logging-enabled based on allowLoggingDbQuery", async () => {
mockFetch.mockResolvedValue(jsonResponse({}));
- const conn = createConnection({ proxyConnection: true });
+ const conn = createConnection({ graphDbUrl: "https://db:8182" });
const flags = createFeatureFlags({ allowLoggingDbQuery: true });
await fetchDatabaseRequest(conn, flags, "/query", { method: "POST" });
@@ -162,7 +159,22 @@ describe("fetchDatabaseRequest", () => {
expect(headers["service-type"]).toBe("neptune-db");
});
- it("does not set proxy or AWS headers when both are disabled", async () => {
+ it("always sends graph-db-connection-url header", async () => {
+ mockFetch.mockResolvedValue(jsonResponse({}));
+ const conn = createConnection({
+ graphDbUrl: "https://my-db:8182",
+ });
+
+ await fetchDatabaseRequest(conn, featureFlags, "/query", {
+ method: "POST",
+ });
+
+ const headers = mockFetch.mock.calls[0][1].headers;
+ expect(headers["graph-db-connection-url"]).toBe("https://my-db:8182");
+ expect(headers["db-query-logging-enabled"]).toBe("false");
+ });
+
+ it("does not set AWS headers when awsAuthEnabled is disabled", async () => {
mockFetch.mockResolvedValue(jsonResponse({}));
await fetchDatabaseRequest(connection, featureFlags, "/query", {
@@ -170,14 +182,13 @@ describe("fetchDatabaseRequest", () => {
});
const headers = mockFetch.mock.calls[0][1].headers;
- expect(headers).not.toHaveProperty("graph-db-connection-url");
expect(headers).not.toHaveProperty("aws-neptune-region");
expect(headers).not.toHaveProperty("service-type");
});
it("merges caller-provided headers with auth headers", async () => {
mockFetch.mockResolvedValue(jsonResponse({}));
- const conn = createConnection({ proxyConnection: true });
+ const conn = createConnection({ graphDbUrl: "https://db:8182" });
await fetchDatabaseRequest(conn, featureFlags, "/query", {
method: "POST",
diff --git a/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts b/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts
index 80696820f..e80e36d2d 100644
--- a/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts
+++ b/packages/graph-explorer/src/connector/fetchDatabaseRequest.ts
@@ -53,12 +53,10 @@ function getAuthHeaders(
typeHeaders: HeadersInit | undefined,
) {
const headers: Record = {};
- if (connection.proxyConnection) {
- headers["graph-db-connection-url"] = connection.graphDbUrl || "";
- headers["db-query-logging-enabled"] = String(
- featureFlags.allowLoggingDbQuery,
- );
- }
+ headers["graph-db-connection-url"] = connection.graphDbUrl || "";
+ headers["db-query-logging-enabled"] = String(
+ featureFlags.allowLoggingDbQuery,
+ );
if (connection.awsAuthEnabled) {
headers["aws-neptune-region"] = connection.awsRegion || "";
headers["service-type"] = connection.serviceType || DEFAULT_SERVICE_TYPE;
diff --git a/packages/graph-explorer/src/connector/gremlin/gremlinExplorer.test.ts b/packages/graph-explorer/src/connector/gremlin/gremlinExplorer.test.ts
index 280bc6fbd..4a54a19f9 100644
--- a/packages/graph-explorer/src/connector/gremlin/gremlinExplorer.test.ts
+++ b/packages/graph-explorer/src/connector/gremlin/gremlinExplorer.test.ts
@@ -1,3 +1,4 @@
+// @vitest-environment happy-dom
import type { FeatureFlags, NormalizedConnection } from "@/core";
import { createGremlinExplorer } from "./gremlinExplorer";
@@ -6,10 +7,8 @@ function createConnection(
overrides?: Partial,
): NormalizedConnection {
return {
- url: "http://localhost:8182",
queryEngine: "gremlin",
- graphDbUrl: "",
- proxyConnection: false,
+ graphDbUrl: "https://my-neptune:8182",
awsAuthEnabled: false,
...overrides,
};
@@ -41,6 +40,7 @@ describe("createGremlinExplorer", () => {
beforeEach(() => {
mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);
+ document.head.innerHTML = '';
});
afterEach(() => {
@@ -72,7 +72,7 @@ describe("createGremlinExplorer", () => {
await explorer.fetchSchema();
expect(mockFetch).toHaveBeenCalledWith(
- "http://localhost:8182/pg/statistics/summary?mode=basic",
+ new URL("http://localhost/pg/statistics/summary?mode=basic"),
expect.objectContaining({ method: "GET" }),
);
});
diff --git a/packages/graph-explorer/src/connector/gremlin/gremlinExplorer.ts b/packages/graph-explorer/src/connector/gremlin/gremlinExplorer.ts
index 874ac404d..6180babb1 100644
--- a/packages/graph-explorer/src/connector/gremlin/gremlinExplorer.ts
+++ b/packages/graph-explorer/src/connector/gremlin/gremlinExplorer.ts
@@ -9,6 +9,7 @@ import type { Explorer, ExplorerRequestOptions } from "../useGEFetchTypes";
import type { GraphSummary, GremlinFetch } from "./types";
import { fetchDatabaseRequest } from "../fetchDatabaseRequest";
+import { apiUrl } from "../utils/apiUrl";
import { edgeDetails } from "./edgeDetails";
import fetchEdgeConnections from "./fetchEdgeConnections";
import fetchNeighbors from "./fetchNeighbors";
@@ -31,21 +32,16 @@ function _gremlinFetch(
"Content-Type": "application/json",
Accept: "application/vnd.gremlin-v3.0+json",
};
- if (options?.queryId && connection.proxyConnection === true) {
+ if (options?.queryId) {
headers.queryId = options.queryId;
}
- return fetchDatabaseRequest(
- connection,
- featureFlags,
- `${connection.url}/gremlin`,
- {
- method: "POST",
- headers,
- body,
- ...options,
- },
- );
+ return fetchDatabaseRequest(connection, featureFlags, apiUrl("gremlin"), {
+ method: "POST",
+ headers,
+ body,
+ ...options,
+ });
};
}
@@ -58,7 +54,7 @@ async function fetchSummary(
const response = await fetchDatabaseRequest(
connection,
featureFlags,
- `${connection.url}/pg/statistics/summary?mode=basic`,
+ apiUrl("pg/statistics/summary?mode=basic"),
{
method: "GET",
...options,
diff --git a/packages/graph-explorer/src/connector/openCypher/openCypherExplorer.test.ts b/packages/graph-explorer/src/connector/openCypher/openCypherExplorer.test.ts
index 90524d8b9..1946d5d9d 100644
--- a/packages/graph-explorer/src/connector/openCypher/openCypherExplorer.test.ts
+++ b/packages/graph-explorer/src/connector/openCypher/openCypherExplorer.test.ts
@@ -1,3 +1,4 @@
+// @vitest-environment happy-dom
import type { FeatureFlags, NormalizedConnection } from "@/core";
import { createOpenCypherExplorer } from "./openCypherExplorer";
@@ -6,10 +7,8 @@ function createConnection(
overrides?: Partial,
): NormalizedConnection {
return {
- url: "http://localhost:8182",
queryEngine: "openCypher",
- graphDbUrl: "",
- proxyConnection: false,
+ graphDbUrl: "https://my-neptune:8182",
awsAuthEnabled: false,
...overrides,
};
@@ -35,6 +34,7 @@ describe("createOpenCypherExplorer", () => {
beforeEach(() => {
mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);
+ document.head.innerHTML = '';
});
afterEach(() => {
@@ -66,7 +66,7 @@ describe("createOpenCypherExplorer", () => {
await explorer.fetchSchema();
expect(mockFetch).toHaveBeenCalledWith(
- "http://localhost:8182/pg/statistics/summary?mode=basic",
+ new URL("http://localhost/pg/statistics/summary?mode=basic"),
expect.objectContaining({ method: "GET" }),
);
});
@@ -93,7 +93,7 @@ describe("createOpenCypherExplorer", () => {
await explorer.fetchSchema();
expect(mockFetch).toHaveBeenCalledWith(
- "http://localhost:8182/summary?mode=basic",
+ new URL("http://localhost/summary?mode=basic"),
expect.objectContaining({ method: "GET" }),
);
});
diff --git a/packages/graph-explorer/src/connector/openCypher/openCypherExplorer.ts b/packages/graph-explorer/src/connector/openCypher/openCypherExplorer.ts
index ef66e182f..bd52400d5 100644
--- a/packages/graph-explorer/src/connector/openCypher/openCypherExplorer.ts
+++ b/packages/graph-explorer/src/connector/openCypher/openCypherExplorer.ts
@@ -8,6 +8,7 @@ import type { Explorer, ExplorerRequestOptions } from "../useGEFetchTypes";
import type { GraphSummary } from "./types";
import { fetchDatabaseRequest } from "../fetchDatabaseRequest";
+import { apiUrl } from "../utils/apiUrl";
import { edgeDetails } from "./edgeDetails";
import fetchEdgeConnections from "./fetchEdgeConnections";
import fetchNeighbors from "./fetchNeighbors";
@@ -28,7 +29,7 @@ function _openCypherFetch(
return fetchDatabaseRequest(
connection,
featureFlags,
- `${connection.url}/openCypher`,
+ apiUrl("openCypher"),
{
method: "POST",
headers: {
@@ -130,8 +131,8 @@ async function fetchSummary(
try {
const endpoint =
serviceType === DEFAULT_SERVICE_TYPE
- ? `${connection.url}/pg/statistics/summary?mode=basic`
- : `${connection.url}/summary?mode=basic`;
+ ? apiUrl("pg/statistics/summary?mode=basic")
+ : apiUrl("summary?mode=basic");
const response = await fetchDatabaseRequest(
connection,
featureFlags,
diff --git a/packages/graph-explorer/src/connector/sparql/sparqlExplorer.test.ts b/packages/graph-explorer/src/connector/sparql/sparqlExplorer.test.ts
index d7bc2f831..470ba3b3a 100644
--- a/packages/graph-explorer/src/connector/sparql/sparqlExplorer.test.ts
+++ b/packages/graph-explorer/src/connector/sparql/sparqlExplorer.test.ts
@@ -1,3 +1,4 @@
+// @vitest-environment happy-dom
import type { FeatureFlags, NormalizedConnection } from "@/core";
import { createSparqlExplorer } from "./sparqlExplorer";
@@ -6,10 +7,8 @@ function createConnection(
overrides?: Partial,
): NormalizedConnection {
return {
- url: "http://localhost:8182",
queryEngine: "sparql",
- graphDbUrl: "",
- proxyConnection: false,
+ graphDbUrl: "https://my-neptune:8182",
awsAuthEnabled: false,
...overrides,
};
@@ -35,6 +34,7 @@ describe("createSparqlExplorer", () => {
beforeEach(() => {
mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);
+ document.head.innerHTML = '';
});
afterEach(() => {
@@ -68,7 +68,7 @@ describe("createSparqlExplorer", () => {
await explorer.fetchSchema();
expect(mockFetch).toHaveBeenCalledWith(
- "http://localhost:8182/rdf/statistics/summary?mode=basic",
+ new URL("http://localhost/rdf/statistics/summary?mode=basic"),
expect.objectContaining({ method: "GET" }),
);
});
diff --git a/packages/graph-explorer/src/connector/sparql/sparqlExplorer.ts b/packages/graph-explorer/src/connector/sparql/sparqlExplorer.ts
index 94b9a64dc..076f0311b 100644
--- a/packages/graph-explorer/src/connector/sparql/sparqlExplorer.ts
+++ b/packages/graph-explorer/src/connector/sparql/sparqlExplorer.ts
@@ -18,6 +18,7 @@ import type {
} from "./types";
import { fetchDatabaseRequest } from "../fetchDatabaseRequest";
+import { apiUrl } from "../utils/apiUrl";
import { edgeDetails } from "./edgeDetails";
import fetchEdgeConnections from "./fetchEdgeConnections";
import fetchNeighbors from "./fetchNeighbors";
@@ -40,28 +41,22 @@ function _sparqlFetch(
logger.debug(queryTemplate);
const body = `query=${encodeURIComponent(queryTemplate)}`;
const queryId = options?.queryId;
- const headers: Record =
- queryId && connection.proxyConnection === true
- ? {
- accept: "application/sparql-results+json",
- "Content-Type": "application/x-www-form-urlencoded",
- queryId: queryId,
- }
- : {
- accept: "application/sparql-results+json",
- "Content-Type": "application/x-www-form-urlencoded",
- };
- return fetchDatabaseRequest(
- connection,
- featureFlags,
- `${connection.url}/sparql`,
- {
- method: "POST",
- headers,
- body,
- ...options,
- },
- );
+ const headers: Record = queryId
+ ? {
+ accept: "application/sparql-results+json",
+ "Content-Type": "application/x-www-form-urlencoded",
+ queryId: queryId,
+ }
+ : {
+ accept: "application/sparql-results+json",
+ "Content-Type": "application/x-www-form-urlencoded",
+ };
+ return fetchDatabaseRequest(connection, featureFlags, apiUrl("sparql"), {
+ method: "POST",
+ headers,
+ body,
+ ...options,
+ });
};
}
@@ -74,7 +69,7 @@ async function fetchSummary(
const response = await fetchDatabaseRequest(
connection,
featureFlags,
- `${connection.url}/rdf/statistics/summary?mode=basic`,
+ apiUrl("rdf/statistics/summary?mode=basic"),
{
method: "GET",
...options,
diff --git a/packages/graph-explorer/src/connector/utils/apiUrl.test.ts b/packages/graph-explorer/src/connector/utils/apiUrl.test.ts
new file mode 100644
index 000000000..6b945e819
--- /dev/null
+++ b/packages/graph-explorer/src/connector/utils/apiUrl.test.ts
@@ -0,0 +1,43 @@
+import { apiUrl } from "./apiUrl";
+
+describe("apiUrl", () => {
+ test("resolves a simple endpoint relative to baseURI", () => {
+ const result = apiUrl("gremlin", "http://localhost/explorer/");
+ expect(result.href).toBe("http://localhost/gremlin");
+ });
+
+ test("resolves sparql endpoint", () => {
+ const result = apiUrl("sparql", "http://localhost/explorer/");
+ expect(result.href).toBe("http://localhost/sparql");
+ });
+
+ test("resolves openCypher endpoint", () => {
+ const result = apiUrl("openCypher", "http://localhost/explorer/");
+ expect(result.href).toBe("http://localhost/openCypher");
+ });
+
+ test("resolves endpoint with nested base path", () => {
+ const result = apiUrl("gremlin", "http://localhost/proxy/9250/explorer/");
+ expect(result.href).toBe("http://localhost/proxy/9250/gremlin");
+ });
+
+ test("resolves endpoint with query parameters", () => {
+ const result = apiUrl(
+ "pg/statistics/summary?mode=detailed",
+ "http://localhost/explorer/",
+ );
+ expect(result.href).toBe(
+ "http://localhost/pg/statistics/summary?mode=detailed",
+ );
+ });
+
+ test("resolves logger endpoint", () => {
+ const result = apiUrl("logger", "http://localhost/explorer/");
+ expect(result.href).toBe("http://localhost/logger");
+ });
+
+ test("resolves defaultConnection endpoint", () => {
+ const result = apiUrl("defaultConnection", "http://localhost/explorer/");
+ expect(result.href).toBe("http://localhost/defaultConnection");
+ });
+});
diff --git a/packages/graph-explorer/src/connector/utils/apiUrl.ts b/packages/graph-explorer/src/connector/utils/apiUrl.ts
new file mode 100644
index 000000000..08bb0d004
--- /dev/null
+++ b/packages/graph-explorer/src/connector/utils/apiUrl.ts
@@ -0,0 +1,9 @@
+/** Resolves an API endpoint path relative to the given baseURI.
+ * The server mounts static files at `/explorer` and API routes at `/`,
+ * so `../endpoint` from the static directory resolves to the API root. */
+export function apiUrl(
+ endpoint: string,
+ baseURI: string = document.baseURI,
+): URL {
+ return new URL(`../${endpoint}`, baseURI);
+}
diff --git a/packages/graph-explorer/src/core/AppStatusLoader.tsx b/packages/graph-explorer/src/core/AppStatusLoader.tsx
index 36535788f..4c3c46493 100644
--- a/packages/graph-explorer/src/core/AppStatusLoader.tsx
+++ b/packages/graph-explorer/src/core/AppStatusLoader.tsx
@@ -27,7 +27,7 @@ function LoadDefaultConfig({ children }: PropsWithChildren) {
const defaultConfigQuery = useQuery({
queryKey: ["default-connection"],
- queryFn: fetchDefaultConnection,
+ queryFn: () => fetchDefaultConnection(),
staleTime: Infinity,
// Run the query only if the store is loaded and there are no configs
enabled: configuration.size === 0,
diff --git a/packages/graph-explorer/src/core/StateProvider/configuration.test.ts b/packages/graph-explorer/src/core/StateProvider/configuration.test.ts
index c71b6ba13..3e98cc7f9 100644
--- a/packages/graph-explorer/src/core/StateProvider/configuration.test.ts
+++ b/packages/graph-explorer/src/core/StateProvider/configuration.test.ts
@@ -24,6 +24,7 @@ import {
getDefaultEdgeTypeConfig,
getDefaultVertexTypeConfig,
mergeConfiguration,
+ migrateLegacyConnection,
normalizeConnection,
type NormalizedConnection,
patchToRemoveDisplayLabel,
@@ -31,10 +32,8 @@ import {
/** The default empty connection values when no value is provided. */
const defaultEmptyConnection: NormalizedConnection = {
- url: "",
graphDbUrl: "",
queryEngine: "gremlin",
- proxyConnection: false,
awsAuthEnabled: false,
};
@@ -107,7 +106,6 @@ describe("mergedConfiguration", () => {
connection: {
...defaultEmptyConnection,
...config.connection,
- url: config.connection?.url ?? "",
graphDbUrl: config.connection?.graphDbUrl ?? "",
},
schema: expectedSchema,
@@ -164,7 +162,6 @@ describe("mergedConfiguration", () => {
connection: {
...defaultEmptyConnection,
...config.connection,
- url: config.connection?.url ?? "",
graphDbUrl: config.connection?.graphDbUrl ?? "",
},
schema: expectedSchema,
@@ -329,51 +326,23 @@ describe("patchToRemoveDisplayLabel", () => {
});
describe("normalizeConnection", () => {
- test("should remove trailing slash from url", () => {
- const result = normalizeConnection({ url: "https://example.com/" });
- expect(result.url).toBe("https://example.com");
+ test("should remove trailing slash from graphDbUrl", () => {
+ const result = normalizeConnection({ graphDbUrl: "https://example.com/" });
+ expect(result.graphDbUrl).toBe("https://example.com");
});
test("should default queryEngine to gremlin", () => {
- const result = normalizeConnection({ url: "https://example.com" });
+ const result = normalizeConnection({ graphDbUrl: "https://example.com" });
expect(result.queryEngine).toBe("gremlin");
});
- test("should default proxyConnection to true when graphDbUrl is present", () => {
- const result = normalizeConnection({
- url: "https://proxy.com",
- graphDbUrl: "https://db.com",
- });
- expect(result.proxyConnection).toBe(true);
- });
-
- test("should default proxyConnection to false when graphDbUrl is absent", () => {
- const result = normalizeConnection({ url: "https://example.com" });
- expect(result.proxyConnection).toBe(false);
- });
-
test("should default awsAuthEnabled to false", () => {
- const result = normalizeConnection({ url: "https://example.com" });
+ const result = normalizeConnection({ graphDbUrl: "https://example.com" });
expect(result.awsAuthEnabled).toBe(false);
});
- test("should preserve path in url", () => {
- const result = normalizeConnection({
- url: "http://localhost:9999/blazegraph/namespace/kb",
- });
- expect(result.url).toBe("http://localhost:9999/blazegraph/namespace/kb");
- });
-
- test("should remove only trailing slash from url with path", () => {
- const result = normalizeConnection({
- url: "http://localhost:9999/blazegraph/namespace/kb/",
- });
- expect(result.url).toBe("http://localhost:9999/blazegraph/namespace/kb");
- });
-
test("should preserve path in graphDbUrl", () => {
const result = normalizeConnection({
- url: "http://proxy:8080",
graphDbUrl: "http://blazegraph:9999/blazegraph/namespace/kb",
});
expect(result.graphDbUrl).toBe(
@@ -383,13 +352,109 @@ describe("normalizeConnection", () => {
test("should remove only trailing slash from graphDbUrl with path", () => {
const result = normalizeConnection({
- url: "http://proxy:8080",
graphDbUrl: "http://blazegraph:9999/blazegraph/namespace/kb/",
});
expect(result.graphDbUrl).toBe(
"http://blazegraph:9999/blazegraph/namespace/kb",
);
});
+
+ test("should migrate legacy connection with url and proxyConnection=true", () => {
+ const result = normalizeConnection({
+ url: "https://proxy.com",
+ proxyConnection: true,
+ graphDbUrl: "https://db.com",
+ } as any);
+ expect(result.graphDbUrl).toBe("https://db.com");
+ });
+
+ test("should migrate legacy connection with url and proxyConnection=false", () => {
+ const result = normalizeConnection({
+ url: "https://my-neptune:8182",
+ proxyConnection: false,
+ } as any);
+ expect(result.graphDbUrl).toBe("https://my-neptune:8182");
+ });
+});
+
+describe("migrateLegacyConnection", () => {
+ test("should use graphDbUrl directly when proxyConnection is true", () => {
+ const result = migrateLegacyConnection({
+ url: "https://proxy.example.com",
+ proxyConnection: true,
+ graphDbUrl: "https://my-neptune:8182",
+ });
+ expect(result.graphDbUrl).toBe("https://my-neptune:8182");
+ });
+
+ test("should use url as graphDbUrl when proxyConnection is false", () => {
+ const result = migrateLegacyConnection({
+ url: "https://my-neptune:8182",
+ proxyConnection: false,
+ });
+ expect(result.graphDbUrl).toBe("https://my-neptune:8182");
+ });
+
+ test("should use url as graphDbUrl when proxyConnection is absent and no graphDbUrl", () => {
+ const result = migrateLegacyConnection({
+ url: "https://my-neptune:8182",
+ });
+ expect(result.graphDbUrl).toBe("https://my-neptune:8182");
+ });
+
+ test("should not include proxyConnection in result", () => {
+ const result = migrateLegacyConnection({
+ url: "https://proxy.com",
+ proxyConnection: true,
+ graphDbUrl: "https://db.com",
+ });
+ expect(result).not.toHaveProperty("proxyConnection");
+ });
+
+ test("should not include url in result", () => {
+ const result = migrateLegacyConnection({
+ url: "https://proxy.com",
+ proxyConnection: true,
+ graphDbUrl: "https://db.com",
+ });
+ expect(result).not.toHaveProperty("url");
+ });
+
+ test("should preserve other connection properties", () => {
+ const result = migrateLegacyConnection({
+ url: "https://proxy.com",
+ proxyConnection: true,
+ graphDbUrl: "https://db.com",
+ queryEngine: "sparql",
+ awsAuthEnabled: true,
+ awsRegion: "us-east-1",
+ serviceType: "neptune-graph",
+ fetchTimeoutMs: 30000,
+ nodeExpansionLimit: 100,
+ });
+ expect(result.queryEngine).toBe("sparql");
+ expect(result.awsAuthEnabled).toBe(true);
+ expect(result.awsRegion).toBe("us-east-1");
+ expect(result.serviceType).toBe("neptune-graph");
+ expect(result.fetchTimeoutMs).toBe(30000);
+ expect(result.nodeExpansionLimit).toBe(100);
+ });
+
+ test("should pass through a connection that already has graphDbUrl and no url", () => {
+ const result = migrateLegacyConnection({
+ graphDbUrl: "https://db.com",
+ queryEngine: "gremlin",
+ });
+ expect(result.graphDbUrl).toBe("https://db.com");
+ });
+
+ test("should fall back to empty string when no url is present", () => {
+ const result = migrateLegacyConnection({
+ proxyConnection: false,
+ queryEngine: "gremlin",
+ });
+ expect(result.graphDbUrl).toBe("");
+ });
});
describe("getDefaultVertexTypeConfig", () => {
diff --git a/packages/graph-explorer/src/core/StateProvider/configuration.ts b/packages/graph-explorer/src/core/StateProvider/configuration.ts
index 53b588390..7c92e9287 100644
--- a/packages/graph-explorer/src/core/StateProvider/configuration.ts
+++ b/packages/graph-explorer/src/core/StateProvider/configuration.ts
@@ -1,4 +1,4 @@
-import type { ConnectionConfig } from "@shared/types";
+import type { ConnectionConfig, LegacyConnectionConfig } from "@shared/types";
import { atom } from "jotai";
import { selectAtom } from "jotai/utils";
@@ -76,7 +76,9 @@ export function mergeConfiguration(
return {
id: currentConfig.id,
displayLabel: currentConfig.displayLabel,
- connection: normalizeConnection(currentConfig.connection || { url: "" }),
+ connection: normalizeConnection(
+ currentConfig.connection || { graphDbUrl: "" },
+ ),
schema: {
vertices: mergedVertices,
edges: mergedEdges,
@@ -93,16 +95,30 @@ export function mergeConfiguration(
};
}
-export function normalizeConnection(connection: ConnectionConfig) {
+/** Migrates a legacy connection (with `url` and `proxyConnection`) to the new
+ * format where only `graphDbUrl` exists. */
+export function migrateLegacyConnection(
+ connection: LegacyConnectionConfig,
+): ConnectionConfig {
+ const { url, proxyConnection, ...rest } = connection;
+ // Proxy connections stored the database endpoint in `graphDbUrl`; direct
+ // connections stored it in `url`. The final `connection.graphDbUrl` fallback
+ // covers already-migrated data where `url` is absent, and the empty-string
+ // fallback keeps the result valid when no URL is present at all.
+ const graphDbUrl = proxyConnection ? connection.graphDbUrl : url;
return {
- ...connection,
- // Remove trailing slash
- url: connection.url.replace(/\/$/, "") || "",
- queryEngine: connection.queryEngine || "gremlin",
- graphDbUrl: connection.graphDbUrl?.replace(/\/$/, "") || "",
- proxyConnection:
- connection.proxyConnection ?? connection.graphDbUrl != null,
- awsAuthEnabled: connection.awsAuthEnabled ?? false,
+ ...rest,
+ graphDbUrl: graphDbUrl || connection.graphDbUrl || "",
+ };
+}
+
+export function normalizeConnection(connection: LegacyConnectionConfig) {
+ const migrated = migrateLegacyConnection(connection);
+ return {
+ ...migrated,
+ graphDbUrl: migrated.graphDbUrl.replace(/\/$/, "") || "",
+ queryEngine: migrated.queryEngine || "gremlin",
+ awsAuthEnabled: migrated.awsAuthEnabled ?? false,
};
}
export type NormalizedConnection = ReturnType;
diff --git a/packages/graph-explorer/src/core/connector.ts b/packages/graph-explorer/src/core/connector.ts
index 5693c6349..8b061c360 100644
--- a/packages/graph-explorer/src/core/connector.ts
+++ b/packages/graph-explorer/src/core/connector.ts
@@ -73,23 +73,14 @@ export const loggerSelector = atom(get =>
createLoggerFromConnection(get(activeConnectionAtom)),
);
-/** Creates a logger instance that will be remote if the connection is using the
- * proxy server. Otherwise it will be a client only logger. */
+/** Creates a logger instance that sends logs to the server via relative URL. */
export function createLoggerFromConnection(
connection: NormalizedConnection | null,
): LoggerConnector {
- // Check for a url and that we are using the proxy server
- if (!connection || !connection.url || connection.proxyConnection !== true) {
- logger.debug(
- "Connection did not contain enough information to create a remote logger, so using a client logger instead",
- connection,
- );
+ if (!connection) {
+ logger.debug("No connection provided, using a client logger instead");
return new ClientLoggerConnector();
}
- logger.debug(
- "Creating a remote server logger using proxy server URL",
- connection.url,
- );
- return new ServerLoggerConnector(connection.url);
+ return new ServerLoggerConnector();
}
diff --git a/packages/graph-explorer/src/core/defaultConnection.test.ts b/packages/graph-explorer/src/core/defaultConnection.test.ts
index 30f637b54..a2e789fae 100644
--- a/packages/graph-explorer/src/core/defaultConnection.test.ts
+++ b/packages/graph-explorer/src/core/defaultConnection.test.ts
@@ -1,3 +1,4 @@
+// @vitest-environment happy-dom
import {
createRandomBoolean,
createRandomInteger,
@@ -13,9 +14,81 @@ import {
import {
DefaultConnectionDataSchema,
+ fetchDefaultConnection,
mapToConnection,
} from "./defaultConnection";
+describe("fetchDefaultConnection", () => {
+ let mockFetch: ReturnType;
+
+ beforeEach(() => {
+ mockFetch = vi.fn();
+ vi.stubGlobal("fetch", mockFetch);
+ document.head.innerHTML = '';
+ });
+
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ });
+
+ test("should fetch from a single relative URL", async () => {
+ mockFetch.mockResolvedValue(
+ new Response(
+ JSON.stringify({
+ GRAPH_EXP_CONNECTION_URL: "https://db.example.com:8182",
+ GRAPH_EXP_GRAPH_TYPE: "gremlin",
+ GRAPH_EXP_IAM: true,
+ GRAPH_EXP_AWS_REGION: "us-east-1",
+ }),
+ { status: 200 },
+ ),
+ );
+
+ const result = await fetchDefaultConnection();
+
+ expect(mockFetch).toHaveBeenCalledTimes(1);
+ expect(mockFetch).toHaveBeenCalledWith(
+ expect.objectContaining({
+ href: "http://localhost/defaultConnection",
+ }),
+ );
+ expect(result).toHaveLength(1);
+ expect(result[0].connection?.graphDbUrl).toBe(
+ "https://db.example.com:8182",
+ );
+ });
+
+ test("should not fall back to sagemaker path", async () => {
+ mockFetch.mockResolvedValue(new Response("", { status: 404 }));
+
+ const result = await fetchDefaultConnection();
+
+ expect(mockFetch).toHaveBeenCalledTimes(1);
+ expect(result).toHaveLength(0);
+ });
+
+ test("should return all query engines when none specified", async () => {
+ mockFetch.mockResolvedValue(
+ new Response(
+ JSON.stringify({
+ GRAPH_EXP_CONNECTION_URL: "https://db.example.com:8182",
+ GRAPH_EXP_IAM: false,
+ }),
+ { status: 200 },
+ ),
+ );
+
+ const result = await fetchDefaultConnection();
+
+ expect(result).toHaveLength(3);
+ expect(result.map(c => c.connection?.queryEngine)).toEqual([
+ "gremlin",
+ "openCypher",
+ "sparql",
+ ]);
+ });
+});
+
describe("mapToConnection", () => {
test("should map default connection data to connection config", () => {
const defaultConnectionData = createRandomDefaultConnectionData();
@@ -25,8 +98,6 @@ describe("mapToConnection", () => {
displayLabel: "Default Connection",
connection: {
graphDbUrl: defaultConnectionData.GRAPH_EXP_CONNECTION_URL,
- url: defaultConnectionData.GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT,
- proxyConnection: defaultConnectionData.GRAPH_EXP_USING_PROXY_SERVER,
queryEngine: defaultConnectionData.GRAPH_EXP_GRAPH_TYPE,
awsAuthEnabled: defaultConnectionData.GRAPH_EXP_IAM,
awsRegion: defaultConnectionData.GRAPH_EXP_AWS_REGION,
@@ -50,9 +121,7 @@ describe("DefaultConnectionDataSchema", () => {
const data = {};
const actual = DefaultConnectionDataSchema.parse(data);
expect(actual).toEqual({
- GRAPH_EXP_USING_PROXY_SERVER: false,
GRAPH_EXP_CONNECTION_URL: "",
- GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT: "",
GRAPH_EXP_IAM: false,
GRAPH_EXP_AWS_REGION: "",
GRAPH_EXP_SERVICE_TYPE: "neptune-db",
@@ -63,7 +132,6 @@ describe("DefaultConnectionDataSchema", () => {
test("should handle invalid service type", () => {
const data: any = createRandomDefaultConnectionData();
data.GRAPH_EXP_SERVICE_TYPE = createRandomName("serviceType");
- // Make the enum less strict
const actual = DefaultConnectionDataSchema.parse(data);
expect(actual).toEqual({ ...data, GRAPH_EXP_SERVICE_TYPE: "neptune-db" });
});
@@ -71,15 +139,10 @@ describe("DefaultConnectionDataSchema", () => {
test("should handle invalid URLs", () => {
const data: any = createRandomDefaultConnectionData();
data.GRAPH_EXP_CONNECTION_URL = createRandomName("connectionURL");
- data.GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT = createRandomName(
- "publicOrProxyEndpoint",
- );
- // Make the enum less strict
const actual = DefaultConnectionDataSchema.parse(data);
expect(actual).toEqual({
...data,
GRAPH_EXP_CONNECTION_URL: "",
- GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT: "",
});
});
@@ -94,25 +157,11 @@ describe("DefaultConnectionDataSchema", () => {
"http://blazegraph:9999/blazegraph/namespace/kb",
);
});
-
- test("should preserve path in GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT", () => {
- const data = {
- ...createRandomDefaultConnectionData(),
- GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT:
- "http://localhost:8080/proxy/explorer",
- };
- const actual = DefaultConnectionDataSchema.parse(data);
- expect(actual.GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT).toBe(
- "http://localhost:8080/proxy/explorer",
- );
- });
});
function createRandomDefaultConnectionData() {
return {
- GRAPH_EXP_USING_PROXY_SERVER: createRandomBoolean(),
GRAPH_EXP_CONNECTION_URL: createRandomUrlString(),
- GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT: createRandomUrlString(),
GRAPH_EXP_GRAPH_TYPE: createRandomQueryEngine(),
GRAPH_EXP_IAM: createRandomBoolean(),
GRAPH_EXP_AWS_REGION: createRandomAwsRegion(),
diff --git a/packages/graph-explorer/src/core/defaultConnection.ts b/packages/graph-explorer/src/core/defaultConnection.ts
index 775f8246f..346a86909 100644
--- a/packages/graph-explorer/src/core/defaultConnection.ts
+++ b/packages/graph-explorer/src/core/defaultConnection.ts
@@ -1,6 +1,7 @@
import { neptuneServiceTypeOptions, queryEngineOptions } from "@shared/types";
import { z } from "zod";
+import { apiUrl } from "@/connector/utils/apiUrl";
import { DEFAULT_SERVICE_TYPE, logger } from "@/utils";
import type {
@@ -10,9 +11,7 @@ import type {
export const DefaultConnectionDataSchema = z.object({
// Connection info
- GRAPH_EXP_USING_PROXY_SERVER: z.boolean().default(false),
GRAPH_EXP_CONNECTION_URL: z.string().url().catch(""),
- GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT: z.string().url().catch(""),
GRAPH_EXP_GRAPH_TYPE: z.enum(queryEngineOptions).optional(),
// IAM auth info
GRAPH_EXP_IAM: z.boolean().default(false),
@@ -28,15 +27,11 @@ export const DefaultConnectionDataSchema = z.object({
export type DefaultConnectionData = z.infer;
-/** Fetches the default connections from multiple possible locations and returns an empty array on failure. */
+/** Fetches the default connection from the server and returns an empty array on failure. */
export async function fetchDefaultConnection() {
- const defaultConnectionPath = `${location.origin}/defaultConnection`;
- const sagemakerConnectionPath = `${location.origin}/proxy/9250/defaultConnection`;
-
try {
- const defaultConnection =
- (await fetchDefaultConnectionFor(defaultConnectionPath)) ??
- (await fetchDefaultConnectionFor(sagemakerConnectionPath));
+ const url = apiUrl("defaultConnection");
+ const defaultConnection = await fetchDefaultConnectionFor(url);
if (!defaultConnection) {
return [];
@@ -44,12 +39,10 @@ export async function fetchDefaultConnection() {
const config = mapToConnection(defaultConnection);
- // A specific query engine was specified, so just return that
if (config.connection?.queryEngine) {
return [config];
}
- // No query engine was specified, so return all the possible ones
const configs = queryEngineOptions.map(queryEngine => {
return {
...config,
@@ -72,7 +65,7 @@ export async function fetchDefaultConnection() {
/** Attempts to fetch a default connection from the given URL and returns null on a failure. */
export async function fetchDefaultConnectionFor(
- url: string,
+ url: URL | string,
): Promise {
try {
logger.debug("Fetching default connection from", url);
@@ -109,10 +102,8 @@ export function mapToConnection(data: DefaultConnectionData): RawConfiguration {
id: "Default Connection" as ConfigurationId,
displayLabel: "Default Connection",
connection: {
- url: data.GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT,
- queryEngine: data.GRAPH_EXP_GRAPH_TYPE,
- proxyConnection: data.GRAPH_EXP_USING_PROXY_SERVER,
graphDbUrl: data.GRAPH_EXP_CONNECTION_URL,
+ queryEngine: data.GRAPH_EXP_GRAPH_TYPE,
awsAuthEnabled: data.GRAPH_EXP_IAM,
awsRegion: data.GRAPH_EXP_AWS_REGION,
serviceType: data.GRAPH_EXP_SERVICE_TYPE,
diff --git a/packages/graph-explorer/src/modules/AvailableConnections/ConnectionRow.tsx b/packages/graph-explorer/src/modules/AvailableConnections/ConnectionRow.tsx
index 2b9fa1909..e6024a4aa 100644
--- a/packages/graph-explorer/src/modules/AvailableConnections/ConnectionRow.tsx
+++ b/packages/graph-explorer/src/modules/AvailableConnections/ConnectionRow.tsx
@@ -24,11 +24,7 @@ function ConnectionRow({
const t = useTranslations();
const setActiveConfig = useSetActiveConfigCallback(connection.id);
- const dbUrl = connection.connection
- ? connection.connection.proxyConnection
- ? connection.connection.graphDbUrl
- : connection.connection.url
- : null;
+ const dbUrl = connection.connection?.graphDbUrl || null;
const graphType = t(
"query-language",
diff --git a/packages/graph-explorer/src/modules/AvailableConnections/useImportConnectionFile.test.tsx b/packages/graph-explorer/src/modules/AvailableConnections/useImportConnectionFile.test.tsx
index 29acfc5c7..9280b33fc 100644
--- a/packages/graph-explorer/src/modules/AvailableConnections/useImportConnectionFile.test.tsx
+++ b/packages/graph-explorer/src/modules/AvailableConnections/useImportConnectionFile.test.tsx
@@ -49,12 +49,12 @@ describe("useImportConnectionFile", () => {
);
const displayLabel = createRandomName("Config");
- const url = createRandomUrlString();
+ const graphDbUrl = createRandomUrlString();
const validConfig = {
id: createNewConfigurationId(),
displayLabel,
connection: {
- url,
+ graphDbUrl,
queryEngine: "gremlin" as const,
},
schema: {
@@ -80,7 +80,7 @@ describe("useImportConnectionFile", () => {
expect(importedId).not.toBe(state.activeConfig.id);
expect(config.displayLabel).toBe(displayLabel);
- expect(config.connection?.url).toBe(url);
+ expect(config.connection?.graphDbUrl).toBe(graphDbUrl);
expect(config.connection?.queryEngine).toBe("gremlin");
expect(schema.vertices).toHaveLength(1);
@@ -91,6 +91,48 @@ describe("useImportConnectionFile", () => {
expect(mockResetState).toHaveBeenCalledOnce();
});
+ test("should migrate a legacy connection file with url and proxyConnection", async () => {
+ const state = new DbState();
+ const { result } = renderHookWithState(
+ () => useImportConnectionFile(),
+ state,
+ );
+
+ // A direct (non-proxy) connection exported before the unified-proxy model
+ // stored the database endpoint in `url`, not `graphDbUrl`.
+ const url = createRandomUrlString();
+ const legacyConfig = {
+ id: createNewConfigurationId(),
+ displayLabel: createRandomName("Config"),
+ connection: {
+ url,
+ proxyConnection: false,
+ queryEngine: "gremlin" as const,
+ },
+ schema: {
+ totalVertices: 0,
+ vertices: [],
+ totalEdges: 0,
+ edges: [],
+ },
+ };
+
+ const file = new File([JSON.stringify(legacyConfig)], "connection.json", {
+ type: "application/json",
+ });
+
+ await act(async () => {
+ await result.current(file);
+ });
+
+ const { config } = getImportedConnection();
+ expect(config.connection?.graphDbUrl).toBe(url);
+ // The legacy fields are folded away during migration.
+ expect(config.connection).not.toHaveProperty("url");
+ expect(config.connection).not.toHaveProperty("proxyConnection");
+ expect(mockResetState).toHaveBeenCalledOnce();
+ });
+
test("should reject invalid configuration file", async () => {
const state = new DbState();
const { result } = renderHookWithState(
@@ -524,13 +566,21 @@ describe("backward compatibility: legacy exported connection file with embedded
const { config: importedConfig, schema: importedSchema } =
getImportedConnection();
- // The connection lands in the config entry, fully preserved.
+ // The connection lands in the config entry. Legacy proxy fields (`url`,
+ // `proxyConnection`) are folded into the canonical `graphDbUrl` on import;
+ // the remaining fields are preserved.
expect(importedConfig.displayLabel).toBe(
legacyExportedConnectionFile.displayLabel,
);
- expect(importedConfig.connection).toMatchObject(
- legacyExportedConnectionFile.connection,
- );
+ expect(importedConfig.connection).toMatchObject({
+ graphDbUrl: legacyExportedConnectionFile.connection.graphDbUrl,
+ queryEngine: legacyExportedConnectionFile.connection.queryEngine,
+ awsAuthEnabled: legacyExportedConnectionFile.connection.awsAuthEnabled,
+ serviceType: legacyExportedConnectionFile.connection.serviceType,
+ awsRegion: legacyExportedConnectionFile.connection.awsRegion,
+ });
+ expect(importedConfig.connection).not.toHaveProperty("url");
+ expect(importedConfig.connection).not.toHaveProperty("proxyConnection");
// The schema must NOT be stored on the config entry — it belongs in
// schemaAtom. This is the invariant the dead `RawConfiguration.schema`
diff --git a/packages/graph-explorer/src/modules/AvailableConnections/useImportConnectionFile.ts b/packages/graph-explorer/src/modules/AvailableConnections/useImportConnectionFile.ts
index 8c007ed66..7e3bba75a 100644
--- a/packages/graph-explorer/src/modules/AvailableConnections/useImportConnectionFile.ts
+++ b/packages/graph-explorer/src/modules/AvailableConnections/useImportConnectionFile.ts
@@ -8,6 +8,7 @@ import {
createNewConfigurationId,
schemaAtom,
} from "@/core";
+import { migrateLegacyConnection } from "@/core/StateProvider/configuration";
import useResetState from "@/core/StateProvider/useResetState";
import { fromFileToJson } from "@/utils/fileData";
import { parseConnectionFile } from "@/utils/parseConnectionFile";
@@ -27,6 +28,10 @@ export function useImportConnectionFile() {
return;
}
+ // Fold any legacy `url`/`proxyConnection` from files exported before the
+ // unified-proxy model into the canonical `graphDbUrl` shape.
+ const connection = migrateLegacyConnection(parsedFile.connection);
+
// Create new id to avoid collisions
const newId = createNewConfigurationId();
set(configurationAtom, prevConfig => {
@@ -34,7 +39,7 @@ export function useImportConnectionFile() {
updatedConfig.set(newId, {
id: newId,
displayLabel: parsedFile.displayLabel,
- connection: parsedFile.connection,
+ connection,
});
return updatedConfig;
});
diff --git a/packages/graph-explorer/src/modules/ConnectionDetail/ConnectionDetail.tsx b/packages/graph-explorer/src/modules/ConnectionDetail/ConnectionDetail.tsx
index 481cfc215..6b5bab56f 100644
--- a/packages/graph-explorer/src/modules/ConnectionDetail/ConnectionDetail.tsx
+++ b/packages/graph-explorer/src/modules/ConnectionDetail/ConnectionDetail.tsx
@@ -83,11 +83,7 @@ function ConnectionDetail({ config }: ConnectionDetailProps) {
const deleteActiveConfig = useDeleteActiveConfiguration();
- const dbUrl = config.connection
- ? config.connection.proxyConnection
- ? config.connection.graphDbUrl
- : config.connection.url
- : LABELS.MISSING_VALUE;
+ const dbUrl = config.connection?.graphDbUrl || LABELS.MISSING_VALUE;
const connectionName = config.displayLabel || config.id;
diff --git a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx
index 637a0dc26..c000c9609 100644
--- a/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx
+++ b/packages/graph-explorer/src/modules/CreateConnection/CreateConnection.tsx
@@ -36,10 +36,8 @@ import {
type ConnectionForm = {
name?: string;
- url?: string;
- queryEngine?: QueryEngine;
- proxyConnection?: boolean;
graphDbUrl?: string;
+ queryEngine?: QueryEngine;
awsAuthEnabled?: boolean;
serviceType?: NeptuneServiceType;
awsRegion?: string;
@@ -65,10 +63,8 @@ export type CreateConnectionProps = {
function mapToConnection(data: Required): ConnectionConfig {
return {
- url: data.url,
- queryEngine: data.queryEngine,
- proxyConnection: data.proxyConnection,
graphDbUrl: data.graphDbUrl,
+ queryEngine: data.queryEngine,
awsAuthEnabled: data.awsAuthEnabled,
serviceType: data.serviceType,
awsRegion: data.awsRegion,
@@ -144,11 +140,10 @@ const CreateConnection = ({
return updated;
});
- const urlChange = initialData?.url !== data.url;
const dbUrlChange = initialData?.graphDbUrl !== data.graphDbUrl;
const typeChange = initialData?.queryEngine !== data.queryEngine;
- if (urlChange || dbUrlChange || typeChange) {
+ if (dbUrlChange || typeChange) {
logger.log(
"Clearing cached schema and previous graph session because connection to database meaningfully changed",
{ original: initialData, updated: data },
@@ -183,8 +178,6 @@ const CreateConnection = ({
name:
initialData?.name ||
`Connection (${formatDate(new Date(), "yyyy-MM-dd HH:mm")})`,
- url: initialData?.url || "",
- proxyConnection: initialData?.proxyConnection || false,
graphDbUrl: initialData?.graphDbUrl || "",
awsAuthEnabled: initialData?.awsAuthEnabled || false,
serviceType: initialData?.serviceType || "neptune-db",
@@ -233,17 +226,12 @@ const CreateConnection = ({
const reset = useResetState();
const onSubmit = () => {
- if (!form.name || !form.url || !form.queryEngine) {
- setError(true);
- return;
- }
-
- if (form.proxyConnection && !form.graphDbUrl) {
+ if (!form.name || !form.graphDbUrl || !form.queryEngine) {
setError(true);
return;
}
- if (form.awsAuthEnabled && (!form.awsRegion || !form.serviceType)) {
+ if (form.awsAuthEnabled && !form.awsRegion) {
setError(true);
return;
}
@@ -277,63 +265,35 @@ const CreateConnection = ({
- {form.proxyConnection && (
-
-
-
-
- )}
- {form.proxyConnection && (
-
- )}
- {form.proxyConnection && form.awsAuthEnabled && (
+ {form.awsAuthEnabled && (
<>
diff --git a/packages/graph-explorer/src/modules/GraphViewer/ImportGraphButton.test.tsx b/packages/graph-explorer/src/modules/GraphViewer/ImportGraphButton.test.tsx
index e6aeab7ef..17a5004d0 100644
--- a/packages/graph-explorer/src/modules/GraphViewer/ImportGraphButton.test.tsx
+++ b/packages/graph-explorer/src/modules/GraphViewer/ImportGraphButton.test.tsx
@@ -80,30 +80,12 @@ describe("createErrorNotification", () => {
);
});
- it("should show the connection name when a connection using the proxy server is a match", () => {
+ it("should show the connection name when a connection is a match", () => {
const connection = createRandomExportedGraphConnection();
const error = new InvalidConnectionError("test", connection);
const file = createRandomFile();
const allConnections = createRandomAllConnections();
allConnections[0].graphDbUrl = connection.dbUrl;
- allConnections[0].proxyConnection = true;
- allConnections[0].queryEngine = connection.queryEngine;
- const matchingConnectionName = allConnections[0].displayLabel;
-
- const notification = createErrorNotification(error, file, allConnections);
-
- expect(notification).toBe(
- `The graph file requires switching to connection ${matchingConnectionName}.`,
- );
- });
-
- it("should show the connection name when a connection not using the proxy server is a match", () => {
- const connection = createRandomExportedGraphConnection();
- const error = new InvalidConnectionError("test", connection);
- const file = createRandomFile();
- const allConnections = createRandomAllConnections();
- allConnections[0].url = connection.dbUrl;
- allConnections[0].proxyConnection = false;
allConnections[0].queryEngine = connection.queryEngine;
const matchingConnectionName = allConnections[0].displayLabel;
diff --git a/packages/graph-explorer/src/modules/GraphViewer/exportedGraph.test.ts b/packages/graph-explorer/src/modules/GraphViewer/exportedGraph.test.ts
index 207cc74c6..910e83128 100644
--- a/packages/graph-explorer/src/modules/GraphViewer/exportedGraph.test.ts
+++ b/packages/graph-explorer/src/modules/GraphViewer/exportedGraph.test.ts
@@ -136,41 +136,25 @@ describe("createExportedGraph", () => {
});
describe("createExportedConnection", () => {
- it("should map graphDbUrl when using proxy server", () => {
+ it("should map graphDbUrl", () => {
const connection = createRandomConnectionWithId();
- connection.proxyConnection = true;
- connection.graphDbUrl = createRandomUrlString();
const exportedConnection = createExportedConnection(connection);
expect(exportedConnection).toEqual({
- dbUrl: connection.graphDbUrl,
- queryEngine: connection.queryEngine!,
- } satisfies ExportedGraphConnection);
- });
-
- it("should map url when not using proxy server", () => {
- const connection = createRandomConnectionWithId();
- connection.proxyConnection = false;
-
- const exportedConnection = createExportedConnection(connection);
-
- expect(exportedConnection).toEqual({
- dbUrl: connection.url,
+ dbUrl: connection.graphDbUrl.toLowerCase(),
queryEngine: connection.queryEngine!,
} satisfies ExportedGraphConnection);
});
it("should default to gremlin when no query engine is provided", () => {
const connection = createRandomConnectionWithId();
- connection.proxyConnection = true;
- connection.graphDbUrl = createRandomUrlString();
delete connection.queryEngine;
const exportedConnection = createExportedConnection(connection);
expect(exportedConnection).toEqual({
- dbUrl: connection.graphDbUrl,
+ dbUrl: connection.graphDbUrl.toLowerCase(),
queryEngine: "gremlin",
} satisfies ExportedGraphConnection);
});
@@ -351,18 +335,6 @@ describe("isMatchingConnection", () => {
it("should return false when graph db url is different", () => {
const connection = createRandomConnectionWithId();
- connection.proxyConnection = true;
- connection.graphDbUrl = createRandomUrlString();
- const exportedConnection = createRandomExportedGraphConnection();
- exportedConnection.dbUrl = connection.url;
- exportedConnection.queryEngine = connection.queryEngine!;
-
- expect(isMatchingConnection(connection, exportedConnection)).toBeFalsy();
- });
-
- it("should return false when url is different", () => {
- const connection = createRandomConnectionWithId();
- connection.proxyConnection = false;
const exportedConnection = createRandomExportedGraphConnection();
exportedConnection.dbUrl = createRandomUrlString();
exportedConnection.queryEngine = connection.queryEngine!;
diff --git a/packages/graph-explorer/src/modules/GraphViewer/exportedGraph.ts b/packages/graph-explorer/src/modules/GraphViewer/exportedGraph.ts
index 2cb2f3aee..d098ee832 100644
--- a/packages/graph-explorer/src/modules/GraphViewer/exportedGraph.ts
+++ b/packages/graph-explorer/src/modules/GraphViewer/exportedGraph.ts
@@ -62,9 +62,7 @@ export function createExportedGraph(
export function createExportedConnection(
connection: ConnectionConfig,
): ExportedGraphConnection {
- const dbUrl = (
- (connection.proxyConnection ? connection.graphDbUrl : connection.url) ?? ""
- ).toLowerCase();
+ const dbUrl = (connection.graphDbUrl ?? "").toLowerCase();
const queryEngine = connection.queryEngine ?? "gremlin";
return {
diff --git a/packages/graph-explorer/src/utils/constants.ts b/packages/graph-explorer/src/utils/constants.ts
index 357f14c2e..dd3adc2ed 100644
--- a/packages/graph-explorer/src/utils/constants.ts
+++ b/packages/graph-explorer/src/utils/constants.ts
@@ -26,10 +26,7 @@ export const RESERVED_ID_PROPERTY = "~id";
export const RESERVED_TYPES_PROPERTY = "types";
/** The root URL for the app used for reloading fresh. */
-export const RELOAD_URL =
- import.meta.env.BASE_URL.substring(-1) !== "/"
- ? import.meta.env.BASE_URL + "/"
- : import.meta.env.BASE_URL;
+export const RELOAD_URL = ".";
/** Labels used in the UI */
export const LABELS = {
diff --git a/packages/graph-explorer/src/utils/parseConnectionFile.test.ts b/packages/graph-explorer/src/utils/parseConnectionFile.test.ts
index ec3c1bb64..9514aae3c 100644
--- a/packages/graph-explorer/src/utils/parseConnectionFile.test.ts
+++ b/packages/graph-explorer/src/utils/parseConnectionFile.test.ts
@@ -64,7 +64,7 @@ describe("parseConnectionFile", () => {
expect(parseConnectionFile(config)).toBeNull();
});
- test("returns null when connection.url is missing", () => {
+ test("returns null when neither graphDbUrl nor url is present", () => {
const config = {
id: createNewConfigurationId(),
connection: { queryEngine: "gremlin" as const },
@@ -74,6 +74,22 @@ describe("parseConnectionFile", () => {
expect(parseConnectionFile(config)).toBeNull();
});
+ test("accepts a connection with only graphDbUrl and no legacy url", () => {
+ const graphDbUrl = "https://neptune.example.com:8182";
+ const config = {
+ id: createNewConfigurationId(),
+ connection: {
+ graphDbUrl,
+ queryEngine: "gremlin" as const,
+ },
+ schema: { vertices: [], edges: [] },
+ };
+
+ const result = parseConnectionFile(config);
+
+ expect(result?.connection.graphDbUrl).toBe(graphDbUrl);
+ });
+
test("returns null when connection.queryEngine is missing", () => {
const config = {
id: createNewConfigurationId(),
diff --git a/packages/graph-explorer/src/utils/parseConnectionFile.ts b/packages/graph-explorer/src/utils/parseConnectionFile.ts
index a9468e716..6a49c8d52 100644
--- a/packages/graph-explorer/src/utils/parseConnectionFile.ts
+++ b/packages/graph-explorer/src/utils/parseConnectionFile.ts
@@ -34,13 +34,28 @@ const exportedConnectionFileSchema = z.looseObject({
.min(1)
.transform(value => value as ConfigurationId),
displayLabel: z.string().optional(),
- connection: z.looseObject({
- url: z.url({ protocol: /^https?$/ }),
- queryEngine: z.enum(queryEngineOptions),
- // `graphDbUrl` is forwarded verbatim as the proxy's request target, so an
- // imported file must not be able to point it at a non-http(s) scheme.
- graphDbUrl: z.url({ protocol: /^https?$/ }).optional(),
- }),
+ connection: z
+ .looseObject({
+ queryEngine: z.enum(queryEngineOptions),
+ // `graphDbUrl` is the canonical database endpoint. It is forwarded
+ // verbatim as the proxy's request target, so an imported file must not be
+ // able to point it at a non-http(s) scheme.
+ graphDbUrl: z.url({ protocol: /^https?$/ }).optional(),
+ // Legacy fields from files exported before the unified-proxy model.
+ // Direct connections stored the endpoint in `url`; `migrateLegacyConnection`
+ // folds these into `graphDbUrl` on import. Validated to the same scheme so
+ // a legacy file cannot smuggle in a non-http(s) target either.
+ url: z.url({ protocol: /^https?$/ }).optional(),
+ proxyConnection: z.boolean().optional(),
+ })
+ // The file must carry a usable endpoint in either the canonical or the
+ // legacy field, otherwise migration would yield an empty `graphDbUrl`.
+ .refine(
+ connection => connection.graphDbUrl != null || connection.url != null,
+ {
+ error: "connection must have a graphDbUrl or url",
+ },
+ ),
schema: z.looseObject({
vertices: z.array(
z.looseObject({
diff --git a/packages/graph-explorer/src/utils/saveConfigurationToFile.test.ts b/packages/graph-explorer/src/utils/saveConfigurationToFile.test.ts
index 4c6bcce1b..9c233dd27 100644
--- a/packages/graph-explorer/src/utils/saveConfigurationToFile.test.ts
+++ b/packages/graph-explorer/src/utils/saveConfigurationToFile.test.ts
@@ -57,7 +57,7 @@ describe("saveConfigurationToFile", () => {
const config: ConfigurationContextProps = {
...createRandomRawConfiguration(),
connection: {
- url: "https://example.com",
+ graphDbUrl: "https://example.com",
},
totalVertices: 0,
vertexTypes: [],
@@ -72,14 +72,14 @@ describe("saveConfigurationToFile", () => {
const parsed = JSON.parse(text);
expect(parsed.connection.queryEngine).toBe("gremlin");
- expect(parsed.connection.url).toBe("https://example.com");
+ expect(parsed.connection.graphDbUrl).toBe("https://example.com");
});
it("should preserve existing queryEngine", async () => {
const config: ConfigurationContextProps = {
...createRandomRawConfiguration(),
connection: {
- url: "https://example.com",
+ graphDbUrl: "https://example.com",
queryEngine: "sparql",
},
totalVertices: 0,
@@ -262,11 +262,11 @@ describe("saveConfigurationToFile", () => {
// A connection-less config is not a real, reachable state — every config
// the app produces has a connection. The `?? ""` fallback in the writer
- // emits `url: ""` rather than omitting it, and the parser then rejects
- // the file (empty string is not a valid URL). This pins that accepted
- // asymmetry; it should disappear in a later slice that makes a connection
- // non-optional on the config rather than defaulting here.
- expect(parsed.connection.url).toBe("");
+ // emits `graphDbUrl: ""` rather than omitting it, and the parser then
+ // rejects the file (empty string is not a valid URL). This pins that
+ // accepted asymmetry; it should disappear in a later slice that makes a
+ // connection non-optional on the config rather than defaulting here.
+ expect(parsed.connection.graphDbUrl).toBe("");
expect(parseConnectionFile(parsed)).toBeNull();
});
@@ -302,7 +302,7 @@ describe("saveConfigurationToFile", () => {
const config: ConfigurationContextProps = {
...createRandomRawConfiguration(),
connection: {
- url: "https://neptune.example.com:8182",
+ graphDbUrl: "https://neptune.example.com:8182",
queryEngine: "gremlin",
},
schema: {
@@ -342,7 +342,9 @@ describe("saveConfigurationToFile", () => {
const result = parseConnectionFile(parsed);
expect(result?.id).toBe(config.id);
expect(result?.displayLabel).toBe(config.displayLabel);
- expect(result?.connection.url).toBe("https://neptune.example.com:8182");
+ expect(result?.connection.graphDbUrl).toBe(
+ "https://neptune.example.com:8182",
+ );
expect(result?.connection.queryEngine).toBe("gremlin");
expect(result?.schema.vertices.map(vertex => vertex.type)).toStrictEqual([
"Person",
diff --git a/packages/graph-explorer/src/utils/saveConfigurationToFile.ts b/packages/graph-explorer/src/utils/saveConfigurationToFile.ts
index 88b6f569e..34ff90db8 100644
--- a/packages/graph-explorer/src/utils/saveConfigurationToFile.ts
+++ b/packages/graph-explorer/src/utils/saveConfigurationToFile.ts
@@ -12,7 +12,7 @@ const saveConfigurationToFile = (config: ConfigurationContextProps) => {
displayLabel: config.displayLabel || config.id,
connection: {
...config.connection,
- url: config.connection?.url ?? "",
+ graphDbUrl: config.connection?.graphDbUrl ?? "",
queryEngine: config.connection?.queryEngine || "gremlin",
},
schema: {
diff --git a/packages/graph-explorer/src/utils/testing/createMockExplorer.ts b/packages/graph-explorer/src/utils/testing/createMockExplorer.ts
index ff5be769e..d85d82eda 100644
--- a/packages/graph-explorer/src/utils/testing/createMockExplorer.ts
+++ b/packages/graph-explorer/src/utils/testing/createMockExplorer.ts
@@ -1,5 +1,7 @@
import type { Explorer } from "@/connector";
+import { normalizeConnection } from "@/core/StateProvider/configuration";
+
import { createRandomRawConfiguration } from "./randomData";
export function createMockExplorer(): Explorer {
@@ -8,7 +10,7 @@ export function createMockExplorer(): Explorer {
keywordSearch: vi.fn(),
fetchNeighbors: vi.fn(),
fetchVertexCountsByType: vi.fn(),
- connection: createRandomRawConfiguration().connection!,
+ connection: normalizeConnection(createRandomRawConfiguration().connection!),
fetchSchema: vi.fn(),
edgeDetails: vi.fn(),
vertexDetails: vi.fn(),
diff --git a/packages/graph-explorer/src/utils/testing/randomData.ts b/packages/graph-explorer/src/utils/testing/randomData.ts
index 32396dbcf..a3f0543be 100644
--- a/packages/graph-explorer/src/utils/testing/randomData.ts
+++ b/packages/graph-explorer/src/utils/testing/randomData.ts
@@ -589,7 +589,6 @@ export function createRandomFile(): File {
}
export function createRandomConnectionWithId(): ConnectionWithId {
- const isProxyConnection = createRandomBoolean();
const isIamEnabled = createRandomBoolean();
const fetchTimeoutMs = randomlyUndefined(createRandomInteger());
const nodeExpansionLimit = randomlyUndefined(createRandomInteger());
@@ -599,10 +598,8 @@ export function createRandomConnectionWithId(): ConnectionWithId {
return {
id: createNewConfigurationId(),
displayLabel: createRandomName("displayLabel"),
- url: createRandomUrlString(),
- ...(isProxyConnection && { graphDbUrl: createRandomUrlString() }),
+ graphDbUrl: createRandomUrlString(),
queryEngine,
- proxyConnection: isProxyConnection,
...(isIamEnabled && { awsAuthEnabled: createRandomBoolean() }),
...(isIamEnabled && {
awsRegion: createRandomAwsRegion(),
diff --git a/packages/graph-explorer/vite.config.ts b/packages/graph-explorer/vite.config.ts
index 0fb7f1fca..e86c65d8f 100644
--- a/packages/graph-explorer/vite.config.ts
+++ b/packages/graph-explorer/vite.config.ts
@@ -47,7 +47,7 @@ export default defineConfig(({ mode }) => {
},
},
},
- base: env.GRAPH_EXP_ENV_ROOT_FOLDER,
+ base: "./",
envPrefix: "GRAPH_EXP",
define: {
__GRAPH_EXP_VERSION__: JSON.stringify(process.env.npm_package_version),
diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts
index 78c85d2a0..c2ce756de 100644
--- a/packages/shared/src/types/index.ts
+++ b/packages/shared/src/types/index.ts
@@ -9,23 +9,14 @@ export type NeptuneServiceType = (typeof neptuneServiceTypeOptions)[number];
export type ConnectionConfig = {
/**
- * Base URL to access to the database through HTTPs endpoints
+ * The URL of the graph database endpoint.
*/
- url: string;
+ graphDbUrl: string;
/**
* Choose between gremlin or sparQL engines.
* By default, it uses gremlin
*/
queryEngine?: QueryEngine;
- /**
- * If the service is Neptune,
- * all requests should be sent through the nodejs proxy-server.
- */
- proxyConnection?: boolean;
- /**
- * If it is Neptune, the URL of the database.
- */
- graphDbUrl?: string;
/**
* If it is Neptune, it could need authentication.
*/
@@ -52,3 +43,10 @@ export type ConnectionConfig = {
*/
nodeExpansionLimit?: number;
};
+
+/** Legacy connection config that may still exist in stored data. */
+export type LegacyConnectionConfig = Omit & {
+ graphDbUrl?: string;
+ url?: string;
+ proxyConnection?: boolean;
+};
diff --git a/process-environment.sh b/process-environment.sh
index e6931ec97..eb2c583ae 100644
--- a/process-environment.sh
+++ b/process-environment.sh
@@ -6,10 +6,8 @@ if [ -f "./config.json" ]; then
json=$(cat ./config.json)
- PUBLIC_OR_PROXY_ENDPOINT=$(echo "$json" | grep -o '"PUBLIC_OR_PROXY_ENDPOINT":[^,}]*' | cut -d '"' -f 4)
GRAPH_TYPE=$(echo "$json" | grep -o '"GRAPH_TYPE":[^,}]*' | cut -d '"' -f 4)
SERVICE_TYPE=$(echo "$json" | grep -o '"SERVICE_TYPE":[^,}]*' | cut -d '"' -f 4)
- USING_PROXY_SERVER=$(echo "$json" | grep -o '"USING_PROXY_SERVER":[^,}]*' | cut -d ':' -f 2 | tr -d '[:space:]' | sed 's/"//g')
IAM=$(echo "$json" | grep -o '"IAM":[^,}]*' | cut -d ':' -f 2 | tr -d '[:space:]' | sed 's/"//g')
GRAPH_CONNECTION_URL=$(echo "$json" | grep -o '"GRAPH_CONNECTION_URL":[^,}]*' | cut -d '"' -f 4)
AWS_REGION=$(echo "$json" | grep -o '"AWS_REGION":[^,}]*' | cut -d '"' -f 4)
@@ -18,15 +16,17 @@ if [ -f "./config.json" ]; then
NEPTUNE_NOTEBOOK=$(echo "$json" | grep -o '"NEPTUNE_NOTEBOOK":[^,}]*' | cut -d ':' -f 2 | tr -d '[:space:]' | sed 's/"//g')
fi
-if [ -n "$NEPTUNE_NOTEBOOK" ]; then
- printf '\nNEPTUNE_NOTEBOOK=%s\n' "$NEPTUNE_NOTEBOOK" >> $CONFIGURATION_FOLDER_PATH/.env
- if [ "$NEPTUNE_NOTEBOOK" = "true" ]; then
- # Override Proxy SSL setting if Neptune notebook
- PROXY_SERVER_HTTPS_CONNECTION="false"
- GRAPH_EXP_HTTPS_CONNECTION="false"
+if [ "$NEPTUNE_NOTEBOOK" = "true" ]; then
+ # Force SSL off for Neptune Notebook environments
+ PROXY_SERVER_HTTPS_CONNECTION="false"
+ GRAPH_EXP_HTTPS_CONNECTION="false"
+ # Set port and log style unless explicitly overridden
+ if [ -z "$PROXY_SERVER_HTTP_PORT" ]; then
+ printf '\nPROXY_SERVER_HTTP_PORT=9250\n' >> $CONFIGURATION_FOLDER_PATH/.env
+ fi
+ if [ -z "$LOG_STYLE" ]; then
+ printf '\nLOG_STYLE=cloudwatch\n' >> $CONFIGURATION_FOLDER_PATH/.env
fi
-else
- printf '\nNEPTUNE_NOTEBOOK=false\n' >> $CONFIGURATION_FOLDER_PATH/.env
fi
if [ -n "$PROXY_SERVER_HTTPS_CONNECTION" ]; then
@@ -42,11 +42,11 @@ else
fi
# Update the default connection file with the configuration values
-if [ -n "$PUBLIC_OR_PROXY_ENDPOINT" ]; then
+if [ -n "$GRAPH_CONNECTION_URL" ]; then
# Overwrite existing file with an empty string
echo "" > $CONFIGURATION_FOLDER_PATH/defaultConnection.json
-
- printf '{\n"GRAPH_EXP_PUBLIC_OR_PROXY_ENDPOINT":"%s",\n' "$PUBLIC_OR_PROXY_ENDPOINT" >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
+
+ printf '{\n"GRAPH_EXP_CONNECTION_URL":"%s",\n' "$GRAPH_CONNECTION_URL" >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
if [ -n "$SERVICE_TYPE" ]; then
echo "\"GRAPH_EXP_SERVICE_TYPE\":\"${SERVICE_TYPE}\"," >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
@@ -54,26 +54,19 @@ if [ -n "$PUBLIC_OR_PROXY_ENDPOINT" ]; then
echo "\"GRAPH_EXP_SERVICE_TYPE\":\"neptune-db\"," >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
fi
- if [ -n "$GRAPH_TYPE" ]; then
+ if [ -n "$GRAPH_TYPE" ]; then
echo "\"GRAPH_EXP_GRAPH_TYPE\":\"${GRAPH_TYPE}\"," >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
else
if [ "$SERVICE_TYPE" = "neptune-graph" ]; then
echo "\"GRAPH_EXP_GRAPH_TYPE\":\"openCypher\"," >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
fi
fi
-
- if [ -n "$USING_PROXY_SERVER" ]; then
- echo "\"GRAPH_EXP_USING_PROXY_SERVER\":${USING_PROXY_SERVER}," >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
- else
- echo "\"GRAPH_EXP_USING_PROXY_SERVER\":false," >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
- fi
- if [ -n "$IAM" ]; then
+ if [ -n "$IAM" ]; then
echo "\"GRAPH_EXP_IAM\":${IAM}," >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
- else
+ else
echo "\"GRAPH_EXP_IAM\":false," >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
fi
- echo "\"GRAPH_EXP_CONNECTION_URL\":\"${GRAPH_CONNECTION_URL}\"," >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
printf '"GRAPH_EXP_AWS_REGION":"%s"\n}\n' "$AWS_REGION" >> $CONFIGURATION_FOLDER_PATH/defaultConnection.json
fi