feat(analytics): add route analytics heatmaps#1520
Conversation
Merge codex/route-heatmaps into the analytics overview filters branch, adding heatmap API routes, token signing, dashboard heatmap page, and dev-tool/event-tracker support.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@greptile-ai review |
📝 WalkthroughWalkthroughThis PR introduces a complete analytics heatmap feature enabling click-level session replay visualization. It spans ClickHouse schema extensions for click aggregation, origin-bound JWT tokens for secure access, backend query and token endpoints, improved client-side click event tracking with viewport scaling and element chain serialization, a dashboard UI for token management and element preview, and a dev-tool overlay with interactive filtering and marker visualization. ChangesHeatmap Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
7 issues found across 22 files
Tip: instead of fixing issues one by one fix them all with cubic
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
- showHeatmap: handle token-creation failure (reset dialog) + call via runAsynchronouslyWithAlert so errors surface (greptile P1, vercel, cubic) - DEVICE_WIDTH_BUCKETS: use Map to avoid prototype-pollution lookup (greptile P2) - dedup formatClickhouseDateTimeParam/parseBoundedDateTime across heatmap routes by exporting from the internal route (greptile P2) - derive heatmap JWT expiry from HEATMAP_TOKEN_TTL_MS (cubic) - only report 'Invalid route regex' for actual ClickHouse regexp errors, via shared isClickhouseRegexpError helper (cubic, both routes) - generic top-elements error text instead of raw err.message (cubic) - dev-tool: add primary-button :hover style to prevent hover flash (cubic) - selector builder: emit the data-test-id attr name actually matched (cubic)
|
@greptile-ai review |
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Button already wraps onClick in useAsyncCallback (loading/disabled tracking) and runAsynchronouslyWithAlert (error surfacing), so the explicit wrapper bypassed loading state. Pass the async handler directly per the established Button idiom (cubic).
|
@greptile-ai review |
|
@greptile-ai review |
…events The new default.clickmap_events view (added to the limited_user grant + row-policy set) now appears in SHOW TABLES, SHOW GRANTS, and system.tables. Update the four affected inline snapshots accordingly. The metrics_result and email snapshots that also fail are inherited from the base branch (codex/analytics-overview-filters) and are out of scope here.
|
@greptile-ai review |
…erlay integration - Consolidated heatmap query logic into shared utility functions for better maintainability. - Updated heatmap overlay token handling to use consistent storage keys across components. - Improved error handling for ClickHouse queries, ensuring proper status reporting. - Added comprehensive tests for new query utilities and heatmap overlay functionality. - Refactored related components to utilize the new utility functions, enhancing code clarity and reducing duplication.
There was a problem hiding this comment.
2 issues found across 15 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/stack-shared/src/utils/dom.tsx">
<violation number="1" location="packages/stack-shared/src/utils/dom.tsx:18">
P2: Fallback doesn't handle leading digits, which are invalid at the start of a CSS identifier. `cssEscapeIdent("3foo")` returns `"3foo"` in non-DOM environments, but `.3foo` is an invalid selector (parsed as a numeric literal). `CSS.escape` handles this by emitting a unicode escape (`\33 foo`).</violation>
</file>
<file name="apps/backend/src/lib/analytics-clickmap-query.ts">
<violation number="1" location="apps/backend/src/lib/analytics-clickmap-query.ts:379">
P2: Applying linear sampling scale-up (`scaleCount`) to `uniqExact` (unique users/replays) is statistically unsound. The sampling is event-level (hash includes timestamp), so unique entities are not sampled at the same rate as events — most users/replays survive even at low sampling rates. Only `count()` aggregates (clicks) should be scaled; unique counts should either be left unscaled or estimated with HyperLogLog/extrapolation methods designed for cardinality under sampling.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
| if (typeof CSS !== "undefined" && typeof CSS.escape === "function") { | ||
| return CSS.escape(value); | ||
| } | ||
| return value.replace(/[^a-zA-Z0-9_-]/g, (char) => `\\${char}`); |
There was a problem hiding this comment.
P2: Fallback doesn't handle leading digits, which are invalid at the start of a CSS identifier. cssEscapeIdent("3foo") returns "3foo" in non-DOM environments, but .3foo is an invalid selector (parsed as a numeric literal). CSS.escape handles this by emitting a unicode escape (\33 foo).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/stack-shared/src/utils/dom.tsx, line 18:
<comment>Fallback doesn't handle leading digits, which are invalid at the start of a CSS identifier. `cssEscapeIdent("3foo")` returns `"3foo"` in non-DOM environments, but `.3foo` is an invalid selector (parsed as a numeric literal). `CSS.escape` handles this by emitting a unicode escape (`\33 foo`).</comment>
<file context>
@@ -5,3 +5,15 @@ export function hasClickableParent(element: HTMLElement): boolean {
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
+ return CSS.escape(value);
+ }
+ return value.replace(/[^a-zA-Z0-9_-]/g, (char) => `\\${char}`);
+}
</file context>
| return value.replace(/[^a-zA-Z0-9_-]/g, (char) => `\\${char}`); | |
| let result = value.replace(/[^a-zA-Z0-9_-]/g, (char) => `\\${char}`); | |
| // CSS idents cannot start with a digit; use unicode escape | |
| if (/^\d/.test(value)) { | |
| result = `\\${value.charCodeAt(0).toString(16)} ` + result.slice(1); | |
| } else if (/^-\d/.test(value)) { | |
| result = `-\\${value.charCodeAt(1).toString(16)} ` + result.slice(2); | |
| } | |
| return result; |
|
|
||
| return { | ||
| samplingPct, | ||
| routes: routesRows.map((row) => ({ path: row.path, clicks: scaleCount(row.clicks), users: scaleCount(row.users), replays: scaleCount(row.replays) })), |
There was a problem hiding this comment.
P2: Applying linear sampling scale-up (scaleCount) to uniqExact (unique users/replays) is statistically unsound. The sampling is event-level (hash includes timestamp), so unique entities are not sampled at the same rate as events — most users/replays survive even at low sampling rates. Only count() aggregates (clicks) should be scaled; unique counts should either be left unscaled or estimated with HyperLogLog/extrapolation methods designed for cardinality under sampling.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/src/lib/analytics-clickmap-query.ts, line 379:
<comment>Applying linear sampling scale-up (`scaleCount`) to `uniqExact` (unique users/replays) is statistically unsound. The sampling is event-level (hash includes timestamp), so unique entities are not sampled at the same rate as events — most users/replays survive even at low sampling rates. Only `count()` aggregates (clicks) should be scaled; unique counts should either be left unscaled or estimated with HyperLogLog/extrapolation methods designed for cardinality under sampling.</comment>
<file context>
@@ -0,0 +1,404 @@
+
+ return {
+ samplingPct,
+ routes: routesRows.map((row) => ({ path: row.path, clicks: scaleCount(row.clicks), users: scaleCount(row.users), replays: scaleCount(row.replays) })),
+ selectors: selectorsRows.map((row) => ({ selector: row.selector, clicks: scaleCount(row.clicks) })),
+ elements: elementsRows.map((row) => ({
</file context>
| routes: routesRows.map((row) => ({ path: row.path, clicks: scaleCount(row.clicks), users: scaleCount(row.users), replays: scaleCount(row.replays) })), | |
| routes: routesRows.map((row) => ({ path: row.path, clicks: scaleCount(row.clicks), users: Number(row.users), replays: Number(row.replays) })), |
…tency - Updated all instances of "heatmap" to "clickmap" in the analytics module to reflect the new terminology. - Adjusted console messages, alerts, and UI elements to align with the clickmap branding. - Modified navigation items and dev-tool components to ensure cohesive user experience across the application.
- Added new API routes for clickmap data retrieval, including public and internal endpoints. - Implemented token generation and validation for clickmap access, ensuring secure data handling. - Refactored existing heatmap references to clickmap throughout the codebase for consistency. - Enhanced error handling and validation for clickmap queries, improving robustness. - Updated related components and tests to support the new clickmap features and ensure functionality.
There was a problem hiding this comment.
1 issue found across 29 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/template/src/dev-tool/dev-tool-styles.ts">
<violation number="1" location="packages/template/src/dev-tool/dev-tool-styles.ts:2731">
P2: CSS specificity bug: `.sdt-hm-mode-btn:hover` (specificity 0,3,0) overrides `.sdt-hm-mode-btn-active` (specificity 0,2,0) when hovering the active button. This makes the white text revert to `var(--sdt-text)` on the accent background, causing a visual glitch.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
| cursor: pointer; | ||
| } | ||
|
|
||
| .stack-devtool .sdt-hm-mode-btn:hover { |
There was a problem hiding this comment.
P2: CSS specificity bug: .sdt-hm-mode-btn:hover (specificity 0,3,0) overrides .sdt-hm-mode-btn-active (specificity 0,2,0) when hovering the active button. This makes the white text revert to var(--sdt-text) on the accent background, causing a visual glitch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/template/src/dev-tool/dev-tool-styles.ts, line 2731:
<comment>CSS specificity bug: `.sdt-hm-mode-btn:hover` (specificity 0,3,0) overrides `.sdt-hm-mode-btn-active` (specificity 0,2,0) when hovering the active button. This makes the white text revert to `var(--sdt-text)` on the accent background, causing a visual glitch.</comment>
<file context>
@@ -2640,39 +2656,209 @@ export const devToolCSS = `
+ cursor: pointer;
+ }
+
+ .stack-devtool .sdt-hm-mode-btn:hover {
+ color: var(--sdt-text);
+ }
</file context>
| .stack-devtool .sdt-hm-mode-btn:hover { | |
| .stack-devtool .sdt-hm-mode-btn:not(.sdt-hm-mode-btn-active):hover { |
Summary
Adds route analytics heatmaps, stacked on top of
codex/analytics-overview-filters(#1496)./analytics/heatmap, internal heatmap + heatmap-token endpoints)Notes
Base branch is
codex/analytics-overview-filtersso the diff shows only the heatmap changes. Will retarget todevonce the base PR lands.🤖 Generated with Claude Code
Summary by cubic
Adds analytics clickmaps (hour‑of‑week activity and session replay clickmaps) with a new Clickmaps dashboard, public/internal APIs, signed overlay tokens, shared ClickHouse query utilities, and tighter overlay/dev‑tool integration. Also renames “heatmap” to “clickmap” across the app, improves token verification and error handling, unifies overlay token handoff, and adds camelCase SDK options.
New Features
@stackframe/stack-shared; admin app exposes getAnalyticsClickmap/createAnalyticsClickmapToken; SDK method supports camelCase options and maps to snake_case.Migration
Written for commit 508f2ff. Summary will update on new commits.
Summary by CodeRabbit
New Features
Style