1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
# frozen_string_literal: true
# == TimeTrackable concern
#
# Contains functionality related to objects that support time tracking.
#
# Used by Issue and MergeRequest.
#
module TimeTrackable
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
included do
attr_reader :time_spent, :time_spent_user, :spent_at, :summary
alias_method :time_spent?, :time_spent
validate :check_time_estimate
validate :check_negative_time_spent
has_many :timelogs, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent
before_save :set_time_estimate_default_value
after_save :clear_memoized_total_time_spent
end
def clear_memoized_total_time_spent
clear_memoization(:total_time_spent)
end
def reset
clear_memoized_total_time_spent
super
end
def reload(*args)
clear_memoized_total_time_spent
super(*args)
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def spend_time(options)
@time_spent = options[:duration]
@time_spent_note_id = options[:note_id]
@time_spent_user = User.find(options[:user_id])
@spent_at = options[:spent_at]
@summary = options[:summary]
@original_total_time_spent = nil
@category_id = category_id(options[:category])
return if @time_spent == 0
@timelog = if @time_spent == :reset
reset_spent_time
else
add_or_subtract_spent_time
end
end
alias_method :spend_time=, :spend_time
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def total_time_spent
sum = timelogs.sum(:time_spent)
# A new restriction has been introduced to limit total time spent to -
# Timelog::MAX_TOTAL_TIME_SPENT or 3.154e+7 seconds (approximately a year, a generous limit)
# Since there could be existing records that breach the limit, check and return the maximum/minimum allowed value.
# (some issuable might have total time spent that's negative because a validation was missing.)
sum.clamp(-Timelog::MAX_TOTAL_TIME_SPENT, Timelog::MAX_TOTAL_TIME_SPENT)
end
strong_memoize_attr :total_time_spent
def human_total_time_spent
Gitlab::TimeTrackingFormatter.output(total_time_spent)
end
def time_change
@timelog&.time_spent.to_i # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def human_time_change
Gitlab::TimeTrackingFormatter.output(time_change)
end
def human_time_estimate
Gitlab::TimeTrackingFormatter.output(time_estimate)
end
def time_estimate=(val)
val.is_a?(Integer) ? super([val, Gitlab::Database::MAX_INT_VALUE].min) : super(val)
end
def time_estimate
super || self.class.column_defaults['time_estimate']
end
def set_time_estimate_default_value
return if new_record?
return unless has_attribute?(:time_estimate)
# time estimate can be set to nil, in case of an invalid value, e.g. a String instead of a number, in which case
# we should not be overwriting it to default value, but rather have the validation catch the error
return if time_estimate_changed?
self.time_estimate = self.class.column_defaults['time_estimate'] if read_attribute(:time_estimate).nil?
end
private
def reset_spent_time
timelogs.new(time_spent: total_time_spent * -1, user: @time_spent_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def add_or_subtract_spent_time
timelogs.new(
time_spent: time_spent,
note_id: @time_spent_note_id,
user: @time_spent_user,
spent_at: @spent_at,
summary: @summary,
timelog_category_id: @category_id
)
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def check_negative_time_spent
return if time_spent.nil? || time_spent == :reset
if time_spent < 0 && (time_spent.abs > original_total_time_spent)
errors.add(:base, _('Time to subtract exceeds the total time spent'))
end
end
# we need to cache the total time spent so multiple calls to #valid?
# doesn't give a false error
def original_total_time_spent
@original_total_time_spent ||= total_time_spent
end
def check_time_estimate
# we'll set the time_tracking to zero at DB level through default value
return unless time_estimate_changed?
return if read_attribute(:time_estimate).is_a?(Numeric) && read_attribute(:time_estimate) >= 0
errors.add(:time_estimate, _('must have a valid format and be greater than or equal to zero.'))
end
def category_id(category)
TimeTracking::TimelogCategory.find_by_name(project&.root_namespace, category).first&.id
end
end
|