Project

General

Profile

Feature #2848 » 2848-with-test-v2.patch

Yuichi HARADA, 2022-01-20 02:23

View differences:

lib/redmine/helpers/time_report.rb
47 47
          time_columns = %w(tyear tmonth tweek spent_on)
48 48
          @hours = []
49 49
          @scope.includes(:activity).
50
              reorder(nil).
50
              reorder(@criteria.collect{|criteria| @available_criteria[criteria][:order]}.compact).
51 51
              group(@criteria.collect{|criteria| @available_criteria[criteria][:sql]} + time_columns).
52 52
              joins(@criteria.collect{|criteria| @available_criteria[criteria][:joins]}.compact).
53 53
              sum(:hours).each do |hash, hours|
......
105 105
      def load_available_criteria
106 106
        @available_criteria = {
107 107
          'project' => {:sql => "#{TimeEntry.table_name}.project_id",
108
                        :order => {Arel.sql("LOWER(#{Project.table_name}.name)") => :asc, "#{TimeEntry.table_name}.project_id" => :asc},
108 109
                        :klass => Project,
109 110
                        :label => :label_project},
110 111
          'status' => {:sql => "#{Issue.table_name}.status_id",
112
                       :joins => "LEFT OUTER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id",
113
                       :order => {"#{IssueStatus.table_name}.position" => :asc, "#{Issue.table_name}.status_id" => :asc},
111 114
                       :klass => IssueStatus,
112 115
                       :label => :field_status},
113 116
          'version' => {:sql => "#{Issue.table_name}.fixed_version_id",
117
                        :joins => "LEFT OUTER JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Issue.table_name}.fixed_version_id",
118
                        :order => {"#{Version.table_name}.name" => :asc, "#{Issue.table_name}.fixed_version_id" => :asc},
114 119
                        :klass => ::Version,
115 120
                        :label => :label_version},
116 121
          'category' => {:sql => "#{Issue.table_name}.category_id",
122
                         :joins => "LEFT OUTER JOIN #{IssueCategory.table_name} ON #{IssueCategory.table_name}.id = #{Issue.table_name}.category_id",
123
                         :order => {"#{IssueCategory.table_name}.name" => :asc, "#{Issue.table_name}.category_id" => :asc},
117 124
                         :klass => IssueCategory,
118 125
                         :label => :field_category},
119 126
          'user' => {:sql => "#{TimeEntry.table_name}.user_id",
127
                     :order => User.fields_for_order_statement.index_with{|_| :asc},
120 128
                     :klass => User,
121 129
                     :label => :label_user},
122 130
          'tracker' => {:sql => "#{Issue.table_name}.tracker_id",
131
                        :joins => "LEFT OUTER JOIN #{Tracker.table_name} ON #{Tracker.table_name}.id = #{Issue.table_name}.tracker_id",
132
                        :order => {"#{Tracker.table_name}.position" => :asc, "#{Issue.table_name}.tracker_id" => :asc},
123 133
                        :klass => Tracker,
124 134
                        :label => :label_tracker},
125 135
          'activity' => {:sql => "COALESCE(#{TimeEntryActivity.table_name}.parent_id, #{TimeEntryActivity.table_name}.id)",
136
                         :order => {"#{TimeEntryActivity.table_name}.position" => :asc},
126 137
                         :klass => TimeEntryActivity,
127 138
                         :label => :field_activity},
128 139
          'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
140
                      :order => {"#{TimeEntry.table_name}.issue_id" => :asc},
129 141
                      :klass => Issue,
130 142
                      :label => :label_issue}
131 143
        }
......
141 153

  
142 154
        # Add list and boolean custom fields as available criteria
143 155
        custom_fields.select {|cf| %w(list bool).include?(cf.field_format) && !cf.multiple?}.each do |cf|
156
          subquery_name = "#{cf.format.__send__(:join_alias, cf)}_options"
157
          subquery =
158
            cf.format.possible_values_options(cf).collect.with_index(1) do |option, idx|
159
              value = option.is_a?(Array) ? option.last : option
160
              cf.class.sanitize_sql_array(["SELECT %d AS id, '%s' AS value", idx, value])
161
            end.join(' UNION ')
162
          joins = cf.join_for_order_statement
163
          joins << " LEFT OUTER JOIN (#{subquery}) AS #{subquery_name} ON #{subquery_name}.value = #{cf.group_statement}"
144 164
          @available_criteria["cf_#{cf.id}"] = {:sql => cf.group_statement,
145
                                                 :joins => cf.join_for_order_statement,
165
                                                 :joins => joins,
166
                                                 :order => {"#{subquery_name}.id" => :asc},
146 167
                                                 :format => cf.field_format,
147 168
                                                 :custom_field => cf,
148 169
                                                 :label => cf.name}
test/functional/timelog_report_test.rb
27 27
           :issues, :time_entries, :users, :trackers, :enumerations,
28 28
           :issue_statuses, :custom_fields, :custom_values,
29 29
           :projects_trackers, :custom_fields_trackers,
30
           :custom_fields_projects
30
           :custom_fields_projects,
31
           :versions, :issue_categories
31 32

  
32 33
  include Redmine::I18n
33 34

  
......
237 238
    end
238 239
  end
239 240

  
241
  def test_report_with_project_criteria_should_be_sorted_by_project_name
242
    project = Project.find(5)
243
    project.update_attribute(:is_public, true)
244
    project.enable_module!(:time_tracking)
245

  
246
    get :report, :params => {:criteria => ['project']}
247
    assert_response :success
248
    assert_select '#time-report tbody' do
249
      assert_select 'tr:nth-child(1) td.name', :text => 'eCookbook'
250
      assert_select 'tr:nth-child(2) td.name', :text => 'eCookbook Subproject 1'
251
      assert_select 'tr:nth-child(3) td.name', :text => 'Private child of eCookbook'
252
    end
253
  end
254

  
255
  def test_report_with_status_criteria_should_be_sorted_by_position
256
    Project.find(3).update_attribute(:parent_id, nil)
257
    Issue.find(1).update_attribute(:status_id, 3)
258

  
259
    get :report, :params => {:project_id => 1, :criteria => ['status']}
260
    assert_response :success
261
    assert_select '#time-report tbody' do
262
      assert_select 'tr:nth-child(1) td.name', :text => 'New'
263
      assert_select 'tr:nth-child(2) td.name', :text => 'Resolved'
264
    end
265
  end
266

  
267
  def test_report_with_version_criteria_should_be_sorted_by_version_name
268
    Project.find(3).update_attribute(:parent_id, nil)
269
    Issue.find(1).update_attribute(:fixed_version_id, 3)
270
    Issue.find(3).update_attribute(:fixed_version_id, 2)
271

  
272
    get :report, :params => {:project_id => 1, :criteria => ['version']}
273
    assert_response :success
274
    assert_select '#time-report tbody' do
275
      assert_select 'tr:nth-child(1) td.name', :text => '1.0'
276
      assert_select 'tr:nth-child(2) td.name', :text => '2.0'
277
    end
278
  end
279

  
280
  def test_report_with_category_criteria_should_be_sorted_by_category_name
281
    Project.find(3).update_attribute(:parent_id, nil)
282
    Issue.find(1).update_attribute(:category_id, 2)
283
    Issue.find(3).update_attribute(:category_id, 1)
284

  
285
    get :report, :params => {:project_id => 1, :criteria => ['category']}
286
    assert_response :success
287
    assert_select '#time-report tbody' do
288
      assert_select 'tr:nth-child(1) td.name', :text => 'Printing'
289
      assert_select 'tr:nth-child(2) td.name', :text => 'Recipes'
290
    end
291
  end
292

  
293
  def test_report_with_user_criteria_should_be_sorted_by_user_name
294
    TimeEntry.find(2).update_attribute(:user_id, 3)
295

  
296
    get :report, :params => {:project_id => 1, :criteria => ['user']}
297
    assert_response :success
298
    assert_select '#time-report tbody' do
299
      assert_select 'tr:nth-child(1) td.name', :text => 'Dave Lopper'
300
      assert_select 'tr:nth-child(2) td.name', :text => 'John Smith'
301
      assert_select 'tr:nth-child(3) td.name', :text => 'Redmine Admin'
302
    end
303
  end
304

  
305
  def test_report_with_tracker_criteria_should_be_sorted_by_position
306
    Project.find(3).update_attribute(:parent_id, nil)
307
    Issue.find(1).update_attribute(:tracker_id, 3)
308
    TimeEntry.find(1).update_attribute(:issue_id, 2)
309

  
310
    get :report, :params => {:project_id => 1, :criteria => ['tracker']}
311
    assert_response :success
312
    assert_select '#time-report tbody' do
313
      assert_select 'tr:nth-child(1) td.name', :text => 'Bug'
314
      assert_select 'tr:nth-child(2) td.name', :text => 'Feature request'
315
      assert_select 'tr:nth-child(3) td.name', :text => 'Support request'
316
    end
317
  end
318

  
319
  def test_report_with_activity_criteria_should_be_sorted_by_position
320
    TimeEntry.find(1).update_attribute(:activity_id, 11)
321

  
322
    get :report, :params => {:project_id => 1, :criteria => ['activity']}
323
    assert_response :success
324
    assert_select '#time-report tbody' do
325
      assert_select 'tr:nth-child(1) td.name', :text => 'Design'
326
      assert_select 'tr:nth-child(2) td.name', :text => 'Development'
327
      assert_select 'tr:nth-child(3) td.name', :text => 'QA'
328
    end
329
  end
330

  
331
  def test_report_with_issue_criteria_should_be_sorted_by_id
332
    Project.find(3).update_attribute(:parent_id, nil)
333
    TimeEntry.find(1).update_attribute(:issue_id, 2)
334

  
335
    get :report, :params => {:project_id => 1, :criteria => ['issue']}
336
    assert_response :success
337
    assert_select '#time-report tbody' do
338
      assert_select 'tr:nth-child(1) td.name a[href="/issues/1"]'
339
      assert_select 'tr:nth-child(2) td.name a[href="/issues/2"]'
340
      assert_select 'tr:nth-child(3) td.name a[href="/issues/3"]'
341
    end
342
  end
343

  
344
  def test_report_with_bool_format_custom_field_criteria_should_be_sorted_by_yes_no
345
    Project.find(3).update_attribute(:parent_id, nil)
346
    TimeEntry.find(3).destroy
347
    time_entry1 = TimeEntry.find(1)
348
    time_entry1.custom_field_values = {'10' => '0'}
349
    time_entry1.save!
350
    time_entry2 = TimeEntry.find(2)
351
    time_entry2.custom_field_values = {'10' => '1'}
352
    time_entry2.save!
353

  
354
    get :report, :params => {:project_id => 1, :criteria => ['cf_10']}
355
    assert_response :success
356
    assert_select '#time-report tbody' do
357
      assert_select 'tr:nth-child(1) td.name', :text => 'Yes'
358
      assert_select 'tr:nth-child(2) td.name', :text => 'No'
359
    end
360
  end
361

  
362
  def test_report_with_list_format_custom_field_criteria_should_be_sorted_by_possible_values_order
363
    Project.find(3).update_attribute(:parent_id, nil)
364
    TimeEntry.find(1).update_attribute(:issue_id, 7)
365
    issue1 = Issue.find(1)
366
    issue1.custom_field_values = {'1' => 'Oracle'}
367
    issue1.save!
368
    issue7 = Issue.find(7)
369
    issue7.custom_field_values = {'1' => 'PostgreSQL'}
370
    issue7.save!
371

  
372
    get :report, :params => {:project_id => 1, :criteria => ['cf_1']}
373
    assert_response :success
374
    assert_select '#time-report tbody' do
375
      assert_select 'tr:nth-child(1) td.name', :text => 'MySQL'
376
      assert_select 'tr:nth-child(2) td.name', :text => 'PostgreSQL'
377
      assert_select 'tr:nth-child(3) td.name', :text => 'Oracle'
378
    end
379
  end
380

  
240 381
  def test_report_all_projects_csv_export
241 382
    get :report, :params => {
242 383
      :columns => 'month',
(5-5/5)