##// END OF EJS Templates
Bulk edit workflows for multiple trackers/roles (#16164)....
Jean-Philippe Lang -
r12649:b6c794d16b47
parent child
Show More
@@ -0,0 +1,93
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../test_helper', __FILE__)
19
20 class WorkflowTransitionTest < ActiveSupport::TestCase
21 fixtures :roles, :trackers, :issue_statuses
22
23 def setup
24 WorkflowTransition.delete_all
25 end
26
27 def test_replace_transitions_should_create_enabled_transitions
28 w = WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
29
30 transitions = {'1' => {
31 '2' => {'always' => '1'},
32 '3' => {'always' => '1'}
33 }}
34 assert_difference 'WorkflowTransition.count' do
35 WorkflowTransition.replace_transitions(Tracker.find(1), Role.find(1), transitions)
36 end
37 assert WorkflowTransition.where(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3).exists?
38 end
39
40 def test_replace_transitions_should_delete_disabled_transitions
41 w1 = WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
42 w2 = WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
43
44 transitions = {'1' => {
45 '2' => {'always' => '0'},
46 '3' => {'always' => '1'}
47 }}
48 assert_difference 'WorkflowTransition.count', -1 do
49 WorkflowTransition.replace_transitions(Tracker.find(1), Role.find(1), transitions)
50 end
51 assert !WorkflowTransition.exists?(w1.id)
52 end
53
54 def test_replace_transitions_should_create_enabled_additional_transitions
55 transitions = {'1' => {
56 '2' => {'always' => '0', 'assignee' => '0', 'author' => '1'}
57 }}
58 assert_difference 'WorkflowTransition.count' do
59 WorkflowTransition.replace_transitions(Tracker.find(1), Role.find(1), transitions)
60 end
61 w = WorkflowTransition.where(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2).first
62 assert w
63 assert_equal false, w.assignee
64 assert_equal true, w.author
65 end
66
67 def test_replace_transitions_should_delete_disabled_additional_transitions
68 w = WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :assignee => true)
69
70 transitions = {'1' => {
71 '2' => {'always' => '0', 'assignee' => '0', 'author' => '0'}
72 }}
73 assert_difference 'WorkflowTransition.count', -1 do
74 WorkflowTransition.replace_transitions(Tracker.find(1), Role.find(1), transitions)
75 end
76 assert !WorkflowTransition.exists?(w.id)
77 end
78
79 def test_replace_transitions_should_update_additional_transitions
80 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :assignee => true)
81
82 transitions = {'1' => {
83 '2' => {'always' => '0', 'assignee' => '0', 'author' => '1'}
84 }}
85 assert_no_difference 'WorkflowTransition.count' do
86 WorkflowTransition.replace_transitions(Tracker.find(1), Role.find(1), transitions)
87 end
88 w = WorkflowTransition.where(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2).first
89 assert w
90 assert_equal false, w.assignee
91 assert_equal true, w.author
92 end
93 end
@@ -18,40 +18,30
18 class WorkflowsController < ApplicationController
18 class WorkflowsController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20
20
21 before_filter :require_admin, :find_roles, :find_trackers
21 before_filter :require_admin
22
22
23 def index
23 def index
24 @workflow_counts = WorkflowTransition.count_by_tracker_and_role
24 @workflow_counts = WorkflowTransition.count_by_tracker_and_role
25 end
25 end
26
26
27 def edit
27 def edit
28 @role = Role.find_by_id(params[:role_id]) if params[:role_id]
28 find_trackers_roles_and_statuses_for_edit
29 @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
30
29
31 if request.post?
30 if request.post? && @roles && @trackers && params[:transitions]
32 WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id])
31 transitions = params[:transitions].deep_dup
33 (params[:issue_status] || []).each { |status_id, transitions|
32 transitions.each do |old_status_id, transitions_by_new_status|
34 transitions.each { |new_status_id, options|
33 transitions_by_new_status.each do |new_status_id, transition_by_rule|
35 author = options.is_a?(Array) && options.include?('author') && !options.include?('always')
34 transition_by_rule.reject! {|rule, transition| transition == 'no_change'}
36 assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always')
35 end
37 WorkflowTransition.create(:role_id => @role.id, :tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee)
38 }
39 }
40 if @role.save
41 flash[:notice] = l(:notice_successful_update)
42 redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
43 return
44 end
36 end
37 WorkflowTransition.replace_transitions(@trackers, @roles, transitions)
38 flash[:notice] = l(:notice_successful_update)
39 redirect_to_referer_or workflows_edit_path
40 return
45 end
41 end
46
42
47 @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
43 if @trackers && @roles && @statuses.any?
48 if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
44 workflows = WorkflowTransition.where(:role_id => @roles.map(&:id), :tracker_id => @trackers.map(&:id)).all
49 @statuses = @tracker.issue_statuses
50 end
51 @statuses ||= IssueStatus.sorted.all
52
53 if @tracker && @role && @statuses.any?
54 workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all
55 @workflows = {}
45 @workflows = {}
56 @workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
46 @workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
57 @workflows['author'] = workflows.select {|w| w.author}
47 @workflows['author'] = workflows.select {|w| w.author}
@@ -60,36 +50,31 class WorkflowsController < ApplicationController
60 end
50 end
61
51
62 def permissions
52 def permissions
63 @role = Role.find_by_id(params[:role_id]) if params[:role_id]
53 find_trackers_roles_and_statuses_for_edit
64 @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id]
65
54
66 if request.post? && @role && @tracker
55 if request.post? && @roles && @trackers && params[:permissions]
67 WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {})
56 permissions = params[:permissions].deep_dup
57 permissions.each { |field, rule_by_status_id|
58 rule_by_status_id.reject! {|status_id, rule| rule == 'no_change'}
59 }
60 WorkflowPermission.replace_permissions(@trackers, @roles, permissions)
68 flash[:notice] = l(:notice_successful_update)
61 flash[:notice] = l(:notice_successful_update)
69 redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only])
62 redirect_to_referer_or workflows_permissions_path
70 return
63 return
71 end
64 end
72
65
73 @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
66 if @roles && @trackers
74 if @tracker && @used_statuses_only && @tracker.issue_statuses.any?
67 @fields = (Tracker::CORE_FIELDS_ALL - @trackers.map(&:disabled_core_fields).reduce(:&)).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
75 @statuses = @tracker.issue_statuses
68 @custom_fields = @trackers.map(&:custom_fields).flatten.uniq.sort
76 end
69 @permissions = WorkflowPermission.rules_by_status_id(@trackers, @roles)
77 @statuses ||= IssueStatus.sorted.all
78
79 if @role && @tracker
80 @fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
81 @custom_fields = @tracker.custom_fields
82 @permissions = WorkflowPermission.
83 where(:tracker_id => @tracker.id, :role_id => @role.id).inject({}) do |h, w|
84 h[w.old_status_id] ||= {}
85 h[w.old_status_id][w.field_name] = w.rule
86 h
87 end
88 @statuses.each {|status| @permissions[status.id] ||= {}}
70 @statuses.each {|status| @permissions[status.id] ||= {}}
89 end
71 end
90 end
72 end
91
73
92 def copy
74 def copy
75 @roles = Role.sorted
76 @trackers = Tracker.sorted
77
93 if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
78 if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
94 @source_tracker = nil
79 @source_tracker = nil
95 else
80 else
@@ -119,11 +104,37 class WorkflowsController < ApplicationController
119
104
120 private
105 private
121
106
107 def find_trackers_roles_and_statuses_for_edit
108 find_roles
109 find_trackers
110 find_statuses
111 end
112
122 def find_roles
113 def find_roles
123 @roles = Role.sorted.all
114 ids = Array.wrap(params[:role_id])
115 if ids == ['all']
116 @roles = Role.sorted.all
117 elsif ids.present?
118 @roles = Role.where(:id => ids).all
119 end
120 @roles = nil if @roles.blank?
124 end
121 end
125
122
126 def find_trackers
123 def find_trackers
127 @trackers = Tracker.sorted.all
124 ids = Array.wrap(params[:tracker_id])
125 if ids == ['all']
126 @trackers = Tracker.sorted.all
127 elsif ids.present?
128 @trackers = Tracker.where(:id => ids).all
129 end
130 @trackers = nil if @trackers.blank?
131 end
132
133 def find_statuses
134 @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
135 if @trackers && @used_statuses_only
136 @statuses = @trackers.map(&:issue_statuses).flatten.uniq.sort.presence
137 end
138 @statuses ||= IssueStatus.sorted.all
128 end
139 end
129 end
140 end
@@ -18,24 +18,74
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module WorkflowsHelper
20 module WorkflowsHelper
21 def options_for_workflow_select(name, objects, selected, options={})
22 option_tags = ''.html_safe
23 multiple = false
24 if selected
25 if selected.size == objects.size
26 selected = 'all'
27 else
28 selected = selected.map(&:id)
29 if selected.size > 1
30 multiple = true
31 end
32 end
33 else
34 selected = objects.first.try(:id)
35 end
36 all_tag_options = {:value => 'all', :selected => (selected == 'all')}
37 if multiple
38 all_tag_options.merge!(:style => "display:none;")
39 end
40 option_tags << content_tag('option', 'All', all_tag_options)
41 option_tags << options_from_collection_for_select(objects, "id", "name", selected)
42 select_tag name, option_tags, {:multiple => multiple}.merge(options)
43 end
44
21 def field_required?(field)
45 def field_required?(field)
22 field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
46 field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field)
23 end
47 end
24
48
25 def field_permission_tag(permissions, status, field, role)
49 def field_permission_tag(permissions, status, field, roles)
26 name = field.is_a?(CustomField) ? field.id.to_s : field
50 name = field.is_a?(CustomField) ? field.id.to_s : field
27 options = [["", ""], [l(:label_readonly), "readonly"]]
51 options = [["", ""], [l(:label_readonly), "readonly"]]
28 options << [l(:label_required), "required"] unless field_required?(field)
52 options << [l(:label_required), "required"] unless field_required?(field)
29 html_options = {}
53 html_options = {}
30 selected = permissions[status.id][name]
54
55 if perm = permissions[status.id][name]
56 if perm.uniq.size > 1 || perm.size < @roles.size * @trackers.size
57 options << [l(:label_no_change_option), "no_change"]
58 selected = 'no_change'
59 else
60 selected = perm.first
61 end
62 end
63
64 hidden = field.is_a?(CustomField) &&
65 !field.visible? &&
66 !roles.detect {|role| role.custom_fields.to_a.include?(field)}
31
67
32 hidden = field.is_a?(CustomField) && !field.visible? && !role.custom_fields.to_a.include?(field)
33 if hidden
68 if hidden
34 options[0][0] = l(:label_hidden)
69 options[0][0] = l(:label_hidden)
35 selected = ''
70 selected = ''
36 html_options[:disabled] = true
71 html_options[:disabled] = true
37 end
72 end
38
73
39 select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, selected), html_options)
74 select_tag("permissions[#{status.id}][#{name}]", options_for_select(options, selected), html_options)
75 end
76
77 def transition_tag(workflows, old_status, new_status, name)
78 w = workflows.select {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id}.size
79
80 tag_name = "transitions[#{ old_status.id }][#{new_status.id}][#{name}]"
81 if w == 0 || w == @roles.size * @trackers.size
82
83 hidden_field_tag(tag_name, "0") +
84 check_box_tag(tag_name, "1", w != 0,
85 :class => "old-status-#{old_status.id} new-status-#{new_status.id}")
86 else
87 select_tag tag_name,
88 options_for_select([["Oui", "1"], ["Non", "0"], [l(:label_no_change_option), "no_change"]], "no_change")
89 end
40 end
90 end
41 end
91 end
@@ -19,20 +19,43 class WorkflowPermission < WorkflowRule
19 validates_inclusion_of :rule, :in => %w(readonly required)
19 validates_inclusion_of :rule, :in => %w(readonly required)
20 validate :validate_field_name
20 validate :validate_field_name
21
21
22 # Replaces the workflow permissions for the given tracker and role
22 # Returns the workflow permissions for the given trackers and roles
23 # grouped by status_id
23 #
24 #
24 # Example:
25 # Example:
25 # WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required'}}
26 # WorkflowPermission.rules_by_status_id trackers, roles
26 def self.replace_permissions(tracker, role, permissions)
27 # # => {1 => {'start_date' => 'required', 'due_date' => 'readonly'}}
27 destroy_all(:tracker_id => tracker.id, :role_id => role.id)
28 def self.rules_by_status_id(trackers, roles)
29 WorkflowPermission.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).inject({}) do |h, w|
30 h[w.old_status_id] ||= {}
31 h[w.old_status_id][w.field_name] ||= []
32 h[w.old_status_id][w.field_name] << w.rule
33 h
34 end
35 end
28
36
29 permissions.each { |field, rule_by_status_id|
37 # Replaces the workflow permissions for the given trackers and roles
30 rule_by_status_id.each { |status_id, rule|
38 #
31 if rule.present?
39 # Example:
32 WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule)
40 # WorkflowPermission.replace_permissions trackers, roles, {'1' => {'start_date' => 'required', 'due_date' => 'readonly'}}
33 end
41 def self.replace_permissions(trackers, roles, permissions)
42 trackers = Array.wrap trackers
43 roles = Array.wrap roles
44
45 transaction do
46 permissions.each { |status_id, rule_by_field|
47 rule_by_field.each { |field, rule|
48 destroy_all(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id), :old_status_id => status_id, :field_name => field)
49 if rule.present?
50 trackers.each do |tracker|
51 roles.each do |role|
52 WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule)
53 end
54 end
55 end
56 }
34 }
57 }
35 }
58 end
36 end
59 end
37
60
38 protected
61 protected
@@ -36,4 +36,69 class WorkflowTransition < WorkflowRule
36
36
37 result
37 result
38 end
38 end
39
40 def self.replace_transitions(trackers, roles, transitions)
41 trackers = Array.wrap trackers
42 roles = Array.wrap roles
43
44 transaction do
45 records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).all
46
47 transitions.each do |old_status_id, transitions_by_new_status|
48 transitions_by_new_status.each do |new_status_id, transition_by_rule|
49 transition_by_rule.each do |rule, transition|
50 trackers.each do |tracker|
51 roles.each do |role|
52 w = records.select {|r|
53 r.old_status_id == old_status_id.to_i &&
54 r.new_status_id == new_status_id.to_i &&
55 r.tracker_id == tracker.id &&
56 r.role_id == role.id &&
57 !r.destroyed?
58 }
59
60 if rule == 'always'
61 w = w.select {|r| !r.author && !r.assignee}
62 else
63 w = w.select {|r| r.author || r.assignee}
64 end
65 if w.size > 1
66 w[1..-1].each(&:destroy)
67 end
68 w = w.first
69
70 if transition == "1" || transition == true
71 unless w
72 w = WorkflowTransition.new(:old_status_id => old_status_id, :new_status_id => new_status_id, :tracker_id => tracker.id, :role_id => role.id)
73 records << w
74 end
75 w.author = true if rule == "author"
76 w.assignee = true if rule == "assignee"
77 w.save if w.changed?
78 elsif w
79 if rule == 'always'
80 w.destroy
81 elsif rule == 'author'
82 if w.assignee
83 w.author = false
84 w.save if w.changed?
85 else
86 w.destroy
87 end
88 elsif rule == 'assignee'
89 if w.author
90 w.assignee = false
91 w.save if w.changed?
92 else
93 w.destroy
94 end
95 end
96 end
97 end
98 end
99 end
100 end
101 end
102 end
103 end
39 end
104 end
@@ -1,4 +1,4
1 <table class="list transitions transitions-<%= name %>">
1 <table class="list workflows transitions transitions-<%= name %>">
2 <thead>
2 <thead>
3 <tr>
3 <tr>
4 <th>
4 <th>
@@ -31,8 +31,7
31 <% for new_status in @statuses -%>
31 <% for new_status in @statuses -%>
32 <% checked = workflows.detect {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id} %>
32 <% checked = workflows.detect {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id} %>
33 <td class="<%= checked ? 'enabled' : '' %>">
33 <td class="<%= checked ? 'enabled' : '' %>">
34 <%= check_box_tag "issue_status[#{ old_status.id }][#{new_status.id}][]", name, checked,
34 <%= transition_tag workflows, old_status, new_status, name %>
35 :class => "old-status-#{old_status.id} new-status-#{new_status.id}" %>
36 </td>
35 </td>
37 <% end -%>
36 <% end -%>
38 </tr>
37 </tr>
@@ -4,8 +4,8
4
4
5 <div class="tabs">
5 <div class="tabs">
6 <ul>
6 <ul>
7 <li><%= link_to l(:label_status_transitions), {:action => 'edit', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %></li>
7 <li><%= link_to l(:label_status_transitions), workflows_edit_path(:role_id => @roles, :tracker_id => @trackers), :class => 'selected' %></li>
8 <li><%= link_to l(:label_fields_permissions), {:action => 'permissions', :role_id => @role, :tracker_id => @tracker} %></li>
8 <li><%= link_to l(:label_fields_permissions), workflows_permissions_path(:role_id => @roles, :tracker_id => @trackers) %></li>
9 </ul>
9 </ul>
10 </div>
10 </div>
11
11
@@ -14,10 +14,14
14 <%= form_tag({}, :method => 'get') do %>
14 <%= form_tag({}, :method => 'get') do %>
15 <p>
15 <p>
16 <label><%=l(:label_role)%>:
16 <label><%=l(:label_role)%>:
17 <%= select_tag 'role_id', options_from_collection_for_select(@roles, "id", "name", @role && @role.id) %></label>
17 <%= options_for_workflow_select 'role_id[]', Role.sorted, @roles, :id => 'role_id', :class => 'expandable' %>
18 </label>
19 <a href="#" data-expands="#role_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
18
20
19 <label><%=l(:label_tracker)%>:
21 <label><%=l(:label_tracker)%>:
20 <%= select_tag 'tracker_id', options_from_collection_for_select(@trackers, "id", "name", @tracker && @tracker.id) %></label>
22 <%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %>
23 </label>
24 <a href="#" data-expands="#tracker_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
21
25
22 <%= submit_tag l(:button_edit), :name => nil %>
26 <%= submit_tag l(:button_edit), :name => nil %>
23
27
@@ -27,10 +31,10
27 </p>
31 </p>
28 <% end %>
32 <% end %>
29
33
30 <% if @tracker && @role && @statuses.any? %>
34 <% if @trackers && @roles && @statuses.any? %>
31 <%= form_tag({}, :id => 'workflow_form' ) do %>
35 <%= form_tag({}, :id => 'workflow_form' ) do %>
32 <%= hidden_field_tag 'tracker_id', @tracker.id %>
36 <%= @trackers.map {|tracker| hidden_field_tag 'tracker_id[]', tracker.id}.join.html_safe %>
33 <%= hidden_field_tag 'role_id', @role.id %>
37 <%= @roles.map {|role| hidden_field_tag 'role_id[]', role.id}.join.html_safe %>
34 <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %>
38 <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %>
35 <div class="autoscroll">
39 <div class="autoscroll">
36 <%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %>
40 <%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %>
@@ -54,3 +58,18
54 <%= submit_tag l(:button_save) %>
58 <%= submit_tag l(:button_save) %>
55 <% end %>
59 <% end %>
56 <% end %>
60 <% end %>
61
62 <%= javascript_tag do %>
63 $("a[data-expands]").click(function(e){
64 e.preventDefault();
65 var target = $($(this).attr("data-expands"));
66 if (target.attr("multiple")) {
67 target.attr("multiple", false);
68 target.find("option[value=all]").show();
69 } else {
70 target.attr("multiple", true);
71 target.find("option[value=all]").attr("selected", false).hide();
72 }
73 });
74
75 <% end %>
@@ -4,8 +4,8
4
4
5 <div class="tabs">
5 <div class="tabs">
6 <ul>
6 <ul>
7 <li><%= link_to l(:label_status_transitions), {:action => 'edit', :role_id => @role, :tracker_id => @tracker} %></li>
7 <li><%= link_to l(:label_status_transitions), workflows_edit_path(:role_id => @roles, :tracker_id => @trackers) %></li>
8 <li><%= link_to l(:label_fields_permissions), {:action => 'permissions', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %></li>
8 <li><%= link_to l(:label_fields_permissions), workflows_permissions_path(:role_id => @roles, :tracker_id => @trackers), :class => 'selected' %></li>
9 </ul>
9 </ul>
10 </div>
10 </div>
11
11
@@ -14,10 +14,14
14 <%= form_tag({}, :method => 'get') do %>
14 <%= form_tag({}, :method => 'get') do %>
15 <p>
15 <p>
16 <label><%=l(:label_role)%>:
16 <label><%=l(:label_role)%>:
17 <%= select_tag 'role_id', options_from_collection_for_select(@roles, "id", "name", @role && @role.id) %></label>
17 <%= options_for_workflow_select 'role_id[]', Role.sorted, @roles, :id => 'role_id', :class => 'expandable' %>
18 </label>
19 <a href="#" data-expands="#role_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
18
20
19 <label><%=l(:label_tracker)%>:
21 <label><%=l(:label_tracker)%>:
20 <%= select_tag 'tracker_id', options_from_collection_for_select(@trackers, "id", "name", @tracker && @tracker.id) %></label>
22 <%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %>
23 </label>
24 <a href="#" data-expands="#tracker_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
21
25
22 <%= submit_tag l(:button_edit), :name => nil %>
26 <%= submit_tag l(:button_edit), :name => nil %>
23
27
@@ -26,13 +30,13
26 </p>
30 </p>
27 <% end %>
31 <% end %>
28
32
29 <% if @tracker && @role && @statuses.any? %>
33 <% if @trackers && @roles && @statuses.any? %>
30 <%= form_tag({}, :id => 'workflow_form' ) do %>
34 <%= form_tag({}, :id => 'workflow_form' ) do %>
31 <%= hidden_field_tag 'tracker_id', @tracker.id %>
35 <%= @trackers.map {|tracker| hidden_field_tag 'tracker_id[]', tracker.id}.join.html_safe %>
32 <%= hidden_field_tag 'role_id', @role.id %>
36 <%= @roles.map {|role| hidden_field_tag 'role_id[]', role.id}.join.html_safe %>
33 <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %>
37 <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %>
34 <div class="autoscroll">
38 <div class="autoscroll">
35 <table class="list fields_permissions">
39 <table class="list workflows fields_permissions">
36 <thead>
40 <thead>
37 <tr>
41 <tr>
38 <th>
42 <th>
@@ -62,7 +66,7
62 </td>
66 </td>
63 <% for status in @statuses -%>
67 <% for status in @statuses -%>
64 <td class="<%= @permissions[status.id][field] %>">
68 <td class="<%= @permissions[status.id][field] %>">
65 <%= field_permission_tag(@permissions, status, field, @role) %>
69 <%= field_permission_tag(@permissions, status, field, @roles) %>
66 <% unless status == @statuses.last %><a href="#" class="repeat-value">&#187;</a><% end %>
70 <% unless status == @statuses.last %><a href="#" class="repeat-value">&#187;</a><% end %>
67 </td>
71 </td>
68 <% end -%>
72 <% end -%>
@@ -82,7 +86,7
82 </td>
86 </td>
83 <% for status in @statuses -%>
87 <% for status in @statuses -%>
84 <td class="<%= @permissions[status.id][field.id.to_s] %>">
88 <td class="<%= @permissions[status.id][field.id.to_s] %>">
85 <%= field_permission_tag(@permissions, status, field, @role) %>
89 <%= field_permission_tag(@permissions, status, field, @roles) %>
86 <% unless status == @statuses.last %><a href="#" class="repeat-value">&#187;</a><% end %>
90 <% unless status == @statuses.last %><a href="#" class="repeat-value">&#187;</a><% end %>
87 </td>
91 </td>
88 <% end -%>
92 <% end -%>
@@ -103,4 +107,17 $("a.repeat-value").click(function(e){
103 var selected = td.find("select").find(":selected").val();
107 var selected = td.find("select").find(":selected").val();
104 td.nextAll('td').find("select").val(selected);
108 td.nextAll('td').find("select").val(selected);
105 });
109 });
110
111 $("a[data-expands]").click(function(e){
112 e.preventDefault();
113 var target = $($(this).attr("data-expands"));
114 if (target.attr("multiple")) {
115 target.attr("multiple", false);
116 target.find("option[value=all]").show();
117 } else {
118 target.attr("multiple", true);
119 target.find("option[value=all]").attr("selected", false).hide();
120 }
121 });
122
106 <% end %>
123 <% end %>
@@ -254,6 +254,8 table.boards td.last-message {text-align:left;font-size:80%;}
254
254
255 table.messages td.last_message {text-align:left;}
255 table.messages td.last_message {text-align:left;}
256
256
257 #query_form_content {font-size:90%;}
258
257 table.query-columns {
259 table.query-columns {
258 border-collapse: collapse;
260 border-collapse: collapse;
259 border: 0;
261 border: 0;
@@ -340,7 +342,7 div.issue table.attributes td {width:28%;}
340 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
342 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
341 #relations td.buttons {padding:0;}
343 #relations td.buttons {padding:0;}
342
344
343 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
345 fieldset.collapsible {border-width: 1px 0 0 0;}
344 fieldset.collapsible>legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
346 fieldset.collapsible>legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
345 fieldset.collapsible.collapsed>legend { background-image: url(../images/arrow_collapsed.png); }
347 fieldset.collapsible.collapsed>legend { background-image: url(../images/arrow_collapsed.png); }
346
348
@@ -453,10 +455,12 ul.properties li span {font-style:italic;}
453
455
454 #workflow_copy_form select { width: 200px; }
456 #workflow_copy_form select { width: 200px; }
455 table.transitions td.enabled {background: #bfb;}
457 table.transitions td.enabled {background: #bfb;}
456 table.fields_permissions select {font-size:90%}
458 #workflow_form table select {font-size:90%; max-width:100px;}
457 table.fields_permissions td.readonly {background:#ddd;}
459 table.fields_permissions td.readonly {background:#ddd;}
458 table.fields_permissions td.required {background:#d88;}
460 table.fields_permissions td.required {background:#d88;}
459
461
462 select.expandable {vertical-align:top;}
463
460 textarea#custom_field_possible_values {width: 95%; resize:vertical}
464 textarea#custom_field_possible_values {width: 95%; resize:vertical}
461 textarea#custom_field_default_value {width: 95%; resize:vertical}
465 textarea#custom_field_default_value {width: 95%; resize:vertical}
462
466
@@ -39,8 +39,6 class WorkflowsControllerTest < ActionController::TestCase
39 get :edit
39 get :edit
40 assert_response :success
40 assert_response :success
41 assert_template 'edit'
41 assert_template 'edit'
42 assert_not_nil assigns(:roles)
43 assert_not_nil assigns(:trackers)
44 end
42 end
45
43
46 def test_get_edit_with_role_and_tracker
44 def test_get_edit_with_role_and_tracker
@@ -57,18 +55,11 class WorkflowsControllerTest < ActionController::TestCase
57 assert_equal [2, 3, 5], assigns(:statuses).collect(&:id)
55 assert_equal [2, 3, 5], assigns(:statuses).collect(&:id)
58
56
59 # allowed transitions
57 # allowed transitions
60 assert_tag :tag => 'input', :attributes => { :type => 'checkbox',
58 assert_select 'input[type=checkbox][name=?][value=1][checked=checked]', 'transitions[3][5][always]'
61 :name => 'issue_status[3][5][]',
62 :value => 'always',
63 :checked => 'checked' }
64 # not allowed
59 # not allowed
65 assert_tag :tag => 'input', :attributes => { :type => 'checkbox',
60 assert_select 'input[type=checkbox][name=?][value=1]:not([checked=checked])', 'transitions[3][2][always]'
66 :name => 'issue_status[3][2][]',
67 :value => 'always',
68 :checked => nil }
69 # unused
61 # unused
70 assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox',
62 assert_select 'input[type=checkbox][name=?]', 'transitions[1][1][always]', 0
71 :name => 'issue_status[1][1][]' }
72 end
63 end
73
64
74 def test_get_edit_with_role_and_tracker_and_all_statuses
65 def test_get_edit_with_role_and_tracker_and_all_statuses
@@ -81,19 +72,18 class WorkflowsControllerTest < ActionController::TestCase
81 assert_not_nil assigns(:statuses)
72 assert_not_nil assigns(:statuses)
82 assert_equal IssueStatus.count, assigns(:statuses).size
73 assert_equal IssueStatus.count, assigns(:statuses).size
83
74
84 assert_tag :tag => 'input', :attributes => { :type => 'checkbox',
75 assert_select 'input[type=checkbox][name=?]', 'transitions[1][1][always]'
85 :name => 'issue_status[1][1][]',
86 :value => 'always',
87 :checked => nil }
88 end
76 end
89
77
90 def test_post_edit
78 def test_post_edit
79 WorkflowTransition.delete_all
80
91 post :edit, :role_id => 2, :tracker_id => 1,
81 post :edit, :role_id => 2, :tracker_id => 1,
92 :issue_status => {
82 :transitions => {
93 '4' => {'5' => ['always']},
83 '4' => {'5' => {'always' => '1'}},
94 '3' => {'1' => ['always'], '2' => ['always']}
84 '3' => {'1' => {'always' => '1'}, '2' => {'always' => '1'}}
95 }
85 }
96 assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1'
86 assert_response 302
97
87
98 assert_equal 3, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count
88 assert_equal 3, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count
99 assert_not_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first
89 assert_not_nil WorkflowTransition.where(:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2).first
@@ -101,12 +91,16 class WorkflowsControllerTest < ActionController::TestCase
101 end
91 end
102
92
103 def test_post_edit_with_additional_transitions
93 def test_post_edit_with_additional_transitions
94 WorkflowTransition.delete_all
95
104 post :edit, :role_id => 2, :tracker_id => 1,
96 post :edit, :role_id => 2, :tracker_id => 1,
105 :issue_status => {
97 :transitions => {
106 '4' => {'5' => ['always']},
98 '4' => {'5' => {'always' => '1', 'author' => '0', 'assignee' => '0'}},
107 '3' => {'1' => ['author'], '2' => ['assignee'], '4' => ['author', 'assignee']}
99 '3' => {'1' => {'always' => '0', 'author' => '1', 'assignee' => '0'},
100 '2' => {'always' => '0', 'author' => '0', 'assignee' => '1'},
101 '4' => {'always' => '0', 'author' => '1', 'assignee' => '1'}}
108 }
102 }
109 assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1'
103 assert_response 302
110
104
111 assert_equal 4, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count
105 assert_equal 4, WorkflowTransition.where(:tracker_id => 1, :role_id => 2).count
112
106
@@ -124,20 +118,11 class WorkflowsControllerTest < ActionController::TestCase
124 assert w.assignee
118 assert w.assignee
125 end
119 end
126
120
127 def test_clear_workflow
128 assert WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count > 0
129
130 post :edit, :role_id => 1, :tracker_id => 2
131 assert_equal 0, WorkflowTransition.where(:role_id => 1, :tracker_id => 2).count
132 end
133
134 def test_get_permissions
121 def test_get_permissions
135 get :permissions
122 get :permissions
136
123
137 assert_response :success
124 assert_response :success
138 assert_template 'permissions'
125 assert_template 'permissions'
139 assert_not_nil assigns(:roles)
140 assert_not_nil assigns(:trackers)
141 end
126 end
142
127
143 def test_get_permissions_with_role_and_tracker
128 def test_get_permissions_with_role_and_tracker
@@ -150,11 +135,11 class WorkflowsControllerTest < ActionController::TestCase
150 assert_response :success
135 assert_response :success
151 assert_template 'permissions'
136 assert_template 'permissions'
152
137
153 assert_select 'input[name=role_id][value=1]'
138 assert_select 'input[name=?][value=1]', 'role_id[]'
154 assert_select 'input[name=tracker_id][value=2]'
139 assert_select 'input[name=?][value=2]', 'tracker_id[]'
155
140
156 # Required field
141 # Required field
157 assert_select 'select[name=?]', 'permissions[assigned_to_id][2]' do
142 assert_select 'select[name=?]', 'permissions[2][assigned_to_id]' do
158 assert_select 'option[value=]'
143 assert_select 'option[value=]'
159 assert_select 'option[value=][selected=selected]', 0
144 assert_select 'option[value=][selected=selected]', 0
160 assert_select 'option[value=readonly]', :text => 'Read-only'
145 assert_select 'option[value=readonly]', :text => 'Read-only'
@@ -164,7 +149,7 class WorkflowsControllerTest < ActionController::TestCase
164 end
149 end
165
150
166 # Read-only field
151 # Read-only field
167 assert_select 'select[name=?]', 'permissions[fixed_version_id][3]' do
152 assert_select 'select[name=?]', 'permissions[3][fixed_version_id]' do
168 assert_select 'option[value=]'
153 assert_select 'option[value=]'
169 assert_select 'option[value=][selected=selected]', 0
154 assert_select 'option[value=][selected=selected]', 0
170 assert_select 'option[value=readonly]', :text => 'Read-only'
155 assert_select 'option[value=readonly]', :text => 'Read-only'
@@ -174,7 +159,7 class WorkflowsControllerTest < ActionController::TestCase
174 end
159 end
175
160
176 # Other field
161 # Other field
177 assert_select 'select[name=?]', 'permissions[due_date][3]' do
162 assert_select 'select[name=?]', 'permissions[3][due_date]' do
178 assert_select 'option[value=]'
163 assert_select 'option[value=]'
179 assert_select 'option[value=][selected=selected]', 0
164 assert_select 'option[value=][selected=selected]', 0
180 assert_select 'option[value=readonly]', :text => 'Read-only'
165 assert_select 'option[value=readonly]', :text => 'Read-only'
@@ -193,7 +178,7 class WorkflowsControllerTest < ActionController::TestCase
193
178
194 # Custom field that is always required
179 # Custom field that is always required
195 # The default option is "(Required)"
180 # The default option is "(Required)"
196 assert_select 'select[name=?]', "permissions[#{cf.id}][3]" do
181 assert_select 'select[name=?]', "permissions[3][#{cf.id}]" do
197 assert_select 'option[value=]'
182 assert_select 'option[value=]'
198 assert_select 'option[value=readonly]', :text => 'Read-only'
183 assert_select 'option[value=readonly]', :text => 'Read-only'
199 assert_select 'option[value=required]', 0
184 assert_select 'option[value=required]', 0
@@ -209,15 +194,56 class WorkflowsControllerTest < ActionController::TestCase
209 assert_response :success
194 assert_response :success
210 assert_template 'permissions'
195 assert_template 'permissions'
211
196
212 assert_select 'select[name=?]:not(.disabled)', "permissions[#{cf1.id}][1]"
197 assert_select 'select[name=?]:not(.disabled)', "permissions[1][#{cf1.id}]"
213 assert_select 'select[name=?]:not(.disabled)', "permissions[#{cf3.id}][1]"
198 assert_select 'select[name=?]:not(.disabled)', "permissions[1][#{cf3.id}]"
214
199
215 assert_select 'select[name=?][disabled=disabled]', "permissions[#{cf2.id}][1]" do
200 assert_select 'select[name=?][disabled=disabled]', "permissions[1][#{cf2.id}]" do
216 assert_select 'option[value=][selected=selected]', :text => 'Hidden'
201 assert_select 'option[value=][selected=selected]', :text => 'Hidden'
217 end
202 end
218 end
203 end
219
204
220 def test_get_permissions_with_role_and_tracker_and_all_statuses
205 def test_get_permissions_with_missing_permissions_for_roles_should_default_to_no_change
206 WorkflowPermission.delete_all
207 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :field_name => 'assigned_to_id', :rule => 'required')
208
209 get :permissions, :role_id => [1, 2], :tracker_id => 2
210 assert_response :success
211
212 assert_select 'select[name=?]', 'permissions[1][assigned_to_id]' do
213 assert_select 'option[selected]', 1
214 assert_select 'option[selected][value=no_change]'
215 end
216 end
217
218 def test_get_permissions_with_different_permissions_for_roles_should_default_to_no_change
219 WorkflowPermission.delete_all
220 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :field_name => 'assigned_to_id', :rule => 'required')
221 WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 1, :field_name => 'assigned_to_id', :rule => 'readonly')
222
223 get :permissions, :role_id => [1, 2], :tracker_id => 2
224 assert_response :success
225
226 assert_select 'select[name=?]', 'permissions[1][assigned_to_id]' do
227 assert_select 'option[selected]', 1
228 assert_select 'option[selected][value=no_change]'
229 end
230 end
231
232 def test_get_permissions_with_same_permissions_for_roles_should_default_to_permission
233 WorkflowPermission.delete_all
234 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :field_name => 'assigned_to_id', :rule => 'required')
235 WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 1, :field_name => 'assigned_to_id', :rule => 'required')
236
237 get :permissions, :role_id => [1, 2], :tracker_id => 2
238 assert_response :success
239
240 assert_select 'select[name=?]', 'permissions[1][assigned_to_id]' do
241 assert_select 'option[selected]', 1
242 assert_select 'option[selected][value=required]'
243 end
244 end
245
246 def test_get_permissions_with_role_and_tracker_and_all_statuses_should_show_all_statuses
221 WorkflowTransition.delete_all
247 WorkflowTransition.delete_all
222
248
223 get :permissions, :role_id => 1, :tracker_id => 2, :used_statuses_only => '0'
249 get :permissions, :role_id => 1, :tracker_id => 2, :used_statuses_only => '0'
@@ -229,11 +255,11 class WorkflowsControllerTest < ActionController::TestCase
229 WorkflowPermission.delete_all
255 WorkflowPermission.delete_all
230
256
231 post :permissions, :role_id => 1, :tracker_id => 2, :permissions => {
257 post :permissions, :role_id => 1, :tracker_id => 2, :permissions => {
232 'assigned_to_id' => {'1' => '', '2' => 'readonly', '3' => ''},
258 '1' => {'assigned_to_id' => '', 'fixed_version_id' => 'required', 'due_date' => ''},
233 'fixed_version_id' => {'1' => 'required', '2' => 'readonly', '3' => ''},
259 '2' => {'assigned_to_id' => 'readonly', 'fixed_version_id' => 'readonly', 'due_date' => ''},
234 'due_date' => {'1' => '', '2' => '', '3' => ''},
260 '3' => {'assigned_to_id' => '', 'fixed_version_id' => '', 'due_date' => ''}
235 }
261 }
236 assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2'
262 assert_response 302
237
263
238 workflows = WorkflowPermission.all
264 workflows = WorkflowPermission.all
239 assert_equal 3, workflows.size
265 assert_equal 3, workflows.size
@@ -246,22 +272,6 class WorkflowsControllerTest < ActionController::TestCase
246 assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'fixed_version_id' && wf.rule == 'readonly'}
272 assert workflows.detect {|wf| wf.old_status_id == 2 && wf.field_name == 'fixed_version_id' && wf.rule == 'readonly'}
247 end
273 end
248
274
249 def test_post_permissions_should_clear_permissions
250 WorkflowPermission.delete_all
251 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'assigned_to_id', :rule => 'required')
252 WorkflowPermission.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required')
253 wf1 = WorkflowPermission.create!(:role_id => 1, :tracker_id => 3, :old_status_id => 2, :field_name => 'fixed_version_id', :rule => 'required')
254 wf2 = WorkflowPermission.create!(:role_id => 2, :tracker_id => 2, :old_status_id => 3, :field_name => 'fixed_version_id', :rule => 'readonly')
255
256 post :permissions, :role_id => 1, :tracker_id => 2
257 assert_redirected_to '/workflows/permissions?role_id=1&tracker_id=2'
258
259 workflows = WorkflowPermission.all
260 assert_equal 2, workflows.size
261 assert wf1.reload
262 assert wf2.reload
263 end
264
265 def test_get_copy
275 def test_get_copy
266 get :copy
276 get :copy
267 assert_response :success
277 assert_response :success
General Comments 0
You need to be logged in to leave comments. Login now