Feature #2848 » 2848-with-test-v2.patch
| 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', |
- « Previous
- 1
- …
- 3
- 4
- 5
- Next »