From 0af6f347580dd7c331f16aa50904d010fad19e23 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sun, 28 Oct 2007 14:31:59 +0000 Subject: [PATCH] Added the hability to copy an issue. It can be done from the 'issue/show' view or from the context menu on the issue list. The Copy functionality is of course only available if the user is allowed to create an issue. It copies the issue attributes and the custom fields values. git-svn-id: http://redmine.rubyforge.org/svn/trunk@873 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/issues_controller.rb | 1 + app/controllers/projects_controller.rb | 38 ++++++++++---------- app/models/issue.rb | 7 ++++ app/views/issues/context_menu.rhtml | 2 ++ app/views/issues/show.rhtml | 1 + app/views/projects/add_issue.rhtml | 6 ++-- lang/en.yml | 1 + lang/fr.yml | 1 + public/images/copy.png | Bin 0 -> 291 bytes public/stylesheets/application.css | 1 + test/functional/projects_controller_test.rb | 21 ++++++++++- test/unit/issue_test.rb | 12 ++++++- 12 files changed, 68 insertions(+), 23 deletions(-) create mode 100644 public/images/copy.png diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index d3949cbe..22859793 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -174,6 +174,7 @@ class IssuesController < ApplicationController @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to) @can = {:edit => User.current.allowed_to?(:edit_issues, @project), :change_status => User.current.allowed_to?(:change_issue_status, @project), + :add => User.current.allowed_to?(:add_issues, @project), :move => User.current.allowed_to?(:move_issues, @project), :delete => User.current.allowed_to?(:delete_issues, @project)} render :layout => false diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 377073fc..394e545d 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -192,43 +192,45 @@ class ProjectsController < ApplicationController end # Add a new issue to @project + # The new issue will be created from an existing one if copy_from parameter is given def add_issue - @tracker = Tracker.find(params[:tracker_id]) - @priorities = Enumeration::get_values('IPRI') + @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue]) + @issue.project = @project + @issue.author = User.current + @issue.tracker ||= Tracker.find(params[:tracker_id]) default_status = IssueStatus.default unless default_status - flash.now[:error] = 'No default issue status defined. Please check your configuration.' + flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' render :nothing => true, :layout => true return - end - @issue = Issue.new(:project => @project, :tracker => @tracker) + end @issue.status = default_status @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user + if request.get? - @issue.start_date = Date.today - @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } + @issue.start_date ||= Date.today + @custom_values = @issue.custom_values.empty? ? + @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } : + @issue.custom_values else - @issue.attributes = params[:issue] - requested_status = IssueStatus.find_by_id(params[:issue][:status_id]) + # Check that the user is allowed to apply the requested status @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status - - @issue.author_id = self.logged_in_user.id if self.logged_in_user - # Multiple file upload - @attachments = [] - params[:attachments].each { |a| - @attachments << Attachment.new(:container => @issue, :file => a, :author => logged_in_user) unless a.size == 0 - } if params[:attachments] and params[:attachments].is_a? Array - @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) } + @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) } @issue.custom_values = @custom_values if @issue.save - @attachments.each(&:save) + if params[:attachments] && params[:attachments].is_a?(Array) + # Save attachments + params[:attachments].each {|a| Attachment.create(:container => @issue, :file => a, :author => User.current) unless a.size == 0} + end flash[:notice] = l(:notice_successful_create) Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') redirect_to :action => 'list_issues', :id => @project + return end end + @priorities = Enumeration::get_values('IPRI') end # Show filtered/sorted issues list of @project diff --git a/app/models/issue.rb b/app/models/issue.rb index 972bf013..0d2d6fc0 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -54,6 +54,13 @@ class Issue < ActiveRecord::Base end end + def copy_from(arg) + issue = arg.is_a?(Issue) ? arg : Issue.find(arg) + self.attributes = issue.attributes.dup + self.custom_values = issue.custom_values.collect {|v| v.clone} + self + end + def priority_id=(pid) self.priority = nil write_attribute(:priority_id, pid) diff --git a/app/views/issues/context_menu.rhtml b/app/views/issues/context_menu.rhtml index caf6a76e..798fd42c 100644 --- a/app/views/issues/context_menu.rhtml +++ b/app/views/issues/context_menu.rhtml @@ -31,6 +31,8 @@ :selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %> +
  • <%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue}, + :class => 'icon-copy', :disabled => !@can[:add] %>
  • <%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon-move', :disabled => !@can[:move] %>
  • <%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml index ed615c69..dfeccc66 100644 --- a/app/views/issues/show.rhtml +++ b/app/views/issues/show.rhtml @@ -3,6 +3,7 @@ <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %> <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %> <%= watcher_tag(@issue, User.current) %> +<%= link_to_if_authorized l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %> <%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %> <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> diff --git a/app/views/projects/add_issue.rhtml b/app/views/projects/add_issue.rhtml index 8382d6c9..a6892290 100644 --- a/app/views/projects/add_issue.rhtml +++ b/app/views/projects/add_issue.rhtml @@ -1,10 +1,10 @@ -

    <%=l(:label_issue_new)%>: <%= @tracker.name %>

    +

    <%=l(:label_issue_new)%>: <%= @issue.tracker %>

    <% labelled_tabular_form_for :issue, @issue, :url => {:action => 'add_issue'}, :html => {:multipart => true, :id => 'issue-form'} do |f| %> - <%= hidden_field_tag 'tracker_id', @tracker.id %> - <%= render :partial => 'issues/form', :locals => {:f => f} %> + <%= f.hidden_field :tracker_id %> + <%= render :partial => 'issues/form', :locals => {:f => f} %> <%= submit_tag l(:button_create) %> <%= link_to_remote l(:label_preview), { :url => { :controller => 'issues', :action => 'preview', :id => @issue }, diff --git a/lang/en.yml b/lang/en.yml index ec0ee307..4a52d281 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -476,6 +476,7 @@ button_unarchive: Unarchive button_reset: Reset button_rename: Rename button_change_password: Change password +button_copy: Copy status_active: active status_registered: registered diff --git a/lang/fr.yml b/lang/fr.yml index 0aeed308..e5a2b843 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -476,6 +476,7 @@ button_unarchive: Désarchiver button_reset: Réinitialiser button_rename: Renommer button_change_password: Changer de mot de passe +button_copy: Copier status_active: actif status_registered: enregistré diff --git a/public/images/copy.png b/public/images/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..dccaa0614c0018226b20f390d96f269764f18aff GIT binary patch literal 291 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPHFD{+k|aV|Q z%+*scvQWrRE&sB(+6IOO2J@L-%mV6UFY)wsWxviP!z(3W!P%Y< z6yhxKh%9Dc;1&X5#!GkW{s0A8(j9#r85lP9bN@+X1@eUgd_r9RGcX)Gc5La=t^fc3 zfByXWy?giCCR@t_Wf@C?{DK)Ap4~_Ta(q2q978H@B_}j6x~VD%Fc{6~X>?#bV!+GG z%Vylu+ECP`Aeg#fj^GSIDJiZb3FXcmI~H^+T;bqqToX`MC}_xZlTAdBP4X;*(M(20 Z2I~aAy*qO@-31!V;OXk;vd$@?2>_lQRbv1E literal 0 HcmV?d00001 diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index d1041f16..97d4a291 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -421,6 +421,7 @@ vertical-align: middle; .icon-add { background-image: url(../images/add.png); } .icon-edit { background-image: url(../images/edit.png); } +.icon-copy { background-image: url(../images/copy.png); } .icon-del { background-image: url(../images/delete.png); } .icon-move { background-image: url(../images/move.png); } .icon-save { background-image: url(../images/save.png); } diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index b7d7962b..744cc49d 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -22,7 +22,7 @@ require 'projects_controller' class ProjectsController; def rescue_action(e) raise e end; end class ProjectsControllerTest < Test::Unit::TestCase - fixtures :projects, :users, :roles, :enabled_modules, :enumerations + fixtures :projects, :users, :roles, :members, :issues, :enabled_modules, :enumerations def setup @controller = ProjectsController.new @@ -143,4 +143,23 @@ class ProjectsControllerTest < Test::Unit::TestCase assert_redirected_to 'admin/projects' assert Project.find(1).active? end + + def test_add_issue + @request.session[:user_id] = 2 + get :add_issue, :id => 1, :tracker_id => 1 + assert_response :success + assert_template 'add_issue' + post :add_issue, :id => 1, :issue => {:tracker_id => 1, :subject => 'This is the test_add_issue issue', :description => 'This is the description', :priority_id => 5} + assert_redirected_to 'projects/list_issues' + assert Issue.find_by_subject('This is the test_add_issue issue') + end + + def test_copy_issue + @request.session[:user_id] = 2 + get :add_issue, :id => 1, :copy_from => 1 + assert_template 'add_issue' + assert_not_nil assigns(:issue) + orig = Issue.find(1) + assert_equal orig.subject, assigns(:issue).subject + end end diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index 254bffa2..5ddd4bde 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -18,13 +18,23 @@ require File.dirname(__FILE__) + '/../test_helper' class IssueTest < Test::Unit::TestCase - fixtures :projects, :users, :members, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues + fixtures :projects, :users, :members, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values def test_category_based_assignment issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1) assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to end + def test_copy + issue = Issue.new.copy_from(1) + assert issue.save + issue.reload + orig = Issue.find(1) + assert_equal orig.subject, issue.subject + assert_equal orig.tracker, issue.tracker + assert_equal orig.custom_values.first.value, issue.custom_values.first.value + end + def test_close_duplicates # Create 3 issues issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Duplicates test', :description => 'Duplicates test')