diff --git a/app/controllers/issue_statuses_controller.rb b/app/controllers/issue_statuses_controller.rb index bee7f4833..3be6abf3f 100644 --- a/app/controllers/issue_statuses_controller.rb +++ b/app/controllers/issue_statuses_controller.rb @@ -18,7 +18,7 @@ class IssueStatusesController < ApplicationController before_filter :require_admin - verify :method => :post, :only => [ :destroy, :create, :update, :move ], + verify :method => :post, :only => [ :destroy, :create, :update, :move, :update_issue_done_ratio ], :redirect_to => { :action => :list } def index @@ -66,4 +66,13 @@ class IssueStatusesController < ApplicationController flash[:error] = "Unable to delete issue status" redirect_to :action => 'list' end + + def update_issue_done_ratio + if IssueStatus.update_issue_done_ratios + flash[:notice] = l(:notice_issue_done_ratios_updated) + else + flash[:error] = l(:error_issue_done_ratios_not_updated) + end + redirect_to :action => 'list' + end end diff --git a/app/models/issue.rb b/app/models/issue.rb index a0f47fdb8..2062e58e8 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -45,6 +45,8 @@ class Issue < ActiveRecord::Base acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]}, :author_key => :author_id + + DONE_RATIO_OPTIONS = %w(issue_field issue_status) validates_presence_of :subject, :priority, :project, :tracker, :author, :status validates_length_of :subject, :maximum => 255 @@ -55,7 +57,8 @@ class Issue < ActiveRecord::Base :conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } } named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status - + + before_save :update_done_ratio_from_issue_status after_save :create_journal # Returns true if usr or current user is allowed to view the issue @@ -162,6 +165,22 @@ class Issue < ActiveRecord::Base write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) end + def done_ratio + if Issue.use_status_for_done_ratio? && !self.status.default_done_ratio.blank? + self.status.default_done_ratio + else + read_attribute(:done_ratio) + end + end + + def self.use_status_for_done_ratio? + Setting.issue_done_ratio == 'issue_status' + end + + def self.use_field_for_done_ratio? + Setting.issue_done_ratio == 'issue_field' + end + def validate if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty? errors.add :due_date, :not_a_date @@ -198,6 +217,14 @@ class Issue < ActiveRecord::Base end end + # Set the done_ratio using the status if that setting is set. This will keep the done_ratios + # even if the user turns off the setting later + def update_done_ratio_from_issue_status + if Issue.use_status_for_done_ratio? && !self.status.default_done_ratio.blank? + self.done_ratio = self.status.default_done_ratio + end + end + def after_save # Reload is needed in order to get the right status reload diff --git a/app/models/issue_status.rb b/app/models/issue_status.rb index ca33d37d6..a9c1db584 100644 --- a/app/models/issue_status.rb +++ b/app/models/issue_status.rb @@ -33,6 +33,18 @@ class IssueStatus < ActiveRecord::Base def self.default find(:first, :conditions =>["is_default=?", true]) end + + # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+ + def self.update_issue_done_ratios + if Issue.use_status_for_done_ratio? + IssueStatus.find(:all, :conditions => ["default_done_ratio >= 0"]).each do |status| + Issue.update_all(["done_ratio = ?", status.default_done_ratio], + ["status_id = ?", status.id]) + end + end + + return Issue.use_status_for_done_ratio? + end # Returns an array of all statuses the given role can switch to # Uses association cache when called more than one time diff --git a/app/views/issue_statuses/_form.rhtml b/app/views/issue_statuses/_form.rhtml index b6a5bc19f..e36dec824 100644 --- a/app/views/issue_statuses/_form.rhtml +++ b/app/views/issue_statuses/_form.rhtml @@ -5,6 +5,11 @@

<%= text_field 'issue_status', 'name' %>

+<% if Issue.use_status_for_done_ratio? %> +

+<%= select 'issue_status', :default_done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>

+<% end %> +

<%= check_box 'issue_status', 'is_closed' %>

diff --git a/app/views/issue_statuses/list.rhtml b/app/views/issue_statuses/list.rhtml index ca973153d..a98ed0cd8 100644 --- a/app/views/issue_statuses/list.rhtml +++ b/app/views/issue_statuses/list.rhtml @@ -1,5 +1,6 @@
<%= link_to l(:label_issue_status_new), {:action => 'new'}, :class => 'icon icon-add' %> +<%= link_to(l(:label_update_issue_done_ratios), {:action => 'update_issue_done_ratio'}, :class => 'icon icon-multiple', :method => 'post', :confirm => l(:text_are_you_sure)) if Issue.use_status_for_done_ratio? %>

<%=l(:label_issue_status_plural)%>

@@ -7,6 +8,9 @@ + <% if Issue.use_status_for_done_ratio? %> + + <% end %> @@ -16,6 +20,9 @@ <% for status in @issue_statuses %> "> + <% if Issue.use_status_for_done_ratio? %> + + <% end %> diff --git a/app/views/issues/_attributes.rhtml b/app/views/issues/_attributes.rhtml index 9adf0f153..f8fc8d672 100644 --- a/app/views/issues/_attributes.rhtml +++ b/app/views/issues/_attributes.rhtml @@ -34,7 +34,9 @@

<%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %>

<%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %>

<%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %>

+<% if Issue.use_field_for_done_ratio? %>

<%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>

+<% end %>
diff --git a/app/views/issues/_form_update.rhtml b/app/views/issues/_form_update.rhtml index 5304ee23a..e29c41a7c 100644 --- a/app/views/issues/_form_update.rhtml +++ b/app/views/issues/_form_update.rhtml @@ -4,7 +4,9 @@

<%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %>

+<% if Issue.use_field_for_done_ratio? %>

<%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>

+<% end %> <% unless @issue.assignable_versions.empty? %>

<%= f.select :fixed_version_id, (@issue.assignable_versions.collect {|v| [v.name, v.id]}), :include_blank => true %>

<% end %> diff --git a/app/views/issues/bulk_edit.rhtml b/app/views/issues/bulk_edit.rhtml index f428566b2..b298c3c46 100644 --- a/app/views/issues/bulk_edit.rhtml +++ b/app/views/issues/bulk_edit.rhtml @@ -39,8 +39,10 @@ <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %> +<% if Issue.use_field_for_done_ratio? %> +<% end %>

<% @custom_fields.each do |custom_field| %> diff --git a/app/views/issues/context_menu.rhtml b/app/views/issues/context_menu.rhtml index 6cb05606a..4a1d0c310 100644 --- a/app/views/issues/context_menu.rhtml +++ b/app/views/issues/context_menu.rhtml @@ -77,6 +77,7 @@ <% end -%> + <% if Issue.use_field_for_done_ratio? %>
  • <%= l(:field_done_ratio) %>
  • - + <% end %> <% if !@issue.nil? %> <% if @can[:log_time] -%>
  • <%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, diff --git a/app/views/settings/_issues.rhtml b/app/views/settings/_issues.rhtml index b0277fab3..725d57da6 100644 --- a/app/views/settings/_issues.rhtml +++ b/app/views/settings/_issues.rhtml @@ -11,6 +11,9 @@ <%= check_box_tag 'settings[display_subprojects_issues]', 1, Setting.display_subprojects_issues? %>

    +

    +<%= select_tag 'settings[issue_done_ratio]', options_for_select(Issue::DONE_RATIO_OPTIONS.collect {|i| [l(i.to_sym), i]}, Setting.issue_done_ratio) %>

    +

    <%= text_field_tag 'settings[issues_export_limit]', Setting.issues_export_limit, :size => 6 %>

  • diff --git a/config/locales/en.yml b/config/locales/en.yml index 184788eee..6a439a337 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -147,6 +147,7 @@ en: notice_account_pending: "Your account was created and is now pending administrator approval." notice_default_data_loaded: Default configuration successfully loaded. notice_unable_delete_version: Unable to delete version. + notice_issue_done_ratios_updated: Issue done ratios updated. error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}" error_scm_not_found: "The entry or revision was not found in the repository." @@ -157,7 +158,8 @@ en: error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' error_can_not_archive_project: This project can not be archived - + error_issue_done_ratios_not_updated: "Issue done ratios not updated." + warning_attachments_not_saved: "{{count}} file(s) could not be saved." mail_subject_lost_password: "Your {{value}} password" @@ -309,6 +311,7 @@ en: setting_sequential_project_identifiers: Generate sequential project identifiers setting_gravatar_enabled: Use Gravatar user icons setting_gravatar_default: Default Gravatar image + setting_issue_done_ratio: Calculate the issue done ratio with setting_diff_max_lines_displayed: Max number of diff lines displayed setting_file_max_size_displayed: Max size of text files displayed inline setting_repository_log_display_limit: Maximum number of revisions displayed on file log @@ -716,6 +719,7 @@ en: label_version_sharing_hierarchy: With project hierarchy label_version_sharing_tree: With project tree label_version_sharing_system: With all projects + label_update_issue_done_ratios: Update issue done ratios button_login: Login button_submit: Submit @@ -850,3 +854,6 @@ en: enumeration_doc_categories: Document categories enumeration_activities: Activities (time tracking) enumeration_system_activity: System Activity + + issue_field: Use the issue field + issue_status: Use the issue status diff --git a/config/settings.yml b/config/settings.yml index ca0cc3d7b..754c0248a 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -129,6 +129,8 @@ issue_list_default_columns: - updated_on display_subprojects_issues: default: 1 +issue_done_ratio: + default: 'issue_field' default_projects_public: default: 1 default_projects_modules: diff --git a/db/migrate/20091123212029_add_default_done_ratio_to_issue_status.rb b/db/migrate/20091123212029_add_default_done_ratio_to_issue_status.rb new file mode 100644 index 000000000..0ce672100 --- /dev/null +++ b/db/migrate/20091123212029_add_default_done_ratio_to_issue_status.rb @@ -0,0 +1,9 @@ +class AddDefaultDoneRatioToIssueStatus < ActiveRecord::Migration + def self.up + add_column :issue_statuses, :default_done_ratio, :integer + end + + def self.down + remove_column :issue_statuses, :default_done_ratio + end +end diff --git a/public/images/table_multiple.png b/public/images/table_multiple.png new file mode 100644 index 000000000..d76448e34 Binary files /dev/null and b/public/images/table_multiple.png differ diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index afbee7638..3d0c3b028 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -702,6 +702,7 @@ vertical-align: middle; .icon-move { background-image: url(../images/move.png); } .icon-save { background-image: url(../images/save.png); } .icon-cancel { background-image: url(../images/cancel.png); } +.icon-multiple { background-image: url(../images/table_multiple.png); } .icon-folder { background-image: url(../images/folder.png); } .open .icon-folder { background-image: url(../images/folder_open.png); } .icon-package { background-image: url(../images/package.png); } diff --git a/test/functional/issue_statuses_controller_test.rb b/test/functional/issue_statuses_controller_test.rb index 5e012f7f4..4f88433bd 100644 --- a/test/functional/issue_statuses_controller_test.rb +++ b/test/functional/issue_statuses_controller_test.rb @@ -70,4 +70,27 @@ class IssueStatusesControllerTest < ActionController::TestCase assert_redirected_to 'issue_statuses/list' assert_not_nil IssueStatus.find_by_id(1) end + + context "on POST to :update_issue_done_ratio" do + context "with Setting.issue_done_ratio using the issue_field" do + setup do + Setting.issue_done_ratio = 'issue_field' + post :update_issue_done_ratio + end + + should_set_the_flash_to /not updated/ + should_redirect_to('the list') { '/issue_statuses/list' } + end + + context "with Setting.issue_done_ratio using the issue_status" do + setup do + Setting.issue_done_ratio = 'issue_status' + post :update_issue_done_ratio + end + + should_set_the_flash_to /Issue done ratios updated/ + should_redirect_to('the list') { '/issue_statuses/list' } + end + end + end diff --git a/test/unit/issue_status_test.rb b/test/unit/issue_status_test.rb index 042f30e32..2c0685cec 100644 --- a/test/unit/issue_status_test.rb +++ b/test/unit/issue_status_test.rb @@ -66,4 +66,40 @@ class IssueStatusTest < ActiveSupport::TestCase status.reload assert status.is_default? end + + context "#update_done_ratios" do + setup do + @issue = Issue.find(1) + @issue_status = IssueStatus.find(1) + @issue_status.update_attribute(:default_done_ratio, 50) + end + + context "with Setting.issue_done_ratio using the issue_field" do + setup do + Setting.issue_done_ratio = 'issue_field' + end + + should "change nothing" do + IssueStatus.update_issue_done_ratios + + assert_equal 0, Issue.count(:conditions => {:done_ratio => 50}) + end + end + + context "with Setting.issue_done_ratio using the issue_status" do + setup do + Setting.issue_done_ratio = 'issue_status' + end + + should "update all of the issue's done_ratios to match their Issue Status" do + IssueStatus.update_issue_done_ratios + + issues = Issue.find([1,3,4,5,6,7,9,10]) + issues.each do |issue| + assert_equal @issue_status, issue.status + assert_equal 50, issue.read_attribute(:done_ratio) + end + end + end + end end diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index e91880c08..fa9d56dbd 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -529,4 +529,64 @@ class IssueTest < ActiveSupport::TestCase end assert ActionMailer::Base.deliveries.empty? end + + context "#done_ratio" do + setup do + @issue = Issue.find(1) + @issue_status = IssueStatus.find(1) + @issue_status.update_attribute(:default_done_ratio, 50) + end + + context "with Setting.issue_done_ratio using the issue_field" do + setup do + Setting.issue_done_ratio = 'issue_field' + end + + should "read the issue's field" do + assert_equal 0, @issue.done_ratio + end + end + + context "with Setting.issue_done_ratio using the issue_status" do + setup do + Setting.issue_done_ratio = 'issue_status' + end + + should "read the Issue Status's default done ratio" do + assert_equal 50, @issue.done_ratio + end + end + end + + context "#update_done_ratio_from_issue_status" do + setup do + @issue = Issue.find(1) + @issue_status = IssueStatus.find(1) + @issue_status.update_attribute(:default_done_ratio, 50) + end + + context "with Setting.issue_done_ratio using the issue_field" do + setup do + Setting.issue_done_ratio = 'issue_field' + end + + should "not change the issue" do + @issue.update_done_ratio_from_issue_status + + assert_equal 0, @issue.done_ratio + end + end + + context "with Setting.issue_done_ratio using the issue_status" do + setup do + Setting.issue_done_ratio = 'issue_status' + end + + should "not change the issue's done ratio" do + @issue.update_done_ratio_from_issue_status + + assert_equal 50, @issue.done_ratio + end + end + end end
    <%=l(:field_status)%><%=l(:field_done_ratio)%><%=l(:field_is_default)%> <%=l(:field_is_closed)%> <%=l(:button_sort)%>
    <%= link_to status.name, :action => 'edit', :id => status %><%= h status.default_done_ratio %><%= image_tag 'true.png' if status.is_default? %> <%= image_tag 'true.png' if status.is_closed? %> <%= reorder_links('issue_status', {:action => 'update', :id => status}) %>