##// END OF EJS Templates
Don't show roles without issue add/edit permission in workflow setup (#15988)....
Jean-Philippe Lang -
r13366:c886ffe200a4
parent child
Show More
@@ -1,140 +1,140
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class WorkflowsController < ApplicationController
19 19 layout 'admin'
20 20
21 21 before_filter :require_admin
22 22
23 23 def index
24 24 @workflow_counts = WorkflowTransition.count_by_tracker_and_role
25 25 end
26 26
27 27 def edit
28 28 find_trackers_roles_and_statuses_for_edit
29 29
30 30 if request.post? && @roles && @trackers && params[:transitions]
31 31 transitions = params[:transitions].deep_dup
32 32 transitions.each do |old_status_id, transitions_by_new_status|
33 33 transitions_by_new_status.each do |new_status_id, transition_by_rule|
34 34 transition_by_rule.reject! {|rule, transition| transition == 'no_change'}
35 35 end
36 36 end
37 37 WorkflowTransition.replace_transitions(@trackers, @roles, transitions)
38 38 flash[:notice] = l(:notice_successful_update)
39 39 redirect_to_referer_or workflows_edit_path
40 40 return
41 41 end
42 42
43 43 if @trackers && @roles && @statuses.any?
44 44 workflows = WorkflowTransition.where(:role_id => @roles.map(&:id), :tracker_id => @trackers.map(&:id))
45 45 @workflows = {}
46 46 @workflows['always'] = workflows.select {|w| !w.author && !w.assignee}
47 47 @workflows['author'] = workflows.select {|w| w.author}
48 48 @workflows['assignee'] = workflows.select {|w| w.assignee}
49 49 end
50 50 end
51 51
52 52 def permissions
53 53 find_trackers_roles_and_statuses_for_edit
54 54
55 55 if request.post? && @roles && @trackers && params[:permissions]
56 56 permissions = params[:permissions].deep_dup
57 57 permissions.each { |field, rule_by_status_id|
58 58 rule_by_status_id.reject! {|status_id, rule| rule == 'no_change'}
59 59 }
60 60 WorkflowPermission.replace_permissions(@trackers, @roles, permissions)
61 61 flash[:notice] = l(:notice_successful_update)
62 62 redirect_to_referer_or workflows_permissions_path
63 63 return
64 64 end
65 65
66 66 if @roles && @trackers
67 67 @fields = (Tracker::CORE_FIELDS_ALL - @trackers.map(&:disabled_core_fields).reduce(:&)).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]}
68 68 @custom_fields = @trackers.map(&:custom_fields).flatten.uniq.sort
69 69 @permissions = WorkflowPermission.rules_by_status_id(@trackers, @roles)
70 70 @statuses.each {|status| @permissions[status.id] ||= {}}
71 71 end
72 72 end
73 73
74 74 def copy
75 @roles = Role.sorted
75 @roles = Role.sorted.select(&:consider_workflow?)
76 76 @trackers = Tracker.sorted
77 77
78 78 if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any'
79 79 @source_tracker = nil
80 80 else
81 81 @source_tracker = Tracker.find_by_id(params[:source_tracker_id].to_i)
82 82 end
83 83 if params[:source_role_id].blank? || params[:source_role_id] == 'any'
84 84 @source_role = nil
85 85 else
86 86 @source_role = Role.find_by_id(params[:source_role_id].to_i)
87 87 end
88 88 @target_trackers = params[:target_tracker_ids].blank? ?
89 89 nil : Tracker.where(:id => params[:target_tracker_ids]).to_a
90 90 @target_roles = params[:target_role_ids].blank? ?
91 91 nil : Role.where(:id => params[:target_role_ids]).to_a
92 92 if request.post?
93 93 if params[:source_tracker_id].blank? || params[:source_role_id].blank? || (@source_tracker.nil? && @source_role.nil?)
94 94 flash.now[:error] = l(:error_workflow_copy_source)
95 95 elsif @target_trackers.blank? || @target_roles.blank?
96 96 flash.now[:error] = l(:error_workflow_copy_target)
97 97 else
98 98 WorkflowRule.copy(@source_tracker, @source_role, @target_trackers, @target_roles)
99 99 flash[:notice] = l(:notice_successful_update)
100 100 redirect_to workflows_copy_path(:source_tracker_id => @source_tracker, :source_role_id => @source_role)
101 101 end
102 102 end
103 103 end
104 104
105 105 private
106 106
107 107 def find_trackers_roles_and_statuses_for_edit
108 108 find_roles
109 109 find_trackers
110 110 find_statuses
111 111 end
112 112
113 113 def find_roles
114 114 ids = Array.wrap(params[:role_id])
115 115 if ids == ['all']
116 116 @roles = Role.sorted.to_a
117 117 elsif ids.present?
118 118 @roles = Role.where(:id => ids).to_a
119 119 end
120 120 @roles = nil if @roles.blank?
121 121 end
122 122
123 123 def find_trackers
124 124 ids = Array.wrap(params[:tracker_id])
125 125 if ids == ['all']
126 126 @trackers = Tracker.sorted.to_a
127 127 elsif ids.present?
128 128 @trackers = Tracker.where(:id => ids).to_a
129 129 end
130 130 @trackers = nil if @trackers.blank?
131 131 end
132 132
133 133 def find_statuses
134 134 @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true)
135 135 if @trackers && @used_statuses_only
136 136 @statuses = @trackers.map(&:issue_statuses).flatten.uniq.sort.presence
137 137 end
138 138 @statuses ||= IssueStatus.sorted.to_a
139 139 end
140 140 end
@@ -1,74 +1,74
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class WorkflowRule < ActiveRecord::Base
19 19 self.table_name = "#{table_name_prefix}workflows#{table_name_suffix}"
20 20
21 21 belongs_to :role
22 22 belongs_to :tracker
23 23 belongs_to :old_status, :class_name => 'IssueStatus'
24 24 belongs_to :new_status, :class_name => 'IssueStatus'
25 25
26 26 validates_presence_of :role, :tracker, :old_status
27 27 attr_protected :id
28 28
29 29 # Copies workflows from source to targets
30 30 def self.copy(source_tracker, source_role, target_trackers, target_roles)
31 31 unless source_tracker.is_a?(Tracker) || source_role.is_a?(Role)
32 32 raise ArgumentError.new("source_tracker or source_role must be specified")
33 33 end
34 34
35 35 target_trackers = [target_trackers].flatten.compact
36 36 target_roles = [target_roles].flatten.compact
37 37
38 38 target_trackers = Tracker.sorted.to_a if target_trackers.empty?
39 target_roles = Role.all if target_roles.empty?
39 target_roles = Role.all.select(&:consider_workflow?) if target_roles.empty?
40 40
41 41 target_trackers.each do |target_tracker|
42 42 target_roles.each do |target_role|
43 43 copy_one(source_tracker || target_tracker,
44 44 source_role || target_role,
45 45 target_tracker,
46 46 target_role)
47 47 end
48 48 end
49 49 end
50 50
51 51 # Copies a single set of workflows from source to target
52 52 def self.copy_one(source_tracker, source_role, target_tracker, target_role)
53 53 unless source_tracker.is_a?(Tracker) && !source_tracker.new_record? &&
54 54 source_role.is_a?(Role) && !source_role.new_record? &&
55 55 target_tracker.is_a?(Tracker) && !target_tracker.new_record? &&
56 56 target_role.is_a?(Role) && !target_role.new_record?
57 57
58 58 raise ArgumentError.new("arguments can not be nil or unsaved objects")
59 59 end
60 60
61 61 if source_tracker == target_tracker && source_role == target_role
62 62 false
63 63 else
64 64 transaction do
65 65 delete_all :tracker_id => target_tracker.id, :role_id => target_role.id
66 66 connection.insert "INSERT INTO #{WorkflowRule.table_name} (tracker_id, role_id, old_status_id, new_status_id, author, assignee, field_name, #{connection.quote_column_name 'rule'}, type)" +
67 67 " SELECT #{target_tracker.id}, #{target_role.id}, old_status_id, new_status_id, author, assignee, field_name, #{connection.quote_column_name 'rule'}, type" +
68 68 " FROM #{WorkflowRule.table_name}" +
69 69 " WHERE tracker_id = #{source_tracker.id} AND role_id = #{source_role.id}"
70 70 end
71 71 true
72 72 end
73 73 end
74 74 end
@@ -1,102 +1,102
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class WorkflowTransition < WorkflowRule
19 19 validates_presence_of :new_status
20 20
21 21 # Returns workflow transitions count by tracker and role
22 22 def self.count_by_tracker_and_role
23 23 counts = connection.select_all("SELECT role_id, tracker_id, count(id) AS c FROM #{table_name} WHERE type = 'WorkflowTransition' GROUP BY role_id, tracker_id")
24 roles = Role.sorted
24 roles = Role.sorted.to_a.select(&:consider_workflow?)
25 25 trackers = Tracker.sorted
26 26 result = []
27 27 trackers.each do |tracker|
28 28 t = []
29 29 roles.each do |role|
30 30 row = counts.detect {|c| c['role_id'].to_s == role.id.to_s && c['tracker_id'].to_s == tracker.id.to_s}
31 31 t << [role, (row.nil? ? 0 : row['c'].to_i)]
32 32 end
33 33 result << [tracker, t]
34 34 end
35 35 result
36 36 end
37 37
38 38 def self.replace_transitions(trackers, roles, transitions)
39 39 trackers = Array.wrap trackers
40 40 roles = Array.wrap roles
41 41
42 42 transaction do
43 43 records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).to_a
44 44
45 45 transitions.each do |old_status_id, transitions_by_new_status|
46 46 transitions_by_new_status.each do |new_status_id, transition_by_rule|
47 47 transition_by_rule.each do |rule, transition|
48 48 trackers.each do |tracker|
49 49 roles.each do |role|
50 50 w = records.select {|r|
51 51 r.old_status_id == old_status_id.to_i &&
52 52 r.new_status_id == new_status_id.to_i &&
53 53 r.tracker_id == tracker.id &&
54 54 r.role_id == role.id &&
55 55 !r.destroyed?
56 56 }
57 57
58 58 if rule == 'always'
59 59 w = w.select {|r| !r.author && !r.assignee}
60 60 else
61 61 w = w.select {|r| r.author || r.assignee}
62 62 end
63 63 if w.size > 1
64 64 w[1..-1].each(&:destroy)
65 65 end
66 66 w = w.first
67 67
68 68 if transition == "1" || transition == true
69 69 unless w
70 70 w = WorkflowTransition.new(:old_status_id => old_status_id, :new_status_id => new_status_id, :tracker_id => tracker.id, :role_id => role.id)
71 71 records << w
72 72 end
73 73 w.author = true if rule == "author"
74 74 w.assignee = true if rule == "assignee"
75 75 w.save if w.changed?
76 76 elsif w
77 77 if rule == 'always'
78 78 w.destroy
79 79 elsif rule == 'author'
80 80 if w.assignee
81 81 w.author = false
82 82 w.save if w.changed?
83 83 else
84 84 w.destroy
85 85 end
86 86 elsif rule == 'assignee'
87 87 if w.author
88 88 w.assignee = false
89 89 w.save if w.changed?
90 90 else
91 91 w.destroy
92 92 end
93 93 end
94 94 end
95 95 end
96 96 end
97 97 end
98 98 end
99 99 end
100 100 end
101 101 end
102 102 end
@@ -1,75 +1,75
1 1 <%= render :partial => 'action_menu' %>
2 2
3 3 <%= title l(:label_workflow) %>
4 4
5 5 <div class="tabs">
6 6 <ul>
7 7 <li><%= link_to l(:label_status_transitions), workflows_edit_path(:role_id => @roles, :tracker_id => @trackers), :class => 'selected' %></li>
8 8 <li><%= link_to l(:label_fields_permissions), workflows_permissions_path(:role_id => @roles, :tracker_id => @trackers) %></li>
9 9 </ul>
10 10 </div>
11 11
12 12 <p><%=l(:text_workflow_edit)%>:</p>
13 13
14 14 <%= form_tag({}, :method => 'get') do %>
15 15 <p>
16 16 <label><%=l(:label_role)%>:
17 <%= options_for_workflow_select 'role_id[]', Role.sorted, @roles, :id => 'role_id', :class => 'expandable' %>
17 <%= options_for_workflow_select 'role_id[]', Role.sorted.select(&:consider_workflow?), @roles, :id => 'role_id', :class => 'expandable' %>
18 18 </label>
19 19 <a href="#" data-expands="#role_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
20 20
21 21 <label><%=l(:label_tracker)%>:
22 22 <%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %>
23 23 </label>
24 24 <a href="#" data-expands="#tracker_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
25 25
26 26 <%= submit_tag l(:button_edit), :name => nil %>
27 27
28 28 <%= hidden_field_tag 'used_statuses_only', '0', :id => nil %>
29 29 <label><%= check_box_tag 'used_statuses_only', '1', @used_statuses_only %> <%= l(:label_display_used_statuses_only) %></label>
30 30
31 31 </p>
32 32 <% end %>
33 33
34 34 <% if @trackers && @roles && @statuses.any? %>
35 35 <%= form_tag({}, :id => 'workflow_form' ) do %>
36 36 <%= @trackers.map {|tracker| hidden_field_tag 'tracker_id[]', tracker.id, :id => nil}.join.html_safe %>
37 37 <%= @roles.map {|role| hidden_field_tag 'role_id[]', role.id, :id => nil}.join.html_safe %>
38 38 <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only], :id => nil %>
39 39 <div class="autoscroll">
40 40 <%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %>
41 41
42 42 <fieldset class="collapsible" style="padding: 0; margin-top: 0.5em;">
43 43 <legend onclick="toggleFieldset(this);"><%= l(:label_additional_workflow_transitions_for_author) %></legend>
44 44 <div id="author_workflows" style="margin: 0.5em 0 0.5em 0;">
45 45 <%= render :partial => 'form', :locals => {:name => 'author', :workflows => @workflows['author']} %>
46 46 </div>
47 47 </fieldset>
48 48 <%= javascript_tag "hideFieldset($('#author_workflows'))" unless @workflows['author'].present? %>
49 49
50 50 <fieldset class="collapsible" style="padding: 0;">
51 51 <legend onclick="toggleFieldset(this);"><%= l(:label_additional_workflow_transitions_for_assignee) %></legend>
52 52 <div id="assignee_workflows" style="margin: 0.5em 0 0.5em 0;">
53 53 <%= render :partial => 'form', :locals => {:name => 'assignee', :workflows => @workflows['assignee']} %>
54 54 </div>
55 55 </fieldset>
56 56 <%= javascript_tag "hideFieldset($('#assignee_workflows'))" unless @workflows['assignee'].present? %>
57 57 </div>
58 58 <%= submit_tag l(:button_save) %>
59 59 <% end %>
60 60 <% end %>
61 61
62 62 <%= javascript_tag do %>
63 63 $("a[data-expands]").click(function(e){
64 64 e.preventDefault();
65 65 var target = $($(this).attr("data-expands"));
66 66 if (target.attr("multiple")) {
67 67 target.attr("multiple", false);
68 68 target.find("option[value=all]").show();
69 69 } else {
70 70 target.attr("multiple", true);
71 71 target.find("option[value=all]").attr("selected", false).hide();
72 72 }
73 73 });
74 74
75 75 <% end %>
@@ -1,123 +1,123
1 1 <%= render :partial => 'action_menu' %>
2 2
3 3 <%= title l(:label_workflow) %>
4 4
5 5 <div class="tabs">
6 6 <ul>
7 7 <li><%= link_to l(:label_status_transitions), workflows_edit_path(:role_id => @roles, :tracker_id => @trackers) %></li>
8 8 <li><%= link_to l(:label_fields_permissions), workflows_permissions_path(:role_id => @roles, :tracker_id => @trackers), :class => 'selected' %></li>
9 9 </ul>
10 10 </div>
11 11
12 12 <p><%=l(:text_workflow_edit)%>:</p>
13 13
14 14 <%= form_tag({}, :method => 'get') do %>
15 15 <p>
16 16 <label><%=l(:label_role)%>:
17 <%= options_for_workflow_select 'role_id[]', Role.sorted, @roles, :id => 'role_id', :class => 'expandable' %>
17 <%= options_for_workflow_select 'role_id[]', Role.sorted.select(&:consider_workflow?), @roles, :id => 'role_id', :class => 'expandable' %>
18 18 </label>
19 19 <a href="#" data-expands="#role_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
20 20
21 21 <label><%=l(:label_tracker)%>:
22 22 <%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %>
23 23 </label>
24 24 <a href="#" data-expands="#tracker_id"><%= image_tag 'bullet_toggle_plus.png' %></a>
25 25
26 26 <%= submit_tag l(:button_edit), :name => nil %>
27 27
28 28 <%= hidden_field_tag 'used_statuses_only', '0', :id => nil %>
29 29 <label><%= check_box_tag 'used_statuses_only', '1', @used_statuses_only %> <%= l(:label_display_used_statuses_only) %></label>
30 30 </p>
31 31 <% end %>
32 32
33 33 <% if @trackers && @roles && @statuses.any? %>
34 34 <%= form_tag({}, :id => 'workflow_form' ) do %>
35 35 <%= @trackers.map {|tracker| hidden_field_tag 'tracker_id[]', tracker.id, :id => nil}.join.html_safe %>
36 36 <%= @roles.map {|role| hidden_field_tag 'role_id[]', role.id, :id => nil}.join.html_safe %>
37 37 <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only], :id => nil %>
38 38 <div class="autoscroll">
39 39 <table class="list workflows fields_permissions">
40 40 <thead>
41 41 <tr>
42 42 <th>
43 43 </th>
44 44 <th colspan="<%= @statuses.length %>"><%=l(:label_issue_status)%></th>
45 45 </tr>
46 46 <tr>
47 47 <td></td>
48 48 <% for status in @statuses %>
49 49 <td style="width:<%= 75 / @statuses.size %>%;">
50 50 <%=h status.name %>
51 51 </td>
52 52 <% end %>
53 53 </tr>
54 54 </thead>
55 55 <tbody>
56 56 <tr class="group open">
57 57 <td colspan="<%= @statuses.size + 1 %>">
58 58 <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
59 59 <%= l(:field_core_fields) %>
60 60 </td>
61 61 </tr>
62 62 <% @fields.each do |field, name| %>
63 63 <tr class="<%= cycle("odd", "even") %>">
64 64 <td class="name">
65 65 <%=h name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %>
66 66 </td>
67 67 <% for status in @statuses -%>
68 68 <td class="<%= @permissions[status.id][field] %>">
69 69 <%= field_permission_tag(@permissions, status, field, @roles) %>
70 70 <% unless status == @statuses.last %><a href="#" class="repeat-value">&#187;</a><% end %>
71 71 </td>
72 72 <% end -%>
73 73 </tr>
74 74 <% end %>
75 75 <% if @custom_fields.any? %>
76 76 <tr class="group open">
77 77 <td colspan="<%= @statuses.size + 1 %>">
78 78 <span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
79 79 <%= l(:label_custom_field_plural) %>
80 80 </td>
81 81 </tr>
82 82 <% @custom_fields.each do |field| %>
83 83 <tr class="<%= cycle("odd", "even") %>">
84 84 <td class="name">
85 85 <%=h field.name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %>
86 86 </td>
87 87 <% for status in @statuses -%>
88 88 <td class="<%= @permissions[status.id][field.id.to_s] %>">
89 89 <%= field_permission_tag(@permissions, status, field, @roles) %>
90 90 <% unless status == @statuses.last %><a href="#" class="repeat-value">&#187;</a><% end %>
91 91 </td>
92 92 <% end -%>
93 93 </tr>
94 94 <% end %>
95 95 <% end %>
96 96 </tbody>
97 97 </table>
98 98 </div>
99 99 <%= submit_tag l(:button_save) %>
100 100 <% end %>
101 101 <% end %>
102 102
103 103 <%= javascript_tag do %>
104 104 $("a.repeat-value").click(function(e){
105 105 e.preventDefault();
106 106 var td = $(this).closest('td');
107 107 var selected = td.find("select").find(":selected").val();
108 108 td.nextAll('td').find("select").val(selected);
109 109 });
110 110
111 111 $("a[data-expands]").click(function(e){
112 112 e.preventDefault();
113 113 var target = $($(this).attr("data-expands"));
114 114 if (target.attr("multiple")) {
115 115 target.attr("multiple", false);
116 116 target.find("option[value=all]").show();
117 117 } else {
118 118 target.attr("multiple", true);
119 119 target.find("option[value=all]").attr("selected", false).hide();
120 120 }
121 121 });
122 122
123 123 <% end %>
General Comments 0
You need to be logged in to leave comments. Login now