spanDetailsHeader.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import styled from '@emotion/styled';
  2. import {SectionHeading} from 'sentry/components/charts/styles';
  3. import Count from 'sentry/components/count';
  4. import PerformanceDuration from 'sentry/components/performanceDuration';
  5. import {t, tct} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import {defined} from 'sentry/utils';
  8. import {formatPercentage} from 'sentry/utils/formatters';
  9. import type {SpanSlug, SuspectSpan} from 'sentry/utils/performance/suspectSpans/types';
  10. interface HeaderProps {
  11. spanSlug: SpanSlug;
  12. totalCount: number | null;
  13. suspectSpan?: SuspectSpan;
  14. }
  15. export default function SpanDetailsHeader(props: HeaderProps) {
  16. const {spanSlug, suspectSpan, totalCount} = props;
  17. const {
  18. description,
  19. frequency,
  20. avgOccurrences,
  21. p50ExclusiveTime,
  22. p75ExclusiveTime,
  23. p95ExclusiveTime,
  24. p99ExclusiveTime,
  25. sumExclusiveTime,
  26. } = suspectSpan ?? {};
  27. return (
  28. <ContentHeader>
  29. <HeaderInfo data-test-id="header-operation-name">
  30. <StyledSectionHeading>{t('Span Operation')}</StyledSectionHeading>
  31. <SectionBody>
  32. <SpanLabelContainer>{description ?? emptyValue}</SpanLabelContainer>
  33. </SectionBody>
  34. <SectionSubtext data-test-id="operation-name">{spanSlug.op}</SectionSubtext>
  35. </HeaderInfo>
  36. <HeaderInfo data-test-id="header-percentiles">
  37. <StyledSectionHeading>{t('Self Time Percentiles')}</StyledSectionHeading>
  38. <PercentileHeaderBodyWrapper>
  39. <div data-test-id="section-p50">
  40. <SectionBody>
  41. {defined(p50ExclusiveTime) ? (
  42. <PerformanceDuration abbreviation milliseconds={p50ExclusiveTime} />
  43. ) : (
  44. '\u2014'
  45. )}
  46. </SectionBody>
  47. <SectionSubtext>{t('p50')}</SectionSubtext>
  48. </div>
  49. <div data-test-id="section-p75">
  50. <SectionBody>
  51. {defined(p75ExclusiveTime) ? (
  52. <PerformanceDuration abbreviation milliseconds={p75ExclusiveTime} />
  53. ) : (
  54. '\u2014'
  55. )}
  56. </SectionBody>
  57. <SectionSubtext>{t('p75')}</SectionSubtext>
  58. </div>
  59. <div data-test-id="section-p95">
  60. <SectionBody>
  61. {defined(p95ExclusiveTime) ? (
  62. <PerformanceDuration abbreviation milliseconds={p95ExclusiveTime} />
  63. ) : (
  64. '\u2014'
  65. )}
  66. </SectionBody>
  67. <SectionSubtext>{t('p95')}</SectionSubtext>
  68. </div>
  69. <div data-test-id="section-p99">
  70. <SectionBody>
  71. {defined(p99ExclusiveTime) ? (
  72. <PerformanceDuration abbreviation milliseconds={p99ExclusiveTime} />
  73. ) : (
  74. '\u2014'
  75. )}
  76. </SectionBody>
  77. <SectionSubtext>{t('p99')}</SectionSubtext>
  78. </div>
  79. </PercentileHeaderBodyWrapper>
  80. </HeaderInfo>
  81. <HeaderInfo data-test-id="header-frequency">
  82. <StyledSectionHeading>{t('Frequency')}</StyledSectionHeading>
  83. <SectionBody>
  84. {defined(frequency) && defined(totalCount)
  85. ? formatPercentage(Math.min(frequency, totalCount) / totalCount)
  86. : '\u2014'}
  87. </SectionBody>
  88. <SectionSubtext>
  89. {defined(avgOccurrences)
  90. ? tct('[times] times per event', {times: avgOccurrences.toFixed(2)})
  91. : '\u2014'}
  92. </SectionSubtext>
  93. </HeaderInfo>
  94. <HeaderInfo data-test-id="header-total-exclusive-time">
  95. <StyledSectionHeading>{t('Total Self Time')}</StyledSectionHeading>
  96. <SectionBody>
  97. {defined(sumExclusiveTime) ? (
  98. <PerformanceDuration abbreviation milliseconds={sumExclusiveTime} />
  99. ) : (
  100. '\u2014'
  101. )}
  102. </SectionBody>
  103. <SectionSubtext>
  104. {defined(frequency)
  105. ? tct('[events] events', {events: <Count value={frequency} />})
  106. : '\u2014'}
  107. </SectionSubtext>
  108. </HeaderInfo>
  109. </ContentHeader>
  110. );
  111. }
  112. const ContentHeader = styled('div')`
  113. display: grid;
  114. grid-template-columns: 1fr;
  115. gap: ${space(4)};
  116. margin-bottom: ${space(2)};
  117. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  118. grid-template-columns: 1fr repeat(3, max-content);
  119. }
  120. `;
  121. const HeaderInfo = styled('div')`
  122. ${p => p.theme.overflowEllipsis};
  123. height: 78px;
  124. `;
  125. const StyledSectionHeading = styled(SectionHeading)`
  126. margin: 0;
  127. `;
  128. const SectionBody = styled('div')<{overflowEllipsis?: boolean}>`
  129. font-size: ${p => p.theme.fontSizeExtraLarge};
  130. padding: ${space(0.5)} 0;
  131. max-height: 32px;
  132. `;
  133. const SectionSubtext = styled('div')`
  134. color: ${p => p.theme.subText};
  135. font-size: ${p => p.theme.fontSizeMedium};
  136. `;
  137. const PercentileHeaderBodyWrapper = styled('div')`
  138. display: grid;
  139. grid-template-columns: repeat(4, max-content);
  140. gap: ${space(3)};
  141. `;
  142. export const SpanLabelContainer = styled('div')`
  143. ${p => p.theme.overflowEllipsis};
  144. `;
  145. const EmptyValueContainer = styled('span')`
  146. color: ${p => p.theme.gray300};
  147. `;
  148. const emptyValue = <EmptyValueContainer>{t('(unnamed span)')}</EmptyValueContainer>;