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/app/models/sprint.rb

187 lines
7.2 KiB

renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
require 'date'
15 years ago
class Sprint < Version
unloadable
15 years ago
named_scope :open_sprints, lambda { |project|
{
renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
:order => 'sprint_start_date ASC, effective_date ASC',
15 years ago
:conditions => [ "status = 'open' and project_id = ?", project.id ]
}
}
def stories
return Story.sprint_backlog(self)
end
def points
return stories.sum('story_points')
end
15 years ago
def wiki_page
if ! project.wiki
return ''
end
if wiki_page_title.nil? || wiki_page_title.blank?
self.update_attribute(:wiki_page_title, Wiki.titleize(self.name))
end
page = project.wiki.find_page(self.wiki_page_title)
template = project.wiki.find_page(Setting.plugin_redmine_backlogs[:wiki_template])
if template and not page
page = WikiPage.new(:wiki => project.wiki, :title => self.wiki_page_title)
page.content = WikiContent.new
page.content.text = "h1. #{self.name}\n\n#{template.text}"
page.save!
end
return wiki_page_title
end
renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
def days(cutoff = nil)
# assumes mon-fri are working days, sat-sun are not. this
# assumption is not globally right, we need to make this configurable.
cutoff = self.effective_date if cutoff.nil?
return (self.sprint_start_date .. cutoff).select {|d| (d.wday > 0 and d.wday < 6) }
renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
end
def has_burndown
return !!(self.effective_date and self.sprint_start_date)
end
renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
def burndown
return nil if not self.has_burndown
renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
end_date = self.effective_date > Date.today ? Date.today : self.effective_date
so_far = self.days(end_date)
cached = {}
BurndownDay.find(:all, :order=>'created_at', :conditions => ["version_id = ?", self.id]).each {|data|
day = data.created_at.to_date
next if day > end_date or day < self.sprint_start_date
cached[day] = {
:points_committed => data.points_committed,
:points_resolved => data.points_resolved,
:points_accepted => data.points_accepted,
:remaining_hours => data.remaining_hours
}
}
backlog = nil
remaining_days = so_far.length
datapoints = []
max_points = 0
max_hours = 0
so_far.each { |day|
if cached.has_key?(day)
datapoint = cached[day]
else
if day == self.sprint_start_date or day == end_date
backlog = backlog.nil? ? self.stories : backlog
# no stories, nothing to do
renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
break if backlog.length == 0
datapoint = {
:points_committed => backlog.inject(0) {|sum, story| sum + story.story_points.to_f } ,
:points_resolved => backlog.select {|s| s.done_ratio == 100 }.inject(0) {|sum, story| sum + story.story_points.to_f },
:points_accepted => backlog.select {|s| s.closed? }.inject(0) {|sum, story| sum + story.story_points.to_f },
}
# start of sprint
if day == self.sprint_start_date
datapoint[:remaining_hours] = backlog.inject(0) {|sum, story| sum + story.estimated_hours.to_f }
else
datapoint[:remaining_hours] = backlog.select {|s| not s.closed? && s.done_ratio != 100 }.inject(0) {|sum, story| sum + story.remaining_hours.to_f }
renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
end
bdd = BurndownDay.new datapoint.merge(:created_at => day, :updated_at => day, :version_id => self.id)
bdd.save!
else
# we should never get here.
# for some reason the burndown wasn't generated on
# the specified day, return the last known values
# I don't save these because they're a) cheap to
# regenerate, and b) not actual measurements
datapoint = datapoints[-1].dup
end
end
if datapoint[:points_committed].class == NilClass or datapoint[:points_resolved].class == NilClass
datapoint[:required_burn_rate_points] = nil
else
datapoint[:required_burn_rate_points] = (datapoint[:points_committed] - datapoint[:points_resolved]) / remaining_days
end
max_points = [max_points, datapoint[:points_committed]].max
max_hours = [max_hours, datapoint[:remaining_hours]].max
if datapoint[:remaining_hours].class == NilClass
datapoint[:required_burn_rate_hours] = nil
else
datapoint[:required_burn_rate_hours] = datapoint[:remaining_hours] / remaining_days
end
datapoints << datapoint
remaining_days -= 1
}
datasets = {}
[ [:points_committed, :points],
[:points_resolved, :points],
renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
[:points_accepted, :points],
[:remaining_hours, :hours],
[:required_burn_rate_points, :points],
[:required_burn_rate_hours, :hours]].each { |series, units|
data = datapoints.collect {|d| d[series]}
if not data.select{|d| d != 0 and not d.class == NilClass }.empty?
renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
datasets[series] = { :units => units, :series => data }
end
}
if Setting.plugin_redmine_backlogs[:points_burn_direction] == 'down'
if datasets[:points_committed]
[[:points_accepted, :points_to_accept], [:points_resolved, :points_to_resolve]].each{|src, tgt|
continue if not datasets.include? src
datasets[tgt] = { :units => :points, :series => [] }
datasets[src][:series].each_with_index {|d, i|
datasets[tgt][:series] << (datasets[:points_committed][:series][i] - d)
}
}
end
# only show points committed if they're not constant
datasets.delete(:points_committed) if datasets[:points_committed] and datasets[:points_committed][:series].collect{|d| d != datasets[:points_committed][:series][0]}.empty?
datasets.delete(:points_resolved)
datasets.delete(:points_accepted)
end
# clear overlap between accepted/resolved
[[:points_resolved, :points_accepted], [:points_to_resolve, :points_to_accept]].each{|r, a|
datasets.delete(r) if datasets.has_key? r and datasets.has_key? a and datasets[a][:series] == datasets[r][:series]
}
renamed start_date, burndown generation, parameter quotation * start_date was added to the core Issue model, overriding our start_date. The core start_date is just an alias for the effective_date, so it's not useful to us. * burndown generation. Sprint.generate_burndown will generate the burndown for all current sprints by calling +generate+ on each of them. +generate+ picks what it can from the cache and interpolates the rest. The cache can be primed by just calling +generate+ at least once a day. Calling it multiple times will have no effect _unless_ you've changed a story or task, which will cause today's cache entry to be discarded. This way you will always have the latest data. The charts being generated: - points committed: the total of points included in the sprint. This would typically always be the same from the start, but will change if stories are added or removed mid-sprint - points resolved: when a story goes to 100% completed (typically by setting its tasks to closed), the story is deemed resolved. - points accepted: when a story is closed, it is deemed to be accepted by the PO - remaining hours: total of hours remaining on all tasks - required burn rate in points/hours: the number of points/hours the team will have to burn down each day to complete all the work at the end of the sprint. A flat line is perfect, if the line starts dropping it means the team is ahead of the curve, if the line starts climbing it means the team is running behind * parameter quotation added to raw sql
15 years ago
return { :dates => self.days, :series => datasets, :max => {:points => max_points, :hours => max_hours} }
end
def self.generate_burndown(only_current = true)
if only_current
conditions = ["? between sprint_start_date and effective_date", Date.today]
else
conditions = "1 = 1"
end
Version.find(:all, :conditions => conditions).each { |sprint|
sprint.burndown
}
end
15 years ago
end