diff --git a/src/sentry/static/sentry/app/components/events/interfaces/spans/header.tsx b/src/sentry/static/sentry/app/components/events/interfaces/spans/header.tsx index 37fc7a4c1d604d..319e7635bd995c 100644 --- a/src/sentry/static/sentry/app/components/events/interfaces/spans/header.tsx +++ b/src/sentry/static/sentry/app/components/events/interfaces/spans/header.tsx @@ -2,9 +2,13 @@ import React from 'react'; import styled from '@emotion/styled'; import space from 'app/styles/space'; +import {SentryTransactionEvent} from 'app/types'; import * as CursorGuideHandler from './cursorGuideHandler'; +import * as DividerHandlerManager from './dividerHandlerManager'; import {DragManagerChildrenProps} from './dragManager'; +import MeasurementsPanel from './measurementsPanel'; +import * as ScrollbarManager from './scrollbarManager'; import {zIndex} from './styles'; import { ParsedTraceType, @@ -24,17 +28,21 @@ import { toPercent, } from './utils'; -export const MINIMAP_CONTAINER_HEIGHT = 106; export const MINIMAP_SPAN_BAR_HEIGHT = 4; const MINIMAP_HEIGHT = 120; export const NUM_OF_SPANS_FIT_IN_MINI_MAP = MINIMAP_HEIGHT / MINIMAP_SPAN_BAR_HEIGHT; const TIME_AXIS_HEIGHT = 20; const VIEW_HANDLE_HEIGHT = 18; +const SECONDARY_HEADER_HEIGHT = 20; +export const MINIMAP_CONTAINER_HEIGHT = + MINIMAP_HEIGHT + TIME_AXIS_HEIGHT + SECONDARY_HEADER_HEIGHT + 1; type PropType = { minimapInteractiveRef: React.RefObject; + virtualScrollBarContainerRef: React.RefObject; dragProps: DragManagerChildrenProps; trace: ParsedTraceType; + event: SentryTransactionEvent; }; class TraceViewHeader extends React.Component { @@ -266,6 +274,65 @@ class TraceViewHeader extends React.Component { ); } + generateBounds() { + const {dragProps, trace} = this.props; + + return boundsGenerator({ + traceStartTimestamp: trace.traceStartTimestamp, + traceEndTimestamp: trace.traceEndTimestamp, + viewStart: dragProps.viewWindowStart, + viewEnd: dragProps.viewWindowEnd, + }); + } + + renderSecondaryHeader() { + const {event} = this.props; + + const hasMeasurements = Object.keys(event.measurements ?? {}).length > 0; + + return ( + + {dividerHandlerChildrenProps => { + const {dividerPosition} = dividerHandlerChildrenProps; + + return ( + + + + {({virtualScrollbarRef, onDragStart}) => { + return ( + + + + ); + }} + + + + {hasMeasurements ? ( + + ) : null} + + ); + }} + + ); + } + render() { return ( @@ -322,6 +389,7 @@ class TraceViewHeader extends React.Component { )} + {this.renderSecondaryHeader()} ); } @@ -484,7 +552,7 @@ const TimeAxis = styled('div')` width: 100%; position: absolute; left: 0; - bottom: 0; + top: ${MINIMAP_HEIGHT}px; border-top: 1px solid ${p => p.theme.border}; height: ${TIME_AXIS_HEIGHT}px; background-color: ${p => p.theme.background}; @@ -580,7 +648,7 @@ const HeaderContainer = styled('div')` z-index: ${zIndex.minimapContainer}; background-color: ${p => p.theme.background}; border-bottom: 1px solid ${p => p.theme.border}; - height: ${MINIMAP_HEIGHT + TIME_AXIS_HEIGHT + 1}px; + height: ${MINIMAP_CONTAINER_HEIGHT}px; `; const MinimapBackground = styled('div')` @@ -683,4 +751,52 @@ const WindowSelection = styled('div')` background-color: rgba(69, 38, 80, 0.1); `; +const SecondaryHeader = styled('div')` + position: absolute; + top: ${MINIMAP_HEIGHT + TIME_AXIS_HEIGHT}px; + left: 0; + height: ${TIME_AXIS_HEIGHT}px; + width: 100%; + background-color: ${p => p.theme.backgroundSecondary}; + display: flex; + border-top: 1px solid ${p => p.theme.border}; +`; + +const DividerSpacer = styled('div')` + width: 1px; + background-color: ${p => p.theme.gray200}; +`; + +const ScrollBarContainer = styled('div')` + display: flex; + align-items: center; + width: 100%; + height: ${SECONDARY_HEADER_HEIGHT}px; + left: 0; + bottom: 0; + & > div[data-type='virtual-scrollbar'].dragging > div { + background-color: ${p => p.theme.gray500}; + cursor: grabbing; + } +`; + +const VirtualScrollBar = styled('div')` + height: 8px; + width: 0; + padding-left: 4px; + padding-right: 4px; + position: relative; + top: 0; + left: 0; + cursor: grab; +`; + +const VirtualScrollBarGrip = styled('div')` + height: 8px; + width: 100%; + border-radius: 20px; + transition: background-color 150ms ease; + background-color: rgba(48, 40, 57, 0.5); +`; + export default TraceViewHeader; diff --git a/src/sentry/static/sentry/app/components/events/interfaces/spans/measurementsManager.tsx b/src/sentry/static/sentry/app/components/events/interfaces/spans/measurementsManager.tsx deleted file mode 100644 index 91414c6f289329..00000000000000 --- a/src/sentry/static/sentry/app/components/events/interfaces/spans/measurementsManager.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; - -export type MeasurementsManagerChildrenProps = { - hoveringMeasurement: (measurementName: string) => void; - notHovering: () => void; - currentHoveredMeasurement: string | undefined; -}; - -const MeasurementsManagerContext = React.createContext({ - hoveringMeasurement: () => {}, - notHovering: () => {}, - currentHoveredMeasurement: undefined, -}); - -type Props = { - children: React.ReactNode; -}; - -type State = { - currentHoveredMeasurement: string | undefined; -}; - -export class Provider extends React.Component { - state: State = { - currentHoveredMeasurement: undefined, - }; - - hoveringMeasurement = (measurementName: string) => { - this.setState({ - currentHoveredMeasurement: measurementName, - }); - }; - - notHovering = () => { - this.setState({ - currentHoveredMeasurement: undefined, - }); - }; - - render() { - const childrenProps = { - hoveringMeasurement: this.hoveringMeasurement, - notHovering: this.notHovering, - currentHoveredMeasurement: this.state.currentHoveredMeasurement, - }; - - return ( - - {this.props.children} - - ); - } -} - -export const Consumer = MeasurementsManagerContext.Consumer; diff --git a/src/sentry/static/sentry/app/components/events/interfaces/spans/measurementsPanel.tsx b/src/sentry/static/sentry/app/components/events/interfaces/spans/measurementsPanel.tsx index 53a05897e1c255..10e35bdb0a9db0 100644 --- a/src/sentry/static/sentry/app/components/events/interfaces/spans/measurementsPanel.tsx +++ b/src/sentry/static/sentry/app/components/events/interfaces/spans/measurementsPanel.tsx @@ -9,7 +9,6 @@ import { WEB_VITAL_ACRONYMS, } from 'app/views/performance/transactionVitals/constants'; -import * as MeasurementsManager from './measurementsManager'; import { getMeasurementBounds, getMeasurements, @@ -47,8 +46,6 @@ class MeasurementsPanel extends React.PureComponent { const names = Object.keys(verticalMark.marks); - const hoverMeasurementName = names.join(''); - // generate vertical marker label const acronyms = names.map(name => WEB_VITAL_ACRONYMS[name]); const lastAcronym = acronyms.pop() as string; @@ -64,25 +61,13 @@ class MeasurementsPanel extends React.PureComponent { : lastName; return ( - - {({hoveringMeasurement, notHovering}) => { - return ( - { - notHovering(); - }} - onMouseOver={() => { - hoveringMeasurement(hoverMeasurementName); - }} - /> - ); - }} - + ); })} @@ -118,8 +103,6 @@ type LabelContainerProps = { left: string; label: string; tooltipLabel: string; - onMouseLeave: () => void; - onMouseOver: () => void; failedThreshold: boolean; }; @@ -145,14 +128,7 @@ class LabelContainer extends React.Component { elementDOMRef = React.createRef(); render() { - const { - left, - onMouseLeave, - onMouseOver, - label, - tooltipLabel, - failedThreshold, - } = this.props; + const {left, label, tooltipLabel, failedThreshold} = this.props; return ( { style={{ left: `clamp(calc(0.5 * ${this.state.width}px), ${left}, calc(100% - 0.5 * ${this.state.width}px))`, }} - onMouseLeave={() => { - onMouseLeave(); - }} - onMouseOver={() => { - onMouseOver(); - }} >