Commit 0af6f347 authored by Jean-Philippe Lang's avatar Jean-Philippe Lang

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
parent bb4acc02
......@@ -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
......
......@@ -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
......
......@@ -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)
......
......@@ -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},
......
......@@ -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>
......
<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 },
......
......@@ -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
......
......@@ -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é
......
......@@ -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); }
......
......@@ -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
......@@ -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')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment