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 ...@@ -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
......
...@@ -192,43 +192,45 @@ class ProjectsController < ApplicationController ...@@ -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
......
...@@ -54,6 +54,13 @@ class Issue < ActiveRecord::Base ...@@ -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)
......
...@@ -31,6 +31,8 @@ ...@@ -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},
......
...@@ -3,6 +3,7 @@ ...@@ -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>
......
<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),
{ :url => { :controller => 'issues', :action => 'preview', :id => @issue }, { :url => { :controller => 'issues', :action => 'preview', :id => @issue },
......
...@@ -476,6 +476,7 @@ button_unarchive: Unarchive ...@@ -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
......
...@@ -476,6 +476,7 @@ button_unarchive: Désarchiver ...@@ -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é
......
...@@ -421,6 +421,7 @@ vertical-align: middle; ...@@ -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); }
......
...@@ -22,7 +22,7 @@ require 'projects_controller' ...@@ -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 ...@@ -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
...@@ -18,13 +18,23 @@ ...@@ -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')
......
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