Repository navigation
Diagnostic connection probe — surface Iceberg metadata to caller - #28
Merged
Merged
Conversation
/api/connect/test used to return a binary success/failure with the path
echoed back. The probe internally read version-hint.text and
iceberg_metadata but discarded everything it learned, leaving the UI
with nothing to show the user beyond a tick.
This rewires the probe to return structured metadata so the Connection
panel can show 'Iceberg v2, 37,537 rows, last snapshot 2h ago' — turning
a silent success into real onboarding signal. Directly attacks the
funnel problem (3 logged in, 0 saved connection).
Backend:
- New TableInfo response model (path, suggestedQuery, format, rows,
files, hasDeletes, snapshotId, lastSnapshotAt — all Iceberg-specific
fields optional so REST-catalog and demo paths work unchanged).
- _probe_iceberg_table reads the latest `metadata/*.metadata.json` via
glob+ORDER BY (handles both pyiceberg's NNNNN- and Spark's vN- naming;
version-hint.text is not written by pyiceberg) for format version,
current-snapshot-id, last-updated-ms. iceberg_metadata() supplies row
count, file count, delete detection. Either probe succeeding yields a
populated TableInfo; both failing bubbles up so run_connection_test
returns None — preserves the invalid-path => 400 behaviour.
- run_connection_test returns Optional[TableInfo] instead of bool.
Route handler serialises via model_dump(exclude_none=True).
Frontend:
- renderTableInfo builds a labelled value grid under Connection. Numbers
are thousand-formatted; timestamps rendered as relative age
("2h ago"); a table with deletes is flagged in red.
- All user-derived values are HTML-escaped on the way in.
Tests:
- backend/tests/test_connection_probing.py: probe returns None on
failure, reads *.metadata.json (not version-hint.text), populates all
TableInfo fields from a real-shaped JSON payload, degrades gracefully
when metadata JSON is unreadable, flags delete manifests.
Closes #26
gordonmurray
added a commit
that referenced
this pull request
Apr 15, 2026
Covers the three most common 400s users will hit — delete manifests, connection test failure (with the four likely causes in order of how often they turn up), the "query is slow" triage flow, and the read-only SQL guard — with the concrete fix for each. Also updates the Test Connection blurb to mention the format, row count, file count, and snapshot age the panel now shows (from PR #28). File stays at 277 lines — well under the ~500-line split threshold.
gordonmurray
added a commit
that referenced
this pull request
Apr 15, 2026
…#34) Covers the three most common 400s users will hit — delete manifests, connection test failure (with the four likely causes in order of how often they turn up), the "query is slow" triage flow, and the read-only SQL guard — with the concrete fix for each. Also updates the Test Connection blurb to mention the format, row count, file count, and snapshot age the panel now shows (from PR #28).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #26.
Before
/api/connect/testreturned a binary success/failure with the path echoed back:{"status":"success","tableInfo":{"path":"s3://...","suggestedQuery":"SELECT * ..."}}The probe internally read
version-hint.textandiceberg_metadata()but discarded the results, so the UI had nothing to show beyond a green tick. A user with a typo in their path got a silent success (if the bucket existed) and no confidence signal.After
{ "status": "success", "tableInfo": { "path": "s3://movies/warehouse/demo/movies", "suggestedQuery": "SELECT * FROM iceberg_scan('s3://movies/warehouse/demo/movies') LIMIT 10", "format": "iceberg-v2", "rows": 37537, "files": 1, "hasDeletes": false, "snapshotId": "6450139374914496971", "lastSnapshotAt": "2026-04-15T19:02:46.193000Z" } }Frontend renders this as a labelled grid under the Connection panel: "Format iceberg-v2, Rows 37,537, Files 1, Last snapshot 2h ago" — turning a silent probe into real onboarding signal. A table with deletes is flagged in red.
How the probe works
Two sub-probes, at least one must succeed:
SELECT content FROM read_text('<path>/metadata/*.metadata.json') ORDER BY filename DESC LIMIT 1. The glob handles both pyiceberg'sNNNNN-<uuid>.metadata.jsonand Spark'svN.metadata.jsonnaming. Parsed forformat-version,current-snapshot-id,last-updated-ms.SUM(record_count),COUNT(*),BOOL_OR(manifest_content <> 'DATA')for rows / files / deletes.If both fail, the whole probe fails →
run_connection_testreturnsNone→ route returns 400. If one fails but the other succeeds, the response just has fewer fields populated.Design notes
version-hint.textisn't written by pyiceberg, so we don't rely on it. The previous implementation's attempt was effectively dead code.snapshotIdis stringified — Iceberg snapshot IDs are 64-bit and would suffer JSNumberprecision loss as integers on the wire.run_connection_testnow returnsOptional[TableInfo](breaking change to its Python signature; only caller updated). Route still returns a 400 onNone.Verified
pytest backend/tests/— 5 new tests for the probe behaviour; 41/42 pass (the 1 failure is the intentionally-redtest_metrics.pydriving feat(metrics): replace placeholder bytesScanned with real DuckDB profile #25, unrelated to this PR).ruff checkclean.docker compose up --build:renderTableInfoexercised in a headless Node harness against the real API payload; HTML output verified for happy / deletes / null cases.