Skip to content

Commit da81b2f

Browse files
authored
feat(studio): add click tracking for top bar buttons (supabase#45414)
## Summary Adds PostHog click/open tracking for every interactive element in the Studio top bar. Previously only 5 of ~16 surfaces were tracked. ### New events (16) | Event | Surface | |---|---| | `home_logo_clicked` | Supabase logo | | `header_back_to_dashboard_clicked` | Mobile back chevron | | `header_exceeding_usage_badge_clicked` | "Exceeding usage limits" badge | | `organization_dropdown_opened` | Org dropdown trigger | | `project_dropdown_opened` | Project dropdown trigger | | `branch_dropdown_opened` | Branch dropdown trigger | | `merge_request_button_clicked` | MR trigger (separate from existing success event) | | `connect_button_clicked` | Connect CTA | | `feedback_dropdown_opened` | Feedback dropdown trigger | | `advisor_button_clicked` | Advisor toggle | | `inline_editor_button_clicked` | SQL editor toggle | | `assistant_button_clicked` | AI Assistant toggle | | `user_dropdown_opened` | Account dropdown | | `local_dropdown_opened` | Local-dev settings dropdown | | `local_version_popover_opened` | CLI version popover | ### Notes - Uses `useTrack` (per `telemetry-standards`), all event names use approved `_clicked` / `_opened` verbs. - Dropdown `onOpenChange` handlers guard against Radix's double-fire by only tracking when `open === true`. - `merge_request_button_clicked` fires on the trigger click; the existing `branch_create_merge_request_button_clicked` continues to fire on successful MR creation. - Pre-existing tracked surfaces (`command_menu_opened`, `help_button_clicked`, `header_upgrade_cta_clicked`, `send_feedback_button_clicked`) are unchanged. ## Test plan - [x] Spot-check each event fires once per interaction in PostHog Live Events - [x] Verify no double-fire on dropdown close <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Added telemetry tracking for many header/navigation interactions (logo, back-to-dashboard, usage badge, connect/merge/advisor/assistant/inline-editor buttons, and multiple dropdowns/popovers). * **Tests** * Updated tests to stub telemetry calls so UI tests remain stable and deterministic. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 2c892ac commit da81b2f

16 files changed

Lines changed: 265 additions & 8 deletions

apps/studio/components/interfaces/ConnectButton/ConnectButton.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Button, cn } from 'ui'
66
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
77
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
88
import { PROJECT_STATUS } from '@/lib/constants'
9+
import { useTrack } from '@/lib/telemetry/track'
910
import { useAppStateSnapshot } from '@/state/app-state'
1011

1112
interface ConnectButtonProps {
@@ -17,6 +18,7 @@ export const ConnectButton = ({ buttonType = 'default', className }: ConnectButt
1718
const { data: selectedProject } = useSelectedProjectQuery()
1819
const { setConnectSheetSource } = useAppStateSnapshot()
1920
const isActiveHealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY
21+
const track = useTrack()
2022

2123
const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false))
2224

@@ -27,6 +29,7 @@ export const ConnectButton = ({ buttonType = 'default', className }: ConnectButt
2729
className={cn('rounded-full', className)}
2830
icon={<Plug className="rotate-90" />}
2931
onClick={() => {
32+
track('header_connect_button_clicked')
3033
setConnectSheetSource('header_button')
3134
setShowConnect(true)
3235
}}

apps/studio/components/interfaces/LocalDropdown.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ vi.mock('./App/FeaturePreview/FeaturePreviewContext', () => ({
6060
}),
6161
}))
6262

63+
vi.mock('@/lib/telemetry/track', () => ({ useTrack: () => vi.fn() }))
64+
6365
vi.mock('ui', async () => {
6466
const React = await import('react')
6567

apps/studio/components/interfaces/LocalDropdown.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { ButtonTooltip } from '../ui/ButtonTooltip'
2121
import { useFeaturePreviewModal } from './App/FeaturePreview/FeaturePreviewContext'
2222
import { ProfileImage } from '@/components/ui/ProfileImage'
23+
import { useTrack } from '@/lib/telemetry/track'
2324
import { useAppStateSnapshot } from '@/state/app-state'
2425

2526
export const LocalDropdown = ({
@@ -33,9 +34,14 @@ export const LocalDropdown = ({
3334
const { theme, setTheme } = useTheme()
3435
const appStateSnapshot = useAppStateSnapshot()
3536
const { toggleFeaturePreviewModal } = useFeaturePreviewModal()
37+
const track = useTrack()
3638

3739
return (
38-
<DropdownMenu>
40+
<DropdownMenu
41+
onOpenChange={(open) => {
42+
if (open) track('header_local_dropdown_opened')
43+
}}
44+
>
3945
<DropdownMenuTrigger className={cn('border shrink-0 px-3', triggerClassName)} asChild>
4046
<ButtonTooltip
4147
type="default"

apps/studio/components/interfaces/UserDropdown.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { ProfileImage } from '@/components/ui/ProfileImage'
2323
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
2424
import { IS_PLATFORM } from '@/lib/constants'
2525
import { useProfileNameAndPicture } from '@/lib/profile'
26+
import { useTrack } from '@/lib/telemetry/track'
2627
import { useAppStateSnapshot } from '@/state/app-state'
2728

2829
export function UserDropdown({
@@ -39,9 +40,14 @@ export function UserDropdown({
3940
const { username, avatarUrl, primaryEmail, isLoading } = useProfileNameAndPicture()
4041

4142
const { toggleFeaturePreviewModal } = useFeaturePreviewModal()
43+
const track = useTrack()
4244

4345
return (
44-
<DropdownMenu>
46+
<DropdownMenu
47+
onOpenChange={(open) => {
48+
if (open) track('header_user_dropdown_opened')
49+
}}
50+
>
4551
<DropdownMenuTrigger asChild className={cn('border shrink-0 px-3', triggerClassName)}>
4652
<ButtonTooltip
4753
type="default"

apps/studio/components/layouts/AppLayout/AdvisorButton.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import { useAdvisorSignals } from '@/components/ui/AdvisorPanel/useAdvisorSignal
77
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
88
import { useProjectLintsQuery } from '@/data/lint/lint-query'
99
import { useNotificationsV2Query } from '@/data/notifications/notifications-v2-query'
10+
import { useTrack } from '@/lib/telemetry/track'
1011
import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
1112

1213
export const AdvisorButton = ({ projectRef }: { projectRef?: string }) => {
1314
const { toggleSidebar, activeSidebar } = useSidebarManagerSnapshot()
15+
const track = useTrack()
1416

1517
const { data: lints } = useProjectLintsQuery({ projectRef })
1618
const { data: signalItems } = useAdvisorSignals({ projectRef })
@@ -36,6 +38,7 @@ export const AdvisorButton = ({ projectRef }: { projectRef?: string }) => {
3638
const isOpen = activeSidebar?.id === SIDEBAR_KEYS.ADVISOR_PANEL
3739

3840
const handleClick = () => {
41+
track('header_advisor_button_clicked')
3942
toggleSidebar(SIDEBAR_KEYS.ADVISOR_PANEL)
4043
}
4144

apps/studio/components/layouts/AppLayout/AssistantButton.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import { AiIconAnimation, cn, KeyboardShortcut } from 'ui'
22

33
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
44
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
5+
import { useTrack } from '@/lib/telemetry/track'
56
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
67
import { useIsShortcutEnabled } from '@/state/shortcuts/useIsShortcutEnabled'
78
import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
89

910
export const AssistantButton = () => {
1011
const { activeSidebar, toggleSidebar } = useSidebarManagerSnapshot()
1112
const isAIAssistantHotkeyEnabled = useIsShortcutEnabled(SHORTCUT_IDS.AI_ASSISTANT_TOGGLE)
13+
const track = useTrack()
1214

1315
const isOpen = activeSidebar?.id === SIDEBAR_KEYS.AI_ASSISTANT
1416

@@ -22,6 +24,7 @@ export const AssistantButton = () => {
2224
isOpen && 'bg-foreground text-background'
2325
)}
2426
onClick={() => {
27+
track('header_assistant_button_clicked')
2528
toggleSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
2629
}}
2730
tooltip={{

apps/studio/components/layouts/AppLayout/BranchDropdown.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useEmbeddedCloseHandler } from './useEmbeddedCloseHandler'
99
import { useBranchesQuery } from '@/data/branches/branches-query'
1010
import type { Branch } from '@/data/branches/branches-query'
1111
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
12+
import { useTrack } from '@/lib/telemetry/track'
1213
import { useAppStateSnapshot } from '@/state/app-state'
1314

1415
interface BranchDropdownProps {
@@ -28,6 +29,12 @@ export const BranchDropdown = ({
2829

2930
const [open, setOpen] = useState(false)
3031
const close = useEmbeddedCloseHandler(embedded, onClose, setOpen)
32+
const track = useTrack()
33+
34+
const handleOpenChange = (next: boolean) => {
35+
if (next) track('header_branch_dropdown_opened')
36+
setOpen(next)
37+
}
3138

3239
const projectRef = projectDetails?.parent_project_ref || ref
3340

@@ -100,7 +107,7 @@ export const BranchDropdown = ({
100107
linkClassName="flex items-center gap-2 shrink-0"
101108
commandContent={commandContent}
102109
open={open}
103-
onOpenChange={setOpen}
110+
onOpenChange={handleOpenChange}
104111
/>
105112
)
106113
}

apps/studio/components/layouts/AppLayout/InlineEditorButton.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { cn, KeyboardShortcut } from 'ui'
33

44
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
55
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
6+
import { useTrack } from '@/lib/telemetry/track'
67
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
78
import { useIsShortcutEnabled } from '@/state/shortcuts/useIsShortcutEnabled'
89
import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
@@ -16,8 +17,10 @@ const InlineEditorKeyboardTooltip = () => {
1617
export const InlineEditorButton = () => {
1718
const { activeSidebar, toggleSidebar } = useSidebarManagerSnapshot()
1819
const isOpen = activeSidebar?.id === SIDEBAR_KEYS.EDITOR_PANEL
20+
const track = useTrack()
1921

2022
const handleClick = () => {
23+
track('header_inline_editor_button_clicked')
2124
toggleSidebar(SIDEBAR_KEYS.EDITOR_PANEL)
2225
}
2326

apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import PartnerIcon from '@/components/ui/PartnerIcon'
1212
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
1313
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
1414
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
15+
import { useTrack } from '@/lib/telemetry/track'
1516

1617
interface OrganizationDropdownProps {
1718
embedded?: boolean
@@ -40,6 +41,12 @@ export const OrganizationDropdown = ({
4041

4142
const [open, setOpen] = useState(false)
4243
const close = useEmbeddedCloseHandler(embedded, onClose, setOpen)
44+
const track = useTrack()
45+
46+
const handleOpenChange = (next: boolean) => {
47+
if (next) track('header_organization_dropdown_opened')
48+
setOpen(next)
49+
}
4350

4451
if (isLoadingOrganizations && !embedded)
4552
return <ShimmeringLoader className="p-2 md:mr-2 w-[90px]" />
@@ -84,7 +91,7 @@ export const OrganizationDropdown = ({
8491
}
8592
commandContent={commandContent}
8693
open={open}
87-
onOpenChange={setOpen}
94+
onOpenChange={handleOpenChange}
8895
/>
8996
)
9097
}

apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganizati
2121
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
2222
import { IS_PLATFORM } from '@/lib/constants'
2323
import type { ManagedBy } from '@/lib/constants/infrastructure'
24+
import { useTrack } from '@/lib/telemetry/track'
2425

2526
// --- Sub-components ---
2627

@@ -140,6 +141,7 @@ export const ProjectDropdown = ({
140141
const selectedProject = parentProject ?? project
141142

142143
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
144+
const track = useTrack()
143145

144146
const [open, setOpen] = useState(false)
145147
const close = useEmbeddedCloseHandler(embedded, onClose, setOpen)
@@ -153,7 +155,12 @@ export const ProjectDropdown = ({
153155
if (!embedded) return <ShimmeringLoader className="p-2 md:mr-2 md:w-[90px]" />
154156
}
155157

156-
const handleSetOpen = embedded ? (_value: boolean) => onClose?.() : setOpen
158+
const handleSetOpen = embedded
159+
? (_value: boolean) => onClose?.()
160+
: (next: boolean) => {
161+
if (next) track('header_project_dropdown_opened')
162+
setOpen(next)
163+
}
157164

158165
const selectorProps = {
159166
open,

0 commit comments

Comments
 (0)