Browse Source

fix(dashboards): `BigNumberWidget` improvement grab-bag I (#78223)

A variety of small changes to `BigNumberWidget` to make it nicer to
render, easier to use, and fit more closely to how it's currently used
in the app.

- **Pull field meta off `meta['fields']`**
- **Tweak font sizes**
- **Use field renderers instead of formatters**
- **Reduce spacing**
- **Enforce minimum height**
- **Enforce minimum width**
- **Rename component**
- **More CSS tweaks**
- **Sync deemphasis colours**
- **Improve alignment of description tooltip**
- **Rename confusing prop**
- **Make meta units optional**
George Gritsouk 5 months ago
parent
commit
876bc7c222

+ 2 - 2
static/app/utils/discover/fieldRenderers.tsx

@@ -1004,7 +1004,7 @@ export function getFieldRenderer(
   }
 
   const fieldName = isAlias ? getAggregateAlias(field) : field;
-  const fieldType = meta[fieldName];
+  const fieldType = meta[fieldName] || meta.fields?.[fieldName];
 
   for (const alias in SPECIAL_FUNCTIONS) {
     if (fieldName.startsWith(alias)) {
@@ -1038,7 +1038,7 @@ export function getFieldFormatter(
   isAlias: boolean = true
 ): FieldTypeFormatterRenderFunctionPartial {
   const fieldName = isAlias ? getAggregateAlias(field) : field;
-  const fieldType = meta[fieldName] || meta.fields[fieldName];
+  const fieldType = meta[fieldName] || meta.fields?.[fieldName];
 
   if (FIELD_FORMATTERS.hasOwnProperty(fieldType)) {
     return partial(FIELD_FORMATTERS[fieldType].renderFunc, fieldName);

+ 5 - 7
static/app/views/dashboards/widgets/bigNumberWidget/bigNumberWidget.stories.tsx

@@ -40,6 +40,11 @@ export default storyBook(BigNumberWidget, story => {
                   'eps()': 0.01087819860850493,
                 },
               ]}
+              previousPeriodData={[
+                {
+                  'eps()': 0.01087819860850493,
+                },
+              ]}
               meta={{
                 fields: {
                   'eps()': 'rate',
@@ -167,9 +172,6 @@ export default storyBook(BigNumberWidget, story => {
                 fields: {
                   'http_rate(500)': 'percentage',
                 },
-                units: {
-                  'http_rate(500)': null,
-                },
               }}
             />
           </NormalWidget>
@@ -191,9 +193,6 @@ export default storyBook(BigNumberWidget, story => {
                 fields: {
                   'http_rate(200)': 'percentage',
                 },
-                units: {
-                  'http_rate(200)': null,
-                },
               }}
             />
           </NormalWidget>
@@ -210,5 +209,4 @@ const SmallSizingWindow = styled(SizingWindow)`
 
 const NormalWidget = styled('div')`
   width: 250px;
-  height: 100px;
 `;

+ 14 - 11
static/app/views/dashboards/widgets/bigNumberWidget/bigNumberWidgetVisualization.tsx

@@ -4,12 +4,15 @@ import type {Polarity} from 'sentry/components/percentChange';
 import {Tooltip} from 'sentry/components/tooltip';
 import {defined} from 'sentry/utils';
 import type {MetaType} from 'sentry/utils/discover/eventView';
-import {getFieldFormatter} from 'sentry/utils/discover/fieldRenderers';
+import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
 import {AutoSizedText} from 'sentry/views/dashboards/widgetCard/autoSizedText';
 import {DifferenceToPreviousPeriodData} from 'sentry/views/dashboards/widgets/bigNumberWidget/differenceToPreviousPeriodData';
-import {NO_DATA_PLACEHOLDER} from 'sentry/views/dashboards/widgets/bigNumberWidget/settings';
+import {
+  DEEMPHASIS_COLOR_NAME,
+  NO_DATA_PLACEHOLDER,
+} from 'sentry/views/dashboards/widgets/bigNumberWidget/settings';
 import {ErrorPanel} from 'sentry/views/dashboards/widgets/common/errorPanel';
 import type {
   Meta,
@@ -52,8 +55,8 @@ export function BigNumberWidgetVisualization(props: Props) {
   const field = fields[0];
 
   // TODO: meta as MetaType is a white lie. `MetaType` doesn't know that types can be null, but they can!
-  const fieldFormatter = meta
-    ? getFieldFormatter(field, meta as MetaType, false)
+  const fieldRenderer = meta
+    ? getFieldRenderer(field, meta as MetaType, false)
     : value => value.toString();
 
   const unit = meta?.units?.[field];
@@ -63,12 +66,12 @@ export function BigNumberWidgetVisualization(props: Props) {
     unit: unit ?? undefined, // TODO: Field formatters think units can't be null but they can
   };
 
-  const rendered = fieldFormatter(datum, baggage);
+  const rendered = fieldRenderer(datum, baggage);
 
   return (
     <AutoResizeParent>
       <AutoSizedText>
-        <NumberContainer>
+        <NumberAndDifferenceContainer>
           <NumberContainerOverride>
             <Tooltip title={datum[field]} isHoverable delay={0}>
               {rendered}
@@ -80,13 +83,13 @@ export function BigNumberWidgetVisualization(props: Props) {
               data={data}
               previousPeriodData={previousPeriodData}
               preferredPolarity={preferredPolarity}
-              formatter={(previousDatum: TableData[number]) =>
-                fieldFormatter(previousDatum, baggage)
+              renderer={(previousDatum: TableData[number]) =>
+                fieldRenderer(previousDatum, baggage)
               }
               field={field}
             />
           )}
-        </NumberContainer>
+        </NumberAndDifferenceContainer>
       </AutoSizedText>
     </AutoResizeParent>
   );
@@ -107,7 +110,7 @@ const AutoResizeParent = styled('div')`
   }
 `;
 
-const NumberContainer = styled('div')`
+const NumberAndDifferenceContainer = styled('div')`
   display: flex;
   align-items: flex-end;
   gap: min(8px, 3cqw);
@@ -124,5 +127,5 @@ const NumberContainerOverride = styled('div')`
 `;
 
 const Deemphasize = styled('span')`
-  color: ${p => p.theme.gray300};
+  color: ${p => p.theme[DEEMPHASIS_COLOR_NAME]};
 `;

+ 17 - 13
static/app/views/dashboards/widgets/bigNumberWidget/differenceToPreviousPeriodData.tsx

@@ -9,14 +9,17 @@ import {
 } from 'sentry/components/percentChange';
 import {IconArrow} from 'sentry/icons';
 import {space} from 'sentry/styles/space';
-import {NO_DATA_PLACEHOLDER} from 'sentry/views/dashboards/widgets/bigNumberWidget/settings';
+import {
+  DEEMPHASIS_COLOR_NAME,
+  NO_DATA_PLACEHOLDER,
+} from 'sentry/views/dashboards/widgets/bigNumberWidget/settings';
 import type {TableData} from 'sentry/views/dashboards/widgets/common/types';
 
 interface Props {
   data: TableData;
   field: string;
-  formatter: (datum: TableData[number]) => React.ReactNode;
   previousPeriodData: TableData;
+  renderer: (datum: TableData[number]) => React.ReactNode;
   preferredPolarity?: Polarity;
 }
 
@@ -25,7 +28,7 @@ export function DifferenceToPreviousPeriodData({
   previousPeriodData,
   preferredPolarity = '',
   field,
-  formatter,
+  renderer,
 }: Props) {
   const currentValue = data[0][field];
   const previousValue = previousPeriodData[0][field];
@@ -47,31 +50,32 @@ export function DifferenceToPreviousPeriodData({
 
   return (
     <Difference rating={rating}>
-      <Indicator>{directionMarker}</Indicator>
-      <Number>{formatter(differenceAsDatum)}</Number>
+      <Text>{directionMarker}</Text>
+      <Text>{renderer(differenceAsDatum)}</Text>
     </Difference>
   );
 }
 
 const Difference = styled(ColorizedRating)`
   display: flex;
-  gap: ${space(0.5)};
+  gap: ${space(0.25)};
+  margin-bottom: 6cqh;
 
   @container (min-height: 50px) {
-    padding-bottom: 5cqh;
+    margin-bottom: 10cqh;
   }
 `;
 
-const Number = styled('div')`
-  font-size: clamp(14px, calc(10px + 4cqi), 30cqh);
-`;
+const Text = styled('div')`
+  font-size: 14px;
 
-const Indicator = styled('div')`
-  font-size: clamp(14px, calc(10px + 4cqi), 30cqh);
+  @container (min-height: 50px) {
+    font-size: clamp(14px, calc(10px + 4cqi), 30cqh);
+  }
 `;
 
 const Deemphasize = styled('span')`
-  color: ${p => p.theme.gray300};
+  color: ${p => p.theme[DEEMPHASIS_COLOR_NAME]};
 `;
 
 function getDifferenceDirectionMarker(difference: number) {

+ 1 - 0
static/app/views/dashboards/widgets/bigNumberWidget/settings.tsx

@@ -1 +1,2 @@
 export const NO_DATA_PLACEHOLDER = '\u2014';
+export const DEEMPHASIS_COLOR_NAME = 'gray300';

+ 3 - 2
static/app/views/dashboards/widgets/common/errorPanel.tsx

@@ -2,6 +2,7 @@ import styled from '@emotion/styled';
 
 import {IconWarning} from 'sentry/icons';
 import {space} from 'sentry/styles/space';
+import {DEEMPHASIS_COLOR_NAME} from 'sentry/views/dashboards/widgets/bigNumberWidget/settings';
 import type {StateProps} from 'sentry/views/dashboards/widgets/common/types';
 
 interface Props {
@@ -11,7 +12,7 @@ interface Props {
 export function ErrorPanel({error}: Props) {
   return (
     <Panel>
-      <IconWarning color="gray500" size="lg" />
+      <IconWarning color={DEEMPHASIS_COLOR_NAME} size="lg" />
       <span>{error?.toString()}</span>
     </Panel>
   );
@@ -29,6 +30,6 @@ const Panel = styled('div')<{height?: string}>`
 
   overflow: hidden;
 
-  color: ${p => p.theme.gray300};
+  color: ${p => p.theme[DEEMPHASIS_COLOR_NAME]};
   font-size: ${p => p.theme.fontSizeExtraLarge};
 `;

+ 1 - 1
static/app/views/dashboards/widgets/common/types.tsx

@@ -1,6 +1,6 @@
 export type Meta = {
   fields: Record<string, string>;
-  units: Record<string, string | null>;
+  units?: Record<string, string | null>;
 };
 
 export type TableData = Record<string, number | string | undefined>[];

+ 11 - 1
static/app/views/dashboards/widgets/common/widgetFrame.tsx

@@ -24,7 +24,9 @@ export function WidgetFrame(props: Props) {
           </Tooltip>
 
           {description && showDescriptionInTooltip && (
-            <QuestionTooltip size="sm" title={description} />
+            <TooltipAligner>
+              <QuestionTooltip size="sm" title={description} />
+            </TooltipAligner>
           )}
         </Title>
 
@@ -51,7 +53,9 @@ const Frame = styled('div')`
   flex-direction: column;
 
   height: 100%;
+  min-height: 96px;
   width: 100%;
+  min-width: 120px;
 
   padding: ${space(2)};
 
@@ -86,6 +90,12 @@ const TitleText = styled(HeaderTitle)`
   font-weight: ${p => p.theme.fontWeightBold};
 `;
 
+const TooltipAligner = styled('div')`
+  font-size: 0;
+  line-height: 1;
+  margin-bottom: 2px;
+`;
+
 const VisualizationWrapper = styled('div')`
   display: flex;
   flex-grow: 1;