This can be done by clicking on the edit link (little pen icon) at the upper-left corner of the issue list. Most properties can be set (priority, assignee, category, fixed version, start and due dates, done ratio) and a note can be entered. Only issues of the current project can be selected for bulk edit (subproject issues can't). git-svn-id: http://redmine.rubyforge.org/svn/trunk@817 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/0.6.0
@@ -336,6 +336,36 @@ class ProjectsController < ApplicationController | |||
@options_for_rfpdf[:file_name] = "export.pdf" | |||
render :layout => false | |||
end | |||
# Bulk edit issues | |||
def bulk_edit_issues | |||
if request.post? | |||
priority = Enumeration.find_by_id(params[:priority_id]) | |||
assigned_to = User.find_by_id(params[:assigned_to_id]) | |||
issues = @project.issues.find_all_by_id(params[:issue_ids]) | |||
unsaved_issue_ids = [] | |||
issues.each do |issue| | |||
issue.init_journal(User.current, params[:notes]) | |||
issue.priority = priority if priority | |||
issue.assigned_to = assigned_to if assigned_to | |||
issue.start_date = params[:start_date] unless params[:start_date].blank? | |||
issue.due_date = params[:due_date] unless params[:due_date].blank? | |||
issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank? | |||
unsaved_issue_ids << issue.id unless issue.save | |||
end | |||
if unsaved_issue_ids.empty? | |||
flash[:notice] = l(:notice_successful_update) unless issues.empty? | |||
else | |||
flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #')) | |||
end | |||
redirect_to :action => 'list_issues', :id => @project | |||
return | |||
end | |||
render :update do |page| | |||
page.hide 'query_form' | |||
page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form' | |||
end | |||
end | |||
def move_issues | |||
@issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids] |
@@ -143,7 +143,7 @@ class Issue < ActiveRecord::Base | |||
# Users the issue can be assigned to | |||
def assignable_users | |||
project.members.select {|m| m.role.assignable?}.collect {|m| m.user} | |||
project.assignable_users | |||
end | |||
def spent_hours |
@@ -113,6 +113,11 @@ class Project < ActiveRecord::Base | |||
children.select {|child| child.active?} | |||
end | |||
# Users issues can be assigned to | |||
def assignable_users | |||
members.select {|m| m.role.assignable?}.collect {|m| m.user} | |||
end | |||
# Returns an array of all custom fields enabled for project issues | |||
# (explictly associated custom fields and custom fields enabled for all projects) | |||
def custom_fields_for_issues(tracker) |
@@ -0,0 +1,30 @@ | |||
<div id="bulk-edit-fields"> | |||
<fieldset class="box"><legend><%= l(:label_bulk_edit_selected_issues) %></legend> | |||
<p> | |||
<label><%= l(:field_priority) %>: | |||
<%= select_tag('priority_id', "<option>#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(Enumeration.get_values('IPRI'), :id, :name)) %></label> | |||
<label><%= l(:field_assigned_to) %>: | |||
<%= select_tag('assigned_to_id', "<option>#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@project.assignable_users, :id, :name)) %></label> | |||
<label><%= l(:field_category) %>: | |||
<%= select_tag('category_id', "<option>#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@project.issue_categories, :id, :name)) %></label> | |||
<label><%= l(:field_fixed_version) %>: | |||
<%= select_tag('fixed_version_id', "<option>#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@project.versions, :id, :name)) %></label> | |||
</p> | |||
<p> | |||
<label><%= l(:field_start_date) %>: | |||
<%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %></label> | |||
<label><%= l(:field_due_date) %>: | |||
<%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %></label> | |||
<label><%= l(:field_done_ratio) %>: | |||
<%= select_tag 'done_ratio', options_for_select([l(:label_no_change_option)] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></label> | |||
</p> | |||
<label for="notes"><%= l(:field_notes) %></label><br /> | |||
<%= text_area_tag 'notes', '', :cols => 80, :rows => 5 %> | |||
</fieldset> | |||
<p><%= submit_tag l(:button_apply) %> | |||
<%= link_to l(:button_cancel), {}, :onclick => 'Element.hide("bulk-edit-fields"); if ($("query_form")) {Element.show("query_form")}; return false;' %></p> | |||
</div> |
@@ -1,6 +1,11 @@ | |||
<div id="bulk-edit"></div> | |||
<table class="list"> | |||
<thead><tr> | |||
<th></th> | |||
<th><%= link_to_remote(image_tag('edit.png'), | |||
{:url => { :controller => 'projects', :action => 'bulk_edit_issues', :id => @project }, | |||
:method => :get}, | |||
{:title => l(:label_bulk_edit_selected_issues)}) if @project && User.current.allowed_to?(:edit_issues, @project) %> | |||
</th> | |||
<%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %> | |||
<% query.columns.each do |column| %> | |||
<%= column_header(column) %> | |||
@@ -9,7 +14,7 @@ | |||
<tbody> | |||
<% issues.each do |issue| %> | |||
<tr class="issue <%= cycle('odd', 'even') %>"> | |||
<td class="checkbox"><%= check_box_tag "issue_ids[]", issue.id, false, :id => "issue_#{issue.id}" %></td> | |||
<td class="checkbox"><%= check_box_tag("issue_ids[]", issue.id, false, :id => "issue_#{issue.id}", :disabled => (!@project || @project != issue.project)) %></td> | |||
<td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td> | |||
<% query.columns.each do |column| %> | |||
<%= content_tag 'td', column_content(column, issue), :class => column.name %> |
@@ -4,7 +4,6 @@ | |||
<% form_tag({ :controller => 'queries', :action => 'new', :project_id => @project }, :id => 'query_form') do %> | |||
<%= render :partial => 'queries/filters', :locals => {:query => @query} %> | |||
<% end %> | |||
<div class="contextual"> | |||
<%= link_to_remote l(:button_apply), | |||
{ :url => { :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 }, | |||
@@ -22,6 +21,8 @@ | |||
<% end %> | |||
</div> | |||
<br /> | |||
| |||
<% end %> | |||
<% else %> | |||
<div class="contextual"> | |||
<% if @query.editable_by?(User.current) %> | |||
@@ -31,6 +32,7 @@ | |||
</div> | |||
<h2><%= @query.name %></h2> | |||
<div id="query_form"></div> | |||
<% set_html_title @query.name %> | |||
<% end %> | |||
<%= error_messages_for 'query' %> | |||
@@ -38,15 +40,14 @@ | |||
<% if @issues.empty? %> | |||
<p class="nodata"><%= l(:label_no_data) %></p> | |||
<% else %> | |||
| |||
<% form_tag({:controller => 'projects', :action => 'move_issues', :id => @project}, :id => 'issues_form' ) do %> | |||
<% form_tag({:controller => 'projects', :action => 'bulk_edit_issues', :id => @project}, :id => 'issues_form', :onsubmit => "if (!checkBulkEdit(this)) {alert('#{l(:notice_no_issue_selected)}'); return false;}" ) do %> | |||
<%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %> | |||
<div class="contextual"> | |||
<%= l(:label_export_to) %> | |||
<%= link_to 'CSV', {:action => 'export_issues_csv', :id => @project}, :class => 'icon icon-csv' %>, | |||
<%= link_to 'PDF', {:action => 'export_issues_pdf', :id => @project}, :class => 'icon icon-pdf' %> | |||
</div> | |||
<p><%= submit_tag(l(:button_move), :class => "button-small") if authorize_for('projects', 'move_issues') %> | |||
<p> | |||
<%= pagination_links_full @issue_pages %> | |||
[ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ] | |||
</p> | |||
@@ -57,3 +58,10 @@ | |||
<% content_for :sidebar do %> | |||
<%= render :partial => 'issues/sidebar' %> | |||
<% end %> | |||
<% content_for :header_tags do %> | |||
<%= javascript_include_tag 'calendar/calendar' %> | |||
<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> | |||
<%= javascript_include_tag 'calendar/calendar-setup' %> | |||
<%= stylesheet_link_tag 'calendar' %> | |||
<% end %> |
@@ -516,3 +516,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -516,3 +516,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -516,3 +516,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -73,6 +73,8 @@ notice_not_authorized: You are not authorized to access this page. | |||
notice_email_sent: An email was sent to %s | |||
notice_email_error: An error occurred while sending mail (%s) | |||
notice_feeds_access_key_reseted: Your RSS access key was reseted. | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
mail_subject_lost_password: Your redMine password | |||
mail_subject_register: redMine account activation | |||
@@ -427,6 +429,8 @@ label_jump_to_a_project: Jump to a project... | |||
label_file_plural: Files | |||
label_changeset_plural: Changesets | |||
label_default_columns: Default columns | |||
label_no_change_option: (No change) | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
button_login: Login | |||
button_submit: Submit |
@@ -519,3 +519,7 @@ label_added_time_by: Added by %s %s ago | |||
field_estimated_hours: Estimated time | |||
label_changeset_plural: Changesets | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -73,6 +73,8 @@ notice_not_authorized: "Vous n'êtes pas autorisés à accéder à cette page." | |||
notice_email_sent: "Un email a été envoyé à %s" | |||
notice_email_error: "Erreur lors de l'envoi de l'email (%s)" | |||
notice_feeds_access_key_reseted: Votre clé d'accès aux flux RSS a été réinitialisée. | |||
notice_failed_to_save_issues: "%d demande(s) sur les %d sélectionnées n'ont pas pu être mise(s) à jour: %s." | |||
notice_no_issue_selected: "Aucune demande sélectionnée ! Cochez les demandes que vous voulez mettre à jour." | |||
mail_subject_lost_password: Votre mot de passe redMine | |||
mail_subject_register: Activation de votre compte redMine | |||
@@ -427,6 +429,8 @@ label_jump_to_a_project: Aller à un projet... | |||
label_file_plural: Fichiers | |||
label_changeset_plural: Révisions | |||
label_default_columns: Colonnes par défaut | |||
label_no_change_option: (Pas de changement) | |||
label_bulk_edit_selected_issues: Modifier les demandes sélectionnées | |||
button_login: Connexion | |||
button_submit: Soumettre |
@@ -516,3 +516,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -517,3 +517,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: 問題の一覧で表示する項目 | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -517,3 +517,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -516,3 +516,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -516,3 +516,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -516,3 +516,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -516,3 +516,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -517,3 +517,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -519,3 +519,7 @@ field_column_names: Columns | |||
label_default_columns: Default columns | |||
setting_issue_list_default_columns: Default columns displayed on the issue list | |||
setting_repositories_encodings: Repositories encodings | |||
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |||
label_bulk_edit_selected_issues: Bulk edit selected issues | |||
label_no_change_option: (No change) | |||
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
@@ -29,7 +29,8 @@ Redmine::AccessControl.map do |map| | |||
:queries => :index, | |||
:reports => :issue_report}, :public => true | |||
map.permission :add_issues, {:projects => :add_issue}, :require => :loggedin | |||
map.permission :edit_issues, {:issues => [:edit, :destroy_attachment]}, :require => :loggedin | |||
map.permission :edit_issues, {:projects => :bulk_edit_issues, | |||
:issues => [:edit, :destroy_attachment]}, :require => :loggedin | |||
map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}, :require => :loggedin | |||
map.permission :add_issue_notes, {:issues => :add_note}, :require => :loggedin | |||
map.permission :change_issue_status, {:issues => :change_status}, :require => :loggedin |
@@ -49,6 +49,16 @@ function promptToRemote(text, param, url) { | |||
} | |||
} | |||
/* checks that at least one checkbox is checked (used when submitting bulk edit form) */ | |||
function checkBulkEdit(form) { | |||
for (var i = 0; i < form.elements.length; i++) { | |||
if (form.elements[i].checked) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
/* shows and hides ajax indicator */ | |||
Ajax.Responders.register({ | |||
onCreate: function(){ |
@@ -83,6 +83,16 @@ class ProjectsControllerTest < Test::Unit::TestCase | |||
assert_response :success | |||
assert_not_nil assigns(:issues) | |||
end | |||
def test_bulk_edit_issues | |||
@request.session[:user_id] = 2 | |||
# update issues priority | |||
post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => "Bulk editing" | |||
assert_response 302 | |||
# check that the issues were updated | |||
assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id} | |||
assert_equal "Bulk editing", Issue.find(1).journals.last.notes | |||
end | |||
def test_list_news | |||
get :list_news, :id => 1 |