diff --git a/frontend/src/components/Topics/Topic/Messages/Message.tsx b/frontend/src/components/Topics/Topic/Messages/Message.tsx index 53046c75c..6a5657f08 100644 --- a/frontend/src/components/Topics/Topic/Messages/Message.tsx +++ b/frontend/src/components/Topics/Topic/Messages/Message.tsx @@ -5,7 +5,7 @@ import MessageToggleIcon from 'components/common/Icons/MessageToggleIcon'; import IconButtonWrapper from 'components/common/Icons/IconButtonWrapper'; import { Dropdown, DropdownItem } from 'components/common/Dropdown'; import { ActionDropdownItem } from 'components/common/ActionComponent'; -import { formatTimestamp } from 'lib/dateTimeHelpers'; +import { formatTimestamp, timeAgo } from 'lib/dateTimeHelpers'; import { JSONPath } from 'jsonpath-plus'; import Ellipsis from 'components/common/Ellipsis/Ellipsis'; import WarningRedIcon from 'components/common/Icons/WarningRedIcon'; @@ -130,13 +130,14 @@ const Message: React.FC = ({ {offset} {partition} -
- {formatTimestamp({ + + /> diff --git a/frontend/src/components/Topics/Topic/Messages/__test__/Message.spec.tsx b/frontend/src/components/Topics/Topic/Messages/__test__/Message.spec.tsx index b9fd58b51..6d5763a08 100644 --- a/frontend/src/components/Topics/Topic/Messages/__test__/Message.spec.tsx +++ b/frontend/src/components/Topics/Topic/Messages/__test__/Message.spec.tsx @@ -6,7 +6,7 @@ import { render } from 'lib/testHelpers'; import userEvent from '@testing-library/user-event'; import useAppParams from 'lib/hooks/useAppParams'; import { TopicActionsProvider } from 'components/contexts/TopicActionsContext'; -import { formatTimestamp } from 'lib/dateTimeHelpers'; +import { timeAgo } from 'lib/dateTimeHelpers'; import { getDefaultActionMessage } from 'components/common/ActionComponent/ActionComponent'; import { UserInfoRolesAccessContext } from 'components/contexts/UserInfoRolesAccessContext'; import { RolesType } from 'lib/permissions'; @@ -78,12 +78,7 @@ describe('Message component', () => { expect(screen.getByText(mockMessage.value as string)).toBeInTheDocument(); expect(screen.getByText(mockMessage.key as string)).toBeInTheDocument(); expect( - screen.getByText( - formatTimestamp({ - timestamp: mockMessage.timestamp, - withMilliseconds: true, - }) - ) + screen.getByText(timeAgo(mockMessage.timestamp)) ).toBeInTheDocument(); expect(screen.getByText(mockMessage.offset.toString())).toBeInTheDocument(); expect( diff --git a/frontend/src/lib/__test__/dateTimeHelpers.spec.ts b/frontend/src/lib/__test__/dateTimeHelpers.spec.ts index c34371452..cda843681 100644 --- a/frontend/src/lib/__test__/dateTimeHelpers.spec.ts +++ b/frontend/src/lib/__test__/dateTimeHelpers.spec.ts @@ -2,6 +2,7 @@ import { passedTime, calculateTimer, formatMilliseconds, + timeAgo, } from 'lib/dateTimeHelpers'; const startedAt = 1664891890889; @@ -49,3 +50,60 @@ describe('calculate timer', () => { expect(calculateTimer(1664891890889199)).toBe('00:00'); }); }); + +describe('timeAgo', () => { + it('returns empty string for undefined timestamp', () => { + expect(timeAgo(undefined)).toBe(''); + }); + + it('returns empty string for invalid date', () => { + expect(timeAgo('invalid-date')).toBe(''); + }); + + it('returns "just now" for timestamps within 1 second', () => { + const now = new Date(); + expect(timeAgo(now)).toBe('just now'); + }); + + it('returns seconds ago for timestamps within a minute', () => { + const thirtySecondsAgo = new Date(Date.now() - 30 * 1000); + expect(timeAgo(thirtySecondsAgo)).toBe('30 seconds ago'); + }); + + it('returns "1 minute ago" for timestamps around 1 minute old', () => { + const oneMinuteAgo = new Date(Date.now() - 60 * 1000); + expect(timeAgo(oneMinuteAgo)).toBe('1 minute ago'); + }); + + it('returns minutes ago for timestamps within an hour', () => { + const twentyMinutesAgo = new Date(Date.now() - 20 * 60 * 1000); + expect(timeAgo(twentyMinutesAgo)).toBe('20 minutes ago'); + }); + + it('returns "1 hour ago" for timestamps around 1 hour old', () => { + const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); + expect(timeAgo(oneHourAgo)).toBe('1 hour ago'); + }); + + it('returns hours ago for timestamps within a day', () => { + const threeHoursAgo = new Date(Date.now() - 3 * 60 * 60 * 1000); + expect(timeAgo(threeHoursAgo)).toBe('3 hours ago'); + }); + + it('returns "1 day ago" for timestamps around 1 day old', () => { + const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); + expect(timeAgo(oneDayAgo)).toBe('1 day ago'); + }); + + it('returns days ago for timestamps within 30 days', () => { + const fiveDaysAgo = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000); + expect(timeAgo(fiveDaysAgo)).toBe('5 days ago'); + }); + + it('returns formatted date for timestamps older than 30 days', () => { + const sixtyDaysAgo = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000); + const result = timeAgo(sixtyDaysAgo); + // Should return a locale date string, not "X days ago" + expect(result).not.toContain('days ago'); + }); +}); diff --git a/frontend/src/lib/dateTimeHelpers.ts b/frontend/src/lib/dateTimeHelpers.ts index 52caa08ba..141ab4a7d 100644 --- a/frontend/src/lib/dateTimeHelpers.ts +++ b/frontend/src/lib/dateTimeHelpers.ts @@ -77,3 +77,43 @@ export const calculateTimer = (startedAt: number) => { return `${passedTime(minutes)}:${passedTime(seconds)}`; }; + +export const timeAgo = ( + timestamp: number | string | Date | undefined +): string => { + if (!timestamp) { + return ''; + } + + const date = new Date(timestamp); + if (Number.isNaN(date.getTime())) { + return ''; + } + + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffSeconds = Math.floor(diffMs / 1000); + const diffMinutes = Math.floor(diffSeconds / 60); + const diffHours = Math.floor(diffMinutes / 60); + const diffDays = Math.floor(diffHours / 24); + + if (diffSeconds < 60) { + return diffSeconds <= 1 ? 'just now' : `${diffSeconds} seconds ago`; + } + + if (diffMinutes < 60) { + return diffMinutes === 1 ? '1 minute ago' : `${diffMinutes} minutes ago`; + } + + if (diffHours < 24) { + return diffHours === 1 ? '1 hour ago' : `${diffHours} hours ago`; + } + + if (diffDays < 30) { + return diffDays === 1 ? '1 day ago' : `${diffDays} days ago`; + } + + // Fall back to formatted date for older timestamps + const language = navigator.language || navigator.languages[0]; + return date.toLocaleDateString(language); +};