mirror of
https://github.com/supabase/supabase.git
synced 2026-06-16 11:58:16 +08:00
## 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 -->
98 lines
3.2 KiB
TypeScript
98 lines
3.2 KiB
TypeScript
import { useParams } from 'common'
|
|
import { Boxes } from 'lucide-react'
|
|
import { useRouter } from 'next/router'
|
|
import { useState } from 'react'
|
|
import { Badge, cn } from 'ui'
|
|
import { GenericSkeletonLoader, ShimmeringLoader } from 'ui-patterns'
|
|
|
|
import { AppLayoutDropdownError, AppLayoutDropdownWithPopover } from './AppLayoutDropdown'
|
|
import { OrganizationDropdownCommandContent } from './OrganizationDropdownCommandContent'
|
|
import { useEmbeddedCloseHandler } from './useEmbeddedCloseHandler'
|
|
import PartnerIcon from '@/components/ui/PartnerIcon'
|
|
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
|
|
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
|
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
|
import { useTrack } from '@/lib/telemetry/track'
|
|
|
|
interface OrganizationDropdownProps {
|
|
embedded?: boolean
|
|
className?: string
|
|
onClose?: () => void
|
|
}
|
|
|
|
export const OrganizationDropdown = ({
|
|
embedded = false,
|
|
className,
|
|
onClose,
|
|
}: OrganizationDropdownProps = {}) => {
|
|
const router = useRouter()
|
|
const { slug: routeSlug } = useParams()
|
|
const { data: selectedOrganization } = useSelectedOrganizationQuery()
|
|
const {
|
|
data: organizations,
|
|
isPending: isLoadingOrganizations,
|
|
isError,
|
|
} = useOrganizationsQuery()
|
|
|
|
const organizationCreationEnabled = useIsFeatureEnabled('organizations:create')
|
|
|
|
const slug = selectedOrganization?.slug
|
|
const orgName = selectedOrganization?.name
|
|
|
|
const [open, setOpen] = useState(false)
|
|
const close = useEmbeddedCloseHandler(embedded, onClose, setOpen)
|
|
const track = useTrack()
|
|
|
|
const handleOpenChange = (next: boolean) => {
|
|
if (next) track('header_organization_dropdown_opened')
|
|
setOpen(next)
|
|
}
|
|
|
|
if (isLoadingOrganizations && !embedded)
|
|
return <ShimmeringLoader className="p-2 md:mr-2 w-[90px]" />
|
|
|
|
if (isError) return <AppLayoutDropdownError message="Failed to load organizations" />
|
|
|
|
const commandContent = (
|
|
<OrganizationDropdownCommandContent
|
|
embedded={embedded}
|
|
className={className}
|
|
organizations={organizations ?? []}
|
|
selectedSlug={slug}
|
|
routePathname={router.pathname}
|
|
hasRouteSlug={!!routeSlug}
|
|
organizationCreationEnabled={organizationCreationEnabled}
|
|
onClose={close}
|
|
/>
|
|
)
|
|
|
|
if (embedded)
|
|
return isLoadingOrganizations ? <GenericSkeletonLoader className="p-2" /> : commandContent
|
|
|
|
return (
|
|
<AppLayoutDropdownWithPopover
|
|
linkHref={slug ? `/org/${slug}` : '/organizations'}
|
|
linkContent={
|
|
<>
|
|
<Boxes size={14} strokeWidth={1.5} className="text-foreground-lighter" />
|
|
<span
|
|
className={cn(
|
|
'md:max-w-32 lg:max-w-none truncate hidden md:block',
|
|
!!selectedOrganization ? 'text-foreground' : 'text-foreground-lighter'
|
|
)}
|
|
>
|
|
{orgName ?? 'Select an organization'}
|
|
</span>
|
|
{!!selectedOrganization && <PartnerIcon organization={selectedOrganization} />}
|
|
{!!selectedOrganization && (
|
|
<Badge variant="default">{selectedOrganization?.plan.name}</Badge>
|
|
)}
|
|
</>
|
|
}
|
|
commandContent={commandContent}
|
|
open={open}
|
|
onOpenChange={handleOpenChange}
|
|
/>
|
|
)
|
|
}
|