From 4ce6669e26ba4c55e8ca3bc3a9590e128eee2f30 Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Sat, 30 May 2026 14:55:56 -0400 Subject: [PATCH] fix(ui): guard invalid log detail timestamps --- ui/src/components/logs/logs-detail-panel.tsx | 3 ++- ui/src/components/logs/utils.ts | 13 +++++++++ .../ui/components/logs-detail-panel.test.tsx | 27 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 ui/tests/unit/ui/components/logs-detail-panel.test.tsx diff --git a/ui/src/components/logs/logs-detail-panel.tsx b/ui/src/components/logs/logs-detail-panel.tsx index 09c68564..4ff0d38d 100644 --- a/ui/src/components/logs/logs-detail-panel.tsx +++ b/ui/src/components/logs/logs-detail-panel.tsx @@ -9,6 +9,7 @@ import { LogLevelBadge } from './log-level-badge'; import { LogsEmpty } from './logs-empty'; import { formatJson, + formatLogTimestampIso, getDisplayLatency, getDisplayModule, getDisplayRequestId, @@ -34,7 +35,7 @@ interface OverviewRow { function buildOverviewRows(entry: LogsEntry, sourceLabel?: string): OverviewRow[] { // Use shared accessors so this panel and the list row never diverge. return [ - { label: 'Time', value: new Date(entry.timestamp).toISOString(), mono: true }, + { label: 'Time', value: formatLogTimestampIso(entry.timestamp), mono: true }, { label: 'Level', value: entry.level }, { label: 'Module', value: getDisplayModule(entry, sourceLabel) }, { label: 'Stage', value: getDisplayStage(entry) }, diff --git a/ui/src/components/logs/utils.ts b/ui/src/components/logs/utils.ts index 1c77dbf3..11180fc5 100644 --- a/ui/src/components/logs/utils.ts +++ b/ui/src/components/logs/utils.ts @@ -21,6 +21,19 @@ export function formatLogTimestamp(timestamp: string | null | undefined) { }).format(date); } +export function formatLogTimestampIso(timestamp: string | null | undefined) { + if (!timestamp) { + return 'No activity yet'; + } + + const date = new Date(timestamp); + if (Number.isNaN(date.getTime())) { + return timestamp; + } + + return date.toISOString(); +} + export function formatRelativeLogTime(timestamp: string | null | undefined) { if (!timestamp) { return 'No activity yet'; diff --git a/ui/tests/unit/ui/components/logs-detail-panel.test.tsx b/ui/tests/unit/ui/components/logs-detail-panel.test.tsx new file mode 100644 index 00000000..4e57da9c --- /dev/null +++ b/ui/tests/unit/ui/components/logs-detail-panel.test.tsx @@ -0,0 +1,27 @@ +import { render, screen } from '@tests/setup/test-utils'; +import { describe, expect, it } from 'vitest'; +import { LogsDetailPanel } from '@/components/logs/logs-detail-panel'; +import type { LogsEntry } from '@/lib/api-client'; + +function buildEntry(overrides: Partial = {}): LogsEntry { + return { + id: 'entry-1', + timestamp: '2026-04-07T11:00:00.000Z', + level: 'info', + source: 'dashboard', + event: 'logs.bootstrap', + message: 'Dashboard log entry', + processId: 25582, + runId: 'run-1', + ...overrides, + }; +} + +describe('LogsDetailPanel', () => { + it('falls back to the raw timestamp when the selected log entry has an invalid timestamp', () => { + render(); + + expect(screen.getByText('not-a-date')).toBeInTheDocument(); + expect(screen.getByTitle('not-a-date')).toHaveTextContent('not-a-date'); + }); +});