Skip to content

Allow users to click on a date on the chart and pull sessions for that date#511

Merged
Blaumaus merged 16 commits into
mainfrom
feature/click-to-view-sessions
Apr 10, 2026
Merged

Allow users to click on a date on the chart and pull sessions for that date#511
Blaumaus merged 16 commits into
mainfrom
feature/click-to-view-sessions

Conversation

@Blaumaus
Copy link
Copy Markdown
Member

@Blaumaus Blaumaus commented Apr 9, 2026

Changes

If applicable, please describe what changes were made in this pull request.

Community Edition support

  • Your feature is implemented for the Swetrix Community Edition
  • This PR only updates the Cloud (Enterprise) Edition code (e.g. Paddle webhooks, blog, payouts, etc.)

Database migrations

  • Clickhouse / MySQL migrations added for this PR
  • No table schemas changed in this PR

Documentation

  • You have updated the documentation according to your PR
  • This PR did not change any publicly documented endpoints

Summary by CodeRabbit

  • New Features

    • Clickable traffic chart points and an "Explore Sessions" action open a sessions side-drawer with paginated/infinite-scroll results; added SessionsDrawer and exported Session item.
    • New reusable Drawer UI component.
  • UI Improvements

    • Enhanced chart interactivity (hover/focus, click targets, extra tooltip action) and cursor styles.
    • Refined session/profile/errors/feature-flags/experiments list layouts and live session pill.
  • Localization

    • Added session-related strings in EN, DE, FR, PL, and UK.
  • Bug Fixes

    • Improved date validation and backend funnel error handling for greater robustness.

@Blaumaus Blaumaus self-assigned this Apr 9, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds clickable traffic-chart data points that open a paginated Sessions drawer; introduces a Drawer UI, wires chart/context-menu actions to open sessions, adjusts chart tooltip/point behavior and DOM event wiring, adds session i18n, and refactors several list UIs and icon helpers.

Changes

Cohort / File(s) Summary
Chart settings & event wiring
web/app/pages/Project/View/ViewProject.helpers.tsx, web/app/pages/Project/tabs/Traffic/TrafficChart.tsx, web/app/styles/ProjectViewStyle.css
Added optional onDataPointClick / dataPointClickLabel to chart settings and TrafficChart props; conditional billboard onclick, tooltip row injection, point radius/focus/sensitivity changes; guarded onrendered mousemove/mouseleave/click listeners to map rect → nearest circle → { x: Date, index }. CSS: pointer/hover/scale for #traffic-chart.
Sessions exploration UI
web/app/pages/Project/tabs/Traffic/SessionsDrawer.tsx, web/app/pages/Project/tabs/Sessions/Sessions.tsx, web/public/locales/*.{json}
Added exported SessionsDrawer (paginated fetch, AbortController, IntersectionObserver sentinel, load-more sentinel) and exported Session component; added noSessionsFound / exploreSessions translations across locales.
Traffic view & context menu integration
web/app/pages/Project/tabs/Traffic/TrafficView.tsx, web/app/pages/Project/View/components/ChartContextMenu.tsx
TrafficView: dayjs timezone setup, handleDataPointClick computes from/to/label and opens SessionsDrawer; ChartContextMenu: added optional onExploreSessions action and unified rendering for view-only vs manage menus.
New Drawer primitive
web/app/ui/Drawer.tsx, web/package.json
Introduced Drawer wrapper and semantic subcomponents (Content, Overlay, Header, Title, Description, Close) built on vaul; added vaul dependency.
List/UI refinements & icons
web/app/pages/Project/tabs/*, web/app/pages/Project/tabs/SharedIcons.tsx
Refactored session/profile/error/feature-flag/experiment row layouts to use Text/Badge, updated spacing/responsive structure, added BrowserIcon/OSIcon helpers and theme-aware OS icon usage.
Backend/date validation & controllers
backend/apps/*/src/analytics/*.ts
isValidDate updated to accept ISO-like datetimes via dayjs(date).isValid(); getFunnel controllers wrap getFunnelStepDetails in try/catch and log errors to avoid Promise.all rejections.
Misc locales, constants & docs
web/app/lib/constants/index.ts, web/public/locales/*.{json}, docs/content/docs/.../*.mdx
Bumped I18N_CACHE_BREAKER (35→36); added session-related translations (en,de,fr,pl,uk); added docs explaining "Accessing/Viewing Sessions from Charts".

Sequence Diagram

sequenceDiagram
    actor User
    participant Chart as Traffic Chart
    participant TrafficView as TrafficView
    participant SessionsDrawer as SessionsDrawer
    participant API as /api/analytics

    User->>Chart: click data point
    Chart->>TrafficView: onDataPointClick({ x, index })
    TrafficView->>TrafficView: compute from/to & label (timezone, bucket)
    TrafficView->>SessionsDrawer: open(from, to, label, filters)

    Note over SessionsDrawer: isOpen = true
    SessionsDrawer->>API: POST action=getSessions (from,to,filters,skip=0,take=30)
    API-->>SessionsDrawer: sessions page
    SessionsDrawer->>SessionsDrawer: render list

    User->>SessionsDrawer: scroll -> sentinel visible
    SessionsDrawer->>API: POST getSessions (skip += 30)
    API-->>SessionsDrawer: next page
    SessionsDrawer->>SessionsDrawer: append results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A tiny dot, a gentle click,
The drawer yawns and sessions tick.
Pages fetch as rabbits cheer,
Hop through timeframes, far and near.
Click, explore, then close—hip hip hooray!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description follows the template structure with checkboxes for Community Edition support, database migrations, and documentation. However, documentation updates are not indicated despite docs being modified. Update the description to check the documentation checkbox and explain that analytics dashboard docs were updated with the new chart click-to-sessions feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main feature: allowing users to click on a chart date to view sessions for that date.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/click-to-view-sessions

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/app/pages/Project/tabs/Traffic/SessionsDrawer.tsx`:
- Around line 77-100: The loadSessions async callback can apply results from
stale requests after the date/range changes; modify loadSessions (and the
similar logic around lines 102-113) to cancel or invalidate prior requests
before applying state by using an AbortController passed into fetchSessionsPage
or a monotonically increasing requestId captured when the call starts and
checked before calling setSessions/setHasMore; ensure fetchSessionsPage supports
AbortSignal or that you ignore results whose requestId doesn’t match the latest,
and clean up controllers on unmount or when dependencies (projectId, from, to,
timezone, filters) change.
- Around line 22-42: fetchSessionsPage can throw during fetch/response.json and
loadMore exits early without resetting loadingRef/loadingMore, which blocks
further pagination; update loadMore (the function controlling pagination and
using loadingRef and setLoadingMore) to wrap the await call to fetchSessionsPage
in try/catch/finally (or ensure a finally-equivalent cleanup) so that
loadingRef.current is set back to false and setLoadingMore(false) is called
regardless of success or error, and apply the same error-safe cleanup pattern to
the other pagination call site noted (the block around lines 115-126) to always
reset loading flags even when fetch or JSON parsing fails.
- Around line 65-66: The default parameter filters = [] creates a new array each
render which makes loadSessions unstable and re-triggers the initial-load
useEffect when isOpen is true; remove the default empty array from the
SessionsDrawerProps parameter and instead create a stableFilters value inside
the component (e.g., const stableFilters = useMemo(() => filters ?? [],
[filters]) or memoize any transformed filters) and use stableFilters wherever
loadSessions and the isOpen effect use filters (references: SessionsDrawerProps,
filters param, loadSessions function, isOpen-driven initial-load effect).

In `@web/app/pages/Project/tabs/Traffic/TrafficView.tsx`:
- Around line 337-379: The date range for the sessions drawer is being computed
from a plain dayjs(d.x) (in handleDataPointClick) which anchors startOf/endOf to
the viewer's local timezone; update the logic to first convert the point to the
project/dashboard timezone (e.g., using dayjs.tz or equivalent with the shared
timezone variable) then call startOf(...)/endOf(...).toISOString() and format
the label using the same tz-aware dayjs instance before calling
setSessionsDrawer({ from, to, label }); apply the same tz-aware fix to the other
occurrence around the session drawer code (the second block referenced).
- Around line 344-375: The switch on timeBucket currently falls through for
'week' and uses the 'day' range; add an explicit case 'week' in the switch (near
the existing cases for 'minute','hour','day','month','year') that sets from =
date.startOf('week').toISOString(), to = date.endOf('week').toISOString(), and
computes an appropriate label (for example a range like
date.startOf('week').format('MMM D') + ' - ' + date.endOf('week').format('MMM D,
YYYY') or "Week of " + date.startOf('week').format('MMM D, YYYY')) so the drawer
range for weekly chart points matches the chart bucket boundaries.

In `@web/app/pages/Project/View/ViewProject.helpers.tsx`:
- Around line 910-912: Replace the hardcoded tooltip hint "Click to explore
sessions" with a localized string from the app's i18n/locale mechanism (the same
key used for the menu label) inside the tooltip HTML template construction where
onDataPointClick is used; update both occurrences (the current template near the
join(...) block and the other instance noted around line 1007) to call the
localization function (e.g., t('...') or getString('...')) and inject its value
into the template instead of the literal text so non-English dashboards render
the translated hint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e9008202-0c27-47ea-8c65-6b571dbc5ac6

📥 Commits

Reviewing files that changed from the base of the PR and between 0712994 and 8a9e305.

📒 Files selected for processing (8)
  • web/app/pages/Project/View/ViewProject.helpers.tsx
  • web/app/pages/Project/View/components/ChartContextMenu.tsx
  • web/app/pages/Project/tabs/Sessions/Sessions.tsx
  • web/app/pages/Project/tabs/Traffic/SessionsDrawer.tsx
  • web/app/pages/Project/tabs/Traffic/TrafficChart.tsx
  • web/app/pages/Project/tabs/Traffic/TrafficView.tsx
  • web/app/styles/ProjectViewStyle.css
  • web/public/locales/en.json

Comment thread web/app/pages/Project/tabs/Traffic/SessionsDrawer.tsx
Comment thread web/app/pages/Project/tabs/Traffic/SessionsDrawer.tsx Outdated
Comment thread web/app/pages/Project/tabs/Traffic/SessionsDrawer.tsx
Comment thread web/app/pages/Project/tabs/Traffic/TrafficView.tsx Outdated
Comment thread web/app/pages/Project/tabs/Traffic/TrafficView.tsx
Comment thread web/app/pages/Project/View/ViewProject.helpers.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
web/app/pages/Project/tabs/Sessions/Sessions.tsx (2)

216-223: Redundant session in useMemo dependencies.

The session object is included in the dependency array, but the memo only uses session.isLive. Since sessionStartTime (which depends on session.sessionStart) is already a dependency, including session is redundant and could cause unnecessary recalculations when other session properties change.

♻️ Proposed fix
   }, [
-    session,
+    session.isLive,
     language,
     timeFormat,
     sessionDurationString,
     sessionStartTime,
     t,
   ])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/pages/Project/tabs/Sessions/Sessions.tsx` around lines 216 - 223, The
useMemo dependency array currently includes the entire session object, which is
redundant; update the dependency list for the useMemo (the hook computing
session duration/display) to remove session and instead depend on session.isLive
(and keep sessionStartTime, language, timeFormat, sessionDurationString, and t)
so the memo only re-runs when the live flag or the actual inputs change.

86-139: Duplicate helper components — see comment in Profiles.tsx.

BrowserIcon and OSIcon are duplicated here. As noted in the Profiles.tsx review, consider extracting these to shared components.

Additionally, the fallback styling differs slightly between files:

  • Here (line 96): text-gray-400 (no dark variant)
  • In Profiles.tsx (line 76): text-gray-400 dark:text-gray-500
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/pages/Project/tabs/Sessions/Sessions.tsx` around lines 86 - 139,
Extract the duplicated BrowserIcon and OSIcon into a shared component (e.g.,
SharedIcons.tsx) and import them into Sessions.tsx and the other file instead of
duplicating logic; keep the same props (BrowserIcon: browser, className; OSIcon:
os, theme, className) and preserve behavior for null/missing mappings. While
extracting, normalize the fallback className to include the dark variant (use
'size-3.5 text-gray-400 dark:text-gray-500' as the default) so both usages
render consistently, and ensure the exported components still return the same
<img> or <GlobeIcon> branches (refer to BrowserIcon and OSIcon for exact
branches to preserve).
web/app/pages/Project/tabs/Profiles/Profiles.tsx (1)

66-119: Consider extracting BrowserIcon and OSIcon into shared utility components.

These helper components are duplicated nearly identically in Sessions.tsx (lines 86-139). Extracting them to a shared location (e.g., ~/ui/BrowserIcon.tsx and ~/ui/OSIcon.tsx) would reduce duplication and ensure consistent behavior across the codebase.

Note a minor inconsistency: BrowserIcon fallback in this file uses dark:text-gray-500 (line 76) while the same component in Sessions.tsx uses only text-gray-400 without a dark variant. This could cause visual inconsistency.

♻️ Suggested shared component location

Create ~/ui/icons/BrowserIcon.tsx and ~/ui/icons/OSIcon.tsx with a consistent API, then import from both files:

// ~/ui/icons/BrowserIcon.tsx
import { GlobeIcon } from '@phosphor-icons/react'
import { BROWSER_LOGO_MAP } from '~/lib/constants'

interface BrowserIconProps {
  browser: string | null
  className?: string
}

export const BrowserIcon = ({ browser, className }: BrowserIconProps) => {
  const fallbackClass = className || 'size-3.5 text-gray-400 dark:text-gray-500'
  
  if (!browser) {
    return <GlobeIcon className={fallbackClass} weight='duotone' />
  }
  
  const logoUrl = BROWSER_LOGO_MAP[browser as keyof typeof BROWSER_LOGO_MAP]
  if (!logoUrl) {
    return <GlobeIcon className={fallbackClass} weight='duotone' />
  }
  
  return <img src={logoUrl} className={className || 'size-3.5'} alt={browser} />
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/pages/Project/tabs/Profiles/Profiles.tsx` around lines 66 - 119,
BrowserIcon and OSIcon are duplicated here and in Sessions.tsx and have a minor
fallback-class inconsistency (dark:text-gray-500 present only in this file);
extract both components into shared files (e.g., ~/ui/icons/BrowserIcon.tsx and
~/ui/icons/OSIcon.tsx), move the logic from the local BrowserIcon and OSIcon
into those shared components, update both Profiles.tsx and Sessions.tsx to
import the shared components, and standardize the fallback class (e.g.,
'size-3.5 text-gray-400 dark:text-gray-500') so both usages render consistently.
web/app/pages/Project/View/ViewProject.helpers.tsx (1)

1092-1157: Consider logging errors in the catch block for debugging.

The DOM listener attachment logic is well-guarded against duplicate registration via __clickAttached. However, the empty catch block at lines 1155-1157 silently swallows all errors, which could make debugging difficult if something unexpected fails.

🔧 Suggested improvement
-            } catch {
-              // ignore
-            }
+            } catch (err) {
+              // Log for debugging but don't break chart rendering
+              if (process.env.NODE_ENV !== 'production') {
+                console.warn('[Chart] onrendered hook error:', err)
+              }
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/pages/Project/View/ViewProject.helpers.tsx` around lines 1092 - 1157,
The empty catch block swallows errors when attaching DOM listeners for
onDataPointClick; update the catch to log the caught error (including context)
using the existing logging mechanism or console.error so failures in the chart
listener setup are visible — locate the try/catch around the block that reads
chartInstance.$.svg, eventRectsGroup and __clickAttached, and replace the silent
catch with code that logs a descriptive message and the error object (e.g.,
referencing chartInstance or eventRectsGroup in the message) while preserving
the current suppression behavior otherwise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/app/pages/Project/tabs/FeatureFlags/FeatureFlagsView.tsx`:
- Around line 151-160: The Badge currently renders hardcoded 'true'/'false'
strings for profile.lastResult inside FeatureFlagsView's JSX (the Badge element
rendering the span with CheckIcon/XIcon); replace those literals with localized
strings from the i18n translator (e.g., use the component's translation function
like t('featureFlags.true') / t('featureFlags.false') or equivalent keys) so the
badge label reads t(...) instead of 'true'/'false', keeping the icons and
conditional logic based on profile.lastResult unchanged.

---

Nitpick comments:
In `@web/app/pages/Project/tabs/Profiles/Profiles.tsx`:
- Around line 66-119: BrowserIcon and OSIcon are duplicated here and in
Sessions.tsx and have a minor fallback-class inconsistency (dark:text-gray-500
present only in this file); extract both components into shared files (e.g.,
~/ui/icons/BrowserIcon.tsx and ~/ui/icons/OSIcon.tsx), move the logic from the
local BrowserIcon and OSIcon into those shared components, update both
Profiles.tsx and Sessions.tsx to import the shared components, and standardize
the fallback class (e.g., 'size-3.5 text-gray-400 dark:text-gray-500') so both
usages render consistently.

In `@web/app/pages/Project/tabs/Sessions/Sessions.tsx`:
- Around line 216-223: The useMemo dependency array currently includes the
entire session object, which is redundant; update the dependency list for the
useMemo (the hook computing session duration/display) to remove session and
instead depend on session.isLive (and keep sessionStartTime, language,
timeFormat, sessionDurationString, and t) so the memo only re-runs when the live
flag or the actual inputs change.
- Around line 86-139: Extract the duplicated BrowserIcon and OSIcon into a
shared component (e.g., SharedIcons.tsx) and import them into Sessions.tsx and
the other file instead of duplicating logic; keep the same props (BrowserIcon:
browser, className; OSIcon: os, theme, className) and preserve behavior for
null/missing mappings. While extracting, normalize the fallback className to
include the dark variant (use 'size-3.5 text-gray-400 dark:text-gray-500' as the
default) so both usages render consistently, and ensure the exported components
still return the same <img> or <GlobeIcon> branches (refer to BrowserIcon and
OSIcon for exact branches to preserve).

In `@web/app/pages/Project/View/ViewProject.helpers.tsx`:
- Around line 1092-1157: The empty catch block swallows errors when attaching
DOM listeners for onDataPointClick; update the catch to log the caught error
(including context) using the existing logging mechanism or console.error so
failures in the chart listener setup are visible — locate the try/catch around
the block that reads chartInstance.$.svg, eventRectsGroup and __clickAttached,
and replace the silent catch with code that logs a descriptive message and the
error object (e.g., referencing chartInstance or eventRectsGroup in the message)
while preserving the current suppression behavior otherwise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 30c3941c-1ff1-4b9e-b9c1-63c98d64c6dd

📥 Commits

Reviewing files that changed from the base of the PR and between 8a9e305 and fbbf89f.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • web/app/pages/Project/View/ViewProject.helpers.tsx
  • web/app/pages/Project/tabs/Errors/ErrorsView.tsx
  • web/app/pages/Project/tabs/Experiments/ExperimentsView.tsx
  • web/app/pages/Project/tabs/FeatureFlags/FeatureFlagsView.tsx
  • web/app/pages/Project/tabs/Profiles/Profiles.tsx
  • web/app/pages/Project/tabs/Sessions/Sessions.tsx
  • web/app/pages/Project/tabs/Traffic/SessionsDrawer.tsx
  • web/app/pages/Project/tabs/Traffic/TrafficChart.tsx
  • web/app/pages/Project/tabs/Traffic/TrafficView.tsx
  • web/app/styles/ProjectViewStyle.css
  • web/app/ui/Drawer.tsx
  • web/package.json
✅ Files skipped from review due to trivial changes (2)
  • web/app/styles/ProjectViewStyle.css
  • web/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/app/pages/Project/tabs/Traffic/SessionsDrawer.tsx
  • web/app/pages/Project/tabs/Traffic/TrafficChart.tsx

Comment thread web/app/pages/Project/tabs/FeatureFlags/FeatureFlagsView.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/apps/community/src/analytics/analytics.service.ts`:
- Around line 179-181: The current check in analytics.service (the regex +
dayjs(date).isValid() call) uses dayjs non-strict parsing and can accept invalid
dates; change it to use dayjs with the customParseFormat plugin in strict mode:
import and extend dayjs with customParseFormat, then replace
dayjs(date).isValid() with dayjs(date, 'YYYY-MM-DDTHH:mm:ss', true).isValid()
(or the exact format string you expect for your ISO-like input) so parsing is
strict; keep the existing regex guard but ensure the strict-format check uses
the same format pattern.

In `@web/app/pages/Project/View/ViewProject.helpers.tsx`:
- Around line 1095-1097: The one-time guard using
eventRectsGroup.__clickAttached causes the attached click handlers to close over
stale columns and onDataPointClick; remove or change the one-time-only
attachment so handlers are rebound when chart props change (or instead attach a
stable wrapper that reads the latest columns and onDataPointClick from a mutable
ref stored on eventRectsGroup). Specifically, update the logic around
eventRectsGroup.__clickAttached (and the similar block covering the handlers at
1137–1172) to either (a) always reattach listeners each render, ensuring you
remove previous listeners first, or (b) attach a single stable listener that
references up-to-date values by reading
eventRectsGroup.currentColumns/eventRectsGroup.currentOnDataPointClick (or a
React ref) so clicks resolve to the latest columns and onDataPointClick
callback.
- Around line 911-913: The tooltip HTML builder currently appends a hint row
whenever onDataPointClick is set, which can render the literal "undefined" if
dataPointClickLabel is missing; update the conditional that builds the `<li>`
(where it currently uses onDataPointClick ? `<li...>${dataPointClickLabel}</li>`
: '') to require both onDataPointClick and a non-empty dataPointClickLabel
(e.g., onDataPointClick && dataPointClickLabel) so the hint is only included
when the label exists; apply the same guard to the other occurrence that uses
dataPointClickLabel.

In `@web/app/routes/api.analytics.ts`:
- Around line 82-88: The current validation is a no-op: you construct const date
= new Date(dateStr) but always return dateStr; change the branch so that when
Number.isNaN(date.getTime()) is false you return the normalized ISO timestamp
(use date.toISOString() to preserve a canonical full ISO 8601 timestamp) and
when the date is invalid do not forward the original value—instead throw a
validation error (or return a 400/bad-request response) so malformed dateStr
values are rejected; update the code that uses dateStr accordingly to handle the
thrown error/400.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8e106a2-3eae-431f-8301-2ce04ce59979

📥 Commits

Reviewing files that changed from the base of the PR and between 4826e6a and 3c65051.

📒 Files selected for processing (8)
  • backend/apps/cloud/src/analytics/analytics.controller.ts
  • backend/apps/cloud/src/analytics/analytics.service.ts
  • backend/apps/community/src/analytics/analytics.controller.ts
  • backend/apps/community/src/analytics/analytics.service.ts
  • web/app/pages/Project/View/ViewProject.helpers.tsx
  • web/app/pages/Project/tabs/Traffic/SessionsDrawer.tsx
  • web/app/pages/Project/tabs/Traffic/TrafficView.tsx
  • web/app/routes/api.analytics.ts
✅ Files skipped from review due to trivial changes (1)
  • web/app/pages/Project/tabs/Traffic/SessionsDrawer.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/app/pages/Project/tabs/Traffic/TrafficView.tsx

Comment thread backend/apps/community/src/analytics/analytics.service.ts
Comment on lines +911 to +913
).join(
'',
)}${onDataPointClick ? `<li class='pt-1 mt-1 border-t border-gray-200 dark:border-slate-700/80 text-[10px] text-gray-400 dark:text-slate-500'>${dataPointClickLabel}</li>` : ''}</ul>`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard tooltip hint text to avoid rendering undefined.

On Line 913 and Line 1008, the hint row is shown whenever onDataPointClick is set, even if dataPointClickLabel is missing. That can surface literal undefined in UI.

💡 Suggested fix
-          )}${onDataPointClick ? `<li class='pt-1 mt-1 border-t border-gray-200 dark:border-slate-700/80 text-[10px] text-gray-400 dark:text-slate-500'>${dataPointClickLabel}</li>` : ''}</ul>`
+          )}${onDataPointClick && dataPointClickLabel ? `<li class='pt-1 mt-1 border-t border-gray-200 dark:border-slate-700/80 text-[10px] text-gray-400 dark:text-slate-500'>${dataPointClickLabel}</li>` : ''}</ul>`
-          ${onDataPointClick ? `<li class='pt-1 mt-1 border-t border-gray-200 dark:border-slate-700/80 text-[10px] text-gray-400 dark:text-slate-500'>${dataPointClickLabel}</li>` : ''}
+          ${onDataPointClick && dataPointClickLabel ? `<li class='pt-1 mt-1 border-t border-gray-200 dark:border-slate-700/80 text-[10px] text-gray-400 dark:text-slate-500'>${dataPointClickLabel}</li>` : ''}

Also applies to: 1008-1008

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/pages/Project/View/ViewProject.helpers.tsx` around lines 911 - 913,
The tooltip HTML builder currently appends a hint row whenever onDataPointClick
is set, which can render the literal "undefined" if dataPointClickLabel is
missing; update the conditional that builds the `<li>` (where it currently uses
onDataPointClick ? `<li...>${dataPointClickLabel}</li>` : '') to require both
onDataPointClick and a non-empty dataPointClickLabel (e.g., onDataPointClick &&
dataPointClickLabel) so the hint is only included when the label exists; apply
the same guard to the other occurrence that uses dataPointClickLabel.

Comment on lines +1095 to +1097
if (!eventRectsGroup || eventRectsGroup.__clickAttached) return
eventRectsGroup.__clickAttached = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

One-time listener attachment causes stale click/date mappings after rerenders.

Line 1095–1097 attaches handlers only once via __clickAttached. Those handlers close over the initial columns and onDataPointClick. After chart data/time updates, clicks can resolve to stale dates (or old callback behavior).

🔧 Suggested fix (rebind handlers with latest closures)
-                const eventRectsGroup = svg.querySelector('.bb-event-rects')
-                if (!eventRectsGroup || eventRectsGroup.__clickAttached) return
-                eventRectsGroup.__clickAttached = true
+                type EventRectsGroup = SVGGElement & {
+                  __handlers?: {
+                    mousemove: (e: MouseEvent) => void
+                    mouseleave: () => void
+                    click: (e: MouseEvent) => void
+                  }
+                }
+                const eventRectsGroup = svg.querySelector(
+                  '.bb-event-rects',
+                ) as EventRectsGroup | null
+                if (!eventRectsGroup) return
+
+                if (eventRectsGroup.__handlers) {
+                  eventRectsGroup.removeEventListener(
+                    'mousemove',
+                    eventRectsGroup.__handlers.mousemove,
+                  )
+                  eventRectsGroup.removeEventListener(
+                    'mouseleave',
+                    eventRectsGroup.__handlers.mouseleave,
+                  )
+                  eventRectsGroup.removeEventListener(
+                    'click',
+                    eventRectsGroup.__handlers.click,
+                  )
+                }

-                eventRectsGroup.addEventListener(
-                  'mousemove',
-                  (e: MouseEvent) => {
+                const mousemove = (e: MouseEvent) => {
                     // existing logic
-                  },
-                )
+                }
+                eventRectsGroup.addEventListener('mousemove', mousemove)

-                eventRectsGroup.addEventListener('mouseleave', () => {
+                const mouseleave = () => {
                   // existing logic
-                })
+                }
+                eventRectsGroup.addEventListener('mouseleave', mouseleave)

-                eventRectsGroup.addEventListener('click', (e: MouseEvent) => {
+                const click = (e: MouseEvent) => {
                   // existing logic (uses latest `columns` / `onDataPointClick`)
-                })
+                }
+                eventRectsGroup.addEventListener('click', click)
+
+                eventRectsGroup.__handlers = { mousemove, mouseleave, click }

Also applies to: 1137-1172

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/pages/Project/View/ViewProject.helpers.tsx` around lines 1095 - 1097,
The one-time guard using eventRectsGroup.__clickAttached causes the attached
click handlers to close over stale columns and onDataPointClick; remove or
change the one-time-only attachment so handlers are rebound when chart props
change (or instead attach a stable wrapper that reads the latest columns and
onDataPointClick from a mutable ref stored on eventRectsGroup). Specifically,
update the logic around eventRectsGroup.__clickAttached (and the similar block
covering the handlers at 1137–1172) to either (a) always reattach listeners each
render, ensuring you remove previous listeners first, or (b) attach a single
stable listener that references up-to-date values by reading
eventRectsGroup.currentColumns/eventRectsGroup.currentOnDataPointClick (or a
React ref) so clicks resolve to the latest columns and onDataPointClick
callback.

Comment thread web/app/routes/api.analytics.ts Outdated
@Blaumaus Blaumaus merged commit 0196d6d into main Apr 10, 2026
10 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 12, 2026
6 tasks
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.

1 participant