From aa290cfa092dac7c77a82a049bbbc9e9f57de6bd Mon Sep 17 00:00:00 2001 From: Rohith Pariki Date: Wed, 5 Aug 2026 04:29:22 +0530 Subject: [PATCH 1/4] feat(export): Exclude hidden fields from vector data (closes #1676) --- .../src/components/layout/TopToolbar.tsx | 3 +- .../src/components/panels/AttributeTable.tsx | 24 ++++++++- .../src/components/panels/LayerPanel.tsx | 11 +++- .../src/hooks/useProjectFileActions.ts | 8 ++- .../geolibre-desktop/src/i18n/locales/en.json | 2 + .../geolibre_server/app/postgis.py | 8 ++- backend/geolibre_server/tests/test_postgis.py | 20 +++++++ packages/core/src/index.ts | 4 ++ packages/core/src/types.ts | 12 +++++ packages/core/src/visibility.ts | 54 +++++++++++++++++++ packages/processing/src/sidecar-client.ts | 1 + 11 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 packages/core/src/visibility.ts diff --git a/apps/geolibre-desktop/src/components/layout/TopToolbar.tsx b/apps/geolibre-desktop/src/components/layout/TopToolbar.tsx index 653fc626c1..63adcb372d 100644 --- a/apps/geolibre-desktop/src/components/layout/TopToolbar.tsx +++ b/apps/geolibre-desktop/src/components/layout/TopToolbar.tsx @@ -1,5 +1,6 @@ import { DEFAULT_PROJECT_NAME, + excludeHiddenFieldsFromProject, redactProjectCredentials, serializeProject, useAppStore, @@ -2002,7 +2003,7 @@ export function TopToolbar({ // Shared projects are opened on another machine where the local files // don't exist, so always embed the vector data (never file references). const { project, defaultProjectName } = await projectFiles.buildEmbeddedProject(title); - const redacted = redactProjectCredentials(project); + const redacted = redactProjectCredentials(excludeHiddenFieldsFromProject(project)); // Strip path separators, control chars, and other characters that are // illegal in filenames so the server gets a predictable name. const safeName = defaultProjectName.replace( diff --git a/apps/geolibre-desktop/src/components/panels/AttributeTable.tsx b/apps/geolibre-desktop/src/components/panels/AttributeTable.tsx index 3c54aa4421..36674e0c76 100644 --- a/apps/geolibre-desktop/src/components/panels/AttributeTable.tsx +++ b/apps/geolibre-desktop/src/components/panels/AttributeTable.tsx @@ -5,6 +5,7 @@ import { isDuckDBQueryLayer, useAppStore, validateAttributeFormValues, + excludeHiddenFieldsFromGeojson, type AttributeFormConfig, type AttributeFormFieldConfig, type AttributeFormFieldError, @@ -70,6 +71,7 @@ import { Telescope, Trash2, X, + Ban, } from "lucide-react"; import { type MouseEvent as ReactMouseEvent, @@ -927,8 +929,11 @@ export function AttributeTable({ mapControllerRef }: AttributeTableProps) { try { setExportError(null); setExportWarning(null); - const exportGeojson = geojsonWithDrafts(); + let exportGeojson = geojsonWithDrafts(); if (!exportGeojson) return; + if (layer.fieldVisibility) { + exportGeojson = excludeHiddenFieldsFromGeojson(exportGeojson, layer.fieldVisibility); + } const baseName = sanitizeExportFileName(layer.name); const savedPath = await exportVectorLayer(exportGeojson, format, baseName, layer.name); @@ -995,6 +1000,19 @@ export function AttributeTable({ mapControllerRef }: AttributeTableProps) { updateLayer(layer.id, toggleColumnHidden(layer, col)); }; + const handleToggleExcluded = (col: string) => { + if (!layer) return; + const current = layer.fieldVisibility || {}; + const isExcluded = current[col] === "excluded"; + const next = { ...current }; + if (isExcluded) { + delete next[col]; + } else { + next[col] = "excluded"; + } + updateLayer(layer.id, { fieldVisibility: next }); + }; + const handleShowAllColumns = () => { if (!layer) return; updateLayer(layer.id, showAllColumns(layer)); @@ -1368,6 +1386,10 @@ export function AttributeTable({ mapControllerRef }: AttributeTableProps) { {t("attributeTable.hideField")} + handleToggleExcluded(col)}> + + {layer?.fieldVisibility?.[col] === "excluded" ? t("attributeTable.includeField", "Include field on export") : t("attributeTable.excludeField", "Exclude field on export")} + handleMoveColumn(col, isRtl ? "right" : "left")} diff --git a/apps/geolibre-desktop/src/components/panels/LayerPanel.tsx b/apps/geolibre-desktop/src/components/panels/LayerPanel.tsx index f782c739dd..c96f802c3f 100644 --- a/apps/geolibre-desktop/src/components/panels/LayerPanel.tsx +++ b/apps/geolibre-desktop/src/components/panels/LayerPanel.tsx @@ -27,6 +27,7 @@ import { pluginOwnsPaint, supportsBridgedOpacity, useAppStore, + excludeHiddenFieldsFromGeojson, } from "@geolibre/core"; import type { EllipsoidId, GeoLibreLayer, LayerGroup } from "@geolibre/core"; import type { FeatureCollection } from "geojson"; @@ -1542,8 +1543,11 @@ export function LayerPanel({ scheduleStatusClear(layer.id); return; } + const egressGeojson = layer.fieldVisibility + ? excludeHiddenFieldsFromGeojson(geojson, layer.fieldVisibility) + : geojson; const savedPath = await exportVectorLayer( - geojson, + egressGeojson, format, sanitizeExportFileName(layer.name), layer.name, @@ -1915,6 +1919,11 @@ export function LayerPanel({ connection, schema_name: schema, table, + excluded_fields: layer.fieldVisibility + ? Object.keys(layer.fieldVisibility).filter( + (k) => layer.fieldVisibility![k] === "excluded", + ) + : undefined, }); } catch { // The write committed; only the refresh failed. Reporting this as diff --git a/apps/geolibre-desktop/src/hooks/useProjectFileActions.ts b/apps/geolibre-desktop/src/hooks/useProjectFileActions.ts index 15227ad355..f878eaf4f2 100644 --- a/apps/geolibre-desktop/src/hooks/useProjectFileActions.ts +++ b/apps/geolibre-desktop/src/hooks/useProjectFileActions.ts @@ -3,6 +3,7 @@ import { detachProjectCopy, projectFromStore, redactProjectCredentials, + excludeHiddenFieldsFromProject, serializeProject, useAppStore, type GeoLibreLayer, @@ -843,13 +844,18 @@ export function useProjectFileActions(mapControllerRef: MapControllerRef) { // them. Make keeping them an explicit choice and use the same central // redaction pass as every external egress. let contentToSave = content; - const redacted = redactProjectCredentials(project); + const projectToEgress = excludeHiddenFieldsFromProject(project); + const redacted = redactProjectCredentials(projectToEgress); if (redacted.redactedPaths.length > 0) { const choice = await askStripCredentials(redacted.redactedCount); if (choice === "cancel") return false; if (choice === "strip") { contentToSave = serializeProject(redacted.project); + } else { + contentToSave = serializeProject(projectToEgress); } + } else { + contentToSave = serializeProject(projectToEgress); } // Projects opened from a URL have no writable path, so both Save and // Save As fall back to the save dialog for them. diff --git a/apps/geolibre-desktop/src/i18n/locales/en.json b/apps/geolibre-desktop/src/i18n/locales/en.json index 693ddf5180..14ccbf6a44 100644 --- a/apps/geolibre-desktop/src/i18n/locales/en.json +++ b/apps/geolibre-desktop/src/i18n/locales/en.json @@ -4207,6 +4207,8 @@ "manageFieldAria": "Manage field {{name}}", "renameField": "Rename field", "hideField": "Hide field", + "excludeField": "Exclude field on export", + "includeField": "Include field on export", "moveLeft": "Move left", "moveRight": "Move right", "deleteField": "Delete field", diff --git a/backend/geolibre_server/geolibre_server/app/postgis.py b/backend/geolibre_server/geolibre_server/app/postgis.py index 9dc9bf08e1..1bd28147a0 100644 --- a/backend/geolibre_server/geolibre_server/app/postgis.py +++ b/backend/geolibre_server/geolibre_server/app/postgis.py @@ -256,6 +256,7 @@ class PostgisReadRequest(BaseModel): connection: str schema_name: str = "public" table: str + excluded_fields: list[str] = [] class PostgisWriteRequest(BaseModel): @@ -538,8 +539,11 @@ def postgis_read(request: PostgisReadRequest) -> dict[str, Any]: if info["srid"] not in (0, 4326) else sql.SQL("ST_AsGeoJSON({geom})").format(geom=geom) ) + read_columns = [ + col for col in info["columns"] if col not in request.excluded_fields + ] column_list = sql.SQL(", ").join( - [geom_expr] + [sql.Identifier(column) for column in info["columns"]] + [geom_expr] + [sql.Identifier(col) for col in read_columns] ) query = sql.SQL("SELECT {columns} FROM {schema}.{table} LIMIT %s").format( columns=column_list, @@ -576,7 +580,7 @@ def postgis_read(request: PostgisReadRequest) -> dict[str, Any]: pk = info["primary_key"] features = [] for row in rows: - properties = {column: _json_safe(value) for column, value in zip(info["columns"], row[1:])} + properties = {column: _json_safe(value) for column, value in zip(read_columns, row[1:])} feature: dict[str, Any] = { "type": "Feature", "geometry": json.loads(row[0]) if row[0] else None, diff --git a/backend/geolibre_server/tests/test_postgis.py b/backend/geolibre_server/tests/test_postgis.py index c041d91ab6..d700e2694b 100644 --- a/backend/geolibre_server/tests/test_postgis.py +++ b/backend/geolibre_server/tests/test_postgis.py @@ -361,6 +361,26 @@ def test_read_returns_wgs84_with_primary_key(live_table) -> None: assert knox["id"] == knox["properties"]["gid"] +@requires_live_postgis +def test_read_drops_excluded_fields(live_table) -> None: + result = postgis_read( + PostgisReadRequest( + connection=LIVE_DSN, + table=TABLE, + excluded_fields=["population", "name"] + ) + ) + features = result["geojson"]["features"] + assert len(features) == 3 + knox = features[0] + assert "gid" in knox["properties"] + assert "population" not in knox["properties"] + assert "name" not in knox["properties"] + # The geometry and id must still be populated correctly. + assert "geometry" in knox + assert "id" in knox + + @requires_live_postgis def test_read_unknown_table_404(live_table) -> None: with pytest.raises(HTTPException) as exc: diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index bb92ef2fa2..217f9dc8c5 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -130,3 +130,7 @@ export { redactUrlCredentials, type CredentialRedactionResult, } from "./credentials"; +export { + excludeHiddenFieldsFromGeojson, + excludeHiddenFieldsFromProject, +} from "./visibility"; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 0b0b62160a..68bb0dd09f 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -852,6 +852,13 @@ export interface LayerConnection { onFailure: "keep-last" | "clear"; } +/** + * Visibility of a layer's attribute field. + * - "hidden": Not shown in the attribute table, identify popup, tooltips, or field pickers, but remains in the data. + * - "excluded": Removed entirely from the data when the project is shared or exported. + */ +export type FieldVisibility = "hidden" | "excluded"; + export interface GeoLibreLayer { id: string; name: string; @@ -863,6 +870,11 @@ export interface GeoLibreLayer { metadata: Record; beforeId?: string; geojson?: FeatureCollection; + /** + * Field-level visibility overrides. Fields marked as "excluded" are physically + * removed from the data during export and sharing. + */ + fieldVisibility?: Record; /** * Per-field edit-widget, constraint, and visibility configuration authored * in the Attribute Form designer. Applied by the attribute editing surfaces diff --git a/packages/core/src/visibility.ts b/packages/core/src/visibility.ts new file mode 100644 index 0000000000..f5b0b7d750 --- /dev/null +++ b/packages/core/src/visibility.ts @@ -0,0 +1,54 @@ +import type { FeatureCollection } from "geojson"; +import type { GeoLibreProject, FieldVisibility } from "./types"; + +/** + * Returns a new FeatureCollection with properties marked as "excluded" removed. + */ +export function excludeHiddenFieldsFromGeojson( + geojson: FeatureCollection, + fieldVisibility?: Record +): FeatureCollection { + const excludedKeys = new Set( + Object.entries(fieldVisibility || {}) + .filter(([_, visibility]) => visibility === "excluded") + .map(([key]) => key) + ); + + if (excludedKeys.size === 0) { + return geojson; + } + + // Deep clone to avoid mutating the live store state + const stripped: FeatureCollection = { + ...geojson, + features: geojson.features.map((feature) => { + const properties = { ...feature.properties }; + for (const key of excludedKeys) { + delete properties[key]; + } + return { ...feature, properties }; + }), + }; + + return stripped; +} + +/** + * Returns a new GeoLibreProject where all layers have their excluded fields + * physically removed from their inline GeoJSON. + */ +export function excludeHiddenFieldsFromProject(project: GeoLibreProject): GeoLibreProject { + let changed = false; + const layers = project.layers.map((layer) => { + if (layer.fieldVisibility && layer.geojson) { + const strippedGeojson = excludeHiddenFieldsFromGeojson(layer.geojson, layer.fieldVisibility); + if (strippedGeojson !== layer.geojson) { + changed = true; + return { ...layer, geojson: strippedGeojson }; + } + } + return layer; + }); + + return changed ? { ...project, layers } : project; +} diff --git a/packages/processing/src/sidecar-client.ts b/packages/processing/src/sidecar-client.ts index 33c0c55335..0675e97a8f 100644 --- a/packages/processing/src/sidecar-client.ts +++ b/packages/processing/src/sidecar-client.ts @@ -796,6 +796,7 @@ export interface ReadPostgisTableRequest { connection: string; schema_name?: string; table: string; + excluded_fields?: string[]; } export interface ReadPostgisTableResult { From 04e12ec2d043d25689d6986d8abb3b03f879dcae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:10:06 +0000 Subject: [PATCH 2/4] style: auto-format (ruff + oxfmt) [pre-commit.ci] --- .../src/components/panels/AttributeTable.tsx | 4 +++- backend/geolibre_server/geolibre_server/app/postgis.py | 4 +--- backend/geolibre_server/tests/test_postgis.py | 6 +----- packages/core/src/index.ts | 5 +---- packages/core/src/visibility.ts | 4 ++-- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/apps/geolibre-desktop/src/components/panels/AttributeTable.tsx b/apps/geolibre-desktop/src/components/panels/AttributeTable.tsx index 36674e0c76..b5d9bc452a 100644 --- a/apps/geolibre-desktop/src/components/panels/AttributeTable.tsx +++ b/apps/geolibre-desktop/src/components/panels/AttributeTable.tsx @@ -1388,7 +1388,9 @@ export function AttributeTable({ mapControllerRef }: AttributeTableProps) { handleToggleExcluded(col)}> - {layer?.fieldVisibility?.[col] === "excluded" ? t("attributeTable.includeField", "Include field on export") : t("attributeTable.excludeField", "Exclude field on export")} + {layer?.fieldVisibility?.[col] === "excluded" + ? t("attributeTable.includeField", "Include field on export") + : t("attributeTable.excludeField", "Exclude field on export")} dict[str, Any]: if info["srid"] not in (0, 4326) else sql.SQL("ST_AsGeoJSON({geom})").format(geom=geom) ) - read_columns = [ - col for col in info["columns"] if col not in request.excluded_fields - ] + read_columns = [col for col in info["columns"] if col not in request.excluded_fields] column_list = sql.SQL(", ").join( [geom_expr] + [sql.Identifier(col) for col in read_columns] ) diff --git a/backend/geolibre_server/tests/test_postgis.py b/backend/geolibre_server/tests/test_postgis.py index d700e2694b..f5803cebd0 100644 --- a/backend/geolibre_server/tests/test_postgis.py +++ b/backend/geolibre_server/tests/test_postgis.py @@ -364,11 +364,7 @@ def test_read_returns_wgs84_with_primary_key(live_table) -> None: @requires_live_postgis def test_read_drops_excluded_fields(live_table) -> None: result = postgis_read( - PostgisReadRequest( - connection=LIVE_DSN, - table=TABLE, - excluded_fields=["population", "name"] - ) + PostgisReadRequest(connection=LIVE_DSN, table=TABLE, excluded_fields=["population", "name"]) ) features = result["geojson"]["features"] assert len(features) == 3 diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 217f9dc8c5..315f778b9b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -130,7 +130,4 @@ export { redactUrlCredentials, type CredentialRedactionResult, } from "./credentials"; -export { - excludeHiddenFieldsFromGeojson, - excludeHiddenFieldsFromProject, -} from "./visibility"; +export { excludeHiddenFieldsFromGeojson, excludeHiddenFieldsFromProject } from "./visibility"; diff --git a/packages/core/src/visibility.ts b/packages/core/src/visibility.ts index f5b0b7d750..ba6347fb29 100644 --- a/packages/core/src/visibility.ts +++ b/packages/core/src/visibility.ts @@ -6,12 +6,12 @@ import type { GeoLibreProject, FieldVisibility } from "./types"; */ export function excludeHiddenFieldsFromGeojson( geojson: FeatureCollection, - fieldVisibility?: Record + fieldVisibility?: Record, ): FeatureCollection { const excludedKeys = new Set( Object.entries(fieldVisibility || {}) .filter(([_, visibility]) => visibility === "excluded") - .map(([key]) => key) + .map(([key]) => key), ); if (excludedKeys.size === 0) { From 978ed5494526e9d599a01a612dbaf13c1a36f78a Mon Sep 17 00:00:00 2001 From: Rohith Pariki Date: Wed, 5 Aug 2026 04:53:05 +0530 Subject: [PATCH 3/4] fix(core,backend): address CodeRabbit review feedback on field exclusion --- .../geolibre_server/app/postgis.py | 15 +++-- backend/geolibre_server/tests/test_postgis.py | 8 ++- packages/core/src/visibility.ts | 25 +++++++- tests/visibility.test.ts | 64 +++++++++++++++++++ 4 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 tests/visibility.test.ts diff --git a/backend/geolibre_server/geolibre_server/app/postgis.py b/backend/geolibre_server/geolibre_server/app/postgis.py index ee51076ccd..e79b94de21 100644 --- a/backend/geolibre_server/geolibre_server/app/postgis.py +++ b/backend/geolibre_server/geolibre_server/app/postgis.py @@ -539,7 +539,10 @@ def postgis_read(request: PostgisReadRequest) -> dict[str, Any]: if info["srid"] not in (0, 4326) else sql.SQL("ST_AsGeoJSON({geom})").format(geom=geom) ) - read_columns = [col for col in info["columns"] if col not in request.excluded_fields] + pk = info["primary_key"] + read_columns = [ + col for col in info["columns"] if col not in request.excluded_fields or col == pk + ] column_list = sql.SQL(", ").join( [geom_expr] + [sql.Identifier(col) for col in read_columns] ) @@ -578,14 +581,16 @@ def postgis_read(request: PostgisReadRequest) -> dict[str, Any]: pk = info["primary_key"] features = [] for row in rows: - properties = {column: _json_safe(value) for column, value in zip(read_columns, row[1:])} + properties_raw = {column: _json_safe(value) for column, value in zip(read_columns, row[1:], strict=True)} feature: dict[str, Any] = { "type": "Feature", "geometry": json.loads(row[0]) if row[0] else None, - "properties": properties, + "properties": { + k: v for k, v in properties_raw.items() if k not in request.excluded_fields + }, } - if pk is not None and properties.get(pk) is not None: - feature["id"] = properties[pk] + if pk is not None and properties_raw.get(pk) is not None: + feature["id"] = properties_raw[pk] features.append(feature) return { diff --git a/backend/geolibre_server/tests/test_postgis.py b/backend/geolibre_server/tests/test_postgis.py index f5803cebd0..0f80adb753 100644 --- a/backend/geolibre_server/tests/test_postgis.py +++ b/backend/geolibre_server/tests/test_postgis.py @@ -364,14 +364,18 @@ def test_read_returns_wgs84_with_primary_key(live_table) -> None: @requires_live_postgis def test_read_drops_excluded_fields(live_table) -> None: result = postgis_read( - PostgisReadRequest(connection=LIVE_DSN, table=TABLE, excluded_fields=["population", "name"]) + PostgisReadRequest( + connection=LIVE_DSN, + table=TABLE, + excluded_fields=["population", "name", "gid"] + ) ) features = result["geojson"]["features"] assert len(features) == 3 knox = features[0] - assert "gid" in knox["properties"] assert "population" not in knox["properties"] assert "name" not in knox["properties"] + assert "gid" not in knox["properties"] # The geometry and id must still be populated correctly. assert "geometry" in knox assert "id" in knox diff --git a/packages/core/src/visibility.ts b/packages/core/src/visibility.ts index ba6347fb29..10b7def21d 100644 --- a/packages/core/src/visibility.ts +++ b/packages/core/src/visibility.ts @@ -40,14 +40,33 @@ export function excludeHiddenFieldsFromGeojson( export function excludeHiddenFieldsFromProject(project: GeoLibreProject): GeoLibreProject { let changed = false; const layers = project.layers.map((layer) => { - if (layer.fieldVisibility && layer.geojson) { + if (!layer.fieldVisibility) return layer; + + let updatedLayer = layer; + + if (layer.geojson) { const strippedGeojson = excludeHiddenFieldsFromGeojson(layer.geojson, layer.fieldVisibility); if (strippedGeojson !== layer.geojson) { changed = true; - return { ...layer, geojson: strippedGeojson }; + updatedLayer = { ...updatedLayer, geojson: strippedGeojson }; + } + } + + if (layer.metadata?.embeddedGeoJSON) { + const strippedEmbedded = excludeHiddenFieldsFromGeojson(layer.metadata.embeddedGeoJSON as FeatureCollection, layer.fieldVisibility); + if (strippedEmbedded !== layer.metadata.embeddedGeoJSON) { + changed = true; + updatedLayer = { + ...updatedLayer, + metadata: { + ...updatedLayer.metadata, + embeddedGeoJSON: strippedEmbedded, + }, + }; } } - return layer; + + return updatedLayer; }); return changed ? { ...project, layers } : project; diff --git a/tests/visibility.test.ts b/tests/visibility.test.ts new file mode 100644 index 0000000000..27b96c6450 --- /dev/null +++ b/tests/visibility.test.ts @@ -0,0 +1,64 @@ +import { test, describe } from "node:test"; +import assert from "node:assert"; +import type { GeoLibreProject } from "@geolibre/core"; +import { excludeHiddenFieldsFromProject } from "../packages/core/src/visibility"; + +describe("visibility", () => { + test("excludeHiddenFieldsFromProject strips excluded fields from geojson and embeddedGeoJSON", () => { + const project: GeoLibreProject = { + id: "proj-1", + name: "Test", + version: 1, + viewState: { + longitude: 0, + latitude: 0, + zoom: 0, + pitch: 0, + bearing: 0, + }, + layers: [ + { + id: "layer-1", + name: "Layer", + type: "geojson", + visible: true, + metadata: { + embeddedGeoJSON: { + type: "FeatureCollection", + features: [ + { + type: "Feature", + geometry: { type: "Point", coordinates: [0, 0] }, + properties: { keep: 1, drop: 2 }, + }, + ], + }, + }, + fieldVisibility: { drop: "excluded" }, + geojson: { + type: "FeatureCollection", + features: [ + { + type: "Feature", + geometry: { type: "Point", coordinates: [0, 0] }, + properties: { keep: 1, drop: 2 }, + }, + ], + }, + }, + ], + }; + + const stripped = excludeHiddenFieldsFromProject(project); + + // Check main geojson + const feature1 = stripped.layers[0].geojson!.features[0]; + assert.strictEqual(feature1.properties?.keep, 1); + assert.strictEqual(feature1.properties?.drop, undefined); + + // Check embedded geojson + const embeddedFeature = (stripped.layers[0].metadata.embeddedGeoJSON as any).features[0]; + assert.strictEqual(embeddedFeature.properties?.keep, 1); + assert.strictEqual(embeddedFeature.properties?.drop, undefined); + }); +}); From eb48425c7d77d256cb6179e69c163b057f3bcea4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:24:57 +0000 Subject: [PATCH 4/4] style: auto-format (ruff + oxfmt) [pre-commit.ci] --- backend/geolibre_server/geolibre_server/app/postgis.py | 4 +++- backend/geolibre_server/tests/test_postgis.py | 4 +--- packages/core/src/visibility.ts | 7 +++++-- tests/visibility.test.ts | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/geolibre_server/geolibre_server/app/postgis.py b/backend/geolibre_server/geolibre_server/app/postgis.py index e79b94de21..28213f744f 100644 --- a/backend/geolibre_server/geolibre_server/app/postgis.py +++ b/backend/geolibre_server/geolibre_server/app/postgis.py @@ -581,7 +581,9 @@ def postgis_read(request: PostgisReadRequest) -> dict[str, Any]: pk = info["primary_key"] features = [] for row in rows: - properties_raw = {column: _json_safe(value) for column, value in zip(read_columns, row[1:], strict=True)} + properties_raw = { + column: _json_safe(value) for column, value in zip(read_columns, row[1:], strict=True) + } feature: dict[str, Any] = { "type": "Feature", "geometry": json.loads(row[0]) if row[0] else None, diff --git a/backend/geolibre_server/tests/test_postgis.py b/backend/geolibre_server/tests/test_postgis.py index 0f80adb753..0be010a6b5 100644 --- a/backend/geolibre_server/tests/test_postgis.py +++ b/backend/geolibre_server/tests/test_postgis.py @@ -365,9 +365,7 @@ def test_read_returns_wgs84_with_primary_key(live_table) -> None: def test_read_drops_excluded_fields(live_table) -> None: result = postgis_read( PostgisReadRequest( - connection=LIVE_DSN, - table=TABLE, - excluded_fields=["population", "name", "gid"] + connection=LIVE_DSN, table=TABLE, excluded_fields=["population", "name", "gid"] ) ) features = result["geojson"]["features"] diff --git a/packages/core/src/visibility.ts b/packages/core/src/visibility.ts index 10b7def21d..b73cea70a9 100644 --- a/packages/core/src/visibility.ts +++ b/packages/core/src/visibility.ts @@ -41,7 +41,7 @@ export function excludeHiddenFieldsFromProject(project: GeoLibreProject): GeoLib let changed = false; const layers = project.layers.map((layer) => { if (!layer.fieldVisibility) return layer; - + let updatedLayer = layer; if (layer.geojson) { @@ -53,7 +53,10 @@ export function excludeHiddenFieldsFromProject(project: GeoLibreProject): GeoLib } if (layer.metadata?.embeddedGeoJSON) { - const strippedEmbedded = excludeHiddenFieldsFromGeojson(layer.metadata.embeddedGeoJSON as FeatureCollection, layer.fieldVisibility); + const strippedEmbedded = excludeHiddenFieldsFromGeojson( + layer.metadata.embeddedGeoJSON as FeatureCollection, + layer.fieldVisibility, + ); if (strippedEmbedded !== layer.metadata.embeddedGeoJSON) { changed = true; updatedLayer = { diff --git a/tests/visibility.test.ts b/tests/visibility.test.ts index 27b96c6450..8854386071 100644 --- a/tests/visibility.test.ts +++ b/tests/visibility.test.ts @@ -50,7 +50,7 @@ describe("visibility", () => { }; const stripped = excludeHiddenFieldsFromProject(project); - + // Check main geojson const feature1 = stripped.layers[0].geojson!.features[0]; assert.strictEqual(feature1.properties?.keep, 1);