bigNumberWidgetVisualization.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import styled from '@emotion/styled';
  2. import type {Polarity} from 'sentry/components/percentChange';
  3. import {Tooltip} from 'sentry/components/tooltip';
  4. import {defined} from 'sentry/utils';
  5. import type {MetaType} from 'sentry/utils/discover/eventView';
  6. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  7. import {useLocation} from 'sentry/utils/useLocation';
  8. import useOrganization from 'sentry/utils/useOrganization';
  9. import {AutoSizedText} from 'sentry/views/dashboards/widgetCard/autoSizedText';
  10. import {DifferenceToPreviousPeriodValue} from 'sentry/views/dashboards/widgets/bigNumberWidget/differenceToPreviousPeriodValue';
  11. import type {
  12. Meta,
  13. TableData,
  14. Thresholds,
  15. } from 'sentry/views/dashboards/widgets/common/types';
  16. import {DEFAULT_FIELD} from '../common/settings';
  17. import {ThresholdsIndicator} from './thresholdsIndicator';
  18. export interface BigNumberWidgetVisualizationProps {
  19. value: number | string;
  20. field?: string;
  21. maximumValue?: number;
  22. meta?: Meta;
  23. preferredPolarity?: Polarity;
  24. previousPeriodValue?: number | string;
  25. thresholds?: Thresholds;
  26. }
  27. export function BigNumberWidgetVisualization(props: BigNumberWidgetVisualizationProps) {
  28. const {
  29. field = DEFAULT_FIELD,
  30. value,
  31. previousPeriodValue,
  32. maximumValue = Number.MAX_VALUE,
  33. preferredPolarity,
  34. meta,
  35. } = props;
  36. const location = useLocation();
  37. const organization = useOrganization();
  38. // TODO: meta as MetaType is a white lie. `MetaType` doesn't know that types can be null, but they can!
  39. const fieldRenderer =
  40. meta && field
  41. ? getFieldRenderer(field, meta as MetaType, false)
  42. : renderableValue => renderableValue.toString();
  43. const unit = meta?.units?.[field];
  44. const type = meta?.fields?.[field];
  45. const baggage = {
  46. location,
  47. organization,
  48. unit: unit ?? undefined, // TODO: Field formatters think units can't be null but they can
  49. };
  50. // String values don't support differences, thresholds, max values, or anything else.
  51. if (typeof value === 'string') {
  52. return (
  53. <Wrapper>
  54. <NumberAndDifferenceContainer>
  55. {fieldRenderer(
  56. {
  57. [field]: value,
  58. },
  59. baggage
  60. )}
  61. </NumberAndDifferenceContainer>
  62. </Wrapper>
  63. );
  64. }
  65. const doesValueHitMaximum = maximumValue ? value >= maximumValue : false;
  66. const clampedValue = Math.min(value, maximumValue);
  67. return (
  68. <Wrapper>
  69. <NumberAndDifferenceContainer>
  70. {props.thresholds && (
  71. <ThresholdsIndicator
  72. preferredPolarity={props.preferredPolarity}
  73. thresholds={props.thresholds}
  74. unit={unit ?? ''}
  75. value={clampedValue}
  76. type={type ?? 'integer'}
  77. />
  78. )}
  79. <NumberContainerOverride>
  80. <Tooltip
  81. title={value}
  82. isHoverable
  83. delay={0}
  84. disabled={doesValueHitMaximum}
  85. containerDisplayMode="inline-flex"
  86. >
  87. {doesValueHitMaximum ? '>' : ''}
  88. {fieldRenderer(
  89. {
  90. [field]: clampedValue,
  91. },
  92. baggage
  93. )}
  94. </Tooltip>
  95. </NumberContainerOverride>
  96. {defined(previousPeriodValue) &&
  97. typeof previousPeriodValue === 'number' &&
  98. Number.isFinite(previousPeriodValue) &&
  99. !Number.isNaN(previousPeriodValue) &&
  100. !doesValueHitMaximum && (
  101. <DifferenceToPreviousPeriodValue
  102. value={value}
  103. previousPeriodValue={previousPeriodValue}
  104. field={field}
  105. preferredPolarity={preferredPolarity}
  106. renderer={(previousDatum: TableData[number]) =>
  107. fieldRenderer(previousDatum, baggage)
  108. }
  109. />
  110. )}
  111. </NumberAndDifferenceContainer>
  112. </Wrapper>
  113. );
  114. }
  115. function Wrapper({children}) {
  116. return (
  117. <AutoResizeParent>
  118. <AutoSizedText>{children}</AutoSizedText>
  119. </AutoResizeParent>
  120. );
  121. }
  122. const AutoResizeParent = styled('div')`
  123. position: absolute;
  124. inset: 0;
  125. color: ${p => p.theme.headingColor};
  126. container-type: size;
  127. container-name: auto-resize-parent;
  128. * {
  129. line-height: 1;
  130. text-align: left !important;
  131. }
  132. `;
  133. const NumberAndDifferenceContainer = styled('div')`
  134. display: flex;
  135. align-items: flex-end;
  136. gap: min(8px, 3cqw);
  137. `;
  138. const NumberContainerOverride = styled('div')`
  139. display: inline-flex;
  140. * {
  141. text-overflow: clip !important;
  142. display: inline;
  143. white-space: nowrap;
  144. }
  145. `;