summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2007-10-09 19:07:19 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2007-10-09 19:07:19 +0000
commit2c4647f8c6d23c999ae959a047104eef49d00993 (patch)
treefa1241406a68d951ded62d04269d85dfb6883a2c
parentdf631e8c06d755fd673e4bbf072a7e15766d1e0c (diff)
downloadredmine-2c4647f8c6d23c999ae959a047104eef49d00993.tar.gz
redmine-2c4647f8c6d23c999ae959a047104eef49d00993.zip
Added 'Bulk edit' functionality.
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-8f06a7374b81
-rw-r--r--app/controllers/projects_controller.rb30
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/project.rb5
-rw-r--r--app/views/issues/_bulk_edit_form.rhtml30
-rw-r--r--app/views/issues/_list.rhtml9
-rw-r--r--app/views/projects/list_issues.rhtml16
-rw-r--r--lang/bg.yml4
-rw-r--r--lang/cs.yml4
-rw-r--r--lang/de.yml4
-rw-r--r--lang/en.yml4
-rw-r--r--lang/es.yml4
-rw-r--r--lang/fr.yml4
-rw-r--r--lang/it.yml4
-rw-r--r--lang/ja.yml4
-rw-r--r--lang/nl.yml4
-rw-r--r--lang/pl.yml4
-rw-r--r--lang/pt-br.yml4
-rw-r--r--lang/pt.yml4
-rw-r--r--lang/ro.yml4
-rw-r--r--lang/sv.yml4
-rw-r--r--lang/zh.yml4
-rw-r--r--lib/redmine.rb3
-rw-r--r--public/javascripts/application.js10
-rw-r--r--test/functional/projects_controller_test.rb10
24 files changed, 167 insertions, 8 deletions
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index cf1325518..13253939a 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -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]
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 52e21b416..fc7f6e1b8 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -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
diff --git a/app/models/project.rb b/app/models/project.rb
index b17f7ba74..3e6593f58 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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)
diff --git a/app/views/issues/_bulk_edit_form.rhtml b/app/views/issues/_bulk_edit_form.rhtml
new file mode 100644
index 000000000..fc0972fd6
--- /dev/null
+++ b/app/views/issues/_bulk_edit_form.rhtml
@@ -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>
diff --git a/app/views/issues/_list.rhtml b/app/views/issues/_list.rhtml
index 567ccba09..b81559c29 100644
--- a/app/views/issues/_list.rhtml
+++ b/app/views/issues/_list.rhtml
@@ -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 %>
diff --git a/app/views/projects/list_issues.rhtml b/app/views/projects/list_issues.rhtml
index 60e8f7be2..9f2ff870d 100644
--- a/app/views/projects/list_issues.rhtml
+++ b/app/views/projects/list_issues.rhtml
@@ -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 />
+ &nbsp;
+ <% 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 %>
-&nbsp;
-<% 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 %>
diff --git a/lang/bg.yml b/lang/bg.yml
index 12a5378d6..e987e1adb 100644
--- a/lang/bg.yml
+++ b/lang/bg.yml
@@ -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."
diff --git a/lang/cs.yml b/lang/cs.yml
index 078f824f6..7b5c1a650 100644
--- a/lang/cs.yml
+++ b/lang/cs.yml
@@ -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."
diff --git a/lang/de.yml b/lang/de.yml
index 470785579..23dd65181 100644
--- a/lang/de.yml
+++ b/lang/de.yml
@@ -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."
diff --git a/lang/en.yml b/lang/en.yml
index 79e8f61d8..af3faf49b 100644
--- a/lang/en.yml
+++ b/lang/en.yml
@@ -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
diff --git a/lang/es.yml b/lang/es.yml
index 2e8e6efbc..40929e466 100644
--- a/lang/es.yml
+++ b/lang/es.yml
@@ -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."
diff --git a/lang/fr.yml b/lang/fr.yml
index bb875288e..a42ead913 100644
--- a/lang/fr.yml
+++ b/lang/fr.yml
@@ -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
diff --git a/lang/it.yml b/lang/it.yml
index 4d114b15e..207a5e649 100644
--- a/lang/it.yml
+++ b/lang/it.yml
@@ -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."
diff --git a/lang/ja.yml b/lang/ja.yml
index 433320aa1..41c1ba5d4 100644
--- a/lang/ja.yml
+++ b/lang/ja.yml
@@ -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."
diff --git a/lang/nl.yml b/lang/nl.yml
index 7e252d9d1..716618d79 100644
--- a/lang/nl.yml
+++ b/lang/nl.yml
@@ -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."
diff --git a/lang/pl.yml b/lang/pl.yml
index 4db0d6761..f883e2d2f 100644
--- a/lang/pl.yml
+++ b/lang/pl.yml
@@ -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."
diff --git a/lang/pt-br.yml b/lang/pt-br.yml
index 59a5e1ccb..91d487e55 100644
--- a/lang/pt-br.yml
+++ b/lang/pt-br.yml
@@ -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."
diff --git a/lang/pt.yml b/lang/pt.yml
index b4e4a0cbd..3c6cec375 100644
--- a/lang/pt.yml
+++ b/lang/pt.yml
@@ -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."
diff --git a/lang/ro.yml b/lang/ro.yml
index 25c488709..042b596fa 100644
--- a/lang/ro.yml
+++ b/lang/ro.yml
@@ -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."
diff --git a/lang/sv.yml b/lang/sv.yml
index e59e9233e..02ecc6e13 100644
--- a/lang/sv.yml
+++ b/lang/sv.yml
@@ -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."
diff --git a/lang/zh.yml b/lang/zh.yml
index b881ee4df..bbd088772 100644
--- a/lang/zh.yml
+++ b/lang/zh.yml
@@ -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."
diff --git a/lib/redmine.rb b/lib/redmine.rb
index f5682a959..cb6af8aed 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -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
diff --git a/public/javascripts/application.js b/public/javascripts/application.js
index 6a30e42e7..bf86f398a 100644
--- a/public/javascripts/application.js
+++ b/public/javascripts/application.js
@@ -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(){
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
index b065d82b7..e9ffa3023 100644
--- a/test/functional/projects_controller_test.rb
+++ b/test/functional/projects_controller_test.rb
@@ -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