import {Fragment} from 'react';
import styled from '@emotion/styled';
import {SectionHeading} from 'sentry/components/charts/styles';
import {Panel} from 'sentry/components/panels';
import {Tooltip} from 'sentry/components/tooltip';
import {IconFire, IconWarning} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {Event} from 'sentry/types/event';
import {defined} from 'sentry/utils';
import {formattedValue} from 'sentry/utils/measurements/index';
import {
MOBILE_VITAL_DETAILS,
WEB_VITAL_DETAILS,
} from 'sentry/utils/performance/vitals/constants';
import {Vital} from 'sentry/utils/performance/vitals/types';
import {IconSize} from 'sentry/utils/theme';
function isOutdatedSdk(event: Event): boolean {
if (!event.sdk?.version) {
return false;
}
const sdkVersion = event.sdk.version;
return (
sdkVersion.startsWith('5.26.') ||
sdkVersion.startsWith('5.27.0') ||
sdkVersion.startsWith('5.27.1') ||
sdkVersion.startsWith('5.27.2')
);
}
type Props = {
event: Event;
};
export default function EventVitals({event}: Props) {
return (
);
}
function WebVitals({event}: Props) {
const measurementNames = Object.keys(event.measurements ?? {})
.filter(name => Boolean(WEB_VITAL_DETAILS[`measurements.${name}`]))
.sort();
if (measurementNames.length === 0) {
return null;
}
return (
{t('Web Vitals')}
{isOutdatedSdk(event) && (
)}
{measurementNames.map(name => {
// Measurements are referred to by their full name `measurements.`
// here but are stored using their abbreviated name ``. Make sure
// to convert it appropriately.
const measurement = `measurements.${name}`;
const vital = WEB_VITAL_DETAILS[measurement];
return ;
})}
);
}
function MobileVitals({event}: Props) {
const measurementNames = Object.keys(event.measurements ?? {})
.filter(name => Boolean(MOBILE_VITAL_DETAILS[`measurements.${name}`]))
.sort();
if (measurementNames.length === 0) {
return null;
}
return (
{t('Mobile Vitals')}
{measurementNames.map(name => {
// Measurements are referred to by their full name `measurements.`
// here but are stored using their abbreviated name ``. Make sure
// to convert it appropriately.
const measurement = `measurements.${name}`;
const vital = MOBILE_VITAL_DETAILS[measurement];
return ;
})}
);
}
type EventVitalProps = Props & {
name: string;
vital?: Vital;
};
function EventVital({event, name, vital}: EventVitalProps) {
const value = event.measurements?.[name].value ?? null;
if (value === null || !vital) {
return null;
}
const failedThreshold = defined(vital.poorThreshold) && value >= vital.poorThreshold;
const currentValue = formattedValue(vital, value);
const thresholdValue = formattedValue(vital, vital?.poorThreshold ?? 0);
return (
{vital.name ?? name}
{failedThreshold ? (
) : null}
{currentValue}
);
}
const Measurements = styled('div')`
display: grid;
grid-column-gap: ${space(1)};
`;
const Container = styled('div')`
font-size: ${p => p.theme.fontSizeMedium};
margin-bottom: ${space(4)};
`;
const StyledPanel = styled(Panel)<{failedThreshold: boolean}>`
padding: ${space(1)} ${space(1.5)};
margin-bottom: ${space(1)};
${p => p.failedThreshold && `border: 1px solid ${p.theme.red300};`}
`;
const Name = styled('div')``;
const ValueRow = styled('div')`
display: flex;
align-items: center;
`;
const WarningIconContainer = styled('span')<{size: IconSize | string}>`
display: inline-block;
height: ${p => p.theme.iconSizes[p.size] ?? p.size};
line-height: ${p => p.theme.iconSizes[p.size] ?? p.size};
margin-left: ${space(0.5)};
color: ${p => p.theme.errorText};
`;
const FireIconContainer = styled('span')<{size: IconSize | string}>`
display: inline-block;
height: ${p => p.theme.iconSizes[p.size] ?? p.size};
line-height: ${p => p.theme.iconSizes[p.size] ?? p.size};
margin-right: ${space(0.5)};
color: ${p => p.theme.errorText};
`;
const Value = styled('span')<{failedThreshold: boolean}>`
font-size: ${p => p.theme.fontSizeExtraLarge};
${p => p.failedThreshold && `color: ${p.theme.errorText};`}
`;
export const EventVitalContainer = styled('div')``;