Better stats

pull/6827/head
friflaj 15 years ago
parent 46c2275495
commit 573fc9802b
  1. 2
      app/controllers/backlogs_global_controller.rb
  2. 46
      app/views/backlogs_global/statistics.rhtml
  3. 11
      config/locales/en.yml
  4. 15
      config/locales/nl.yml
  5. 67
      lib/project_patch.rb

@ -8,7 +8,7 @@ class BacklogsGlobalController < ApplicationController
:conditions => ["enabled_modules.name = 'backlogs' and status = ?", Project::STATUS_ACTIVE],
:include => :project,
:joins => :project).collect { |mod| mod.project }
@projects.sort! {|a, b| a.scrum_statistics[:score] <=> b.scrum_statistics[:score]}
@projects.sort! {|a, b| a.scrum_statistics[:score][:score] <=> b.scrum_statistics[:score][:score]}
render :action => 'statistics'
end

@ -14,51 +14,11 @@
<div id="projects">
<% @projects.each do |project| %>
<% stats = project.scrum_statistics %>
<h3><a href="#"><span class="score score_<%= stats[:score] %>"><%= stats[:score] %></span> <%= project.name %></a> </h3>
<h3><a href="#"><span class="score score_<%= stats[:score][:score] %>"><%= stats[:score][:score] %></span> <%= project.name %></a> </h3>
<div>
<% if ! stats[:backlog_ready] %>
<% stats[:score][:errors].each do |e| %>
<div>
<%= l(:product_backlog_unprepared) %>
</div>
<% end %>
<% if ! stats[:active] %>
<div>
<%= l(:project_dormant) %>
</div>
<% end %>
<% if stats[:has_unsized] %>
<div>
<%= l(:unsized_stories, {:sprints => stats[:sprints].length}) %>
</div>
<% end %>
<% if stats[:has_unestimated] %>
<div>
<%= l(:unestimated_tasks, {:sprints => stats[:sprints].length}) %>
</div>
<% end %>
<% if ! stats[:velocity_predictable] %>
<div>
<%= l(:velocity_varies, {:variance => Integer((stats[:velocity_mismatch] || 0) * 100)}) %>
</div>
<% end %>
<% if ! stats[:stable_sizes] %>
<div>
<%= l(:size_accuracy, {:variance => Integer((stats[:points_per_hour_variance] || 0) * 100)}) %>
</div>
<% end %>
<% if stats[:sprint_notes_missing] %>
<div>
<%= l(:sprint_notes_missing) %>
</div>
<% end %>
<% if stats[:velocity] == 0 %>
<div>
<%= l(:no_velocity) %>
</div>
<% end %>
<% if stats[:planned_velocity] == 0 %>
<div>
<%= l(:no_planned_velocity) %>
<%= e %>
</div>
<% end %>
</div>

@ -26,12 +26,13 @@ en:
remaining_hours: "remaining hours"
field_velocity_based_estimate: "Velocity based estimate"
product_backlog_unprepared: "Product backlog is short or has unsized stories at the top"
project_dormant: "Project shows no activity"
product_backlog_empty: "Product backlog is empty"
product_backlog_unsized: "Top of the product backlog has unsized stories"
unsized_stories: "The last {{sprints}} sprints have unsized stories"
unestimated_tasks: "The last {{sprints}} sprints have unestimated tasks"
velocity_varies: "Velocity varies by {{variance}}%"
size_accuracy: "Story size estimates vary by {{variance}}%"
size_accuracy: "Story size estimates vary by {{pct}}%"
sprint_notes_missing: "Last sprint doesn't have sprint notes"
project_dormant: "Project shows no activity"
no_velocity: "Project has no velocity"
no_planned_velocity: "Project has no planned velocity"
optimistic_velocity: "Project plans too optimistically by {{pct}}%"
pessimistic_velocity: "Project plans too pessimistically by {{pct}}%"

@ -26,12 +26,13 @@ nl:
remaining_hours: "resterende uren"
field_velocity_based_estimate: "Schatting obv velocity"
product_backlog_unprepared: "Product backlog is kort of heeft unsized stories bovenaan"
product_backlog_empty: "Product backlog is leeg"
product_backlog_unsized: "De top van de product backlog heeft stories zonder sizes"
unsized_stories: "De laatste {{sprints}} sprints hebben stories zonder sizes"
unestimated_tasks: "De laatste {{sprints}} sprints hebben taken zonder urenschatting"
size_accuracy: "Story size estimates vary by {{pct}}%"
sprint_notes_missing: "De laatste sprint heeft geen notities"
project_dormant: "Project vertoont geen activiteit"
unsized_stories: "De laatste {{sprints}} sprints hebben unsized stories"
unestimated_tasks: "De laatste last {{sprints}} sprints hebben taken zonder schattingen"
velocity_varies: "Velocity varieert {{variance}}%"
size_accuracy: "Story size varieert {{variance}}% t.o.v. uren schattingen"
sprint_notes_missing: "Laatste sprint heeft geen notities"
no_velocity: "Project heeft geen velocity"
no_planned_velocity: "Project heeft geen geplande velocity"
optimistic_velocity: "Project plant {{pct}}% te optimistisch"
pessimistic_velocity: "Project plant {{pct}}% te pessimistisch"

@ -21,25 +21,40 @@ module ProjectPatch
return @scrum_statistics if @scrum_statistics
stats = {}
score = []
backlog = Story.product_backlog(self)[0,10]
stats[:backlog_ready] = (backlog.length) > 0 && (backlog.inject(true) {|unprep, story| unprep && !story.story_points.nil? })
if backlog.length == 0
score << l(:product_backlog_empty)
elsif backlog.inject(true) {|unprep, story| unprep && !story.story_points.nil? }
score << l(:product_backlog_unsized)
else
score << nil
end
active = self.active_sprint
stats[:active] = !!active && active.activity
active = active && active.activity
## 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] = sprints
sprint_ids = sprints.collect{|s| "#{s.id}"}.join(',')
story_trackers = Story.trackers.collect{|s| "#{s.object_id}"}.join(',')
stats[:has_unsized] = Issue.exists? ["id = root_id and story_points is NULL and fixed_version_id in (#{sprint_ids}) and tracker_id in (?)", Story.trackers]
stats[:has_unestimated] = Issue.exists? ["id <> root_id and estimated_hours is NULL and fixed_version_id in (#{sprint_ids}) and tracker_id = ?", Task.tracker]
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
@ -80,34 +95,42 @@ module ProjectPatch
end
}
if points_per_hour and pph_count > 0
stats[:points_per_hour_variance] = (pph_diff / pph_count)
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[-1]
stats[:active] |= (last_sprint.effective_date > -7.days.from_now.to_date)
stats[:sprint_notes_missing] = !last_sprint.has_wiki_page
active |= (last_sprint.effective_date > -7.days.from_now.to_date)
score << (!last_sprint.has_wiki_page ? l(:sprint_notes_missing) : nil)
stats[:average_days_per_sprint] = days / sprints.length
stats[:velocity] = accepted / sprints.length
stats[:planned_velocity] = committed / 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
score << (!active ? l(:project_dormant) : nil)
stats[:velocity] ||= 0
stats[:planned_velocity] ||= 0
stats[:velocity_mismatch] = (1 - (stats[:velocity] / stats[:planned_velocity])) if stats[:planned_velocity] > 0
score = {}
score[:backlog_ready] = stats[:backlog_ready]
score[:has_velocity] = stats[:velocity] != 0
score[:plans_velocity] = stats[:planned_velocity] != 0
score[:is_active] = stats[:active]
score[:all_sized] = !stats[:has_unsized]
score[:all_estimated] = !stats[:has_unestimated]
score[:stable_sizes] = stats[:points_per_hour_variance] && stats[:points_per_hour_variance] < 0.1
score[:has_sprint_notes] = !stats[:sprint_notes_missing]
score[:velocity_predictable] = stats[:planned_velocity] > 0 && stats[:velocity_mismatch].abs < 0.1
stats[:score] = (10 * score.values.inject(0){|sum, v| sum + (v ? 1 : 0)}) / score.keys.length
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

Loading…
Cancel
Save