Files
2024-02-15 17:01:50 +00:00

216 lines
5.5 KiB
TypeScript

import {Component, createRef} from 'react';
import styled from '@emotion/styled';
import {Tooltip} from 'sentry/components/tooltip';
import {space} from 'sentry/styles/space';
import {defined} from 'sentry/utils';
import toPercent from 'sentry/utils/number/toPercent';
import {VITAL_DETAILS} from 'sentry/utils/performance/vitals/constants';
import type {Vital} from 'sentry/utils/performance/vitals/types';
import type {SpanBoundsType, SpanGeneratedBoundsType, VerticalMark} from './utils';
import {getMeasurementBounds} from './utils';
type Props = {
dividerPosition: number;
generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType;
measurements: Map<number, VerticalMark>;
};
type VitalLabel = {
isPoorValue: boolean;
vital: Vital;
};
function MeasurementsPanel(props: Props) {
const {measurements, generateBounds, dividerPosition} = props;
return (
<Container
style={{
// the width of this component is shrunk to compensate for half of the width of the divider line
width: `calc(${toPercent(1 - dividerPosition)} - 0.5px)`,
}}
>
{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;
}
const vitalLabels: VitalLabel[] = Object.keys(verticalMark.marks).map(name => ({
vital: VITAL_DETAILS[`measurements.${name}`],
isPoorValue: verticalMark.marks[name].failedThreshold,
}));
if (vitalLabels.length > 1) {
return (
<MultiLabelContainer
key={String(timestamp)}
left={toPercent(bounds.left || 0)}
vitalLabels={vitalLabels}
/>
);
}
return (
<LabelContainer
key={String(timestamp)}
left={toPercent(bounds.left || 0)}
vitalLabel={vitalLabels[0]}
/>
);
})}
</Container>
);
}
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.errorText}` : `${p.theme.textColor}`)};
background: ${p => p.theme.background};
border: 1px solid;
border-color: ${p => (p.failedThreshold ? p.theme.red300 : p.theme.gray200)};
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 = {
left: string;
vitalLabel: VitalLabel;
};
type LabelContainerState = {
width: number;
};
class LabelContainer extends Component<LabelContainerProps> {
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<HTMLDivElement>();
render() {
const {left, vitalLabel} = this.props;
return (
<StyledLabelContainer
ref={this.elementDOMRef}
style={{
left: `clamp(calc(0.5 * ${this.state.width}px), ${left}, calc(100% - 0.5 * ${this.state.width}px))`,
}}
>
<Label failedThreshold={vitalLabel.isPoorValue} isSingleLabel>
<Tooltip
title={vitalLabel.vital.name}
position="top"
containerDisplayMode="inline-block"
>
{vitalLabel.vital.acronym}
</Tooltip>
</Label>
</StyledLabelContainer>
);
}
}
type MultiLabelContainerProps = {
left: string;
vitalLabels: VitalLabel[];
};
class MultiLabelContainer extends Component<MultiLabelContainerProps> {
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<HTMLDivElement>();
render() {
const {left, vitalLabels} = this.props;
return (
<StyledMultiLabelContainer
ref={this.elementDOMRef}
style={{
left: `clamp(calc(0.5 * ${this.state.width}px), ${left}, calc(100% - 0.5 * ${this.state.width}px))`,
}}
>
{vitalLabels.map(label => (
<Label failedThreshold={label.isPoorValue} key={`${label.vital.name}-label`}>
<Tooltip
title={label.vital.name}
position="top"
containerDisplayMode="inline-block"
>
{label.vital.acronym}
</Tooltip>
</Label>
))}
</StyledMultiLabelContainer>
);
}
}