Class: Central::Support::IterationService
- Inherits:
-
Object
- Object
- Central::Support::IterationService
- Defined in:
- lib/central/support/iteration_service.rb
Constant Summary collapse
- DAYS_IN_WEEK =
(1.week / 1.day)
- VELOCITY_ITERATIONS =
3
Instance Attribute Summary collapse
-
#project ⇒ Object
readonly
Returns the value of attribute project.
Instance Method Summary collapse
- #backlog_iterations(velocity_value = velocity) ⇒ Object
- #bugs_impact(stories) ⇒ Object
- #calculate_iterations! ⇒ Object
- #current_iteration_details ⇒ Object
- #current_iteration_number ⇒ Object
- #date_for_iteration_number(iteration_number) ⇒ Object
-
#fix_owner! ⇒ Object
FIXME must figure out why the Story allows a nil owner in delivered states.
- #group_by_bugs ⇒ Object
- #group_by_developer ⇒ Object
- #group_by_iteration ⇒ Object
- #group_by_velocity ⇒ Object
-
#initialize(project, since = nil) ⇒ IterationService
constructor
A new instance of IterationService.
- #iteration_number_for_date(compare_date) ⇒ Object
- #iteration_start_date(date = nil) ⇒ Object
- #standard_deviation(groups = [], sample = false) ⇒ Object
- #stories_estimates(stories) ⇒ Object
- #velocity(number_of_iterations = VELOCITY_ITERATIONS) ⇒ Object
- #volatility(number_of_iterations = VELOCITY_ITERATIONS) ⇒ Object
Constructor Details
#initialize(project, since = nil) ⇒ IterationService
Returns a new instance of IterationService.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/central/support/iteration_service.rb', line 14 def initialize(project, since = nil) @project = project relation = project.stories.includes(:owned_by) relation = relation.where('accepted_at > ? or accepted_at is null', since) if since @stories = relation.to_a @accepted_stories = @stories. select { |story| story.column == '#done' }. select { |story| story.accepted_at < iteration_start_date(Time.current) } calculate_iterations! fix_owner! @stories.each { |s| s.iteration_service = self } @backlog = ( @stories - @accepted_stories.select { |s| s.column == '#done' } ).sort_by(&:position) end |
Instance Attribute Details
#project ⇒ Object (readonly)
Returns the value of attribute project.
7 8 9 |
# File 'lib/central/support/iteration_service.rb', line 7 def project @project end |
Instance Method Details
#backlog_iterations(velocity_value = velocity) ⇒ Object
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/central/support/iteration_service.rb', line 153 def backlog_iterations(velocity_value = velocity) velocity_value = 1 if velocity_value < 1 @backlog_iterations ||= {} # mimics the project.js rebuildIteration() function @backlog_iterations[velocity_value] ||= begin current_iteration = Iteration.new(self, current_iteration_number, velocity_value) backlog_iteration = Iteration.new(self, current_iteration_number + 1, velocity_value) iterations = [current_iteration, backlog_iteration] @backlog. select { |story| story.column != '#chilly_bin' }. each do |story| if current_iteration.can_take_story?(story) current_iteration << story else if !backlog_iteration.can_take_story?(story) # Iterations sometimes 'overflow', i.e. an iteration may contain a # 5 point story but the project velocity is 1. In this case, the # next iteration that can have a story added is the current + 4. next_number = backlog_iteration.number + 1 + (backlog_iteration.overflows_by / velocity_value).ceil backlog_iteration = Iteration.new(self, next_number, velocity_value) iterations << backlog_iteration end backlog_iteration << story end end iterations end end |
#bugs_impact(stories) ⇒ Object
101 102 103 104 105 106 107 108 109 |
# File 'lib/central/support/iteration_service.rb', line 101 def bugs_impact(stories) stories.map do |story| if Story::ESTIMABLE_TYPES.include? story.story_type 0 else 1 end end end |
#calculate_iterations! ⇒ Object
60 61 62 63 64 65 66 67 |
# File 'lib/central/support/iteration_service.rb', line 60 def calculate_iterations! @accepted_stories.each do |record| iteration_number = iteration_number_for_date(record.accepted_at) iteration_start_date = date_for_iteration_number(iteration_number) record.iteration_number = iteration_number record.iteration_start_date = iteration_start_date end end |
#current_iteration_details ⇒ Object
182 183 184 185 186 187 188 189 |
# File 'lib/central/support/iteration_service.rb', line 182 def current_iteration_details current_iteration = backlog_iterations.first %w(started finished delivered accepted rejected).reduce({}) do |data, state| data.merge(state => current_iteration. select { |story| story.state == state }. reduce(0) { |points, story| points + (story.estimate || 0) } ) end end |
#current_iteration_number ⇒ Object
56 57 58 |
# File 'lib/central/support/iteration_service.rb', line 56 def current_iteration_number iteration_number_for_date(Time.current) end |
#date_for_iteration_number(iteration_number) ⇒ Object
51 52 53 54 |
# File 'lib/central/support/iteration_service.rb', line 51 def date_for_iteration_number(iteration_number) difference = (iteration_length * DAYS_IN_WEEK) * (iteration_number - 1) iteration_start_date + difference.days end |
#fix_owner! ⇒ Object
FIXME must figure out why the Story allows a nil owner in delivered states
70 71 72 73 74 75 |
# File 'lib/central/support/iteration_service.rb', line 70 def fix_owner! @dummy_user ||= User.find_or_create_by!(username: "dummy", email: "[email protected]", name: "Dummy", initials: "XX") @accepted_stories. select { |record| record.owned_by.nil? }. each { |record| record.owned_by = @dummy_user } end |
#group_by_bugs ⇒ Object
111 112 113 114 115 116 117 118 119 120 |
# File 'lib/central/support/iteration_service.rb', line 111 def group_by_bugs @group_by_bugs ||= @accepted_stories. group_by { |story| story.iteration_number }. reduce({}) do |group, iteration| group.merge(iteration.first => bugs_impact(iteration.last)) end. reduce({}) do |group, iteration| group.merge(iteration.first => iteration.last.reduce(&:+)) end end |
#group_by_developer ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/central/support/iteration_service.rb', line 135 def group_by_developer @group_by_developer ||= begin min_iteration = @accepted_stories.map(&:iteration_number).min max_iteration = @accepted_stories.map(&:iteration_number).max @accepted_stories. group_by { |story| story.owned_by.name }. map do |owner| # all multiple series must have all the same keys or they will mess the graph data = (min_iteration..max_iteration).reduce({}) { |group, key| group.merge(key => 0)} owner.last.group_by { |story| story.iteration_number }. each do |iteration| data[iteration.first] = stories_estimates(iteration.last).reduce(&:+) end { name: owner.first, data: data } end end end |
#group_by_iteration ⇒ Object
77 78 79 80 81 82 83 |
# File 'lib/central/support/iteration_service.rb', line 77 def group_by_iteration @group_by_iteration ||= @accepted_stories. group_by { |story| story.iteration_number }. reduce({}) do |group, iteration| group.merge(iteration.first => stories_estimates(iteration.last)) end end |
#group_by_velocity ⇒ Object
95 96 97 98 99 |
# File 'lib/central/support/iteration_service.rb', line 95 def group_by_velocity @group_by_velocity ||= group_by_iteration.reduce({}) do |group, iteration| group.merge(iteration.first => iteration.last.reduce(&:+)) end end |
#iteration_number_for_date(compare_date) ⇒ Object
44 45 46 47 48 49 |
# File 'lib/central/support/iteration_service.rb', line 44 def iteration_number_for_date(compare_date) compare_date = compare_date.to_time if compare_date.is_a?(Date) days_apart = ( compare_date - iteration_start_date ) / 1.day days_in_iteration = iteration_length * DAYS_IN_WEEK ( days_apart / days_in_iteration ).floor + 1 end |
#iteration_start_date(date = nil) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/central/support/iteration_service.rb', line 32 def iteration_start_date(date = nil) date = start_date if date.nil? iteration_start_date = date.beginning_of_day if start_date.wday != iteration_start_day day_difference = start_date.wday - iteration_start_day day_difference += DAYS_IN_WEEK if day_difference < 0 iteration_start_date -= day_difference.days end iteration_start_date end |
#standard_deviation(groups = [], sample = false) ⇒ Object
191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/central/support/iteration_service.rb', line 191 def standard_deviation(groups = [], sample = false) return 0 if groups.empty? # algorithm: https://www.mathsisfun.com/data/standard-deviation-formulas.html # mean = groups.sum.to_f / groups.size.to_f differences_sqr = groups.map { |velocity| (velocity.to_f - mean) ** 2 } count = sample ? (groups.size - 1) : groups.size variance = differences_sqr.sum / count.to_f Math.sqrt(variance) end |
#stories_estimates(stories) ⇒ Object
85 86 87 88 89 90 91 92 93 |
# File 'lib/central/support/iteration_service.rb', line 85 def stories_estimates(stories) stories.map do |story| if Story::ESTIMABLE_TYPES.include? story.story_type story.estimate || 0 else 0 end end end |
#velocity(number_of_iterations = VELOCITY_ITERATIONS) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/central/support/iteration_service.rb', line 122 def velocity(number_of_iterations = VELOCITY_ITERATIONS) @velocity ||= {} @velocity[number_of_iterations] ||= begin number_of_iterations = group_by_iteration.size if number_of_iterations > group_by_iteration.size return 1 if number_of_iterations.zero? sum = group_by_velocity.values.slice((-1 * number_of_iterations)..-1).sum velocity = (sum / number_of_iterations).floor velocity < 1 ? 1 : velocity end end |
#volatility(number_of_iterations = VELOCITY_ITERATIONS) ⇒ Object
203 204 205 206 207 208 209 210 211 212 |
# File 'lib/central/support/iteration_service.rb', line 203 def volatility(number_of_iterations = VELOCITY_ITERATIONS) number_of_iterations = group_by_velocity.size if number_of_iterations > group_by_velocity.size is_sample = number_of_iterations != group_by_velocity.size last_iterations = group_by_velocity.values.reverse.take(number_of_iterations) std_dev = standard_deviation(last_iterations, is_sample) velocity_value = velocity(number_of_iterations) ( std_dev / velocity_value ) end |