mirror of
https://github.com/redmine/redmine.git
synced 2025-11-12 00:06:01 +01:00
Bulk addition of related issues (#33418).
Patch by Dmitry Makurin and Marius BALTEANU. git-svn-id: http://svn.redmine.org/redmine/trunk@20689 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
@@ -44,22 +44,29 @@ class IssueRelationsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
saved = false
|
||||||
|
params_relation = params[:relation]
|
||||||
|
unsaved_relations = []
|
||||||
|
|
||||||
|
relation_issues_to_id.each do |issue_to_id|
|
||||||
|
params_relation[:issue_to_id] = issue_to_id
|
||||||
|
|
||||||
@relation = IssueRelation.new
|
@relation = IssueRelation.new
|
||||||
@relation.issue_from = @issue
|
@relation.issue_from = @issue
|
||||||
@relation.safe_attributes = params[:relation]
|
@relation.safe_attributes = params_relation
|
||||||
@relation.init_journals(User.current)
|
@relation.init_journals(User.current)
|
||||||
|
|
||||||
begin
|
unless saved = @relation.save
|
||||||
saved = @relation.save
|
|
||||||
rescue ActiveRecord::RecordNotUnique
|
|
||||||
saved = false
|
saved = false
|
||||||
@relation.errors.add :base, :taken
|
unsaved_relations << @relation
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html {redirect_to issue_path(@issue)}
|
format.html {redirect_to issue_path(@issue)}
|
||||||
format.js do
|
format.js do
|
||||||
@relations = @issue.reload.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible?}
|
@relations = @issue.reload.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible?}
|
||||||
|
@unsaved_relations = unsaved_relations
|
||||||
end
|
end
|
||||||
format.api do
|
format.api do
|
||||||
if saved
|
if saved
|
||||||
@@ -98,4 +105,12 @@ class IssueRelationsController < ApplicationController
|
|||||||
rescue ActiveRecord::RecordNotFound
|
rescue ActiveRecord::RecordNotFound
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def relation_issues_to_id
|
||||||
|
params[:relation].require(:issue_to_id).split(',').reject(&:blank?)
|
||||||
|
rescue ActionController::ParameterMissing => e
|
||||||
|
# We return a empty array just to loop once and return a validation error
|
||||||
|
# ToDo: Find a better method to return an error if the param is missing.
|
||||||
|
['']
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,4 +22,23 @@ module IssueRelationsHelper
|
|||||||
values = IssueRelation::TYPES
|
values = IssueRelation::TYPES
|
||||||
values.keys.sort_by{|k| values[k][:order]}.collect{|k| [l(values[k][:name]), k]}
|
values.keys.sort_by{|k| values[k][:order]}.collect{|k| [l(values[k][:name]), k]}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def relation_error_messages(relations)
|
||||||
|
messages = {}
|
||||||
|
relations.each do |item|
|
||||||
|
item.errors.full_messages.each do |message|
|
||||||
|
messages[message] ||= []
|
||||||
|
messages[message] << item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
messages.map do |message, items|
|
||||||
|
ids = items.map(&:issue_to_id).compact
|
||||||
|
if ids.empty?
|
||||||
|
message
|
||||||
|
else
|
||||||
|
"#{message}: ##{ids.join(', ')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
<%= error_messages_for 'relation' %>
|
<% unsaved_relations_ids = '' %>
|
||||||
|
<% if @unsaved_relations && @unsaved_relations.any? %>
|
||||||
|
<% unsaved_relations_ids = @unsaved_relations.map(&:issue_to_id).compact.join(", ") %>
|
||||||
|
<div id="errorExplanation">
|
||||||
|
<ul>
|
||||||
|
<% relation_error_messages(@unsaved_relations).each do |message| %>
|
||||||
|
<li><%= message %></li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
<p><%= f.select :relation_type, collection_for_relation_type_select, {}, :onchange => "setPredecessorFieldsVisibility();" %>
|
<p><%= f.select :relation_type, collection_for_relation_type_select, {}, :onchange => "setPredecessorFieldsVisibility();" %>
|
||||||
<%= l(:label_issue) %> #<%= f.text_field :issue_to_id, :size => 10 %>
|
<%= l(:label_issue) %> #<%= f.text_field :issue_to_id, :value => unsaved_relations_ids, :size => 10 %>
|
||||||
<span id="predecessor_fields" style="display:none;">
|
<span id="predecessor_fields" style="display:none;">
|
||||||
<%= l(:field_delay) %>: <%= f.text_field :delay, :size => 3 %> <%= l(:label_day_plural) %>
|
<%= l(:field_delay) %>: <%= f.text_field :delay, :size => 3 %> <%= l(:label_day_plural) %>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -216,6 +216,53 @@ class IssueRelationsControllerTest < Redmine::ControllerTest
|
|||||||
assert_include 'Related issue cannot be blank', response.body
|
assert_include 'Related issue cannot be blank', response.body
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_bulk_create_with_multiple_issue_to_id_issues
|
||||||
|
assert_difference 'IssueRelation.count', +3 do
|
||||||
|
post :create, :params => {
|
||||||
|
:issue_id => 1,
|
||||||
|
:relation => {
|
||||||
|
# js autocomplete adds a comma at the end
|
||||||
|
# issue to id should accept both id and hash with id
|
||||||
|
:issue_to_id => '2,3,#7, ',
|
||||||
|
:relation_type => 'relates',
|
||||||
|
:delay => ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
:xhr => true
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
relations = IssueRelation.where(:issue_from_id => 1, :issue_to_id => [2, 3, 7])
|
||||||
|
assert_equal 3, relations.count
|
||||||
|
# all relations types should be 'relates'
|
||||||
|
relations.map {|r| assert_equal 'relates', r.relation_type}
|
||||||
|
|
||||||
|
# no error messages should be returned in the response
|
||||||
|
assert_not_include 'id=\"errorExplanation\"', response.body
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_bulk_create_should_show_errors
|
||||||
|
assert_difference 'IssueRelation.count', +3 do
|
||||||
|
post :create, :params => {
|
||||||
|
:issue_id => 1,
|
||||||
|
:relation => {
|
||||||
|
:issue_to_id => '1,2,3,4,5,7',
|
||||||
|
:relation_type => 'relates',
|
||||||
|
:delay => ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
:xhr => true
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
assert_equal 'text/javascript', response.media_type
|
||||||
|
# issue #1 is invalid
|
||||||
|
assert_include 'Related issue is invalid: #1', response.body
|
||||||
|
# issues #4 and #5 can't be related by default
|
||||||
|
assert_include 'Related issue cannot be blank', response.body
|
||||||
|
assert_include 'Related issue doesn't belong to the same project', response.body
|
||||||
|
end
|
||||||
|
|
||||||
def test_destroy
|
def test_destroy
|
||||||
assert_difference 'IssueRelation.count', -1 do
|
assert_difference 'IssueRelation.count', -1 do
|
||||||
delete(:destroy, :params => {:id => '2'})
|
delete(:destroy, :params => {:id => '2'})
|
||||||
|
|||||||
Reference in New Issue
Block a user