123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- import styled from '@emotion/styled';
- import ProjectBadge from 'sentry/components/idBadge/projectBadge';
- import Panel from 'sentry/components/panels/panel';
- import {Tooltip} from 'sentry/components/tooltip';
- import {IconFire} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {Measurement} from 'sentry/types';
- import {getDuration} from 'sentry/utils/formatters';
- import type {Vital} from 'sentry/utils/performance/vitals/types';
- import type {IconSize} from 'sentry/utils/theme';
- import useProjects from 'sentry/utils/useProjects';
- import {isTransactionNode} from 'sentry/views/performance/newTraceDetails/guards';
- import {TraceDrawerComponents} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/styles';
- import {
- TRACE_MEASUREMENT_LOOKUP,
- type TraceTree,
- } from 'sentry/views/performance/newTraceDetails/traceTree';
- interface TraceVitalsProps {
- trace: TraceTree;
- }
- export function TraceVitals(props: TraceVitalsProps) {
- const {projects} = useProjects();
- const measurements = Array.from(props.trace.vitals.entries());
- return (
- <TraceDrawerComponents.DetailContainer>
- {measurements.map(([node, vital]) => {
- const op = isTransactionNode(node) ? node.value['transaction.op'] : '';
- const project = projects.find(p => p.slug === node.metadata.project_slug);
- return (
- <div key="">
- <TraceDrawerComponents.HeaderContainer>
- <TraceDrawerComponents.Title>
- <Tooltip title={node.metadata.project_slug}>
- <ProjectBadge
- project={project ? project : {slug: node.metadata.project_slug ?? ''}}
- avatarSize={30}
- hideName
- />
- </Tooltip>
- <div>
- <div>{t('transaction')}</div>
- <TraceDrawerComponents.TitleOp> {op}</TraceDrawerComponents.TitleOp>
- </div>
- </TraceDrawerComponents.Title>
- </TraceDrawerComponents.HeaderContainer>
- <VitalsContainer>
- {vital.map((v, i) => {
- return <EventVital key={i} vital={v} value={v.measurement} />;
- })}
- </VitalsContainer>
- </div>
- );
- })}
- </TraceDrawerComponents.DetailContainer>
- );
- }
- const VitalsContainer = styled('div')`
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- gap: ${space(1)};
- margin-top: ${space(2)};
- `;
- interface EventVitalProps {
- value: Measurement;
- vital: TraceTree.CollectedVital;
- }
- function formatVitalDuration(vital: Vital, value: number) {
- if (vital?.type === 'duration') {
- return getDuration(value / 1000, 2, true);
- }
- if (vital?.type === 'integer') {
- return value.toFixed(0);
- }
- return value.toFixed(2);
- }
- function EventVital(props: EventVitalProps) {
- const vital = TRACE_MEASUREMENT_LOOKUP[props.vital.key];
- if (!vital) {
- return null;
- }
- const failedThreshold =
- vital.poorThreshold !== undefined && props.value.value >= vital.poorThreshold;
- const currentValue = formatVitalDuration(vital, props.value.value);
- const thresholdValue = formatVitalDuration(vital, vital?.poorThreshold ?? 0);
- return (
- <StyledPanel failedThreshold={failedThreshold}>
- <div>{vital.name ?? name}</div>
- <ValueRow>
- {failedThreshold ? (
- <FireIconContainer data-test-id="threshold-failed-warning" size="sm">
- <Tooltip
- title={t('Fails threshold at %s.', thresholdValue)}
- position="top"
- containerDisplayMode="inline-block"
- >
- <IconFire size="sm" />
- </Tooltip>
- </FireIconContainer>
- ) : null}
- <Value failedThreshold={failedThreshold}>{currentValue}</Value>
- </ValueRow>
- </StyledPanel>
- );
- }
- 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 ValueRow = styled('div')`
- display: flex;
- align-items: center;
- `;
- 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};`}
- `;
|