import {Component, createRef} from 'react'; import styled from '@emotion/styled'; import {toPercent} from 'sentry/components/performance/waterfall/utils'; import Tooltip from 'sentry/components/tooltip'; import space from 'sentry/styles/space'; import {EventTransaction} from 'sentry/types/event'; import {defined} from 'sentry/utils'; import {WEB_VITAL_DETAILS} from 'sentry/utils/performance/vitals/constants'; import {Vital} from 'sentry/utils/performance/vitals/types'; import { getMeasurementBounds, getMeasurements, SpanBoundsType, SpanGeneratedBoundsType, } from './utils'; type Props = { dividerPosition: number; event: EventTransaction; generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType; }; function MeasurementsPanel(props: Props) { const {event, generateBounds, dividerPosition} = props; const measurements = getMeasurements(event, generateBounds); return ( {Array.from(measurements.values()).map(verticalMark => { const mark = Object.values(verticalMark.marks)[0]; const {timestamp} = mark; const bounds = getMeasurementBounds(timestamp, generateBounds); const shouldDisplay = defined(bounds.left) && defined(bounds.width); if (!shouldDisplay || !bounds.isSpanVisibleInView) { return null; } // Measurements are referred to by their full name `measurements.` // here but are stored using their abbreviated name ``. Make sure // to convert it appropriately. const vitals: Vital[] = Object.keys(verticalMark.marks).map( name => WEB_VITAL_DETAILS[`measurements.${name}`] ); if (vitals.length > 1) { return ( ); } return ( ); })} ); } const Container = styled('div')` position: relative; overflow: hidden; height: 20px; `; const StyledMultiLabelContainer = styled('div')` transform: translateX(-50%); position: absolute; display: flex; top: 0; height: 100%; user-select: none; white-space: nowrap; `; const StyledLabelContainer = styled('div')` position: absolute; top: 0; height: 100%; user-select: none; white-space: nowrap; `; const Label = styled('div')<{ failedThreshold: boolean; isSingleLabel?: boolean; }>` transform: ${p => (p.isSingleLabel ? `translate(-50%, 15%)` : `translateY(15%)`)}; font-size: ${p => p.theme.fontSizeExtraSmall}; font-weight: 600; color: ${p => (p.failedThreshold ? `${p.theme.red300}` : `${p.theme.gray500}`)}; background: ${p => p.theme.white}; border: 1px solid; border-color: ${p => (p.failedThreshold ? p.theme.red300 : p.theme.gray100)}; border-radius: ${p => p.theme.borderRadius}; height: 75%; display: flex; justify-content: center; align-items: center; padding: ${space(0.25)}; margin-right: ${space(0.25)}; `; export default MeasurementsPanel; type LabelContainerProps = { failedThreshold: boolean; left: string; vital: Vital; }; type LabelContainerState = { width: number; }; class LabelContainer extends Component { state: LabelContainerState = { width: 1, }; componentDidMount() { const {current} = this.elementDOMRef; if (current) { // eslint-disable-next-line react/no-did-mount-set-state this.setState({ width: current.clientWidth, }); } } elementDOMRef = createRef(); render() { const {left, failedThreshold, vital} = this.props; return ( ); } } type MultiLabelContainerProps = Omit & { vitals: Vital[]; }; class MultiLabelContainer extends Component { state: LabelContainerState = { width: 1, }; componentDidMount() { const {current} = this.elementDOMRef; if (current) { // eslint-disable-next-line react/no-did-mount-set-state this.setState({ width: current.clientWidth, }); } } elementDOMRef = createRef(); render() { const {left, failedThreshold, vitals} = this.props; return ( {vitals.map(vital => ( ))} ); } }