Skip to content
Open
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
9 changes: 5 additions & 4 deletions frontend/src/components/Topics/Topic/Messages/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -130,13 +130,14 @@ const Message: React.FC<Props> = ({
<td>{offset}</td>
<td>{partition}</td>
<td>
<div>
{formatTimestamp({
<Tooltip
value={timeAgo(timestamp)}
content={formatTimestamp({
timestamp,
timezone: currentTimezone.value,
withMilliseconds: true,
})}
</div>
/>
</td>
<S.DataCell title={key}>
<Ellipsis text={renderFilteredJson(key, keyFilters)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(
Expand Down
58 changes: 58 additions & 0 deletions frontend/src/lib/__test__/dateTimeHelpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
passedTime,
calculateTimer,
formatMilliseconds,
timeAgo,
} from 'lib/dateTimeHelpers';

const startedAt = 1664891890889;
Expand Down Expand Up @@ -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');
});
});
40 changes: 40 additions & 0 deletions frontend/src/lib/dateTimeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Loading