diff --git a/static/app/components/nav/config.tsx b/static/app/components/nav/config.tsx
index b0b058936d6881..723f612ead32ba 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'},
+ analyticsKey: '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/',
+ analyticsKey: 'performance',
icon: ,
feature: {
features: 'performance-view',
@@ -104,6 +106,7 @@ export function createNavConfig({organization}: {organization: Organization}): N
const perfDomainViews: NavSidebarItem = {
label: t('Perf.'),
icon: ,
+ analyticsKey: '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: ,
+ analyticsKey: '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'),
+ analyticsKey: 'projects',
+ to: `/${prefix}/projects/`,
+ icon: ,
+ },
{
label: t('Explore'),
icon: ,
+ analyticsKey: 'explore',
submenu: [
{
label: t('Traces'),
@@ -201,6 +211,7 @@ export function createNavConfig({organization}: {organization: Organization}): N
...(hasPerfDomainViews ? [perfDomainViews, perf] : [insights, perf]),
{
label: t('Boards'),
+ analyticsKey: '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'),
+ analyticsKey: 'alerts',
+ to: `/${prefix}/alerts/rules/`,
+ icon: ,
+ },
],
footer: [
{
label: t('Help'),
icon: ,
+ analyticsKey: 'help',
dropdown: [
{
key: 'search',
@@ -242,6 +259,7 @@ export function createNavConfig({organization}: {organization: Organization}): N
},
{
label: t('Settings'),
+ analyticsKey: 'settings',
to: `/settings/${organization.slug}/`,
icon: ,
},
diff --git a/static/app/components/nav/index.spec.tsx b/static/app/components/nav/index.spec.tsx
index 0ca7e1efb43bde..a94c20f1747c0c 100644
--- a/static/app/components/nav/index.spec.tsx
+++ b/static/app/components/nav/index.spec.tsx
@@ -2,7 +2,13 @@ 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 {trackAnalytics} from 'sentry/utils/analytics';
+
+jest.mock('sentry/utils/analytics', () => ({
+ trackAnalytics: jest.fn(),
+}));
+
+import {getAllByRole, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
import Nav from 'sentry/components/nav';
@@ -165,4 +171,26 @@ describe('Nav', function () {
});
});
});
+
+ describe('analytics', function () {
+ beforeEach(() => {
+ render(, {
+ router: RouterFixture({
+ location: LocationFixture({pathname: '/organizations/org-slug/traces/'}),
+ }),
+ organization: OrganizationFixture({features: ALL_AVAILABLE_FEATURES}),
+ });
+ });
+
+ it('tracks primary sidebar item', async function () {
+ const issues = screen.getByRole('link', {name: 'Issues'});
+ await userEvent.click(issues);
+ expect(trackAnalytics).toHaveBeenCalledWith(
+ 'growth.clicked_sidebar',
+ expect.objectContaining({
+ item: 'issues',
+ })
+ );
+ });
+ });
});
diff --git a/static/app/components/nav/sidebar.tsx b/static/app/components/nav/sidebar.tsx
index f2f47ec04d4877..2911ca134206ce 100644
--- a/static/app/components/nav/sidebar.tsx
+++ b/static/app/components/nav/sidebar.tsx
@@ -1,4 +1,5 @@
-import {Fragment} from 'react';
+import type {MouseEventHandler} from 'react';
+import {Fragment, useCallback} from 'react';
import styled from '@emotion/styled';
import Feature from 'sentry/components/acl/feature';
@@ -18,8 +19,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 (
@@ -93,19 +96,28 @@ const SidebarItemList = styled('ul')`
interface SidebarItemProps {
item: NavSidebarItem;
+ children?: React.ReactNode;
+ onClick?: MouseEventHandler;
}
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.analyticsKey, organization}),
+ [organization, item.analyticsKey]
+ );
+
return (
-
+
{item.icon}
{item.label}
@@ -127,7 +139,7 @@ const NavButton = styled('button')`
${linkStyles}
`;
-function SidebarLink({children, item}: SidebarItemProps & {children: React.ReactNode}) {
+function SidebarLink({children, item, onClick}: SidebarItemProps) {
const location = useLocation();
const isActive = isNavItemActive(item, location);
const isSubmenuActive = isSubmenuItemActive(item, location);
@@ -142,6 +154,7 @@ function SidebarLink({children, item}: SidebarItemProps & {children: React.React
return (
@@ -151,7 +164,7 @@ function SidebarLink({children, item}: SidebarItemProps & {children: React.React
);
}
-function SidebarMenu({item, children}: SidebarItemProps & {children: React.ReactNode}) {
+function SidebarMenu({item, children, onClick}: SidebarItemProps) {
if (!item.dropdown) {
throw new Error(
`Nav item "${item.label}" must have either a \`dropdown\` or \`to\` value!`
@@ -162,7 +175,13 @@ function SidebarMenu({item, children}: SidebarItemProps & {children: React.React
position="right-end"
trigger={(props, isOpen) => {
return (
-
+ {
+ onClick?.(event);
+ props.onClick?.(event);
+ }}
+ >
{children}
diff --git a/static/app/components/nav/utils.tsx b/static/app/components/nav/utils.tsx
index 98193be8222d9f..498162e743828d 100644
--- a/static/app/components/nav/utils.tsx
+++ b/static/app/components/nav/utils.tsx
@@ -33,6 +33,10 @@ export interface NavItemLayout- {
* SidebarItem is a top-level NavItem which is always displayed in the app sidebar
*/
export interface NavSidebarItem extends NavItem {
+ /**
+ * A unique identifier string, used as a key for analytics
+ */
+ analyticsKey: string;
/**
* The icon to render in the sidebar
*/