summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2007-10-28 14:31:59 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2007-10-28 14:31:59 +0000
commit0af6f347580dd7c331f16aa50904d010fad19e23 (patch)
tree62abc086a5c11385d04901dba5c0b050a2346034
parentbb4acc02d06d507424057ea41eebe54fdb224b85 (diff)
downloadredmine-0af6f347580dd7c331f16aa50904d010fad19e23.tar.gz
redmine-0af6f347580dd7c331f16aa50904d010fad19e23.zip
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
-rw-r--r--app/controllers/issues_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb38
-rw-r--r--app/models/issue.rb7
-rw-r--r--app/views/issues/context_menu.rhtml2
-rw-r--r--app/views/issues/show.rhtml1
-rw-r--r--app/views/projects/add_issue.rhtml6
-rw-r--r--lang/en.yml1
-rw-r--r--lang/fr.yml1
-rw-r--r--public/images/copy.pngbin0 -> 291 bytes
-rw-r--r--public/stylesheets/application.css1
-rw-r--r--test/functional/projects_controller_test.rb21
-rw-r--r--test/unit/issue_test.rb12
12 files changed, 68 insertions, 23 deletions
diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb
index d3949cbee..228597931 100644
--- a/app/controllers/issues_controller.rb
+++ b/app/controllers/issues_controller.rb
@@ -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
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 377073fc9..394e545d0 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -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
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 972bf0135..0d2d6fc0d 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -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)
diff --git a/app/views/issues/context_menu.rhtml b/app/views/issues/context_menu.rhtml
index caf6a76ea..798fd42c3 100644
--- a/app/views/issues/context_menu.rhtml
+++ b/app/views/issues/context_menu.rhtml
@@ -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},
diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml
index ed615c69f..dfeccc669 100644
--- a/app/views/issues/show.rhtml
+++ b/app/views/issues/show.rhtml
@@ -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>
diff --git a/app/views/projects/add_issue.rhtml b/app/views/projects/add_issue.rhtml
index 8382d6c9f..a68922906 100644
--- a/app/views/projects/add_issue.rhtml
+++ b/app/views/projects/add_issue.rhtml
@@ -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 },
diff --git a/lang/en.yml b/lang/en.yml
index ec0ee3077..4a52d281b 100644
--- a/lang/en.yml
+++ b/lang/en.yml
@@ -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
diff --git a/lang/fr.yml b/lang/fr.yml
index 0aeed308b..e5a2b843f 100644
--- a/lang/fr.yml
+++ b/lang/fr.yml
@@ -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é
diff --git a/public/images/copy.png b/public/images/copy.png
new file mode 100644
index 000000000..dccaa0614
--- /dev/null
+++ b/public/images/copy.png
Binary files differ
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index d1041f162..97d4a291d 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -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); }
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
index b7d7962b6..744cc49d6 100644
--- a/test/functional/projects_controller_test.rb
+++ b/test/functional/projects_controller_test.rb
@@ -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
diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb
index 254bffa20..5ddd4bde4 100644
--- a/test/unit/issue_test.rb
+++ b/test/unit/issue_test.rb
@@ -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')