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
This commit is contained in:
Jean-Philippe Lang 2007-10-28 14:31:59 +00:00
parent bb4acc02d0
commit 0af6f34758
12 changed files with 68 additions and 23 deletions

View File

@ -174,6 +174,7 @@ class IssuesController < ApplicationController
@assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to) @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
@can = {:edit => User.current.allowed_to?(:edit_issues, @project), @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
:change_status => User.current.allowed_to?(:change_issue_status, @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), :move => User.current.allowed_to?(:move_issues, @project),
:delete => User.current.allowed_to?(:delete_issues, @project)} :delete => User.current.allowed_to?(:delete_issues, @project)}
render :layout => false render :layout => false

View File

@ -192,43 +192,45 @@ class ProjectsController < ApplicationController
end end
# Add a new issue to @project # 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 def add_issue
@tracker = Tracker.find(params[:tracker_id]) @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
@priorities = Enumeration::get_values('IPRI') @issue.project = @project
@issue.author = User.current
@issue.tracker ||= Tracker.find(params[:tracker_id])
default_status = IssueStatus.default default_status = IssueStatus.default
unless default_status 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 render :nothing => true, :layout => true
return return
end end
@issue = Issue.new(:project => @project, :tracker => @tracker)
@issue.status = default_status @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 @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? if request.get?
@issue.start_date = Date.today @issue.start_date ||= Date.today
@custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } @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 else
@issue.attributes = params[:issue]
requested_status = IssueStatus.find_by_id(params[:issue][:status_id]) 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.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
@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.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]) }
@issue.custom_values = @custom_values @issue.custom_values = @custom_values
if @issue.save 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) flash[:notice] = l(:notice_successful_create)
Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
redirect_to :action => 'list_issues', :id => @project redirect_to :action => 'list_issues', :id => @project
return
end end
end end
@priorities = Enumeration::get_values('IPRI')
end end
# Show filtered/sorted issues list of @project # Show filtered/sorted issues list of @project

View File

@ -54,6 +54,13 @@ class Issue < ActiveRecord::Base
end end
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) def priority_id=(pid)
self.priority = nil self.priority = nil
write_attribute(:priority_id, pid) write_attribute(:priority_id, pid)

View File

@ -31,6 +31,8 @@
:selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li> :selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li>
</ul> </ul>
</li> </li>
<li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue},
:class => 'icon-copy', :disabled => !@can[:add] %></li>
<li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, <li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
:class => 'icon-move', :disabled => !@can[:move] %> :class => 'icon-move', :disabled => !@can[:move] %>
<li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},

View File

@ -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_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' %> <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
<%= watcher_tag(@issue, User.current) %> <%= 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_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' %> <%= 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' %>
</div> </div>

View File

@ -1,9 +1,9 @@
<h2><%=l(:label_issue_new)%>: <%= @tracker.name %></h2> <h2><%=l(:label_issue_new)%>: <%= @issue.tracker %></h2>
<% labelled_tabular_form_for :issue, @issue, <% labelled_tabular_form_for :issue, @issue,
:url => {:action => 'add_issue'}, :url => {:action => 'add_issue'},
:html => {:multipart => true, :id => 'issue-form'} do |f| %> :html => {:multipart => true, :id => 'issue-form'} do |f| %>
<%= hidden_field_tag 'tracker_id', @tracker.id %> <%= f.hidden_field :tracker_id %>
<%= render :partial => 'issues/form', :locals => {:f => f} %> <%= render :partial => 'issues/form', :locals => {:f => f} %>
<%= submit_tag l(:button_create) %> <%= submit_tag l(:button_create) %>
<%= link_to_remote l(:label_preview), <%= link_to_remote l(:label_preview),

View File

@ -476,6 +476,7 @@ button_unarchive: Unarchive
button_reset: Reset button_reset: Reset
button_rename: Rename button_rename: Rename
button_change_password: Change password button_change_password: Change password
button_copy: Copy
status_active: active status_active: active
status_registered: registered status_registered: registered

View File

@ -476,6 +476,7 @@ button_unarchive: Désarchiver
button_reset: Réinitialiser button_reset: Réinitialiser
button_rename: Renommer button_rename: Renommer
button_change_password: Changer de mot de passe button_change_password: Changer de mot de passe
button_copy: Copier
status_active: actif status_active: actif
status_registered: enregistré status_registered: enregistré

BIN
public/images/copy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

View File

@ -421,6 +421,7 @@ vertical-align: middle;
.icon-add { background-image: url(../images/add.png); } .icon-add { background-image: url(../images/add.png); }
.icon-edit { background-image: url(../images/edit.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-del { background-image: url(../images/delete.png); }
.icon-move { background-image: url(../images/move.png); } .icon-move { background-image: url(../images/move.png); }
.icon-save { background-image: url(../images/save.png); } .icon-save { background-image: url(../images/save.png); }

View File

@ -22,7 +22,7 @@ require 'projects_controller'
class ProjectsController; def rescue_action(e) raise e end; end class ProjectsController; def rescue_action(e) raise e end; end
class ProjectsControllerTest < Test::Unit::TestCase class ProjectsControllerTest < Test::Unit::TestCase
fixtures :projects, :users, :roles, :enabled_modules, :enumerations fixtures :projects, :users, :roles, :members, :issues, :enabled_modules, :enumerations
def setup def setup
@controller = ProjectsController.new @controller = ProjectsController.new
@ -143,4 +143,23 @@ class ProjectsControllerTest < Test::Unit::TestCase
assert_redirected_to 'admin/projects' assert_redirected_to 'admin/projects'
assert Project.find(1).active? assert Project.find(1).active?
end 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 end

View File

@ -18,13 +18,23 @@
require File.dirname(__FILE__) + '/../test_helper' require File.dirname(__FILE__) + '/../test_helper'
class IssueTest < Test::Unit::TestCase 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 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) 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 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
end 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 def test_close_duplicates
# Create 3 issues # 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') 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')