From 5a9e90686ffe2f90f9f949abefa6106e4e646c10 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 8 Nov 2024 18:49:00 -0600 Subject: [PATCH 1/7] feat(nav): add analytics tracking --- static/app/components/nav/config.tsx | 22 ++++++++++++++++++++-- static/app/components/nav/sidebar.tsx | 12 ++++++++++-- static/app/components/nav/utils.tsx | 4 ++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/static/app/components/nav/config.tsx b/static/app/components/nav/config.tsx index b0b058936d6881..73c743bd93f2b5 100644 --- a/static/app/components/nav/config.tsx +++ b/static/app/components/nav/config.tsx @@ -52,6 +52,7 @@ export function createNavConfig({organization}: {organization: Organization}): N label: t('Insights'), icon: , feature: {features: 'insights-entry-points'}, + id: 'insights', submenu: [ { label: MODULE_TITLE_HTTP, @@ -94,6 +95,7 @@ export function createNavConfig({organization}: {organization: Organization}): N const perf: NavSidebarItem = { label: t('Perf.'), to: '/performance/', + id: 'performance', icon: , feature: { features: 'performance-view', @@ -104,6 +106,7 @@ export function createNavConfig({organization}: {organization: Organization}): N const perfDomainViews: NavSidebarItem = { label: t('Perf.'), icon: , + id: 'insights-domains', feature: {features: ['insights-domain-view', 'performance-view']}, submenu: [ { @@ -130,6 +133,7 @@ export function createNavConfig({organization}: {organization: Organization}): N { label: t('Issues'), icon: , + id: 'issues', submenu: [ { label: t('All'), @@ -154,10 +158,16 @@ export function createNavConfig({organization}: {organization: Organization}): N {label: t('Feedback'), to: `/${prefix}/feedback/`}, ], }, - {label: t('Projects'), to: `/${prefix}/projects/`, icon: }, + { + label: t('Projects'), + id: 'projects', + to: `/${prefix}/projects/`, + icon: , + }, { label: t('Explore'), icon: , + id: 'explore', submenu: [ { label: t('Traces'), @@ -201,6 +211,7 @@ export function createNavConfig({organization}: {organization: Organization}): N ...(hasPerfDomainViews ? [perfDomainViews, perf] : [insights, perf]), { label: t('Boards'), + id: 'customizable-dashboards', to: '/dashboards/', icon: , feature: { @@ -209,12 +220,18 @@ export function createNavConfig({organization}: {organization: Organization}): N requireAll: false, }, }, - {label: t('Alerts'), to: `/${prefix}/alerts/rules/`, icon: }, + { + label: t('Alerts'), + id: 'alerts', + to: `/${prefix}/alerts/rules/`, + icon: , + }, ], footer: [ { label: t('Help'), icon: , + id: 'help', dropdown: [ { key: 'search', @@ -242,6 +259,7 @@ export function createNavConfig({organization}: {organization: Organization}): N }, { label: t('Settings'), + id: 'settings', to: `/settings/${organization.slug}/`, icon: , }, diff --git a/static/app/components/nav/sidebar.tsx b/static/app/components/nav/sidebar.tsx index f2f47ec04d4877..70fa1dc248c0a0 100644 --- a/static/app/components/nav/sidebar.tsx +++ b/static/app/components/nav/sidebar.tsx @@ -1,4 +1,4 @@ -import {Fragment} from 'react'; +import {Fragment, useCallback} from 'react'; import styled from '@emotion/styled'; import Feature from 'sentry/components/acl/feature'; @@ -18,8 +18,10 @@ import { } from 'sentry/components/nav/utils'; import SidebarDropdown from 'sentry/components/sidebar/sidebarDropdown'; import {space} from 'sentry/styles/space'; +import {trackAnalytics} from 'sentry/utils/analytics'; import theme from 'sentry/utils/theme'; import {useLocation} from 'sentry/utils/useLocation'; +import useOrganization from 'sentry/utils/useOrganization'; function Sidebar() { return ( @@ -98,13 +100,19 @@ interface SidebarItemProps { function SidebarItem({item}: SidebarItemProps) { const to = resolveNavItemTo(item); const SidebarChild = to ? SidebarLink : SidebarMenu; + const organization = useOrganization(); const FeatureGuard = item.feature ? Feature : Fragment; const featureGuardProps: any = item.feature ?? {}; + const recordAnalytics = useCallback( + () => trackAnalytics('growth.clicked_sidebar', {item: item.id, organization}), + [organization, item.id] + ); + return ( - + {item.icon} {item.label} diff --git a/static/app/components/nav/utils.tsx b/static/app/components/nav/utils.tsx index 98193be8222d9f..15b3db7db1f68f 100644 --- a/static/app/components/nav/utils.tsx +++ b/static/app/components/nav/utils.tsx @@ -37,6 +37,10 @@ export interface NavSidebarItem extends NavItem { * The icon to render in the sidebar */ icon: React.ReactElement; + /** + * A unique identifier string, used as a key for analytics + */ + id: string; /** * dropdown menu to display when this SidebarItem is clicked */ From cbc292ea8f8a2fb2d365141fba106c87c35a69e5 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 8 Nov 2024 19:10:28 -0600 Subject: [PATCH 2/7] refactor(nav): support onClick callback --- static/app/components/nav/sidebar.tsx | 31 ++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/static/app/components/nav/sidebar.tsx b/static/app/components/nav/sidebar.tsx index 70fa1dc248c0a0..020ec9fcaf4b91 100644 --- a/static/app/components/nav/sidebar.tsx +++ b/static/app/components/nav/sidebar.tsx @@ -112,8 +112,8 @@ function SidebarItem({item}: SidebarItemProps) { return ( - - + + {item.icon} {item.label} @@ -135,7 +135,14 @@ const NavButton = styled('button')` ${linkStyles} `; -function SidebarLink({children, item}: SidebarItemProps & {children: React.ReactNode}) { +function SidebarLink({ + children, + item, + onClick, +}: SidebarItemProps & { + children: React.ReactNode; + onClick: React.HTMLAttributes['onClick']; +}) { const location = useLocation(); const isActive = isNavItemActive(item, location); const isSubmenuActive = isSubmenuItemActive(item, location); @@ -150,6 +157,7 @@ function SidebarLink({children, item}: SidebarItemProps & {children: React.React return ( @@ -159,7 +167,14 @@ function SidebarLink({children, item}: SidebarItemProps & {children: React.React ); } -function SidebarMenu({item, children}: SidebarItemProps & {children: React.ReactNode}) { +function SidebarMenu({ + item, + children, + onClick, +}: SidebarItemProps & { + children: React.ReactNode; + onClick: React.HTMLAttributes['onClick']; +}) { if (!item.dropdown) { throw new Error( `Nav item "${item.label}" must have either a \`dropdown\` or \`to\` value!` @@ -170,7 +185,13 @@ function SidebarMenu({item, children}: SidebarItemProps & {children: React.React position="right-end" trigger={(props, isOpen) => { return ( - + { + onClick?.(event); + props.onClick?.(event); + }} + > {children} From 1b82bc635ac1bcfbcd543c468401f4aff7c0cf26 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 8 Nov 2024 19:10:49 -0600 Subject: [PATCH 3/7] test(nav): add trackAnalytics test --- static/app/components/nav/index.spec.tsx | 28 +++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/static/app/components/nav/index.spec.tsx b/static/app/components/nav/index.spec.tsx index 0ca7e1efb43bde..19cc3354329b0a 100644 --- a/static/app/components/nav/index.spec.tsx +++ b/static/app/components/nav/index.spec.tsx @@ -2,7 +2,11 @@ import {LocationFixture} from 'sentry-fixture/locationFixture'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {RouterFixture} from 'sentry-fixture/routerFixture'; -import {getAllByRole, render, screen} from 'sentry-test/reactTestingLibrary'; +import * as analytics from 'sentry/utils/analytics'; + +const analyticsSpy = jest.spyOn(analytics, 'trackAnalytics'); + +import {getAllByRole, render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import Nav from 'sentry/components/nav'; @@ -165,4 +169,26 @@ describe('Nav', function () { }); }); }); + + describe('analytics', function () { + beforeEach(() => { + render(