Adds workflow copy functionality (#1727).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3154 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
parent
ddeaf9da96
commit
5c6ce51ec9
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<div class="contextual">
|
||||
<%= 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' %>
|
||||
</div>
|
|
@ -0,0 +1,33 @@
|
|||
<%= render :partial => 'action_menu' %>
|
||||
|
||||
<h2><%=l(:label_workflow)%></h2>
|
||||
|
||||
<% form_tag({}, :id => 'workflow_copy_form') do %>
|
||||
<div class="tabular box">
|
||||
<p>
|
||||
<label><%= l(:label_copy_source) %></label>
|
||||
<%= l(:label_tracker) %><br />
|
||||
<%= select_tag('source_tracker_id',
|
||||
"<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" +
|
||||
"<option value=\"any\">--- #{ l(:label_copy_same_as_target) } ---</option>" +
|
||||
options_from_collection_for_select(@trackers, 'id', 'name', @source_tracker && @source_tracker.id)) %><br />
|
||||
<%= l(:label_role) %><br />
|
||||
<%= select_tag('source_role_id',
|
||||
"<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" +
|
||||
"<option value=\"any\">--- #{ l(:label_copy_same_as_target) } ---</option>" +
|
||||
options_from_collection_for_select(@roles, 'id', 'name', @source_role && @source_role.id)) %>
|
||||
</p>
|
||||
<p>
|
||||
<label><%= l(:label_copy_target) %></label>
|
||||
<%= l(:label_tracker) %><br />
|
||||
<%= select_tag 'target_tracker_ids',
|
||||
"<option value=\"\" disabled=\"disabled\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" +
|
||||
options_from_collection_for_select(@trackers, 'id', 'name', @target_trackers && @target_trackers.map(&:id)), :multiple => true %><br />
|
||||
<%= l(:label_role) %><br />
|
||||
<%= select_tag 'target_role_ids',
|
||||
"<option value=\"\" disabled=\"disabled\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" +
|
||||
options_from_collection_for_select(@roles, 'id', 'name', @target_roles && @target_roles.map(&:id)), :multiple => true %>
|
||||
</p>
|
||||
</div>
|
||||
<%= submit_tag l(:button_copy) %>
|
||||
<% end %>
|
|
@ -1,6 +1,4 @@
|
|||
<div class="contextual">
|
||||
<%= link_to l(:field_summary), :action => 'index' %>
|
||||
</div>
|
||||
<%= render :partial => 'action_menu' %>
|
||||
|
||||
<h2><%=l(:label_workflow)%></h2>
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<%= render :partial => 'action_menu' %>
|
||||
|
||||
<h2><%=l(:label_workflow)%></h2>
|
||||
|
||||
<% if @workflow_counts.empty? %>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 634 B |
|
@ -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); }
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue