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