Project

General

Profile

Feature #2848 » 2848-with-test.patch

Yuichi HARADA, 2021-09-28 07:00

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 => "#{TimeEntry.table_name}.activity_id",
136
                         :order => {"#{TimeEntryActivity.table_name}.position" => :asc, "#{TimeEntry.table_name}.activity_id" => :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

  
......
224 225
    assert_select 'td', :text => 'New'
225 226
  end
226 227

  
228
  def test_report_with_project_criteria_should_be_sorted_by_project_name
229
    project = Project.find(5)
230
    project.update_attribute(:is_public, true)
231
    project.enable_module!(:time_tracking)
232

  
233
    get :report, :params => {:criteria => ['project']}
234
    assert_response :success
235
    assert_select '#time-report tbody' do
236
      assert_select 'tr:nth-child(1) td.name', :text => 'eCookbook'
237
      assert_select 'tr:nth-child(2) td.name', :text => 'eCookbook Subproject 1'
238
      assert_select 'tr:nth-child(3) td.name', :text => 'Private child of eCookbook'
239
    end
240
  end
241

  
242
  def test_report_with_status_criteria_should_be_sorted_by_position
243
    Project.find(3).update_attribute(:parent_id, nil)
244
    Issue.find(1).update_attribute(:status_id, 3)
245

  
246
    get :report, :params => {:project_id => 1, :criteria => ['status']}
247
    assert_response :success
248
    assert_select '#time-report tbody' do
249
      assert_select 'tr:nth-child(1) td.name', :text => 'New'
250
      assert_select 'tr:nth-child(2) td.name', :text => 'Resolved'
251
    end
252
  end
253

  
254
  def test_report_with_version_criteria_should_be_sorted_by_version_name
255
    Project.find(3).update_attribute(:parent_id, nil)
256
    Issue.find(1).update_attribute(:fixed_version_id, 3)
257
    Issue.find(3).update_attribute(:fixed_version_id, 2)
258

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

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

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

  
280
  def test_report_with_user_criteria_should_be_sorted_by_user_name
281
    TimeEntry.find(2).update_attribute(:user_id, 3)
282

  
283
    get :report, :params => {:project_id => 1, :criteria => ['user']}
284
    assert_response :success
285
    assert_select '#time-report tbody' do
286
      assert_select 'tr:nth-child(1) td.name', :text => 'Dave Lopper'
287
      assert_select 'tr:nth-child(2) td.name', :text => 'John Smith'
288
      assert_select 'tr:nth-child(3) td.name', :text => 'Redmine Admin'
289
    end
290
  end
291

  
292
  def test_report_with_tracker_criteria_should_be_sorted_by_position
293
    Project.find(3).update_attribute(:parent_id, nil)
294
    Issue.find(1).update_attribute(:tracker_id, 3)
295
    TimeEntry.find(1).update_attribute(:issue_id, 2)
296

  
297
    get :report, :params => {:project_id => 1, :criteria => ['tracker']}
298
    assert_response :success
299
    assert_select '#time-report tbody' do
300
      assert_select 'tr:nth-child(1) td.name', :text => 'Bug'
301
      assert_select 'tr:nth-child(2) td.name', :text => 'Feature request'
302
      assert_select 'tr:nth-child(3) td.name', :text => 'Support request'
303
    end
304
  end
305

  
306
  def test_report_with_activity_criteria_should_be_sorted_by_position
307
    TimeEntry.find(1).update_attribute(:activity_id, 11)
308

  
309
    get :report, :params => {:project_id => 1, :criteria => ['activity']}
310
    assert_response :success
311
    assert_select '#time-report tbody' do
312
      assert_select 'tr:nth-child(1) td.name', :text => 'Design'
313
      assert_select 'tr:nth-child(2) td.name', :text => 'Development'
314
      assert_select 'tr:nth-child(3) td.name', :text => 'QA'
315
    end
316
  end
317

  
318
  def test_report_with_issue_criteria_should_be_sorted_by_id
319
    Project.find(3).update_attribute(:parent_id, nil)
320
    TimeEntry.find(1).update_attribute(:issue_id, 2)
321

  
322
    get :report, :params => {:project_id => 1, :criteria => ['issue']}
323
    assert_response :success
324
    assert_select '#time-report tbody' do
325
      assert_select 'tr:nth-child(1) td.name a[href="/issues/1"]'
326
      assert_select 'tr:nth-child(2) td.name a[href="/issues/2"]'
327
      assert_select 'tr:nth-child(3) td.name a[href="/issues/3"]'
328
    end
329
  end
330

  
331
  def test_report_with_bool_format_custom_field_criteria_should_be_sorted_by_yes_no
332
    Project.find(3).update_attribute(:parent_id, nil)
333
    TimeEntry.find(3).destroy
334
    time_entry1 = TimeEntry.find(1)
335
    time_entry1.custom_field_values = {'10' => '0'}
336
    time_entry1.save!
337
    time_entry2 = TimeEntry.find(2)
338
    time_entry2.custom_field_values = {'10' => '1'}
339
    time_entry2.save!
340

  
341
    get :report, :params => {:project_id => 1, :criteria => ['cf_10']}
342
    assert_response :success
343
    assert_select '#time-report tbody' do
344
      assert_select 'tr:nth-child(1) td.name', :text => 'Yes'
345
      assert_select 'tr:nth-child(2) td.name', :text => 'No'
346
    end
347
  end
348

  
349
  def test_report_with_list_format_custom_field_criteria_should_be_sorted_by_possible_values_order
350
    Project.find(3).update_attribute(:parent_id, nil)
351
    TimeEntry.find(1).update_attribute(:issue_id, 7)
352
    issue1 = Issue.find(1)
353
    issue1.custom_field_values = {'1' => 'Oracle'}
354
    issue1.save!
355
    issue7 = Issue.find(7)
356
    issue7.custom_field_values = {'1' => 'PostgreSQL'}
357
    issue7.save!
358

  
359
    get :report, :params => {:project_id => 1, :criteria => ['cf_1']}
360
    assert_response :success
361
    assert_select '#time-report tbody' do
362
      assert_select 'tr:nth-child(1) td.name', :text => 'MySQL'
363
      assert_select 'tr:nth-child(2) td.name', :text => 'PostgreSQL'
364
      assert_select 'tr:nth-child(3) td.name', :text => 'Oracle'
365
    end
366
  end
367

  
227 368
  def test_report_all_projects_csv_export
228 369
    get :report, :params => {
229 370
      :columns => 'month',
(4-4/5)