OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
openproject/lib/backlogs_project_patch.rb

151 lines
6.1 KiB

require_dependency 'project'
module Backlogs
module ProjectPatch
def self.included(base) # :nodoc:
base.extend(ClassMethods)
base.send(:include, InstanceMethods)
end
module ClassMethods
end
module InstanceMethods
def active_sprint
return Sprint.find(:first,
:conditions => ["project_id = ? and status = 'open' and ? between sprint_start_date and effective_date", self.id, Time.now])
end
def scrum_statistics
## pretty expensive to compute, so if we're calling this multiple times, return the cached results
return @scrum_statistics if @scrum_statistics
stats = {}
score = []
backlog = Story.product_backlog(self)[0,10]
if backlog.length == 0
score << l(:product_backlog_empty)
elsif backlog.inject(true) {|unsized, story| unsized && story.story_points.nil? }
score << l(:product_backlog_unsized)
else
score << nil
end
active = self.active_sprint
if active
stats[:active_sprint] = active
score <<
(Issue.exists?(["id <> root_id and estimated_hours is NULL and fixed_version_id =? and tracker_id = ?", active.id, Task.tracker]) ?
l(:active_sprint_unsized_stories) : nil)
score << (
Issue.exists?(["id <> root_id and estimated_hours is NULL and fixed_version_id = ? and tracker_id = ?", active.id, Task.tracker]) ?
l(:active_sprint_unestimated_tasks) : nil)
score << (!active.activity ? l(:active_sprint_dormant) : nil)
end
## base sprint stats on the last 5 closed sprints
sprints = Sprint.find(:all,
:conditions => ["project_id = ? and status in ('closed', 'locked') and not(effective_date is null or sprint_start_date is null)", self.id],
:order => "effective_date desc",
:limit => 5)
planned_velocity = nil
if sprints.length == 0
stats[:sprints] = []
else
stats[:sprints] = sprints
sprint_ids = sprints.collect{|s| "#{s.id}"}.join(',')
story_trackers = Story.trackers.collect{|s| "#{s.object_id}"}.join(',')
score << (
Issue.exists?(["id = root_id and story_points is NULL and fixed_version_id in (#{sprint_ids}) and tracker_id in (?)", Story.trackers]) ?
l(:unsized_stories, {:sprints => sprints.length}) : nil)
score << (
Issue.exists?(["id <> root_id and estimated_hours is NULL and fixed_version_id in (#{sprint_ids}) and tracker_id = ?", Task.tracker]) ?
l(:unestimated_tasks, {:sprints => sprints.length}) : nil)
## average points per hour over the selected sprints
points_per_hour = nil
res = Project.connection.execute("
select avg(story_points), avg(estimated_hours)
from issues
where not story_points is null
and fixed_version_id in (#{sprint_ids})
and id = root_id
and tracker_id in (#{story_trackers})
")
res.each {|p, h|
points_per_hour = ((1.0 * p) / h) if h && h != 0
}
accepted = 0
committed = 0
days = 0
pph_count = 0
pph_diff = 0
sprints.each {|sprint|
days += sprint.days.length
bd = sprint.burndown('up')
if bd
_accepted = bd.points_accepted || [0]
_committed = bd.points_committed || [0]
_remaining = bd.remaining_hours || [0]
accepted += _accepted[-1]
committed += _committed[0]
if points_per_hour && _remaining[0] > 0
pph = (1.0 * _committed[0]) / _remaining[0]
pph_count += 1
pph_diff += (pph - points_per_hour).abs
end
end
}
if points_per_hour and pph_count > 0
pph_variance = (Integer(100 * (pph_diff / pph_count)) - 100).abs
score << (pph_variance > 10 ? l(:size_accuracy, {:pct => pph_variance}) : nil)
end
last_sprint = sprints[0]
score << (last_sprint.effective_date < -14.days.from_now.to_date ? l(:project_dormant) : nil) if !active
score << (!last_sprint.has_wiki_page ? l(:sprint_notes_missing) : nil)
stats[:average_days_per_sprint] = days / sprints.length
stats[:velocity] = accepted / sprints.length
planned_velocity = committed / sprints.length
stats[:days_per_point] = (stats[:average_days_per_sprint] * 1.0) / stats[:velocity] if stats[:velocity] > 0
end
stats[:velocity] ||= 0
score << (stats[:velocity] == 0 ? l(:no_velocity) : nil)
if stats[:velocity] > 0
planned_velocity ||= 0
mood = Integer((100.0 * planned_velocity) / stats[:velocity]) - 100
if mood > 10
score << l(:optimistic_velocity, {:pct => mood})
elsif mood < -10
score << l(:pessimistic_velocity, {:pct => mood})
else
score << nil
end
end
stats[:score] = {
:score => (10 * (score.size - score.compact.size)) / score.size,
:errors => score.compact
}
@scrum_statistics = stats
return @scrum_statistics
end
end
end
end
Project.send(:include, Backlogs::ProjectPatch) unless Project.included_modules.include? Backlogs::ProjectPatch