Skip timeago elements with an unparsable datetime

What does this MR do and why?

Server-rendered <time class="js-timeago"> elements are enhanced on page load by localTimeAgo. It replaces the text with a relative time ("2 days ago") and sets a tooltip with the absolute date via Intl.DateTimeFormat.

The gitlab-test fixture repository contains a commit with a committer date of year 292277024627, beyond the range JavaScript Date accepts. new Date() returns an Invalid Date. Intl.DateTimeFormat.format then throws RangeError: Invalid time value inside the idle callback, so the error is uncaught.

In feature specs this logged Uncaught RangeError: Invalid time value in 14 examples across 6 spec files. All of them render a branch or commit list that includes that commit:

  • spec/features/projects/branches_spec.rb
  • spec/features/projects/branches/user_views_branches_spec.rb
  • spec/features/projects/branches/user_deletes_branch_spec.rb
  • spec/features/protected_branches_spec.rb
  • ee/spec/features/projects/protected_branches_spec.rb
  • spec/features/projects/members/member_leaves_project_spec.rb

Real-world relevance: git allows any 64-bit committer timestamp, so a real repository can contain such a commit and break the other timestamps on its pages.

The fix filters the elements to those whose datetime parses to a valid Date, with the existing isValidDate helper. Invalid elements keep their server-rendered text and title. Valid elements render as before. This mirrors what initLocalDateTimes and the print handler in the same file already do with a try/catch.

The Vue component TimeAgoTooltip has the same problem on its own path. Its tooltipText computed calls Intl.DateTimeFormat through the timeago mixin, so an invalid time prop throws during render. This is the stack trace in the Sentry issue linked below. Following review feedback, the component now checks isValidDate too. An invalid time renders as its raw value with no tooltip. The commit list page had a local guard around the component for the same case, added in !233945 (merged). That guard is removed, because the component now covers it. The other two guards from that MR stay, because they format dates without the component.

Changes made:

  • app/assets/javascripts/lib/utils/datetime/timeago_utility.js — filter to parsable elements before rendering text and tooltips
  • spec/frontend/lib/utils/datetime/timeago_utility_spec.js — new block "when an element has a datetime that cannot be parsed" with two cases: the invalid element keeps its text and title and nothing throws; a sibling valid element still renders
  • app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue — new computed isParsable; an invalid time renders as raw text with no title or aria-label
  • spec/frontend/vue_shared/components/time_ago_tooltip_spec.js — new block "when time cannot be parsed"
  • app/assets/javascripts/projects/commits/components/commit_list_item.vue — remove the hasParsableAuthoredDate guard and fallback <span>; render <timeago-tooltip> unconditionally
  • spec/frontend/projects/commits/components/commit_list_item_spec.js — remove the fallback block now covered by the component spec

How to set up and validate locally

  1. Use a project that contains the gitlab-test repository. The default GDK seed project "gitlab-test" has the branch spooky-stuff.

  2. Open Code > Branches > All.

  3. Open the browser DevTools console.

  4. Check there is no RangeError: Invalid time value.

  5. Check the spooky-stuff row still shows the date text "Dec 06, 292277024627".

    Branches list with the far-future commit date kept as text
  6. Check the other rows show relative times with tooltips.

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

References

🤖 Generated with Claude Code

Edited by Miguel Rincon

Merge request reports

Loading
Loading