1# frozen_string_literal: true
2
3module Milestoneish
4  DISPLAY_ISSUES_LIMIT = 500
5
6  def total_issues_count
7    @total_issues_count ||= Milestones::IssuesCountService.new(self).count
8  end
9
10  def closed_issues_count
11    @close_issues_count ||= Milestones::ClosedIssuesCountService.new(self).count
12  end
13
14  def opened_issues_count
15    total_issues_count - closed_issues_count
16  end
17
18  def total_merge_requests_count
19    @total_merge_request_count ||= Milestones::MergeRequestsCountService.new(self).count
20  end
21
22  def complete?
23    total_issues_count > 0 && total_issues_count == closed_issues_count
24  end
25
26  def percent_complete
27    closed_issues_count * 100 / total_issues_count
28  rescue ZeroDivisionError
29    0
30  end
31
32  def remaining_days
33    return 0 if !due_date || expired?
34
35    (due_date - Date.today).to_i
36  end
37
38  def elapsed_days
39    return 0 if !start_date || start_date.future?
40
41    (Date.today - start_date).to_i
42  end
43
44  def issues_visible_to_user(user)
45    memoize_per_user(user, :issues_visible_to_user) do
46      IssuesFinder.new(user, issues_finder_params)
47        .execute.preload(:assignees).where(milestone_id: milestoneish_id)
48    end
49  end
50
51  def issue_participants_visible_by_user(user)
52    User.joins(:issue_assignees)
53      .where('issue_assignees.issue_id' => issues_visible_to_user(user).select(:id))
54      .distinct
55  end
56
57  def issue_labels_visible_by_user(user)
58    Label.joins(:label_links)
59      .where('label_links.target_id' => issues_visible_to_user(user).select(:id), 'label_links.target_type' => 'Issue')
60      .distinct
61  end
62
63  def sorted_issues(user)
64    # This method is used on milestone view to filter opened assigned, opened unassigned and closed issues columns.
65    # We want a limit of DISPLAY_ISSUES_LIMIT for total issues present on all columns.
66    limited_ids =
67      issues_visible_to_user(user).sort_by_attribute('label_priority').limit(DISPLAY_ISSUES_LIMIT)
68
69    Issue
70      .where(id: Issue.select(:id).from(limited_ids))
71      .preload_associated_models
72      .sort_by_attribute('label_priority')
73  end
74
75  def sorted_merge_requests(user)
76    merge_requests_visible_to_user(user).sort_by_attribute('label_priority')
77  end
78
79  def merge_requests_visible_to_user(user)
80    memoize_per_user(user, :merge_requests_visible_to_user) do
81      MergeRequestsFinder.new(user, issues_finder_params)
82        .execute.where(milestone_id: milestoneish_id)
83    end
84  end
85
86  def upcoming?
87    start_date && start_date.future?
88  end
89
90  def expires_at
91    if due_date
92      if due_date.past?
93        "expired on #{due_date.to_s(:medium)}"
94      else
95        "expires on #{due_date.to_s(:medium)}"
96      end
97    end
98  end
99
100  def expired?
101    due_date && due_date.past?
102  end
103
104  def expired
105    expired? || false
106  end
107
108  def total_time_spent
109    @total_time_spent ||= issues.joins(:timelogs).sum(:time_spent) + merge_requests.joins(:timelogs).sum(:time_spent)
110  end
111
112  def human_total_time_spent
113    Gitlab::TimeTrackingFormatter.output(total_time_spent)
114  end
115
116  def total_time_estimate
117    @total_time_estimate ||= issues.sum(:time_estimate) + merge_requests.sum(:time_estimate)
118  end
119
120  def human_total_time_estimate
121    Gitlab::TimeTrackingFormatter.output(total_time_estimate)
122  end
123
124  private
125
126  def memoize_per_user(user, method_name)
127    memoized_users[method_name][user&.id] ||= yield
128  end
129
130  def memoized_users
131    @memoized_users ||= Hash.new { |h, k| h[k] = {} }
132  end
133
134  # override in a class that includes this module to get a faster query
135  # from IssuesFinder
136  def issues_finder_params
137    {}
138  end
139end
140