feat(app): Query Stats debug drawer#2390
Conversation
Captures every ClickHouse query the app dispatches into an in-memory ring buffer (max 200 events) and surfaces them in a bottom drawer opened from the user menu. Each row shows status, duration with a 2s/10s color threshold, and SQL with params interpolated client-side for readability. Expanded view exposes the raw parameterized SQL as sent to ClickHouse, params, query_id, connection, and a one-click "Run EXPLAIN". Filter to current-page events (reactive on client-side nav) and toggle visibility of EXPLAIN events. Capture is wrapped in defensive error handling and an error boundary so instrumentation can never break a production query path.
🦋 Changeset detectedLatest commit: 8174d06 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🟡 Tier 3 — StandardIntroduces new logic, modifies core functionality, or touches areas with non-trivial risk. Why this tier:
Review process: Full human review — logic, architecture, edge cases. Stats
|
E2E Test Results❌ 1 test failed • 190 passed • 3 skipped • 1178s
Tests ran across 4 shards in parallel. |
Deep Review✅ No critical issues found. No P0/P1 issues after re-grading. The instrumentation path is defensively wrapped, the store guards against SSR, and the drawer is gated behind an error boundary. Several P2 items below are worth addressing before merge — most concentrate on the drawer's render cost and the missing UI test coverage for ~450 new lines of React code. 🟡 P2 -- recommended
🔵 P3 nitpicks (19)
Reviewers (12): correctness, testing, maintainability, project-standards, agent-native, learnings, security, performance, reliability, adversarial, kieran-typescript, frontend-races. Testing gaps:
|
- Separate internal event id from ClickHouse query_id so duplicate caller-supplied query_ids no longer conflate events in the store - Shallow-clone captured query params so post-call mutation by the caller cannot rewrite the stored event - Gate drawer mount until first open so the global event subscription doesn't re-render an unused panel app-wide on every captured query - Truncate captured SQL/error strings at 32KB so chart configs with multi-KB SQL can't pin tens of MB in the buffer - No-op store mutators on the server so SSR renders don't accumulate across requests in the same Node process - Use window.location.pathname as the single source of truth for capture-side pathname and drawer-side filter; re-render on Next's routeChangeComplete - Hide Run EXPLAIN for non-SELECT/WITH statements and disable while loading to avoid concurrent EXPLAIN races - Reset error boundary when the drawer is reopened after a crash - Memoize collapseWhitespace result so multi-KB SQL isn't re-scanned twice per row render - Fix .spin -> .spin-animate so the pending indicator actually spins - Guard test-only reset exports behind NODE_ENV !== 'production' - Add tests for event-id/queryId separation, duplicate-queryId isolation, and params shallow-clone
|
Pushed Addressed (P2):
Addressed (P3):
Skipped — with reasons:
|
Summary
Adds an in-app "Query Stats" debug drawer that captures every ClickHouse query the app dispatches and surfaces them in a bottom drawer (opened from the user menu). Built to help engineers and support quickly see what queries a page is actually running, how slow they are, and what they look like with params filled in.
What's in it
InstrumentedClickhouseClient) wraps the existing browserClickhouseClient, captures everyquery()call into an in-memory ring buffer (max 200 events), and auto-assigns aquery_idwhen callers don't supply one. Capture is wrapped in defensive try/catch so a broken store can never throw into a production query path.useSyncExternalStorefor state (no Jotai/Zustand). Stable snapshot reference until a change happens.EXPLAIN PLAN indexes = 1, returned asTabSeparatedRaw).router.asPath) and a toggle to show EXPLAIN events.Notes for reviewers
InstrumentedClickhouseClientis now the default constructor ingetClickhouseClientfor both local and proxy modes. Behavior is identical to the bareClickhouseClientaside from instrumentation.event.pathnameiswindow.location.pathnameat dispatch time, so events stay attributed to the page that initiated them even if the user navigates before the query resolves./dashboards/abcand/dashboards/defare treated as separate "pages". This is intentional for now; route-pattern grouping is a future iteration.QueryFunctionContext.queryKeythroughQueryInputs).EXPLAIN PLAN indexes = 1is MergeTree-specific; failures render gracefully in the UI as an error message.Test plan
make ci-lintandmake ci-unitpass