diff --git a/.changeset/wild-lobsters-doubt.md b/.changeset/wild-lobsters-doubt.md new file mode 100644 index 00000000..fda48352 --- /dev/null +++ b/.changeset/wild-lobsters-doubt.md @@ -0,0 +1,8 @@ +--- +'@spotlightjs/spotlight': patch +'@spotlightjs/electron': patch +'@spotlightjs/overlay': patch +'@spotlightjs/astro': patch +--- + +Unify and simplify duration calculations and representations diff --git a/packages/overlay/src/integrations/sentry/components/explore/traces/TraceDetails/components/TraceTreeview.tsx b/packages/overlay/src/integrations/sentry/components/explore/traces/TraceDetails/components/TraceTreeview.tsx index 0bd6c3ff..a716a9d9 100644 --- a/packages/overlay/src/integrations/sentry/components/explore/traces/TraceDetails/components/TraceTreeview.tsx +++ b/packages/overlay/src/integrations/sentry/components/explore/traces/TraceDetails/components/TraceTreeview.tsx @@ -1,10 +1,10 @@ import { useState } from 'react'; import { useParams } from 'react-router-dom'; import sentryDataCache from '../../../../../data/sentryDataCache'; -import { getDuration } from '../../../../../utils/duration'; import DateTime from '../../../../DateTime'; import SpanDetails from '../../spans/SpanDetails'; import SpanTree from '../../spans/SpanTree'; +import { getFormattedSpanDuration } from '../../../../../utils/duration'; type TraceTreeViewProps = { traceId: string }; @@ -29,10 +29,7 @@ export default function TraceTreeview({ traceId }: TraceTreeViewProps) { - - {getDuration(trace.start_timestamp, trace.timestamp).toLocaleString()} ms - {' '} - recorded in{' '} + {getFormattedSpanDuration(trace)} recorded in{' '} {trace.spans.length.toLocaleString()} spans diff --git a/packages/overlay/src/integrations/sentry/components/explore/traces/TraceList.tsx b/packages/overlay/src/integrations/sentry/components/explore/traces/TraceList.tsx index 285b2076..1982c13f 100644 --- a/packages/overlay/src/integrations/sentry/components/explore/traces/TraceList.tsx +++ b/packages/overlay/src/integrations/sentry/components/explore/traces/TraceList.tsx @@ -7,7 +7,7 @@ import classNames from '../../../../../lib/classNames'; import { useSpotlightContext } from '../../../../../lib/useSpotlightContext'; import { useSentryHelpers } from '../../../data/useSentryHelpers'; import { useSentryTraces } from '../../../data/useSentryTraces'; -import { getDuration } from '../../../utils/duration'; +import { getFormattedSpanDuration } from '../../../utils/duration'; import { truncateId } from '../../../utils/text'; import HiddenItemsButton from '../../HiddenItemsButton'; import { TraceRootTxnName } from './TraceDetails/components/TraceRootTxnName'; @@ -35,7 +35,6 @@ export default function TraceList() { /> )} {filteredTraces.map(trace => { - const duration = getDuration(trace.start_timestamp, trace.timestamp); return (
-
{duration} ms
+
{getFormattedSpanDuration(trace)}
{trace.spans.length.toLocaleString()} spans, {trace.transactions.length.toLocaleString()} txns diff --git a/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanDetails.tsx b/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanDetails.tsx index 2a870ede..80fc6f91 100644 --- a/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanDetails.tsx +++ b/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanDetails.tsx @@ -7,7 +7,7 @@ import { DB_SPAN_REGEX } from '../../../../constants'; import dataCache from '../../../../data/sentryDataCache'; import type { SentryErrorEvent, Span, TraceContext } from '../../../../types'; import { formatBytes } from '../../../../utils/bytes'; -import { getDuration } from '../../../../utils/duration'; +import { getFormattedDuration } from '../../../../utils/duration'; import DateTime from '../../../DateTime'; import { ErrorTitle } from '../../../events/error/Error'; import SpanTree from './SpanTree'; @@ -87,7 +87,7 @@ export default function SpanDetails({ }) { const [spanNodeWidth, setSpanNodeWidth] = useState(50); - const spanDuration = getDuration(span.start_timestamp, span.timestamp); + const spanDuration = span.timestamp - span.start_timestamp; const errors = dataCache.getEventsByTrace(span.trace_id).filter(e => e.type !== 'transaction' && 'exception' in e); @@ -115,7 +115,7 @@ export default function SpanDetails({ - {getDuration(startTimestamp, span.start_timestamp)} ms into trace + {getFormattedDuration(spanDuration)} into trace
@@ -123,11 +123,11 @@ export default function SpanDetails({
- {spanDuration} ms + {getFormattedDuration(spanDuration)}
diff --git a/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanItem.tsx b/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanItem.tsx index f97e3488..28bb2903 100644 --- a/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanItem.tsx +++ b/packages/overlay/src/integrations/sentry/components/explore/traces/spans/SpanItem.tsx @@ -3,7 +3,7 @@ import { Link, useParams } from 'react-router-dom'; import { ReactComponent as ChevronIcon } from '~/assets/chevronDown.svg'; import classNames from '../../../../../../lib/classNames'; import type { Span, TraceContext } from '../../../../types'; -import { getDuration, getSpanDurationClassName } from '../../../../utils/duration'; +import { getSpanDurationClassName, getFormattedDuration } from '../../../../utils/duration'; import PlatformIcon from '../../../PlatformIcon'; import SpanResizer from '../../../SpanResizer'; import SpanTree from './SpanTree'; @@ -37,7 +37,7 @@ const SpanItem = ({ ); const [isResizing, setIsResizing] = useState(false); - const spanDuration = getDuration(span.start_timestamp, span.timestamp); + const spanDuration = span.timestamp - span.start_timestamp; const handleResize = (e: MouseEvent) => { if (containerRef.current) { @@ -117,7 +117,7 @@ const SpanItem = ({ }} > - {spanDuration.toLocaleString()} ms + {getFormattedDuration(spanDuration)} diff --git a/packages/overlay/src/integrations/sentry/utils/duration.ts b/packages/overlay/src/integrations/sentry/utils/duration.ts index 1b400742..f0aead3a 100644 --- a/packages/overlay/src/integrations/sentry/utils/duration.ts +++ b/packages/overlay/src/integrations/sentry/utils/duration.ts @@ -1,27 +1,16 @@ -export const SECOND = 1000; -export const MINUTE = 60000; -export const HOUR = 3600000; -export const DAY = 86400000; -export const WEEK = 604800000; -export const MONTH = 2629800000; -export const YEAR = 31557600000; - export const DURATION_LABELS = { - yr: 'yr', - mo: 'mo', - wk: 'wk', - d: 'd', - hr: 'hr', - min: 'min', - s: 's', - ms: 'ms', + 31557600000: 'yr', + 2629800000: 'mo', + 604800000: 'wk', + 86400000: 'd', + 3600000: 'hr', + 60000: 'min', + 1000: 's', }; -export function getDuration(start: string | number, end: string | number) { - const startTs = typeof start === 'string' ? new Date(start).getTime() : start; - const endTs = typeof end === 'string' ? new Date(end).getTime() : end; - return Math.floor(endTs - startTs); -} +const DURATIONS = Object.keys(DURATION_LABELS) + .map(Number) + .sort((a, b) => b - a); export function getSpanDurationClassName(duration: number) { if (duration > 1000) return 'text-red-400'; @@ -30,49 +19,19 @@ export function getSpanDurationClassName(duration: number) { } export function getFormattedNumber(num: number, decimalPlaces: number = 2): string { - if (num % 1 !== 0 || (num % 1 === 0 && num.toString().includes('.'))) { - return num.toFixed(decimalPlaces); - } else { - return num.toFixed(0); - } + return num.toFixed(decimalPlaces).replace(/\.00$/, ''); } export function getFormattedDuration(duration: number): string { - if (duration >= YEAR) { - const num = getFormattedNumber(duration / YEAR); - return `${num}${DURATION_LABELS.yr}`; - } - - if (duration >= MONTH) { - const num = getFormattedNumber(duration / MONTH); - return `${num}${DURATION_LABELS.mo}`; - } - - if (duration >= WEEK) { - const num = getFormattedNumber(duration / WEEK); - return `${num}${DURATION_LABELS.wk}`; - } - - if (duration >= DAY) { - const num = getFormattedNumber(duration / DAY); - return `${num}${DURATION_LABELS.d}`; - } - - if (duration >= HOUR) { - const num = getFormattedNumber(duration / HOUR); - return `${num}${DURATION_LABELS.hr}`; - } - - if (duration >= MINUTE) { - const num = getFormattedNumber(duration / MINUTE); - return `${num}${DURATION_LABELS.min}`; - } - - if (duration >= SECOND) { - const num = getFormattedNumber(duration / SECOND); - return `${num}${DURATION_LABELS.s}`; + for (const limit of DURATIONS) { + if (duration >= limit) { + const num = getFormattedNumber(duration / limit); + return `${num}${DURATION_LABELS[limit as keyof typeof DURATION_LABELS]}`; + } } + return `${getFormattedNumber(duration)}ms`; +} - const num = getFormattedNumber(duration); - return `${num}${DURATION_LABELS.ms}`; +export function getFormattedSpanDuration(span: { timestamp: number; start_timestamp: number }): string { + return getFormattedDuration(span.timestamp - span.start_timestamp); }