diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb
index 380d4e7..f11aa47 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 db58257..22ce2a6 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 7c5bae2..8f7a98c 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 a96abaf..da358b5 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 0000000..5772811
--- /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 0000000..2cecc84
--- /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 399825d..4feaf3a 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 2fd080d..9f06269 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 6a439a3..3f7e021 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 b39907c..7072a0a 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
zc$@)x0)_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
Hc$@ 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