Defect #44299 » project_copy_skipped_issues.patch
| app/controllers/projects_controller.rb | ||
|---|---|---|
| 149 | 149 |
Mailer.with_deliveries(params[:notifications] == '1') do |
| 150 | 150 |
@project = Project.new |
| 151 | 151 |
@project.safe_attributes = params[:project] |
| 152 |
if @project.copy(@source_project, :only => params[:only]) |
|
| 152 |
copied = @project.copy(@source_project, :only => params[:only]) |
|
| 153 |
if @project.issues_not_copied.present? |
|
| 154 |
flash[:warning] = l(:warning_issues_not_copied, @project.issues_not_copied.size) |
|
| 155 |
end |
|
| 156 |
if copied |
|
| 153 | 157 |
flash[:notice] = l(:notice_successful_create) |
| 154 | 158 |
redirect_to settings_project_path(@project) |
| 155 | 159 |
elsif !@project.new_record? |
| 156 | 160 |
# Project was created |
| 157 | 161 |
# But some objects were not copied due to validation failures |
| 158 |
# (eg. issues from disabled trackers) |
|
| 159 | 162 |
# TODO: inform about that |
| 160 | 163 |
redirect_to settings_project_path(@project) |
| 161 | 164 |
end |
| app/models/project.rb | ||
|---|---|---|
| 917 | 917 |
p.nil? ? nil : p.identifier.to_s.succ |
| 918 | 918 |
end |
| 919 | 919 | |
| 920 |
# Returns the issues of the source project that could not be copied |
|
| 921 |
# by the last call to #copy |
|
| 922 |
attr_reader :issues_not_copied |
|
| 923 | ||
| 920 | 924 |
# Copies and saves the Project instance based on the +project+. |
| 921 | 925 |
# Duplicates the source project's: |
| 922 | 926 |
# * Wiki |
| ... | ... | |
| 934 | 938 |
# project.copy(1, :only => ['members', 'versions']) # => copies members and versions |
| 935 | 939 |
def copy(project, options={})
|
| 936 | 940 |
project = Project.find(project) unless project.is_a?(Project) |
| 941 |
@issues_not_copied = [] |
|
| 937 | 942 | |
| 938 | 943 |
to_be_copied = %w(members wiki versions issue_categories issues queries boards documents) |
| 939 | 944 |
to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil? |
| ... | ... | |
| 1199 | 1204 |
"#{new_issue.errors.full_messages}"
|
| 1200 | 1205 |
) |
| 1201 | 1206 |
end |
| 1207 |
@issues_not_copied << issue |
|
| 1208 |
# Remove the issue that could not be saved from the association target, |
|
| 1209 |
# otherwise it would make the project invalid and prevent any further save |
|
| 1210 |
self.issues.target.delete(new_issue) |
|
| 1202 | 1211 |
else |
| 1203 | 1212 |
issues_map[issue.id] = new_issue unless new_issue.new_record? |
| 1204 | 1213 |
end |
| ... | ... | |
| 1230 | 1239 |
end |
| 1231 | 1240 | |
| 1232 | 1241 |
issue.relations_to.each do |source_relation| |
| 1242 |
# Relations between two copied issues have already been copied above |
|
| 1243 |
next if issues_map[source_relation.issue_from_id] |
|
| 1244 | ||
| 1233 | 1245 |
new_issue_relation = IssueRelation.new |
| 1234 | 1246 |
new_issue_relation.attributes = |
| 1235 | 1247 |
source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
|
| config/locales/de.yml | ||
|---|---|---|
| 1159 | 1159 |
version_status_open: offen |
| 1160 | 1160 | |
| 1161 | 1161 |
warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden."
|
| 1162 |
warning_issues_not_copied: "%{count} Ticket(s) konnten nicht kopiert werden."
|
|
| 1162 | 1163 |
label_search_attachments_yes: Namen und Beschreibungen von Anhängen durchsuchen |
| 1163 | 1164 |
label_search_attachments_no: Keine Anhänge suchen |
| 1164 | 1165 |
label_search_attachments_only: Nur Anhänge suchen |
| config/locales/en.yml | ||
|---|---|---|
| 227 | 227 |
error_session_expired: "Your session has expired. Please login again." |
| 228 | 228 |
error_token_expired: "This password recovery link has expired, please try again." |
| 229 | 229 |
warning_attachments_not_saved: "%{count} file(s) could not be saved."
|
| 230 |
warning_issues_not_copied: "%{count} issue(s) could not be copied."
|
|
| 230 | 231 |
error_password_expired: "Your password has expired or the administrator requires you to change it." |
| 231 | 232 |
error_invalid_file_encoding: "The file is not a valid %{encoding} encoded file"
|
| 232 | 233 |
error_invalid_csv_file_or_settings: "The file is not a CSV file or does not match the settings below (%{value})"
|
| config/locales/fr.yml | ||
|---|---|---|
| 231 | 231 |
error_attachment_too_big: Ce fichier ne peut pas être attaché car il excède la taille maximale autorisée (%{max_size})
|
| 232 | 232 |
error_session_expired: "Votre session a expiré. Veuillez vous reconnecter." |
| 233 | 233 |
warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu être sauvegardés."
|
| 234 |
warning_issues_not_copied: "%{count} demande(s) n'ont pas pu être copiées."
|
|
| 234 | 235 |
error_password_expired: "Votre mot de passe a expiré ou nécessite d'être changé." |
| 235 | 236 |
error_invalid_file_encoding: "Le fichier n'est pas un fichier %{encoding} valide"
|
| 236 | 237 |
error_invalid_csv_file_or_settings: "Le fichier n'est pas un fichier CSV ou n'est pas conforme aux paramètres sélectionnés (%{value})"
|
| config/locales/ja.yml | ||
|---|---|---|
| 223 | 223 |
error_can_not_delete_tracker_html: 'このトラッカーは使用中です。削除できません。<p>以下のプロジェクトにこのトラッカーを使用しているチケットがあります:<br>%{projects}</p>'
|
| 224 | 224 | |
| 225 | 225 |
warning_attachments_not_saved: "%{count}個の添付ファイルが保存できませんでした。"
|
| 226 |
warning_issues_not_copied: "%{count}件のチケットをコピーできませんでした。"
|
|
| 226 | 227 | |
| 227 | 228 |
mail_subject_lost_password: "%{value} パスワード再設定"
|
| 228 | 229 |
mail_body_lost_password: 'パスワードを変更するには、以下のリンクをクリックしてください:' |
| test/functional/projects_controller_test.rb | ||
|---|---|---|
| 1496 | 1496 |
assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy' |
| 1497 | 1497 |
end |
| 1498 | 1498 | |
| 1499 |
def test_post_copy_should_warn_about_issues_that_could_not_be_copied |
|
| 1500 |
@request.session[:user_id] = 1 # admin |
|
| 1501 |
# Issues of the source project can not be copied because this required |
|
| 1502 |
# custom field is blank |
|
| 1503 |
IssueCustomField.generate!(:field_format => 'string', :is_required => true, |
|
| 1504 |
:is_for_all => true, :trackers => Tracker.all) |
|
| 1505 |
post( |
|
| 1506 |
:copy, |
|
| 1507 |
:params => {
|
|
| 1508 |
:id => 1, |
|
| 1509 |
:project => {
|
|
| 1510 |
:name => 'Copy', |
|
| 1511 |
:identifier => 'unique-copy', |
|
| 1512 |
:tracker_ids => ['1', '2', '3', ''], |
|
| 1513 |
:enabled_module_names => %w(issue_tracking) |
|
| 1514 |
}, |
|
| 1515 |
:only => %w(issues) |
|
| 1516 |
} |
|
| 1517 |
) |
|
| 1518 |
assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy' |
|
| 1519 |
project = Project.find('unique-copy')
|
|
| 1520 |
assert_equal 0, project.issues.count |
|
| 1521 |
assert_equal l(:warning_issues_not_copied, Project.find(1).issues.count), flash[:warning] |
|
| 1522 |
end |
|
| 1523 | ||
| 1499 | 1524 |
def test_post_copy_with_failure |
| 1500 | 1525 |
@request.session[:user_id] = 1 |
| 1501 | 1526 |
post( |
| test/unit/project_copy_test.rb | ||
|---|---|---|
| 66 | 66 |
assert_equal "Closed", copied_issue.status.name |
| 67 | 67 |
end |
| 68 | 68 | |
| 69 |
test "#copy should keep copying when an issue cannot be copied" do |
|
| 70 |
# This issue can not be copied because the required custom field below is blank |
|
| 71 |
Issue.generate!(:project => @source_project, :tracker_id => 2, |
|
| 72 |
:subject => 'No required value') |
|
| 73 |
IssueCustomField.generate!(:field_format => 'string', :is_required => true, |
|
| 74 |
:is_for_all => true, :trackers => [Tracker.find(2)]) |
|
| 75 | ||
| 76 |
assert @project.copy(@source_project) |
|
| 77 |
assert @project.valid? |
|
| 78 |
assert_nil @project.issues.find_by(:subject => 'No required value') |
|
| 79 |
# The other issues and objects are copied even though an issue was skipped |
|
| 80 |
assert @project.issues.any? |
|
| 81 |
assert @project.versions.any? |
|
| 82 |
assert @project.members.any? |
|
| 83 |
end |
|
| 84 | ||
| 85 |
test "#copy should report the issues that could not be copied" do |
|
| 86 |
skipped_issue = |
|
| 87 |
Issue.generate!(:project => @source_project, :tracker_id => 2, |
|
| 88 |
:subject => 'No required value') |
|
| 89 |
IssueCustomField.generate!(:field_format => 'string', :is_required => true, |
|
| 90 |
:is_for_all => true, :trackers => [Tracker.find(2)]) |
|
| 91 | ||
| 92 |
assert @project.copy(@source_project) |
|
| 93 |
assert_equal [skipped_issue], @project.issues_not_copied |
|
| 94 |
end |
|
| 95 | ||
| 96 |
test "#copy should not report any issue when they are all copied" do |
|
| 97 |
assert @project.copy(@source_project) |
|
| 98 |
assert_equal [], @project.issues_not_copied |
|
| 99 |
end |
|
| 100 | ||
| 69 | 101 |
test "#copy should copy issues custom values" do |
| 70 | 102 |
field = IssueCustomField.generate!(:is_for_all => true, :trackers => Tracker.all) |
| 71 | 103 |
issue = Issue.generate!(:project => @source_project, :subject => 'Custom field copy') |
| ... | ... | |
| 187 | 219 |
end |
| 188 | 220 |
end |
| 189 | 221 | |
| 222 |
test "#copy should copy a relation between two copied issues only once" do |
|
| 223 |
issue = Issue.generate!(:project => @source_project, :subject => 'Relation from') |
|
| 224 |
other_issue = Issue.generate!(:project => @source_project, :subject => 'Relation to') |
|
| 225 |
IssueRelation.create!(:issue_from => issue, :issue_to => other_issue, |
|
| 226 |
:relation_type => 'relates') |
|
| 227 | ||
| 228 |
assert @project.copy(@source_project) |
|
| 229 |
# A second copy of the relation would be rejected by the uniqueness |
|
| 230 |
# validation and would leave the copied issue invalid |
|
| 231 |
assert @project.issues.all?(&:valid?) |
|
| 232 |
assert_equal( |
|
| 233 |
1, |
|
| 234 |
IssueRelation.where(:issue_from_id => @project.issues, :issue_to_id => @project.issues).count |
|
| 235 |
) |
|
| 236 |
end |
|
| 237 | ||
| 190 | 238 |
test "#copy should copy issue attachments" do |
| 191 | 239 |
set_tmp_attachments_directory |
| 192 | 240 |
issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id) |