Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/number-tile-duration-factor-on-output-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@hyperdx/app": patch
---

fix(dashboards): seed the duration unit when switching a number tile to Duration

Switching the Output format to Duration (or Time) on a number tile now seeds the
input unit from the datasource precision. Previously the unit stayed on Seconds,
so a nanosecond trace Duration value was read as seconds (a 367ms value showed as
roughly 11.7 years) until the user re-picked Nanoseconds by hand. Tiles whose
source has no detected duration precision keep the existing behavior.
13 changes: 13 additions & 0 deletions packages/app/src/components/ChartDisplaySettingsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@ export default function ChartDisplaySettingsDrawer({
const showBackgroundChart = displayType === DisplayType.Number;
const isBackgroundChartDisabled = configType === 'sql';

// When the datasource auto-detects a time-based format (e.g. a trace Duration
// column, factor 1e-9 for nanoseconds), seed that factor if the user switches
// the output to duration / time in the format control. Without it the prior
// factor sticks and a nanosecond value renders as seconds (367,700,000 ns
// read as ~11.7y). Only meaningful when the detected default is itself
// time-based; otherwise there is no source precision to seed from.
const preferredDurationFactor =
defaultNumberFormat?.output === 'duration' ||
defaultNumberFormat?.output === 'time'
? defaultNumberFormat.factor
: undefined;

return (
<Drawer
title="Display Settings"
Expand Down Expand Up @@ -333,6 +345,7 @@ export default function ChartDisplaySettingsDrawer({
<NumberFormatForm
control={control}
setValue={setValue}
preferredDurationFactor={preferredDurationFactor}
disclaimer={
isPerSeriesNumberFormatAllowed ? (
<Alert variant="outline" color="yellow" p="xs">
Expand Down
21 changes: 20 additions & 1 deletion packages/app/src/components/NumberFormat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,15 @@ export const NumberFormatForm: React.FC<{
control: Control<Pick<ChartConfigDisplaySettings, 'numberFormat'>>;
setValue: UseFormSetValue<Pick<ChartConfigDisplaySettings, 'numberFormat'>>;
disclaimer?: React.ReactNode;
}> = ({ control, setValue, disclaimer }) => {
/**
* Source-derived duration factor (e.g. 1e-9 for a nanosecond trace Duration
* column). When set, switching the output to a time-based format (duration or
* time) seeds `numberFormat.factor` from it instead of keeping the prior
* factor, so the value is read with the right input unit. Left undefined for
* sources with no detected duration precision, where the factor is untouched.
*/
preferredDurationFactor?: number;
}> = ({ control, setValue, disclaimer, preferredDurationFactor }) => {
const format =
useWatch({ control, name: 'numberFormat' }) ?? DEFAULT_NUMBER_FORMAT;

Expand Down Expand Up @@ -198,6 +206,17 @@ export const NumberFormatForm: React.FC<{
'numberFormat.numericUnit',
DEFAULT_NUMERIC_UNIT_BY_OUTPUT[newOutput] ?? undefined,
);
// Time-based formats interpret the value through `factor`
// (input unit to seconds). Seed it from the source-derived
// precision so a nanosecond Duration column is not misread as
// seconds; otherwise the prior factor (often Seconds = 1)
// sticks and the user has to re-pick the unit by hand.
if (
(newOutput === 'duration' || newOutput === 'time') &&
preferredDurationFactor != null
) {
setValue('numberFormat.factor', preferredDurationFactor);
}
}}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,5 +347,102 @@ describe('ChartDisplaySettingsDrawer', () => {
numberFormat: { output: 'currency' },
});
});

// Switching the output to Duration on a tile whose source is a nanosecond
// Duration column should seed the factor from the source precision (1e-9),
// not keep the prior Seconds factor (which would read the value as seconds).
it('seeds the duration factor from the source precision when output switches to Duration', async () => {
const onChange = jest.fn();
const user = userEvent.setup();

renderWithMantine(
<ChartDisplaySettingsDrawer
{...numberBuilderProps}
settings={
{
numberFormat: { output: 'number', factor: 1 },
} as ChartConfigDisplaySettings
}
defaultNumberFormat={durationFormat}
onChange={onChange}
/>,
);

await user.selectOptions(
screen.getByRole('combobox', { name: /output format/i }),
'duration',
);
await user.click(screen.getByRole('button', { name: /apply/i }));

expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange.mock.calls[0][0].numberFormat).toMatchObject({
output: 'duration',
factor: 1e-9,
});
});

// With no source-derived duration format (non-duration source), switching
// to Duration must not force a factor; the prior value (Seconds = 1) stays
// and the user picks the input unit manually as before.
it('does not force a factor when no source duration format is available', async () => {
const onChange = jest.fn();
const user = userEvent.setup();

renderWithMantine(
<ChartDisplaySettingsDrawer
{...numberBuilderProps}
settings={
{
numberFormat: { output: 'number', factor: 1 },
} as ChartConfigDisplaySettings
}
onChange={onChange}
/>,
);

await user.selectOptions(
screen.getByRole('combobox', { name: /output format/i }),
'duration',
);
await user.click(screen.getByRole('button', { name: /apply/i }));

expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange.mock.calls[0][0].numberFormat).toMatchObject({
output: 'duration',
factor: 1,
});
});
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved

// The Time output reads the value through the same `factor` select as
// Duration, so switching to Time also seeds the source-derived factor.
it('seeds the duration factor when output switches to Time', async () => {
const onChange = jest.fn();
const user = userEvent.setup();

renderWithMantine(
<ChartDisplaySettingsDrawer
{...numberBuilderProps}
settings={
{
numberFormat: { output: 'number', factor: 1 },
} as ChartConfigDisplaySettings
}
defaultNumberFormat={durationFormat}
onChange={onChange}
/>,
);

await user.selectOptions(
screen.getByRole('combobox', { name: /output format/i }),
'time',
);
await user.click(screen.getByRole('button', { name: /apply/i }));

expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange.mock.calls[0][0].numberFormat).toMatchObject({
output: 'time',
factor: 1e-9,
});
});
});
});