Repository: getsentry/sentry
Pull request: feat(replays): Analytics for open/close Replay>Network>Details panel
Fixes https://github.com/getsentry/sentry/issues/47835

Problem statement:
[Success Metrics] Add analytics for engagement related to the network details panel req/resp
Log steps in the funnel:

- Clicking on the CTA
- Setting up new SDK
- Setting up allow-list of bodies and/or headers
- Clicking on the network panel and seeing bodies and/or headers

Jasmin's Notes:

I'd like to see the following steps in the funnel:

1. Click on the Network tab  (we already have this)
2. Clicking on XHR and Fetch requests and seeing our opt-in state 
2. Clicking on XHR and Fetch requests and seeing the bodies and/or header

Hints from issue discussion:

Commit message: update field names

Patch to review:
diff --git a/static/app/utils/analytics/replayAnalyticsEvents.tsx b/static/app/utils/analytics/replayAnalyticsEvents.tsx
index 8fb0061c964a52..e3ce57cd4afccd 100644
--- a/static/app/utils/analytics/replayAnalyticsEvents.tsx
+++ b/static/app/utils/analytics/replayAnalyticsEvents.tsx
@@ -1,10 +1,28 @@
 import type {LayoutKey} from 'sentry/utils/replays/hooks/useReplayLayout';
+import {Output} from 'sentry/views/replays/detail/network/details/getOutputType';
 
 export type ReplayEventParameters = {
   'replay.details-layout-changed': {
     chosen_layout: LayoutKey;
     default_layout: LayoutKey;
   };
+  'replay.details-network-panel-closed': {
+    isSDKSetup: boolean;
+  };
+  'replay.details-network-panel-opened': {
+    isSDKSetup: boolean;
+    resource_method: string;
+    resource_status: string;
+    resource_type: string;
+  };
+  'replay.details-network-tab-changed': {
+    isSDKSetup: boolean;
+    output: Output;
+    resource_method: string;
+    resource_status: string;
+    resource_type: string;
+    tab: string;
+  };
   'replay.details-resized-panel': {
     layout: LayoutKey;
     slide_motion: 'toTop' | 'toBottom' | 'toLeft' | 'toRight';
@@ -59,6 +77,9 @@ export type ReplayEventKey = keyof ReplayEventParameters;
 
 export const replayEventMap: Record<ReplayEventKey, string | null> = {
   'replay.details-layout-changed': 'Changed Replay Details Layout',
+  'replay.details-network-panel-closed': 'Closed Replay Network Details Panel',
+  'replay.details-network-panel-opened': 'Opened Replay Network Details Panel',
+  'replay.details-network-tab-changed': 'Changed Replay Network Details Tab',
   'replay.details-resized-panel': 'Resized Replay Details Panel',
   'replay.details-tab-changed': 'Changed Replay Details Tab',
   'replay.details-time-spent': 'Time Spent Viewing Replay Details',
diff --git a/static/app/views/replays/detail/network/details/content.tsx b/static/app/views/replays/detail/network/details/content.tsx
index c7a434fdde2dd8..2a0a4b8fbeb01f 100644
--- a/static/app/views/replays/detail/network/details/content.tsx
+++ b/static/app/views/replays/detail/network/details/content.tsx
@@ -1,5 +1,8 @@
+import {useEffect} from 'react';
 import styled from '@emotion/styled';
 
+import {trackAnalytics} from 'sentry/utils/analytics';
+import useOrganization from 'sentry/utils/useOrganization';
 import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
 import getOutputType, {
   Output,
@@ -21,10 +24,23 @@ import {
 type Props = Parameters<typeof getOutputType>[0] & SectionProps;
 
 export default function NetworkDetailsContent(props: Props) {
-  const {visibleTab} = props;
+  const {item, isSetup, visibleTab} = props;
 
   const output = getOutputType(props);
 
+  const organization = useOrganization();
+  useEffect(() => {
+    trackAnalytics('replay.details-network-tab-changed', {
+      isSDKSetup: isSetup,
+      organization,
+      output,
+      resource_method: item.data.method,
+      resource_status: item.data.statusCode,
+      resource_type: item.op,
+      tab: visibleTab,
+    });
+  }, [isSetup, item, organization, output, visibleTab]);
+
   switch (visibleTab) {
     case 'request':
       return (
diff --git a/static/app/views/replays/detail/network/index.tsx b/static/app/views/replays/detail/network/index.tsx
index 0bb432bdd7c868..9e7ab626151fbc 100644
--- a/static/app/views/replays/detail/network/index.tsx
+++ b/static/app/views/replays/detail/network/index.tsx
@@ -6,6 +6,7 @@ import Feature from 'sentry/components/acl/feature';
 import Placeholder from 'sentry/components/placeholder';
 import {useReplayContext} from 'sentry/components/replays/replayContext';
 import {t} from 'sentry/locale';
+import {trackAnalytics} from 'sentry/utils/analytics';
 import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
 import useOrganization from 'sentry/utils/useOrganization';
 import {useResizableDrawer} from 'sentry/utils/useResizableDrawer';
@@ -101,8 +102,17 @@ function NetworkList({
     ({dataIndex, rowIndex}: {dataIndex: number; rowIndex: number}) => {
       setDetailRow(String(dataIndex));
       setScrollToRow(rowIndex);
+
+      const item = items[dataIndex];
+      trackAnalytics('replay.details-network-panel-opened', {
+        isSDKSetup: isNetworkDetailsSetup,
+        organization,
+        resource_method: item.data.method,
+        resource_status: item.data.statusCode,
+        resource_type: item.op,
+      });
     },
-    [setDetailRow]
+    [isNetworkDetailsSetup, items, organization, setDetailRow]
   );
 
   const cellRenderer = ({columnIndex, rowIndex, key, style, parent}: GridCellProps) => {
@@ -219,7 +229,13 @@ function NetworkList({
               {...resizableDrawerProps}
               isSetup={isNetworkDetailsSetup}
               item={detailDataIndex ? (items[detailDataIndex] as NetworkSpan) : null}
-              onClose={() => setDetailRow('')}
+              onClose={() => {
+                setDetailRow('');
+                trackAnalytics('replay.details-network-panel-closed', {
+                  isSDKSetup: isNetworkDetailsSetup,
+                  organization,
+                });
+              }}
               onScrollToRow={() => setScrollToRow(Number(detailRowIndex))}
               projectId={projectId}
               startTimestampMs={startTimestampMs}


Code review comments:
diff_hunk: @@ -1,10 +1,28 @@
 import type {LayoutKey} from 'sentry/utils/replays/hooks/useReplayLayout';
+import {Output} from 'sentry/views/replays/detail/network/details/getOutputType';
 
 export type ReplayEventParameters = {
   'replay.details-layout-changed': {
     chosen_layout: LayoutKey;
     default_layout: LayoutKey;
   };
+  'replay.details-network-panel-closed': {
+    isSDKSetup: boolean;
+  };
+  'replay.details-network-panel-opened': {
+    isSDKSetup: boolean;
path: static/app/utils/analytics/replayAnalyticsEvents.tsx
text: @user1:
should this be snake case?

@author:
d'oh