Repository navigation
feat: send navbar clicks made before analytics loads through the beacon #909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 4 commits
7945cef
6bf7784
bf52e07
78291c4
09c759c
b7dc1dc
609402e
a379f0d
e709356
d881b17
916d4d4
ed2c8c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,14 @@ | ||
| import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' | ||
| import { useLocation } from 'react-router-dom' | ||
| import { useAnalytics } from '@dcl/hooks' | ||
| import { isBotClient, useAnalytics } from '@dcl/hooks' | ||
| import { useFormatMessage } from '../../hooks/adapters/useFormatMessage' | ||
| import { useDeferredTrack } from '../../hooks/useDeferredTrack' | ||
| import { MIN_DISPLAY_BALANCE } from '../../hooks/useManaBalances' | ||
| import { useLocale } from '../../intl/LocaleContext' | ||
| import { isAnalyticsDisabledForSession } from '../../modules/analyticsSessionGate' | ||
| import { SectionViewedTrack, SegmentEvent } from '../../modules/segment' | ||
| import { ensureSegmentAnonymousId } from '../../modules/segmentAnonymousId' | ||
| import { postSegmentEvent } from '../../modules/segmentBeacon' | ||
| import { assetUrl } from '../../utils/assetUrl' | ||
| import { getAvatarBackgroundColor, getDisplayName } from '../../utils/avatarColor' | ||
| // Module-level cache for notification type→component map from ui2. | ||
|
|
@@ -29,7 +33,7 @@ import { | |
| ShoppingBagIcon, | ||
| WearableIcon | ||
| } from './icons' | ||
| import { isSectionActive, toNavbarAction, toNotificationLocale } from './LandingNavbar.helpers' | ||
| import { isSameTabNavigation, isSectionActive, toNavbarAction, toNotificationLocale } from './LandingNavbar.helpers' | ||
| import { DROPDOWN_SECTIONS, MENU_CONFIG, USER_MENU_ITEMS } from './navbarConfig' | ||
| import type { DropdownSection } from './navbarConfig' | ||
| import { | ||
|
|
@@ -255,16 +259,44 @@ const LandingNavbar = memo(function LandingNavbar({ | |
| const { isInitialized, track } = useAnalytics() | ||
| const [mobileMenuOpen, setMobileMenuOpen] = useState(false) | ||
|
|
||
| // Read through a ref (as useDownloadClick does) so a click sees Segment's current readiness even if it | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] This comment claims more than the ref does. A ref assigned during render only ever holds the value from the last render, so a click cannot see a readiness change that has not been rendered yet. What the ref does is keep
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in d881b17: both comments (here and in |
||
| // finished loading since the last render. | ||
| const isInitializedRef = useRef(isInitialized) | ||
| isInitializedRef.current = isInitialized | ||
|
|
||
| const deferredTrack = useDeferredTrack() | ||
|
|
||
| const trackNavbar = useCallback( | ||
| (action: string, link?: { section: string; href: string }) => { | ||
| if (!isInitialized) return | ||
| track(SegmentEvent.CLICK, { place: SectionViewedTrack.LANDING_NAVBAR, event: 'click', action, ...link }) | ||
| (action: string, link?: { section: string; href: string }, leavesPage = true) => { | ||
| const properties = { place: SectionViewedTrack.LANDING_NAVBAR, event: 'click', action, ...link } | ||
| if (isInitializedRef.current) { | ||
| track(SegmentEvent.CLICK, properties) | ||
| return | ||
| } | ||
| // Analytics loads once the page is idle and is ready only after Segment fetched its settings. A click | ||
| // that stays on the page (new tab, Jump In) waits in the queue with the SDK's full context. | ||
| if (!leavesPage) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] In a session that started on an exempt page, or for a bot, analytics never initializes, so this queue is never flushed. Nothing is sent (correct), but each click that keeps the page leaves a closure in if (isAnalyticsDisabledForSession() || isBotClient(navigator.userAgent)) return
if (!leavesPage) {
deferredTrack(SegmentEvent.CLICK, properties)
return
}
postDeferredClick(properties)This doesn't block the merge. |
||
| deferredTrack(SegmentEvent.CLICK, properties) | ||
| return | ||
| } | ||
| // One that replaces the page would be lost with it; the beacon survives the unload. It follows the same | ||
| // rules that keep the SDK off: sessions that started on an exempt page, and bots. | ||
| if (isAnalyticsDisabledForSession() || isBotClient(navigator.userAgent)) return | ||
| // Same deferral fields as useDownloadClick's beacon and useDeferredTrack, so every deferred click carries them. | ||
| const calledAt = Date.now() | ||
| postSegmentEvent( | ||
| SegmentEvent.CLICK, | ||
| // eslint-disable-next-line @typescript-eslint/naming-convention | ||
| { ...properties, track_called_at: calledAt, track_delivered_at: calledAt, track_deferred: true }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] This copies the deferred-beacon payload from
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applied in b7dc1dc. New |
||
| ensureSegmentAnonymousId() | ||
| ) | ||
| }, | ||
| [isInitialized, track] | ||
| [deferredTrack, track] | ||
| ) | ||
|
|
||
| const trackNavbarLink = useCallback( | ||
| (section: string, labelKey: string, href: string) => trackNavbar(toNavbarAction(labelKey), { section, href }), | ||
| (section: string, labelKey: string, href: string, leavesPage = true) => | ||
| trackNavbar(toNavbarAction(labelKey), { section, href }, leavesPage), | ||
| [trackNavbar] | ||
| ) | ||
|
|
||
|
|
@@ -273,10 +305,10 @@ const LandingNavbar = memo(function LandingNavbar({ | |
| const navbarLinkHandlers = useCallback( | ||
| (section: string, labelKey: string, href: string) => ({ | ||
| onClick: (event: React.MouseEvent) => { | ||
| if (event.button === 0) trackNavbarLink(section, labelKey, href) | ||
| if (event.button === 0) trackNavbarLink(section, labelKey, href, isSameTabNavigation(event)) | ||
| }, | ||
| onAuxClick: (event: React.MouseEvent) => { | ||
| if (event.button === 1) trackNavbarLink(section, labelKey, href) | ||
| if (event.button === 1) trackNavbarLink(section, labelKey, href, false) | ||
| } | ||
| }), | ||
| [trackNavbarLink] | ||
|
|
@@ -556,7 +588,7 @@ const LandingNavbar = memo(function LandingNavbar({ | |
| {scrolled && onClickJumpIn ? ( | ||
| <NavJumpInButton | ||
| onClick={e => { | ||
| trackNavbar('jump_in') | ||
| trackNavbar('jump_in', undefined, false) | ||
| onClickJumpIn(e) | ||
| }} | ||
| > | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Alt-click (Option-click on macOS) on a link downloads it in Chrome and keeps the page open, but this helper counts it as leaving the page. Before analytics is ready, that click goes out by beacon instead of waiting in the queue for the SDK. It is still counted once, just without the SDK's full context. Consider adding
altKeyto the parameter type and the check (&& !event.altKey), plus anit.eachcase for it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applied in 09c759c:
isSameTabNavigationalso treatsaltKeyas keeping the page, and theit.eachcovers it.