Skip to content
Merged
32 changes: 32 additions & 0 deletions packages/app/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getMetricTableName,
mapKeyBy,
orderByStringToSortingState,
parseTimestampToMs,
sortingStateToOrderByString,
stripTrailingSlash,
useQueryHistory,
Expand Down Expand Up @@ -1125,6 +1126,37 @@ describe('mapKeyBy', () => {
});
});

describe('parseTimestampToMs', () => {
it('returns integer ms when there are no sub-millisecond digits', () => {
const result = parseTimestampToMs('2024-01-01T00:00:01.000000000Z');
expect(result).toBe(new Date('2024-01-01T00:00:01.000Z').getTime());
});

it('preserves sub-millisecond precision as a fractional ms', () => {
const base = new Date('2024-01-01T00:00:01.000Z').getTime();
const result = parseTimestampToMs('2024-01-01T00:00:01.000500000Z');
expect(result).toBeCloseTo(base + 0.5, 4);
});

it('preserves whole-millisecond component when sub-ms digits are also present', () => {
const base = new Date('2024-01-01T00:00:01.500Z').getTime();
const result = parseTimestampToMs('2024-01-01T00:00:01.500500000Z');
expect(result).toBeCloseTo(base + 0.5, 4);
});

it('handles max sub-millisecond value (999 µs + 999 ns)', () => {
const base = new Date('2024-01-01T00:00:01.000Z').getTime();
const result = parseTimestampToMs('2024-01-01T00:00:01.000999999Z');
expect(result).toBeCloseTo(base + 0.999999, 3);
});

it('orders two timestamps within the same millisecond correctly', () => {
const earlier = parseTimestampToMs('2024-01-01T00:00:01.000400000Z');
const later = parseTimestampToMs('2024-01-01T00:00:01.000800000Z');
expect(earlier).toBeLessThan(later);
});
});

describe('formatDurationMsCompact', () => {
it('returns 0 for zero', () => {
expect(formatDurationMsCompact(0)).toBe('0');
Expand Down
7 changes: 4 additions & 3 deletions packages/app/src/components/DBTraceWaterfallChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
getChartColorSuccessHighlight,
getChartColorWarning,
getChartColorWarningHighlight,
parseTimestampToMs,
} from '@/utils';
import {
getHighlightedAttributesFromData,
Expand Down Expand Up @@ -753,7 +754,7 @@ export function DBTraceWaterfallChartContainer({
// All units in ms!
const foundMinOffset =
rows?.reduce((acc, result) => {
return Math.min(acc, new Date(result.Timestamp).getTime());
return Math.min(acc, parseTimestampToMs(result.Timestamp));
}, Number.MAX_SAFE_INTEGER) ?? 0;
const minOffset =
foundMinOffset === Number.MAX_SAFE_INTEGER ? 0 : foundMinOffset;
Expand All @@ -765,7 +766,7 @@ export function DBTraceWaterfallChartContainer({
() =>
flattenedNodes.map((result, i) => {
const tookMs = (result.Duration || 0) * 1000;
const startOffset = new Date(result.Timestamp).getTime();
const startOffset = parseTimestampToMs(result.Timestamp);
const start = startOffset - minOffset;
const end = start + tookMs;

Expand Down Expand Up @@ -799,7 +800,7 @@ export function DBTraceWaterfallChartContainer({
const markers =
showSpanEvents && result.SpanEvents
? result.SpanEvents.map(spanEvent => ({
timestamp: new Date(spanEvent.Timestamp).getTime() - minOffset,
timestamp: parseTimestampToMs(spanEvent.Timestamp) - minOffset,
name: spanEvent.Name,
attributes: spanEvent.Attributes || {},
}))
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
import { formatDistanceToNowStrict } from 'date-fns';
import numbro from 'numbro';
import type { SetStateAction } from 'react';
import TimestampNano from 'timestamp-nano';
import { TableConnection } from '@hyperdx/common-utils/dist/core/metadata';
import {
NumericUnit,
Expand Down Expand Up @@ -1118,3 +1119,8 @@ export const isElementClickable = (el: HTMLElement): boolean => {
// or if the element at point is a descendant of the element passed in
return el === elementAtPoint || el.contains(elementAtPoint);
};

export function parseTimestampToMs(isoString: string): number {
const ts = TimestampNano.fromString(isoString);
return ts.toDate().getTime() + (ts.getNano() % 1_000_000) / 1_000_000;
}
Loading