Feature #34040 » feature-34040-v2.patch
app/helpers/queries_helper.rb | ||
---|---|---|
318 | 318 | |
319 | 319 |
def query_to_csv(items, query, options={}) |
320 | 320 |
columns = query.columns |
321 |
journal_notes_columns = query.journal_notes_columns |
|
321 | 322 | |
322 | 323 |
Redmine::Export::CSV.generate(:encoding => params[:encoding]) do |csv| |
323 | 324 |
# csv header fields |
324 | 325 |
csv << columns.map {|c| c.caption.to_s} |
325 | 326 |
# csv lines |
326 |
items.each do |item| |
|
327 |
csv << columns.map {|c| csv_content(c, item)} |
|
327 |
if journal_notes_columns.present? |
|
328 |
without_journal_notes_columns = query.without_journal_notes_columns |
|
329 | ||
330 |
items.each do |item| |
|
331 |
if item.journals_with_notes.blank? |
|
332 |
csv << without_journal_notes_columns.map {|c| csv_content(c, item)} |
|
333 |
next |
|
334 |
end |
|
335 | ||
336 |
item.journals_with_notes.each_with_index do |journal, index| |
|
337 |
if index == 0 |
|
338 |
csv << without_journal_notes_columns.map {|c| csv_content(c, item)} + journal_notes_columns.map {|c| csv_content(c, journal)} |
|
339 |
else |
|
340 |
csv << without_journal_notes_columns.map {|c| csv_content(c, item) if c.name == :id } + journal_notes_columns.map {|c| csv_content(c, journal)} |
|
341 |
end |
|
342 |
end |
|
343 |
end |
|
344 |
else |
|
345 |
items.each do |item| |
|
346 |
csv << columns.map {|c| csv_content(c, item)} |
|
347 |
end |
|
328 | 348 |
end |
329 | 349 |
end |
330 | 350 |
end |
... | ... | |
344 | 364 |
elsif api_request? || params[:set_filter] || !use_session || |
345 | 365 |
session[session_key].nil? || |
346 | 366 |
session[session_key][:project_id] != (@project ? @project.id : nil) |
367 |
# Replace journal_notes params |
|
368 |
if params[:journal_notes] && params[:format] == 'csv' |
|
369 |
params['c'] ||= [] |
|
370 |
params['c'] += IssueQuery.journal_column_names.map(&:to_s) |
|
371 |
end |
|
372 | ||
347 | 373 |
# Give it a name, required to be valid |
348 | 374 |
@query = klass.new(:name => "_", :project => @project) |
349 | 375 |
@query.build_from_params(params, options[:defaults]) |
app/models/issue.rb | ||
---|---|---|
263 | 263 |
@total_estimated_hours = nil |
264 | 264 |
@last_updated_by = nil |
265 | 265 |
@last_notes = nil |
266 |
@journals_with_notes = nil |
|
266 | 267 |
base_reload(*args) |
267 | 268 |
end |
268 | 269 | |
... | ... | |
1173 | 1174 |
def last_notes |
1174 | 1175 |
if @last_notes |
1175 | 1176 |
@last_notes |
1177 |
elsif @journals_with_notes && @journals_with_notes.first.notes |
|
1178 |
@journals_with_notes.first.notes |
|
1176 | 1179 |
else |
1177 | 1180 |
journals.visible.where.not(notes: '').reorder(:id => :desc).first.try(:notes) |
1178 | 1181 |
end |
1179 | 1182 |
end |
1180 | 1183 | |
1184 |
def journals_with_notes |
|
1185 |
if @journals_with_notes |
|
1186 |
@journals_with_notes |
|
1187 |
else |
|
1188 |
journals.where.not(notes: '').reorder(:id => :asc) |
|
1189 |
end |
|
1190 |
end |
|
1191 | ||
1181 | 1192 |
# Preloads relations for a collection of issues |
1182 | 1193 |
def self.load_relations(issues) |
1183 | 1194 |
if issues.any? |
... | ... | |
1268 | 1279 |
end |
1269 | 1280 | |
1270 | 1281 |
# Preloads visible last notes for a collection of issues |
1271 |
def self.load_visible_last_notes(issues, user=User.current) |
|
1282 |
def self.load_visible_last_notes(issues, user=User.current, has_notes=false)
|
|
1272 | 1283 |
if issues.any? |
1284 |
if has_notes |
|
1285 |
issues.each do |issue| |
|
1286 |
issue.instance_variable_set(:@last_notes, issue.journals_with_notes.last.notes) |
|
1287 |
end |
|
1288 |
return |
|
1289 |
end |
|
1290 | ||
1273 | 1291 |
issue_ids = issues.map(&:id) |
1274 | 1292 |
journal_ids = Journal.joins(issue: :project). |
1275 | 1293 |
where(:journalized_type => 'Issue', :journalized_id => issue_ids). |
... | ... | |
1287 | 1305 |
end |
1288 | 1306 |
end |
1289 | 1307 | |
1308 |
# Preloads visible journals_with_notes for a collection of issues |
|
1309 |
def self.load_visible_journals_with_notes(issues, user=User.current) |
|
1310 |
return unless issues.any? |
|
1311 | ||
1312 |
issue_ids = issues.map(&:id) |
|
1313 |
journals = Journal.joins(issue: :project). |
|
1314 |
where(:journalized_type => 'Issue', :journalized_id => issue_ids). |
|
1315 |
where(Journal.visible_notes_condition(user, :skip_pre_condition => true)). |
|
1316 |
where.not(notes: ''). |
|
1317 |
reorder(id: :asc) |
|
1318 | ||
1319 |
issues.each do |issue| |
|
1320 |
issue.instance_variable_set(:@journals_with_notes, journals.where(journalized_id: issue.id)) |
|
1321 |
end |
|
1322 |
end |
|
1323 | ||
1290 | 1324 |
# Finds an issue relation given its id. |
1291 | 1325 |
def find_relation(relation_id) |
1292 | 1326 |
IssueRelation.where("issue_to_id = ? OR issue_from_id = ?", id, id).find(relation_id) |
app/models/issue_query.rb | ||
---|---|---|
70 | 70 |
QueryColumn.new(:relations, :caption => :label_related_issues), |
71 | 71 |
QueryColumn.new(:attachments, :caption => :label_attachment_plural), |
72 | 72 |
QueryColumn.new(:description, :inline => false), |
73 |
QueryColumn.new(:last_notes, :caption => :label_last_notes, :inline => false) |
|
73 |
QueryColumn.new(:last_notes, :caption => :label_last_notes, :inline => false), |
|
74 |
QueryJournalsColumn.new(:id, :caption => :label_notes_id), |
|
75 |
QueryJournalsColumn.new(:notes, :caption => :field_notes), |
|
76 |
QueryJournalsColumn.new(:user, :caption => :label_notes_author), |
|
77 |
QueryJournalsColumn.new(:created_on, :caption => :label_notes_created_on), |
|
78 |
QueryJournalsColumn.new(:private_notes, :caption => :field_private_notes) |
|
74 | 79 |
] |
75 | 80 | |
76 | 81 |
has_many :projects, foreign_key: 'default_issue_query_id', dependent: :nullify, inverse_of: 'default_issue_query' |
... | ... | |
332 | 337 |
@available_columns |
333 | 338 |
end |
334 | 339 | |
340 |
def self.journal_column_names |
|
341 |
available_columns.select{|c| c.is_a?(QueryJournalsColumn)}.map(&:name) |
|
342 |
end |
|
343 | ||
335 | 344 |
def default_columns_names |
336 | 345 |
@default_columns_names ||= begin |
337 | 346 |
default_columns = Setting.issue_list_default_columns.map(&:to_sym) |
... | ... | |
414 | 423 |
if has_column?(:relations) |
415 | 424 |
Issue.load_visible_relations(issues) |
416 | 425 |
end |
426 |
if IssueQuery.journal_column_names.any?{|name| has_column?(name)} |
|
427 |
Issue.load_visible_journals_with_notes(issues) |
|
428 |
end |
|
417 | 429 |
if has_column?(:last_notes) |
418 |
Issue.load_visible_last_notes(issues) |
|
430 |
Issue.load_visible_last_notes(issues, User.current, IssueQuery.journal_column_names.any?{|name| has_column?(name)})
|
|
419 | 431 |
end |
420 | 432 |
issues |
421 | 433 |
rescue ::ActiveRecord::StatementInvalid => e |
app/models/query.rb | ||
---|---|---|
62 | 62 |
@inline |
63 | 63 |
end |
64 | 64 | |
65 |
def journal_notes? |
|
66 |
false |
|
67 |
end |
|
68 | ||
65 | 69 |
def frozen? |
66 | 70 |
@frozen |
67 | 71 |
end |
... | ... | |
201 | 205 |
end |
202 | 206 |
end |
203 | 207 | |
208 |
class QueryJournalsColumn < QueryColumn |
|
209 |
def initialize(name, options={}) |
|
210 |
@attribute = name |
|
211 |
@inline = false |
|
212 |
name_with_assoc = "journals.#{name}".to_sym |
|
213 |
super(name_with_assoc, options) |
|
214 |
end |
|
215 | ||
216 |
def value_object(object) |
|
217 |
return nil unless object.is_a?(Journal) |
|
218 | ||
219 |
object.send @attribute |
|
220 |
end |
|
221 | ||
222 |
def journal_notes? |
|
223 |
true |
|
224 |
end |
|
225 |
end |
|
226 | ||
204 | 227 |
class QueryFilter |
205 | 228 |
include Redmine::I18n |
206 | 229 | |
... | ... | |
800 | 823 |
columns.reject(&:inline?) |
801 | 824 |
end |
802 | 825 | |
826 |
def journal_notes_columns |
|
827 |
columns.select(&:journal_notes?) |
|
828 |
end |
|
829 | ||
830 |
def without_journal_notes_columns |
|
831 |
columns.reject(&:journal_notes?) |
|
832 |
end |
|
833 | ||
803 | 834 |
def available_inline_columns |
804 | 835 |
available_columns.select(&:inline?) |
805 | 836 |
end |
806 | 837 | |
807 | 838 |
def available_block_columns |
808 |
available_columns.reject(&:inline?) |
|
839 |
available_columns.reject(&:inline?).reject(&:journal_notes?)
|
|
809 | 840 |
end |
810 | 841 | |
811 | 842 |
def available_totalable_columns |
812 | 843 |
available_columns.select(&:totalable) |
813 | 844 |
end |
814 | 845 | |
846 |
def available_journal_notes_columns |
|
847 |
available_columns.select(&:journal_notes?) |
|
848 |
end |
|
849 | ||
815 | 850 |
def default_columns_names |
816 | 851 |
[] |
817 | 852 |
end |
app/views/issues/index.html.erb | ||
---|---|---|
47 | 47 |
<label><%= radio_button_tag 'c[]', '', true %> <%= l(:description_selected_columns) %></label><br /> |
48 | 48 |
<label><%= radio_button_tag 'c[]', 'all_inline' %> <%= l(:description_all_columns) %></label> |
49 | 49 |
</p> |
50 |
<% if @query.available_block_columns.any? %> |
|
51 |
<fieldset id="csv-export-block-columns">
|
|
50 |
<% if @query.available_block_columns.any? || @query.available_journal_notes_columns.any? %>
|
|
51 |
<fieldset id="csv-export-other-columns">
|
|
52 | 52 |
<legend> |
53 |
<%= toggle_checkboxes_link('#csv-export-block-columns input[type=checkbox]') %>
|
|
53 |
<%= toggle_checkboxes_link('#csv-export-other-columns input[type=checkbox]') %>
|
|
54 | 54 |
</legend> |
55 |
<% @query.available_block_columns.each do |column| %> |
|
56 |
<label><%= check_box_tag 'c[]', column.name, @query.has_column?(column), :id => nil %> <%= column.caption %></label> |
|
55 |
<% if @query.available_block_columns.any? %> |
|
56 |
<% @query.available_block_columns.each do |column| %> |
|
57 |
<label><%= check_box_tag 'c[]', column.name, @query.has_column?(column), :id => nil %> <%= column.caption %></label> |
|
58 |
<% end %> |
|
59 |
<% end %> |
|
60 |
<% if @query.available_journal_notes_columns.any? %> |
|
61 |
<label><%= check_box_tag 'journal_notes', 1, params[:journal_notes], :id => nil %> <%= l(:label_all_notes) %></label> |
|
57 | 62 |
<% end %> |
58 | 63 |
</fieldset> |
59 | 64 |
<% end %> |
test/functional/issues_controller_test.rb | ||
---|---|---|
960 | 960 |
assert lines.detect {|line| line.include?('"MySQL, Oracle"')} |
961 | 961 |
end |
962 | 962 | |
963 |
def test_index_csv_with_journal_notes_params |
|
964 |
get( |
|
965 |
:index, |
|
966 |
:params => { |
|
967 |
:format => 'csv', |
|
968 |
:c => ['subject'], |
|
969 |
:project => 'ecookbook', |
|
970 |
:journal_notes => 1 |
|
971 |
} |
|
972 |
) |
|
973 |
assert_response :success |
|
974 |
lines = @response.body.chomp.split("\n") |
|
975 |
assert_equal 7, lines.first.split(',').count |
|
976 | ||
977 |
journal1 = Journal.find(1) |
|
978 |
assert_equal "1,Cannot print recipes,#{journal1.id},#{journal1.notes},#{journal1.user},#{format_time(journal1.created_on)},No", lines[-2] |
|
979 |
journal2 = Journal.find(2) |
|
980 |
assert_equal "1,,#{journal2.id},\"#{journal2.notes}\",#{journal2.user},#{format_time(journal2.created_on)},No", lines[-1] |
|
981 |
end |
|
982 | ||
963 | 983 |
def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator |
964 | 984 |
field = |
965 | 985 |
IssueCustomField. |
test/unit/query_test.rb | ||
---|---|---|
1755 | 1755 |
assert_not_nil issues.first.instance_variable_get(:@last_notes) |
1756 | 1756 |
end |
1757 | 1757 | |
1758 |
def test_query_should_preload_journals_with_notes |
|
1759 |
q = IssueQuery.new(:name => '_', :column_names => [:subject, :'journals.notes']) |
|
1760 |
assert q.has_column?(:'journals.notes') |
|
1761 |
issues = q.issues |
|
1762 |
assert_not_nil issues.first.instance_variable_get(:@journals_with_notes) |
|
1763 |
end |
|
1764 | ||
1758 | 1765 |
def test_groupable_columns_should_include_custom_fields |
1759 | 1766 |
q = IssueQuery.new |
1760 | 1767 |
column = q.groupable_columns.detect {|c| c.name == :cf_1} |
- « Previous
- 1
- …
- 3
- 4
- 5
- Next »