Clean up patches Part III

Removing Statistics feature
pull/6827/head
Gregor Schmidt 13 years ago
parent 1f7bfe7059
commit d1b101261b
  1. 14
      app/controllers/rb_statistics_controller.rb
  2. 10
      app/models/sprint.rb
  3. 67
      app/views/rb_statistics/show.html.erb
  4. 6
      app/views/shared/_settings.html.erb
  5. 3
      config/locales/de.yml
  6. 5
      config/locales/en-GB.yml
  7. 3
      config/locales/en.yml
  8. 2
      config/locales/fr.yml
  9. 2
      config/locales/nl.yml
  10. 2
      config/locales/pt-BR.yml
  11. 6
      config/locales/zh.yml
  12. 1
      config/routes.rb
  13. 42
      features/statistics.feature
  14. 8
      features/step_definitions/_given_steps.rb
  15. 4
      features/step_definitions/_when_steps.rb
  16. 3
      features/support/paths.rb
  17. 8
      init.rb
  18. 10
      lib/backlogs_hooks.rb
  19. 221
      lib/backlogs_project_patch.rb
  20. 1
      lib/backlogs_query_patch.rb
  21. 9
      lib/redmine_backlogs/patches/issue_patch.rb

@ -1,14 +0,0 @@
class RbStatisticsController < RbApplicationController
unloadable
skip_before_filter :authorize
before_filter :authorize_global
def show
@projects = EnabledModule.find(:all,
: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}
end
end

@ -81,16 +81,6 @@ class Sprint < Version
(self.start_date .. cutoff).select {|d| alldays || (d.wday > 0 and d.wday < 6) }
end
def eta
return nil unless self.start_date
dpp = self.project.scrum_statistics.info[:average_days_per_point]
return nil unless dpp
# assume 5 out of 7 are working days
self.start_date + Integer(self.points * dpp * 7.0/5)
end
def has_burndown?
!!(self.effective_date and self.start_date)
end

@ -1,67 +0,0 @@
<%- content_for :header_tags do %>
<%= stylesheet_link_tag 'rb_default/statistics', 'rb_default/jquery-ui', :plugin => 'redmine_backlogs' %>
<%= javascript_include_tag_backlogs 'lib/jquery.js',
'lib/jquery-ui.js' %>
<%- end %>
<script>
var $j = jQuery.noConflict();
$j(document).ready(function(){
$j("#projects").accordion({ collapsible: true, header: 'h3'});
});
</script>
<div id="projects">
<% global = {} %>
<% @projects.each do |project| %>
<%
stats = project.scrum_statistics
scores = stats.scores
errors = stats.errors('backlogs_')
info = stats.info
global = stats.merge(global, 'backlogs_')
%>
<h3><a href="#"><span class="score score_<%= stats.score %>"><%= stats.score %></span> <%= project.name %></a> </h3>
<div>
<% if !scores[:velocity_missing] %>
<%= l(:label_sprint_velocity, { :velocity => info[:velocity], :sprints => info[:closed_sprints].length, :days => info[:average_days_per_sprint]}) %><br/>
<% end %>
<h4><%= l(:backlogs_product_backlog) %></h4>
<%= link_to(l(:backlogs_product_backlog), { :controller => 'rb_queries', :action => 'show', :project_id => project }) %><br/>
<% if info[:active_sprint] || info[:closed_sprints] %>
<h4><%= l(:backlogs_sprints) %></h4>
<% if info[:active_sprint] %>
<%= link_to(info[:active_sprint].name, {
:controller => 'rb_queries',
:action => 'show',
:project_id => project,
:sprint_id => info[:active_sprint].id }) %> (<%= l(:backlogs_active) %>)<br/>
<% end %>
<% if info[:closed_sprints] %>
<% info[:closed_sprints].each do |sprint| %>
<%= link_to(sprint.name, { :controller => 'rb_queries', :action => 'show', :project_id => project, :sprint_id => sprint.id }) %><br/>
<% end %>
<% end %>
<% end %>
<% errors.each do |e| %>
<ul>
<li><%= l(e) %></li>
</ul>
<% end %>
</div>
<% end %>
</div>
<% if @projects.size > 0 %>
<h3>Overall</h3>
<table>
<% global.each_pair do |e, n| %>
<tr>
<td><%= l(e) %></td>
<td><b><%= (n * 100) / @projects.size %>%</b></td>
<td><span class="score_<%= 10 - ((n * 10) / @projects.size) %>">&nbsp;&nbsp;</td>
</tr>
<% end %>
</table>
<% end %>

@ -44,12 +44,6 @@
<%= select_tag("settings[task_tracker]",
options_from_collection_for_select(Tracker.all, :id, :name, Task.tracker)) %>
</p>
<p>
<%= label_tag("settings[show_statistics]", l('backlogs.show_statistics')) %>
<%= check_box_tag("settings[show_statistics]",
"1",
Setting.plugin_redmine_backlogs[:show_statistics]) %>
</p>
<p>
<%= label_tag("settings[points_burn_direction]", l(:backlogs_points_burn_direction)) %>
<%= select_tag("settings[points_burn_direction]",

@ -27,7 +27,6 @@ de:
points_to_resolve: "Nicht beschlossene Punkte"
points_resolved: "Beschlossene Punkte"
remaining_hours: "Verbleibende Stunden"
field_velocity_based_estimate: "Geschwindigkeitsbasierende Absch\xC3\xA4tzung"
backlogs_active: "aktiv"
@ -41,7 +40,6 @@ de:
label_back_to_project: "Zur\xC3\xBCck zur Projektseite"
label_sprint_name: "Sprint \"%{name}\""
label_backlogs: "Backlogs"
label_scrum_statistics: "Scrum Statistiken"
label_backlog: "Backlog"
label_sprint_backlog: "Sprint Backlog"
@ -114,7 +112,6 @@ de:
remaining_hours: "Verbleibende Stunden"
remaining_hours_ideal: "Verbleibende Stunden (ideal)"
show_burndown_chart: "Burndown-Chart"
show_statistics: "Scrum Statistiken anzeigen"
story: "Story"
story_points: "Story Points"
story_points_ideal: "Story Points (ideal)"

@ -43,7 +43,6 @@ en-GB:
points_to_resolve: points not resolved
label_select_all: Select all
backlogs_sprint_unestimated: Closed or active sprints with unestimated stories
label_scrum_statistics: Scrum statistics
required_burn_rate_points: required burn rate (points)
error_blank: cannot be blank
points_to_accept: points not accepted
@ -75,7 +74,6 @@ en-GB:
%{description}
label_burndown: Burndown
rb_label_copy_tasks_all: "[[All]]"
field_velocity_based_estimate: Velocity based estimate
todo_issue_description: |-
%{summary}: %{url}
%{description}
@ -95,7 +93,6 @@ en-GB:
error_must_block_at_least_one_issue: must contain the ID of at least one ticket
backlogs:
show_statistics: 'Show Scrum statistics'
show_burndown_chart: 'Burndown Chart'
add_new_story: 'New Story'
column_with: "Column width:"
@ -103,4 +100,4 @@ en-GB:
task: Task
story: Story
any: any

@ -27,7 +27,6 @@ en:
points_to_resolve: "points not resolved"
points_resolved: "points resolved"
remaining_hours: "remaining hours"
field_velocity_based_estimate: "Velocity based estimate"
backlogs_active: "active"
@ -41,7 +40,6 @@ en:
label_back_to_project: "Back to project page"
label_sprint_name: "Sprint \"%{name}\""
label_backlogs: "Backlogs"
label_scrum_statistics: "Scrum statistics"
label_backlog: "Backlog"
label_sprint_backlog: "sprint backlog"
@ -114,7 +112,6 @@ en:
remaining_hours: "Remaining hours"
remaining_hours_ideal: "Remaining hours (ideal)"
show_burndown_chart: "Burndown Chart"
show_statistics: "Show Scrum statistics"
story: "Story"
story_points: "Story Points"
story_points_ideal: "Story Points (ideal)"

@ -43,7 +43,6 @@ fr:
points_to_resolve: "points non-résolus"
label_select_all: "Sélectionner tout"
backlogs_sprint_unestimated: "Sprints fermés ou actifs avec scénarios non-estimés"
label_scrum_statistics: Statistiques du Scrum
required_burn_rate_points: taux de progression requis (points)
error_blank: "ne peut être vide"
points_to_accept: "points non-acceptés"
@ -75,7 +74,6 @@ fr:
%{description}
label_burndown: Progression
rb_label_copy_tasks_all: "[[All]]"
field_velocity_based_estimate: "Estimé basé sur la vélocité"
todo_issue_description: |-
%{summary}: %{url}
%{description}

@ -43,7 +43,6 @@ nl:
field_blocks: Blokkeert
label_select_all: Selecteer alles
backlogs_sprint_unestimated: Gesloten of actieve sprints bevatten stories zonder tijdschattingen
label_scrum_statistics: Scrum statistieken
required_burn_rate_points: benodigde burn rate (points)
error_blank: mag niet leeg zijn
points_to_accept: points niet geaccepteerd
@ -79,7 +78,6 @@ nl:
%{summary}: %{url}
%{description}
label_chart_options: Grafiek Opties
field_velocity_based_estimate: Schatting obv velocity
backlogs_product_backlog: Product backlog
remaining_hours: resterende uren
error_outro: Verbeter a.u.b de eerdergenoemde problemen voordat u nogmaals verzend.

@ -45,7 +45,6 @@ pt-BR:
points_to_resolve: "Pontos n\xC3\xA3o resolvidos"
label_select_all: Selecinar Todos
backlogs_sprint_unestimated: "Ativar ou desativar sprints com est\xC3\xB3rias sem estimativas"
label_scrum_statistics: "Estat\xC3\xADsticas do scrum"
required_burn_rate_points: "Taxa pontos necess\xC3\xA1ria"
error_blank: "[[cannot be blank]]"
points_to_accept: "Pontos n\xC3\xA3o aceitos"
@ -77,7 +76,6 @@ pt-BR:
%{description}
label_burndown: Burndown
rb_label_copy_tasks_all: "[[All]]"
field_velocity_based_estimate: Velocidade estimada
todo_issue_description: |-
%{summary}: %{url}
%{description}

@ -118,9 +118,6 @@ zh:
5bey5YWz6Zet5LqG55qE77yI5oiW5rS76LeD55qE77yJ5Yay5Yi66YeM5a2Y
5Zyo5pyq5Lyw6YeP55qE5pWF5LqL
label_scrum_statistics: !binary |
U2NydW0g57uf6K6h
required_burn_rate_points: !binary |
5b+F6KaB54eD54On546HICjngrnmlbAp
@ -197,9 +194,6 @@ zh:
54eD5bC9
rb_label_copy_tasks_all: "[[All]]"
field_velocity_based_estimate: !binary |
6YCf5bqm6K6h6YeP5Y2V5L2N
todo_issue_description: |-
%{summary}: %{url}
%{description}

@ -5,7 +5,6 @@ ActionController::Routing::Routes.draw do |map|
map.resource :rb, :only => :none do |rb|
rb.resource :query, :only => :show, :controller => :rb_queries, :as => "queries/:project_id"
rb.resource :wiki, :only => [:show, :edit], :controller => :rb_wikis, :as => "wikis/:sprint_id"
rb.resource :statistics, :only => :show, :controller => :rb_statistics
rb.resource :burndown_chart, :only => :show, :controller => :rb_burndown_charts, :as => "projects/:project_id/burndown_charts/:sprint_id"
rb.resource :impediment, :except => :index, :controller => :rb_impediments, :as => "impediment/:id"
rb.resources :impediments, :only => :index, :controller => :rb_impediments, :as => "impediments/:sprint_id"

@ -1,42 +0,0 @@
Feature: Scrum statistics
As a product owner
I want to see scrum statistics
So that I am able to check the performance of the team
Background:
Given there is 1 project with:
| name | ecookbook |
And I am working in project "ecookbook"
And the project uses the following modules:
| backlogs |
And the backlogs module is initialized
And there is 1 user with:
| login | mathias |
And there is a role "product owner"
And the role "product owner" may have the following rights:
| view_scrum_statistics |
And the user "mathias" is a "product owner"
And there is 1 user with:
| login | andre |
And there is a role "visitor"
And the role "visitor" has no permissions
And the user "andre" is a "visitor"
Scenario: View scrum statistics
Given the scrum statistics are enabled
And I am logged in as "mathias"
When I go to the home page
And I follow "Scrum statistics"
Then I should be on the scrum statistics page
Scenario: Hide scrum statistics
Given the scrum statistics are enabled
And I am logged in as "andre"
When I go to the home page
Then I should not see "Scrum statistics" within "#main-menu"
Scenario: Deactivate scrum statistics
Given the scrum statistics are disabled
And I am logged in as "mathias"
When I go to the home page
Then I should not see "Scrum statistics" within "#main-menu"

@ -298,14 +298,6 @@ Given /^I am working in [pP]roject "(.+?)"$/ do |project_name|
@project = Project.find_by_name(project_name)
end
Given /^the scrum statistics are disabled$/ do
Setting.plugin_redmine_backlogs = Setting.plugin_redmine_backlogs.merge(:show_statistics => false)
end
Given /^the scrum statistics are enabled$/ do
Setting.plugin_redmine_backlogs = Setting.plugin_redmine_backlogs.merge(:show_statistics => true)
end
Given /^the tracker "(.+?)" is configured to track tasks$/ do |tracker_name|
tracker = Tracker.find_by_name(tracker_name)
tracker = Factory.create(:tracker, :name => tracker_name) if tracker.blank?

@ -92,10 +92,6 @@ When /^I update the task$/ do
@task_params.merge({ "_method" => "put" })
end
Given /^I visit the scrum statistics page$/ do
visit url_for(:controller => :rb_statistics, :action => :show)
end
When /^I view the master backlog$/ do
visit url_for(:controller => :projects, :action => :show, :id => @project)
click_link("Backlogs")

@ -35,9 +35,6 @@ module BacklogsNavigationHelpers
"/rb/taskboards/#{sprint.id}"
when /^the scrum statistics page$/
"/rb/statistics"
when /^the backlogs plugin configuration page$/
"/settings/plugin/redmine_backlogs"
else

@ -14,7 +14,6 @@ Dispatcher.to_prepare do
require_dependency 'backlogs_query_patch'
require_dependency 'backlogs_version_patch'
require_dependency 'backlogs_project_patch'
require_dependency 'backlogs_user_patch'
require_dependency 'backlogs_version_controller_patch'
require_dependency 'backlogs_hooks'
@ -99,8 +98,6 @@ Redmine::Plugin.register :redmine_backlogs do
permission :create_impediments, { :rb_impediments => [:new, :create] }
permission :update_impediments, { :rb_impediments => [:edit, :update],
:issue_boxes => [:edit, :update] }
permission :view_scrum_statistics, { :rb_statistics => :show }
end
menu :project_menu,
@ -109,9 +106,4 @@ Redmine::Plugin.register :redmine_backlogs do
:caption => :project_module_backlogs,
:after => :new_issue,
:param => :project_id
menu :application_menu,
:backlogs,
{:controller => :rb_statistics, :action => :show},
:caption => :label_scrum_statistics,
:if => proc { Setting.plugin_redmine_backlogs[:show_statistics] && User.current.allowed_to?(:view_scrum_statistics, nil, :global => true) }
end

@ -49,16 +49,6 @@ module BacklogsPlugin
<td class="story-points">#{issue.story_points || '-'}</td>
</tr>
}
if Setting.plugin_redmine_backlogs[:show_statistics]
vbe = issue.velocity_based_estimate
snippet += %Q{
<tr>
<th class="velocity-based-estimate">#{l(:field_velocity_based_estimate)}:</th>
<td class="velocity-based-estimate">#{vbe ? vbe.to_s + ' days' : '-'}</td>
</tr>
}
end
end
snippet += %Q{

@ -1,221 +0,0 @@
require_dependency 'project'
module Backlogs
class Statistics
def initialize
@errors = {}
@info = {}
end
def merge(stats, prefix = '')
errors.each {|err|
err = "#{prefix}#{err}".intern
stats[err] ||= 0
stats[err] += 1
}
return stats
end
def []=(cat, key, *args)
raise "Unexpected data category #{cat}" unless [:error, :info].include?(cat)
case args.size
when 2
subkey, value = *args
when 1
value = args[0]
subkey = nil
else
raise "Unexpected number of argments"
end
case cat
when :error
if subkey.nil?
raise "Already reported #{key.inspect}" if @errors.include?(key)
@errors[key] = value.nil? ? nil : (!!value)
else
raise "Already reported #{key.inspect}" if @errors.include?(key) && ! @errors[key].is_a?(Hash)
@errors[key] ||= {}
raise "Already errors #{key.inspect}/#{subkey.inspect}" if @errors[key].include?(subkey)
@errors[key][subkey] = value.nil? ? nil : (!!value)
end
when :info
raise "Already added info #{key.inspect}" if @info.include?(key)
@info[key] = value
end
end
def score
scoring = {}
@errors.each_pair{ |k, v|
if v.is_a? Hash
v = v.values.select{|s| !s.nil?}
scoring[k] = v.select{|s| s}.size == 0 if v.size != 0
else
scoring[k] = !v unless v.nil?
end
}
return ((scoring.values.select{|v| v}.size * 10) / scoring.size)
end
def scores(prefix='')
score = {}
@errors.each_pair{|k, v|
if v.is_a? Hash
v.each_pair {|sk, rv|
score["#{prefix}#{k}_#{sk}".intern] = rv if !rv.blank?
}
else
score["#{prefix}#{k}".intern] = v if !v.blank?
end
}
return score
end
def errors(prefix = '')
score = scores(prefix)
return score.keys.select{|k| score[k]}
end
def info(prefix='')
info = {}
@info.each_pair {|k, v|
info["#{prefix}#{k}".intern] = v
}
return info
end
end
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 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
@scrum_statistics = Backlogs::Statistics.new
# magic constant
backlog = Story.product_backlog(self, 10)
active_sprint = self.active_sprint
closed_sprints = Sprint.find(:all,
:conditions => ["project_id = ? and status in ('closed', 'locked') and not(effective_date is null or start_date is null)", self.id],
:order => "effective_date desc",
:limit => 5)
all_sprints = ([active_sprint] + closed_sprints).compact
@scrum_statistics[:info, :active_sprint] = active_sprint
@scrum_statistics[:info, :closed_sprints] = closed_sprints
@scrum_statistics[:error, :product_backlog, :is_empty] = (self.status == Project::STATUS_ACTIVE && backlog.length == 0)
@scrum_statistics[:error, :product_backlog, :unsized] = backlog.inject(false) {|unsized, story| unsized || story.story_points.blank? }
@scrum_statistics[:error, :sprint, :unsized] = Issue.exists?(["story_points is null and parent_id is null and fixed_version_id in (?) and tracker_id in (?)", all_sprints.collect{|s| s.id}, Story.trackers])
@scrum_statistics[:error, :sprint, :unestimated] = Issue.exists?(["estimated_hours is null and not parent_id is null and fixed_version_id in (?) and tracker_id = ?", all_sprints.collect{|s| s.id}, Task.tracker])
@scrum_statistics[:error, :sprint, :notes_missing] = closed_sprints.inject(false){|missing, sprint| missing || !sprint.has_wiki_page}
@scrum_statistics[:error, :inactive] = (self.status == Project::STATUS_ACTIVE && !(active_sprint && active_sprint.activity))
velocity = nil
begin
points = 0
error = 0
days = 0
closed_sprints.each {|sprint|
bd = sprint.burndown('up')
accepted = (bd.points_accepted || [0])[-1]
committed = (bd.points_committed || [0])[0]
error += (1 - (accepted.to_f / committed.to_f)).abs
points += accepted
days += bd.ideal.size
}
error = (error / closed_sprints.size)
# magic constant
@scrum_statistics[:error, :velocity, :varies] = (error > 0.1)
@scrum_statistics[:error, :velocity, :missing] = false
velocity = (points / closed_sprints.size)
@scrum_statistics[:info, :velocity_divergance] = error * 100
rescue ZeroDivisionError
@scrum_statistics[:error, :velocity, :varies] = nil
@scrum_statistics[:error, :velocity, :missing] = true
@scrum_statistics[:info, :velocity_divergance] = nil
end
@scrum_statistics[:info, :velocity] = velocity
if all_sprints.size != 0 && velocity && velocity != 0
begin
dps = (all_sprints.inject(0){|d, s| d + s.days.size} / all_sprints.size)
@scrum_statistics[:info, :average_days_per_sprint] = dps
@scrum_statistics[:info, :average_days_per_point] = (velocity ? (dps.to_f / velocity) : nil)
rescue ZeroDivisionError
dps = nil
end
else
dps = nil
end
if dps.nil?
@scrum_statistics[:info, :average_days_per_sprint] = nil
@scrum_statistics[:info, :average_days_per_point] = nil
end
sizing_divergance = nil
sizing_is_consistent = false
sprint_ids = all_sprints.collect{|s| "#{s.id}"}.join(',')
story_trackers = Story.trackers.collect{|t| "#{t}"}.join(',')
if sprint_ids != '' && story_trackers != ''
select_stories = "
not (story_points is null or story_points = 0)
and not (estimated_hours is null or estimated_hours = 0)
and fixed_version_id in (#{sprint_ids})
and project_id = #{self.id}
and not parent_id is null
and tracker_id in (#{story_trackers})
"
points_per_hour = Story.find_by_sql("select avg(story_points) / avg(estimated_hours) as points_per_hour from issues where #{select_stories}")[0].points_per_hour
if points_per_hour
points_per_hour = Float(points_per_hour)
stories = Story.find(:all, :conditions => [select_stories])
error = stories.inject(0) {|err, story|
err + (1 - (points_per_hour / (story.story_points / story.estimated_hours)))
}
sizing_divergance = error * 100
# magic constant
sizing_is_consistent = (error < 0.1)
end
end
@scrum_statistics[:info, :sizing_divergance] = sizing_divergance
@scrum_statistics[:error, :sizing_inconsistent] = !sizing_is_consistent
return @scrum_statistics
end
end
end
end
Project.send(:include, Backlogs::ProjectPatch) unless Project.included_modules.include? Backlogs::ProjectPatch

@ -11,7 +11,6 @@ module Backlogs
unloadable # Send unloadable so it will not be unloaded in development
base.add_available_column(QueryColumn.new(:story_points, :sortable => "#{Issue.table_name}.story_points"))
base.add_available_column(QueryColumn.new(:remaining_hours, :sortable => "#{Issue.table_name}.remaining_hours"))
base.add_available_column(QueryColumn.new(:velocity_based_estimate))
base.add_available_column(QueryColumn.new(:position,
:sortable => [

@ -125,15 +125,6 @@ module RedmineBacklogs::Patches::IssuePatch
relations_to.collect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed? ? ir.issue_from : nil}.compact
end
def velocity_based_estimate
return nil if !self.is_story? || !self.story_points || self.story_points <= 0
dpp = self.project.scrum_statistics.info[:average_days_per_point]
return nil if !dpp
(self.story_points * dpp).to_i
end
def recalculate_attributes_for_with_remaining_hours(issue_id)
recalculate_attributes_for_without_remaining_hours(issue_id)

Loading…
Cancel
Save