bigNumberWidgetVisualization.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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 {X_GUTTER, Y_GUTTER} from '../common/settings';
  17. import {ThresholdsIndicator} from './thresholdsIndicator';
  18. export interface BigNumberWidgetVisualizationProps {
  19. field: string;
  20. value: number | 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,
  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 = meta
  40. ? getFieldRenderer(field, meta as MetaType, false)
  41. : renderableValue => renderableValue.toString();
  42. const unit = meta?.units?.[field];
  43. const type = meta?.fields?.[field];
  44. const baggage = {
  45. location,
  46. organization,
  47. unit: unit ?? undefined, // TODO: Field formatters think units can't be null but they can
  48. };
  49. // String values don't support differences, thresholds, max values, or anything else.
  50. if (typeof value === 'string') {
  51. return (
  52. <Wrapper>
  53. <NumberAndDifferenceContainer>
  54. {fieldRenderer(
  55. {
  56. [field]: value,
  57. },
  58. baggage
  59. )}
  60. </NumberAndDifferenceContainer>
  61. </Wrapper>
  62. );
  63. }
  64. const doesValueHitMaximum = maximumValue ? value >= maximumValue : false;
  65. const clampedValue = Math.min(value, maximumValue);
  66. return (
  67. <Wrapper>
  68. <NumberAndDifferenceContainer>
  69. {defined(props.thresholds?.max_values.max1) &&
  70. defined(props.thresholds?.max_values.max2) && (
  71. <ThresholdsIndicator
  72. preferredPolarity={props.preferredPolarity}
  73. thresholds={{
  74. unit: props.thresholds.unit ?? undefined,
  75. max_values: {
  76. max1: props.thresholds.max_values.max1,
  77. max2: props.thresholds.max_values.max2,
  78. },
  79. }}
  80. unit={unit ?? ''}
  81. value={clampedValue}
  82. type={type ?? 'integer'}
  83. />
  84. )}
  85. <NumberContainerOverride>
  86. <Tooltip
  87. title={value}
  88. isHoverable
  89. delay={0}
  90. disabled={doesValueHitMaximum}
  91. containerDisplayMode="inline-flex"
  92. >
  93. {doesValueHitMaximum ? '>' : ''}
  94. {fieldRenderer(
  95. {
  96. [field]: clampedValue,
  97. },
  98. baggage
  99. )}
  100. </Tooltip>
  101. </NumberContainerOverride>
  102. {defined(previousPeriodValue) &&
  103. typeof previousPeriodValue === 'number' &&
  104. Number.isFinite(previousPeriodValue) &&
  105. !Number.isNaN(previousPeriodValue) &&
  106. !doesValueHitMaximum && (
  107. <DifferenceToPreviousPeriodValue
  108. value={value}
  109. previousPeriodValue={previousPeriodValue}
  110. field={field}
  111. preferredPolarity={preferredPolarity}
  112. renderer={(previousDatum: TableData[number]) =>
  113. fieldRenderer(previousDatum, baggage)
  114. }
  115. />
  116. )}
  117. </NumberAndDifferenceContainer>
  118. </Wrapper>
  119. );
  120. }
  121. function Wrapper({children}) {
  122. return (
  123. <AutoResizeParent>
  124. <AutoSizedText>{children}</AutoSizedText>
  125. </AutoResizeParent>
  126. );
  127. }
  128. const AutoResizeParent = styled('div')`
  129. position: absolute;
  130. inset: ${Y_GUTTER} ${X_GUTTER} ${Y_GUTTER} ${X_GUTTER};
  131. color: ${p => p.theme.headingColor};
  132. container-type: size;
  133. container-name: auto-resize-parent;
  134. * {
  135. line-height: 1;
  136. text-align: left !important;
  137. }
  138. `;
  139. const NumberAndDifferenceContainer = styled('div')`
  140. display: flex;
  141. align-items: flex-end;
  142. gap: min(8px, 3cqw);
  143. `;
  144. const NumberContainerOverride = styled('div')`
  145. display: inline-flex;
  146. * {
  147. text-overflow: clip !important;
  148. display: inline;
  149. white-space: nowrap;
  150. }
  151. `;