metricReadout.tsx 3.1 KB

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