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:
parent
bb4acc02d0
commit
0af6f34758
|
@ -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
|
||||||
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
|
||||||
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 @@
|
||||||
: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 @@
|
||||||
<%= 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>
|
||||||
|
|
|
@ -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,
|
<% 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
|
||||||
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
|
||||||
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é
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 291 B |
|
@ -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'
|
||||||
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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in New Issue