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)
@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

View File

@ -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

View File

@ -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)

View File

@ -31,6 +31,8 @@
:selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li>
</ul>
</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 },
:class => 'icon-move', :disabled => !@can[:move] %>
<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_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' %>
</div>

View File

@ -1,10 +1,10 @@
<h2><%=l(:label_issue_new)%>: <%= @tracker.name %></h2>
<h2><%=l(:label_issue_new)%>: <%= @issue.tracker %></h2>
<% 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 },

View File

@ -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

View File

@ -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é

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-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); }

View File

@ -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

View File

@ -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')