@@ -0,0 +1,5 | |||
|
1 | <div class="contextual"> | |
|
2 | <%= link_to l(:button_edit), {:action => 'edit'}, :class => 'icon icon-edit' %> | |
|
3 | <%= link_to l(:button_copy), {:action => 'copy'}, :class => 'icon icon-copy' %> | |
|
4 | <%= link_to l(:field_summary), {:action => 'index'}, :class => 'icon icon-summary' %> | |
|
5 | </div> |
@@ -0,0 +1,33 | |||
|
1 | <%= render :partial => 'action_menu' %> | |
|
2 | ||
|
3 | <h2><%=l(:label_workflow)%></h2> | |
|
4 | ||
|
5 | <% form_tag({}, :id => 'workflow_copy_form') do %> | |
|
6 | <div class="tabular box"> | |
|
7 | <p> | |
|
8 | <label><%= l(:label_copy_source) %></label> | |
|
9 | <%= l(:label_tracker) %><br /> | |
|
10 | <%= select_tag('source_tracker_id', | |
|
11 | "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" + | |
|
12 | "<option value=\"any\">--- #{ l(:label_copy_same_as_target) } ---</option>" + | |
|
13 | options_from_collection_for_select(@trackers, 'id', 'name', @source_tracker && @source_tracker.id)) %><br /> | |
|
14 | <%= l(:label_role) %><br /> | |
|
15 | <%= select_tag('source_role_id', | |
|
16 | "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" + | |
|
17 | "<option value=\"any\">--- #{ l(:label_copy_same_as_target) } ---</option>" + | |
|
18 | options_from_collection_for_select(@roles, 'id', 'name', @source_role && @source_role.id)) %> | |
|
19 | </p> | |
|
20 | <p> | |
|
21 | <label><%= l(:label_copy_target) %></label> | |
|
22 | <%= l(:label_tracker) %><br /> | |
|
23 | <%= select_tag 'target_tracker_ids', | |
|
24 | "<option value=\"\" disabled=\"disabled\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" + | |
|
25 | options_from_collection_for_select(@trackers, 'id', 'name', @target_trackers && @target_trackers.map(&:id)), :multiple => true %><br /> | |
|
26 | <%= l(:label_role) %><br /> | |
|
27 | <%= select_tag 'target_role_ids', | |
|
28 | "<option value=\"\" disabled=\"disabled\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" + | |
|
29 | options_from_collection_for_select(@roles, 'id', 'name', @target_roles && @target_roles.map(&:id)), :multiple => true %> | |
|
30 | </p> | |
|
31 | </div> | |
|
32 | <%= submit_tag l(:button_copy) %> | |
|
33 | <% end %> |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
@@ -42,4 +42,27 class WorkflowsController < ApplicationController | |||
|
42 | 42 | @trackers = Tracker.find(:all, :order => 'position') |
|
43 | 43 | @statuses = IssueStatus.find(:all, :order => 'position') |
|
44 | 44 | end |
|
45 | ||
|
46 | def copy | |
|
47 | @trackers = Tracker.find(:all, :order => 'position') | |
|
48 | @roles = Role.find(:all, :order => 'builtin, position') | |
|
49 | ||
|
50 | @source_tracker = params[:source_tracker_id].blank? ? nil : Tracker.find_by_id(params[:source_tracker_id]) | |
|
51 | @source_role = params[:source_role_id].blank? ? nil : Role.find_by_id(params[:source_role_id]) | |
|
52 | ||
|
53 | @target_trackers = params[:target_tracker_ids].blank? ? nil : Tracker.find_all_by_id(params[:target_tracker_ids]) | |
|
54 | @target_roles = params[:target_role_ids].blank? ? nil : Role.find_all_by_id(params[:target_role_ids]) | |
|
55 | ||
|
56 | if request.post? | |
|
57 | if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?) | |
|
58 | flash.now[:error] = l(:error_workflow_copy_source) | |
|
59 | elsif @target_trackers.nil? || @target_roles.nil? | |
|
60 | flash.now[:error] = l(:error_workflow_copy_target) | |
|
61 | else | |
|
62 | Workflow.copy(@source_tracker, @source_role, @target_trackers, @target_roles) | |
|
63 | flash[:notice] = l(:notice_successful_update) | |
|
64 | redirect_to :action => 'copy', :source_tracker_id => @source_tracker, :source_role_id => @source_role | |
|
65 | end | |
|
66 | end | |
|
67 | end | |
|
45 | 68 | end |
@@ -28,14 +28,8 class Role < ActiveRecord::Base | |||
|
28 | 28 | |
|
29 | 29 | before_destroy :check_deletable |
|
30 | 30 | has_many :workflows, :dependent => :delete_all do |
|
31 | def copy(role) | |
|
32 | raise "Can not copy workflow from a #{role.class}" unless role.is_a?(Role) | |
|
33 | raise "Can not copy workflow from/to an unsaved role" if proxy_owner.new_record? || role.new_record? | |
|
34 | clear | |
|
35 | connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, old_status_id, new_status_id, role_id)" + | |
|
36 | " SELECT tracker_id, old_status_id, new_status_id, #{proxy_owner.id}" + | |
|
37 | " FROM #{Workflow.table_name}" + | |
|
38 | " WHERE role_id = #{role.id}" | |
|
31 | def copy(source_role) | |
|
32 | Workflow.copy(nil, source_role, nil, proxy_owner) | |
|
39 | 33 | end |
|
40 | 34 | end |
|
41 | 35 |
@@ -19,14 +19,8 class Tracker < ActiveRecord::Base | |||
|
19 | 19 | before_destroy :check_integrity |
|
20 | 20 | has_many :issues |
|
21 | 21 | has_many :workflows, :dependent => :delete_all do |
|
22 | def copy(tracker) | |
|
23 | raise "Can not copy workflow from a #{tracker.class}" unless tracker.is_a?(Tracker) | |
|
24 | raise "Can not copy workflow from/to an unsaved tracker" if proxy_owner.new_record? || tracker.new_record? | |
|
25 | clear | |
|
26 | connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, old_status_id, new_status_id, role_id)" + | |
|
27 | " SELECT #{proxy_owner.id}, old_status_id, new_status_id, role_id" + | |
|
28 | " FROM #{Workflow.table_name}" + | |
|
29 | " WHERE tracker_id = #{tracker.id}" | |
|
22 | def copy(source_tracker) | |
|
23 | Workflow.copy(source_tracker, nil, proxy_owner, nil) | |
|
30 | 24 | end |
|
31 | 25 | end |
|
32 | 26 |
@@ -51,4 +51,50 class Workflow < ActiveRecord::Base | |||
|
51 | 51 | uniq. |
|
52 | 52 | sort |
|
53 | 53 | end |
|
54 | ||
|
55 | # Copies workflows from source to targets | |
|
56 | def self.copy(source_tracker, source_role, target_trackers, target_roles) | |
|
57 | unless source_tracker.is_a?(Tracker) || source_role.is_a?(Role) | |
|
58 | raise ArgumentError.new "source_tracker or source_role must be specified" | |
|
59 | end | |
|
60 | ||
|
61 | target_trackers = [target_trackers].flatten.compact | |
|
62 | target_roles = [target_roles].flatten.compact | |
|
63 | ||
|
64 | target_trackers = Tracker.all if target_trackers.empty? | |
|
65 | target_roles = Role.all if target_roles.empty? | |
|
66 | ||
|
67 | target_trackers.each do |target_tracker| | |
|
68 | target_roles.each do |target_role| | |
|
69 | copy_one(source_tracker || target_tracker, | |
|
70 | source_role || target_role, | |
|
71 | target_tracker, | |
|
72 | target_role) | |
|
73 | end | |
|
74 | end | |
|
75 | end | |
|
76 | ||
|
77 | # Copies a single set of workflows from source to target | |
|
78 | def self.copy_one(source_tracker, source_role, target_tracker, target_role) | |
|
79 | unless source_tracker.is_a?(Tracker) && !source_tracker.new_record? && | |
|
80 | source_role.is_a?(Role) && !source_role.new_record? && | |
|
81 | target_tracker.is_a?(Tracker) && !target_tracker.new_record? && | |
|
82 | target_role.is_a?(Role) && !target_role.new_record? | |
|
83 | ||
|
84 | raise ArgumentError.new("arguments can not be nil or unsaved objects") | |
|
85 | end | |
|
86 | ||
|
87 | if source_tracker == target_tracker && source_role == target_role | |
|
88 | false | |
|
89 | else | |
|
90 | transaction do | |
|
91 | delete_all :tracker_id => target_tracker.id, :role_id => target_role.id | |
|
92 | connection.insert "INSERT INTO #{Workflow.table_name} (tracker_id, role_id, old_status_id, new_status_id)" + | |
|
93 | " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id" + | |
|
94 | " FROM #{Workflow.table_name}" + | |
|
95 | " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}" | |
|
96 | end | |
|
97 | true | |
|
98 | end | |
|
99 | end | |
|
54 | 100 | end |
@@ -1,6 +1,4 | |||
|
1 | <div class="contextual"> | |
|
2 | <%= link_to l(:field_summary), :action => 'index' %> | |
|
3 | </div> | |
|
1 | <%= render :partial => 'action_menu' %> | |
|
4 | 2 | |
|
5 | 3 | <h2><%=l(:label_workflow)%></h2> |
|
6 | 4 |
@@ -1,3 +1,5 | |||
|
1 | <%= render :partial => 'action_menu' %> | |
|
2 | ||
|
1 | 3 | <h2><%=l(:label_workflow)%></h2> |
|
2 | 4 | |
|
3 | 5 | <% if @workflow_counts.empty? %> |
@@ -159,6 +159,8 en: | |||
|
159 | 159 | error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' |
|
160 | 160 | error_can_not_archive_project: This project can not be archived |
|
161 | 161 | error_issue_done_ratios_not_updated: "Issue done ratios not updated." |
|
162 | error_workflow_copy_source: 'Please select a source tracker or role' | |
|
163 | error_workflow_copy_target: 'Please select target tracker(s) and role(s)' | |
|
162 | 164 | |
|
163 | 165 | warning_attachments_not_saved: "{{count}} file(s) could not be saved." |
|
164 | 166 | |
@@ -720,6 +722,9 en: | |||
|
720 | 722 | label_version_sharing_tree: With project tree |
|
721 | 723 | label_version_sharing_system: With all projects |
|
722 | 724 | label_update_issue_done_ratios: Update issue done ratios |
|
725 | label_copy_source: Source | |
|
726 | label_copy_target: Target | |
|
727 | label_copy_same_as_target: Same as target | |
|
723 | 728 | |
|
724 | 729 | button_login: Login |
|
725 | 730 | button_submit: Submit |
@@ -179,6 +179,8 fr: | |||
|
179 | 179 | error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ ce projet" |
|
180 | 180 | error_can_not_reopen_issue_on_closed_version: 'Une demande assignΓ©e Γ une version fermΓ©e ne peut pas Γͺtre rΓ©ouverte' |
|
181 | 181 | error_can_not_archive_project: "Ce projet ne peut pas Γͺtre archivΓ©" |
|
182 | error_workflow_copy_source: 'Veuillez sΓ©lectionner un tracker et/ou un rΓ΄le source' | |
|
183 | error_workflow_copy_target: 'Veuillez sΓ©lectionner les trackers et rΓ΄les cibles' | |
|
182 | 184 | |
|
183 | 185 | warning_attachments_not_saved: "{{count}} fichier(s) n'ont pas pu Γͺtre sauvegardΓ©s." |
|
184 | 186 | |
@@ -732,6 +734,9 fr: | |||
|
732 | 734 | label_version_sharing_hierarchy: Avec toute la hiΓ©rarchie |
|
733 | 735 | label_version_sharing_tree: Avec tout l'arbre |
|
734 | 736 | label_version_sharing_system: Avec tous les projets |
|
737 | label_copy_source: Source | |
|
738 | label_copy_target: Cible | |
|
739 | label_copy_same_as_target: Comme la cible | |
|
735 | 740 | |
|
736 | 741 | button_login: Connexion |
|
737 | 742 | button_submit: Soumettre |
@@ -297,6 +297,8 ul.properties li span {font-style:italic;} | |||
|
297 | 297 | .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;} |
|
298 | 298 | #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; } |
|
299 | 299 | |
|
300 | #workflow_copy_form select { width: 200px; } | |
|
301 | ||
|
300 | 302 | .pagination {font-size: 90%} |
|
301 | 303 | p.pagination {margin-top:8px;} |
|
302 | 304 | |
@@ -728,6 +730,7 vertical-align: middle; | |||
|
728 | 730 | .icon-details { background-image: url(../images/zoom_in.png); } |
|
729 | 731 | .icon-report { background-image: url(../images/report.png); } |
|
730 | 732 | .icon-comment { background-image: url(../images/comment.png); } |
|
733 | .icon-summary { background-image: url(../images/lightning.png); } | |
|
731 | 734 | |
|
732 | 735 | .icon-file { background-image: url(../images/files/default.png); } |
|
733 | 736 | .icon-file.text-plain { background-image: url(../images/files/text.png); } |
@@ -22,7 +22,7 require 'workflows_controller' | |||
|
22 | 22 | class WorkflowsController; def rescue_action(e) raise e end; end |
|
23 | 23 | |
|
24 | 24 | class WorkflowsControllerTest < ActionController::TestCase |
|
25 | fixtures :roles, :trackers, :workflows | |
|
25 | fixtures :roles, :trackers, :workflows, :users | |
|
26 | 26 | |
|
27 | 27 | def setup |
|
28 | 28 | @controller = WorkflowsController.new |
@@ -81,4 +81,50 class WorkflowsControllerTest < ActionController::TestCase | |||
|
81 | 81 | post :edit, :role_id => 2, :tracker_id => 1 |
|
82 | 82 | assert_equal 0, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2}) |
|
83 | 83 | end |
|
84 | ||
|
85 | def test_get_copy | |
|
86 | get :copy | |
|
87 | assert_response :success | |
|
88 | assert_template 'copy' | |
|
89 | end | |
|
90 | ||
|
91 | def test_post_copy_one_to_one | |
|
92 | source_transitions = status_transitions(:tracker_id => 1, :role_id => 2) | |
|
93 | ||
|
94 | post :copy, :source_tracker_id => '1', :source_role_id => '2', | |
|
95 | :target_tracker_ids => ['3'], :target_role_ids => ['1'] | |
|
96 | assert_response 302 | |
|
97 | assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1) | |
|
98 | end | |
|
99 | ||
|
100 | def test_post_copy_one_to_many | |
|
101 | source_transitions = status_transitions(:tracker_id => 1, :role_id => 2) | |
|
102 | ||
|
103 | post :copy, :source_tracker_id => '1', :source_role_id => '2', | |
|
104 | :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] | |
|
105 | assert_response 302 | |
|
106 | assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 1) | |
|
107 | assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 1) | |
|
108 | assert_equal source_transitions, status_transitions(:tracker_id => 2, :role_id => 3) | |
|
109 | assert_equal source_transitions, status_transitions(:tracker_id => 3, :role_id => 3) | |
|
110 | end | |
|
111 | ||
|
112 | def test_post_copy_many_to_many | |
|
113 | source_t2 = status_transitions(:tracker_id => 2, :role_id => 2) | |
|
114 | source_t3 = status_transitions(:tracker_id => 3, :role_id => 2) | |
|
115 | ||
|
116 | post :copy, :source_tracker_id => 'any', :source_role_id => '2', | |
|
117 | :target_tracker_ids => ['2', '3'], :target_role_ids => ['1', '3'] | |
|
118 | assert_response 302 | |
|
119 | assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 1) | |
|
120 | assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 1) | |
|
121 | assert_equal source_t2, status_transitions(:tracker_id => 2, :role_id => 3) | |
|
122 | assert_equal source_t3, status_transitions(:tracker_id => 3, :role_id => 3) | |
|
123 | end | |
|
124 | ||
|
125 | # Returns an array of status transitions that can be compared | |
|
126 | def status_transitions(conditions) | |
|
127 | Workflow.find(:all, :conditions => conditions, | |
|
128 | :order => 'tracker_id, role_id, old_status_id, new_status_id').collect {|w| [w.old_status, w.new_status_id]} | |
|
129 | end | |
|
84 | 130 | end |
General Comments 0
You need to be logged in to leave comments.
Login now