Skip to content

feat: add custom tile provider settings for Geometry Viewer#10142

Open
NivGreenstein wants to merge 12 commits into
pgadmin-org:masterfrom
NivGreenstein:feat/geometry-viewer-settings
Open

feat: add custom tile provider settings for Geometry Viewer#10142
NivGreenstein wants to merge 12 commits into
pgadmin-org:masterfrom
NivGreenstein:feat/geometry-viewer-settings

Conversation

@NivGreenstein

@NivGreenstein NivGreenstein commented Jul 5, 2026

Copy link
Copy Markdown

Summary

  • Add Geometry Viewer preferences for a custom XYZ tile provider URL, name, CRS, attribution, and max zoom.
  • Support custom tile layers in the Geometry Viewer while preserving the default built-in layers.
  • Hide incompatible built-in tile layers when a custom provider uses a non-Web Mercator CRS.
  • Document the new Geometry Viewer preferences.

Testing

  • Added JavaScript regression tests for Geometry Viewer tile provider helpers.

Summary by CodeRabbit

  • New Features
    • Geometry Viewer now supports a custom XYZ tile provider (URL template, name, CRS, attribution, max zoom) and can automatically use it as the default base layer.
  • Bug Fixes
    • Geometry Viewer dynamically adjusts CRS/base-layer options based on the selected SRID and provider, and shows an “invalid custom tile provider URL” warning when configuration is unusable.
    • Improved Preferences handling for resets and edge-case stored values.
  • Documentation
    • Updated Geometry Data Viewer and Preferences pages with the new custom tile provider setup.
  • Tests
    • Added Jest coverage for provider parsing/sanitization, CRS selection, and base-layer composition.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f2ef6d51-b8da-4c8f-9c4d-f22005e62198

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc2441 and 15cece5.

📒 Files selected for processing (1)
  • web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx

Walkthrough

This PR adds custom XYZ tile provider support for the Geometry Viewer, wires preference-driven CRS and base-layer selection into rendering, and updates preference form handling for select-file and keyboard-shortcut fields.

Changes

Custom Tile Provider Support

Layer / File(s) Summary
Backend preference registration
web/pgadmin/utils/constants.py, web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py
Adds the Geometry Viewer preference label and registers custom tile provider settings for URL, name, CRS, attribution, and max zoom.
Geometry viewer utility functions
web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewerUtils.js
Adds helpers to parse provider preferences, resolve map CRS, and build base-layer definitions.
GeometryViewer component integration
web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx
Reads preferences, derives the custom provider, updates map CRS and base layers, shows invalid-provider feedback, and remounts the map when provider settings change.
Tests and documentation
web/regression/javascript/sqleditor/GeometryViewerUtils.spec.js, docs/en_US/editgrid.rst, docs/en_US/preferences.rst
Adds tests for the new utility behavior and documents the Geometry Viewer tile provider settings.

Preferences helper normalization

Layer / File(s) Summary
Preference form preparation and reset flow
web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx
Normalizes preference field preparation, collection value serialization, and reset-modal handling in the preferences UI.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GeometryViewer
  participant usePreferences
  participant GeometryViewerUtils
  participant MapContainer
  participant TheMap

  GeometryViewer->>usePreferences: read module preferences
  GeometryViewer->>GeometryViewerUtils: getCustomTileProvider(prefs)
  GeometryViewerUtils-->>GeometryViewer: provider config or invalid flag
  GeometryViewer->>GeometryViewerUtils: getMapCrs(selectedSRID, customTileProvider)
  GeometryViewerUtils-->>GeometryViewer: resolved CRS
  GeometryViewer->>MapContainer: render with crs and providerKey
  GeometryViewer->>TheMap: render with customTileProvider
  TheMap->>GeometryViewerUtils: getBaseLayers(customTileProvider)
  GeometryViewerUtils-->>TheMap: base layer list
  TheMap->>TheMap: show warning when provider is invalid
Loading

Suggested reviewers: adityatoshniwal

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding custom tile provider settings for the Geometry Viewer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx (2)

400-407: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unscoped usePreferences() subscription causes broad re-renders.

Calling usePreferences() without a selector subscribes to the whole preferences store, so GeometryViewer re-renders on any preference change app-wide, not just sqleditor/geometry-viewer ones. Since the only value used is prefStore.version (as a memo dependency) plus a one-off getPreferencesForModule call, select just version.

♻️ Suggested fix
-  const prefStore = usePreferences();
+  const prefStoreVersion = usePreferences((state) => state.version);

   const customTileProvider = useMemo(() => {
-    return getCustomTileProvider(prefStore.getPreferencesForModule('sqleditor'));
-  }, [prefStore.version]);
+    return getCustomTileProvider(usePreferences.getState().getPreferencesForModule('sqleditor'));
+  }, [prefStoreVersion]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
around lines 400 - 407, The `GeometryViewer` component is subscribing to the
entire preferences store via `usePreferences()`, which causes unnecessary
re-renders on unrelated preference updates. Update the `GeometryViewer` hook
usage to select only the `version` field from `usePreferences`, and keep the
`getCustomTileProvider` lookup driven by that selected value so the memo still
refreshes when preferences change.

369-378: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Potential duplicate React key if custom tile name matches a built-in layer name.

key={layer.name} can collide when a user sets Custom tile provider name to "Street", "Topography", "Gray Style", "Light Color", or "Dark Matter" (the field is free text, default is only "Custom"). Duplicate keys can cause React reconciliation to misbehave for the LayersControl entries. Consider a positionally-unique key, e.g. `${idx}-${layer.name}`.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
around lines 369 - 378, The LayersControl base-layer list in GeometryViewer.jsx
uses key={layer.name}, which can collide when a custom tile provider name
matches one of the built-in layer names. Update the map over
getBaseLayers(customTileProvider) to use a positionally unique key (for example,
include the index with the layer name) so each LayersControl.BaseLayer remains
uniquely identified even when names duplicate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`:
- Around line 423-428: The custom tile provider warning is being added to
parsed.infoList in GeometryViewer, which makes it render as a full-panel
EmptyPanelMessage even when geometry exists. Update the GeometryViewer
parsing/render flow so invalid customTileProvider warnings are only shown in
empty-state/fallback cases or inline near the tile controls, and keep
parsed.infoList reserved for actual empty-panel messages.

In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewerUtils.js`:
- Around line 22-40: The custom tile provider name in getCustomTileProvider is
returned unsanitized and later rendered by L.Control.Layers via innerHTML, which
can allow injected markup. Update the name handling in
GeometryViewerUtils.getCustomTileProvider to sanitize or escape
prefs.custom_tile_name the same way attribution is handled before returning the
provider object, while keeping the fallback gettext('Custom') intact.

---

Nitpick comments:
In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`:
- Around line 400-407: The `GeometryViewer` component is subscribing to the
entire preferences store via `usePreferences()`, which causes unnecessary
re-renders on unrelated preference updates. Update the `GeometryViewer` hook
usage to select only the `version` field from `usePreferences`, and keep the
`getCustomTileProvider` lookup driven by that selected value so the memo still
refreshes when preferences change.
- Around line 369-378: The LayersControl base-layer list in GeometryViewer.jsx
uses key={layer.name}, which can collide when a custom tile provider name
matches one of the built-in layer names. Update the map over
getBaseLayers(customTileProvider) to use a positionally unique key (for example,
include the index with the layer name) so each LayersControl.BaseLayer remains
uniquely identified even when names duplicate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 346ffa83-31fa-4fe6-93ec-bad82d724884

📥 Commits

Reviewing files that changed from the base of the PR and between dfc4ef3 and 3983559.

📒 Files selected for processing (7)
  • docs/en_US/editgrid.rst
  • docs/en_US/preferences.rst
  • web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx
  • web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewerUtils.js
  • web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py
  • web/pgadmin/utils/constants.py
  • web/regression/javascript/sqleditor/GeometryViewerUtils.spec.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx (1)

374-375: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid provider-controlled React keys. layer.name can collide with built-in layer names like Empty or Street, which creates duplicate keys and unstable reconciliation. Use a stable key that includes a non-display field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
around lines 374 - 375, The BaseLayer list in GeometryViewer.jsx is using
provider-controlled layer.name as the React key, which can collide with built-in
names and cause unstable reconciliation. Update the map over
getBaseLayers(customTileProvider) so the key is derived from a stable identifier
that includes a non-display field from the layer object, and keep the visible
name prop unchanged on LayersControl.BaseLayer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`:
- Around line 374-375: The BaseLayer list in GeometryViewer.jsx is using
provider-controlled layer.name as the React key, which can collide with built-in
names and cause unstable reconciliation. Update the map over
getBaseLayers(customTileProvider) so the key is derived from a stable identifier
that includes a non-display field from the layer object, and keep the visible
name prop unchanged on LayersControl.BaseLayer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 60345c4a-d32b-400c-8fd1-31dfac5c6342

📥 Commits

Reviewing files that changed from the base of the PR and between 3983559 and 8380aeb.

📒 Files selected for processing (3)
  • web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx
  • web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewerUtils.js
  • web/regression/javascript/sqleditor/GeometryViewerUtils.spec.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewerUtils.js
  • web/regression/javascript/sqleditor/GeometryViewerUtils.spec.js

@NivGreenstein NivGreenstein force-pushed the feat/geometry-viewer-settings branch from 4e8b1e9 to 5cc2441 Compare July 6, 2026 10:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx (1)

452-458: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep the observed container stable when remounting the map. The StyledBox key remounts the element that ResizeObserver watches, but the observer is only created when queryToolCtx changes. After an SRID/provider/column switch, the new container is never observed, so later resizes can stop invalidating the Leaflet map. Move the key to MapContainer or reattach the observer when the ref target changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
around lines 452 - 458, The StyledBox key in GeometryViewer.jsx is remounting
the element that contentRef and ResizeObserver depend on, so the new container
is not re-observed after SRID/provider/column changes. Update the GeometryViewer
render so the stable observed container keeps the same ref target, and apply the
remount key to MapContainer instead, or add logic in the effect that sets up
ResizeObserver to reattach whenever the ref element changes. Ensure the observer
lifecycle is tied to the actual container referenced by contentRef and not only
to queryToolCtx.
🧹 Nitpick comments (1)
web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx (1)

184-260: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated dynamic-options loader.

Both branches implement the same cache/API/fallback flow. A small helper would keep refresh and non-refresh selects from drifting.

Refactor sketch
+const getDynamicOptionsLoader = (optionsEndpoint, staticOptions = []) => () => {
+	if (optionsCache[optionsEndpoint]) {
+		return Promise.resolve([
+			...optionsCache[optionsEndpoint],
+			...staticOptions,
+		]);
+	}
+
+	const api = getApiInstance();
+	const optionsUrl = url_for(optionsEndpoint);
+	return api.get(optionsUrl)
+		.then((res) => {
+			const dynamicOptions = res.data?.data?.models;
+			if (!dynamicOptions) {
+				return staticOptions;
+			}
+			optionsCache[optionsEndpoint] = dynamicOptions;
+			return [...dynamicOptions, ...staticOptions];
+		})
+		.catch(() => staticOptions);
+};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx` around
lines 184 - 260, The dynamic options loading logic is duplicated in both
branches that assign element.options in PreferencesHelper.jsx, and both
implement the same cache, API fetch, and static fallback flow. Extract that
shared behavior into a small helper used by both the refresh and non-refresh
paths, preserving the existing optionsCache, getApiInstance, and url_for
handling so the two select modes cannot drift apart.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx`:
- Line 99: Preserve any previously injected control handlers when normalizing
control props in PreferencesHelper’s control prop handling. The assignment to
element.controlProps should merge with existing handlers instead of replacing
them, so the onFocus/onBlur wiring added in the select-refresh dependency path
still survives and depchange can fire. Update the normalization logic in
PreferencesHelper to keep both the existing controlProps and the normalized
control_props data, especially for the dependency-related flow that attaches
handlers later.
- Around line 269-273: The shortcut value fallback in PreferencesHelper should
preserve intentionally empty strings instead of restoring the default. Update
the field assignment in PreferencesHelper.jsx around the getPreferences lookup
so it uses a nullish fallback rather than a truthy one, ensuring a saved empty
shortcut remains cleared while still falling back only when the stored
preference is missing or undefined.
- Around line 391-401: The follow-up actions in the PreferencesHelper flow are
not awaited, so rejections from reloadPgAdmin() or
pgAdmin.Browser.tree.destroy() bypass the existing error handling. Update the
branch that handles reloadNow and the tree.destroy() path to await those async
calls (and keep the pgAdmin.Browser.Events.trigger and modalClose sequencing
after destruction), so any failure is caught by the surrounding catch/notifier
logic.
- Line 112: The JSON.parse call in PreferencesHelper.jsx can throw when a
persisted binary-path preference contains malformed JSON, which blocks the
Preferences UI during schema preparation. Update the PreferencesHelper flow that
builds fieldValues from element.value to safely parse this specific persisted
value, catching parse failures and falling back to a safe default so rendering
can continue. Use the existing fieldValues assignment logic in PreferencesHelper
and keep the guard localized to the preference-parsing path.

---

Outside diff comments:
In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`:
- Around line 452-458: The StyledBox key in GeometryViewer.jsx is remounting the
element that contentRef and ResizeObserver depend on, so the new container is
not re-observed after SRID/provider/column changes. Update the GeometryViewer
render so the stable observed container keeps the same ref target, and apply the
remount key to MapContainer instead, or add logic in the effect that sets up
ResizeObserver to reattach whenever the ref element changes. Ensure the observer
lifecycle is tied to the actual container referenced by contentRef and not only
to queryToolCtx.

---

Nitpick comments:
In `@web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx`:
- Around line 184-260: The dynamic options loading logic is duplicated in both
branches that assign element.options in PreferencesHelper.jsx, and both
implement the same cache, API fetch, and static fallback flow. Extract that
shared behavior into a small helper used by both the refresh and non-refresh
paths, preserving the existing optionsCache, getApiInstance, and url_for
handling so the two select modes cannot drift apart.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e71c64bd-99de-42af-b4a0-563e2887c76e

📥 Commits

Reviewing files that changed from the base of the PR and between 8380aeb and 4e8b1e9.

📒 Files selected for processing (3)
  • web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx
  • web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx
  • web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py

Comment thread web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx Outdated
Comment thread web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx Outdated
Comment thread web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx Outdated
Comment thread web/pgadmin/preferences/static/js/components/PreferencesHelper.jsx Outdated
@NivGreenstein

Copy link
Copy Markdown
Author

Hey @adityatoshniwal!
CodeRabbit has resolved all the issues it found, and it suggested you as a reviewer for this PR.
Could you please take a look and help me move it forward?
Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant