metricReadout.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import type {ReactText} from 'react';
  2. import {Fragment} from 'react';
  3. import styled from '@emotion/styled';
  4. import Duration from 'sentry/components/duration';
  5. import FileSize from 'sentry/components/fileSize';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import {Tooltip} from 'sentry/components/tooltip';
  8. import {defined} from 'sentry/utils';
  9. import type {CountUnit, PercentageUnit} from 'sentry/utils/discover/fields';
  10. import {DurationUnit, RateUnit, SizeUnit} from 'sentry/utils/discover/fields';
  11. import {
  12. formatAbbreviatedNumber,
  13. formatPercentage,
  14. formatRate,
  15. } from 'sentry/utils/formatters';
  16. import {Block} from 'sentry/views/starfish/views/spanSummaryPage/block';
  17. type Unit =
  18. | DurationUnit.MILLISECOND
  19. | SizeUnit.BYTE
  20. | RateUnit
  21. | CountUnit
  22. | PercentageUnit;
  23. interface Props {
  24. title: string;
  25. unit: Unit;
  26. value: ReactText | undefined;
  27. align?: 'left' | 'right';
  28. isLoading?: boolean;
  29. tooltip?: React.ReactNode;
  30. }
  31. export function MetricReadout(props: Props) {
  32. return (
  33. <Block title={props.title} alignment={props.align}>
  34. <ReadoutContent {...props} />
  35. </Block>
  36. );
  37. }
  38. function ReadoutContent({unit, value, tooltip, align = 'right', isLoading}: Props) {
  39. if (isLoading) {
  40. return (
  41. <LoadingContainer align={align}>
  42. <LoadingIndicator mini />
  43. </LoadingContainer>
  44. );
  45. }
  46. if (!defined(value)) {
  47. return <Fragment>--</Fragment>;
  48. }
  49. let renderedValue: React.ReactNode;
  50. if (isARateUnit(unit)) {
  51. renderedValue = (
  52. <NumberContainer align={align}>
  53. {formatRate(typeof value === 'string' ? parseFloat(value) : value, unit, {
  54. minimumValue: MINIMUM_RATE_VALUE,
  55. })}
  56. </NumberContainer>
  57. );
  58. }
  59. if (unit === DurationUnit.MILLISECOND) {
  60. // TODO: Implement other durations
  61. renderedValue = (
  62. <NumberContainer align={align}>
  63. <Duration
  64. seconds={typeof value === 'string' ? parseFloat(value) : value / 1000}
  65. fixedDigits={2}
  66. abbreviation
  67. />
  68. </NumberContainer>
  69. );
  70. }
  71. if (unit === SizeUnit.BYTE) {
  72. // TODO: Implement other sizes
  73. renderedValue = (
  74. <NumberContainer align={align}>
  75. <FileSize bytes={typeof value === 'string' ? parseInt(value, 10) : value} />
  76. </NumberContainer>
  77. );
  78. }
  79. if (unit === 'count') {
  80. renderedValue = (
  81. <NumberContainer align={align}>
  82. {formatAbbreviatedNumber(typeof value === 'string' ? parseInt(value, 10) : value)}
  83. </NumberContainer>
  84. );
  85. }
  86. if (unit === 'percentage') {
  87. renderedValue = (
  88. <NumberContainer align={align}>
  89. {formatPercentage(
  90. typeof value === 'string' ? parseFloat(value) : value,
  91. undefined,
  92. {minimumValue: MINIMUM_PERCENTAGE_VALUE}
  93. )}
  94. </NumberContainer>
  95. );
  96. }
  97. if (tooltip) {
  98. return (
  99. <NumberContainer align={align}>
  100. <Tooltip title={tooltip} isHoverable showUnderline>
  101. {renderedValue}
  102. </Tooltip>
  103. </NumberContainer>
  104. );
  105. }
  106. return <NumberContainer align={align}>{renderedValue}</NumberContainer>;
  107. }
  108. const MINIMUM_RATE_VALUE = 0.01;
  109. const MINIMUM_PERCENTAGE_VALUE = 0.0001; // 0.01%
  110. const NumberContainer = styled('div')<{align: 'left' | 'right'}>`
  111. text-align: ${p => p.align};
  112. font-variant-numeric: tabular-nums;
  113. `;
  114. const LoadingContainer = styled('div')<{align: 'left' | 'right'}>`
  115. display: flex;
  116. justify-content: ${p => (p.align === 'right' ? 'flex-end' : 'flex-start')};
  117. align-items: center;
  118. `;
  119. function isARateUnit(unit: string): unit is RateUnit {
  120. return (Object.values(RateUnit) as string[]).includes(unit);
  121. }