Browse Source

feat(trace-view): Add horizontal scrolling (#24982)

This adds the horizontal scrolling capabilities on the transaction tree.
Tony 3 years ago
parent
commit
c6674aec31

+ 5 - 5
src/sentry/static/sentry/app/components/events/interfaces/spans/header.tsx

@@ -859,7 +859,7 @@ const WindowSelection = styled('div')`
   opacity: 0.1;
 `;
 
-const SecondaryHeader = styled('div')`
+export const SecondaryHeader = styled('div')`
   position: absolute;
   top: ${MINIMAP_HEIGHT + TIME_AXIS_HEIGHT}px;
   left: 0;
@@ -870,12 +870,12 @@ const SecondaryHeader = styled('div')`
   border-top: 1px solid ${p => p.theme.border};
 `;
 
-const DividerSpacer = styled('div')`
+export const DividerSpacer = styled('div')`
   width: 1px;
   background-color: ${p => p.theme.border};
 `;
 
-const ScrollBarContainer = styled('div')`
+export const ScrollBarContainer = styled('div')`
   display: flex;
   align-items: center;
   width: 100%;
@@ -903,7 +903,7 @@ const RightSidePane = styled('div')`
   top: 0;
 `;
 
-const VirtualScrollBar = styled('div')`
+export const VirtualScrollBar = styled('div')`
   height: 8px;
   width: 0;
   padding-left: 4px;
@@ -914,7 +914,7 @@ const VirtualScrollBar = styled('div')`
   cursor: grab;
 `;
 
-const VirtualScrollBarGrip = styled('div')`
+export const VirtualScrollBarGrip = styled('div')`
   height: 8px;
   width: 100%;
   border-radius: 20px;

+ 5 - 3
src/sentry/static/sentry/app/components/events/interfaces/spans/scrollbarManager.tsx

@@ -47,7 +47,7 @@ const lerp = (start: number, end: number, needle: number) => {
 type Props = {
   children: React.ReactNode;
   dividerPosition: number;
-  dragProps: DragManagerChildrenProps;
+  dragProps?: DragManagerChildrenProps;
 
   // this is the DOM element where the drag events occur. it's also the reference point
   // for calculating the relative mouse x coordinate.
@@ -79,8 +79,10 @@ export class Provider extends React.Component<Props, State> {
       this.props.dividerPosition !== prevProps.dividerPosition;
 
     const viewWindowChanged =
-      prevProps.dragProps.viewWindowStart !== this.props.dragProps.viewWindowStart ||
-      prevProps.dragProps.viewWindowEnd !== this.props.dragProps.viewWindowEnd;
+      prevProps.dragProps &&
+      this.props.dragProps &&
+      (prevProps.dragProps.viewWindowStart !== this.props.dragProps.viewWindowStart ||
+        prevProps.dragProps.viewWindowEnd !== this.props.dragProps.viewWindowEnd);
 
     if (dividerPositionChanged || viewWindowChanged) {
       this.initializeScrollState();

+ 71 - 31
src/sentry/static/sentry/app/views/performance/traceDetails/content.tsx

@@ -5,6 +5,7 @@ import {Location} from 'history';
 
 import Alert from 'app/components/alert';
 import * as DividerHandlerManager from 'app/components/events/interfaces/spans/dividerHandlerManager';
+import * as ScrollbarManager from 'app/components/events/interfaces/spans/scrollbarManager';
 import FeatureBadge from 'app/components/featureBadge';
 import * as Layout from 'app/components/layouts/thirds';
 import LoadingError from 'app/components/loadingError';
@@ -21,17 +22,22 @@ import Breadcrumb from 'app/views/performance/breadcrumb';
 import {MetaData} from 'app/views/performance/transactionDetails/styles';
 
 import {
+  DividerSpacer,
+  ScrollbarContainer,
   SearchContainer,
   StyledPanel,
   StyledSearchBar,
   TraceDetailBody,
   TraceDetailHeader,
   TraceViewContainer,
+  TraceViewHeaderContainer,
   TransactionRowMessage,
+  VirtualScrollBar,
+  VirtualScrollBarGrip,
 } from './styles';
 import TransactionGroup from './transactionGroup';
 import {TraceInfo, TreeDepth} from './types';
-import {getTraceInfo, isRootTransaction} from './utils';
+import {getTraceInfo, isRootTransaction, toPercent} from './utils';
 
 type IndexedFusedTransaction = {
   transaction: TraceFullDetailed;
@@ -76,6 +82,7 @@ class TraceDetailsContent extends React.Component<Props, State> {
   };
 
   traceViewRef = React.createRef<HTMLDivElement>();
+  virtualScrollbarContainerRef = React.createRef<HTMLDivElement>();
 
   renderTraceLoading() {
     return <LoadingIndicator />;
@@ -432,36 +439,69 @@ class TraceDetailsContent extends React.Component<Props, State> {
 
     const traceView = (
       <TraceDetailBody>
-        <StyledPanel>
-          <DividerHandlerManager.Provider interactiveLayerRef={this.traceViewRef}>
-            <TraceViewContainer ref={this.traceViewRef}>
-              <TransactionGroup
-                location={location}
-                organization={organization}
-                traceInfo={traceInfo}
-                transaction={{
-                  traceSlug,
-                  generation: 0,
-                  'transaction.duration':
-                    traceInfo.endTimestamp - traceInfo.startTimestamp,
-                  children: traces,
-                  start_timestamp: traceInfo.startTimestamp,
-                  timestamp: traceInfo.endTimestamp,
-                }}
-                continuingDepths={[]}
-                isOrphan={false}
-                isLast={false}
-                index={0}
-                isVisible
-                renderedChildren={transactionGroups}
-              />
-              {this.renderInfoMessage({
-                isVisible: true,
-                numberOfHiddenTransactionsAbove,
-              })}
-            </TraceViewContainer>
-          </DividerHandlerManager.Provider>
-        </StyledPanel>
+        <DividerHandlerManager.Provider interactiveLayerRef={this.traceViewRef}>
+          <DividerHandlerManager.Consumer>
+            {({dividerPosition}) => (
+              <ScrollbarManager.Provider
+                dividerPosition={dividerPosition}
+                interactiveLayerRef={this.virtualScrollbarContainerRef}
+              >
+                <StyledPanel>
+                  <TraceViewHeaderContainer>
+                    <ScrollbarContainer
+                      ref={this.virtualScrollbarContainerRef}
+                      style={{
+                        // the width of this component is shrunk to compensate for half of the width of the divider line
+                        width: `calc(${toPercent(dividerPosition)} - 0.5px)`,
+                      }}
+                    >
+                      <ScrollbarManager.Consumer>
+                        {({virtualScrollbarRef, onDragStart}) => {
+                          return (
+                            <VirtualScrollBar
+                              data-type="virtual-scrollbar"
+                              ref={virtualScrollbarRef}
+                              onMouseDown={onDragStart}
+                            >
+                              <VirtualScrollBarGrip />
+                            </VirtualScrollBar>
+                          );
+                        }}
+                      </ScrollbarManager.Consumer>
+                    </ScrollbarContainer>
+                    <DividerSpacer />
+                  </TraceViewHeaderContainer>
+                  <TraceViewContainer ref={this.traceViewRef}>
+                    <TransactionGroup
+                      location={location}
+                      organization={organization}
+                      traceInfo={traceInfo}
+                      transaction={{
+                        traceSlug,
+                        generation: 0,
+                        'transaction.duration':
+                          traceInfo.endTimestamp - traceInfo.startTimestamp,
+                        children: traces,
+                        start_timestamp: traceInfo.startTimestamp,
+                        timestamp: traceInfo.endTimestamp,
+                      }}
+                      continuingDepths={[]}
+                      isOrphan={false}
+                      isLast={false}
+                      index={0}
+                      isVisible
+                      renderedChildren={transactionGroups}
+                    />
+                    {this.renderInfoMessage({
+                      isVisible: true,
+                      numberOfHiddenTransactionsAbove,
+                    })}
+                  </TraceViewContainer>
+                </StyledPanel>
+              </ScrollbarManager.Provider>
+            )}
+          </DividerHandlerManager.Consumer>
+        </DividerHandlerManager.Provider>
       </TraceDetailBody>
     );
 

+ 15 - 0
src/sentry/static/sentry/app/views/performance/traceDetails/styles.tsx

@@ -3,6 +3,7 @@ import styled from '@emotion/styled';
 import {Location} from 'history';
 
 import EventTagsPill from 'app/components/events/eventTags/eventTagsPill';
+import {SecondaryHeader} from 'app/components/events/interfaces/spans/header';
 import {SpanBarTitle} from 'app/components/events/interfaces/spans/spanBar';
 import {SpanRow} from 'app/components/events/interfaces/spans/styles';
 import {Panel} from 'app/components/panels';
@@ -16,6 +17,13 @@ import {TraceFullDetailed} from 'app/utils/performance/quickTrace/types';
 import {appendTagCondition} from 'app/utils/queryString';
 import {transactionSummaryRouteWithQuery} from 'app/views/performance/transactionSummary/utils';
 
+export {
+  DividerSpacer,
+  ScrollBarContainer as ScrollbarContainer,
+  VirtualScrollBar,
+  VirtualScrollBarGrip,
+} from 'app/components/events/interfaces/spans/header';
+
 export {
   ConnectorBar,
   DividerLine,
@@ -52,6 +60,13 @@ export const StyledSearchBar = styled(SearchBar)`
   flex-grow: 1;
 `;
 
+export const TraceViewHeaderContainer = styled(SecondaryHeader)`
+  position: static;
+  top: auto;
+  border-top: none;
+  border-bottom: 1px solid ${p => p.theme.border};
+`;
+
 export const TraceDetailHeader = styled('div')`
   display: grid;
   grid-template-columns: repeat(3, 1fr);

+ 23 - 8
src/sentry/static/sentry/app/views/performance/traceDetails/transactionBar.tsx

@@ -4,6 +4,7 @@ import {Location} from 'history';
 
 import Count from 'app/components/count';
 import * as DividerHandlerManager from 'app/components/events/interfaces/spans/dividerHandlerManager';
+import * as ScrollbarManager from 'app/components/events/interfaces/spans/scrollbarManager';
 import ProjectBadge from 'app/components/idBadge/projectBadge';
 import {Organization} from 'app/types';
 import {TraceFullDetailed} from 'app/utils/performance/quickTrace/types';
@@ -192,7 +193,10 @@ class TransactionBar extends React.Component<Props, State> {
     );
   }
 
-  renderTitle() {
+  renderTitle(
+    scrollbarManagerChildrenProps: ScrollbarManager.ScrollbarManagerChildrenProps
+  ) {
+    const {generateContentSpanBarRef} = scrollbarManagerChildrenProps;
     const {organization, transaction} = this.props;
     const left = this.getCurrentOffset();
 
@@ -231,7 +235,7 @@ class TransactionBar extends React.Component<Props, State> {
     );
 
     return (
-      <TransactionBarTitleContainer>
+      <TransactionBarTitleContainer ref={generateContentSpanBarRef()}>
         {this.renderToggle()}
         <TransactionBarTitle
           style={{
@@ -356,8 +360,10 @@ class TransactionBar extends React.Component<Props, State> {
 
   renderHeader({
     dividerHandlerChildrenProps,
+    scrollbarManagerChildrenProps,
   }: {
     dividerHandlerChildrenProps: DividerHandlerManager.DividerHandlerManagerChildrenProps;
+    scrollbarManagerChildrenProps: ScrollbarManager.ScrollbarManagerChildrenProps;
   }) {
     const {index} = this.props;
     const {showDetail} = this.state;
@@ -374,7 +380,7 @@ class TransactionBar extends React.Component<Props, State> {
           showDetail={showDetail}
           onClick={this.toggleDisplayDetail}
         >
-          {this.renderTitle()}
+          {this.renderTitle(scrollbarManagerChildrenProps)}
         </TransactionRowCell>
         {this.renderDivider(dividerHandlerChildrenProps)}
         <TransactionRowCell
@@ -404,11 +410,20 @@ class TransactionBar extends React.Component<Props, State> {
         showBorder={showDetail}
         cursor={isTraceFullDetailed(transaction) ? 'pointer' : 'default'}
       >
-        <DividerHandlerManager.Consumer>
-          {dividerHandlerChildrenProps =>
-            this.renderHeader({dividerHandlerChildrenProps})
-          }
-        </DividerHandlerManager.Consumer>
+        <ScrollbarManager.Consumer>
+          {scrollbarManagerChildrenProps => {
+            return (
+              <DividerHandlerManager.Consumer>
+                {dividerHandlerChildrenProps =>
+                  this.renderHeader({
+                    dividerHandlerChildrenProps,
+                    scrollbarManagerChildrenProps,
+                  })
+                }
+              </DividerHandlerManager.Consumer>
+            );
+          }}
+        </ScrollbarManager.Consumer>
         {isTraceFullDetailed(transaction) && isVisible && showDetail && (
           <TransactionDetail
             location={location}