Browse Source

feat(toolbar): add issues related to release & implement analytics provider (#75194)

- add a section to the releases panel with the latest issues for that
release
- also create an analytics provider, which helps re-use the issue list
item component, as well as simplify some of the analytics
tracking/reduce copy-paste
- will need to follow up with updating the corresponding amplitude
dashboards
- closes https://github.com/getsentry/sentry/issues/74564



https://github.com/user-attachments/assets/25e43dfe-e8e4-4aa9-bd29-9a439023e862




With no issues:
<img width="353" alt="SCR-20240729-lwce"
src="https://github.com/user-attachments/assets/c25c8295-4415-4687-8695-dc7d72f70907">
Michelle Zhang 7 months ago
parent
commit
c6effcc162

+ 43 - 52
static/app/components/devtoolbar/components/alerts/alertsPanel.tsx

@@ -2,6 +2,7 @@ import {css} from '@emotion/react';
 
 import ActorAvatar from 'sentry/components/avatar/actorAvatar';
 import AlertBadge from 'sentry/components/badge/alertBadge';
+import AnalyticsProvider from 'sentry/components/devtoolbar/components/analyticsProvider';
 import ProjectBadge from 'sentry/components/idBadge/projectBadge';
 import Placeholder from 'sentry/components/placeholder';
 import TextOverflow from 'sentry/components/textOverflow';
@@ -33,7 +34,7 @@ import useTeams from '../teams/useTeams';
 import useInfiniteAlertsList from './useInfiniteAlertsList';
 
 export default function AlertsPanel() {
-  const {projectId, projectSlug, projectPlatform, trackAnalytics} = useConfiguration();
+  const {projectId, projectSlug, projectPlatform} = useConfiguration();
   const queryResult = useInfiniteAlertsList();
 
   const estimateSize = 84;
@@ -41,40 +42,34 @@ export default function AlertsPanel() {
 
   return (
     <PanelLayout title="Alerts">
-      <div css={[smallCss, panelSectionCss, panelInsetContentCss]}>
-        <span css={[resetFlexRowCss, {gap: 'var(--space50)'}]}>
-          Active alerts in{' '}
-          <SentryAppLink
-            to={{url: `/projects/${projectSlug}/`}}
-            onClick={() => {
-              trackAnalytics?.({
-                eventKey: `devtoolbar.alerts-list.header.click`,
-                eventName: `devtoolbar: Click alert-list header`,
-              });
-            }}
-          >
-            <div
-              css={[
-                resetFlexRowCss,
-                {display: 'inline-flex', gap: 'var(--space50)', alignItems: 'center'},
-              ]}
-            >
-              <ProjectBadge
-                css={css({'&& img': {boxShadow: 'none'}})}
-                project={{
-                  slug: projectSlug,
-                  id: projectId,
-                  platform: projectPlatform as PlatformKey,
-                }}
-                avatarSize={16}
-                hideName
-                avatarProps={{hasTooltip: false}}
-              />
-              {projectSlug}
-            </div>
-          </SentryAppLink>
-        </span>
-      </div>
+      <AnalyticsProvider nameVal="header" keyVal="header">
+        <div css={[smallCss, panelSectionCss, panelInsetContentCss]}>
+          <span css={[resetFlexRowCss, {gap: 'var(--space50)'}]}>
+            Active alerts in{' '}
+            <SentryAppLink to={{url: `/projects/${projectSlug}/`}}>
+              <div
+                css={[
+                  resetFlexRowCss,
+                  {display: 'inline-flex', gap: 'var(--space50)', alignItems: 'center'},
+                ]}
+              >
+                <ProjectBadge
+                  css={css({'&& img': {boxShadow: 'none'}})}
+                  project={{
+                    slug: projectSlug,
+                    id: projectId,
+                    platform: projectPlatform as PlatformKey,
+                  }}
+                  avatarSize={16}
+                  hideName
+                  avatarProps={{hasTooltip: false}}
+                />
+                {projectSlug}
+              </div>
+            </SentryAppLink>
+          </span>
+        </div>
+      </AnalyticsProvider>
 
       <div css={resetFlexColumnCss}>
         <InfiniteListState
@@ -109,7 +104,7 @@ export default function AlertsPanel() {
 }
 
 function AlertListItem({item}: {item: Incident}) {
-  const {organizationSlug, trackAnalytics} = useConfiguration();
+  const {organizationSlug} = useConfiguration();
 
   const ownerId = item.alertRule.owner?.split(':').at(1);
 
@@ -154,22 +149,18 @@ function AlertListItem({item}: {item: Incident}) {
         <TimeSince date={item.dateStarted} unitStyle="extraShort" />
       </div>
 
-      <TextOverflow css={smallCss} style={{gridArea: 'name'}}>
-        <SentryAppLink
-          to={{
-            url: alertDetailsLink({slug: organizationSlug} as Organization, item),
-            query: {alert: item.identifier},
-          }}
-          onClick={() => {
-            trackAnalytics?.({
-              eventKey: `devtoolbar.alert-list.item.click`,
-              eventName: `devtoolbar: Click alert-list item`,
-            });
-          }}
-        >
-          <strong>{item.title}</strong>
-        </SentryAppLink>
-      </TextOverflow>
+      <AnalyticsProvider nameVal="item" keyVal="item">
+        <TextOverflow css={smallCss} style={{gridArea: 'name'}}>
+          <SentryAppLink
+            to={{
+              url: alertDetailsLink({slug: organizationSlug} as Organization, item),
+              query: {alert: item.identifier},
+            }}
+          >
+            <strong>{item.title}</strong>
+          </SentryAppLink>
+        </TextOverflow>
+      </AnalyticsProvider>
 
       <div css={smallCss} style={{gridArea: 'message'}}>
         <ActivatedMetricAlertRuleStatus rule={rule} />

+ 25 - 0
static/app/components/devtoolbar/components/analyticsProvider.tsx

@@ -0,0 +1,25 @@
+import {createContext, type ReactNode, useContext} from 'react';
+
+export const AnalyticsContext = createContext<{eventKey: string; eventName: string}>({
+  eventName: 'devtoolbar',
+  eventKey: 'devtoolbar',
+});
+
+export default function AnalyticsProvider({
+  children,
+  keyVal,
+  nameVal,
+}: {
+  children: ReactNode;
+  keyVal: string;
+  nameVal: string;
+}) {
+  const {eventKey, eventName} = useContext(AnalyticsContext);
+  return (
+    <AnalyticsContext.Provider
+      value={{eventName: eventName + ' ' + nameVal, eventKey: eventKey + '.' + keyVal}}
+    >
+      {children}
+    </AnalyticsContext.Provider>
+  );
+}

+ 4 - 1
static/app/components/devtoolbar/components/app.tsx

@@ -1,6 +1,7 @@
 import {Fragment, Suspense} from 'react';
 import {Global} from '@emotion/react';
 
+import AnalyticsProvider from 'sentry/components/devtoolbar/components/analyticsProvider';
 import LoadingTriangle from 'sentry/components/loadingTriangle';
 import {useSessionStorage} from 'sentry/utils/useSessionStorage';
 
@@ -33,7 +34,9 @@ export default function App() {
       <div css={[fixedContainerBaseCss, placement.fixedContainer.css, {visibility}]}>
         {isDisabled ? null : (
           <Fragment>
-            <Navigation setIsDisabled={setIsDisabled} />
+            <AnalyticsProvider nameVal="nav" keyVal="nav">
+              <Navigation setIsDisabled={setIsDisabled} />
+            </AnalyticsProvider>
             <Suspense fallback={<LoadingPanel />}>
               <PanelRouter />
             </Suspense>

+ 10 - 13
static/app/components/devtoolbar/components/featureFlags/featureFlagsPanel.tsx

@@ -1,5 +1,6 @@
 import {useRef, useState} from 'react';
 
+import AnalyticsProvider from 'sentry/components/devtoolbar/components/analyticsProvider';
 import useEnabledFeatureFlags from 'sentry/components/devtoolbar/components/featureFlags/useEnabledFeatureFlags';
 import {inlineLinkCss} from 'sentry/components/devtoolbar/styles/link';
 import EmptyStateWarning from 'sentry/components/emptyStateWarning';
@@ -15,7 +16,7 @@ import PanelLayout from '../panelLayout';
 
 export default function FeatureFlagsPanel() {
   const featureFlags = useEnabledFeatureFlags();
-  const {organizationSlug, featureFlagTemplateUrl, trackAnalytics} = useConfiguration();
+  const {organizationSlug, featureFlagTemplateUrl} = useConfiguration();
   const [searchTerm, setSearchTerm] = useState('');
   const searchInput = useRef<HTMLInputElement>(null);
 
@@ -58,18 +59,14 @@ export default function FeatureFlagsPanel() {
             return (
               <Cell key={flag} css={[panelInsetContentCss, {alignItems: 'flex-start'}]}>
                 {featureFlagTemplateUrl?.(flag) ? (
-                  <ExternalLink
-                    css={[smallCss, inlineLinkCss]}
-                    href={featureFlagTemplateUrl(flag)}
-                    onClick={() => {
-                      trackAnalytics?.({
-                        eventKey: `devtoolbar.feature-flag-list.item.click`,
-                        eventName: `devtoolbar: Click feature-flag-list item`,
-                      });
-                    }}
-                  >
-                    {flag}
-                  </ExternalLink>
+                  <AnalyticsProvider nameVal="item" keyVal="item">
+                    <ExternalLink
+                      css={[smallCss, inlineLinkCss]}
+                      href={featureFlagTemplateUrl(flag)}
+                    >
+                      {flag}
+                    </ExternalLink>
+                  </AnalyticsProvider>
                 ) : (
                   <span>{flag}</span>
                 )}

+ 58 - 61
static/app/components/devtoolbar/components/feedback/feedbackPanel.tsx

@@ -2,6 +2,7 @@ import {useMemo} from 'react';
 import {css} from '@emotion/react';
 
 import ActorAvatar from 'sentry/components/avatar/actorAvatar';
+import AnalyticsProvider from 'sentry/components/devtoolbar/components/analyticsProvider';
 import ProjectBadge from 'sentry/components/idBadge/projectBadge';
 import Placeholder from 'sentry/components/placeholder';
 import TextOverflow from 'sentry/components/textOverflow';
@@ -109,7 +110,7 @@ export default function FeedbackPanel() {
 }
 
 function FeedbackListItem({item}: {item: FeedbackIssueListItem}) {
-  const {projectSlug, projectId, trackAnalytics} = useConfiguration();
+  const {projectSlug, projectId} = useConfiguration();
   const {feedbackHasReplay} = useReplayCountForFeedbacks();
 
   const hasReplayId = feedbackHasReplay(item.id);
@@ -120,71 +121,67 @@ function FeedbackListItem({item}: {item: FeedbackIssueListItem}) {
   const hasComments = item.numComments > 0;
 
   return (
-    <div css={listItemGridCss}>
-      <TextOverflow css={smallCss} style={{gridArea: 'name'}}>
-        <SentryAppLink
-          to={{
-            url: '/feedback/',
-            query: {project: projectId, feedbackSlug: `${projectSlug}:${item.id}`},
-          }}
-          onClick={() => {
-            trackAnalytics?.({
-              eventKey: `devtoolbar.feedback-list.item.click`,
-              eventName: `devtoolbar: Click feedback-list item`,
-            });
-          }}
-        >
-          <strong>
-            {item.metadata.name ?? item.metadata.contact_email ?? 'Anonymous User'}
-          </strong>
-        </SentryAppLink>
-      </TextOverflow>
-
-      <div
-        css={[gridFlexEndCss, xSmallCss]}
-        style={{gridArea: 'time', color: 'var(--gray300)'}}
-      >
-        <TimeSince date={item.firstSeen} unitStyle="extraShort" />
-      </div>
-
-      <div style={{gridArea: 'message'}}>
-        <TextOverflow css={[smallCss, textOverflowTwoLinesCss]}>
-          {item.metadata.message}
+    <AnalyticsProvider nameVal="item" keyVal="item">
+      <div css={listItemGridCss}>
+        <TextOverflow css={smallCss} style={{gridArea: 'name'}}>
+          <SentryAppLink
+            to={{
+              url: '/feedback/',
+              query: {project: projectId, feedbackSlug: `${projectSlug}:${item.id}`},
+            }}
+          >
+            <strong>
+              {item.metadata.name ?? item.metadata.contact_email ?? 'Anonymous User'}
+            </strong>
+          </SentryAppLink>
         </TextOverflow>
-      </div>
 
-      <div css={[badgeWithLabelCss, xSmallCss]} style={{gridArea: 'owner'}}>
-        {item.project ? (
-          <ProjectBadge
-            css={css({'&& img': {boxShadow: 'none'}})}
-            project={item.project}
-            avatarSize={16}
-            hideName
-            avatarProps={{hasTooltip: false}}
-          />
-        ) : null}
-        <TextOverflow>{item.shortId}</TextOverflow>
-      </div>
-
-      <div css={gridFlexEndCss} style={{gridArea: 'icons'}}>
-        {/* IssueTrackingSignals could have some refactoring so it doesn't
+        <div
+          css={[gridFlexEndCss, xSmallCss]}
+          style={{gridArea: 'time', color: 'var(--gray300)'}}
+        >
+          <TimeSince date={item.firstSeen} unitStyle="extraShort" />
+        </div>
+
+        <div style={{gridArea: 'message'}}>
+          <TextOverflow css={[smallCss, textOverflowTwoLinesCss]}>
+            {item.metadata.message}
+          </TextOverflow>
+        </div>
+
+        <div css={[badgeWithLabelCss, xSmallCss]} style={{gridArea: 'owner'}}>
+          {item.project ? (
+            <ProjectBadge
+              css={css({'&& img': {boxShadow: 'none'}})}
+              project={item.project}
+              avatarSize={16}
+              hideName
+              avatarProps={{hasTooltip: false}}
+            />
+          ) : null}
+          <TextOverflow>{item.shortId}</TextOverflow>
+        </div>
+
+        <div css={gridFlexEndCss} style={{gridArea: 'icons'}}>
+          {/* IssueTrackingSignals could have some refactoring so it doesn't
             depend on useOrganization, and so the filenames match up better with
             the exported functions */}
-        {/* <IssueTrackingSignals group={item as unknown as Group} /> */}
-
-        {hasComments ? <IconChat size="sm" /> : null}
-        {isFatal ? <IconFatal size="xs" color="red400" /> : null}
-        {hasReplayId ? <IconPlay size="xs" /> : null}
-        {hasAttachments ? <IconImage size="xs" /> : null}
-        {item.assignedTo ? (
-          <ActorAvatar
-            actor={item.assignedTo}
-            size={16}
-            tooltipOptions={{containerDisplayMode: 'flex'}}
-          />
-        ) : null}
+          {/* <IssueTrackingSignals group={item as unknown as Group} /> */}
+
+          {hasComments ? <IconChat size="sm" /> : null}
+          {isFatal ? <IconFatal size="xs" color="red400" /> : null}
+          {hasReplayId ? <IconPlay size="xs" /> : null}
+          {hasAttachments ? <IconImage size="xs" /> : null}
+          {item.assignedTo ? (
+            <ActorAvatar
+              actor={item.assignedTo}
+              size={16}
+              tooltipOptions={{containerDisplayMode: 'flex'}}
+            />
+          ) : null}
+        </div>
       </div>
-    </div>
+    </AnalyticsProvider>
   );
 }
 

+ 82 - 0
static/app/components/devtoolbar/components/issueListItem.tsx

@@ -0,0 +1,82 @@
+import {css} from '@emotion/react';
+
+import ActorAvatar from 'sentry/components/avatar/actorAvatar';
+import AnalyticsProvider from 'sentry/components/devtoolbar/components/analyticsProvider';
+import SentryAppLink from 'sentry/components/devtoolbar/components/sentryAppLink';
+import useConfiguration from 'sentry/components/devtoolbar/hooks/useConfiguration';
+import {
+  badgeWithLabelCss,
+  gridFlexEndCss,
+  listItemGridCss,
+} from 'sentry/components/devtoolbar/styles/listItem';
+import {smallCss, xSmallCss} from 'sentry/components/devtoolbar/styles/typography';
+import TimesTag from 'sentry/components/group/inboxBadges/timesTag';
+import ProjectBadge from 'sentry/components/idBadge/projectBadge';
+import TextOverflow from 'sentry/components/textOverflow';
+import TimeSince from 'sentry/components/timeSince';
+import type {Group} from 'sentry/types/group';
+
+export default function IssueListItem({item}: {item: Group}) {
+  const {projectId} = useConfiguration();
+
+  return (
+    <AnalyticsProvider keyVal="issue-list.item" nameVal="issue list item">
+      <div css={listItemGridCss}>
+        <TextOverflow
+          css={[badgeWithLabelCss, smallCss, 'display: block']}
+          style={{gridArea: 'name', fontWeight: item.hasSeen ? 'bold' : 400}}
+        >
+          <SentryAppLink
+            to={{
+              url: `/issues/${item.id}/`,
+              query: {project: projectId},
+            }}
+          >
+            <strong>{item.metadata.type ?? '<unknown>'}</strong>
+          </SentryAppLink>
+        </TextOverflow>
+
+        <div
+          css={[gridFlexEndCss, xSmallCss]}
+          style={{gridArea: 'time', color: 'var(--gray300)'}}
+        >
+          <TimeSince date={item.firstSeen} unitStyle="extraShort" />
+        </div>
+
+        <div style={{gridArea: 'message'}}>
+          <TextOverflow css={[smallCss]}>{item.metadata.value}</TextOverflow>
+        </div>
+
+        <div css={[badgeWithLabelCss, xSmallCss]} style={{gridArea: 'owner'}}>
+          <ProjectBadge
+            css={css({'&& img': {boxShadow: 'none'}})}
+            project={item.project}
+            avatarSize={16}
+            hideName
+            avatarProps={{hasTooltip: false}}
+          />
+          <TextOverflow>{item.shortId}</TextOverflow>
+        </div>
+
+        <div css={gridFlexEndCss} style={{gridArea: 'icons'}}>
+          {item.lifetime || item.firstSeen || item.lastSeen ? (
+            <div className="flex-row">
+              <TimesTag
+                lastSeen={item.lifetime?.lastSeen || item.lastSeen}
+                firstSeen={item.lifetime?.firstSeen || item.firstSeen}
+              />
+            </div>
+          ) : null}
+
+          {item.assignedTo ? (
+            <ActorAvatar
+              actor={item.assignedTo}
+              size={16}
+              tooltipOptions={{containerDisplayMode: 'flex'}}
+            />
+          ) : null}
+        </div>
+      </div>
+    </AnalyticsProvider>
+  );
+}

+ 4 - 87
static/app/components/devtoolbar/components/issues/issuesPanel.tsx

@@ -1,32 +1,18 @@
-import {css} from '@emotion/react';
-
-import ActorAvatar from 'sentry/components/avatar/actorAvatar';
-import TimesTag from 'sentry/components/group/inboxBadges/timesTag';
-import ProjectBadge from 'sentry/components/idBadge/projectBadge';
+import IssueListItem from 'sentry/components/devtoolbar/components/issueListItem';
 import Placeholder from 'sentry/components/placeholder';
-import TextOverflow from 'sentry/components/textOverflow';
-import TimeSince from 'sentry/components/timeSince';
-import type {Group} from 'sentry/types/group';
 
-import useConfiguration from '../../hooks/useConfiguration';
 import useCurrentTransactionName from '../../hooks/useCurrentTransactionName';
-import {
-  badgeWithLabelCss,
-  gridFlexEndCss,
-  listItemGridCss,
-  listItemPlaceholderWrapperCss,
-} from '../../styles/listItem';
+import {listItemPlaceholderWrapperCss} from '../../styles/listItem';
 import {panelInsetContentCss, panelSectionCss} from '../../styles/panel';
 import {resetFlexColumnCss} from '../../styles/reset';
-import {smallCss, xSmallCss} from '../../styles/typography';
+import {smallCss} from '../../styles/typography';
 import InfiniteListItems from '../infiniteListItems';
 import InfiniteListState from '../infiniteListState';
 import PanelLayout from '../panelLayout';
-import SentryAppLink from '../sentryAppLink';
 
 import useInfiniteIssuesList from './useInfiniteIssuesList';
 
-export default function FeedbackPanel() {
+export default function IssuesPanel() {
   const transactionName = useCurrentTransactionName();
   const queryResult = useInfiniteIssuesList({
     query: `url:*${transactionName}`,
@@ -74,72 +60,3 @@ export default function FeedbackPanel() {
     </PanelLayout>
   );
 }
-
-function IssueListItem({item}: {item: Group}) {
-  const {projectSlug, projectId, trackAnalytics} = useConfiguration();
-
-  return (
-    <div css={listItemGridCss}>
-      <TextOverflow
-        css={[badgeWithLabelCss, smallCss]}
-        style={{gridArea: 'name', fontWeight: item.hasSeen ? 'bold' : 400}}
-      >
-        <SentryAppLink
-          to={{
-            url: `/issues/${item.id}/`,
-            query: {project: projectId, feedbackSlug: `${projectSlug}:${item.id}`},
-          }}
-          onClick={() => {
-            trackAnalytics?.({
-              eventKey: `devtoolbar.issue-list.item.click`,
-              eventName: `devtoolbar: Click issue-list item`,
-            });
-          }}
-        >
-          <strong>{item.metadata.type ?? '<unknown>'}</strong>
-        </SentryAppLink>
-      </TextOverflow>
-
-      <div
-        css={[gridFlexEndCss, xSmallCss]}
-        style={{gridArea: 'time', color: 'var(--gray300)'}}
-      >
-        <TimeSince date={item.firstSeen} unitStyle="extraShort" />
-      </div>
-
-      <div style={{gridArea: 'message'}}>
-        <TextOverflow css={[smallCss]}>{item.metadata.value}</TextOverflow>
-      </div>
-
-      <div css={[badgeWithLabelCss, xSmallCss]} style={{gridArea: 'owner'}}>
-        <ProjectBadge
-          css={css({'&& img': {boxShadow: 'none'}})}
-          project={item.project}
-          avatarSize={16}
-          hideName
-          avatarProps={{hasTooltip: false}}
-        />
-        <TextOverflow>{item.shortId}</TextOverflow>
-      </div>
-
-      <div css={gridFlexEndCss} style={{gridArea: 'icons'}}>
-        {item.lifetime || item.firstSeen || item.lastSeen ? (
-          <div className="flex-row">
-            <TimesTag
-              lastSeen={item.lifetime?.lastSeen || item.lastSeen}
-              firstSeen={item.lifetime?.firstSeen || item.firstSeen}
-            />
-          </div>
-        ) : null}
-
-        {item.assignedTo ? (
-          <ActorAvatar
-            actor={item.assignedTo}
-            size={16}
-            tooltipOptions={{containerDisplayMode: 'flex'}}
-          />
-        ) : null}
-      </div>
-    </div>
-  );
-}

+ 7 - 5
static/app/components/devtoolbar/components/navigation.tsx

@@ -1,6 +1,8 @@
-import type {ReactNode} from 'react';
+import {type ReactNode, useContext} from 'react';
 
+import {AnalyticsContext} from 'sentry/components/devtoolbar/components/analyticsProvider';
 import SessionStatusBadge from 'sentry/components/devtoolbar/components/releases/sessionStatusBadge';
+import useConfiguration from 'sentry/components/devtoolbar/hooks/useConfiguration';
 import {
   IconClose,
   IconFlag,
@@ -10,7 +12,6 @@ import {
   IconSiren,
 } from 'sentry/icons';
 
-import useConfiguration from '../hooks/useConfiguration';
 import usePlacementCss from '../hooks/usePlacementCss';
 import useToolbarRoute from '../hooks/useToolbarRoute';
 import {navigationCss} from '../styles/navigation';
@@ -28,6 +29,7 @@ export default function Navigation({
   const placement = usePlacementCss();
 
   const {state: route} = useToolbarRoute();
+  const {eventName, eventKey} = useContext(AnalyticsContext);
   const isRouteActive = !!route.activePanel;
 
   return (
@@ -37,11 +39,11 @@ export default function Navigation({
     >
       <IconButton
         onClick={() => {
-          setIsDisabled(true);
           trackAnalytics?.({
-            eventKey: `devtoolbar.nav.hide.click`,
-            eventName: `devtoolbar: Hide devtoolbar`,
+            eventKey: eventKey + '.hide.click',
+            eventName: eventName + ' hide devtoolbar clicked',
           });
+          setIsDisabled(true);
         }}
         title="Hide for this session"
         icon={<IconClose />}

+ 1 - 1
static/app/components/devtoolbar/components/panelLayout.tsx

@@ -4,8 +4,8 @@ import {panelCss, panelHeadingCss, panelSectionCss} from '../styles/panel';
 import {resetDialogCss, resetFlexColumnCss} from '../styles/reset';
 
 interface Props {
-  title: string;
   children?: React.ReactNode;
+  title?: string;
   titleLeft?: React.ReactNode;
   titleRight?: React.ReactNode;
 }

+ 27 - 5
static/app/components/devtoolbar/components/panelRouter.tsx

@@ -1,5 +1,7 @@
 import {lazy} from 'react';
 
+import AnalyticsProvider from 'sentry/components/devtoolbar/components/analyticsProvider';
+
 import useToolbarRoute from '../hooks/useToolbarRoute';
 
 const PanelAlerts = lazy(() => import('./alerts/alertsPanel'));
@@ -13,15 +15,35 @@ export default function PanelRouter() {
 
   switch (state.activePanel) {
     case 'alerts':
-      return <PanelAlerts />;
+      return (
+        <AnalyticsProvider keyVal="alerts-panel" nameVal="Alerts panel">
+          <PanelAlerts />
+        </AnalyticsProvider>
+      );
     case 'feedback':
-      return <PanelFeedback />;
+      return (
+        <AnalyticsProvider keyVal="feedback-panel" nameVal="Feedback panel">
+          <PanelFeedback />
+        </AnalyticsProvider>
+      );
     case 'issues':
-      return <PanelIssues />;
+      return (
+        <AnalyticsProvider keyVal="issues-panel" nameVal="Issues panel">
+          <PanelIssues />
+        </AnalyticsProvider>
+      );
     case 'featureFlags':
-      return <PanelFeatureFlags />;
+      return (
+        <AnalyticsProvider keyVal="feature-flags-panel" nameVal="Feature Flags panel">
+          <PanelFeatureFlags />
+        </AnalyticsProvider>
+      );
     case 'releases':
-      return <PanelReleases />;
+      return (
+        <AnalyticsProvider keyVal="releases-panel" nameVal="Releases panel">
+          <PanelReleases />
+        </AnalyticsProvider>
+      );
     default:
       return null;
   }

Some files were not shown because too many files changed in this diff