Speed up roadmap rendering when milestones are shown

What does this MR do and why?

MilestoneTimeline creates a MilestoneItem for every milestone in every column, though only the one in the milestone's start column draws. By week over three years, 100 milestones make 16,200 instances, and on a production-mode build in Chromium the longest task takes 2.1 to 2.3 s.

Each MilestoneItem is now created once, in its start column, and the longest task drops to 0.11 to 0.13 s. EpicItem has rendered EpicItemTimeline this way since !50652 (merged), where @euko offered to do the same for milestones if the gain was meaningful.

milestone_timeline.vue repeats the three hasStartDateFor* mixin checks to find the start column. Moving them into shared helpers touches epic code, so it is tracked in #632774.

Render path

flowchart TB
  subgraph before["master: per column"]
    direction TB
    A1["MilestoneTimeline<br/>T timeline columns"] --> A2["each column: v-for over all M milestones"]
    A2 --> A3["M x T MilestoneItem instances"]
    A3 --> A4{"hasStartDate<br/>in this column?"}
    A4 -->|"1 of T"| A5["draw the bar"]
    A4 -->|"T - 1 of T"| A6["empty 32 px wrapper<br/>keeps the row"]
  end
  subgraph after["this MR: once"]
    direction TB
    B1["MilestoneTimeline<br/>T timeline columns"] --> B2["milestonesByTimeframeIndex:<br/>start column of each milestone"]
    B2 --> B3["M MilestoneItem instances,<br/>each in its start column"]
    B3 --> B4["absolute, top = row x 32 px<br/>bar drawn as before"]
    B1 --> B5["one spacer per column,<br/>M x 32 px tall"]
  end
  before ~~~ after

References

Screenshots or screen recordings

No visual change: in Chromium, with the same 100 milestones seeded, the roadmap renders identical pixels in all six layouts (by month and by week this year, by week this quarter, and by quarter, month and week within 3 years), and hovering a milestone shows the same popover. Firefox renders identical pixels by month this year and by week within 3 years.

Before After
before after

How to set up and validate locally

  1. In a group with an epic dated this year, create 100 milestones from the Rails console, with <group-path> replaced by the group's path:

    group = Group.find_by_full_path('<group-path>'); 100.times { |i| group.milestones.create!(title: "Roadmap #{i}", start_date: Date.current.beginning_of_year + (3 * i).days, due_date: Date.current.beginning_of_year + (3 * i + 14).days) }
  2. Open the group's roadmap, and in Settings choose Within 3 years and By week.

  3. Each milestone renders once at its start date, with one .timeline-bar-wrapper per milestone.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Andrew Dunn

Merge request reports

Loading
Loading