import styled from '@emotion/styled'; import {SectionHeading} from 'app/components/charts/styles'; import {Panel} from 'app/components/panels'; import Tooltip from 'app/components/tooltip'; import {IconFire, IconWarning} from 'app/icons'; import {t} from 'app/locale'; import space from 'app/styles/space'; import {Event} from 'app/types/event'; import {formattedValue} from 'app/utils/measurements/index'; import {WEB_VITAL_DETAILS} from 'app/utils/performance/vitals/constants'; import {IconSize} from 'app/utils/theme'; type Props = { event: Event; showSectionHeader?: boolean; }; 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') ); } export default function EventVitals({event, showSectionHeader = true}: Props) { const measurementNames = Object.keys(event.measurements ?? {}) .filter(name => Boolean(WEB_VITAL_DETAILS[`measurements.${name}`])) .sort(); if (measurementNames.length === 0) { return null; } const component = ( {measurementNames.map(name => ( ))} ); if (showSectionHeader) { return ( {t('Web Vitals')} {isOutdatedSdk(event) && ( )} {component} ); } return component; } type EventVitalProps = Props & { name: string; }; function EventVital({event, name}: EventVitalProps) { const value = event.measurements?.[name].value ?? null; if (value === null) { 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 record = WEB_VITAL_DETAILS[`measurements.${name}`]; if (!record) { return null; } const failedThreshold = value >= record.poorThreshold; const currentValue = formattedValue(record, value); const thresholdValue = formattedValue(record, record?.poorThreshold ?? 0); return ( {record.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.red300}; `; 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.red300}; `; const Value = styled('span')<{failedThreshold: boolean}>` font-size: ${p => p.theme.fontSizeExtraLarge}; ${p => p.failedThreshold && `color: ${p.theme.red300};`} `; export const EventVitalContainer = styled('div')``;