From 2c4647f8c6d23c999ae959a047104eef49d00993 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Tue, 9 Oct 2007 19:07:19 +0000 Subject: [PATCH] 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 --- app/controllers/projects_controller.rb | 30 +++++++++++++++++++++ app/models/issue.rb | 2 +- app/models/project.rb | 5 ++++ app/views/issues/_bulk_edit_form.rhtml | 30 +++++++++++++++++++++ app/views/issues/_list.rhtml | 9 +++++-- app/views/projects/list_issues.rhtml | 16 ++++++++--- lang/bg.yml | 4 +++ lang/cs.yml | 4 +++ lang/de.yml | 4 +++ lang/en.yml | 4 +++ lang/es.yml | 4 +++ lang/fr.yml | 4 +++ lang/it.yml | 4 +++ lang/ja.yml | 4 +++ lang/nl.yml | 4 +++ lang/pl.yml | 4 +++ lang/pt-br.yml | 4 +++ lang/pt.yml | 4 +++ lang/ro.yml | 4 +++ lang/sv.yml | 4 +++ lang/zh.yml | 4 +++ lib/redmine.rb | 3 ++- public/javascripts/application.js | 10 +++++++ test/functional/projects_controller_test.rb | 10 +++++++ 24 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 app/views/issues/_bulk_edit_form.rhtml diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index cf132551..13253939 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 52e21b41..fc7f6e1b 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 b17f7ba7..3e6593f5 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 00000000..fc0972fd --- /dev/null +++ b/app/views/issues/_bulk_edit_form.rhtml @@ -0,0 +1,30 @@ +
+
<%= l(:label_bulk_edit_selected_issues) %> + +

+ + + + +

+ +

+ + + +

+ +
+<%= text_area_tag 'notes', '', :cols => 80, :rows => 5 %> + +
+

<%= 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;' %>

+
diff --git a/app/views/issues/_list.rhtml b/app/views/issues/_list.rhtml index 567ccba0..b81559c2 100644 --- a/app/views/issues/_list.rhtml +++ b/app/views/issues/_list.rhtml @@ -1,6 +1,11 @@ +
- + <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %> <% query.columns.each do |column| %> <%= column_header(column) %> @@ -9,7 +14,7 @@ <% issues.each do |issue| %> - + <% 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 60e8f7be..9f2ff870 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 %>
<%= link_to_remote l(:button_apply), { :url => { :controller => 'projects', :action => 'list_issues', :id => @project, :set_filter => 1 }, @@ -22,6 +21,8 @@ <% end %>

+   + <% end %> <% else %>
<% if @query.editable_by?(User.current) %> @@ -31,6 +32,7 @@

<%= @query.name %>

+
<% set_html_title @query.name %> <% end %> <%= error_messages_for 'query' %> @@ -38,15 +40,14 @@ <% if @issues.empty? %>

<%= l(:label_no_data) %>

<% 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} %>
<%= 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' %>
-

<%= submit_tag(l(:button_move), :class => "button-small") if authorize_for('projects', 'move_issues') %> +

<%= pagination_links_full @issue_pages %> [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]

@@ -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 12a5378d..e987e1ad 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 078f824f..7b5c1a65 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 47078557..23dd6518 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 79e8f61d..af3faf49 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 2e8e6efb..40929e46 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 bb875288..a42ead91 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 4d114b15..207a5e64 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 433320aa..41c1ba5d 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 7e252d9d..716618d7 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 4db0d676..f883e2d2 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 59a5e1cc..91d487e5 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 b4e4a0cb..3c6cec37 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 25c48870..042b596f 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 e59e9233..02ecc6e1 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 b881ee4d..bbd08877 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 f5682a95..cb6af8ae 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 6a30e42e..bf86f398 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 b065d82b..e9ffa302 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
<%= 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) %> +
<%= check_box_tag "issue_ids[]", issue.id, false, :id => "issue_#{issue.id}" %><%= check_box_tag("issue_ids[]", issue.id, false, :id => "issue_#{issue.id}", :disabled => (!@project || @project != issue.project)) %> <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %>