diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2014-02-25 02:54:47 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2014-02-25 02:54:47 +0000 |
commit | b6c794d16b47bf353a1a2dfc00e9cbd078525ee8 (patch) | |
tree | 7dec97be4c38b4e3b6321f7c55b81697ca6f5ee2 /app | |
parent | c6f71f727bc73188ce7285c6e19bf50553246ec7 (diff) | |
download | redmine-b6c794d16b47bf353a1a2dfc00e9cbd078525ee8.tar.gz redmine-b6c794d16b47bf353a1a2dfc00e9cbd078525ee8.zip |
Bulk edit workflows for multiple trackers/roles (#16164).
git-svn-id: http://svn.redmine.org/redmine/trunk@12924 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/workflows_controller.rb | 103 | ||||
-rw-r--r-- | app/helpers/workflows_helper.rb | 58 | ||||
-rw-r--r-- | app/models/workflow_permission.rb | 43 | ||||
-rw-r--r-- | app/models/workflow_transition.rb | 65 | ||||
-rw-r--r-- | app/views/workflows/_form.html.erb | 5 | ||||
-rw-r--r-- | app/views/workflows/edit.html.erb | 33 | ||||
-rw-r--r-- | app/views/workflows/permissions.html.erb | 37 |
7 files changed, 264 insertions, 80 deletions
diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index 596462cbd..0a9600cba 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -18,40 +18,30 @@ class WorkflowsController < ApplicationController layout 'admin' - before_filter :require_admin, :find_roles, :find_trackers + before_filter :require_admin def index @workflow_counts = WorkflowTransition.count_by_tracker_and_role end def edit - @role = Role.find_by_id(params[:role_id]) if params[:role_id] - @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id] + find_trackers_roles_and_statuses_for_edit - if request.post? - WorkflowTransition.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) - (params[:issue_status] || []).each { |status_id, transitions| - transitions.each { |new_status_id, options| - author = options.is_a?(Array) && options.include?('author') && !options.include?('always') - assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always') - 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) - } - } - if @role.save - flash[:notice] = l(:notice_successful_update) - redirect_to workflows_edit_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) - return + if request.post? && @roles && @trackers && params[:transitions] + transitions = params[:transitions].deep_dup + transitions.each do |old_status_id, transitions_by_new_status| + transitions_by_new_status.each do |new_status_id, transition_by_rule| + transition_by_rule.reject! {|rule, transition| transition == 'no_change'} + end end + WorkflowTransition.replace_transitions(@trackers, @roles, transitions) + flash[:notice] = l(:notice_successful_update) + redirect_to_referer_or workflows_edit_path + return end - @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) - if @tracker && @used_statuses_only && @tracker.issue_statuses.any? - @statuses = @tracker.issue_statuses - end - @statuses ||= IssueStatus.sorted.all - - if @tracker && @role && @statuses.any? - workflows = WorkflowTransition.where(:role_id => @role.id, :tracker_id => @tracker.id).all + if @trackers && @roles && @statuses.any? + workflows = WorkflowTransition.where(:role_id => @roles.map(&:id), :tracker_id => @trackers.map(&:id)).all @workflows = {} @workflows['always'] = workflows.select {|w| !w.author && !w.assignee} @workflows['author'] = workflows.select {|w| w.author} @@ -60,36 +50,31 @@ class WorkflowsController < ApplicationController end def permissions - @role = Role.find_by_id(params[:role_id]) if params[:role_id] - @tracker = Tracker.find_by_id(params[:tracker_id]) if params[:tracker_id] + find_trackers_roles_and_statuses_for_edit - if request.post? && @role && @tracker - WorkflowPermission.replace_permissions(@tracker, @role, params[:permissions] || {}) + if request.post? && @roles && @trackers && params[:permissions] + permissions = params[:permissions].deep_dup + permissions.each { |field, rule_by_status_id| + rule_by_status_id.reject! {|status_id, rule| rule == 'no_change'} + } + WorkflowPermission.replace_permissions(@trackers, @roles, permissions) flash[:notice] = l(:notice_successful_update) - redirect_to workflows_permissions_path(:role_id => @role, :tracker_id => @tracker, :used_statuses_only => params[:used_statuses_only]) + redirect_to_referer_or workflows_permissions_path return end - @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) - if @tracker && @used_statuses_only && @tracker.issue_statuses.any? - @statuses = @tracker.issue_statuses - end - @statuses ||= IssueStatus.sorted.all - - if @role && @tracker - @fields = (Tracker::CORE_FIELDS_ALL - @tracker.disabled_core_fields).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]} - @custom_fields = @tracker.custom_fields - @permissions = WorkflowPermission. - where(:tracker_id => @tracker.id, :role_id => @role.id).inject({}) do |h, w| - h[w.old_status_id] ||= {} - h[w.old_status_id][w.field_name] = w.rule - h - end + if @roles && @trackers + @fields = (Tracker::CORE_FIELDS_ALL - @trackers.map(&:disabled_core_fields).reduce(:&)).map {|field| [field, l("field_"+field.sub(/_id$/, ''))]} + @custom_fields = @trackers.map(&:custom_fields).flatten.uniq.sort + @permissions = WorkflowPermission.rules_by_status_id(@trackers, @roles) @statuses.each {|status| @permissions[status.id] ||= {}} end end def copy + @roles = Role.sorted + @trackers = Tracker.sorted + if params[:source_tracker_id].blank? || params[:source_tracker_id] == 'any' @source_tracker = nil else @@ -119,11 +104,37 @@ class WorkflowsController < ApplicationController private + def find_trackers_roles_and_statuses_for_edit + find_roles + find_trackers + find_statuses + end + def find_roles - @roles = Role.sorted.all + ids = Array.wrap(params[:role_id]) + if ids == ['all'] + @roles = Role.sorted.all + elsif ids.present? + @roles = Role.where(:id => ids).all + end + @roles = nil if @roles.blank? end def find_trackers - @trackers = Tracker.sorted.all + ids = Array.wrap(params[:tracker_id]) + if ids == ['all'] + @trackers = Tracker.sorted.all + elsif ids.present? + @trackers = Tracker.where(:id => ids).all + end + @trackers = nil if @trackers.blank? + end + + def find_statuses + @used_statuses_only = (params[:used_statuses_only] == '0' ? false : true) + if @trackers && @used_statuses_only + @statuses = @trackers.map(&:issue_statuses).flatten.uniq.sort.presence + end + @statuses ||= IssueStatus.sorted.all end end diff --git a/app/helpers/workflows_helper.rb b/app/helpers/workflows_helper.rb index bfeacd821..185488e36 100644 --- a/app/helpers/workflows_helper.rb +++ b/app/helpers/workflows_helper.rb @@ -18,24 +18,74 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. module WorkflowsHelper + def options_for_workflow_select(name, objects, selected, options={}) + option_tags = ''.html_safe + multiple = false + if selected + if selected.size == objects.size + selected = 'all' + else + selected = selected.map(&:id) + if selected.size > 1 + multiple = true + end + end + else + selected = objects.first.try(:id) + end + all_tag_options = {:value => 'all', :selected => (selected == 'all')} + if multiple + all_tag_options.merge!(:style => "display:none;") + end + option_tags << content_tag('option', 'All', all_tag_options) + option_tags << options_from_collection_for_select(objects, "id", "name", selected) + select_tag name, option_tags, {:multiple => multiple}.merge(options) + end + def field_required?(field) field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field) end - def field_permission_tag(permissions, status, field, role) + def field_permission_tag(permissions, status, field, roles) name = field.is_a?(CustomField) ? field.id.to_s : field options = [["", ""], [l(:label_readonly), "readonly"]] options << [l(:label_required), "required"] unless field_required?(field) html_options = {} - selected = permissions[status.id][name] + + if perm = permissions[status.id][name] + if perm.uniq.size > 1 || perm.size < @roles.size * @trackers.size + options << [l(:label_no_change_option), "no_change"] + selected = 'no_change' + else + selected = perm.first + end + end + + hidden = field.is_a?(CustomField) && + !field.visible? && + !roles.detect {|role| role.custom_fields.to_a.include?(field)} - hidden = field.is_a?(CustomField) && !field.visible? && !role.custom_fields.to_a.include?(field) if hidden options[0][0] = l(:label_hidden) selected = '' html_options[:disabled] = true end - select_tag("permissions[#{name}][#{status.id}]", options_for_select(options, selected), html_options) + select_tag("permissions[#{status.id}][#{name}]", options_for_select(options, selected), html_options) + end + + def transition_tag(workflows, old_status, new_status, name) + w = workflows.select {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id}.size + + tag_name = "transitions[#{ old_status.id }][#{new_status.id}][#{name}]" + if w == 0 || w == @roles.size * @trackers.size + + hidden_field_tag(tag_name, "0") + + check_box_tag(tag_name, "1", w != 0, + :class => "old-status-#{old_status.id} new-status-#{new_status.id}") + else + select_tag tag_name, + options_for_select([["Oui", "1"], ["Non", "0"], [l(:label_no_change_option), "no_change"]], "no_change") + end end end diff --git a/app/models/workflow_permission.rb b/app/models/workflow_permission.rb index 0338a3ea1..bf4430ec5 100644 --- a/app/models/workflow_permission.rb +++ b/app/models/workflow_permission.rb @@ -19,20 +19,43 @@ class WorkflowPermission < WorkflowRule validates_inclusion_of :rule, :in => %w(readonly required) validate :validate_field_name - # Replaces the workflow permissions for the given tracker and role + # Returns the workflow permissions for the given trackers and roles + # grouped by status_id # # Example: - # WorkflowPermission.replace_permissions role, tracker, {'due_date' => {'1' => 'readonly', '2' => 'required'}} - def self.replace_permissions(tracker, role, permissions) - destroy_all(:tracker_id => tracker.id, :role_id => role.id) + # WorkflowPermission.rules_by_status_id trackers, roles + # # => {1 => {'start_date' => 'required', 'due_date' => 'readonly'}} + def self.rules_by_status_id(trackers, roles) + WorkflowPermission.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).inject({}) do |h, w| + h[w.old_status_id] ||= {} + h[w.old_status_id][w.field_name] ||= [] + h[w.old_status_id][w.field_name] << w.rule + h + end + end - permissions.each { |field, rule_by_status_id| - rule_by_status_id.each { |status_id, rule| - if rule.present? - WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule) - end + # Replaces the workflow permissions for the given trackers and roles + # + # Example: + # WorkflowPermission.replace_permissions trackers, roles, {'1' => {'start_date' => 'required', 'due_date' => 'readonly'}} + def self.replace_permissions(trackers, roles, permissions) + trackers = Array.wrap trackers + roles = Array.wrap roles + + transaction do + permissions.each { |status_id, rule_by_field| + rule_by_field.each { |field, rule| + destroy_all(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id), :old_status_id => status_id, :field_name => field) + if rule.present? + trackers.each do |tracker| + roles.each do |role| + WorkflowPermission.create(:role_id => role.id, :tracker_id => tracker.id, :old_status_id => status_id, :field_name => field, :rule => rule) + end + end + end + } } - } + end end protected diff --git a/app/models/workflow_transition.rb b/app/models/workflow_transition.rb index 35d9f26a4..fb0c31c80 100644 --- a/app/models/workflow_transition.rb +++ b/app/models/workflow_transition.rb @@ -36,4 +36,69 @@ class WorkflowTransition < WorkflowRule result end + + def self.replace_transitions(trackers, roles, transitions) + trackers = Array.wrap trackers + roles = Array.wrap roles + + transaction do + records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).all + + transitions.each do |old_status_id, transitions_by_new_status| + transitions_by_new_status.each do |new_status_id, transition_by_rule| + transition_by_rule.each do |rule, transition| + trackers.each do |tracker| + roles.each do |role| + w = records.select {|r| + r.old_status_id == old_status_id.to_i && + r.new_status_id == new_status_id.to_i && + r.tracker_id == tracker.id && + r.role_id == role.id && + !r.destroyed? + } + + if rule == 'always' + w = w.select {|r| !r.author && !r.assignee} + else + w = w.select {|r| r.author || r.assignee} + end + if w.size > 1 + w[1..-1].each(&:destroy) + end + w = w.first + + if transition == "1" || transition == true + unless w + w = WorkflowTransition.new(:old_status_id => old_status_id, :new_status_id => new_status_id, :tracker_id => tracker.id, :role_id => role.id) + records << w + end + w.author = true if rule == "author" + w.assignee = true if rule == "assignee" + w.save if w.changed? + elsif w + if rule == 'always' + w.destroy + elsif rule == 'author' + if w.assignee + w.author = false + w.save if w.changed? + else + w.destroy + end + elsif rule == 'assignee' + if w.author + w.assignee = false + w.save if w.changed? + else + w.destroy + end + end + end + end + end + end + end + end + end + end end diff --git a/app/views/workflows/_form.html.erb b/app/views/workflows/_form.html.erb index 19d94d60c..9c5ef62fb 100644 --- a/app/views/workflows/_form.html.erb +++ b/app/views/workflows/_form.html.erb @@ -1,4 +1,4 @@ -<table class="list transitions transitions-<%= name %>"> +<table class="list workflows transitions transitions-<%= name %>"> <thead> <tr> <th> @@ -31,8 +31,7 @@ <% for new_status in @statuses -%> <% checked = workflows.detect {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id} %> <td class="<%= checked ? 'enabled' : '' %>"> - <%= check_box_tag "issue_status[#{ old_status.id }][#{new_status.id}][]", name, checked, - :class => "old-status-#{old_status.id} new-status-#{new_status.id}" %> + <%= transition_tag workflows, old_status, new_status, name %> </td> <% end -%> </tr> diff --git a/app/views/workflows/edit.html.erb b/app/views/workflows/edit.html.erb index 8c02653c5..19b409357 100644 --- a/app/views/workflows/edit.html.erb +++ b/app/views/workflows/edit.html.erb @@ -4,8 +4,8 @@ <div class="tabs"> <ul> - <li><%= link_to l(:label_status_transitions), {:action => 'edit', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %></li> - <li><%= link_to l(:label_fields_permissions), {:action => 'permissions', :role_id => @role, :tracker_id => @tracker} %></li> + <li><%= link_to l(:label_status_transitions), workflows_edit_path(:role_id => @roles, :tracker_id => @trackers), :class => 'selected' %></li> + <li><%= link_to l(:label_fields_permissions), workflows_permissions_path(:role_id => @roles, :tracker_id => @trackers) %></li> </ul> </div> @@ -14,10 +14,14 @@ <%= form_tag({}, :method => 'get') do %> <p> <label><%=l(:label_role)%>: - <%= select_tag 'role_id', options_from_collection_for_select(@roles, "id", "name", @role && @role.id) %></label> + <%= options_for_workflow_select 'role_id[]', Role.sorted, @roles, :id => 'role_id', :class => 'expandable' %> + </label> + <a href="#" data-expands="#role_id"><%= image_tag 'bullet_toggle_plus.png' %></a> <label><%=l(:label_tracker)%>: - <%= select_tag 'tracker_id', options_from_collection_for_select(@trackers, "id", "name", @tracker && @tracker.id) %></label> + <%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %> + </label> + <a href="#" data-expands="#tracker_id"><%= image_tag 'bullet_toggle_plus.png' %></a> <%= submit_tag l(:button_edit), :name => nil %> @@ -27,10 +31,10 @@ </p> <% end %> -<% if @tracker && @role && @statuses.any? %> +<% if @trackers && @roles && @statuses.any? %> <%= form_tag({}, :id => 'workflow_form' ) do %> - <%= hidden_field_tag 'tracker_id', @tracker.id %> - <%= hidden_field_tag 'role_id', @role.id %> + <%= @trackers.map {|tracker| hidden_field_tag 'tracker_id[]', tracker.id}.join.html_safe %> + <%= @roles.map {|role| hidden_field_tag 'role_id[]', role.id}.join.html_safe %> <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %> <div class="autoscroll"> <%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %> @@ -54,3 +58,18 @@ <%= submit_tag l(:button_save) %> <% end %> <% end %> + +<%= javascript_tag do %> +$("a[data-expands]").click(function(e){ + e.preventDefault(); + var target = $($(this).attr("data-expands")); + if (target.attr("multiple")) { + target.attr("multiple", false); + target.find("option[value=all]").show(); + } else { + target.attr("multiple", true); + target.find("option[value=all]").attr("selected", false).hide(); + } +}); + +<% end %> diff --git a/app/views/workflows/permissions.html.erb b/app/views/workflows/permissions.html.erb index 11212e6b8..54b8304ce 100644 --- a/app/views/workflows/permissions.html.erb +++ b/app/views/workflows/permissions.html.erb @@ -4,8 +4,8 @@ <div class="tabs"> <ul> - <li><%= link_to l(:label_status_transitions), {:action => 'edit', :role_id => @role, :tracker_id => @tracker} %></li> - <li><%= link_to l(:label_fields_permissions), {:action => 'permissions', :role_id => @role, :tracker_id => @tracker}, :class => 'selected' %></li> + <li><%= link_to l(:label_status_transitions), workflows_edit_path(:role_id => @roles, :tracker_id => @trackers) %></li> + <li><%= link_to l(:label_fields_permissions), workflows_permissions_path(:role_id => @roles, :tracker_id => @trackers), :class => 'selected' %></li> </ul> </div> @@ -14,10 +14,14 @@ <%= form_tag({}, :method => 'get') do %> <p> <label><%=l(:label_role)%>: - <%= select_tag 'role_id', options_from_collection_for_select(@roles, "id", "name", @role && @role.id) %></label> + <%= options_for_workflow_select 'role_id[]', Role.sorted, @roles, :id => 'role_id', :class => 'expandable' %> + </label> + <a href="#" data-expands="#role_id"><%= image_tag 'bullet_toggle_plus.png' %></a> <label><%=l(:label_tracker)%>: - <%= select_tag 'tracker_id', options_from_collection_for_select(@trackers, "id", "name", @tracker && @tracker.id) %></label> + <%= options_for_workflow_select 'tracker_id[]', Tracker.sorted, @trackers, :id => 'tracker_id', :class => 'expandable' %> + </label> + <a href="#" data-expands="#tracker_id"><%= image_tag 'bullet_toggle_plus.png' %></a> <%= submit_tag l(:button_edit), :name => nil %> @@ -26,13 +30,13 @@ </p> <% end %> -<% if @tracker && @role && @statuses.any? %> +<% if @trackers && @roles && @statuses.any? %> <%= form_tag({}, :id => 'workflow_form' ) do %> - <%= hidden_field_tag 'tracker_id', @tracker.id %> - <%= hidden_field_tag 'role_id', @role.id %> + <%= @trackers.map {|tracker| hidden_field_tag 'tracker_id[]', tracker.id}.join.html_safe %> + <%= @roles.map {|role| hidden_field_tag 'role_id[]', role.id}.join.html_safe %> <%= hidden_field_tag 'used_statuses_only', params[:used_statuses_only] %> <div class="autoscroll"> - <table class="list fields_permissions"> + <table class="list workflows fields_permissions"> <thead> <tr> <th> @@ -62,7 +66,7 @@ </td> <% for status in @statuses -%> <td class="<%= @permissions[status.id][field] %>"> - <%= field_permission_tag(@permissions, status, field, @role) %> + <%= field_permission_tag(@permissions, status, field, @roles) %> <% unless status == @statuses.last %><a href="#" class="repeat-value">»</a><% end %> </td> <% end -%> @@ -82,7 +86,7 @@ </td> <% for status in @statuses -%> <td class="<%= @permissions[status.id][field.id.to_s] %>"> - <%= field_permission_tag(@permissions, status, field, @role) %> + <%= field_permission_tag(@permissions, status, field, @roles) %> <% unless status == @statuses.last %><a href="#" class="repeat-value">»</a><% end %> </td> <% end -%> @@ -103,4 +107,17 @@ $("a.repeat-value").click(function(e){ var selected = td.find("select").find(":selected").val(); td.nextAll('td').find("select").val(selected); }); + +$("a[data-expands]").click(function(e){ + e.preventDefault(); + var target = $($(this).attr("data-expands")); + if (target.attr("multiple")) { + target.attr("multiple", false); + target.find("option[value=all]").show(); + } else { + target.attr("multiple", true); + target.find("option[value=all]").attr("selected", false).hide(); + } +}); + <% end %> |