Project

General

Profile

Feature #34040 » feature-34040-v2.patch

Go MAEDA, 2022-08-02 11:45

View differences:

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}
(5-5/5)