Repository: getsentry/sentry
Pull request: feat(replays): Breadcrumb Icon Tab Navigation
### Changes
- Adds click handler to breadcrumb icon that opens related tab

Closes #36803 

<!--

  Sentry employees and contractors can delete or ignore the following.

-->

### Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.


Problem statement:
Timeline Events should be clickable and open the relevant detailed tab
I want to be able to click on the event circle, and/or the content of the tooltip, and be taken into the related tab.

For PageLoad, LCP, Reload events the Network tab should be opened.
- In future we should scroll and highlight the specific row in the network table. This highlight must be made obvious so the user can follow along.

For UserClick events, it makes sense to highlight the element in the replay player... but for that we'd need to scrub the player to the right spot, which is what the breadcrumbs list does. So we might need something else.

Hints from issue discussion:

Commit message: fix(replays): add tab nav handler to timeline nodes

Patch to review:
diff --git a/static/app/components/replays/breadcrumbs/replayTimelineEvents.tsx b/static/app/components/replays/breadcrumbs/replayTimelineEvents.tsx
index c35b9f63371496..3fcc927f6d8d8f 100644
--- a/static/app/components/replays/breadcrumbs/replayTimelineEvents.tsx
+++ b/static/app/components/replays/breadcrumbs/replayTimelineEvents.tsx
@@ -8,6 +8,7 @@ import {getCrumbsByColumn, relativeTimeInMs} from 'sentry/components/replays/uti
 import Tooltip from 'sentry/components/tooltip';
 import space from 'sentry/styles/space';
 import {Crumb} from 'sentry/types/breadcrumbs';
+import useActiveReplayTab from 'sentry/utils/replays/hooks/useActiveReplayTab';
 import type {Color} from 'sentry/utils/theme';
 import theme from 'sentry/utils/theme';
 import BreadcrumbItem from 'sentry/views/replays/detail/breadcrumbs/breadcrumbItem';
@@ -67,6 +68,7 @@ function Event({
   className?: string;
 }) {
   const {setCurrentTime} = useReplayContext();
+  const {setActiveTab} = useActiveReplayTab();
 
   const handleClick = useCallback(
     (crumb: Crumb) => {
@@ -105,16 +107,41 @@ function Event({
   // We just need to stack up to 3 times
   const totalStackNumber = Math.min(crumbs.length, 3);
 
+  // If there is only 1 event use the tab navigation handler on the node
+  const nodeClickHandler = () => {
+    if (crumbs.length === 1) {
+      const crumb = crumbs[0];
+
+      switch (crumb.type) {
+        case 'navigation':
+        case 'debug':
+          setActiveTab('network');
+          break;
+        case 'ui':
+          setActiveTab('dom');
+          break;
+        case 'error':
+          setActiveTab('issues');
+          break;
+        default:
+          setActiveTab('console');
+          break;
+      }
+    }
+  };
+
   return (
-    <IconPosition>
+    <IconPosition onClick={nodeClickHandler}>
       <IconNodeTooltip title={title} overlayStyle={overlayStyle} isHoverable>
-        {crumbs.slice(0, totalStackNumber).map((crumb, index) => (
-          <IconNode
-            color={colors[index] || crumb.color}
-            key={crumb.id}
-            stack={{totalStackNumber, index}}
-          />
-        ))}
+        {crumbs.slice(0, totalStackNumber).map((crumb, index) => {
+          return (
+            <IconNode
+              color={colors[index] || crumb.color}
+              key={crumb.id}
+              stack={{totalStackNumber, index}}
+            />
+          );
+        })}
       </IconNodeTooltip>
     </IconPosition>
   );
diff --git a/static/app/views/replays/detail/breadcrumbs/breadcrumbItem.tsx b/static/app/views/replays/detail/breadcrumbs/breadcrumbItem.tsx
index d5c5269618f9db..a2e1227a457efa 100644
--- a/static/app/views/replays/detail/breadcrumbs/breadcrumbItem.tsx
+++ b/static/app/views/replays/detail/breadcrumbs/breadcrumbItem.tsx
@@ -8,6 +8,7 @@ import PlayerRelativeTime from 'sentry/components/replays/playerRelativeTime';
 import {SVGIconProps} from 'sentry/icons/svgIcon';
 import space from 'sentry/styles/space';
 import type {Crumb} from 'sentry/types/breadcrumbs';
+import useActiveReplayTab from 'sentry/utils/replays/hooks/useActiveReplayTab';
 
 type MouseCallback = (crumb: Crumb, e: React.MouseEvent<HTMLElement>) => void;
 
@@ -31,6 +32,7 @@ function BreadcrumbItem({
   onClick,
 }: Props) {
   const {title, description} = getDetails(crumb);
+  const {setActiveTab} = useActiveReplayTab();
 
   const handleMouseEnter = useCallback(
     (e: React.MouseEvent<HTMLElement>) => onMouseEnter && onMouseEnter(crumb, e),
@@ -41,8 +43,25 @@ function BreadcrumbItem({
     [onMouseLeave, crumb]
   );
   const handleClick = useCallback(
-    (e: React.MouseEvent<HTMLElement>) => onClick?.(crumb, e),
-    [onClick, crumb]
+    (e: React.MouseEvent<HTMLElement>) => {
+      onClick?.(crumb, e);
+      switch (crumb.type) {
+        case 'navigation':
+        case 'debug':
+          setActiveTab('network');
+          break;
+        case 'ui':
+          setActiveTab('dom');
+          break;
+        case 'error':
+          setActiveTab('issues');
+          break;
+        default:
+          setActiveTab('console');
+          break;
+      }
+    },
+    [crumb, setActiveTab, onClick]
   );
 
   return (


Code review comments:
diff_hunk: @@ -105,16 +107,41 @@ function Event({
   // We just need to stack up to 3 times
   const totalStackNumber = Math.min(crumbs.length, 3);
 
+  // If there is only 1 event use the tab navigation handler on the node
+  const nodeClickHandler = () => {
+    if (crumbs.length === 1) {
+      const crumb = crumbs[0];
+
+      switch (crumb.type) {
+        case 'navigation':
+        case 'debug':
+          setActiveTab('network');
+          break;
+        case 'ui':
+          setActiveTab('dom');
+          break;
+        case 'error':
+          setActiveTab('issues');
+          break;
+        default:
+          setActiveTab('console');
+          break;
+      }
+    }
+  };
+
   return (
-    <IconPosition>
+    <IconPosition onClick={nodeClickHandler}>
       <IconNodeTooltip title={title} overlayStyle={overlayStyle} isHoverable>
-        {crumbs.slice(0, totalStackNumber).map((crumb, index) => (
-          <IconNode
-            color={colors[index] || crumb.color}
-            key={crumb.id}
-            stack={{totalStackNumber, index}}
-          />
-        ))}
+        {crumbs.slice(0, totalStackNumber).map((crumb, index) => {
+          return (
+            <IconNode
+              color={colors[index] || crumb.color}
+              key={crumb.id}
+              stack={{totalStackNumber, index}}
+            />
+          );
+        })}
path: static/app/components/replays/breadcrumbs/replayTimelineEvents.tsx
text: @user1:
formatting changes made git history harder to read
```suggestion
        {crumbs.slice(0, totalStackNumber).map((crumb, index) => (
          <IconNode
            color={colors[index] || crumb.color}
            key={crumb.id}
            stack={{totalStackNumber, index}}
          />
        ))}
```