Browse Source

feat(replays): Replay layout move breadcrumbs and tags to sidebar (#57673)

Added the breadcrumbs and tags tabs to the sidebar and removed the
bottom panel.

Before:
<img width="1417" alt="Screenshot 2023-10-06 at 11 34 28 AM"
src="https://github.com/getsentry/sentry/assets/55311782/0ee5b629-198f-462c-adfa-695ad0a705f7">

After:
<img width="1480" alt="Screenshot 2023-10-06 at 11 36 05 AM"
src="https://github.com/getsentry/sentry/assets/55311782/9a969b5a-97c6-45d9-8645-596414368057">

Relates to https://github.com/getsentry/team-replay/issues/197
Catherine Lee 1 year ago
parent
commit
e10a13a8e0

+ 8 - 8
static/app/utils/replays/hooks/useActiveReplayTab.spec.tsx

@@ -38,10 +38,10 @@ describe('useActiveReplayTab', () => {
     mockPush.mockReset();
   });
 
-  it('should use Console as a default', () => {
+  it('should use Breadcrumbs as a default', () => {
     const {result} = reactHooks.renderHook(useActiveReplayTab);
 
-    expect(result.current.getActiveTab()).toBe(TabKey.CONSOLE);
+    expect(result.current.getActiveTab()).toBe(TabKey.BREADCRUMBS);
   });
 
   it('should use DOM as a default, when there is a click search in the url', () => {
@@ -54,7 +54,7 @@ describe('useActiveReplayTab', () => {
 
   it('should allow case-insensitive tab names', () => {
     const {result} = reactHooks.renderHook(useActiveReplayTab);
-    expect(result.current.getActiveTab()).toBe(TabKey.CONSOLE);
+    expect(result.current.getActiveTab()).toBe(TabKey.BREADCRUMBS);
 
     result.current.setActiveTab('nEtWoRk');
     expect(mockPush).toHaveBeenLastCalledWith({
@@ -65,12 +65,12 @@ describe('useActiveReplayTab', () => {
 
   it('should set the default tab if the name is invalid', () => {
     const {result} = reactHooks.renderHook(useActiveReplayTab);
-    expect(result.current.getActiveTab()).toBe(TabKey.CONSOLE);
+    expect(result.current.getActiveTab()).toBe(TabKey.BREADCRUMBS);
 
     result.current.setActiveTab('foo bar');
     expect(mockPush).toHaveBeenLastCalledWith({
       pathname: '',
-      query: {t_main: TabKey.CONSOLE},
+      query: {t_main: TabKey.BREADCRUMBS},
     });
   });
 
@@ -80,12 +80,12 @@ describe('useActiveReplayTab', () => {
     });
 
     const {result} = reactHooks.renderHook(useActiveReplayTab);
-    expect(result.current.getActiveTab()).toBe(TabKey.CONSOLE);
+    expect(result.current.getActiveTab()).toBe(TabKey.BREADCRUMBS);
 
     result.current.setActiveTab(TabKey.PERF);
     expect(mockPush).toHaveBeenLastCalledWith({
       pathname: '',
-      query: {t_main: TabKey.CONSOLE},
+      query: {t_main: TabKey.BREADCRUMBS},
     });
   });
 
@@ -94,7 +94,7 @@ describe('useActiveReplayTab', () => {
       features: ['session-replay-trace-table'],
     });
     const {result} = reactHooks.renderHook(useActiveReplayTab);
-    expect(result.current.getActiveTab()).toBe(TabKey.CONSOLE);
+    expect(result.current.getActiveTab()).toBe(TabKey.BREADCRUMBS);
 
     result.current.setActiveTab(TabKey.PERF);
     expect(mockPush).toHaveBeenLastCalledWith({

+ 3 - 1
static/app/utils/replays/hooks/useActiveReplayTab.tsx

@@ -9,12 +9,14 @@ import useUrlParams from 'sentry/utils/useUrlParams';
 
 export enum TabKey {
   A11Y = 'a11y',
+  BREADCRUMBS = 'breadcrumbs',
   CONSOLE = 'console',
   DOM = 'dom',
   ERRORS = 'errors',
   MEMORY = 'memory',
   NETWORK = 'network',
   PERF = 'perf',
+  TAGS = 'tags',
   TRACE = 'trace',
 }
 
@@ -46,7 +48,7 @@ function useDefaultTab() {
     return TabKey.DOM;
   }
 
-  return TabKey.CONSOLE;
+  return TabKey.BREADCRUMBS;
 }
 
 function useActiveReplayTab() {

+ 14 - 1
static/app/views/replays/detail/layout/focusArea.tsx

@@ -1,8 +1,10 @@
 import styled from '@emotion/styled';
 
+import {useReplayContext} from 'sentry/components/replays/replayContext';
 import {space} from 'sentry/styles/space';
 import useActiveReplayTab, {TabKey} from 'sentry/utils/replays/hooks/useActiveReplayTab';
 import A11y from 'sentry/views/replays/detail/accessibility/index';
+import Breadcrumbs from 'sentry/views/replays/detail/breadcrumbs';
 import Console from 'sentry/views/replays/detail/console';
 import DomMutations from 'sentry/views/replays/detail/domMutations';
 import DomNodesChart from 'sentry/views/replays/detail/domNodesChart';
@@ -11,12 +13,14 @@ import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
 import MemoryChart from 'sentry/views/replays/detail/memoryChart';
 import NetworkList from 'sentry/views/replays/detail/network';
 import PerfTable from 'sentry/views/replays/detail/perfTable/index';
+import TagPanel from 'sentry/views/replays/detail/tagPanel';
 import Trace from 'sentry/views/replays/detail/trace/index';
 
 type Props = {};
 
 function FocusArea({}: Props) {
   const {getActiveTab} = useActiveReplayTab();
+  const {replay} = useReplayContext();
 
   switch (getActiveTab()) {
     case TabKey.A11Y:
@@ -39,8 +43,17 @@ function FocusArea({}: Props) {
         </MemoryTabWrapper>
       );
     case TabKey.CONSOLE:
-    default: {
       return <Console />;
+    case TabKey.TAGS:
+      return <TagPanel />;
+    case TabKey.BREADCRUMBS:
+    default: {
+      return (
+        <Breadcrumbs
+          frames={replay?.getChapterFrames()}
+          startTimestampMs={replay?.getReplay()?.started_at?.getTime() || 0}
+        />
+      );
     }
   }
 }

+ 2 - 0
static/app/views/replays/detail/layout/focusTabs.tsx

@@ -18,6 +18,7 @@ function getReplayTabs(organization: Organization): Record<TabKey, ReactNode> {
   const hasPerfTab = organization.features.includes('session-replay-trace-table');
 
   return {
+    [TabKey.BREADCRUMBS]: t('Breadcrumbs'),
     [TabKey.CONSOLE]: t('Console'),
     [TabKey.NETWORK]: t('Network'),
     [TabKey.ERRORS]: (
@@ -48,6 +49,7 @@ function getReplayTabs(organization: Organization): Record<TabKey, ReactNode> {
       </Fragment>
     ) : null,
     [TabKey.MEMORY]: t('Memory'),
+    [TabKey.TAGS]: t('Tags'),
   };
 }
 

+ 17 - 55
static/app/views/replays/detail/layout/index.tsx

@@ -12,16 +12,12 @@ import FluidHeight from 'sentry/views/replays/detail/layout/fluidHeight';
 import FluidPanel from 'sentry/views/replays/detail/layout/fluidPanel';
 import FocusArea from 'sentry/views/replays/detail/layout/focusArea';
 import FocusTabs from 'sentry/views/replays/detail/layout/focusTabs';
-import SidebarArea from 'sentry/views/replays/detail/layout/sidebarArea';
-import SideTabs from 'sentry/views/replays/detail/layout/sideTabs';
 import SplitPanel from 'sentry/views/replays/detail/layout/splitPanel';
 
-const MIN_VIDEO_WIDTH = 325;
 const MIN_CONTENT_WIDTH = 340;
 const MIN_SIDEBAR_WIDTH = 325;
 const MIN_VIDEO_HEIGHT = 200;
 const MIN_CONTENT_HEIGHT = 180;
-const MIN_SIDEBAR_HEIGHT = 120;
 
 const DIVIDER_SIZE = 16;
 
@@ -69,14 +65,6 @@ function ReplayLayout({layout = LayoutKey.TOPBAR}: Props) {
     </ErrorBoundary>
   );
 
-  const sidebarArea = (
-    <ErrorBoundary mini>
-      <FluidPanel title={<SmallMarginSideTabs />}>
-        <SidebarArea />
-      </FluidPanel>
-    </ErrorBoundary>
-  );
-
   const hasSize = width + height > 0;
 
   if (layout === LayoutKey.NO_VIDEO) {
@@ -84,19 +72,7 @@ function ReplayLayout({layout = LayoutKey.TOPBAR}: Props) {
       <BodyContent>
         {timeline}
         <FluidHeight ref={measureRef}>
-          {hasSize ? (
-            <SplitPanel
-              key={layout}
-              availableSize={width}
-              left={{
-                content: focusArea,
-                default: (width - DIVIDER_SIZE) * 0.9,
-                min: 0,
-                max: width - DIVIDER_SIZE,
-              }}
-              right={sidebarArea}
-            />
-          ) : null}
+          {hasSize ? <PanelContainer key={layout}>{focusArea}</PanelContainer> : null}
         </FluidHeight>
       </BodyContent>
     );
@@ -112,22 +88,10 @@ function ReplayLayout({layout = LayoutKey.TOPBAR}: Props) {
               key={layout}
               availableSize={width}
               left={{
-                content: (
-                  <SplitPanel
-                    key={layout}
-                    availableSize={height}
-                    top={{
-                      content: video,
-                      default: (height - DIVIDER_SIZE) * 0.65,
-                      min: MIN_CONTENT_HEIGHT,
-                      max: height - DIVIDER_SIZE - MIN_SIDEBAR_HEIGHT,
-                    }}
-                    bottom={sidebarArea}
-                  />
-                ),
-                default: (width - DIVIDER_SIZE) * 0.5,
+                content: <PanelContainer key={layout}>{video}</PanelContainer>,
+                default: width * 0.5,
                 min: MIN_SIDEBAR_WIDTH,
-                max: width - DIVIDER_SIZE - MIN_CONTENT_WIDTH,
+                max: width - MIN_CONTENT_WIDTH,
               }}
               right={focusArea}
             />
@@ -147,18 +111,7 @@ function ReplayLayout({layout = LayoutKey.TOPBAR}: Props) {
             key={layout}
             availableSize={height}
             top={{
-              content: (
-                <SplitPanel
-                  availableSize={width}
-                  left={{
-                    content: video,
-                    default: (width - DIVIDER_SIZE) * 0.5,
-                    min: MIN_VIDEO_WIDTH,
-                    max: width - DIVIDER_SIZE - MIN_SIDEBAR_WIDTH,
-                  }}
-                  right={sidebarArea}
-                />
-              ),
+              content: <PanelContainer>{video}</PanelContainer>,
               default: (height - DIVIDER_SIZE) * 0.5,
               min: MIN_VIDEO_HEIGHT,
               max: height - DIVIDER_SIZE - MIN_CONTENT_HEIGHT,
@@ -185,9 +138,6 @@ const BodyContent = styled('main')`
 const SmallMarginFocusTabs = styled(FocusTabs)`
   margin-bottom: ${space(1)};
 `;
-const SmallMarginSideTabs = styled(SideTabs)`
-  margin-bottom: ${space(1)};
-`;
 
 const VideoSection = styled(FluidHeight)`
   background: ${p => p.theme.background};
@@ -198,4 +148,16 @@ const VideoSection = styled(FluidHeight)`
   }
 `;
 
+const PanelContainer = styled('div')`
+  width: 100%;
+  height: 100%;
+
+  position: relative;
+  display: grid;
+  overflow: auto;
+
+  &.disable-iframe-pointer iframe {
+    pointer-events: none !important;
+  }
+`;
 export default ReplayLayout;

+ 0 - 42
static/app/views/replays/detail/layout/sideTabs.tsx

@@ -1,42 +0,0 @@
-import queryString from 'query-string';
-
-import ListLink from 'sentry/components/links/listLink';
-import ScrollableTabs from 'sentry/components/replays/scrollableTabs';
-import {t} from 'sentry/locale';
-import {useLocation} from 'sentry/utils/useLocation';
-import useUrlParams from 'sentry/utils/useUrlParams';
-
-const TABS = {
-  crumbs: t('Breadcrumbs'),
-  tags: t('Tags'),
-};
-
-type Props = {
-  className?: string;
-};
-
-function SideTabs({className}: Props) {
-  const {pathname, query} = useLocation();
-  const {getParamValue, setParamValue} = useUrlParams('t_side', 'crumbs');
-  const activeTab = getParamValue().toLowerCase();
-
-  return (
-    <ScrollableTabs className={className} underlined>
-      {Object.entries(TABS).map(([tab, label]) => (
-        <ListLink
-          key={tab}
-          isActive={() => tab === activeTab}
-          to={`${pathname}?${queryString.stringify({...query, t_side: tab})}`}
-          onClick={e => {
-            e.preventDefault();
-            setParamValue(tab);
-          }}
-        >
-          {label}
-        </ListLink>
-      ))}
-    </ScrollableTabs>
-  );
-}
-
-export default SideTabs;

+ 0 - 24
static/app/views/replays/detail/layout/sidebarArea.tsx

@@ -1,24 +0,0 @@
-import {useReplayContext} from 'sentry/components/replays/replayContext';
-import useUrlParams from 'sentry/utils/useUrlParams';
-import Breadcrumbs from 'sentry/views/replays/detail/breadcrumbs';
-import TagPanel from 'sentry/views/replays/detail/tagPanel';
-
-function SidebarArea() {
-  const {getParamValue} = useUrlParams('t_side', 'crumbs');
-  const {replay} = useReplayContext();
-
-  switch (getParamValue().toLowerCase()) {
-    case 'tags':
-      return <TagPanel />;
-    case 'crumbs':
-    default:
-      return (
-        <Breadcrumbs
-          frames={replay?.getChapterFrames()}
-          startTimestampMs={replay?.getReplay()?.started_at?.getTime() || 0}
-        />
-      );
-  }
-}
-
-export default SidebarArea;