Skip to content

Diagnostic connection probe — surface Iceberg metadata to caller - #28

Merged
gordonmurray merged 1 commit into
mainfrom
feat/connection-probe-diagnostics
Apr 15, 2026
Merged

gordonmurray merged 1 commit into
mainfrom
feat/connection-probe-diagnostics

Conversation

@gordonmurray

@gordonmurray gordonmurray commented Apr 15, 2026 •

Copy link
Copy Markdown
Owner

Closes #26.

Before

/api/connect/test returned a binary success/failure with the path echoed back:

{"status":"success","tableInfo":{"path":"s3://...","suggestedQuery":"SELECT * ..."}}

The probe internally read version-hint.text and iceberg_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:

  1. Metadata JSON — SELECT content FROM read_text('<path>/metadata/*.metadata.json') ORDER BY filename DESC LIMIT 1. The glob handles both pyiceberg's NNNNN-<uuid>.metadata.json and Spark's vN.metadata.json naming. Parsed for format-version, current-snapshot-id, last-updated-ms.
  2. iceberg_metadata() — aggregate SUM(record_count), COUNT(*), BOOL_OR(manifest_content <> 'DATA') for rows / files / deletes.

If both fail, the whole probe fails → run_connection_test returns None → route returns 400. If one fails but the other succeeds, the response just has fewer fields populated.

Design notes

  • version-hint.text isn't written by pyiceberg, so we don't rely on it. The previous implementation's attempt was effectively dead code.
  • snapshotId is stringified — Iceberg snapshot IDs are 64-bit and would suffer JS Number precision loss as integers on the wire.
  • run_connection_test now returns Optional[TableInfo] (breaking change to its Python signature; only caller updated). Route still returns a 400 on None.

Verified

Happy path  — 200, full tableInfo populated
Invalid path — 400 Connection test failed
Wrong credentials — 400 Connection test failed
Demo (no tablePath) — 200, full tableInfo from the bundled Iceberg table
Query endpoint — still returns 37,537 rows (no regression)
  • Frontend's renderTableInfo exercised in a headless Node harness against the real API payload; HTML output verified for happy / deletes / null cases.

/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
gordonmurray merged commit 9b78c95 into main Apr 15, 2026
4 checks passed
@gordonmurray
gordonmurray deleted the feat/connection-probe-diagnostics branch April 15, 2026 19:14
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).
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.

feat(diagnostics): richer connection probe — surface Iceberg metadata to caller

1 participant