From 5c6ce51ec9f1f3f6687db06d0faeb61d1524198d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sat, 12 Dec 2009 10:06:07 +0000 Subject: [PATCH] Adds workflow copy functionality (#1727). git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3154 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/workflows_controller.rb | 23 +++++++++++++ app/models/role.rb | 10 ++---- app/models/tracker.rb | 10 ++---- app/models/workflow.rb | 46 +++++++++++++++++++++++++ app/views/workflows/_action_menu.rhtml | 5 +++ app/views/workflows/copy.rhtml | 33 ++++++++++++++++++ app/views/workflows/edit.rhtml | 4 +-- app/views/workflows/index.rhtml | 2 ++ config/locales/en.yml | 7 +++- config/locales/fr.yml | 5 +++ public/images/lightning.png | Bin 0 -> 634 bytes public/stylesheets/application.css | 3 ++ test/functional/workflows_controller_test.rb | 48 ++++++++++++++++++++++++++- 13 files changed, 175 insertions(+), 21 deletions(-) create mode 100644 app/views/workflows/_action_menu.rhtml create mode 100644 app/views/workflows/copy.rhtml create mode 100644 public/images/lightning.png diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index 380d4e75..f11aa473 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -42,4 +42,27 @@ class WorkflowsController < ApplicationController @trackers = Tracker.find(:all, :order => 'position') @statuses = IssueStatus.find(:all, :order => 'position') end + + def copy + @trackers = Tracker.find(:all, :order => 'position') + @roles = Role.find(:all, :order => 'builtin, position') + + @source_tracker = params[:source_tracker_id].blank? ? nil : Tracker.find_by_id(params[:source_tracker_id]) + @source_role = params[:source_role_id].blank? ? nil : Role.find_by_id(params[:source_role_id]) + + @target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids]) + @target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids]) + + if request.post? + if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) + flash.now[:error] = l(:error_workflow_copy_source) + elsif @target_trackers.nil? || @target_roles.nil? + flash.now[:error] = l(:error_workflow_copy_target) + else + Workflow.copy(@source_tracker, @source_role, @target_trackers, @target_roles) + flash[:notice] = l(:notice_successful_update) + redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role + end + end + end end diff --git a/app/models/role.rb b/app/models/role.rb index db582570..22ce2a65 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -28,14 +28,8 @@ class Role < ActiveRecord::Base before_destroy :check_deletable has_many :workflows, :dependent => :delete_all do - def copy(role) - raise "Can not copy workflow from a #{role.class}" unless role.is_a?(Role) - raise "Can not copy workflow from/to an unsaved role" if proxy_owner.new_record? || role.new_record? - clear - connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, old_status_id, new_status_id, role_id)" + - " SELECT tracker_id, old_status_id, new_status_id, #{proxy_owner.id}" + - " FROM #{Workflow.table_name}" + - " WHERE role_id = #{role.id}" + def copy(source_role) + Workflow.copy(nil, source_role, nil, proxy_owner) end end diff --git a/app/models/tracker.rb b/app/models/tracker.rb index 7c5bae25..8f7a98c3 100644 --- a/app/models/tracker.rb +++ b/app/models/tracker.rb @@ -19,14 +19,8 @@ class Tracker < ActiveRecord::Base before_destroy :check_integrity has_many :issues has_many :workflows, :dependent => :delete_all do - def copy(tracker) - raise "Can not copy workflow from a #{tracker.class}" unless tracker.is_a?(Tracker) - raise "Can not copy workflow from/to an unsaved tracker" if proxy_owner.new_record? || tracker.new_record? - clear - connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, old_status_id, new_status_id, role_id)" + - " SELECT #{proxy_owner.id}, old_status_id, new_status_id, role_id" + - " FROM #{Workflow.table_name}" + - " WHERE tracker_id = #{tracker.id}" + def copy(source_tracker) + Workflow.copy(source_tracker, nil, proxy_owner, nil) end end diff --git a/app/models/workflow.rb b/app/models/workflow.rb index a96abaf1..da358b51 100644 --- a/app/models/workflow.rb +++ b/app/models/workflow.rb @@ -51,4 +51,50 @@ class Workflow < ActiveRecord::Base uniq. sort end + + # Copies workflows from source to targets + def self.copy(source_tracker, source_role, target_trackers, target_roles) + unless source_tracker.is_a?(Tracker) || source_role.is_a?(Role) + raise ArgumentError.new "source_tracker or source_role must be specified" + end + + target_trackers = [target_trackers].flatten.compact + target_roles = [target_roles].flatten.compact + + target_trackers = Tracker.all if target_trackers.empty? + target_roles = Role.all if target_roles.empty? + + target_trackers.each do |target_tracker| + target_roles.each do |target_role| + copy_one(source_tracker || target_tracker, + source_role || target_role, + target_tracker, + target_role) + end + end + end + + # Copies a single set of workflows from source to target + def self.copy_one(source_tracker, source_role, target_tracker, target_role) + unless source_tracker.is_a?(Tracker) && !source_tracker.new_record? && + source_role.is_a?(Role) && !source_role.new_record? && + target_tracker.is_a?(Tracker) && !target_tracker.new_record? && + target_role.is_a?(Role) && !target_role.new_record? + + raise ArgumentError.new("arguments can not be nil or unsaved objects") + end + + if source_tracker == target_tracker && source_role == target_role + false + else + transaction do + delete_all :tracker_id => target_tracker.id, :role_id => target_role.id + connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id)" + + " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id" + + " FROM #{Workflow.table_name}" + + " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" + end + true + end + end end diff --git a/app/views/workflows/_action_menu.rhtml b/app/views/workflows/_action_menu.rhtml new file mode 100644 index 00000000..57728112 --- /dev/null +++ b/app/views/workflows/_action_menu.rhtml @@ -0,0 +1,5 @@ +
+<%= link_to l(:button_edit), {:action => 'edit'}, :class => 'icon icon-edit' %> +<%= link_to l(:button_copy), {:action => 'copy'}, :class => 'icon icon-copy' %> +<%= link_to l(:field_summary), {:action => 'index'}, :class => 'icon icon-summary' %> +
diff --git a/app/views/workflows/copy.rhtml b/app/views/workflows/copy.rhtml new file mode 100644 index 00000000..2cecc841 --- /dev/null +++ b/app/views/workflows/copy.rhtml @@ -0,0 +1,33 @@ +<%= render :partial => 'action_menu' %> + +

<%=l(:label_workflow)%>

+ +<% form_tag({}, :id => 'workflow_copy_form') do %> +
+

+ + <%= l(:label_tracker) %>
+ <%= select_tag('source_tracker_id', + "" + + "" + + options_from_collection_for_select(@trackers, 'id', 'name', @source_tracker && @source_tracker.id)) %>
+ <%= l(:label_role) %>
+ <%= select_tag('source_role_id', + "" + + "" + + options_from_collection_for_select(@roles, 'id', 'name', @source_role && @source_role.id)) %> +

+

+ + <%= l(:label_tracker) %>
+ <%= select_tag 'target_tracker_ids', + "" + + options_from_collection_for_select(@trackers, 'id', 'name', @target_trackers && @target_trackers.map(&:id)), :multiple => true %>
+ <%= l(:label_role) %>
+ <%= select_tag 'target_role_ids', + "" + + options_from_collection_for_select(@roles, 'id', 'name', @target_roles && @target_roles.map(&:id)), :multiple => true %> +

+
+<%= submit_tag l(:button_copy) %> +<% end %> diff --git a/app/views/workflows/edit.rhtml b/app/views/workflows/edit.rhtml index 399825de..4feaf3a7 100644 --- a/app/views/workflows/edit.rhtml +++ b/app/views/workflows/edit.rhtml @@ -1,6 +1,4 @@ -
-<%= link_to l(:field_summary), :action => 'index' %> -
+<%= render :partial => 'action_menu' %>

<%=l(:label_workflow)%>

diff --git a/app/views/workflows/index.rhtml b/app/views/workflows/index.rhtml index 2fd080d8..9f06269b 100644 --- a/app/views/workflows/index.rhtml +++ b/app/views/workflows/index.rhtml @@ -1,3 +1,5 @@ +<%= render :partial => 'action_menu' %> +

<%=l(:label_workflow)%>

<% if @workflow_counts.empty? %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 6a439a33..3f7e0219 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -159,7 +159,9 @@ en: 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." - + error_workflow_copy_source: 'Please select a source tracker or role' + error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + warning_attachments_not_saved: "{{count}} file(s) could not be saved." mail_subject_lost_password: "Your {{value}} password" @@ -720,6 +722,9 @@ en: label_version_sharing_tree: With project tree label_version_sharing_system: With all projects label_update_issue_done_ratios: Update issue done ratios + label_copy_source: Source + label_copy_target: Target + label_copy_same_as_target: Same as target button_login: Login button_submit: Submit diff --git a/config/locales/fr.yml b/config/locales/fr.yml index b39907c5..7072a0a1 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -179,6 +179,8 @@ fr: error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas à ce projet" error_can_not_reopen_issue_on_closed_version: 'Une demande assignée à une version fermée ne peut pas être réouverte' error_can_not_archive_project: "Ce projet ne peut pas être archivé" + error_workflow_copy_source: 'Veuillez sélectionner un tracker et/ou un rôle source' + error_workflow_copy_target: 'Veuillez sélectionner les trackers et rôles cibles' warning_attachments_not_saved: "{{count}} fichier(s) n'ont pas pu être sauvegardés." @@ -732,6 +734,9 @@ fr: label_version_sharing_hierarchy: Avec toute la hiérarchie label_version_sharing_tree: Avec tout l'arbre label_version_sharing_system: Avec tous les projets + label_copy_source: Source + label_copy_target: Cible + label_copy_same_as_target: Comme la cible button_login: Connexion button_submit: Soumettre diff --git a/public/images/lightning.png b/public/images/lightning.png new file mode 100644 index 0000000000000000000000000000000000000000..9680afd12f8fadf5b83f827240978446af5962b6 GIT binary patch literal 634 zcmV-=0)_pFP)3kp_oQM4NsQ6$aEKj6mx0hg})10sT;E2V;K zUHNY7qG&TIJ`gc&M*EmdrgP_>>zSLTO=}7j2M*`nnfbo+_|8cvrSLzG(R^{I&fHg| z3Nfi70*Jk=pS3m4lD_qhO!oanz~FpWe?InrjDM6H7D@P(w+NOTuL0gfPQ>)8EiKPO>RV|@%G(EApK7|ni6$Or7s}#zN5D2fFuoW z?SUp(cysDe%D^FVRe!-fK+o2zXp zH&*H~_``&05AX{CqjQ9Pq)Pw`p&~+e-<5=lgjH9A;4MsYR*p|Xp3o{VtL6Q8vD&1u z_TBwgt>E(`Zp(h8_8f=r&cr~-uy!|BC|zGKq17aQeR}wL=f1PUhPjus5Lo&7j={xc zCLhQZ=E~bn;<}`gh7Bu?>ih@P+*yw5-^u2k8!el-HG?lt4o$V&*`tbh^4#JtCOgRB z@^-thiGaZ!P|20J4^o7;v)78_|FldDe9Z$i&;9^|y&bKi-n=y{JsccrzVF2T076QP UWao5~B>(^b07*qoM6N<$g7%3ha{vGU literal 0 HcmV?d00001 diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 3d0c3b02..c3e7ca81 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -297,6 +297,8 @@ ul.properties li span {font-style:italic;} .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;} #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; } +#workflow_copy_form select { width: 200px; } + .pagination {font-size: 90%} p.pagination {margin-top:8px;} @@ -728,6 +730,7 @@ vertical-align: middle; .icon-details { background-image: url(../images/zoom_in.png); } .icon-report { background-image: url(../images/report.png); } .icon-comment { background-image: url(../images/comment.png); } +.icon-summary { background-image: url(../images/lightning.png); } .icon-file { background-image: url(../images/files/default.png); } .icon-file.text-plain { background-image: url(../images/files/text.png); } diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index 19187b7f..2b5216f5 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -22,7 +22,7 @@ require 'workflows_controller' class WorkflowsController; def rescue_action(e) raise e end; end class WorkflowsControllerTest < ActionController::TestCase - fixtures :roles, :trackers, :workflows + fixtures :roles, :trackers, :workflows, :users def setup @controller = WorkflowsController.new @@ -81,4 +81,50 @@ class WorkflowsControllerTest < ActionController::TestCase post :edit, :role_id => 2, :tracker_id => 1 assert_equal 0, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2}) end + + def test_get_copy + get :copy + assert_response :success + assert_template 'copy' + end + + def test_post_copy_one_to_one + source_transitions = status_transitions(:tracker_id => 1, :role_id => 2) + + post :copy, :source_tracker_id => '1', :source_role_id => '2', + :target_tracker_ids => ['3'], :target_role_ids => ['1'] + assert_response 302 + assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1) + end + + def test_post_copy_one_to_many + source_transitions = status_transitions(:tracker_id => 1, :role_id => 2) + + post :copy, :source_tracker_id => '1', :source_role_id => '2', + :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] + assert_response 302 + assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 1) + assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1) + assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 3) + assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 3) + end + + def test_post_copy_many_to_many + source_t2 = status_transitions(:tracker_id => 2, :role_id => 2) + source_t3 = status_transitions(:tracker_id => 3, :role_id => 2) + + post :copy, :source_tracker_id => 'any', :source_role_id => '2', + :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] + assert_response 302 + assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 1) + assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 1) + assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 3) + assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 3) + end + + # Returns an array of status transitions that can be compared + def status_transitions(conditions) + Workflow.find(:all, :conditions => conditions, + :order => 'tracker_id, role_id, old_status_id, new_status_id').collect {|w| [w.old_status, w.new_status_id]} + end end -- 2.11.0