metricReadout.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  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 <LoadingIndicator mini />;
  32. }
  33. if (!defined(value)) {
  34. return <Fragment>--</Fragment>;
  35. }
  36. let renderedValue: React.ReactNode;
  37. if (isARateUnit(unit)) {
  38. renderedValue = (
  39. <NumberContainer align={align}>
  40. {formatRate(typeof value === 'string' ? parseFloat(value) : value, unit)}
  41. </NumberContainer>
  42. );
  43. }
  44. if (unit === DurationUnit.MILLISECOND) {
  45. // TODO: Implement other durations
  46. renderedValue = (
  47. <NumberContainer align={align}>
  48. <Duration
  49. seconds={typeof value === 'string' ? parseFloat(value) : value / 1000}
  50. fixedDigits={2}
  51. abbreviation
  52. />
  53. </NumberContainer>
  54. );
  55. }
  56. if (unit === SizeUnit.BYTE) {
  57. // TODO: Implement other sizes
  58. renderedValue = (
  59. <NumberContainer align={align}>
  60. <FileSize bytes={typeof value === 'string' ? parseInt(value, 10) : value} />
  61. </NumberContainer>
  62. );
  63. }
  64. if (unit === 'count') {
  65. renderedValue = (
  66. <NumberContainer align={align}>
  67. {formatAbbreviatedNumber(typeof value === 'string' ? parseInt(value, 10) : value)}
  68. </NumberContainer>
  69. );
  70. }
  71. if (tooltip) {
  72. return (
  73. <NumberContainer align={align}>
  74. <Tooltip title={tooltip} isHoverable showUnderline>
  75. {renderedValue}
  76. </Tooltip>
  77. </NumberContainer>
  78. );
  79. }
  80. return <NumberContainer align={align}>{renderedValue}</NumberContainer>;
  81. }
  82. const NumberContainer = styled('div')<{align: 'left' | 'right'}>`
  83. text-align: ${p => p.align};
  84. font-variant-numeric: tabular-nums;
  85. `;
  86. function isARateUnit(unit: string): unit is RateUnit {
  87. return (Object.values(RateUnit) as string[]).includes(unit);
  88. }