From b5e90972d88c69a1ef2c9e90879e8926d192acff Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Wed, 1 Sep 2010 15:17:45 +0000 Subject: [PATCH 01/49] Refactor: move method, ProjectsController#add_file to FilesController#new. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4052 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/files_controller.rb | 15 ++++++++ app/controllers/projects_controller.rb | 16 -------- app/views/files/index.html.erb | 2 +- .../add_file.rhtml => files/new.html.erb} | 2 +- config/routes.rb | 4 +- lib/redmine.rb | 2 +- test/functional/files_controller_test.rb | 38 +++++++++++++++++++ test/functional/projects_controller_test.rb | 36 ------------------ test/integration/routing_test.rb | 4 +- 9 files changed, 60 insertions(+), 59 deletions(-) rename app/views/{projects/add_file.rhtml => files/new.html.erb} (82%) diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index c84ce5f51..fe5eb48c8 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -19,4 +19,19 @@ class FilesController < ApplicationController render :layout => !request.xhr? end + # TODO: split method into new (GET) and create (POST) + def new + if request.post? + container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) + attachments = Attachment.attach_files(container, params[:attachments]) + render_attachment_warning_if_needed(container) + + if !attachments.empty? && Setting.notified_events.include?('file_added') + Mailer.deliver_attachments_added(attachments[:files]) + end + redirect_to :controller => 'files', :action => 'index', :id => @project + return + end + @versions = @project.versions.sort + end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 05c8d969d..61c7a7c71 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -18,7 +18,6 @@ class ProjectsController < ApplicationController menu_item :overview menu_item :roadmap, :only => :roadmap - menu_item :files, :only => [:add_file] menu_item :settings, :only => :settings before_filter :find_project, :except => [ :index, :list, :add, :copy ] @@ -239,21 +238,6 @@ class ProjectsController < ApplicationController @project = nil end - def add_file - if request.post? - container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) - attachments = Attachment.attach_files(container, params[:attachments]) - render_attachment_warning_if_needed(container) - - if !attachments.empty? && Setting.notified_events.include?('file_added') - Mailer.deliver_attachments_added(attachments[:files]) - end - redirect_to :controller => 'files', :action => 'index', :id => @project - return - end - @versions = @project.versions.sort - end - def save_activities if request.post? && params[:enumerations] Project.transaction do diff --git a/app/views/files/index.html.erb b/app/views/files/index.html.erb index 2b2e5e870..66f5d12b9 100644 --- a/app/views/files/index.html.erb +++ b/app/views/files/index.html.erb @@ -1,5 +1,5 @@
-<%= link_to_if_authorized l(:label_attachment_new), {:controller => 'projects', :action => 'add_file', :id => @project}, :class => 'icon icon-add' %> +<%= link_to_if_authorized l(:label_attachment_new), {:controller => 'files', :action => 'new', :id => @project}, :class => 'icon icon-add' %>

<%=l(:label_attachment_plural)%>

diff --git a/app/views/projects/add_file.rhtml b/app/views/files/new.html.erb similarity index 82% rename from app/views/projects/add_file.rhtml rename to app/views/files/new.html.erb index ab9c7352d..bbb3b1733 100644 --- a/app/views/projects/add_file.rhtml +++ b/app/views/files/new.html.erb @@ -2,7 +2,7 @@ <%= error_messages_for 'attachment' %>
-<% form_tag({ :action => 'add_file', :id => @project }, :multipart => true, :class => "tabular") do %> +<% form_tag({ :action => 'new', :id => @project }, :multipart => true, :class => "tabular") do %> <% if @versions.any? %>

diff --git a/config/routes.rb b/config/routes.rb index 270bab4a0..9c8a2f6ae 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -182,7 +182,7 @@ ActionController::Routing::Routes.draw do |map| project_views.connect 'projects/:id.:format', :action => 'show' project_views.connect 'projects/:id/:action', :action => /destroy|settings/ project_views.connect 'projects/:id/files', :controller => 'files', :action => 'index' - project_views.connect 'projects/:id/files/new', :action => 'add_file' + project_views.connect 'projects/:id/files/new', :controller => 'files', :action => 'new' project_views.connect 'projects/:id/settings/:tab', :action => 'settings' project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new' end @@ -199,7 +199,7 @@ ActionController::Routing::Routes.draw do |map| project_actions.connect 'projects', :action => 'add' project_actions.connect 'projects.:format', :action => 'add', :format => /xml/ project_actions.connect 'projects/:id/:action', :action => /edit|destroy|archive|unarchive/ - project_actions.connect 'projects/:id/files/new', :action => 'add_file' + project_actions.connect 'projects/:id/files/new', :controller => 'files', :action => 'new' project_actions.connect 'projects/:id/activities/save', :action => 'save_activities' end diff --git a/lib/redmine.rb b/lib/redmine.rb index 726a2ad92..745c6d750 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -102,7 +102,7 @@ Redmine::AccessControl.map do |map| end map.project_module :files do |map| - map.permission :manage_files, {:projects => :add_file}, :require => :loggedin + map.permission :manage_files, {:files => :new}, :require => :loggedin map.permission :view_files, :files => :index, :versions => :download end diff --git a/test/functional/files_controller_test.rb b/test/functional/files_controller_test.rb index 908502183..838caee70 100644 --- a/test/functional/files_controller_test.rb +++ b/test/functional/files_controller_test.rb @@ -26,4 +26,42 @@ class FilesControllerTest < ActionController::TestCase :attributes => { :href => '/attachments/download/9/version_file.zip' } end + def test_add_file + set_tmp_attachments_directory + @request.session[:user_id] = 2 + Setting.notified_events = ['file_added'] + ActionMailer::Base.deliveries.clear + + assert_difference 'Attachment.count' do + post :new, :id => 1, :version_id => '', + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} + assert_response :redirect + end + assert_redirected_to 'projects/ecookbook/files' + a = Attachment.find(:first, :order => 'created_on DESC') + assert_equal 'testfile.txt', a.filename + assert_equal Project.find(1), a.container + + mail = ActionMailer::Base.deliveries.last + assert_kind_of TMail::Mail, mail + assert_equal "[eCookbook] New file", mail.subject + assert mail.body.include?('testfile.txt') + end + + def test_add_version_file + set_tmp_attachments_directory + @request.session[:user_id] = 2 + Setting.notified_events = ['file_added'] + + assert_difference 'Attachment.count' do + post :new, :id => 1, :version_id => '2', + :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} + assert_response :redirect + end + assert_redirected_to 'projects/ecookbook/files' + a = Attachment.find(:first, :order => 'created_on DESC') + assert_equal 'testfile.txt', a.filename + assert_equal Version.find(2), a.container + end + end diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index b22efd6ab..9d1582af0 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -317,42 +317,6 @@ class ProjectsControllerTest < ActionController::TestCase assert_nil Project.find_by_id(1) end - def test_add_file - set_tmp_attachments_directory - @request.session[:user_id] = 2 - Setting.notified_events = ['file_added'] - ActionMailer::Base.deliveries.clear - - assert_difference 'Attachment.count' do - post :add_file, :id => 1, :version_id => '', - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} - end - assert_redirected_to 'projects/ecookbook/files' - a = Attachment.find(:first, :order => 'created_on DESC') - assert_equal 'testfile.txt', a.filename - assert_equal Project.find(1), a.container - - mail = ActionMailer::Base.deliveries.last - assert_kind_of TMail::Mail, mail - assert_equal "[eCookbook] New file", mail.subject - assert mail.body.include?('testfile.txt') - end - - def test_add_version_file - set_tmp_attachments_directory - @request.session[:user_id] = 2 - Setting.notified_events = ['file_added'] - - assert_difference 'Attachment.count' do - post :add_file, :id => 1, :version_id => '2', - :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} - end - assert_redirected_to 'projects/ecookbook/files' - a = Attachment.find(:first, :order => 'created_on DESC') - assert_equal 'testfile.txt', a.filename - assert_equal Version.find(2), a.container - end - def test_archive @request.session[:user_id] = 1 # admin post :archive, :id => 1 diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index cc1f451cf..66836c4f9 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -173,7 +173,7 @@ class RoutingTest < ActionController::IntegrationTest should_route :get, "/projects/4223/settings/members", :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members' should_route :get, "/projects/567/destroy", :controller => 'projects', :action => 'destroy', :id => '567' should_route :get, "/projects/33/files", :controller => 'files', :action => 'index', :id => '33' - should_route :get, "/projects/33/files/new", :controller => 'projects', :action => 'add_file', :id => '33' + should_route :get, "/projects/33/files/new", :controller => 'files', :action => 'new', :id => '33' should_route :get, "/projects/33/roadmap", :controller => 'versions', :action => 'index', :project_id => '33' should_route :get, "/projects/33/activity", :controller => 'activities', :action => 'index', :id => '33' should_route :get, "/projects/33/activity.atom", :controller => 'activities', :action => 'index', :id => '33', :format => 'atom' @@ -182,7 +182,7 @@ class RoutingTest < ActionController::IntegrationTest should_route :post, "/projects.xml", :controller => 'projects', :action => 'add', :format => 'xml' should_route :post, "/projects/4223/edit", :controller => 'projects', :action => 'edit', :id => '4223' should_route :post, "/projects/64/destroy", :controller => 'projects', :action => 'destroy', :id => '64' - should_route :post, "/projects/33/files/new", :controller => 'projects', :action => 'add_file', :id => '33' + should_route :post, "/projects/33/files/new", :controller => 'files', :action => 'new', :id => '33' should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64' should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64' should_route :post, "/projects/64/activities/save", :controller => 'projects', :action => 'save_activities', :id => '64' From 83b4343d2d403a21de4614016e165091e4c5914f Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Thu, 2 Sep 2010 17:39:56 +0000 Subject: [PATCH 02/49] Refactor: move method, ProjectsController#save_activities to ProjectEnumerationsController#save git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4053 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- .../project_enumerations_controller.rb | 18 +++ app/controllers/projects_controller.rb | 13 -- app/views/projects/settings/_activities.rhtml | 2 +- config/routes.rb | 2 +- lib/redmine.rb | 2 +- .../project_enumerations_controller_test.rb | 142 ++++++++++++++++++ test/functional/projects_controller_test.rb | 132 ---------------- test/integration/routing_test.rb | 2 +- 8 files changed, 164 insertions(+), 149 deletions(-) create mode 100644 app/controllers/project_enumerations_controller.rb create mode 100644 test/functional/project_enumerations_controller_test.rb diff --git a/app/controllers/project_enumerations_controller.rb b/app/controllers/project_enumerations_controller.rb new file mode 100644 index 000000000..c4b48e476 --- /dev/null +++ b/app/controllers/project_enumerations_controller.rb @@ -0,0 +1,18 @@ +class ProjectEnumerationsController < ApplicationController + before_filter :find_project + before_filter :authorize + + def save + if request.post? && params[:enumerations] + Project.transaction do + params[:enumerations].each do |id, activity| + @project.update_or_create_time_entry_activity(id, activity) + end + end + flash[:notice] = l(:notice_successful_update) + end + + redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project + end + +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 61c7a7c71..0670cb1ec 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -238,19 +238,6 @@ class ProjectsController < ApplicationController @project = nil end - def save_activities - if request.post? && params[:enumerations] - Project.transaction do - params[:enumerations].each do |id, activity| - @project.update_or_create_time_entry_activity(id, activity) - end - end - flash[:notice] = l(:notice_successful_update) - end - - redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project - end - def reset_activities @project.time_entry_activities.each do |time_entry_activity| time_entry_activity.destroy(time_entry_activity.parent) diff --git a/app/views/projects/settings/_activities.rhtml b/app/views/projects/settings/_activities.rhtml index d82bd96fb..e39bf9b91 100644 --- a/app/views/projects/settings/_activities.rhtml +++ b/app/views/projects/settings/_activities.rhtml @@ -1,4 +1,4 @@ -<% form_tag({:controller => 'projects', :action => 'save_activities', :id => @project}, :class => "tabular") do %> +<% form_tag({:controller => 'project_enumerations', :action => 'save', :id => @project}, :class => "tabular") do %> diff --git a/config/routes.rb b/config/routes.rb index 9c8a2f6ae..124115998 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -200,7 +200,7 @@ ActionController::Routing::Routes.draw do |map| project_actions.connect 'projects.:format', :action => 'add', :format => /xml/ project_actions.connect 'projects/:id/:action', :action => /edit|destroy|archive|unarchive/ project_actions.connect 'projects/:id/files/new', :controller => 'files', :action => 'new' - project_actions.connect 'projects/:id/activities/save', :action => 'save_activities' + project_actions.connect 'projects/:id/activities/save', :controller => 'project_enumerations', :action => 'save' end projects.with_options :conditions => {:method => :put} do |project_actions| diff --git a/lib/redmine.rb b/lib/redmine.rb index 745c6d750..fdd61225e 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -87,7 +87,7 @@ Redmine::AccessControl.map do |map| map.permission :view_time_entries, :timelog => [:details, :report] map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin - map.permission :manage_project_activities, {:projects => [:save_activities, :reset_activities]}, :require => :member + map.permission :manage_project_activities, {:projects => [:reset_activities], :project_enumerations => [:save]}, :require => :member end map.project_module :news do |map| diff --git a/test/functional/project_enumerations_controller_test.rb b/test/functional/project_enumerations_controller_test.rb new file mode 100644 index 000000000..23da42378 --- /dev/null +++ b/test/functional/project_enumerations_controller_test.rb @@ -0,0 +1,142 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ProjectEnumerationsControllerTest < ActionController::TestCase + fixtures :all + + def setup + @request.session[:user_id] = nil + Setting.default_language = 'en' + end + + def test_save_to_override_system_activities + @request.session[:user_id] = 2 # manager + billable_field = TimeEntryActivityCustomField.find_by_name("Billable") + + post :save, :id => 1, :enumerations => { + "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design, De-activate + "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"}, # Development, Change custom value + "14"=>{"parent_id"=>"14", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"}, # Inactive Activity, Activate with custom value + "11"=>{"parent_id"=>"11", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"} # QA, no changes + } + + assert_response :redirect + assert_redirected_to 'projects/ecookbook/settings/activities' + + # Created project specific activities... + project = Project.find('ecookbook') + + # ... Design + design = project.time_entry_activities.find_by_name("Design") + assert design, "Project activity not found" + + assert_equal 9, design.parent_id # Relate to the system activity + assert_not_equal design.parent.id, design.id # Different records + assert_equal design.parent.name, design.name # Same name + assert !design.active? + + # ... Development + development = project.time_entry_activities.find_by_name("Development") + assert development, "Project activity not found" + + assert_equal 10, development.parent_id # Relate to the system activity + assert_not_equal development.parent.id, development.id # Different records + assert_equal development.parent.name, development.name # Same name + assert development.active? + assert_equal "0", development.custom_value_for(billable_field).value + + # ... Inactive Activity + previously_inactive = project.time_entry_activities.find_by_name("Inactive Activity") + assert previously_inactive, "Project activity not found" + + assert_equal 14, previously_inactive.parent_id # Relate to the system activity + assert_not_equal previously_inactive.parent.id, previously_inactive.id # Different records + assert_equal previously_inactive.parent.name, previously_inactive.name # Same name + assert previously_inactive.active? + assert_equal "1", previously_inactive.custom_value_for(billable_field).value + + # ... QA + assert_equal nil, project.time_entry_activities.find_by_name("QA"), "Custom QA activity created when it wasn't modified" + end + + def test_save_will_update_project_specific_activities + @request.session[:user_id] = 2 # manager + + project_activity = TimeEntryActivity.new({ + :name => 'Project Specific', + :parent => TimeEntryActivity.find(:first), + :project => Project.find(1), + :active => true + }) + assert project_activity.save + project_activity_two = TimeEntryActivity.new({ + :name => 'Project Specific Two', + :parent => TimeEntryActivity.find(:last), + :project => Project.find(1), + :active => true + }) + assert project_activity_two.save + + + post :save, :id => 1, :enumerations => { + project_activity.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # De-activate + project_activity_two.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"} # De-activate + } + + assert_response :redirect + assert_redirected_to 'projects/ecookbook/settings/activities' + + # Created project specific activities... + project = Project.find('ecookbook') + assert_equal 2, project.time_entry_activities.count + + activity_one = project.time_entry_activities.find_by_name(project_activity.name) + assert activity_one, "Project activity not found" + assert_equal project_activity.id, activity_one.id + assert !activity_one.active? + + activity_two = project.time_entry_activities.find_by_name(project_activity_two.name) + assert activity_two, "Project activity not found" + assert_equal project_activity_two.id, activity_two.id + assert !activity_two.active? + end + + def test_save_when_creating_new_activities_will_convert_existing_data + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size + + @request.session[:user_id] = 2 # manager + post :save, :id => 1, :enumerations => { + "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"} # Design, De-activate + } + assert_response :redirect + + # No more TimeEntries using the system activity + assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries still assigned to system activities" + # All TimeEntries using project activity + project_specific_activity = TimeEntryActivity.find_by_parent_id_and_project_id(9, 1) + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(project_specific_activity.id, 1).size, "No Time Entries assigned to the project activity" + end + + def test_save_when_creating_new_activities_will_not_convert_existing_data_if_an_exception_is_raised + # TODO: Need to cause an exception on create but these tests + # aren't setup for mocking. Just create a record now so the + # second one is a dupicate + parent = TimeEntryActivity.find(9) + TimeEntryActivity.create!({:name => parent.name, :project_id => 1, :position => parent.position, :active => true}) + TimeEntry.create!({:project_id => 1, :hours => 1.0, :user => User.find(1), :issue_id => 3, :activity_id => 10, :spent_on => '2009-01-01'}) + + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size + assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size + + @request.session[:user_id] = 2 # manager + post :save, :id => 1, :enumerations => { + "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design + "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"} # Development, Change custom value + } + assert_response :redirect + + # TimeEntries shouldn't have been reassigned on the failed record + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries are not assigned to system activities" + # TimeEntries shouldn't have been reassigned on the saved record either + assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size, "Time Entries are not assigned to system activities" + end +end diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 9d1582af0..152d0837b 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -427,138 +427,6 @@ class ProjectsControllerTest < ActionController::TestCase assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "TimeEntries still assigned to project specific activity" end - def test_save_activities_to_override_system_activities - @request.session[:user_id] = 2 # manager - billable_field = TimeEntryActivityCustomField.find_by_name("Billable") - - post :save_activities, :id => 1, :enumerations => { - "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design, De-activate - "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"}, # Development, Change custom value - "14"=>{"parent_id"=>"14", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"}, # Inactive Activity, Activate with custom value - "11"=>{"parent_id"=>"11", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"} # QA, no changes - } - - assert_response :redirect - assert_redirected_to 'projects/ecookbook/settings/activities' - - # Created project specific activities... - project = Project.find('ecookbook') - - # ... Design - design = project.time_entry_activities.find_by_name("Design") - assert design, "Project activity not found" - - assert_equal 9, design.parent_id # Relate to the system activity - assert_not_equal design.parent.id, design.id # Different records - assert_equal design.parent.name, design.name # Same name - assert !design.active? - - # ... Development - development = project.time_entry_activities.find_by_name("Development") - assert development, "Project activity not found" - - assert_equal 10, development.parent_id # Relate to the system activity - assert_not_equal development.parent.id, development.id # Different records - assert_equal development.parent.name, development.name # Same name - assert development.active? - assert_equal "0", development.custom_value_for(billable_field).value - - # ... Inactive Activity - previously_inactive = project.time_entry_activities.find_by_name("Inactive Activity") - assert previously_inactive, "Project activity not found" - - assert_equal 14, previously_inactive.parent_id # Relate to the system activity - assert_not_equal previously_inactive.parent.id, previously_inactive.id # Different records - assert_equal previously_inactive.parent.name, previously_inactive.name # Same name - assert previously_inactive.active? - assert_equal "1", previously_inactive.custom_value_for(billable_field).value - - # ... QA - assert_equal nil, project.time_entry_activities.find_by_name("QA"), "Custom QA activity created when it wasn't modified" - end - - def test_save_activities_will_update_project_specific_activities - @request.session[:user_id] = 2 # manager - - project_activity = TimeEntryActivity.new({ - :name => 'Project Specific', - :parent => TimeEntryActivity.find(:first), - :project => Project.find(1), - :active => true - }) - assert project_activity.save - project_activity_two = TimeEntryActivity.new({ - :name => 'Project Specific Two', - :parent => TimeEntryActivity.find(:last), - :project => Project.find(1), - :active => true - }) - assert project_activity_two.save - - - post :save_activities, :id => 1, :enumerations => { - project_activity.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # De-activate - project_activity_two.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"} # De-activate - } - - assert_response :redirect - assert_redirected_to 'projects/ecookbook/settings/activities' - - # Created project specific activities... - project = Project.find('ecookbook') - assert_equal 2, project.time_entry_activities.count - - activity_one = project.time_entry_activities.find_by_name(project_activity.name) - assert activity_one, "Project activity not found" - assert_equal project_activity.id, activity_one.id - assert !activity_one.active? - - activity_two = project.time_entry_activities.find_by_name(project_activity_two.name) - assert activity_two, "Project activity not found" - assert_equal project_activity_two.id, activity_two.id - assert !activity_two.active? - end - - def test_save_activities_when_creating_new_activities_will_convert_existing_data - assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size - - @request.session[:user_id] = 2 # manager - post :save_activities, :id => 1, :enumerations => { - "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"} # Design, De-activate - } - assert_response :redirect - - # No more TimeEntries using the system activity - assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries still assigned to system activities" - # All TimeEntries using project activity - project_specific_activity = TimeEntryActivity.find_by_parent_id_and_project_id(9, 1) - assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(project_specific_activity.id, 1).size, "No Time Entries assigned to the project activity" - end - - def test_save_activities_when_creating_new_activities_will_not_convert_existing_data_if_an_exception_is_raised - # TODO: Need to cause an exception on create but these tests - # aren't setup for mocking. Just create a record now so the - # second one is a dupicate - parent = TimeEntryActivity.find(9) - TimeEntryActivity.create!({:name => parent.name, :project_id => 1, :position => parent.position, :active => true}) - TimeEntry.create!({:project_id => 1, :hours => 1.0, :user => User.find(1), :issue_id => 3, :activity_id => 10, :spent_on => '2009-01-01'}) - - assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size - assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size - - @request.session[:user_id] = 2 # manager - post :save_activities, :id => 1, :enumerations => { - "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design - "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"} # Development, Change custom value - } - assert_response :redirect - - # TimeEntries shouldn't have been reassigned on the failed record - assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries are not assigned to system activities" - # TimeEntries shouldn't have been reassigned on the saved record either - assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size, "Time Entries are not assigned to system activities" - end - # A hook that is manually registered later class ProjectBasedTemplate < Redmine::Hook::ViewListener def view_layouts_base_html_head(context) diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index 66836c4f9..74a0f7a71 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -185,7 +185,7 @@ class RoutingTest < ActionController::IntegrationTest should_route :post, "/projects/33/files/new", :controller => 'files', :action => 'new', :id => '33' should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64' should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64' - should_route :post, "/projects/64/activities/save", :controller => 'projects', :action => 'save_activities', :id => '64' + should_route :post, "/projects/64/activities/save", :controller => 'project_enumerations', :action => 'save', :id => '64' should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'edit', :id => '1', :format => 'xml' From c1068bf0cd32df1edf9b1b69e76e30af84692b7c Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 3 Sep 2010 15:04:03 +0000 Subject: [PATCH 03/49] Refactor: move method, ProjectsController#reset_activities to ProjectEnumerationsController#destroy. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4054 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- .../project_enumerations_controller.rb | 8 ++++ app/controllers/projects_controller.rb | 8 ---- app/views/projects/settings/_activities.rhtml | 2 +- config/routes.rb | 2 +- lib/redmine.rb | 2 +- .../project_enumerations_controller_test.rb | 47 +++++++++++++++++++ test/functional/projects_controller_test.rb | 46 ------------------ test/integration/routing_test.rb | 2 +- 8 files changed, 59 insertions(+), 58 deletions(-) diff --git a/app/controllers/project_enumerations_controller.rb b/app/controllers/project_enumerations_controller.rb index c4b48e476..d63e23641 100644 --- a/app/controllers/project_enumerations_controller.rb +++ b/app/controllers/project_enumerations_controller.rb @@ -15,4 +15,12 @@ class ProjectEnumerationsController < ApplicationController redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project end + def destroy + @project.time_entry_activities.each do |time_entry_activity| + time_entry_activity.destroy(time_entry_activity.parent) + end + flash[:notice] = l(:notice_successful_update) + redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project + end + end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 0670cb1ec..90eddd7b2 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -238,14 +238,6 @@ class ProjectsController < ApplicationController @project = nil end - def reset_activities - @project.time_entry_activities.each do |time_entry_activity| - time_entry_activity.destroy(time_entry_activity.parent) - end - flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project - end - private def find_optional_project return true unless params[:id] diff --git a/app/views/projects/settings/_activities.rhtml b/app/views/projects/settings/_activities.rhtml index e39bf9b91..5909ac64c 100644 --- a/app/views/projects/settings/_activities.rhtml +++ b/app/views/projects/settings/_activities.rhtml @@ -32,7 +32,7 @@

-<%= link_to(l(:button_reset), {:controller => 'projects', :action => 'reset_activities', :id => @project}, +<%= link_to(l(:button_reset), {:controller => 'project_enumerations', :action => 'destroy', :id => @project}, :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %> diff --git a/config/routes.rb b/config/routes.rb index 124115998..5448b5f58 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -209,7 +209,7 @@ ActionController::Routing::Routes.draw do |map| projects.with_options :conditions => {:method => :delete} do |project_actions| project_actions.conditions 'projects/:id.:format', :action => 'destroy', :format => /xml/ - project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities' + project_actions.conditions 'projects/:id/reset_activities', :controller => 'project_enumerations', :action => 'destroy' end end diff --git a/lib/redmine.rb b/lib/redmine.rb index fdd61225e..fc750d110 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -87,7 +87,7 @@ Redmine::AccessControl.map do |map| map.permission :view_time_entries, :timelog => [:details, :report] map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin - map.permission :manage_project_activities, {:projects => [:reset_activities], :project_enumerations => [:save]}, :require => :member + map.permission :manage_project_activities, {:project_enumerations => [:save, :destroy]}, :require => :member end map.project_module :news do |map| diff --git a/test/functional/project_enumerations_controller_test.rb b/test/functional/project_enumerations_controller_test.rb index 23da42378..c03be04fb 100644 --- a/test/functional/project_enumerations_controller_test.rb +++ b/test/functional/project_enumerations_controller_test.rb @@ -139,4 +139,51 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase # TimeEntries shouldn't have been reassigned on the saved record either assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size, "Time Entries are not assigned to system activities" end + + def test_destroy + @request.session[:user_id] = 2 # manager + project_activity = TimeEntryActivity.new({ + :name => 'Project Specific', + :parent => TimeEntryActivity.find(:first), + :project => Project.find(1), + :active => true + }) + assert project_activity.save + project_activity_two = TimeEntryActivity.new({ + :name => 'Project Specific Two', + :parent => TimeEntryActivity.find(:last), + :project => Project.find(1), + :active => true + }) + assert project_activity_two.save + + delete :destroy, :id => 1 + assert_response :redirect + assert_redirected_to 'projects/ecookbook/settings/activities' + + assert_nil TimeEntryActivity.find_by_id(project_activity.id) + assert_nil TimeEntryActivity.find_by_id(project_activity_two.id) + end + + def test_destroy_should_reassign_time_entries_back_to_the_system_activity + @request.session[:user_id] = 2 # manager + project_activity = TimeEntryActivity.new({ + :name => 'Project Specific Design', + :parent => TimeEntryActivity.find(9), + :project => Project.find(1), + :active => true + }) + assert project_activity.save + assert TimeEntry.update_all("activity_id = '#{project_activity.id}'", ["project_id = ? AND activity_id = ?", 1, 9]) + assert 3, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size + + delete :destroy, :id => 1 + assert_response :redirect + assert_redirected_to 'projects/ecookbook/settings/activities' + + assert_nil TimeEntryActivity.find_by_id(project_activity.id) + assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size, "TimeEntries still assigned to project specific activity" + assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "TimeEntries still assigned to project specific activity" + end + end diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 152d0837b..4decb060f 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -381,52 +381,6 @@ class ProjectsControllerTest < ActionController::TestCase assert_template 'show' end - def test_reset_activities - @request.session[:user_id] = 2 # manager - project_activity = TimeEntryActivity.new({ - :name => 'Project Specific', - :parent => TimeEntryActivity.find(:first), - :project => Project.find(1), - :active => true - }) - assert project_activity.save - project_activity_two = TimeEntryActivity.new({ - :name => 'Project Specific Two', - :parent => TimeEntryActivity.find(:last), - :project => Project.find(1), - :active => true - }) - assert project_activity_two.save - - delete :reset_activities, :id => 1 - assert_response :redirect - assert_redirected_to 'projects/ecookbook/settings/activities' - - assert_nil TimeEntryActivity.find_by_id(project_activity.id) - assert_nil TimeEntryActivity.find_by_id(project_activity_two.id) - end - - def test_reset_activities_should_reassign_time_entries_back_to_the_system_activity - @request.session[:user_id] = 2 # manager - project_activity = TimeEntryActivity.new({ - :name => 'Project Specific Design', - :parent => TimeEntryActivity.find(9), - :project => Project.find(1), - :active => true - }) - assert project_activity.save - assert TimeEntry.update_all("activity_id = '#{project_activity.id}'", ["project_id = ? AND activity_id = ?", 1, 9]) - assert 3, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size - - delete :reset_activities, :id => 1 - assert_response :redirect - assert_redirected_to 'projects/ecookbook/settings/activities' - - assert_nil TimeEntryActivity.find_by_id(project_activity.id) - assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size, "TimeEntries still assigned to project specific activity" - assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "TimeEntries still assigned to project specific activity" - end - # A hook that is manually registered later class ProjectBasedTemplate < Redmine::Hook::ViewListener def view_layouts_base_html_head(context) diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index 74a0f7a71..71be6c3c7 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -190,7 +190,7 @@ class RoutingTest < ActionController::IntegrationTest should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'edit', :id => '1', :format => 'xml' should_route :delete, "/projects/1.xml", :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml' - should_route :delete, "/projects/64/reset_activities", :controller => 'projects', :action => 'reset_activities', :id => '64' + should_route :delete, "/projects/64/reset_activities", :controller => 'project_enumerations', :action => 'destroy', :id => '64' end context "repositories" do From a2ce6e236c397a527deb18cc2196371855d27c41 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Fri, 3 Sep 2010 19:54:24 +0000 Subject: [PATCH 04/49] Allow mass status update through context menu. #3411 NB: it cannot be done with issues from different projects, same as other fields. This will be addressed separately, see #5332. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4055 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/context_menus_controller.rb | 6 ++++++ app/views/context_menus/issues.html.erb | 21 +++++++++++-------- .../context_menus_controller_test.rb | 5 ++++- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/controllers/context_menus_controller.rb b/app/controllers/context_menus_controller.rb index 442f85b37..e437a18dc 100644 --- a/app/controllers/context_menus_controller.rb +++ b/app/controllers/context_menus_controller.rb @@ -6,6 +6,12 @@ class ContextMenusController < ApplicationController if (@issues.size == 1) @issue = @issues.first @allowed_statuses = @issue.new_statuses_allowed_to(User.current) + else + @allowed_statuses = @issues.map do |i| + i.new_statuses_allowed_to(User.current) + end.inject do |memo,s| + memo & s + end end projects = @issues.collect(&:project).compact.uniq @project = projects.first if projects.size == 1 diff --git a/app/views/context_menus/issues.html.erb b/app/views/context_menus/issues.html.erb index dc11b5fdb..efd8cde0e 100644 --- a/app/views/context_menus/issues.html.erb +++ b/app/views/context_menus/issues.html.erb @@ -4,20 +4,23 @@ <% if !@issue.nil? -%>
  • <%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon-edit', :disabled => !@can[:edit] %>
  • -
  • - <%= l(:field_status) %> -
      - <% @statuses.each do |s| -%> -
    • <%= context_menu_link s.name, {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:status_id => s}, :back_url => @back}, :method => :put, - :selected => (s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %>
    • - <% end -%> -
    -
  • <% else %>
  • <%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)}, :class => 'icon-edit', :disabled => !@can[:edit] %>
  • <% end %> + <% unless @allowed_statuses.empty? %> +
  • + <%= l(:field_status) %> +
      + <% @statuses.each do |s| -%> +
    • <%= context_menu_link s.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {:status_id => s}, :back_url => @back}, :method => :post, + :selected => (@issue && s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %>
    • + <% end -%> +
    +
  • + <% end %> + <% unless @trackers.nil? %>
  • <%= l(:field_tracker) %> diff --git a/test/functional/context_menus_controller_test.rb b/test/functional/context_menus_controller_test.rb index 764350879..0ebae695a 100644 --- a/test/functional/context_menus_controller_test.rb +++ b/test/functional/context_menus_controller_test.rb @@ -12,7 +12,7 @@ class ContextMenusControllerTest < ActionController::TestCase :attributes => { :href => '/issues/1/edit', :class => 'icon-edit' } assert_tag :tag => 'a', :content => 'Closed', - :attributes => { :href => '/issues/1?issue%5Bstatus_id%5D=5', + :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&issue%5Bstatus_id%5D=5', :class => '' } assert_tag :tag => 'a', :content => 'Immediate', :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&issue%5Bpriority_id%5D=8', @@ -59,6 +59,9 @@ class ContextMenusControllerTest < ActionController::TestCase assert_tag :tag => 'a', :content => 'Edit', :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&ids%5B%5D=2', :class => 'icon-edit' } + assert_tag :tag => 'a', :content => 'Closed', + :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&ids%5B%5D=2&issue%5Bstatus_id%5D=5', + :class => '' } assert_tag :tag => 'a', :content => 'Immediate', :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&ids%5B%5D=2&issue%5Bpriority_id%5D=8', :class => '' } From 4776a5a42716181aaa51edf3fc6fc4bc0d19231e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Fri, 3 Sep 2010 19:59:49 +0000 Subject: [PATCH 05/49] Hide checkboxes in issues list when printing git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4056 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/views/issues/_list.rhtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/issues/_list.rhtml b/app/views/issues/_list.rhtml index 2722b9e7b..1a2953bf5 100644 --- a/app/views/issues/_list.rhtml +++ b/app/views/issues/_list.rhtml @@ -3,7 +3,7 @@
    - <%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %> @@ -25,7 +25,7 @@ <% previous_group = group %> <% end %> "> - + <% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %> From b6d9f2bddf40eed35c4172342516542640304ac6 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Fri, 3 Sep 2010 20:05:51 +0000 Subject: [PATCH 06/49] Add css classes to journals display to facilitate theming git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4057 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/helpers/journals_helper.rb | 7 +++++++ app/views/issues/_history.rhtml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/helpers/journals_helper.rb b/app/helpers/journals_helper.rb index c8d53f253..990bfb1e5 100644 --- a/app/helpers/journals_helper.rb +++ b/app/helpers/journals_helper.rb @@ -39,4 +39,11 @@ module JournalsHelper onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;" link_to text, '#', options.merge(:onclick => onclick) end + + def css_journal_classes(journal) + s = 'journal' + s << ' has-notes' unless journal.notes.blank? + s << ' has-details' unless journal.details.blank? + s + end end diff --git a/app/views/issues/_history.rhtml b/app/views/issues/_history.rhtml index a95cbf81c..4ea2dd2ac 100644 --- a/app/views/issues/_history.rhtml +++ b/app/views/issues/_history.rhtml @@ -1,6 +1,6 @@ <% reply_links = authorize_for('issues', 'edit') -%> <% for journal in journals %> -
    +

    <%= avatar(journal.user, :size => "24") %> <%= content_tag('a', '', :name => "note-#{journal.indice}")%> From c799d03eceab362bac7e99313a255f6569c9756a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Fri, 3 Sep 2010 20:16:00 +0000 Subject: [PATCH 07/49] Added missing tests for User#allowed_to? #6291 git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4058 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- test/unit/user_test.rb | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index ea40ccff6..c263eafc0 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -355,6 +355,49 @@ class UserTest < ActiveSupport::TestCase end + context "#allowed_to?" do + context "with a unique project" do + should "return false if project is archived" do + project = Project.find(1) + Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED) + assert ! @admin.allowed_to?(:view_issues, Project.find(1)) + end + + should "return false if related module is disabled" do + project = Project.find(1) + project.enabled_module_names = ["issue_tracking"] + assert @admin.allowed_to?(:add_issues, project) + assert ! @admin.allowed_to?(:view_wiki_pages, project) + end + + should "authorize nearly everything for admin users" do + project = Project.find(1) + assert ! @admin.member_of?(project) + %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p| + assert @admin.allowed_to?(p.to_sym, project) + end + end + + should "authorize normal users depending on their roles" do + project = Project.find(1) + assert @jsmith.allowed_to?(:delete_messages, project) #Manager + assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper + end + end + + context "with options[:global]" do + should "authorize if user has at least one role that has this permission" do + @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere + @anonymous = User.find(6) + assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true) + assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true) + assert @dlopper2.allowed_to?(:add_issues, nil, :global => true) + assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true) + assert @anonymous.allowed_to?(:view_issues, nil, :global => true) + end + end + end + if Object.const_defined?(:OpenID) def test_setting_identity_url From a9f5a17c678d6ebc495d42d858db0ce5727a5381 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Fri, 3 Sep 2010 21:43:07 +0000 Subject: [PATCH 08/49] Do not display items without valid selection in context menu when on different projects. #4998 git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4059 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/context_menus_controller.rb | 4 ++-- app/views/context_menus/issues.html.erb | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/controllers/context_menus_controller.rb b/app/controllers/context_menus_controller.rb index e437a18dc..5f4b02ca2 100644 --- a/app/controllers/context_menus_controller.rb +++ b/app/controllers/context_menus_controller.rb @@ -13,8 +13,8 @@ class ContextMenusController < ApplicationController memo & s end end - projects = @issues.collect(&:project).compact.uniq - @project = projects.first if projects.size == 1 + @projects = @issues.collect(&:project).compact.uniq + @project = @projects.first if @projects.size == 1 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)), :log_time => (@project && User.current.allowed_to?(:log_time, @project)), diff --git a/app/views/context_menus/issues.html.erb b/app/views/context_menus/issues.html.erb index efd8cde0e..94d4e802f 100644 --- a/app/views/context_menus/issues.html.erb +++ b/app/views/context_menus/issues.html.erb @@ -32,6 +32,8 @@ <% end %> + + <% if @projects.size == 1 %>
  • <%= l(:field_priority) %>
      @@ -41,6 +43,8 @@ <% end -%>
  • + <% end %> + <% unless @project.nil? || @project.shared_versions.open.empty? -%>
  • <%= l(:field_fixed_version) %> @@ -80,7 +84,8 @@
  • <% end -%> - <% if Issue.use_field_for_done_ratio? %> + + <% if Issue.use_field_for_done_ratio? && @projects.size == 1 %>
  • <%= l(:field_done_ratio) %>
      @@ -91,6 +96,7 @@
  • <% end %> + <% if !@issue.nil? %> <% if @can[:log_time] -%>
  • <%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, From c5071cd091ae5eda0dea5e9ba23a42b58462a117 Mon Sep 17 00:00:00 2001 From: Azamat Hackimov Date: Sun, 5 Sep 2010 11:33:08 +0000 Subject: [PATCH 09/49] Translation updates: * ca (#6263) * nl (#6248) git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4060 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- config/locales/ca.yml | 235 +++++++++--------- config/locales/nl.yml | 36 +-- .../javascripts/calendar/lang/calendar-ca.js | 22 +- 3 files changed, 150 insertions(+), 143 deletions(-) diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 80d857e9e..b5c28d98e 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1,4 +1,8 @@ +# Redmine catalan translation: +# by Joan Duran + ca: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) direction: ltr date: formats: @@ -65,6 +69,7 @@ ca: other: "almost {{count}} years" number: + # Default format for numbers format: separator: "." delimiter: "" @@ -83,6 +88,7 @@ ca: mb: "MB" gb: "GB" tb: "TB" + # Used in array.to_sentence. support: @@ -116,6 +122,7 @@ ca: greater_than_start_date: "ha de ser superior que la data inicial" not_same_project: "no pertany al mateix projecte" circular_dependency: "Aquesta relació crearia una dependència circular" + cant_link_an_issue_with_a_descendant: "Un assumpte no es pot enllaçar a una de les seves subtasques" actionview_instancetag_blank_option: Seleccioneu @@ -149,18 +156,33 @@ ca: notice_email_sent: "S'ha enviat un correu electrònic a {{value}}" notice_email_error: "S'ha produït un error en enviar el correu ({{value}})" notice_feeds_access_key_reseted: "S'ha reiniciat la clau d'accés del RSS." + notice_api_access_key_reseted: "S'ha reiniciat la clau d'accés a l'API." notice_failed_to_save_issues: "No s'han pogut desar %s assumptes de {{count}} seleccionats: {{ids}}." + notice_failed_to_save_members: "No s'han pogut desar els membres: {{errors}}." notice_no_issue_selected: "No s'ha seleccionat cap assumpte. Activeu els assumptes que voleu editar." notice_account_pending: "S'ha creat el compte i ara està pendent de l'aprovació de l'administrador." notice_default_data_loaded: "S'ha carregat correctament la configuració predeterminada." notice_unable_delete_version: "No s'ha pogut suprimir la versió." + notice_unable_delete_time_entry: "No s'ha pogut suprimir l'entrada del registre de temps." + notice_issue_done_ratios_updated: "S'ha actualitzat el tant per cent dels assumptes." error_can_t_load_default_data: "No s'ha pogut carregar la configuració predeterminada: {{value}} " error_scm_not_found: "No s'ha trobat l'entrada o la revisió en el dipòsit." error_scm_command_failed: "S'ha produït un error en intentar accedir al dipòsit: {{value}}" error_scm_annotate: "L'entrada no existeix o no s'ha pogut anotar." error_issue_not_found_in_project: "No s'ha trobat l'assumpte o no pertany a aquest projecte" - + error_no_tracker_in_project: "Aquest projecte no té seguidor associat. Comproveu els paràmetres del projecte." + error_no_default_issue_status: "No s'ha definit cap estat d'assumpte predeterminat. Comproveu la configuració (aneu a «Administració -> Estats de l'assumpte»)." + error_can_not_delete_custom_field: "No s'ha pogut suprimir el camp personalitat" + error_can_not_delete_tracker: "Aquest seguidor conté assumptes i no es pot suprimir." + error_can_not_remove_role: "Aquest rol s'està utilitzant i no es pot suprimir." + error_can_not_reopen_issue_on_closed_version: "Un assumpte assignat a una versió tancada no es pot tornar a obrir" + error_can_not_archive_project: "Aquest projecte no es pot arxivar" + error_issue_done_ratios_not_updated: "No s'ha actualitza el tant per cent dels assumptes." + error_workflow_copy_source: "Seleccioneu un seguidor o rol font" + error_workflow_copy_target: "Seleccioneu seguidors i rols objectiu" + error_unable_delete_issue_status: "No s'ha pogut suprimir l'estat de l'assumpte" + error_unable_to_connect: "No s'ha pogut connectar ({{value}})" warning_attachments_not_saved: "No s'han pogut desar {{count}} fitxers." mail_subject_lost_password: "Contrasenya de {{value}}" @@ -173,6 +195,10 @@ ca: mail_body_account_activation_request: "S'ha registrat un usuari nou ({{value}}). El seu compte està pendent d'aprovació:" mail_subject_reminder: "{{count}} assumptes venceran els següents {{days}} dies" mail_body_reminder: "{{count}} assumptes que teniu assignades venceran els següents {{days}} dies:" + mail_subject_wiki_content_added: "S'ha afegit la pàgina wiki «{{page}}»" + mail_body_wiki_content_added: "En {{author}} ha afegit la pàgina wiki «{{page}}»." + mail_subject_wiki_content_updated: "S'ha actualitzat la pàgina wiki «{{page}}»" + mail_body_wiki_content_updated: "En {{author}} ha actualitzat la pàgina wiki «{{page}}»." gui_validation_error: 1 error gui_validation_error_plural: "{{count}} errors" @@ -212,6 +238,7 @@ ca: field_priority: Prioritat field_fixed_version: Versió objectiu field_user: Usuari + field_principal: Principal field_role: Rol field_homepage: Pàgina web field_is_public: Públic @@ -256,6 +283,7 @@ ca: field_redirect_existing_links: Redirigeix els enllaços existents field_estimated_hours: Temps previst field_column_names: Columnes + field_time_entries: "Registre de temps" field_time_zone: Zona horària field_searchable: Es pot cercar field_default_value: Valor predeterminat @@ -265,6 +293,9 @@ ca: field_watcher: Vigilància field_identity_url: URL OpenID field_content: Contingut + field_group_by: "Agrupa els resultats per" + field_sharing: Compartició + field_parent_issue: "Tasca pare" setting_app_title: "Títol de l'aplicació" setting_app_subtitle: "Subtítol de l'aplicació" @@ -300,20 +331,35 @@ ca: setting_activity_days_default: "Dies a mostrar l'activitat del projecte" setting_display_subprojects_issues: "Mostra els assumptes d'un subprojecte en el projecte pare per defecte" setting_enabled_scm: "Habilita l'SCM" + setting_mail_handler_body_delimiters: "Trunca els correus electrònics després d'una d'aquestes línies" setting_mail_handler_api_enabled: "Habilita el WS per correus electrònics d'entrada" setting_mail_handler_api_key: Clau API setting_sequential_project_identifiers: Genera identificadors de projecte seqüencials setting_gravatar_enabled: "Utilitza les icones d'usuari Gravatar" + setting_gravatar_default: "Imatge Gravatar predeterminada" setting_diff_max_lines_displayed: Número màxim de línies amb diferències mostrades setting_file_max_size_displayed: Mida màxima dels fitxers de text mostrats en línia setting_repository_log_display_limit: Número màxim de revisions que es mostren al registre de fitxers setting_openid: "Permet entrar i registrar-se amb l'OpenID" - + setting_password_min_length: "Longitud mínima de la contrasenya" + setting_new_project_user_role_id: "Aquest rol es dóna a un usuari no administrador per a crear projectes" + setting_default_projects_modules: "Mòduls activats per defecte en els projectes nous" + setting_issue_done_ratio: "Calcula tant per cent realitzat de l'assumpte amb" + setting_issue_done_ratio_issue_status: "Utilitza l'estat de l'assumpte" + setting_issue_done_ratio_issue_field: "Utilitza el camp de l'assumpte" + setting_start_of_week: "Inicia les setmanes en" + setting_rest_api_enabled: "Habilita el servei web REST" + setting_cache_formatted_text: Cache formatted text + + permission_add_project: "Crea projectes" + permission_add_subprojects: "Crea subprojectes" permission_edit_project: Edita el projecte permission_select_project_modules: Selecciona els mòduls del projecte permission_manage_members: Gestiona els membres + permission_manage_project_activities: "Gestiona les activitats del projecte" permission_manage_versions: Gestiona les versions permission_manage_categories: Gestiona les categories dels assumptes + permission_view_issues: "Visualitza els assumptes" permission_add_issues: Afegeix assumptes permission_edit_issues: Edita els assumptes permission_manage_issue_relations: Gestiona les relacions dels assumptes @@ -328,6 +374,7 @@ ca: permission_view_calendar: Visualitza el calendari permission_view_issue_watchers: Visualitza la llista de vigilàncies permission_add_issue_watchers: Afegeix vigilàncies + permission_delete_issue_watchers: Suprimeix els vigilants permission_log_time: Registra el temps invertit permission_view_time_entries: Visualitza el temps invertit permission_edit_time_entries: Edita els registres de temps @@ -357,6 +404,8 @@ ca: permission_edit_own_messages: Edita els missatges propis permission_delete_messages: Suprimeix els missatges permission_delete_own_messages: Suprimeix els missatges propis + permission_export_wiki_pages: "Exporta les pàgines wiki" + permission_manage_subtasks: "Gestiona subtasques" project_module_issue_tracking: "Seguidor d'assumptes" project_module_time_tracking: Seguidor de temps @@ -366,10 +415,13 @@ ca: project_module_wiki: Wiki project_module_repository: Dipòsit project_module_boards: Taulers + project_module_calendar: Calendari + project_module_gantt: Gantt label_user: Usuari label_user_plural: Usuaris label_user_new: Usuari nou + label_user_anonymous: Anònim label_project: Projecte label_project_new: Projecte nou label_project_plural: Projectes @@ -416,12 +468,13 @@ ca: label_information_plural: Informació label_please_login: Entreu label_register: Registre - label_login_with_open_id_option: o entra amb l'OpenID + label_login_with_open_id_option: "o entra amb l'OpenID" label_password_lost: Contrasenya perduda label_home: Inici label_my_page: La meva pàgina label_my_account: El meu compte label_my_projects: Els meus projectes + label_my_page_block: "Els meus blocs de pàgina" label_administration: Administració label_login: Entra label_logout: Surt @@ -441,6 +494,7 @@ ca: label_auth_source_new: "Mode d'autenticació nou" label_auth_source_plural: "Modes d'autenticació" label_subproject_plural: Subprojectes + label_subproject_new: "Subprojecte nou" label_and_its_subprojects: "{{value}} i els seus subprojectes" label_min_max_length: Longitud mín - max label_list: Llist @@ -475,8 +529,9 @@ ca: label_version: Versió label_version_new: Versió nova label_version_plural: Versions + label_close_versions: "Tanca les versions completades" label_confirmation: Confirmació - label_export_to: 'També disponible a:' + label_export_to: "També disponible a:" label_read: Llegeix... label_public_projects: Projectes públics label_open_issues: obert @@ -533,6 +588,8 @@ ca: label_not_equals: no és label_in_less_than: en menys de label_in_more_than: en més de + label_greater_or_equal: ">=" + label_less_or_equal: <= label_in: en label_today: avui label_all_time: tot el temps @@ -555,17 +612,21 @@ ca: label_browse: Navega label_modification: "{{count}} canvi" label_modification_plural: "{{count}} canvis" + label_branch: Branca + label_tag: Etiqueta label_revision: Revisió label_revision_plural: Revisions + label_revision_id: "Revisió {{value}}" label_associated_revisions: Revisions associades label_added: afegit label_modified: modificat - label_renamed: reanomenat label_copied: copiat + label_renamed: reanomenat label_deleted: suprimit label_latest_revision: Última revisió label_latest_revision_plural: Últimes revisions label_view_revisions: Visualitza les revisions + label_view_all_revisions: "Visualitza totes les revisions" label_max_size: Mida màxima label_sort_highest: Mou a la part superior label_sort_higher: Mou cap amunt @@ -591,6 +652,7 @@ ca: label_changes_details: Detalls de tots els canvis label_issue_tracking: "Seguiment d'assumptes" label_spent_time: Temps invertit + label_overall_spent_time: "Temps total invertit" label_f_hour: "{{value}} hora" label_f_hour_plural: "{{value}} hores" label_time_tracking: Temps de seguiment @@ -628,6 +690,8 @@ ca: label_board: Fòrum label_board_new: Fòrum nou label_board_plural: Fòrums + label_board_locked: Bloquejat + label_board_sticky: Sticky label_topic_plural: Temes label_message_plural: Missatges label_message_last: Últim missatge @@ -643,6 +707,8 @@ ca: label_language_based: "Basat en l'idioma de l'usuari" label_sort_by: "Ordena per {{value}}" label_send_test_email: Envia un correu electrònic de prova + label_feeds_access_key: "Clau d'accés del RSS" + label_missing_feeds_access_key: "Falta una clau d'accés del RSS" label_feeds_access_key_created_on: "Clau d'accés del RSS creada fa {{value}}" label_module_plural: Mòduls label_added_time_by: "Afegit per {{author}} fa {{age}}" @@ -688,6 +754,28 @@ ca: label_ascending: Ascendent label_descending: Descendent label_date_from_to: Des de {{start}} a {{end}} + label_wiki_content_added: "S'ha afegit la pàgina wiki" + label_wiki_content_updated: "S'ha actualitzat la pàgina wiki" + label_group: Grup + label_group_plural: Grups + label_group_new: Grup nou + label_time_entry_plural: Temps invertit + label_version_sharing_hierarchy: "Amb la jerarquia del projecte" + label_version_sharing_system: "Amb tots els projectes" + label_version_sharing_descendants: "Amb tots els subprojectes" + label_version_sharing_tree: "Amb l'arbre del projecte" + label_version_sharing_none: "Sense compartir" + label_update_issue_done_ratios: "Actualitza el tant per cent dels assumptes realitzats" + label_copy_source: Font + label_copy_target: Objectiu + label_copy_same_as_target: "El mateix que l'objectiu" + label_display_used_statuses_only: "Mostra només els estats que utilitza aquest seguidor" + label_api_access_key: "Clau d'accés a l'API" + label_missing_api_access_key: "Falta una clau d'accés de l'API" + label_api_access_key_created_on: "Clau d'accés de l'API creada fa {{value}}" + label_profile: Perfil + label_subtask_plural: Subtasques + label_project_copy_notifications: "Envia notificacions de correu electrònic durant la còpia del projecte" button_login: Entra button_submit: Tramet @@ -709,11 +797,12 @@ ca: button_list: Llista button_view: Visualitza button_move: Mou + button_move_and_follow: "Mou i segueix" button_back: Enrere button_cancel: Cancel·la button_activate: Activa button_sort: Ordena - button_log_time: "Hora d'entrada" + button_log_time: "Registre de temps" button_rollback: Torna a aquesta versió button_watch: Vigila button_unwatch: No vigilis @@ -724,15 +813,24 @@ ca: button_rename: Reanomena button_change_password: Canvia la contrasenya button_copy: Copia + button_copy_and_follow: "Copia i segueix" button_annotate: Anota button_update: Actualitza button_configure: Configura button_quote: Cita + button_duplicate: Duplica + button_show: Mostra status_active: actiu status_registered: informat status_locked: bloquejat + version_status_open: oberta + version_status_locked: bloquejada + version_status_closed: tancada + + field_active: Actiu + text_select_mail_notifications: "Seleccioneu les accions per les quals s'hauria d'enviar una notificació per correu electrònic." text_regexp_info: ex. ^[A-Z0-9]+$ text_min_max_length_info: 0 significa sense restricció @@ -740,6 +838,10 @@ ca: text_subprojects_destroy_warning: "També seran suprimits els seus subprojectes: {{value}}." text_workflow_edit: Seleccioneu un rol i un seguidor per a editar el flux de treball text_are_you_sure: Segur? + text_journal_changed: "{{label}} ha canviat de {{old}} a {{new}}" + text_journal_set_to: "{{label}} s'ha establert a {{value}}" + text_journal_deleted: "{{label}} s'ha suprimit ({{old}})" + text_journal_added: "S'ha afegit {{label}} {{value}}" text_tip_task_begin_day: "tasca que s'inicia aquest dia" text_tip_task_end_day: tasca que finalitza aquest dia text_tip_task_begin_end_day: "tasca que s'inicia i finalitza aquest dia" @@ -750,6 +852,7 @@ ca: text_tracker_no_workflow: "No s'ha definit cap flux de treball per a aquest seguidor" text_unallowed_characters: Caràcters no permesos text_comma_separated: Es permeten valors múltiples (separats per una coma). + text_line_separated: "Es permeten diversos valors (una línia per cada valor)." text_issues_ref_in_commit_messages: Referència i soluciona els assumptes en els missatges publicats text_issue_added: "L'assumpte {{id}} ha sigut informat per {{author}}." text_issue_updated: "L'assumpte {{id}} ha sigut actualitzat per {{author}}." @@ -770,14 +873,21 @@ ca: text_destroy_time_entries_question: "S'han informat {{hours}} hores en els assumptes que aneu a suprimir. Què voleu fer?" text_destroy_time_entries: Suprimeix les hores informades text_assign_time_entries_to_project: Assigna les hores informades al projecte - text_reassign_time_entries: 'Torna a assignar les hores informades a aquest assumpte:' + text_reassign_time_entries: "Torna a assignar les hores informades a aquest assumpte:" text_user_wrote: "{{value}} va escriure:" text_enumeration_destroy_question: "{{count}} objectes estan assignats a aquest valor." - text_enumeration_category_reassign_to: 'Torna a assignar-los a aquest valor:' + text_enumeration_category_reassign_to: "Torna a assignar-los a aquest valor:" text_email_delivery_not_configured: "El lliurament per correu electrònic no està configurat i les notificacions estan inhabilitades.\nConfigureu el servidor SMTP a config/email.yml i reinicieu l'aplicació per habilitar-lo." text_repository_usernames_mapping: "Seleccioneu l'assignació entre els usuaris del Redmine i cada nom d'usuari trobat al dipòsit.\nEls usuaris amb el mateix nom d'usuari o correu del Redmine i del dipòsit s'assignaran automàticament." text_diff_truncated: "... Aquestes diferències s'han trucat perquè excedeixen la mida màxima que es pot mostrar." - text_custom_field_possible_values_info: 'Una línia per a cada valor' + text_custom_field_possible_values_info: "Una línia per a cada valor" + text_wiki_page_destroy_question: "Aquesta pàgina té {{descendants}} pàgines fill i descendents. Què voleu fer?" + text_wiki_page_nullify_children: "Deixa les pàgines fill com a pàgines arrel" + text_wiki_page_destroy_children: "Suprimeix les pàgines fill i tots els seus descendents" + text_wiki_page_reassign_children: "Reasigna les pàgines fill a aquesta pàgina pare" + text_own_membership_delete_confirmation: "Esteu a punt de suprimir algun o tots els vostres permisos i potser no podreu editar més aquest projecte.\nSegur que voleu continuar?" + text_zoom_in: Redueix + text_zoom_out: Amplia default_role_manager: Gestor default_role_developer: Desenvolupador @@ -804,108 +914,5 @@ ca: enumeration_issue_priorities: Prioritat dels assumptes enumeration_doc_categories: Categories del document enumeration_activities: Activitats (seguidor de temps) - label_greater_or_equal: ">=" - label_less_or_equal: <= - text_wiki_page_destroy_question: This page has {{descendants}} child page(s) and descendant(s). What do you want to do? - text_wiki_page_reassign_children: Reassign child pages to this parent page - text_wiki_page_nullify_children: Keep child pages as root pages - text_wiki_page_destroy_children: Delete child pages and all their descendants - setting_password_min_length: Minimum password length - field_group_by: Group results by - mail_subject_wiki_content_updated: "'{{page}}' wiki page has been updated" - label_wiki_content_added: Wiki page added - mail_subject_wiki_content_added: "'{{page}}' wiki page has been added" - mail_body_wiki_content_added: The '{{page}}' wiki page has been added by {{author}}. - label_wiki_content_updated: Wiki page updated - mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. - permission_add_project: Create project - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - label_view_all_revisions: View all revisions - label_tag: Tag - label_branch: Branch - error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings. - error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses"). - text_journal_changed: "{{label}} changed from {{old}} to {{new}}" - text_journal_set_to: "{{label}} set to {{value}}" - text_journal_deleted: "{{label}} deleted ({{old}})" - label_group_plural: Groups - label_group: Group - label_group_new: New group - label_time_entry_plural: Spent time - text_journal_added: "{{label}} {{value}} added" - field_active: Active - enumeration_system_activity: System Activity - permission_delete_issue_watchers: Delete watchers - version_status_closed: closed - version_status_locked: locked - version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened - label_user_anonymous: Anonymous - button_move_and_follow: Move and follow - setting_default_projects_modules: Default enabled modules for new projects - setting_gravatar_default: Default Gravatar image - field_sharing: Sharing - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_system: With all projects - label_version_sharing_descendants: With subprojects - label_version_sharing_tree: With project tree - label_version_sharing_none: Not shared - error_can_not_archive_project: This project can not be archived - button_duplicate: Duplicate - button_copy_and_follow: Copy and follow - label_copy_source: Source - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_status: Use the issue status - error_issue_done_ratios_not_updated: Issue done ratios not updated. - error_workflow_copy_target: Please select target tracker(s) and role(s) - setting_issue_done_ratio_issue_field: Use the issue field - label_copy_same_as_target: Same as target - label_copy_target: Target - notice_issue_done_ratios_updated: Issue done ratios updated. - error_workflow_copy_source: Please select a source tracker or role - label_update_issue_done_ratios: Update issue done ratios - setting_start_of_week: Start calendars on - permission_view_issues: View Issues - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_revision_id: Revision {{value}} - label_api_access_key: API access key - label_api_access_key_created_on: API access key created {{value}} ago - label_feeds_access_key: RSS access key - notice_api_access_key_reseted: Your API access key was reset. - setting_rest_api_enabled: Enable REST web service - label_missing_api_access_key: Missing an API access key - label_missing_feeds_access_key: Missing a RSS access key - button_show: Show - text_line_separated: Multiple values allowed (one line for each value). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines - permission_add_subprojects: Create subprojects - label_subproject_new: New subproject - text_own_membership_delete_confirmation: |- - You are about to remove some or all of your permissions and may no longer be able to edit this project after that. - Are you sure you want to continue? - label_close_versions: Close completed versions - label_board_sticky: Sticky - label_board_locked: Locked - permission_export_wiki_pages: Export wiki pages - setting_cache_formatted_text: Cache formatted text - permission_manage_project_activities: Manage project activities - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect ({{value}}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): {{errors}}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar + enumeration_system_activity: Activitat del sistema + diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 1e1489940..6dfe989b5 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -701,7 +701,7 @@ nl: setting_date_format: Datumformaat setting_default_language: Standaard taal setting_default_projects_public: Nieuwe projecten zijn standaard publiek - setting_diff_max_lines_displayed: Max number of diff lines displayed + setting_diff_max_lines_displayed: Max aantal diff regels weer te geven setting_display_subprojects_issues: Standaard issues van subproject tonen setting_emails_footer: E-mails footer setting_enabled_scm: SCM ingeschakeld @@ -709,7 +709,7 @@ nl: setting_gravatar_enabled: Gebruik Gravatar gebruikersiconen setting_host_name: Hostnaam setting_issue_list_default_columns: Standaardkolommen getoond op de lijst met issues - setting_issues_export_limit: Limiet export issues + setting_issues_export_limit: Max aantal te exporteren issues setting_login_required: Authenticatie vereist setting_mail_from: Afzender e-mail adres setting_mail_handler_api_enabled: Schakel WS in voor inkomende mail. @@ -727,7 +727,7 @@ nl: setting_welcome_text: Welkomsttekst setting_wiki_compression: Wikigeschiedenis comprimeren status_active: actief - status_locked: gelockt + status_locked: vergrendeld status_registered: geregistreerd text_are_you_sure: Weet u het zeker? text_assign_time_entries_to_project: Gerapporteerde uren toevoegen aan dit project @@ -753,7 +753,7 @@ nl: text_load_default_configuration: Laad de standaardconfiguratie text_min_max_length_info: 0 betekent geen restrictie text_no_configuration_data: "Rollen, trackers, issue statussen en workflows zijn nog niet geconfigureerd.\nHet is ten zeerste aangeraden om de standaard configuratie in te laden. U kunt deze aanpassen nadat deze is ingeladen." - text_plugin_assets_writable: Plugin assets directory writable + text_plugin_assets_writable: Plugin assets directory beschrijfbaar text_project_destroy_confirmation: Weet u zeker dat u dit project en alle gerelateerde gegevens wilt verwijderen? text_project_identifier_info: 'kleine letters (a-z), cijfers en liggende streepjes toegestaan.
    Eenmaal bewaard kan de identificatiecode niet meer worden gewijzigd.' text_reassign_time_entries: 'Gerapporteerde uren opnieuw toewijzen:' @@ -778,7 +778,7 @@ nl: text_custom_field_possible_values_info: 'Per lijn een waarde' label_display: Toon field_editable: Bewerkbaar - setting_repository_log_display_limit: Maximum hoeveelheid van revisies zichbaar + setting_repository_log_display_limit: Max aantal revisies zichbaar setting_file_max_size_displayed: Max grootte van tekst bestanden inline zichtbaar field_watcher: Watcher setting_openid: Sta OpenID login en registratie toe @@ -824,7 +824,7 @@ nl: version_status_closed: gesloten version_status_locked: vergrendeld version_status_open: open - error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened + error_can_not_reopen_issue_on_closed_version: Een issue toegewezen aan een gesloten versie kan niet heropend worden label_user_anonymous: Anoniem button_move_and_follow: Verplaats en volg setting_default_projects_modules: Standaard geactiveerde modules voor nieuwe projecten @@ -833,36 +833,36 @@ nl: label_version_sharing_hierarchy: Met project hiërarchie label_version_sharing_system: Met alle projecten label_version_sharing_descendants: Met subprojecten - label_version_sharing_tree: With project tree + label_version_sharing_tree: Met project boom label_version_sharing_none: Niet gedeeld error_can_not_archive_project: Dit project kan niet worden gearchiveerd button_duplicate: Dupliceer button_copy_and_follow: Kopiëer en volg label_copy_source: Bron - setting_issue_done_ratio: Bereken issue done ratio met + setting_issue_done_ratio: Bereken issue percentage voldaan met setting_issue_done_ratio_issue_status: Gebruik de issue status - error_issue_done_ratios_not_updated: Issue done ratios niet geupdate. + error_issue_done_ratios_not_updated: Issue percentage voldaan niet geupdate. error_workflow_copy_target: Selecteer tracker(s) en rol(len) setting_issue_done_ratio_issue_field: Gebruik het issue veld label_copy_same_as_target: Zelfde als doel label_copy_target: Doel - notice_issue_done_ratios_updated: Issue done ratios updated. + notice_issue_done_ratios_updated: Issue percentage voldaan geupdate. error_workflow_copy_source: Selecteer een bron tracker of rol - label_update_issue_done_ratios: Update issue done ratios + label_update_issue_done_ratios: Update issue percentage voldaan setting_start_of_week: Week begint op permission_view_issues: Bekijk Issues label_display_used_statuses_only: Laat alleen statussen zien die gebruikt worden door deze tracker - label_revision_id: Revision {{value}} + label_revision_id: Revisie {{value}} label_api_access_key: API access key label_api_access_key_created_on: API access key gemaakt {{value}} geleden label_feeds_access_key: RSS access key notice_api_access_key_reseted: Uw API access key was gereset. - setting_rest_api_enabled: Enable REST web service + setting_rest_api_enabled: Activeer REST web service label_missing_api_access_key: Geen API access key label_missing_feeds_access_key: Geen RSS access key button_show: Laat zien text_line_separated: Meerdere waarden toegestaan (elke regel is een waarde). - setting_mail_handler_body_delimiters: Truncate emails after one of these lines + setting_mail_handler_body_delimiters: Breek email verwerking af na een van deze regels permission_add_subprojects: Maak subprojecten label_subproject_new: Nieuw subproject text_own_membership_delete_confirmation: |- @@ -873,10 +873,10 @@ nl: label_board_locked: Vergrendeld permission_export_wiki_pages: Exporteer wiki pagina's setting_cache_formatted_text: Cache opgemaakte tekst - permission_manage_project_activities: Manage project activities + permission_manage_project_activities: Beheer project activiteiten error_unable_delete_issue_status: Verwijderen van issue status niet gelukt label_profile: Profiel - permission_manage_subtasks: Manage subtasks + permission_manage_subtasks: Beheer subtasks field_parent_issue: Parent task label_subtask_plural: Subtasks label_project_copy_notifications: Stuur email notificaties voor de project kopie @@ -891,6 +891,6 @@ nl: text_zoom_in: Zoom in notice_unable_delete_time_entry: Verwijderen niet mogelijk van tijd log invoer. label_overall_spent_time: Totaal bestede tijd - field_time_entries: Log time + field_time_entries: Log tijd project_module_gantt: Gantt - project_module_calendar: Calendar + project_module_calendar: Kalender diff --git a/public/javascripts/calendar/lang/calendar-ca.js b/public/javascripts/calendar/lang/calendar-ca.js index 303f21dfd..9902680e5 100644 --- a/public/javascripts/calendar/lang/calendar-ca.js +++ b/public/javascripts/calendar/lang/calendar-ca.js @@ -45,7 +45,7 @@ Calendar._SDN = new Array // First day of the week. "0" means display Sunday first, "1" means display // Monday first, etc. -Calendar._FD = 0; +Calendar._FD = 1; // full month names Calendar._MN = new Array @@ -84,17 +84,17 @@ Calendar._TT["INFO"] = "Quant al calendari"; Calendar._TT["ABOUT"] = "Selector DHTML de data/hora\n" + "(c) dynarch.com 2002-2005 / Autor: Mihai Bazon\n" + // don't translate this this ;-) -"Per a aconseguir l'última versió visiteu: http://www.dynarch.com/projects/calendar/\n" + -"Distribuït sota la llicència GNU LGPL. Vegeu http://gnu.org/licenses/lgpl.html per a més detalls." + +"Per aconseguir l'última versió visiteu: http://www.dynarch.com/projects/calendar/\n" + +"Distribuït sota la llicència GNU LGPL. Vegeu http://gnu.org/licenses/lgpl.html per obtenir més detalls." + "\n\n" + "Selecció de la data:\n" + -"- Utilitzeu els botons \xab, \xbb per a seleccionar l'any\n" + -"- Utilitzeu els botons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " per a selecciona el mes\n" + -"- Mantingueu premut el botó del ratolí sobre qualsevol d'aquests botons per a uns selecció més ràpida."; +"- Utilitzeu els botons \xab, \xbb per seleccionar l'any\n" + +"- Utilitzeu els botons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " per seleccionar el mes\n" + +"- Mantingueu premut el botó del ratolí sobre qualsevol d'aquests botons per a una selecció més ràpida."; Calendar._TT["ABOUT_TIME"] = "\n\n" + "Selecció de l'hora:\n" + -"- Feu clic en qualsevol part de l'hora per a incrementar-la\n" + -"- o premeu majúscules per a disminuir-la\n" + +"- Feu clic en qualsevol part de l'hora per incrementar-la\n" + +"- o premeu majúscules per disminuir-la\n" + "- o feu clic i arrossegueu per a una selecció més ràpida."; Calendar._TT["PREV_YEAR"] = "Any anterior (mantenir per menú)"; @@ -102,8 +102,8 @@ Calendar._TT["PREV_MONTH"] = "Mes anterior (mantenir per menú)"; Calendar._TT["GO_TODAY"] = "Anar a avui"; Calendar._TT["NEXT_MONTH"] = "Mes següent (mantenir per menú)"; Calendar._TT["NEXT_YEAR"] = "Any següent (mantenir per menú)"; -Calendar._TT["SEL_DATE"] = "Sel·lecciona data"; -Calendar._TT["DRAG_TO_MOVE"] = "Arrossega per a moure"; +Calendar._TT["SEL_DATE"] = "Sel·lecciona la data"; +Calendar._TT["DRAG_TO_MOVE"] = "Arrossega per moure"; Calendar._TT["PART_TODAY"] = " (avui)"; // the following is to inform that "%s" is to be the first day of week @@ -117,7 +117,7 @@ Calendar._TT["WEEKEND"] = "0,6"; Calendar._TT["CLOSE"] = "Tanca"; Calendar._TT["TODAY"] = "Avui"; -Calendar._TT["TIME_PART"] = "(Majúscules-)Feu clic o arrossegueu per a canviar el valor"; +Calendar._TT["TIME_PART"] = "(Majúscules-)Feu clic o arrossegueu per canviar el valor"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; From cbe266079e9105c61bbda84c5cef89927548fcfe Mon Sep 17 00:00:00 2001 From: Azamat Hackimov Date: Sun, 5 Sep 2010 13:01:38 +0000 Subject: [PATCH 10/49] Added string "cant_link_an_issue_with_a_descendant" to translations missed in #443 Solves #6278 git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4061 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- config/locales/bg.yml | 1 + config/locales/bs.yml | 1 + config/locales/cs.yml | 1 + config/locales/da.yml | 1 + config/locales/el.yml | 1 + config/locales/en-GB.yml | 1 + config/locales/es.yml | 1 + config/locales/eu.yml | 1 + config/locales/fi.yml | 2 +- config/locales/gl.yml | 1 + config/locales/he.yml | 1 + config/locales/hr.yml | 1 + config/locales/hu.yml | 1 + config/locales/id.yml | 1 + config/locales/it.yml | 1 + config/locales/ja.yml | 1 + config/locales/ko.yml | 1 + config/locales/lt.yml | 3 ++- config/locales/lv.yml | 1 + config/locales/mn.yml | 1 + config/locales/nl.yml | 1 + config/locales/no.yml | 1 + config/locales/pl.yml | 1 + config/locales/pt-BR.yml | 1 + config/locales/pt.yml | 1 + config/locales/ro.yml | 1 + config/locales/ru.yml | 7 ++++--- config/locales/sk.yml | 1 + config/locales/sl.yml | 1 + config/locales/sv.yml | 1 + config/locales/th.yml | 1 + config/locales/tr.yml | 1 + config/locales/uk.yml | 1 + config/locales/vi.yml | 1 + config/locales/zh-TW.yml | 1 + config/locales/zh.yml | 1 + 36 files changed, 40 insertions(+), 5 deletions(-) diff --git a/config/locales/bg.yml b/config/locales/bg.yml index d4fd50c93..f334633e2 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -116,6 +116,7 @@ bg: greater_than_start_date: "трябва да е след началната дата" not_same_project: "не е от същия проект" circular_dependency: "Тази релация ще доведе до безкрайна зависимост" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Изберете diff --git a/config/locales/bs.yml b/config/locales/bs.yml index 6d729b0ce..90ec9dea2 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -130,6 +130,7 @@ bs: greater_than_start_date: "mora biti veći nego početni datum" not_same_project: "ne pripada istom projektu" circular_dependency: "Ova relacija stvar cirkularnu zavisnost" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Molimo odaberite diff --git a/config/locales/cs.yml b/config/locales/cs.yml index f1abeff5b..f2b114605 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -116,6 +116,7 @@ cs: greater_than_start_date: "musí být větší než počáteční datum" not_same_project: "nepatří stejnému projektu" circular_dependency: "Tento vztah by vytvořil cyklickou závislost" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" # Updated by Josef Liška # CZ translation by Maxim Krušina | Massimo Filippi, s.r.o. | maxim@mxm.cz diff --git a/config/locales/da.yml b/config/locales/da.yml index 45b33d810..4d30dc6f3 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -130,6 +130,7 @@ da: greater_than_start_date: "skal være senere end startdatoen" not_same_project: "hører ikke til samme projekt" circular_dependency: "Denne relation vil skabe et afhængighedsforhold" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" template: header: diff --git a/config/locales/el.yml b/config/locales/el.yml index d9e224e9f..e4487c59f 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -119,6 +119,7 @@ el: greater_than_start_date: "πρέπει να είναι αργότερα από την ημερομηνία έναρξης" not_same_project: "δεν ανήκει στο ίδιο έργο" circular_dependency: "Αυτή η σχέση θα δημιουργήσει κυκλικές εξαρτήσεις" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Παρακαλώ επιλέξτε diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index bc4cb08e8..6a8faa746 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -122,6 +122,7 @@ en-GB: greater_than_start_date: "must be greater than start date" not_same_project: "doesn't belong to the same project" circular_dependency: "This relation would create a circular dependency" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Please select diff --git a/config/locales/es.yml b/config/locales/es.yml index 4783db721..4ffdaf3f6 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -132,6 +132,7 @@ es: greater_than_start_date: "debe ser posterior a la fecha de comienzo" not_same_project: "no pertenece al mismo proyecto" circular_dependency: "Esta relación podría crear una dependencia circular" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" # Append your own errors here or at the model/attributes scope. diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 80fcc63d0..236c5bb7d 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -121,6 +121,7 @@ eu: greater_than_start_date: "hasiera data baino handiagoa izan behar du" not_same_project: "ez dago proiektu berdinean" circular_dependency: "Erlazio honek mendekotasun zirkular bat sortuko luke" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Hautatu mesedez diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 3bde5975b..9aeee5f6e 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -147,7 +147,7 @@ fi: greater_than_start_date: "tulee olla aloituspäivän jälkeinen" not_same_project: "ei kuulu samaan projektiin" circular_dependency: "Tämä suhde loisi kehän." - + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Valitse, ole hyvä diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 7daebd074..db64fabdc 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -150,6 +150,7 @@ gl: greater_than_start_date: "debe ser posterior á data de comezo" not_same_project: "non pertence ao mesmo proxecto" circular_dependency: "Esta relación podería crear unha dependencia circular" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Por favor seleccione diff --git a/config/locales/he.yml b/config/locales/he.yml index 59fa6888d..50f31c294 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -123,6 +123,7 @@ he: greater_than_start_date: "חייב להיות מאוחר יותר מתאריך ההתחלה" not_same_project: "לא שייך לאותו הפרויקט" circular_dependency: "הקשר הזה יצור תלות מעגלית" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: בחר בבקשה diff --git a/config/locales/hr.yml b/config/locales/hr.yml index 1c6d523c3..5727a629e 100644 --- a/config/locales/hr.yml +++ b/config/locales/hr.yml @@ -117,6 +117,7 @@ hr: greater_than_start_date: "mora biti veci nego pocetni datum" not_same_project: "ne pripada istom projektu" circular_dependency: "Ovaj relacija stvara kružnu ovisnost" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Molimo odaberite diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 974ddf76c..c895f4c96 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -143,6 +143,7 @@ greater_than_start_date: "nagyobbnak kell lennie, mint az indítás dátuma" not_same_project: "nem azonos projekthez tartozik" circular_dependency: "Ez a kapcsolat egy körkörös függőséget eredményez" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Kérem válasszon diff --git a/config/locales/id.yml b/config/locales/id.yml index 345fba454..65140c2d0 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -119,6 +119,7 @@ id: greater_than_start_date: "harus lebih besar dari tanggal mulai" not_same_project: "tidak tergabung dalam proyek yang sama" circular_dependency: "kaitan ini akan menghasilkan circular dependency" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Silakan pilih diff --git a/config/locales/it.yml b/config/locales/it.yml index f5fb21fd2..34a7820d2 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -126,6 +126,7 @@ it: greater_than_start_date: "deve essere maggiore della data di partenza" not_same_project: "non appartiene allo stesso progetto" circular_dependency: "Questa relazione creerebbe una dipendenza circolare" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Scegli diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 50ccc8729..68c4d24aa 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -144,6 +144,7 @@ ja: greater_than_start_date: "を開始日より後にしてください" not_same_project: "同じプロジェクトに属していません" circular_dependency: "この関係では、循環依存になります" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: 選んでください diff --git a/config/locales/ko.yml b/config/locales/ko.yml index b30d7518f..0a5fa44e1 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -173,6 +173,7 @@ ko: greater_than_start_date: "는 시작날짜보다 커야 합니다" not_same_project: "는 같은 프로젝트에 속해 있지 않습니다" circular_dependency: "이 관계는 순환 의존관계를 만들 수 있습니다" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: 선택하세요 diff --git a/config/locales/lt.yml b/config/locales/lt.yml index f0c9551a3..9a3684e91 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -143,7 +143,7 @@ lt: other: "Išsaugant objektą {{model}} rastos {{count}} klaidos" body: "Šiuose laukuose yra klaidų:" - pranešimus: + messages: inclusion: "nenumatyta reikšmė" exclusion: "užimtas" invalid: "neteisingas" @@ -179,6 +179,7 @@ lt: greater_than_start_date: "turi būti didesnė negu pradžios data" not_same_project: "nepriklauso tam pačiam projektui" circular_dependency: "Šis ryšys sukurtų ciklinę priklausomybę" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: prašom parinkti diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 4b2421fe4..0ec9b4f4f 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -113,6 +113,7 @@ lv: greater_than_start_date: "jābūt vēlākam par sākuma datumu" not_same_project: "nepieder pie tā paša projekta" circular_dependency: "Šī relācija radītu ciklisku atkarību" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Izvēlieties diff --git a/config/locales/mn.yml b/config/locales/mn.yml index 55d7cf168..73791bb74 100644 --- a/config/locales/mn.yml +++ b/config/locales/mn.yml @@ -117,6 +117,7 @@ mn: greater_than_start_date: "must be greater than start date" not_same_project: "нэг ижил төсөлд хамаарахгүй байна" circular_dependency: "Энэ харьцаа нь гинжин(рекурсив) харьцаа үүсгэх юм байна" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Сонгоно уу diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 6dfe989b5..c55dadb47 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -116,6 +116,7 @@ nl: greater_than_start_date: "moet na de startdatum liggen" not_same_project: "hoort niet bij hetzelfde project" circular_dependency: "Deze relatie zou een circulaire afhankelijkheid tot gevolg hebben" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Selecteer diff --git a/config/locales/no.yml b/config/locales/no.yml index 8845d12b8..b1746fd41 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -111,6 +111,7 @@ greater_than_start_date: "må være større enn startdato" not_same_project: "hører ikke til samme prosjekt" circular_dependency: "Denne relasjonen ville lagd en sirkulær avhengighet" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Vennligst velg diff --git a/config/locales/pl.yml b/config/locales/pl.yml index f36bb1277..d48b3d935 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -129,6 +129,7 @@ pl: greater_than_start_date: "musi być większe niż początkowa data" not_same_project: "nie należy do tego samego projektu" circular_dependency: "Ta relacja może wytworzyć kołową zależność" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" support: array: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index d5b54e509..c41e4e0dc 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -143,6 +143,7 @@ pt-BR: greater_than_start_date: "deve ser maior que a data inicial" not_same_project: "não pertence ao mesmo projeto" circular_dependency: "Esta relação geraria uma dependência circular" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Selecione diff --git a/config/locales/pt.yml b/config/locales/pt.yml index b2a2c08c8..3127f4044 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -128,6 +128,7 @@ pt: greater_than_start_date: "deve ser maior que a data inicial" not_same_project: "não pertence ao mesmo projecto" circular_dependency: "Esta relação iria criar uma dependência circular" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" ## Translated by: Pedro Araújo actionview_instancetag_blank_option: Seleccione diff --git a/config/locales/ro.yml b/config/locales/ro.yml index fd6e2b942..a0458932c 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -110,6 +110,7 @@ ro: greater_than_start_date: "trebuie să fie după data de început" not_same_project: "trebuie să aparțină aceluiași proiect" circular_dependency: "Această relație ar crea o dependență circulară" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Selectați diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 913c81c81..586e2fbcc 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -192,11 +192,12 @@ ru: equal_to: "может иметь лишь значение, равное {{count}}" less_than: "может иметь значение меньшее чем {{count}}" less_than_or_equal_to: "может иметь значение меньшее или равное {{count}}" - odd: "может иметь лишь четное значение" - even: "может иметь лишь нечетное значение" + odd: "может иметь лишь нечетное значение" + even: "может иметь лишь четное значение" greater_than_start_date: "должна быть позднее даты начала" - not_same_project: "не относятся к одному проекту" + not_same_project: "не относится к одному проекту" circular_dependency: "Такая связь приведет к циклической зависимости" + cant_link_an_issue_with_a_descendant: "Задача не может быть связана со своей подзадачей" support: array: diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 94facc047..52c57c79c 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -112,6 +112,7 @@ sk: greater_than_start_date: "musí byť neskôr ako počiatočný dátum" not_same_project: "nepatrí rovnakému projektu" circular_dependency: "Tento vzťah by vytvoril cyklickú závislosť" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" # SK translation by Stanislav Pach | stano.pach@seznam.cz diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 7307d0d93..8ee68f15d 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -116,6 +116,7 @@ sl: greater_than_start_date: "mora biti kasnejši kot začeten datum" not_same_project: "ne pripada istemu projektu" circular_dependency: "Ta odnos bi povzročil krožno odvisnost" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Prosimo izberite diff --git a/config/locales/sv.yml b/config/locales/sv.yml index e02ee7867..657a9adcd 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -130,6 +130,7 @@ sv: greater_than_start_date: "måste vara senare än startdatumet" not_same_project: "tillhör inte samma projekt" circular_dependency: "Denna relation skulle skapa ett cirkulärt beroende" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" direction: ltr date: diff --git a/config/locales/th.yml b/config/locales/th.yml index 01d19c4d0..67bf3c4cc 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -116,6 +116,7 @@ th: greater_than_start_date: "ต้องมากกว่าวันเริ่ม" not_same_project: "ไม่ได้อยู่ในโครงการเดียวกัน" circular_dependency: "ความสัมพันธ์อ้างอิงเป็นวงกลม" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: กรุณาเลือก diff --git a/config/locales/tr.yml b/config/locales/tr.yml index f3f9788b1..e0f23078d 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -143,6 +143,7 @@ tr: greater_than_start_date: "başlangıç tarihinden büyük olmalı" not_same_project: "aynı projeye ait değil" circular_dependency: "Bu ilişki döngüsel bağımlılık meydana getirecektir" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" models: actionview_instancetag_blank_option: Lütfen Seçin diff --git a/config/locales/uk.yml b/config/locales/uk.yml index c5846db2a..d0191618b 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -116,6 +116,7 @@ uk: greater_than_start_date: "повинна бути пізніша за дату початку" not_same_project: "не відносяться до одного проекту" circular_dependency: "Такий зв'язок приведе до циклічної залежності" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: Оберіть diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 5e964896d..66fdbc115 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -139,6 +139,7 @@ vi: greater_than_start_date: "phải đi sau ngày bắt đầu" not_same_project: "không thuộc cùng dự án" circular_dependency: "quan hệ có thể gây ra lặp vô tận" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" direction: ltr date: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 0216525b0..56d8c9b29 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -177,6 +177,7 @@ greater_than_start_date: "必須在起始日期之後" not_same_project: "不屬於同一個專案" circular_dependency: "這個關聯會導致環狀相依" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" # You can define own errors for models or model attributes. # The values :model, :attribute and :value are always available for interpolation. diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 0ffdcdb3b..a05d96cae 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -137,6 +137,7 @@ zh: greater_than_start_date: "必须在起始日期之后" not_same_project: "不属于同一个项目" circular_dependency: "此关联将导致循环依赖" + cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" actionview_instancetag_blank_option: 请选择 From db110304105a7758ee2d3be496c91d037f16425f Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 5 Sep 2010 22:57:20 +0000 Subject: [PATCH 11/49] Refactor: move method to model. (references r4057) git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4062 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/helpers/journals_helper.rb | 7 ------- app/models/journal.rb | 8 ++++++++ app/views/issues/_history.rhtml | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/helpers/journals_helper.rb b/app/helpers/journals_helper.rb index 990bfb1e5..c8d53f253 100644 --- a/app/helpers/journals_helper.rb +++ b/app/helpers/journals_helper.rb @@ -39,11 +39,4 @@ module JournalsHelper onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;" link_to text, '#', options.merge(:onclick => onclick) end - - def css_journal_classes(journal) - s = 'journal' - s << ' has-notes' unless journal.notes.blank? - s << ' has-details' unless journal.details.blank? - s - end end diff --git a/app/models/journal.rb b/app/models/journal.rb index a0e1ae877..3e846aeb8 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -65,4 +65,12 @@ class Journal < ActiveRecord::Base def attachments journalized.respond_to?(:attachments) ? journalized.attachments : nil end + + # Returns a string of css classes + def css_classes + s = 'journal' + s << ' has-notes' unless notes.blank? + s << ' has-details' unless details.blank? + s + end end diff --git a/app/views/issues/_history.rhtml b/app/views/issues/_history.rhtml index 4ea2dd2ac..4851e5f22 100644 --- a/app/views/issues/_history.rhtml +++ b/app/views/issues/_history.rhtml @@ -1,6 +1,6 @@ <% reply_links = authorize_for('issues', 'edit') -%> <% for journal in journals %> -
    +

    <%= avatar(journal.user, :size => "24") %> <%= content_tag('a', '', :name => "note-#{journal.indice}")%> From 563c879e44237eff4846ef44ae93ec43c27ba47f Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Mon, 6 Sep 2010 00:26:02 +0000 Subject: [PATCH 12/49] Use the built in Rails ActionView::TestCase for testing helpers. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4063 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- test/unit/helpers/application_helper_test.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/unit/helpers/application_helper_test.rb b/test/unit/helpers/application_helper_test.rb index 6fd21fe37..533311b1a 100644 --- a/test/unit/helpers/application_helper_test.rb +++ b/test/unit/helpers/application_helper_test.rb @@ -17,10 +17,7 @@ require File.dirname(__FILE__) + '/../../test_helper' -class ApplicationHelperTest < HelperTestCase - include ApplicationHelper - include ActionView::Helpers::TextHelper - include ActionView::Helpers::DateHelper +class ApplicationHelperTest < ActionView::TestCase fixtures :projects, :roles, :enabled_modules, :users, :repositories, :changesets, From d771fa92892a8835168856b8a148d05b550440dc Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Mon, 6 Sep 2010 00:26:08 +0000 Subject: [PATCH 13/49] Change link_to_if_authorized to allow url paths. (Fixes #6195) Both url paths (/issues/1234) and params hashes (:controller => 'issues') are now supported by link_to_if_authorized. The authorize_for method requires a controller/action pair so urls need to be parsed against the routes to find their controller/action. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4064 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/helpers/application_helper.rb | 21 +++++++++++++- test/unit/helpers/application_helper_test.rb | 29 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 19dd654db..0fb44a22b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -32,8 +32,27 @@ module ApplicationHelper end # Display a link if user is authorized + # + # @param [String] name Anchor text (passed to link_to) + # @param [Hash, String] options Hash params or url for the link target (passed to link_to). + # This will checked by authorize_for to see if the user is authorized + # @param [optional, Hash] html_options Options passed to link_to + # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) - link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) + if options.is_a?(String) + begin + route = ActionController::Routing::Routes.recognize_path(options.gsub(/\?.*/,''), :method => options[:method] || :get) + link_controller = route[:controller] + link_action = route[:action] + rescue ActionController::RoutingError # Parse failed, not a route + link_controller, link_action = nil, nil + end + else + link_controller = options[:controller] || params[:controller] + link_action = options[:action] + end + + link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(link_controller, link_action) end # Display a link to remote if user is authorized diff --git a/test/unit/helpers/application_helper_test.rb b/test/unit/helpers/application_helper_test.rb index 533311b1a..1936a981b 100644 --- a/test/unit/helpers/application_helper_test.rb +++ b/test/unit/helpers/application_helper_test.rb @@ -30,6 +30,35 @@ class ApplicationHelperTest < ActionView::TestCase def setup super end + + context "#link_to_if_authorized" do + context "authorized user" do + should "be tested" + end + + context "unauthorized user" do + should "be tested" + end + + should "allow using the :controller and :action for the target link" do + User.current = User.find_by_login('admin') + + @project = Issue.first.project # Used by helper + response = link_to_if_authorized("By controller/action", + {:controller => 'issues', :action => 'edit', :id => Issue.first.id}) + assert_match /href/, response + end + + should "allow using the url for the target link" do + User.current = User.find_by_login('admin') + + @project = Issue.first.project # Used by helper + response = link_to_if_authorized("By url", + new_issue_move_path(:id => Issue.first.id)) + assert_match /href/, response + end + + end def test_auto_links to_test = { From 270b559d362a39a8a9594f5fe6a10804e4653af1 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Mon, 6 Sep 2010 00:48:44 +0000 Subject: [PATCH 14/49] Refocus the related issue field after submitting an issue. #6275 git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4065 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/views/issues/_relations.rhtml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/issues/_relations.rhtml b/app/views/issues/_relations.rhtml index 71eaaa673..5b27fa6a5 100644 --- a/app/views/issues/_relations.rhtml +++ b/app/views/issues/_relations.rhtml @@ -28,6 +28,7 @@ <% remote_form_for(:relation, @relation, :url => {:controller => 'issue_relations', :action => 'new', :issue_id => @issue}, :method => :post, + :complete => "Form.Element.focus('relation_issue_to_id');", :html => {:id => 'new-relation-form', :style => (@relation ? '' : 'display: none;')}) do |f| %> <%= render :partial => 'issue_relations/form', :locals => {:f => f}%> <% end %> From 9da4ee5fcce2d20e125acc64377564a2797d1cbb Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Mon, 6 Sep 2010 01:02:52 +0000 Subject: [PATCH 15/49] Allow user password changes when changing to Internal authentication. #6267 git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4066 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/users_controller.rb | 4 +++- test/functional/users_controller_test.rb | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0354d165d..b854850a3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -95,7 +95,9 @@ class UsersController < ApplicationController if request.post? @user.admin = params[:user][:admin] if params[:user][:admin] @user.login = params[:user][:login] if params[:user][:login] - @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id + if params[:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?) + @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] + end @user.group_ids = params[:user][:group_ids] if params[:user][:group_ids] @user.attributes = params[:user] # Was the account actived ? (do it before User#save clears the change) diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb index 640ce8685..0e4c14c79 100644 --- a/test/functional/users_controller_test.rb +++ b/test/functional/users_controller_test.rb @@ -143,6 +143,18 @@ class UsersControllerTest < ActionController::TestCase assert_equal [u.mail], mail.bcc assert mail.body.include?('newpass') end + + test "POST :edit with a password change to an AuthSource user switching to Internal authentication" do + # Configure as auth source + u = User.find(2) + u.auth_source = AuthSource.find(1) + u.save! + + post :edit, :id => u.id, :user => {:auth_source_id => ''}, :password => 'newpass', :password_confirmation => 'newpass' + + assert_equal nil, u.reload.auth_source + assert_equal User.hash_password('newpass'), u.reload.hashed_password + end def test_edit_membership post :edit_membership, :id => 2, :membership_id => 1, From 763ab079424eb286d781abb9d8f5adb682b0ce9e Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Mon, 6 Sep 2010 14:53:08 +0000 Subject: [PATCH 16/49] Refactor: split ProjectsController#add into #add (GET) and #create (POST). git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4067 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/projects_controller.rb | 70 +++++++++-------- config/routes.rb | 6 +- lib/redmine.rb | 4 +- test/functional/projects_controller_test.rb | 85 +++++++++++++-------- test/integration/routing_test.rb | 4 +- 5 files changed, 100 insertions(+), 69 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 90eddd7b2..65f346d5f 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -20,13 +20,13 @@ class ProjectsController < ApplicationController menu_item :roadmap, :only => :roadmap menu_item :settings, :only => :settings - before_filter :find_project, :except => [ :index, :list, :add, :copy ] - before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy] - before_filter :authorize_global, :only => :add + before_filter :find_project, :except => [ :index, :list, :add, :create, :copy ] + before_filter :authorize, :except => [ :index, :list, :add, :create, :copy, :archive, :unarchive, :destroy] + before_filter :authorize_global, :only => [:add, :create] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] accept_key_auth :index - after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller| + after_filter :only => [:create, :edit, :archive, :unarchive, :destroy] do |controller| if controller.request.post? controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt' end @@ -65,35 +65,41 @@ class ProjectsController < ApplicationController @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @trackers = Tracker.all @project = Project.new(params[:project]) - if request.get? - @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers? - @project.trackers = Tracker.all - @project.is_public = Setting.default_projects_public? - @project.enabled_module_names = Setting.default_projects_modules - else - @project.enabled_module_names = params[:enabled_modules] - if validate_parent_id && @project.save - @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') - # Add current user as a project member if he is not admin - unless User.current.admin? - r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first - m = Member.new(:user => User.current, :roles => [r]) - @project.members << m - end - respond_to do |format| - format.html { - flash[:notice] = l(:notice_successful_create) - redirect_to :controller => 'projects', :action => 'settings', :id => @project - } - format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } - end - else - respond_to do |format| - format.html - format.xml { render :xml => @project.errors, :status => :unprocessable_entity } - end + + @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers? + @project.trackers = Tracker.all + @project.is_public = Setting.default_projects_public? + @project.enabled_module_names = Setting.default_projects_modules + end + + def create + @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") + @trackers = Tracker.all + @project = Project.new(params[:project]) + + @project.enabled_module_names = params[:enabled_modules] + if validate_parent_id && @project.save + @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') + # Add current user as a project member if he is not admin + unless User.current.admin? + r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first + m = Member.new(:user => User.current, :roles => [r]) + @project.members << m end - end + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_create) + redirect_to :controller => 'projects', :action => 'settings', :id => @project + } + format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) } + end + else + respond_to do |format| + format.html { render :action => 'add' } + format.xml { render :xml => @project.errors, :status => :unprocessable_entity } + end + end + end def copy diff --git a/config/routes.rb b/config/routes.rb index 5448b5f58..8bcdd91d6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -195,9 +195,9 @@ ActionController::Routing::Routes.draw do |map| end projects.with_options :conditions => {:method => :post} do |project_actions| - project_actions.connect 'projects/new', :action => 'add' - project_actions.connect 'projects', :action => 'add' - project_actions.connect 'projects.:format', :action => 'add', :format => /xml/ + project_actions.connect 'projects/new', :action => 'create' + project_actions.connect 'projects', :action => 'create' + project_actions.connect 'projects.:format', :action => 'create', :format => /xml/ project_actions.connect 'projects/:id/:action', :action => /edit|destroy|archive|unarchive/ project_actions.connect 'projects/:id/files/new', :controller => 'files', :action => 'new' project_actions.connect 'projects/:id/activities/save', :controller => 'project_enumerations', :action => 'save' diff --git a/lib/redmine.rb b/lib/redmine.rb index fc750d110..aa15770db 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -46,12 +46,12 @@ end Redmine::AccessControl.map do |map| map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true map.permission :search_project, {:search => :index}, :public => true - map.permission :add_project, {:projects => :add}, :require => :loggedin + map.permission :add_project, {:projects => [:add, :create]}, :require => :loggedin map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member map.permission :select_project_modules, {:projects => :modules}, :require => :member map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member map.permission :manage_versions, {:projects => :settings, :versions => [:new, :edit, :close_completed, :destroy]}, :require => :member - map.permission :add_subprojects, {:projects => :add}, :require => :member + map.permission :add_subprojects, {:projects => [:add, :create]}, :require => :member map.project_module :issue_tracking do |map| # Issue categories diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 4decb060f..6b8c84728 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -98,9 +98,53 @@ class ProjectsControllerTest < ActionController::TestCase assert_response :success assert_template 'add' end + + end + + context "by non-admin user with add_project permission" do + setup do + Role.non_member.add_permission! :add_project + @request.session[:user_id] = 9 + end + + should "accept get" do + get :add + assert_response :success + assert_template 'add' + assert_no_tag :select, :attributes => {:name => 'project[parent_id]'} + end + end + + context "by non-admin user with add_subprojects permission" do + setup do + Role.find(1).remove_permission! :add_project + Role.find(1).add_permission! :add_subprojects + @request.session[:user_id] = 2 + end - should "accept post" do - post :add, :project => { :name => "blog", + should "accept get" do + get :add, :parent_id => 'ecookbook' + assert_response :success + assert_template 'add' + # parent project selected + assert_tag :select, :attributes => {:name => 'project[parent_id]'}, + :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}} + # no empty value + assert_no_tag :select, :attributes => {:name => 'project[parent_id]'}, + :child => {:tag => 'option', :attributes => {:value => ''}} + end + end + + end + + context "POST :create" do + context "by admin user" do + setup do + @request.session[:user_id] = 1 + end + + should "create a new project" do + post :create, :project => { :name => "blog", :description => "weblog", :identifier => "blog", :is_public => 1, @@ -115,8 +159,8 @@ class ProjectsControllerTest < ActionController::TestCase assert_nil project.parent end - should "accept post with parent" do - post :add, :project => { :name => "blog", + should "create a new subproject" do + post :create, :project => { :name => "blog", :description => "weblog", :identifier => "blog", :is_public => 1, @@ -137,15 +181,8 @@ class ProjectsControllerTest < ActionController::TestCase @request.session[:user_id] = 9 end - should "accept get" do - get :add - assert_response :success - assert_template 'add' - assert_no_tag :select, :attributes => {:name => 'project[parent_id]'} - end - - should "accept post" do - post :add, :project => { :name => "blog", + should "accept create a Project" do + post :create, :project => { :name => "blog", :description => "weblog", :identifier => "blog", :is_public => 1, @@ -166,7 +203,7 @@ class ProjectsControllerTest < ActionController::TestCase should "fail with parent_id" do assert_no_difference 'Project.count' do - post :add, :project => { :name => "blog", + post :create, :project => { :name => "blog", :description => "weblog", :identifier => "blog", :is_public => 1, @@ -188,20 +225,8 @@ class ProjectsControllerTest < ActionController::TestCase @request.session[:user_id] = 2 end - should "accept get" do - get :add, :parent_id => 'ecookbook' - assert_response :success - assert_template 'add' - # parent project selected - assert_tag :select, :attributes => {:name => 'project[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}} - # no empty value - assert_no_tag :select, :attributes => {:name => 'project[parent_id]'}, - :child => {:tag => 'option', :attributes => {:value => ''}} - end - - should "accept post with parent_id" do - post :add, :project => { :name => "blog", + should "create a project with a parent_id" do + post :create, :project => { :name => "blog", :description => "weblog", :identifier => "blog", :is_public => 1, @@ -214,7 +239,7 @@ class ProjectsControllerTest < ActionController::TestCase should "fail without parent_id" do assert_no_difference 'Project.count' do - post :add, :project => { :name => "blog", + post :create, :project => { :name => "blog", :description => "weblog", :identifier => "blog", :is_public => 1, @@ -230,7 +255,7 @@ class ProjectsControllerTest < ActionController::TestCase should "fail with unauthorized parent_id" do assert !User.find(2).member_of?(Project.find(6)) assert_no_difference 'Project.count' do - post :add, :project => { :name => "blog", + post :create, :project => { :name => "blog", :description => "weblog", :identifier => "blog", :is_public => 1, diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index 71be6c3c7..e75cf4721 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -178,8 +178,8 @@ class RoutingTest < ActionController::IntegrationTest should_route :get, "/projects/33/activity", :controller => 'activities', :action => 'index', :id => '33' should_route :get, "/projects/33/activity.atom", :controller => 'activities', :action => 'index', :id => '33', :format => 'atom' - should_route :post, "/projects/new", :controller => 'projects', :action => 'add' - should_route :post, "/projects.xml", :controller => 'projects', :action => 'add', :format => 'xml' + should_route :post, "/projects/new", :controller => 'projects', :action => 'create' + should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml' should_route :post, "/projects/4223/edit", :controller => 'projects', :action => 'edit', :id => '4223' should_route :post, "/projects/64/destroy", :controller => 'projects', :action => 'destroy', :id => '64' should_route :post, "/projects/33/files/new", :controller => 'files', :action => 'new', :id => '33' From 06878e50041117baa1b4bde40671ab422dd9bc0a Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Mon, 6 Sep 2010 15:09:52 +0000 Subject: [PATCH 17/49] Change project add form to use #create. (From r4067) git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4068 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/views/projects/add.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/add.rhtml b/app/views/projects/add.rhtml index d2ec5db92..c8a9c7600 100644 --- a/app/views/projects/add.rhtml +++ b/app/views/projects/add.rhtml @@ -1,6 +1,6 @@

    <%=l(:label_project_new)%>

    -<% labelled_tabular_form_for :project, @project, :url => { :action => "add" } do |f| %> +<% labelled_tabular_form_for :project, @project, :url => { :action => "create" } do |f| %> <%= render :partial => 'form', :locals => { :f => f } %>
    <%= l(:label_module_plural) %> From 2295b61cb6fde4187875478170da2ae70f114497 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Tue, 7 Sep 2010 15:00:27 +0000 Subject: [PATCH 18/49] Refactor: rename method ProjectsController#add to ProjectsController#new git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4069 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/projects_controller.rb | 11 +++++------ app/views/admin/projects.rhtml | 2 +- app/views/projects/index.rhtml | 2 +- app/views/projects/{add.rhtml => new.html.erb} | 0 config/routes.rb | 2 +- lib/redmine.rb | 4 ++-- test/functional/projects_controller_test.rb | 14 +++++++------- test/integration/routing_test.rb | 2 +- 8 files changed, 18 insertions(+), 19 deletions(-) rename app/views/projects/{add.rhtml => new.html.erb} (100%) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 65f346d5f..376034097 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -20,9 +20,9 @@ class ProjectsController < ApplicationController menu_item :roadmap, :only => :roadmap menu_item :settings, :only => :settings - before_filter :find_project, :except => [ :index, :list, :add, :create, :copy ] - before_filter :authorize, :except => [ :index, :list, :add, :create, :copy, :archive, :unarchive, :destroy] - before_filter :authorize_global, :only => [:add, :create] + before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ] + before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy] + before_filter :authorize_global, :only => [:new, :create] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] accept_key_auth :index @@ -60,8 +60,7 @@ class ProjectsController < ApplicationController end end - # Add a new project - def add + def new @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @trackers = Tracker.all @project = Project.new(params[:project]) @@ -95,7 +94,7 @@ class ProjectsController < ApplicationController end else respond_to do |format| - format.html { render :action => 'add' } + format.html { render :action => 'new' } format.xml { render :xml => @project.errors, :status => :unprocessable_entity } end end diff --git a/app/views/admin/projects.rhtml b/app/views/admin/projects.rhtml index 46b68e4cc..b37fec57b 100644 --- a/app/views/admin/projects.rhtml +++ b/app/views/admin/projects.rhtml @@ -1,5 +1,5 @@
    -<%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add' %> +<%= link_to l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add' %>

    <%=l(:label_project_plural)%>

    diff --git a/app/views/projects/index.rhtml b/app/views/projects/index.rhtml index a2ba1c389..2b506388a 100644 --- a/app/views/projects/index.rhtml +++ b/app/views/projects/index.rhtml @@ -3,7 +3,7 @@ <% end %>
    - <%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add') + ' |' if User.current.allowed_to?(:add_project, nil, :global => true) %> + <%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add') + ' |' if User.current.allowed_to?(:add_project, nil, :global => true) %> <%= link_to(l(:label_issue_view_all), { :controller => 'issues' }) + ' |' if User.current.allowed_to?(:view_issues, nil, :global => true) %> <%= link_to(l(:label_overall_spent_time), { :controller => 'time_entries' }) + ' |' if User.current.allowed_to?(:view_time_entries, nil, :global => true) %> <%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }%> diff --git a/app/views/projects/add.rhtml b/app/views/projects/new.html.erb similarity index 100% rename from app/views/projects/add.rhtml rename to app/views/projects/new.html.erb diff --git a/config/routes.rb b/config/routes.rb index 8bcdd91d6..7f971da7c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -177,7 +177,7 @@ ActionController::Routing::Routes.draw do |map| projects.with_options :conditions => {:method => :get} do |project_views| project_views.connect 'projects', :action => 'index' project_views.connect 'projects.:format', :action => 'index' - project_views.connect 'projects/new', :action => 'add' + project_views.connect 'projects/new', :action => 'new' project_views.connect 'projects/:id', :action => 'show' project_views.connect 'projects/:id.:format', :action => 'show' project_views.connect 'projects/:id/:action', :action => /destroy|settings/ diff --git a/lib/redmine.rb b/lib/redmine.rb index aa15770db..dcddeb902 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -46,12 +46,12 @@ end Redmine::AccessControl.map do |map| map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true map.permission :search_project, {:search => :index}, :public => true - map.permission :add_project, {:projects => [:add, :create]}, :require => :loggedin + map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member map.permission :select_project_modules, {:projects => :modules}, :require => :member map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member map.permission :manage_versions, {:projects => :settings, :versions => [:new, :edit, :close_completed, :destroy]}, :require => :member - map.permission :add_subprojects, {:projects => [:add, :create]}, :require => :member + map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member map.project_module :issue_tracking do |map| # Issue categories diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 6b8c84728..e661ca29e 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -87,16 +87,16 @@ class ProjectsControllerTest < ActionController::TestCase end end - context "#add" do + context "#new" do context "by admin user" do setup do @request.session[:user_id] = 1 end should "accept get" do - get :add + get :new assert_response :success - assert_template 'add' + assert_template 'new' end end @@ -108,9 +108,9 @@ class ProjectsControllerTest < ActionController::TestCase end should "accept get" do - get :add + get :new assert_response :success - assert_template 'add' + assert_template 'new' assert_no_tag :select, :attributes => {:name => 'project[parent_id]'} end end @@ -123,9 +123,9 @@ class ProjectsControllerTest < ActionController::TestCase end should "accept get" do - get :add, :parent_id => 'ecookbook' + get :new, :parent_id => 'ecookbook' assert_response :success - assert_template 'add' + assert_template 'new' # parent project selected assert_tag :select, :attributes => {:name => 'project[parent_id]'}, :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}} diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index e75cf4721..f43dc8a61 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -166,7 +166,7 @@ class RoutingTest < ActionController::IntegrationTest should_route :get, "/projects", :controller => 'projects', :action => 'index' should_route :get, "/projects.atom", :controller => 'projects', :action => 'index', :format => 'atom' should_route :get, "/projects.xml", :controller => 'projects', :action => 'index', :format => 'xml' - should_route :get, "/projects/new", :controller => 'projects', :action => 'add' + should_route :get, "/projects/new", :controller => 'projects', :action => 'new' should_route :get, "/projects/test", :controller => 'projects', :action => 'show', :id => 'test' should_route :get, "/projects/1.xml", :controller => 'projects', :action => 'show', :id => '1', :format => 'xml' should_route :get, "/projects/4223/settings", :controller => 'projects', :action => 'settings', :id => '4223' From bf7476af5b6b2ec70e20254c8f2fe346ca22c089 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Wed, 8 Sep 2010 16:01:51 +0000 Subject: [PATCH 19/49] Refactor: split method ProjectsController#edit to ProjectsController#update. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4070 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/projects_controller.rb | 48 +++++++++++---------- app/views/projects/_edit.rhtml | 2 +- config/routes.rb | 5 ++- lib/redmine.rb | 2 +- test/functional/projects_controller_test.rb | 4 +- test/integration/routing_test.rb | 4 +- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 376034097..f16349329 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -25,13 +25,16 @@ class ProjectsController < ApplicationController before_filter :authorize_global, :only => [:new, :create] before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ] accept_key_auth :index - - after_filter :only => [:create, :edit, :archive, :unarchive, :destroy] do |controller| + + after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller| if controller.request.post? controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt' end end - + + # TODO: convert to PUT only + verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed } + helper :sort include SortHelper helper :custom_fields @@ -179,28 +182,27 @@ class ProjectsController < ApplicationController @wiki ||= @project.wiki end - # Edit @project def edit - if request.get? + end + + def update + @project.attributes = params[:project] + if validate_parent_id && @project.save + @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') + respond_to do |format| + format.html { + flash[:notice] = l(:notice_successful_update) + redirect_to :action => 'settings', :id => @project + } + format.xml { head :ok } + end else - @project.attributes = params[:project] - if validate_parent_id && @project.save - @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') - respond_to do |format| - format.html { - flash[:notice] = l(:notice_successful_update) - redirect_to :action => 'settings', :id => @project - } - format.xml { head :ok } - end - else - respond_to do |format| - format.html { - settings - render :action => 'settings' - } - format.xml { render :xml => @project.errors, :status => :unprocessable_entity } - end + respond_to do |format| + format.html { + settings + render :action => 'settings' + } + format.xml { render :xml => @project.errors, :status => :unprocessable_entity } end end end diff --git a/app/views/projects/_edit.rhtml b/app/views/projects/_edit.rhtml index b7c2987d2..dc667f267 100644 --- a/app/views/projects/_edit.rhtml +++ b/app/views/projects/_edit.rhtml @@ -1,4 +1,4 @@ -<% labelled_tabular_form_for :project, @project, :url => { :action => "edit", :id => @project } do |f| %> +<% labelled_tabular_form_for :project, @project, :url => { :action => "update", :id => @project } do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <%= submit_tag l(:button_save) %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index 7f971da7c..01d6a5062 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -198,13 +198,14 @@ ActionController::Routing::Routes.draw do |map| project_actions.connect 'projects/new', :action => 'create' project_actions.connect 'projects', :action => 'create' project_actions.connect 'projects.:format', :action => 'create', :format => /xml/ - project_actions.connect 'projects/:id/:action', :action => /edit|destroy|archive|unarchive/ + project_actions.connect 'projects/:id/edit', :action => 'update' + project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/ project_actions.connect 'projects/:id/files/new', :controller => 'files', :action => 'new' project_actions.connect 'projects/:id/activities/save', :controller => 'project_enumerations', :action => 'save' end projects.with_options :conditions => {:method => :put} do |project_actions| - project_actions.conditions 'projects/:id.:format', :action => 'edit', :format => /xml/ + project_actions.conditions 'projects/:id.:format', :action => 'update', :format => /xml/ end projects.with_options :conditions => {:method => :delete} do |project_actions| diff --git a/lib/redmine.rb b/lib/redmine.rb index dcddeb902..534f85875 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -47,7 +47,7 @@ Redmine::AccessControl.map do |map| map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true map.permission :search_project, {:search => :index}, :public => true map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin - map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member + map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member map.permission :select_project_modules, {:projects => :modules}, :require => :member map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member map.permission :manage_versions, {:projects => :settings, :versions => [:new, :edit, :close_completed, :destroy]}, :require => :member diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index e661ca29e..636cb1751 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -318,9 +318,9 @@ class ProjectsControllerTest < ActionController::TestCase assert_template 'settings' end - def test_edit + def test_update @request.session[:user_id] = 2 # manager - post :edit, :id => 1, :project => {:name => 'Test changed name', + post :update, :id => 1, :project => {:name => 'Test changed name', :issue_custom_field_ids => ['']} assert_redirected_to 'projects/ecookbook/settings' project = Project.find(1) diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index f43dc8a61..0db45ce02 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -180,14 +180,14 @@ class RoutingTest < ActionController::IntegrationTest should_route :post, "/projects/new", :controller => 'projects', :action => 'create' should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml' - should_route :post, "/projects/4223/edit", :controller => 'projects', :action => 'edit', :id => '4223' + should_route :post, "/projects/4223/edit", :controller => 'projects', :action => 'update', :id => '4223' should_route :post, "/projects/64/destroy", :controller => 'projects', :action => 'destroy', :id => '64' should_route :post, "/projects/33/files/new", :controller => 'files', :action => 'new', :id => '33' should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64' should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64' should_route :post, "/projects/64/activities/save", :controller => 'project_enumerations', :action => 'save', :id => '64' - should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'edit', :id => '1', :format => 'xml' + should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'update', :id => '1', :format => 'xml' should_route :delete, "/projects/1.xml", :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml' should_route :delete, "/projects/64/reset_activities", :controller => 'project_enumerations', :action => 'destroy', :id => '64' From 8d52608dbad63d504ec4b48ffe5ea09cfbe95bd9 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Thu, 9 Sep 2010 18:57:21 +0000 Subject: [PATCH 20/49] Refactor: convert the Projects routes to resources. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4071 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/views/admin/projects.rhtml | 2 +- app/views/projects/_edit.rhtml | 2 +- app/views/projects/destroy.rhtml | 2 +- config/routes.rb | 55 +++++++++++++++----------------- test/integration/routing_test.rb | 7 ++-- 5 files changed, 32 insertions(+), 36 deletions(-) diff --git a/app/views/admin/projects.rhtml b/app/views/admin/projects.rhtml index b37fec57b..6cf933d11 100644 --- a/app/views/admin/projects.rhtml +++ b/app/views/admin/projects.rhtml @@ -35,7 +35,7 @@ <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %> <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if !project.active? && (project.parent.nil? || project.parent.active?) %> <%= link_to(l(:button_copy), { :controller => 'projects', :action => 'copy', :id => project }, :class => 'icon icon-copy') %> - <%= link_to(l(:button_delete), { :controller => 'projects', :action => 'destroy', :id => project }, :class => 'icon icon-del') %> + <%= link_to(l(:button_delete), project_destroy_confirm_path(project), :class => 'icon icon-del') %> <% end %> diff --git a/app/views/projects/_edit.rhtml b/app/views/projects/_edit.rhtml index dc667f267..2ecc822df 100644 --- a/app/views/projects/_edit.rhtml +++ b/app/views/projects/_edit.rhtml @@ -1,4 +1,4 @@ -<% labelled_tabular_form_for :project, @project, :url => { :action => "update", :id => @project } do |f| %> +<% labelled_tabular_form_for :project, @project, :url => project_path(@project), :html => {:method => (@project.new_record? ? :post : :put) } do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <%= submit_tag l(:button_save) %> <% end %> diff --git a/app/views/projects/destroy.rhtml b/app/views/projects/destroy.rhtml index 09d7d2a1c..23844ec67 100644 --- a/app/views/projects/destroy.rhtml +++ b/app/views/projects/destroy.rhtml @@ -8,7 +8,7 @@ <% end %>

    - <% form_tag({:controller => 'projects', :action => 'destroy', :id => @project_to_destroy}) do %> + <% form_tag(project_path(@project_to_destroy), :method => :delete) do %> <%= submit_tag l(:button_delete) %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index 01d6a5062..9f12cd454 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -172,48 +172,45 @@ ActionController::Routing::Routes.draw do |map| user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership' end end - - map.with_options :controller => 'projects' do |projects| - projects.with_options :conditions => {:method => :get} do |project_views| - project_views.connect 'projects', :action => 'index' - project_views.connect 'projects.:format', :action => 'index' - project_views.connect 'projects/new', :action => 'new' - project_views.connect 'projects/:id', :action => 'show' - project_views.connect 'projects/:id.:format', :action => 'show' - project_views.connect 'projects/:id/:action', :action => /destroy|settings/ + + map.resources :projects, :member => { + :copy => [:get, :post], + :settings => :get, + :modules => :post, + :archive => :post, + :unarchive => :post + } + + # Destroy uses a get request to prompt the user before the actual DELETE request + map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get} + + # TODO: port to be part of the resources route(s) + map.with_options :controller => 'projects' do |project_mapper| + project_mapper.with_options :conditions => {:method => :get} do |project_views| project_views.connect 'projects/:id/files', :controller => 'files', :action => 'index' project_views.connect 'projects/:id/files/new', :controller => 'files', :action => 'new' - project_views.connect 'projects/:id/settings/:tab', :action => 'settings' + project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings' project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new' end - projects.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity| - activity.connect 'projects/:id/activity' - activity.connect 'projects/:id/activity.:format' - activity.connect 'activity', :id => nil - activity.connect 'activity.:format', :id => nil - end - - projects.with_options :conditions => {:method => :post} do |project_actions| - project_actions.connect 'projects/new', :action => 'create' - project_actions.connect 'projects', :action => 'create' - project_actions.connect 'projects.:format', :action => 'create', :format => /xml/ - project_actions.connect 'projects/:id/edit', :action => 'update' - project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/ + project_mapper.with_options :conditions => {:method => :post} do |project_actions| project_actions.connect 'projects/:id/files/new', :controller => 'files', :action => 'new' project_actions.connect 'projects/:id/activities/save', :controller => 'project_enumerations', :action => 'save' end - projects.with_options :conditions => {:method => :put} do |project_actions| - project_actions.conditions 'projects/:id.:format', :action => 'update', :format => /xml/ - end - - projects.with_options :conditions => {:method => :delete} do |project_actions| - project_actions.conditions 'projects/:id.:format', :action => 'destroy', :format => /xml/ + project_mapper.with_options :conditions => {:method => :delete} do |project_actions| project_actions.conditions 'projects/:id/reset_activities', :controller => 'project_enumerations', :action => 'destroy' end + end + map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity| + activity.connect 'projects/:id/activity' + activity.connect 'projects/:id/activity.:format' + activity.connect 'activity', :id => nil + activity.connect 'activity.:format', :id => nil + end + map.with_options :controller => 'versions' do |versions| versions.connect 'projects/:project_id/versions/new', :action => 'new' versions.connect 'projects/:project_id/roadmap', :action => 'index' diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index 0db45ce02..5cd0b2d39 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -171,24 +171,23 @@ class RoutingTest < ActionController::IntegrationTest should_route :get, "/projects/1.xml", :controller => 'projects', :action => 'show', :id => '1', :format => 'xml' should_route :get, "/projects/4223/settings", :controller => 'projects', :action => 'settings', :id => '4223' should_route :get, "/projects/4223/settings/members", :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members' - should_route :get, "/projects/567/destroy", :controller => 'projects', :action => 'destroy', :id => '567' should_route :get, "/projects/33/files", :controller => 'files', :action => 'index', :id => '33' should_route :get, "/projects/33/files/new", :controller => 'files', :action => 'new', :id => '33' should_route :get, "/projects/33/roadmap", :controller => 'versions', :action => 'index', :project_id => '33' should_route :get, "/projects/33/activity", :controller => 'activities', :action => 'index', :id => '33' should_route :get, "/projects/33/activity.atom", :controller => 'activities', :action => 'index', :id => '33', :format => 'atom' - should_route :post, "/projects/new", :controller => 'projects', :action => 'create' + should_route :post, "/projects", :controller => 'projects', :action => 'create' should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml' - should_route :post, "/projects/4223/edit", :controller => 'projects', :action => 'update', :id => '4223' - should_route :post, "/projects/64/destroy", :controller => 'projects', :action => 'destroy', :id => '64' should_route :post, "/projects/33/files/new", :controller => 'files', :action => 'new', :id => '33' should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64' should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64' should_route :post, "/projects/64/activities/save", :controller => 'project_enumerations', :action => 'save', :id => '64' + should_route :put, "/projects/4223", :controller => 'projects', :action => 'update', :id => '4223' should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'update', :id => '1', :format => 'xml' + should_route :delete, "/projects/64", :controller => 'projects', :action => 'destroy', :id => '64' should_route :delete, "/projects/1.xml", :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml' should_route :delete, "/projects/64/reset_activities", :controller => 'project_enumerations', :action => 'destroy', :id => '64' end From bdb3937e0f4c8faceb463e23cb28676930ddbd9e Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 10 Sep 2010 03:09:02 +0000 Subject: [PATCH 21/49] Rewrite the Gantt chart. #6276 This version of the Gantt chart supports nested charts. So Projects, Versions, and Issues will be nested underneath their parents correctly. Additional features: * Move all Gantt code to Redmine::Helpers::Gantt class instead of having it in the Gantt class, controller, and view * Recursive and nest sub-projects * Recursive and nest versions * Recursive and nest issues * Draw a line showing when a Project is active and it's progress * Draw a line showing when a Version is active and it's progress * Show a version's % complete * Change the color of Projects, Versions, and Issues if they are late or behind schedule * Added Project#start_date and #due_date * Added Project#completed_percent * Use a mini-gravatar on the Gantt chart * Added tests for the Gantt rendering git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4072 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/gantts_controller.rb | 24 +- app/controllers/issues_controller.rb | 1 + app/helpers/application_helper.rb | 7 + app/helpers/gantt_helper.rb | 24 + app/helpers/issues_helper.rb | 4 +- app/models/issue.rb | 27 +- app/models/project.rb | 44 + app/models/version.rb | 12 + app/views/gantts/show.html.erb | 80 +- lib/redmine/export/pdf.rb | 190 +--- lib/redmine/helpers/gantt.rb | 895 ++++++++++++++++-- public/images/milestone.png | Bin 122 -> 0 bytes public/images/milestone_done.png | Bin 0 -> 137 bytes public/images/milestone_late.png | Bin 0 -> 160 bytes public/images/milestone_todo.png | Bin 0 -> 155 bytes public/images/project_marker.png | Bin 0 -> 204 bytes public/images/task_done.png | Bin 855 -> 137 bytes public/images/version_marker.png | Bin 0 -> 174 bytes public/stylesheets/application.css | 23 +- test/functional/gantts_controller_test.rb | 16 +- test/object_daddy_helpers.rb | 3 +- test/unit/helpers/application_helper_test.rb | 2 +- test/unit/issue_test.rb | 22 + test/unit/lib/redmine/helpers/gantt_test.rb | 703 ++++++++++++++ test/unit/project_test.rb | 118 +++ test/unit/version_test.rb | 52 +- vendor/plugins/gravatar/Rakefile | 2 +- vendor/plugins/gravatar/lib/gravatar.rb | 9 +- vendor/plugins/gravatar/spec/gravatar_spec.rb | 24 +- 29 files changed, 1879 insertions(+), 403 deletions(-) create mode 100644 app/helpers/gantt_helper.rb delete mode 100644 public/images/milestone.png create mode 100644 public/images/milestone_done.png create mode 100644 public/images/milestone_late.png create mode 100644 public/images/milestone_todo.png create mode 100644 public/images/project_marker.png create mode 100644 public/images/version_marker.png create mode 100644 test/unit/lib/redmine/helpers/gantt_test.rb diff --git a/app/controllers/gantts_controller.rb b/app/controllers/gantts_controller.rb index 6a6071e86..50fd8c13d 100644 --- a/app/controllers/gantts_controller.rb +++ b/app/controllers/gantts_controller.rb @@ -4,6 +4,7 @@ class GanttsController < ApplicationController rescue_from Query::StatementInvalid, :with => :query_statement_invalid + helper :gantt helper :issues helper :projects helper :queries @@ -14,32 +15,17 @@ class GanttsController < ApplicationController def show @gantt = Redmine::Helpers::Gantt.new(params) + @gantt.project = @project retrieve_query @query.group_by = nil - if @query.valid? - events = [] - # Issues that have start and due dates - events += @query.issues(:include => [:tracker, :assigned_to, :priority], - :order => "start_date, due_date", - :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] - ) - # Issues that don't have a due date but that are assigned to a version with a date - events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version], - :order => "start_date, effective_date", - :conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to] - ) - # Versions - events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to]) - - @gantt.events = events - end + @gantt.query = @query if @query.valid? basename = (@project ? "#{@project.identifier}-" : '') + 'gantt' respond_to do |format| format.html { render :action => "show", :layout => !request.xhr? } - format.png { send_data(@gantt.to_image(@project), :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image') - format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") } + format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image') + format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") } end end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 0364e307c..9f58cb0a1 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -47,6 +47,7 @@ class IssuesController < ApplicationController include SortHelper include IssuesHelper helper :timelog + helper :gantt include Redmine::Export::PDF verify :method => [:post, :delete], diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0fb44a22b..34b17c760 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -121,6 +121,11 @@ module ApplicationHelper link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision)) end + + def link_to_project(project, options={}) + options[:class] ||= 'project' + link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => options[:class]) + end # Generates a link to a project if active # Examples: @@ -832,6 +837,8 @@ module ApplicationHelper email = $1 end return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil + else + '' end end diff --git a/app/helpers/gantt_helper.rb b/app/helpers/gantt_helper.rb new file mode 100644 index 000000000..38f3765e9 --- /dev/null +++ b/app/helpers/gantt_helper.rb @@ -0,0 +1,24 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +module GanttHelper + def number_of_issues_on_versions(gantt) + versions = gantt.events.collect {|event| (event.is_a? Version) ? event : nil}.compact + + versions.sum {|v| v.fixed_issues.for_gantt.with_query(@query).count} + end +end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 617822986..284aae91a 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -35,8 +35,10 @@ module IssuesHelper @cached_label_due_date ||= l(:field_due_date) @cached_label_assigned_to ||= l(:field_assigned_to) @cached_label_priority ||= l(:field_priority) - + @cached_label_project ||= l(:field_project) + link_to_issue(issue) + "

    " + + "#{@cached_label_project}: #{link_to_project(issue.project)}
    " + "#{@cached_label_status}: #{issue.status.name}
    " + "#{@cached_label_start_date}: #{format_date(issue.start_date)}
    " + "#{@cached_label_due_date}: #{format_date(issue.due_date)}
    " + diff --git a/app/models/issue.rb b/app/models/issue.rb index 7d0682df1..80db48108 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -62,10 +62,28 @@ class Issue < ActiveRecord::Base named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status - named_scope :recently_updated, :order => "#{self.table_name}.updated_on DESC" + named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC" named_scope :with_limit, lambda { |limit| { :limit => limit} } named_scope :on_active_project, :include => [:status, :project, :tracker], :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"] + named_scope :for_gantt, lambda { + { + :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version], + :order => "#{Issue.table_name}.due_date ASC, #{Issue.table_name}.start_date ASC, #{Issue.table_name}.id ASC" + } + } + + named_scope :without_version, lambda { + { + :conditions => { :fixed_version_id => nil} + } + } + + named_scope :with_query, lambda {|query| + { + :conditions => Query.merge_conditions(query.statement) + } + } before_create :default_assign before_save :reschedule_following_issues, :close_duplicates, :update_done_ratio_from_issue_status @@ -357,6 +375,13 @@ class Issue < ActiveRecord::Base def overdue? !due_date.nil? && (due_date < Date.today) && !status.is_closed? end + + # Is the amount of work done less than it should for the due date + def behind_schedule? + return false if start_date.nil? || due_date.nil? + done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor + return done_date <= Date.today + end # Users the issue can be assigned to def assignable_users diff --git a/app/models/project.rb b/app/models/project.rb index 931f89b55..5ef7915de 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -412,6 +412,50 @@ class Project < ActiveRecord::Base def short_description(length = 255) description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description end + + # The earliest start date of a project, based on it's issues and versions + def start_date + if module_enabled?(:issue_tracking) + [ + issues.minimum('start_date'), + shared_versions.collect(&:effective_date), + shared_versions.collect {|v| v.fixed_issues.minimum('start_date')} + ].flatten.compact.min + end + end + + # The latest due date of an issue or version + def due_date + if module_enabled?(:issue_tracking) + [ + issues.maximum('due_date'), + shared_versions.collect(&:effective_date), + shared_versions.collect {|v| v.fixed_issues.maximum('due_date')} + ].flatten.compact.max + end + end + + def overdue? + active? && !due_date.nil? && (due_date < Date.today) + end + + # Returns the percent completed for this project, based on the + # progress on it's versions. + def completed_percent(options={:include_subprojects => false}) + if options.delete(:include_subprojects) + total = self_and_descendants.collect(&:completed_percent).sum + + total / self_and_descendants.count + else + if versions.count > 0 + total = versions.collect(&:completed_pourcent).sum + + total / versions.count + else + 100 + end + end + end # Return true if this project is allowed to do the specified action. # action can be: diff --git a/app/models/version.rb b/app/models/version.rb index 07e66434d..c3969fe87 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -73,6 +73,18 @@ class Version < ActiveRecord::Base def completed? effective_date && (effective_date <= Date.today) && (open_issues_count == 0) end + + def behind_schedule? + if completed_pourcent == 100 + return false + elsif due_date && fixed_issues.present? && fixed_issues.minimum('start_date') # TODO: should use #start_date but that method is wrong... + start_date = fixed_issues.minimum('start_date') + done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor + return done_date <= Date.today + else + false # No issues so it's not late + end + end # Returns the completion percentage of this version based on the amount of open/closed issues # and the time spent on the open issues. diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb index 5d4ef0dbf..ce8c67b26 100644 --- a/app/views/gantts/show.html.erb +++ b/app/views/gantts/show.html.erb @@ -1,3 +1,4 @@ +<% @gantt.view = self %>

    <%= l(:label_gantt) %>

    <% form_tag(gantt_path(:month => params[:month], :year => params[:year], :months => params[:months]), :method => :put, :id => 'query_form') do %> @@ -55,11 +56,12 @@ if @gantt.zoom >1 end end +# Width of the entire chart g_width = (@gantt.date_to - @gantt.date_from + 1)*zoom -g_height = [(20 * @gantt.events.length + 6)+150, 206].max +# Collect the number of issues on Versions +g_height = [(20 * (@gantt.number_of_rows + 6))+150, 206].max t_height = g_height + headers_height %> -

  • <%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;', + <%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;', :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
    <%= check_box_tag("ids[]", issue.id, false, :id => nil) %><%= check_box_tag("ids[]", issue.id, false, :id => nil) %> <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %>
    <% project_tree(@projects) do |project, level| %> - <%= css_project_classes(project) %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> + <%= project.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> From f2a8057d635be2a844b00437cff6668fdd3e87a1 Mon Sep 17 00:00:00 2001 From: Azamat Hackimov Date: Sat, 11 Sep 2010 11:16:56 +0000 Subject: [PATCH 30/49] Translations update * ja (#6363) * ko (#6307) * po (#6458) * pt-BR (#6316) * ru * sr and sr-YU (#6339) * zh-TW (#6306) New strings to translate * field_member_of_group (r4077) * field_assigned_to_role (r4078) git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4081 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- config/locales/bg.yml | 2 + config/locales/bs.yml | 2 + config/locales/ca.yml | 2 + config/locales/cs.yml | 2 + config/locales/da.yml | 2 + config/locales/de.yml | 2 + config/locales/el.yml | 2 + config/locales/en-GB.yml | 2 + config/locales/es.yml | 2 + config/locales/eu.yml | 2 + config/locales/fi.yml | 2 + config/locales/fr.yml | 2 + config/locales/gl.yml | 2 + config/locales/he.yml | 2 + config/locales/hr.yml | 2 + config/locales/hu.yml | 2 + config/locales/id.yml | 2 + config/locales/it.yml | 2 + config/locales/ja.yml | 4 +- config/locales/ko.yml | 22 ++++---- config/locales/lt.yml | 2 + config/locales/lv.yml | 2 + config/locales/mk.yml | 2 + config/locales/mn.yml | 2 + config/locales/nl.yml | 2 + config/locales/no.yml | 2 + config/locales/pl.yml | 112 ++++++++++++++++++++------------------- config/locales/pt-BR.yml | 4 +- config/locales/pt.yml | 2 + config/locales/ro.yml | 2 + config/locales/ru.yml | 2 + config/locales/sk.yml | 2 + config/locales/sl.yml | 2 + config/locales/sr-YU.yml | 10 ++-- config/locales/sr.yml | 11 ++-- config/locales/sv.yml | 2 + config/locales/th.yml | 2 + config/locales/tr.yml | 2 + config/locales/uk.yml | 2 + config/locales/vi.yml | 2 + config/locales/zh-TW.yml | 4 +- config/locales/zh.yml | 2 + 42 files changed, 161 insertions(+), 76 deletions(-) diff --git a/config/locales/bg.yml b/config/locales/bg.yml index f334633e2..0a8c427ef 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -907,3 +907,5 @@ bg: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/bs.yml b/config/locales/bs.yml index 90ec9dea2..741f46232 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -927,3 +927,5 @@ bs: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/ca.yml b/config/locales/ca.yml index b5c28d98e..50a389156 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -916,3 +916,5 @@ ca: enumeration_activities: Activitats (seguidor de temps) enumeration_system_activity: Activitat del sistema + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/cs.yml b/config/locales/cs.yml index f2b114605..96fb608b6 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -913,3 +913,5 @@ cs: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/da.yml b/config/locales/da.yml index 4d30dc6f3..38d9d6646 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -929,3 +929,5 @@ da: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/de.yml b/config/locales/de.yml index 78cc99d4a..5ec7073b3 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -932,3 +932,5 @@ de: enumeration_activities: Aktivitäten (Zeiterfassung) enumeration_system_activity: System-Aktivität + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/el.yml b/config/locales/el.yml index e4487c59f..22bc6a459 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -913,3 +913,5 @@ el: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 6a8faa746..8c0b28cf3 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -917,3 +917,5 @@ en-GB: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/es.yml b/config/locales/es.yml index 4ffdaf3f6..3f848f1d1 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -953,3 +953,5 @@ es: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 236c5bb7d..24ef38ea7 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -917,3 +917,5 @@ eu: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 9aeee5f6e..985ab8b2a 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -938,3 +938,5 @@ fi: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 24b70ad21..7f96b6112 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -931,3 +931,5 @@ fr: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/gl.yml b/config/locales/gl.yml index db64fabdc..b717d8d60 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -929,3 +929,5 @@ gl: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/he.yml b/config/locales/he.yml index 50f31c294..86ebe7d3a 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -918,3 +918,5 @@ he: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/hr.yml b/config/locales/hr.yml index 5727a629e..f4a809a9a 100644 --- a/config/locales/hr.yml +++ b/config/locales/hr.yml @@ -920,3 +920,5 @@ hr: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/hu.yml b/config/locales/hu.yml index c895f4c96..fe1abdf36 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -936,3 +936,5 @@ field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/id.yml b/config/locales/id.yml index 65140c2d0..7c327833f 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -921,3 +921,5 @@ id: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/it.yml b/config/locales/it.yml index 34a7820d2..8d5aa4ef4 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -917,3 +917,5 @@ it: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 68c4d24aa..09eb36690 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -144,7 +144,7 @@ ja: greater_than_start_date: "を開始日より後にしてください" not_same_project: "同じプロジェクトに属していません" circular_dependency: "この関係では、循環依存になります" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + cant_link_an_issue_with_a_descendant: "指定したチケットとは親子関係になっているため関連づけられません" actionview_instancetag_blank_option: 選んでください @@ -319,6 +319,8 @@ ja: field_group_by: グループ条件 field_sharing: 共有 field_parent_issue: 親チケット + field_member_of_group: 担当者のグループ + field_assigned_to_role: 担当者のロール setting_app_title: アプリケーションのタイトル setting_app_subtitle: アプリケーションのサブタイトル diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 0a5fa44e1..eea667d62 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -2,7 +2,7 @@ # by Kihyun Yoon(ddumbugie@gmail.com),http://plenum.textcube.com/ # by John Hwang (jhwang@tavon.org),http://github.com/tavon # by Yonghwan SO(please insert your email), last update at 2009-09-11 -# last update at 2010-01-23 by Kihyun Yoon +# last update at 2010-09-06 by Kihyun Yoon ko: direction: ltr date: @@ -959,13 +959,15 @@ ko: error_unable_to_connect: 연결할 수 없습니다(({{value}}) error_can_not_remove_role: 이 역할은 현재 사용 중이이서 삭제할 수 없습니다. error_can_not_delete_tracker: 이 유형의 일감들이 있에서 삭제할 수 없습니다. - field_principal: Principal - label_my_page_block: My page block + field_principal: 신원 + label_my_page_block: 내 페이지 출력화면 notice_failed_to_save_members: "Failed to save member(s): {{errors}}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar + text_zoom_out: 더 작게 + text_zoom_in: 더 크게 + notice_unable_delete_time_entry: 시간 기록 항목을 삭제할 수 없습니다. + label_overall_spent_time: 총 소요시간 + field_time_entries: 기록된 시간 + project_module_gantt: Gantt 챠트 + project_module_calendar: 달력 + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 9a3684e91..b12a1107b 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -977,3 +977,5 @@ lt: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 0ec9b4f4f..271140cce 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -908,3 +908,5 @@ lv: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/mk.yml b/config/locales/mk.yml index c680f900c..c98761865 100644 --- a/config/locales/mk.yml +++ b/config/locales/mk.yml @@ -913,3 +913,5 @@ mk: enumeration_activities: Активности (следење на време) enumeration_system_activity: Системска активност + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/mn.yml b/config/locales/mn.yml index 73791bb74..b92538a47 100644 --- a/config/locales/mn.yml +++ b/config/locales/mn.yml @@ -914,3 +914,5 @@ mn: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/nl.yml b/config/locales/nl.yml index c55dadb47..e13e67ddf 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -895,3 +895,5 @@ nl: field_time_entries: Log tijd project_module_gantt: Gantt project_module_calendar: Kalender + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/no.yml b/config/locales/no.yml index b1746fd41..cc95dae7d 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -904,3 +904,5 @@ field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/pl.yml b/config/locales/pl.yml index d48b3d935..6621ad39e 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -96,8 +96,8 @@ pl: few: "ponad {{count}} lata" other: "ponad {{count}} lat" almost_x_years: - one: "almost 1 year" - other: "almost {{count}} years" + one: "prawie rok" + other: "prawie {{count}} lata" activerecord: errors: @@ -122,14 +122,14 @@ pl: greater_than: "musi być większe niż {{count}}" greater_than_or_equal_to: "musi być większe lub równe {{count}}" equal_to: "musi być równe {{count}}" - less_than: "musie być mniejsze niż {{count}}" + less_than: "musi być mniejsze niż {{count}}" less_than_or_equal_to: "musi być mniejsze lub równe {{count}}" odd: "musi być nieparzyste" even: "musi być parzyste" greater_than_start_date: "musi być większe niż początkowa data" not_same_project: "nie należy do tego samego projektu" circular_dependency: "Ta relacja może wytworzyć kołową zależność" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + cant_link_an_issue_with_a_descendant: "Zagadnienie nie może zostać powiązane z jednym z własnych podzagadnień" support: array: @@ -160,19 +160,19 @@ pl: button_edit: Edytuj button_list: Lista button_lock: Zablokuj - button_log_time: Log czasu + button_log_time: Dziennik button_login: Login button_move: Przenieś button_quote: Cytuj button_rename: Zmień nazwę button_reply: Odpowiedz button_reset: Resetuj - button_rollback: Przywróc do tej wersji + button_rollback: Przywróć do tej wersji button_save: Zapisz button_sort: Sortuj button_submit: Wyślij button_test: Testuj - button_unarchive: Przywróc z archiwum + button_unarchive: Przywróć z archiwum button_uncheck_all: Odznacz wszystko button_unlock: Odblokuj button_unwatch: Nie obserwuj @@ -539,12 +539,12 @@ pl: label_project_latest: Ostatnie projekty label_project_new: Nowy projekt label_project_plural234: Projekty - label_project_plural5: Projekty + label_project_plural5: Projektów label_project_plural: Projekty label_x_projects: - zero: no projects - one: 1 project - other: "{{count}} projects" + zero: brak projektów + one: jeden projekt + other: "{{count}} projektów" label_public_projects: Projekty publiczne label_query: Kwerenda label_query_new: Nowa kwerenda @@ -590,7 +590,7 @@ pl: label_sort_highest: Przesuń na górę label_sort_lower: Do dołu label_sort_lowest: Przesuń na dół - label_spent_time: Spędzony czas + label_spent_time: Przepracowany czas label_start_to_end: początek do końca label_start_to_start: początek do początku label_statistics: Statystyki @@ -602,7 +602,7 @@ pl: label_this_month: ten miesiąc label_this_week: ten tydzień label_this_year: ten rok - label_time_tracking: Śledzenie czasu + label_time_tracking: Śledzenie czasu pracy label_today: dzisiaj label_topic_plural: Tematy label_total: Ogółem @@ -684,11 +684,11 @@ pl: permission_edit_messages: Edycja wiadomości permission_edit_own_issue_notes: Edycja własnych notatek permission_edit_own_messages: Edycja własnych wiadomości - permission_edit_own_time_entries: Edycja własnego logu czasu + permission_edit_own_time_entries: Edycja własnego dziennika permission_edit_project: Edycja projektów - permission_edit_time_entries: Edycja logów czasu + permission_edit_time_entries: Edycja wpisów dziennika permission_edit_wiki_pages: Edycja stron wiki - permission_log_time: Zapisywanie spędzonego czasu + permission_log_time: Zapisywanie przepracowanego czasu permission_manage_boards: Zarządzanie forami permission_manage_categories: Zarządzanie kategoriami zaganień permission_manage_documents: Zarządzanie dokumentami @@ -712,7 +712,7 @@ pl: permission_view_gantt: Podgląd diagramu Gantta permission_view_issue_watchers: Podgląd listy obserwatorów permission_view_messages: Podgląd wiadomości - permission_view_time_entries: Podgląd spędzonego czasu + permission_view_time_entries: Podgląd przepracowanego czasu permission_view_wiki_edits: Podgląd historii wiki permission_view_wiki_pages: Podgląd wiki project_module_boards: Fora @@ -721,7 +721,7 @@ pl: project_module_issue_tracking: Śledzenie zagadnień project_module_news: Komunikaty project_module_repository: Repozytorium - project_module_time_tracking: Śledzenie czasu + project_module_time_tracking: Śledzenie czasu pracy project_module_wiki: Wiki setting_activity_days_default: Dni wyświetlane w aktywności projektu setting_app_subtitle: Podtytuł aplikacji @@ -743,7 +743,7 @@ pl: setting_feeds_limit: Limit danych RSS setting_gravatar_enabled: Używaj ikon użytkowników Gravatar setting_host_name: Nazwa hosta i ścieżka - setting_issue_list_default_columns: Domyślne kolumny wiświetlane na liście zagadnień + setting_issue_list_default_columns: Domyślne kolumny wyświetlane na liście zagadnień setting_issues_export_limit: Limit eksportu zagadnień setting_login_required: Identyfikacja wymagana setting_mail_from: Adres email wysyłki @@ -765,20 +765,20 @@ pl: status_locked: zablokowany status_registered: zarejestrowany text_are_you_sure: Jesteś pewien ? - text_assign_time_entries_to_project: Przypisz logowany czas do projektu + text_assign_time_entries_to_project: Przypisz wpisy dziennika do projektu text_caracters_maximum: "{{count}} znaków maksymalnie." text_caracters_minimum: "Musi być nie krótsze niż {{count}} znaków." text_comma_separated: Wielokrotne wartości dozwolone (rozdzielone przecinkami). text_default_administrator_account_changed: Zmieniono domyślne hasło administratora - text_destroy_time_entries: Usuń zalogowany czas - text_destroy_time_entries_question: Zalogowano {{hours}} godzin przy zagadnieniu, które chcesz usunąć. Co chcesz zrobić? + text_destroy_time_entries: Usuń wpisy dziennika + text_destroy_time_entries_question: Przepracowano {{hours}} godzin przy zagadnieniu, które chcesz usunąć. Co chcesz zrobić? text_email_delivery_not_configured: "Dostarczanie poczty elektronicznej nie zostało skonfigurowane, więc powiadamianie jest nieaktywne.\nSkonfiguruj serwer SMTP w config/email.yml a następnie zrestartuj aplikację i uaktywnij to." text_enumeration_category_reassign_to: 'Zmień przypisanie na tą wartość:' text_enumeration_destroy_question: "{{count}} obiektów jest przypisana do tej wartości." text_file_repository_writable: Zapisywalne repozytorium plików text_issue_added: "Zagadnienie {{id}} zostało wprowadzone (by {{author}})." text_issue_category_destroy_assignments: Usuń przydziały kategorii - text_issue_category_destroy_question: "Zagadnienia ({{count}}) są przypisane do tej kategorii. Co chcesz uczynić?" + text_issue_category_destroy_question: "Zagadnienia ({{count}}) są przypisane do tej kategorii. Co chcesz zrobić?" text_issue_category_reassign_to: Przydziel zagadnienie do tej kategorii text_issue_updated: "Zagadnienie {{id}} zostało zaktualizowane (by {{author}})." text_issues_destroy_confirmation: 'Czy jestes pewien, że chcesz usunąć wskazane zagadnienia?' @@ -787,9 +787,9 @@ pl: text_load_default_configuration: Załaduj domyślną konfigurację text_min_max_length_info: 0 oznacza brak restrykcji text_no_configuration_data: "Role użytkowników, typy zagadnień, statusy zagadnień oraz przepływ pracy nie zostały jeszcze skonfigurowane.\nJest wysoce rekomendowane by załadować domyślną konfigurację. Po załadowaniu będzie możliwość edycji tych danych." - text_project_destroy_confirmation: Jesteś pewien, że chcesz usunąć ten projekt i wszyskie powiązane dane? + text_project_destroy_confirmation: Jesteś pewien, że chcesz usunąć ten projekt i wszystkie powiązane dane? text_project_identifier_info: 'Małe litery (a-z), liczby i myślniki dozwolone.
    Raz zapisany, identyfikator nie może być zmieniony.' - text_reassign_time_entries: 'Przepnij zalogowany czas do tego zagadnienia:' + text_reassign_time_entries: 'Przepnij przepracowany czas do tego zagadnienia:' text_regexp_info: np. ^[A-Z0-9]+$ text_repository_usernames_mapping: "Wybierz lub uaktualnij przyporządkowanie użytkowników Redmine do użytkowników repozytorium.\nUżytkownicy z taką samą nazwą lub adresem email są przyporządkowani automatycznie." text_rmagick_available: RMagick dostępne (opcjonalnie) @@ -800,9 +800,9 @@ pl: text_tip_task_begin_day: zadanie zaczynające się dzisiaj text_tip_task_begin_end_day: zadanie zaczynające i kończące się dzisiaj text_tip_task_end_day: zadanie kończące się dzisiaj - text_tracker_no_workflow: Brak przepływu zefiniowanego dla tego typu zagadnienia + text_tracker_no_workflow: Brak przepływu zdefiniowanego dla tego typu zagadnienia text_unallowed_characters: Niedozwolone znaki - text_user_mail_option: "W przypadku niezaznaczonych projektów, będziesz otrzymywał powiadomienia tylko na temat zagadnien, które obserwujesz, lub w których bierzesz udział (np. jesteś autorem lub adresatem)." + text_user_mail_option: "W przypadku niezaznaczonych projektów, będziesz otrzymywał powiadomienia tylko na temat zagadnień, które obserwujesz, lub w których bierzesz udział (np. jesteś autorem lub adresatem)." text_user_wrote: "{{value}} napisał:" text_wiki_destroy_confirmation: Jesteś pewien, że chcesz usunąć to wiki i całą jego zawartość ? text_workflow_edit: Zaznacz rolę i typ zagadnienia do edycji przepływu @@ -818,7 +818,7 @@ pl: button_create_and_continue: Stwórz i dodaj kolejne text_custom_field_possible_values_info: 'Każda wartość w osobnej linii' setting_repository_log_display_limit: Maksymalna liczba rewizji pokazywanych w logu pliku - setting_file_max_size_displayed: Maksymalny rozmiar plików tekstowych zagnieżdżanych w stronie + setting_file_max_size_displayed: Maksymalny rozmiar plików tekstowych osadzanych w stronie field_watcher: Obserwator setting_openid: Logowanie i rejestracja przy użyciu OpenID field_identity_url: Identyfikator OpenID (URL) @@ -830,10 +830,10 @@ pl: label_date_from_to: Od {{start}} do {{end}} label_greater_or_equal: ">=" label_less_or_equal: <= - text_wiki_page_destroy_question: This page has {{descendants}} child page(s) and descendant(s). What do you want to do? - text_wiki_page_reassign_children: Reassign child pages to this parent page - text_wiki_page_nullify_children: Keep child pages as root pages - text_wiki_page_destroy_children: Delete child pages and all their descendants + text_wiki_page_destroy_question: Ta strona posiada podstrony ({{descendants}}). Co chcesz zrobić? + text_wiki_page_reassign_children: Podepnij je do strony nadrzędnej względem usuwanej + text_wiki_page_nullify_children: Przesuń je na szczyt hierarchii + text_wiki_page_destroy_children: Usuń wszystkie podstrony setting_password_min_length: Minimalna długość hasła field_group_by: Grupuj wyniki wg mail_subject_wiki_content_updated: "Strona wiki '{{page}}' została uaktualniona" @@ -845,7 +845,7 @@ pl: permission_add_project: Tworzenie projektu setting_new_project_user_role_id: Rola nadawana twórcom projektów, którzy nie posiadają uprawnień administatora label_view_all_revisions: Pokaż wszystkie rewizje - label_tag: Tag + label_tag: Słowo kluczowe label_branch: Gałąź error_no_tracker_in_project: Projekt nie posiada powiązanych typów zagadnień. Sprawdź ustawienia projektu. error_no_default_issue_status: Nie zdefiniowano domyślnego statusu zagadnień. Sprawdź konfigurację (Przejdź do "Administracja -> Statusy zagadnień). @@ -855,7 +855,7 @@ pl: label_group_plural: Grupy label_group: Grupa label_group_new: Nowa grupa - label_time_entry_plural: Spędzony czas + label_time_entry_plural: Przepracowany czas text_journal_added: "Dodano {{label}} {{value}}" field_active: Aktywne enumeration_system_activity: Aktywność Systemowa @@ -913,24 +913,26 @@ pl: label_board_locked: Zamknięta permission_export_wiki_pages: Eksport stron wiki permission_manage_project_activities: Zarządzanie aktywnościami projektu - setting_cache_formatted_text: Cache formatted text - error_unable_delete_issue_status: Unable to delete issue status - label_profile: Profile - permission_manage_subtasks: Manage subtasks - field_parent_issue: Parent task - label_subtask_plural: Subtasks - label_project_copy_notifications: Send email notifications during the project copy - error_can_not_delete_custom_field: Unable to delete custom field - error_unable_to_connect: Unable to connect ({{value}}) - error_can_not_remove_role: This role is in use and can not be deleted. - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): {{errors}}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar + setting_cache_formatted_text: Buforuj sformatowany tekst + error_unable_delete_issue_status: Nie można usunąć statusu zagadnienia + label_profile: Profil + permission_manage_subtasks: Zarządzanie podzagadnieniami + field_parent_issue: Zagadnienie nadrzędne + label_subtask_plural: Podzagadnienia + label_project_copy_notifications: Wyślij powiadomienia mailowe przy kopiowaniu projektu + error_can_not_delete_custom_field: Nie można usunąć tego pola + error_unable_to_connect: Nie można połączyć ({{value}}) + error_can_not_remove_role: Ta rola przypisana jest niektórym użytkownikom i nie może zostać usunięta. + error_can_not_delete_tracker: Ten typ przypisany jest do części zagadnień i nie może zostać usunięty. + field_principal: Przełożony + label_my_page_block: Elementy + notice_failed_to_save_members: "Nie można zapisać uczestników: {{errors}}." + text_zoom_out: Zmniejsz czcionkę + text_zoom_in: Powiększ czcionkę + notice_unable_delete_time_entry: Nie można usunąć wpisu z dziennika. + label_overall_spent_time: Przepracowany czas + field_time_entries: Dziennik + project_module_gantt: Diagram Gantta + project_module_calendar: Kalendarz + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index c41e4e0dc..547074083 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -143,7 +143,7 @@ pt-BR: greater_than_start_date: "deve ser maior que a data inicial" not_same_project: "não pertence ao mesmo projeto" circular_dependency: "Esta relação geraria uma dependência circular" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + cant_link_an_issue_with_a_descendant: "Uma tarefa não pode ser relaciona a uma de suas subtarefas" actionview_instancetag_blank_option: Selecione @@ -937,3 +937,5 @@ pt-BR: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 3127f4044..28822061a 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -921,3 +921,5 @@ pt: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/ro.yml b/config/locales/ro.yml index a0458932c..85fea1399 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -906,3 +906,5 @@ ro: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 586e2fbcc..806b5c0ac 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -294,6 +294,7 @@ ru: field_admin: Администратор field_assignable: Задача может быть назначена этой роли field_assigned_to: Назначена + field_assigned_to_role: Роль участника field_attr_firstname: Имя field_attr_lastname: Фамилия field_attr_login: Атрибут Регистрация @@ -343,6 +344,7 @@ ru: field_mail: Email field_mail_notification: Уведомления по email field_max_length: Максимальная длина + field_member_of_group: Группа участника field_min_length: Минимальная длина field_name: Имя field_new_password: Новый пароль diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 52c57c79c..3fd6c41b2 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -908,3 +908,5 @@ sk: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 8ee68f15d..68d8bb089 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -909,3 +909,5 @@ sl: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/sr-YU.yml b/config/locales/sr-YU.yml index e5df0e427..c2ac531c9 100644 --- a/config/locales/sr-YU.yml +++ b/config/locales/sr-YU.yml @@ -68,7 +68,7 @@ sr-YU: number: format: - separator: "." + separator: "," delimiter: "" precision: 3 human: @@ -910,6 +910,8 @@ sr-YU: enumeration_activities: Aktivnosti (praćenje vremena) enumeration_system_activity: Sistemska aktivnost - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar + field_time_entries: Vreme evidencije + project_module_gantt: Gantov dijagram + project_module_calendar: Kalendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 3641c7091..5b5dfd461 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -68,7 +68,7 @@ sr: number: format: - separator: "." + separator: "," delimiter: "" precision: 3 human: @@ -910,6 +910,9 @@ sr: enumeration_activities: Активности (праћење времена) enumeration_system_activity: Системска активност - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar + field_time_entries: Време евиденције + project_module_gantt: Гантов дијаграм + project_module_calendar: Календар + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role + diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 657a9adcd..8055c572d 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -958,3 +958,5 @@ sv: enumeration_doc_categories: Dokumentkategorier enumeration_activities: Aktiviteter (tidsuppföljning) enumeration_system_activity: Systemaktivitet + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/th.yml b/config/locales/th.yml index 67bf3c4cc..3e855995f 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -910,3 +910,5 @@ th: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/tr.yml b/config/locales/tr.yml index e0f23078d..43871ffe9 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -936,3 +936,5 @@ tr: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/uk.yml b/config/locales/uk.yml index d0191618b..89901d1ea 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -909,3 +909,5 @@ uk: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 66fdbc115..28351ed26 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -968,3 +968,5 @@ vi: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 56d8c9b29..8dab78787 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -177,7 +177,7 @@ greater_than_start_date: "必須在起始日期之後" not_same_project: "不屬於同一個專案" circular_dependency: "這個關聯會導致環狀相依" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" + cant_link_an_issue_with_a_descendant: "項目無法被連結至自己的子項目" # You can define own errors for models or model attributes. # The values :model, :attribute and :value are always available for interpolation. @@ -998,3 +998,5 @@ enumeration_doc_categories: 文件分類 enumeration_activities: 活動 (時間追蹤) enumeration_system_activity: 系統活動 + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role diff --git a/config/locales/zh.yml b/config/locales/zh.yml index a05d96cae..b882d277a 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -931,3 +931,5 @@ zh: field_time_entries: Log time project_module_gantt: Gantt project_module_calendar: Calendar + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role From 4b69a895df5b2bd5838b3c1e277446dd1cbc5395 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Sat, 11 Sep 2010 14:00:23 +0000 Subject: [PATCH 31/49] Fixed broken context_menu on roadmap. #6351 git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4082 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/views/versions/index.html.erb | 2 +- test/functional/versions_controller_test.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/versions/index.html.erb b/app/views/versions/index.html.erb index 100282a7a..d0c5dcac1 100644 --- a/app/views/versions/index.html.erb +++ b/app/views/versions/index.html.erb @@ -51,4 +51,4 @@ <% html_title(l(:label_roadmap)) %> -<%= context_menu :controller => 'issues', :action => 'context_menu' %> +<%= context_menu issues_context_menu_path %> diff --git a/test/functional/versions_controller_test.rb b/test/functional/versions_controller_test.rb index 5fcb1d05a..f738794ae 100644 --- a/test/functional/versions_controller_test.rb +++ b/test/functional/versions_controller_test.rb @@ -40,6 +40,8 @@ class VersionsControllerTest < ActionController::TestCase assert assigns(:versions).include?(Version.find(3)) # Completed version doesn't appear assert !assigns(:versions).include?(Version.find(1)) + # Context menu on issues + assert_select "script", :text => Regexp.new(Regexp.escape("new ContextMenu('/issues/context_menu')")) end def test_index_with_completed_versions From 12e10f6956fcd276fc3da19beb0c7bf4ade5f0da Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Sat, 11 Sep 2010 20:21:27 +0000 Subject: [PATCH 32/49] Fixed "Create and continue" redirection broken by recent changes. #6333 IssuesController#create is no more scoped under project, but IssuesController#new is, so we need to precise project_id when redirecting to "New issue" form. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4083 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/issues_controller.rb | 2 +- test/functional/issues_controller_test.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 9f58cb0a1..385bfa757 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -134,7 +134,7 @@ class IssuesController < ApplicationController call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue}) respond_to do |format| format.html { - redirect_to(params[:continue] ? { :action => 'new', :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } : + redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } : { :action => 'show', :id => @issue }) } format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) } diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 7d69db8ea..9e70233b2 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -405,7 +405,8 @@ class IssuesControllerTest < ActionController::TestCase :subject => 'This is first issue', :priority_id => 5}, :continue => '' - assert_redirected_to :controller => 'issues', :action => 'new', :issue => {:tracker_id => 3} + assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', + :issue => {:tracker_id => 3} end def test_post_create_without_custom_fields_param From 41c055363ee4826799b9ffe7d76fd399c89190d3 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Mon, 13 Sep 2010 20:35:03 +0000 Subject: [PATCH 33/49] Refactor: split FilesController#new into #new and #create. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4084 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/files_controller.rb | 23 +++++++++++------------ app/views/files/new.html.erb | 2 +- config/routes.rb | 2 +- lib/redmine.rb | 2 +- test/functional/files_controller_test.rb | 8 ++++---- test/integration/routing_test.rb | 2 +- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index fe5eb48c8..0a4903d08 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -19,19 +19,18 @@ class FilesController < ApplicationController render :layout => !request.xhr? end - # TODO: split method into new (GET) and create (POST) def new - if request.post? - container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) - attachments = Attachment.attach_files(container, params[:attachments]) - render_attachment_warning_if_needed(container) - - if !attachments.empty? && Setting.notified_events.include?('file_added') - Mailer.deliver_attachments_added(attachments[:files]) - end - redirect_to :controller => 'files', :action => 'index', :id => @project - return - end @versions = @project.versions.sort end + + def create + container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id])) + attachments = Attachment.attach_files(container, params[:attachments]) + render_attachment_warning_if_needed(container) + + if !attachments.empty? && Setting.notified_events.include?('file_added') + Mailer.deliver_attachments_added(attachments[:files]) + end + redirect_to :controller => 'files', :action => 'index', :id => @project + end end diff --git a/app/views/files/new.html.erb b/app/views/files/new.html.erb index bbb3b1733..870a315c9 100644 --- a/app/views/files/new.html.erb +++ b/app/views/files/new.html.erb @@ -2,7 +2,7 @@ <%= error_messages_for 'attachment' %>
    -<% form_tag({ :action => 'new', :id => @project }, :multipart => true, :class => "tabular") do %> +<% form_tag({ :action => 'create', :id => @project }, :multipart => true, :class => "tabular") do %> <% if @versions.any? %>

    diff --git a/config/routes.rb b/config/routes.rb index 129c3b456..152b90f54 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -196,7 +196,7 @@ ActionController::Routing::Routes.draw do |map| end project_mapper.with_options :conditions => {:method => :post} do |project_actions| - project_actions.connect 'projects/:id/files/new', :controller => 'files', :action => 'new' + project_actions.connect 'projects/:id/files/new', :controller => 'files', :action => 'create' end end diff --git a/lib/redmine.rb b/lib/redmine.rb index 526b83e65..9d59390c1 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -102,7 +102,7 @@ Redmine::AccessControl.map do |map| end map.project_module :files do |map| - map.permission :manage_files, {:files => :new}, :require => :loggedin + map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin map.permission :view_files, :files => :index, :versions => :download end diff --git a/test/functional/files_controller_test.rb b/test/functional/files_controller_test.rb index 838caee70..2f6f009e7 100644 --- a/test/functional/files_controller_test.rb +++ b/test/functional/files_controller_test.rb @@ -26,14 +26,14 @@ class FilesControllerTest < ActionController::TestCase :attributes => { :href => '/attachments/download/9/version_file.zip' } end - def test_add_file + def test_create_file set_tmp_attachments_directory @request.session[:user_id] = 2 Setting.notified_events = ['file_added'] ActionMailer::Base.deliveries.clear assert_difference 'Attachment.count' do - post :new, :id => 1, :version_id => '', + post :create, :id => 1, :version_id => '', :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} assert_response :redirect end @@ -48,13 +48,13 @@ class FilesControllerTest < ActionController::TestCase assert mail.body.include?('testfile.txt') end - def test_add_version_file + def test_create_version_file set_tmp_attachments_directory @request.session[:user_id] = 2 Setting.notified_events = ['file_added'] assert_difference 'Attachment.count' do - post :new, :id => 1, :version_id => '2', + post :create, :id => 1, :version_id => '2', :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} assert_response :redirect end diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index ac75d4d7c..1867a60b0 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -179,7 +179,7 @@ class RoutingTest < ActionController::IntegrationTest should_route :post, "/projects", :controller => 'projects', :action => 'create' should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml' - should_route :post, "/projects/33/files/new", :controller => 'files', :action => 'new', :id => '33' + should_route :post, "/projects/33/files/new", :controller => 'files', :action => 'create', :id => '33' should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64' should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64' From 1b90703157a182d37496ae4c8e3c430681abddc0 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Tue, 14 Sep 2010 16:24:07 +0000 Subject: [PATCH 34/49] Refactor: convert FilesController to a restful resource. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4085 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/files_controller.rb | 4 ++-- app/views/files/index.html.erb | 2 +- app/views/files/new.html.erb | 2 +- config/routes.rb | 7 +------ lib/redmine.rb | 2 +- test/functional/files_controller_test.rb | 6 +++--- test/integration/routing_test.rb | 6 +++--- 7 files changed, 12 insertions(+), 17 deletions(-) diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index 0a4903d08..72a4a2411 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -1,7 +1,7 @@ class FilesController < ApplicationController menu_item :files - before_filter :find_project + before_filter :find_project_by_project_id before_filter :authorize helper :sort @@ -31,6 +31,6 @@ class FilesController < ApplicationController if !attachments.empty? && Setting.notified_events.include?('file_added') Mailer.deliver_attachments_added(attachments[:files]) end - redirect_to :controller => 'files', :action => 'index', :id => @project + redirect_to project_files_path(@project) end end diff --git a/app/views/files/index.html.erb b/app/views/files/index.html.erb index 66f5d12b9..20710402b 100644 --- a/app/views/files/index.html.erb +++ b/app/views/files/index.html.erb @@ -1,5 +1,5 @@

    -<%= link_to_if_authorized l(:label_attachment_new), {:controller => 'files', :action => 'new', :id => @project}, :class => 'icon icon-add' %> +<%= link_to_if_authorized l(:label_attachment_new), new_project_file_path(@project), :class => 'icon icon-add' %>

    <%=l(:label_attachment_plural)%>

    diff --git a/app/views/files/new.html.erb b/app/views/files/new.html.erb index 870a315c9..d9d1b6ee1 100644 --- a/app/views/files/new.html.erb +++ b/app/views/files/new.html.erb @@ -2,7 +2,7 @@ <%= error_messages_for 'attachment' %>
    -<% form_tag({ :action => 'create', :id => @project }, :multipart => true, :class => "tabular") do %> +<% form_tag(project_files_path(@project), :multipart => true, :class => "tabular") do %> <% if @versions.any? %>

    diff --git a/config/routes.rb b/config/routes.rb index 152b90f54..c85f23eb2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -181,6 +181,7 @@ ActionController::Routing::Routes.draw do |map| :unarchive => :post } do |project| project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy] + project.resources :files, :only => [:index, :new, :create] end # Destroy uses a get request to prompt the user before the actual DELETE request @@ -189,15 +190,9 @@ ActionController::Routing::Routes.draw do |map| # TODO: port to be part of the resources route(s) map.with_options :controller => 'projects' do |project_mapper| project_mapper.with_options :conditions => {:method => :get} do |project_views| - project_views.connect 'projects/:id/files', :controller => 'files', :action => 'index' - project_views.connect 'projects/:id/files/new', :controller => 'files', :action => 'new' project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings' project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new' end - - project_mapper.with_options :conditions => {:method => :post} do |project_actions| - project_actions.connect 'projects/:id/files/new', :controller => 'files', :action => 'create' - end end map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity| diff --git a/lib/redmine.rb b/lib/redmine.rb index 9d59390c1..f8f564220 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -198,7 +198,7 @@ Redmine::MenuManager.map :project_menu do |menu| :if => Proc.new { |p| p.wiki && !p.wiki.new_record? } menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id, :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural - menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural + menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id menu.push :repository, { :controller => 'repositories', :action => 'show' }, :if => Proc.new { |p| p.repository && !p.repository.new_record? } menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true diff --git a/test/functional/files_controller_test.rb b/test/functional/files_controller_test.rb index 2f6f009e7..6035c4be5 100644 --- a/test/functional/files_controller_test.rb +++ b/test/functional/files_controller_test.rb @@ -12,7 +12,7 @@ class FilesControllerTest < ActionController::TestCase end def test_index - get :index, :id => 1 + get :index, :project_id => 1 assert_response :success assert_template 'index' assert_not_nil assigns(:containers) @@ -33,7 +33,7 @@ class FilesControllerTest < ActionController::TestCase ActionMailer::Base.deliveries.clear assert_difference 'Attachment.count' do - post :create, :id => 1, :version_id => '', + post :create, :project_id => 1, :version_id => '', :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} assert_response :redirect end @@ -54,7 +54,7 @@ class FilesControllerTest < ActionController::TestCase Setting.notified_events = ['file_added'] assert_difference 'Attachment.count' do - post :create, :id => 1, :version_id => '2', + post :create, :project_id => 1, :version_id => '2', :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}} assert_response :redirect end diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index 1867a60b0..38c1b2a0e 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -171,15 +171,15 @@ class RoutingTest < ActionController::IntegrationTest should_route :get, "/projects/1.xml", :controller => 'projects', :action => 'show', :id => '1', :format => 'xml' should_route :get, "/projects/4223/settings", :controller => 'projects', :action => 'settings', :id => '4223' should_route :get, "/projects/4223/settings/members", :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members' - should_route :get, "/projects/33/files", :controller => 'files', :action => 'index', :id => '33' - should_route :get, "/projects/33/files/new", :controller => 'files', :action => 'new', :id => '33' + should_route :get, "/projects/33/files", :controller => 'files', :action => 'index', :project_id => '33' + should_route :get, "/projects/33/files/new", :controller => 'files', :action => 'new', :project_id => '33' should_route :get, "/projects/33/roadmap", :controller => 'versions', :action => 'index', :project_id => '33' should_route :get, "/projects/33/activity", :controller => 'activities', :action => 'index', :id => '33' should_route :get, "/projects/33/activity.atom", :controller => 'activities', :action => 'index', :id => '33', :format => 'atom' should_route :post, "/projects", :controller => 'projects', :action => 'create' should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml' - should_route :post, "/projects/33/files/new", :controller => 'files', :action => 'create', :id => '33' + should_route :post, "/projects/33/files", :controller => 'files', :action => 'create', :project_id => '33' should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64' should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64' From 8900797adaf29adc447925b800d5457ca941795f Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Tue, 14 Sep 2010 19:02:20 +0000 Subject: [PATCH 35/49] Refactor: move method to Model. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4086 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/timelog_controller.rb | 4 ++-- app/models/time_entry.rb | 8 ++++++++ test/unit/time_entry_test.rb | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/controllers/timelog_controller.rb b/app/controllers/timelog_controller.rb index e234848d0..726c69d5b 100644 --- a/app/controllers/timelog_controller.rb +++ b/app/controllers/timelog_controller.rb @@ -260,8 +260,8 @@ private end @from, @to = @to, @from if @from && @to && @from > @to - @from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) || Date.today) - 1 - @to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) || Date.today) + @from ||= (TimeEntry.earilest_date_for_project || Date.today) - 1 + @to ||= (TimeEntry.latest_date_for_project || Date.today) end def load_available_criterias diff --git a/app/models/time_entry.rb b/app/models/time_entry.rb index 73f39f949..56801a4ca 100644 --- a/app/models/time_entry.rb +++ b/app/models/time_entry.rb @@ -81,4 +81,12 @@ class TimeEntry < ActiveRecord::Base yield end end + + def self.earilest_date_for_project + TimeEntry.minimum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) + end + + def self.latest_date_for_project + TimeEntry.maximum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) + end end diff --git a/test/unit/time_entry_test.rb b/test/unit/time_entry_test.rb index 3c135510a..a541fc41f 100644 --- a/test/unit/time_entry_test.rb +++ b/test/unit/time_entry_test.rb @@ -48,4 +48,19 @@ class TimeEntryTest < ActiveSupport::TestCase def test_hours_should_default_to_nil assert_nil TimeEntry.new.hours end + + context "#earilest_date_for_project" do + should "return the lowest spent_on value that is visible to the current user" do + User.current = nil + assert_equal "2007-03-12", TimeEntry.earilest_date_for_project.to_s + end + end + + context "#latest_date_for_project" do + should "return the highest spent_on value that is visible to the current user" do + User.current = nil + assert_equal "2007-04-22", TimeEntry.latest_date_for_project.to_s + end + end + end From cdfc57d5442f72d62437f52af480049c943ecbf8 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Tue, 14 Sep 2010 19:02:25 +0000 Subject: [PATCH 36/49] Change the TimelogController's to/from dates based on the project time entries Instead of looking for the earliest and latest time entry system wide for the dates in the form, now TimelogController will only look at the time entries for the current project (and parent/sub projects). git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4087 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/timelog_controller.rb | 4 +- app/models/project.rb | 9 +++++ app/models/time_entry.rb | 16 ++++++-- test/functional/timelog_controller_test.rb | 6 +-- test/unit/time_entry_test.rb | 45 +++++++++++++++++++--- 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/app/controllers/timelog_controller.rb b/app/controllers/timelog_controller.rb index 726c69d5b..45f41ae5e 100644 --- a/app/controllers/timelog_controller.rb +++ b/app/controllers/timelog_controller.rb @@ -260,8 +260,8 @@ private end @from, @to = @to, @from if @from && @to && @from > @to - @from ||= (TimeEntry.earilest_date_for_project || Date.today) - 1 - @to ||= (TimeEntry.latest_date_for_project || Date.today) + @from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today) + @to ||= (TimeEntry.latest_date_for_project(@project) || Date.today) end def load_available_criterias diff --git a/app/models/project.rb b/app/models/project.rb index 40898a34e..4b0236b37 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -493,6 +493,15 @@ class Project < ActiveRecord::Base enabled_modules.clear end end + + # Returns an array of projects that are in this project's hierarchy + # + # Example: parents, children, siblings + def hierarchy + parents = project.self_and_ancestors || [] + descendants = project.descendants || [] + project_hierarchy = parents | descendants # Set union + end # Returns an auto-generated project identifier based on the last identifier used def self.next_identifier diff --git a/app/models/time_entry.rb b/app/models/time_entry.rb index 56801a4ca..9bf970891 100644 --- a/app/models/time_entry.rb +++ b/app/models/time_entry.rb @@ -82,11 +82,19 @@ class TimeEntry < ActiveRecord::Base end end - def self.earilest_date_for_project - TimeEntry.minimum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) + def self.earilest_date_for_project(project=nil) + finder_conditions = ARCondition.new(Project.allowed_to_condition(User.current, :view_time_entries)) + if project + finder_conditions << ["project_id IN (?)", project.hierarchy.collect(&:id)] + end + TimeEntry.minimum(:spent_on, :include => :project, :conditions => finder_conditions.conditions) end - def self.latest_date_for_project - TimeEntry.maximum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) + def self.latest_date_for_project(project=nil) + finder_conditions = ARCondition.new(Project.allowed_to_condition(User.current, :view_time_entries)) + if project + finder_conditions << ["project_id IN (?)", project.hierarchy.collect(&:id)] + end + TimeEntry.maximum(:spent_on, :include => :project, :conditions => finder_conditions.conditions) end end diff --git a/test/functional/timelog_controller_test.rb b/test/functional/timelog_controller_test.rb index 9e30a0ae8..0559a95ca 100644 --- a/test/functional/timelog_controller_test.rb +++ b/test/functional/timelog_controller_test.rb @@ -283,7 +283,7 @@ class TimelogControllerTest < ActionController::TestCase assert_not_nil assigns(:total_hours) assert_equal "162.90", "%.2f" % assigns(:total_hours) # display all time by default - assert_equal '2007-03-11'.to_date, assigns(:from) + assert_equal '2007-03-12'.to_date, assigns(:from) assert_equal '2007-04-22'.to_date, assigns(:to) end @@ -325,8 +325,8 @@ class TimelogControllerTest < ActionController::TestCase assert_equal 2, assigns(:entries).size assert_not_nil assigns(:total_hours) assert_equal 154.25, assigns(:total_hours) - # display all time by default - assert_equal '2007-03-11'.to_date, assigns(:from) + # display all time based on what's been logged + assert_equal '2007-03-12'.to_date, assigns(:from) assert_equal '2007-04-22'.to_date, assigns(:to) end diff --git a/test/unit/time_entry_test.rb b/test/unit/time_entry_test.rb index a541fc41f..3069f9368 100644 --- a/test/unit/time_entry_test.rb +++ b/test/unit/time_entry_test.rb @@ -50,17 +50,50 @@ class TimeEntryTest < ActiveSupport::TestCase end context "#earilest_date_for_project" do - should "return the lowest spent_on value that is visible to the current user" do + setup do User.current = nil - assert_equal "2007-03-12", TimeEntry.earilest_date_for_project.to_s + @public_project = Project.generate!(:is_public => true) + @issue = Issue.generate_for_project!(@public_project) + TimeEntry.generate!(:spent_on => '2010-01-01', + :issue => @issue, + :project => @public_project) end + + context "without a project" do + should "return the lowest spent_on value that is visible to the current user" do + assert_equal "2007-03-12", TimeEntry.earilest_date_for_project.to_s + end + end + + context "with a project" do + should "return the lowest spent_on value that is visible to the current user for that project and it's subprojects only" do + assert_equal "2010-01-01", TimeEntry.earilest_date_for_project(@public_project).to_s + end + end + end context "#latest_date_for_project" do - should "return the highest spent_on value that is visible to the current user" do + setup do User.current = nil - assert_equal "2007-04-22", TimeEntry.latest_date_for_project.to_s + @public_project = Project.generate!(:is_public => true) + @issue = Issue.generate_for_project!(@public_project) + TimeEntry.generate!(:spent_on => '2010-01-01', + :issue => @issue, + :project => @public_project) end - end - + + context "without a project" do + should "return the highest spent_on value that is visible to the current user" do + assert_equal "2010-01-01", TimeEntry.latest_date_for_project.to_s + end + end + + context "with a project" do + should "return the highest spent_on value that is visible to the current user for that project and it's subprojects only" do + project = Project.find(1) + assert_equal "2007-04-22", TimeEntry.latest_date_for_project(project).to_s + end + end + end end From be6e0927f3ce95e751bd3c6f4a61de6979898012 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Wed, 15 Sep 2010 16:50:25 +0000 Subject: [PATCH 37/49] Refactor: Split VersionsController#edit into #edit and #update git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4088 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/versions_controller.rb | 5 ++++- app/views/versions/edit.rhtml | 2 +- config/routes.rb | 3 +++ lib/redmine.rb | 2 +- test/functional/versions_controller_test.rb | 4 ++-- test/integration/routing_test.rb | 6 ++++++ 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index dd01da95b..96c240c50 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -87,8 +87,11 @@ class VersionsController < ApplicationController end end end - + def edit + end + + def update if request.post? && params[:version] attributes = params[:version].dup attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing']) diff --git a/app/views/versions/edit.rhtml b/app/views/versions/edit.rhtml index 1556ebba1..8c437eb5e 100644 --- a/app/views/versions/edit.rhtml +++ b/app/views/versions/edit.rhtml @@ -1,6 +1,6 @@

    <%=l(:label_version)%>

    -<% labelled_tabular_form_for :version, @version, :url => { :action => 'edit' } do |f| %> +<% labelled_tabular_form_for :version, @version, :url => { :action => 'update', :id => @version } do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <%= submit_tag l(:button_save) %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index c85f23eb2..9f9a4e3f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -205,7 +205,10 @@ ActionController::Routing::Routes.draw do |map| map.with_options :controller => 'versions' do |versions| versions.connect 'projects/:project_id/versions/new', :action => 'new' versions.connect 'projects/:project_id/roadmap', :action => 'index' + versions.connect 'versions/:action/:id', :conditions => {:method => :get} + versions.with_options :conditions => {:method => :post} do |version_actions| + version_actions.connect 'versions/update/:id', :action => 'update' version_actions.connect 'projects/:project_id/versions/close_completed', :action => 'close_completed' end end diff --git a/lib/redmine.rb b/lib/redmine.rb index f8f564220..2b27c206b 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -50,7 +50,7 @@ Redmine::AccessControl.map do |map| map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member map.permission :select_project_modules, {:projects => :modules}, :require => :member map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member - map.permission :manage_versions, {:projects => :settings, :versions => [:new, :edit, :close_completed, :destroy]}, :require => :member + map.permission :manage_versions, {:projects => :settings, :versions => [:new, :edit, :update, :close_completed, :destroy]}, :require => :member map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member map.project_module :issue_tracking do |map| diff --git a/test/functional/versions_controller_test.rb b/test/functional/versions_controller_test.rb index f738794ae..9e8cdf90d 100644 --- a/test/functional/versions_controller_test.rb +++ b/test/functional/versions_controller_test.rb @@ -113,9 +113,9 @@ class VersionsControllerTest < ActionController::TestCase assert_not_nil Version.find_by_status('closed') end - def test_post_edit + def test_post_update @request.session[:user_id] = 2 - post :edit, :id => 2, + post :update, :id => 2, :version => { :name => 'New version name', :effective_date => Date.today.strftime("%Y-%m-%d")} assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index 38c1b2a0e..ba4948008 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -251,10 +251,16 @@ class RoutingTest < ActionController::IntegrationTest should_route :post, "/users/567/memberships/12/destroy", :controller => 'users', :action => 'destroy_membership', :id => '567', :membership_id => '12' end + # TODO: should they all be scoped under /projects/:project_id ? context "versions" do should_route :get, "/projects/foo/versions/new", :controller => 'versions', :action => 'new', :project_id => 'foo' + should_route :get, "/versions/show/1", :controller => 'versions', :action => 'show', :id => '1' + should_route :get, "/versions/edit/1", :controller => 'versions', :action => 'edit', :id => '1' should_route :post, "/projects/foo/versions/new", :controller => 'versions', :action => 'new', :project_id => 'foo' + should_route :post, "/versions/update/1", :controller => 'versions', :action => 'update', :id => '1' + + should_route :delete, "/versions/destroy/1", :controller => 'versions', :action => 'destroy', :id => '1' end context "wiki (singular, project's pages)" do From bde8ab84f27426e425ba2d6a7e733571fcd5242e Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Wed, 15 Sep 2010 22:43:30 +0000 Subject: [PATCH 38/49] Use the full path to the partials When trying to use the issue form in a plugin, it would try to use the relative path to the partials which were incorrect. Example: would render 'my_plugin_views/attributes' git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4089 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/views/issues/_attributes.rhtml | 2 +- app/views/issues/_form.rhtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/issues/_attributes.rhtml b/app/views/issues/_attributes.rhtml index 455eb77b2..f9f3f94ea 100644 --- a/app/views/issues/_attributes.rhtml +++ b/app/views/issues/_attributes.rhtml @@ -40,6 +40,6 @@
    -<%= render :partial => 'form_custom_fields' %> +<%= render :partial => 'issues/form_custom_fields' %> <% end %> diff --git a/app/views/issues/_form.rhtml b/app/views/issues/_form.rhtml index 2bee7d36c..0b1a07499 100644 --- a/app/views/issues/_form.rhtml +++ b/app/views/issues/_form.rhtml @@ -20,7 +20,7 @@
    - <%= render :partial => 'attributes' %> + <%= render :partial => 'issues/attributes' %>
    <% if @issue.new_record? %> From 969bf2107b2ebcdb480cbb56f523ecb72643146d Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Thu, 16 Sep 2010 18:27:33 +0000 Subject: [PATCH 39/49] Refactor: split VersionsController#new into #new and #create git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4090 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/versions_controller.rb | 19 +++++++++++++++---- app/views/issues/_attributes.rhtml | 2 +- app/views/versions/new.html.erb | 4 ++-- config/routes.rb | 1 + lib/redmine.rb | 2 +- test/functional/versions_controller_test.rb | 8 ++++---- test/integration/routing_test.rb | 2 +- 7 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 96c240c50..331935d9b 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -18,9 +18,9 @@ class VersionsController < ApplicationController menu_item :roadmap model_object Version - before_filter :find_model_object, :except => [:index, :new, :close_completed] - before_filter :find_project_from_association, :except => [:index, :new, :close_completed] - before_filter :find_project, :only => [:index, :new, :close_completed] + before_filter :find_model_object, :except => [:index, :new, :create, :close_completed] + before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed] + before_filter :find_project, :only => [:index, :new, :create, :close_completed] before_filter :authorize helper :custom_fields @@ -63,6 +63,17 @@ class VersionsController < ApplicationController attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing']) @version.attributes = attributes end + end + + def create + # TODO: refactor with code above in #new + @version = @project.versions.build + if params[:version] + attributes = params[:version].dup + attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing']) + @version.attributes = attributes + end + if request.post? if @version.save respond_to do |format| @@ -79,7 +90,7 @@ class VersionsController < ApplicationController end else respond_to do |format| - format.html + format.html { render :action => 'new' } format.js do render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) } end diff --git a/app/views/issues/_attributes.rhtml b/app/views/issues/_attributes.rhtml index f9f3f94ea..e10858b09 100644 --- a/app/views/issues/_attributes.rhtml +++ b/app/views/issues/_attributes.rhtml @@ -23,7 +23,7 @@ <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'), l(:label_version_new), 'version[name]', - {:controller => 'versions', :action => 'new', :project_id => @project}, + {:controller => 'versions', :action => 'create', :project_id => @project}, :title => l(:label_version_new), :tabindex => 200) if authorize_for('versions', 'new') %>

    diff --git a/app/views/versions/new.html.erb b/app/views/versions/new.html.erb index a7ef03e76..9fbd94178 100644 --- a/app/views/versions/new.html.erb +++ b/app/views/versions/new.html.erb @@ -1,6 +1,6 @@

    <%=l(:label_version_new)%>

    -<% labelled_tabular_form_for :version, @version, :url => { :action => 'new' } do |f| %> +<% labelled_tabular_form_for :version, @version, :url => { :action => 'create', :project_id => @project } do |f| %> <%= render :partial => 'versions/form', :locals => { :f => f } %> <%= submit_tag l(:button_create) %> -<% end %> \ No newline at end of file +<% end %> diff --git a/config/routes.rb b/config/routes.rb index 9f9a4e3f0..3197df01a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -208,6 +208,7 @@ ActionController::Routing::Routes.draw do |map| versions.connect 'versions/:action/:id', :conditions => {:method => :get} versions.with_options :conditions => {:method => :post} do |version_actions| + version_actions.connect 'projects/:project_id/versions', :action => 'create' version_actions.connect 'versions/update/:id', :action => 'update' version_actions.connect 'projects/:project_id/versions/close_completed', :action => 'close_completed' end diff --git a/lib/redmine.rb b/lib/redmine.rb index 2b27c206b..8acae48a8 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -50,7 +50,7 @@ Redmine::AccessControl.map do |map| map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member map.permission :select_project_modules, {:projects => :modules}, :require => :member map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member - map.permission :manage_versions, {:projects => :settings, :versions => [:new, :edit, :update, :close_completed, :destroy]}, :require => :member + map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member map.project_module :issue_tracking do |map| diff --git a/test/functional/versions_controller_test.rb b/test/functional/versions_controller_test.rb index 9e8cdf90d..1b33ba816 100644 --- a/test/functional/versions_controller_test.rb +++ b/test/functional/versions_controller_test.rb @@ -75,10 +75,10 @@ class VersionsControllerTest < ActionController::TestCase assert_tag :tag => 'h2', :content => /1.0/ end - def test_new + def test_create @request.session[:user_id] = 2 # manager assert_difference 'Version.count' do - post :new, :project_id => '1', :version => {:name => 'test_add_version'} + post :create, :project_id => '1', :version => {:name => 'test_add_version'} end assert_redirected_to '/projects/ecookbook/settings/versions' version = Version.find_by_name('test_add_version') @@ -86,10 +86,10 @@ class VersionsControllerTest < ActionController::TestCase assert_equal 1, version.project_id end - def test_new_from_issue_form + def test_create_from_issue_form @request.session[:user_id] = 2 # manager assert_difference 'Version.count' do - xhr :post, :new, :project_id => '1', :version => {:name => 'test_add_version_from_issue_form'} + xhr :post, :create, :project_id => '1', :version => {:name => 'test_add_version_from_issue_form'} end assert_response :success assert_select_rjs :replace, 'issue_fixed_version_id' diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index ba4948008..794a7780e 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -257,7 +257,7 @@ class RoutingTest < ActionController::IntegrationTest should_route :get, "/versions/show/1", :controller => 'versions', :action => 'show', :id => '1' should_route :get, "/versions/edit/1", :controller => 'versions', :action => 'edit', :id => '1' - should_route :post, "/projects/foo/versions/new", :controller => 'versions', :action => 'new', :project_id => 'foo' + should_route :post, "/projects/foo/versions", :controller => 'versions', :action => 'create', :project_id => 'foo' should_route :post, "/versions/update/1", :controller => 'versions', :action => 'update', :id => '1' should_route :delete, "/versions/destroy/1", :controller => 'versions', :action => 'destroy', :id => '1' From c4d44af54c967d77b1f908de816453aed05a72e0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Thu, 16 Sep 2010 19:28:04 +0000 Subject: [PATCH 40/49] Fixed non standard SQL syntax. #6413 Contributed by Juan G git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4091 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/issue.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/issue.rb b/app/models/issue.rb index 80db48108..2b7362589 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -846,7 +846,7 @@ class Issue < ActiveRecord::Base j.id as #{select_field}, count(i.id) as total from - #{Issue.table_name} i, #{IssueStatus.table_name} s, #{joins} as j + #{Issue.table_name} i, #{IssueStatus.table_name} s, #{joins} j where i.status_id=s.id and #{where} From 0d967c0572762026210db65632dea8649e308ad0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Thu, 16 Sep 2010 21:26:30 +0000 Subject: [PATCH 41/49] Fix links to activity pages broken with r4047. #6392 Contributed by Felix Schafer git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4092 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/helpers/application_helper.rb | 2 +- app/views/wiki/special_page_index.rhtml | 4 ++-- special_page_index.rhtml | 26 +++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 special_page_index.rhtml diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 34b17c760..eecbc5b91 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -326,7 +326,7 @@ module ApplicationHelper def time_tag(time) text = distance_of_time_in_words(Time.now, time) if @project - link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time)) + link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time)) else content_tag('acronym', text, :title => format_time(time)) end diff --git a/app/views/wiki/special_page_index.rhtml b/app/views/wiki/special_page_index.rhtml index b3ad10019..e2d1e44ad 100644 --- a/app/views/wiki/special_page_index.rhtml +++ b/app/views/wiki/special_page_index.rhtml @@ -16,11 +16,11 @@ <% unless @pages.empty? %> <% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %> + <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %> <%= f.link_to('HTML', :url => {:action => 'special', :page => 'export'}) if User.current.allowed_to?(:export_wiki_pages, @project) %> <% end %> <% end %> <% content_for :header_tags do %> -<%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %> +<%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %> <% end %> diff --git a/special_page_index.rhtml b/special_page_index.rhtml new file mode 100644 index 000000000..e2d1e44ad --- /dev/null +++ b/special_page_index.rhtml @@ -0,0 +1,26 @@ +
    +<%= watcher_tag(@wiki, User.current) %> +
    + +

    <%= l(:label_index_by_title) %>

    + +<% if @pages.empty? %> +

    <%= l(:label_no_data) %>

    +<% end %> + +<%= render_page_hierarchy(@pages_by_parent_id) %> + +<% content_for :sidebar do %> + <%= render :partial => 'sidebar' %> +<% end %> + +<% unless @pages.empty? %> +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %> + <%= f.link_to('HTML', :url => {:action => 'special', :page => 'export'}) if User.current.allowed_to?(:export_wiki_pages, @project) %> +<% end %> +<% end %> + +<% content_for :header_tags do %> +<%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %> +<% end %> From 35dba0f412b7aa29a839869fdb9e020585dd89c5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Thu, 16 Sep 2010 21:33:49 +0000 Subject: [PATCH 42/49] Fixed: pressing enter in filters should result to an 'apply', not a 'save'. #2285 git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4093 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/views/issues/index.rhtml | 1 + app/views/queries/_filters.rhtml | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/app/views/issues/index.rhtml b/app/views/issues/index.rhtml index ee6514d0b..ddd5d9080 100644 --- a/app/views/issues/index.rhtml +++ b/app/views/issues/index.rhtml @@ -39,6 +39,7 @@ { :url => { :set_filter => 1 }, :before => 'selectAllOptions("selected_columns");', :update => "content", + :complete => "apply_filters_observer()", :with => "Form.serialize('query_form')" }, :class => 'icon icon-checked' %> diff --git a/app/views/queries/_filters.rhtml b/app/views/queries/_filters.rhtml index 58ea1524c..20640eb8d 100644 --- a/app/views/queries/_filters.rhtml +++ b/app/views/queries/_filters.rhtml @@ -53,6 +53,18 @@ function toggle_multi_select(field) { select.multiple = true; } } + +function apply_filters_observer() { + $$("#query_form input[type=text]").invoke("observe", "keypress", function(e){ + if(e.keyCode == Event.KEY_RETURN) { + <%= remote_function(:url => { :set_filter => 1}, + :update => "content", + :with => "Form.serialize('query_form')", + :complete => "e.stop(); apply_filters_observer()") %> + } + }); +} +Event.observe(document,"dom:loaded", apply_filters_observer); //]]> From ebb445c36468529cc7873136469a6162660ad253 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 17 Sep 2010 04:06:40 +0000 Subject: [PATCH 43/49] Use the relative_url_root when generating asset_paths. #3935 Contributed by Daniel S from Redmine. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4094 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- .../engines/lib/engines/rails_extensions/asset_helpers.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/plugins/engines/lib/engines/rails_extensions/asset_helpers.rb b/vendor/plugins/engines/lib/engines/rails_extensions/asset_helpers.rb index 9fd1742c4..a4a9266f2 100644 --- a/vendor/plugins/engines/lib/engines/rails_extensions/asset_helpers.rb +++ b/vendor/plugins/engines/lib/engines/rails_extensions/asset_helpers.rb @@ -109,11 +109,11 @@ module Engines::RailsExtensions::AssetHelpers # Returns the publicly-addressable relative URI for the given asset, type and plugin def self.plugin_asset_path(plugin_name, type, asset) raise "No plugin called '#{plugin_name}' - please use the full name of a loaded plugin." if Engines.plugins[plugin_name].nil? - "/#{Engines.plugins[plugin_name].public_asset_directory}/#{type}/#{asset}" + "#{ActionController::Base.relative_url_root}/#{Engines.plugins[plugin_name].public_asset_directory}/#{type}/#{asset}" end end module ::ActionView::Helpers::AssetTagHelper #:nodoc: include Engines::RailsExtensions::AssetHelpers -end \ No newline at end of file +end From b8ea55661631be2e7aa535fee7dc43fd3c4ca75d Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 17 Sep 2010 04:22:46 +0000 Subject: [PATCH 44/49] Fix the new subproject link on project overview. #6388 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contributed by Felix Schäfer. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4095 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/views/projects/show.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/show.rhtml b/app/views/projects/show.rhtml index 0ad9a11da..cf3814b77 100644 --- a/app/views/projects/show.rhtml +++ b/app/views/projects/show.rhtml @@ -1,6 +1,6 @@
    <% if User.current.allowed_to?(:add_subprojects, @project) %> - <%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'add', :parent_id => @project}, :class => 'icon icon-add' %> + <%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'new', :parent_id => @project}, :class => 'icon icon-add' %> <% end %>
    From eb1f58f9624a22d4b5a22f1510c858fa6d5cf7cb Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 17 Sep 2010 04:31:17 +0000 Subject: [PATCH 45/49] Added some documentation about the Rails Logger. #6135 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contributed by Felix Schäfer git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4096 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- config/environments/production.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index cfd2aa0f2..ac11f5219 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -4,9 +4,14 @@ # Code is not reloaded between requests config.cache_classes = true +##### +# Customize the default logger (http://ruby-doc.org/core/classes/Logger.html) +# # Use a different logger for distributed setups # config.logger = SyslogLogger.new - +# +# Rotate logs bigger than 1MB, keeps no more than 7 rotated logs around. +# config.logger = Logger.new(config.log_path, 7, 1048576) # Full error reports are disabled and caching is turned on config.action_controller.consider_all_requests_local = false From bd193a026df09ba231b241c24b43762810af7871 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 17 Sep 2010 15:55:08 +0000 Subject: [PATCH 46/49] Refactor: convert VersionsController to a REST resource. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4097 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/versions_controller.rb | 4 ++-- app/views/projects/settings/_versions.rhtml | 4 ++-- app/views/versions/edit.rhtml | 2 +- app/views/versions/new.html.erb | 2 +- config/routes.rb | 17 +++++------------ test/functional/versions_controller_test.rb | 6 +++--- 6 files changed, 14 insertions(+), 21 deletions(-) diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 331935d9b..48612c7b8 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -103,7 +103,7 @@ class VersionsController < ApplicationController end def update - if request.post? && params[:version] + if request.put? && params[:version] attributes = params[:version].dup attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing']) if @version.update_attributes(attributes) @@ -114,7 +114,7 @@ class VersionsController < ApplicationController end def close_completed - if request.post? + if request.put? @project.close_completed_versions end redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project diff --git a/app/views/projects/settings/_versions.rhtml b/app/views/projects/settings/_versions.rhtml index dc81f6265..d41929c2d 100644 --- a/app/views/projects/settings/_versions.rhtml +++ b/app/views/projects/settings/_versions.rhtml @@ -21,7 +21,7 @@
    @@ -34,7 +34,7 @@
    <% if @project.versions.any? %> - <%= link_to l(:label_close_versions), {:controller => 'versions', :action => 'close_completed', :project_id => @project}, :method => :post %> + <%= link_to l(:label_close_versions), close_completed_project_versions_path(@project), :method => :put %> <% end %>
    diff --git a/app/views/versions/edit.rhtml b/app/views/versions/edit.rhtml index 8c437eb5e..8724fe62a 100644 --- a/app/views/versions/edit.rhtml +++ b/app/views/versions/edit.rhtml @@ -1,6 +1,6 @@

    <%=l(:label_version)%>

    -<% labelled_tabular_form_for :version, @version, :url => { :action => 'update', :id => @version } do |f| %> +<% labelled_tabular_form_for :version, @version, :url => project_version_path(@project, @version), :html => {:method => :put} do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <%= submit_tag l(:button_save) %> <% end %> diff --git a/app/views/versions/new.html.erb b/app/views/versions/new.html.erb index 9fbd94178..d60468159 100644 --- a/app/views/versions/new.html.erb +++ b/app/views/versions/new.html.erb @@ -1,6 +1,6 @@

    <%=l(:label_version_new)%>

    -<% labelled_tabular_form_for :version, @version, :url => { :action => 'create', :project_id => @project } do |f| %> +<% labelled_tabular_form_for :version, @version, :url => project_versions_path(@project) do |f| %> <%= render :partial => 'versions/form', :locals => { :f => f } %> <%= submit_tag l(:button_create) %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index 3197df01a..00a2c3608 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -173,6 +173,9 @@ ActionController::Routing::Routes.draw do |map| end end + # For nice "roadmap" in the url for the index action + map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index' + map.resources :projects, :member => { :copy => [:get, :post], :settings => :get, @@ -182,6 +185,7 @@ ActionController::Routing::Routes.draw do |map| } do |project| project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy] project.resources :files, :only => [:index, :new, :create] + project.resources :versions, :collection => {:close_completed => :put} end # Destroy uses a get request to prompt the user before the actual DELETE request @@ -201,19 +205,8 @@ ActionController::Routing::Routes.draw do |map| activity.connect 'activity', :id => nil activity.connect 'activity.:format', :id => nil end + - map.with_options :controller => 'versions' do |versions| - versions.connect 'projects/:project_id/versions/new', :action => 'new' - versions.connect 'projects/:project_id/roadmap', :action => 'index' - versions.connect 'versions/:action/:id', :conditions => {:method => :get} - - versions.with_options :conditions => {:method => :post} do |version_actions| - version_actions.connect 'projects/:project_id/versions', :action => 'create' - version_actions.connect 'versions/update/:id', :action => 'update' - version_actions.connect 'projects/:project_id/versions/close_completed', :action => 'close_completed' - end - end - map.with_options :controller => 'issue_categories' do |categories| categories.connect 'projects/:project_id/issue_categories/new', :action => 'new' end diff --git a/test/functional/versions_controller_test.rb b/test/functional/versions_controller_test.rb index 1b33ba816..e4ac5c068 100644 --- a/test/functional/versions_controller_test.rb +++ b/test/functional/versions_controller_test.rb @@ -108,14 +108,14 @@ class VersionsControllerTest < ActionController::TestCase def test_close_completed Version.update_all("status = 'open'") @request.session[:user_id] = 2 - post :close_completed, :project_id => 'ecookbook' + put :close_completed, :project_id => 'ecookbook' assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' assert_not_nil Version.find_by_status('closed') end def test_post_update @request.session[:user_id] = 2 - post :update, :id => 2, + put :update, :id => 2, :version => { :name => 'New version name', :effective_date => Date.today.strftime("%Y-%m-%d")} assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' @@ -126,7 +126,7 @@ class VersionsControllerTest < ActionController::TestCase def test_destroy @request.session[:user_id] = 2 - post :destroy, :id => 3 + delete :destroy, :id => 3 assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => 'ecookbook' assert_nil Version.find_by_id(3) end From 5fdfe02b3a3e8cc396f38c4af75c586ae584a4d2 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 17 Sep 2010 16:11:43 +0000 Subject: [PATCH 47/49] Refactor: add VersionsController#status_by to the resource. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4098 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/views/versions/_issue_counts.rhtml | 2 +- config/routes.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/versions/_issue_counts.rhtml b/app/views/versions/_issue_counts.rhtml index 4bab5c659..38f3edbcb 100644 --- a/app/views/versions/_issue_counts.rhtml +++ b/app/views/versions/_issue_counts.rhtml @@ -5,7 +5,7 @@ select_tag('status_by', status_by_options_for_select(criteria), :id => 'status_by_select', - :onchange => remote_function(:url => { :action => :status_by, :id => version }, + :onchange => remote_function(:url => status_by_project_version_path(version.project, version), :with => "Form.serialize('status_by_form')"))) %> <% if counts.empty? %> diff --git a/config/routes.rb b/config/routes.rb index 00a2c3608..062bb586e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -185,7 +185,7 @@ ActionController::Routing::Routes.draw do |map| } do |project| project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy] project.resources :files, :only => [:index, :new, :create] - project.resources :versions, :collection => {:close_completed => :put} + project.resources :versions, :collection => {:close_completed => :put}, :member => {:status_by => :post} end # Destroy uses a get request to prompt the user before the actual DELETE request From ffdaead5b72966c7e4f74cca4d5951cc3cefc4c4 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Fri, 17 Sep 2010 23:13:26 +0000 Subject: [PATCH 48/49] Removed file mistakenly added in r4092. #6392 git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4099 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- special_page_index.rhtml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 special_page_index.rhtml diff --git a/special_page_index.rhtml b/special_page_index.rhtml deleted file mode 100644 index e2d1e44ad..000000000 --- a/special_page_index.rhtml +++ /dev/null @@ -1,26 +0,0 @@ -
    -<%= watcher_tag(@wiki, User.current) %> -
    - -

    <%= l(:label_index_by_title) %>

    - -<% if @pages.empty? %> -

    <%= l(:label_no_data) %>

    -<% end %> - -<%= render_page_hierarchy(@pages_by_parent_id) %> - -<% content_for :sidebar do %> - <%= render :partial => 'sidebar' %> -<% end %> - -<% unless @pages.empty? %> -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %> - <%= f.link_to('HTML', :url => {:action => 'special', :page => 'export'}) if User.current.allowed_to?(:export_wiki_pages, @project) %> -<% end %> -<% end %> - -<% content_for :header_tags do %> -<%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %> -<% end %> From 099761d8fbba3d33cd34c9ef24bcdbe09d84232d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Barth Date: Sat, 18 Sep 2010 16:50:08 +0000 Subject: [PATCH 49/49] Fixes switching between inline and side-by-side in diff view with path. #6242 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contributed by Felix Schäfer git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4100 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/views/repositories/diff.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/repositories/diff.rhtml b/app/views/repositories/diff.rhtml index 36e86403f..24f92a540 100644 --- a/app/views/repositories/diff.rhtml +++ b/app/views/repositories/diff.rhtml @@ -1,7 +1,7 @@

    <%= l(:label_revision) %> <%= format_revision(@rev_to) + ':' if @rev_to %><%= format_revision(@rev) %> <%=h @path %>

    -<% form_tag({:path => @path}, :method => 'get') do %> +<% form_tag({:path => to_path_param(@path)}, :method => 'get') do %> <%= hidden_field_tag('rev', params[:rev]) if params[:rev] %> <%= hidden_field_tag('rev_to', params[:rev_to]) if params[:rev_to] %>

    @@ -67,26 +69,10 @@ t_height = g_height + headers_height
    -<% -# -# Tasks subjects -# -top = headers_height + 8 -@gantt.events.each do |i| -left = 4 + (i.is_a?(Issue) ? i.level * 16 : 0) - %> -
    - <% if i.is_a? Issue %> - <%= h("#{i.project} -") unless @project && @project == i.project %> - <%= link_to_issue i %> - <% else %> - - <%= link_to_version i %> - - <% end %> -
    - <% top = top + 20 -end %> +<% top = headers_height + 8 %> + +<%= @gantt.subjects(:headers_height => headers_height, :top => top, :g_width => g_width) %> +
    @@ -164,53 +150,9 @@ if show_days end end %> -<% -# -# Tasks -# -top = headers_height + 10 -@gantt.events.each do |i| - if i.is_a? Issue - i_start_date = (i.start_date >= @gantt.date_from ? i.start_date : @gantt.date_from ) - i_end_date = (i.due_before <= @gantt.date_to ? i.due_before : @gantt.date_to ) - - i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor - i_done_date = (i_done_date <= @gantt.date_from ? @gantt.date_from : i_done_date ) - i_done_date = (i_done_date >= @gantt.date_to ? @gantt.date_to : i_done_date ) - - i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today - - i_left = ((i_start_date - @gantt.date_from)*zoom).floor - i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2 # total width of the issue (- 2 for left and right borders) - d_width = ((i_done_date - i_start_date)*zoom).floor - 2 # done width - l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width - css = "task " + (i.leaf? ? 'leaf' : 'parent') - %> -
     
    - <% if l_width > 0 %> -
     
    - <% end %> - <% if d_width > 0 %> -
     
    - <% end %> -
    - <%= i.status.name %> - <%= (i.done_ratio).to_i %>% -
    -
    - - <%= render_issue_tooltip i %> -
    -<% else - i_left = ((i.start_date - @gantt.date_from)*zoom).floor - %> -
     
    -
    - <%= format_version_name i %> -
    -<% end %> - <% top = top + 20 -end %> +<% top = headers_height + 10 %> + +<%= @gantt.lines(:top => top, :zoom => zoom, :g_width => g_width ) %> <% # diff --git a/lib/redmine/export/pdf.rb b/lib/redmine/export/pdf.rb index 9b1f2b8a2..27905b2bb 100644 --- a/lib/redmine/export/pdf.rb +++ b/lib/redmine/export/pdf.rb @@ -184,7 +184,7 @@ module Redmine end pdf.Output end - + # Returns a PDF string of a single issue def issue_to_pdf(issue) pdf = IFPDF.new(current_language) @@ -208,7 +208,7 @@ module Redmine pdf.SetFontStyle('',9) pdf.Cell(60,5, issue.priority.to_s,"RT") pdf.Ln - + pdf.SetFontStyle('B',9) pdf.Cell(35,5, l(:field_author) + ":","L") pdf.SetFontStyle('',9) @@ -238,14 +238,14 @@ module Redmine pdf.SetFontStyle('',9) pdf.Cell(60,5, format_date(issue.due_date),"RB") pdf.Ln - + for custom_value in issue.custom_field_values pdf.SetFontStyle('B',9) pdf.Cell(35,5, custom_value.custom_field.name + ":","L") pdf.SetFontStyle('',9) pdf.MultiCell(155,5, (show_value custom_value),"R") end - + pdf.SetFontStyle('B',9) pdf.Cell(35,5, l(:field_subject) + ":","LTB") pdf.SetFontStyle('',9) @@ -311,187 +311,7 @@ module Redmine end pdf.Output end - - # Returns a PDF string of a gantt chart - def gantt_to_pdf(gantt, project) - pdf = IFPDF.new(current_language) - pdf.SetTitle("#{l(:label_gantt)} #{project}") - pdf.AliasNbPages - pdf.footer_date = format_date(Date.today) - pdf.AddPage("L") - pdf.SetFontStyle('B',12) - pdf.SetX(15) - pdf.Cell(70, 20, project.to_s) - pdf.Ln - pdf.SetFontStyle('B',9) - - subject_width = 100 - header_heigth = 5 - - headers_heigth = header_heigth - show_weeks = false - show_days = false - - if gantt.months < 7 - show_weeks = true - headers_heigth = 2*header_heigth - if gantt.months < 3 - show_days = true - headers_heigth = 3*header_heigth - end - end - - g_width = 280 - subject_width - zoom = (g_width) / (gantt.date_to - gantt.date_from + 1) - g_height = 120 - t_height = g_height + headers_heigth - - y_start = pdf.GetY - - # Months headers - month_f = gantt.date_from - left = subject_width - height = header_heigth - gantt.months.times do - width = ((month_f >> 1) - month_f) * zoom - pdf.SetY(y_start) - pdf.SetX(left) - pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C") - left = left + width - month_f = month_f >> 1 - end - - # Weeks headers - if show_weeks - left = subject_width - height = header_heigth - if gantt.date_from.cwday == 1 - # gantt.date_from is monday - week_f = gantt.date_from - else - # find next monday after gantt.date_from - week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1) - width = (7 - gantt.date_from.cwday + 1) * zoom-1 - pdf.SetY(y_start + header_heigth) - pdf.SetX(left) - pdf.Cell(width + 1, height, "", "LTR") - left = left + width+1 - end - while week_f <= gantt.date_to - width = (week_f + 6 <= gantt.date_to) ? 7 * zoom : (gantt.date_to - week_f + 1) * zoom - pdf.SetY(y_start + header_heigth) - pdf.SetX(left) - pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C") - left = left + width - week_f = week_f+7 - end - end - - # Days headers - if show_days - left = subject_width - height = header_heigth - wday = gantt.date_from.cwday - pdf.SetFontStyle('B',7) - (gantt.date_to - gantt.date_from + 1).to_i.times do - width = zoom - pdf.SetY(y_start + 2 * header_heigth) - pdf.SetX(left) - pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C") - left = left + width - wday = wday + 1 - wday = 1 if wday > 7 - end - end - - pdf.SetY(y_start) - pdf.SetX(15) - pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1) - - # Tasks - top = headers_heigth + y_start - pdf.SetFontStyle('B',7) - gantt.events.each do |i| - pdf.SetY(top) - pdf.SetX(15) - - text = "" - if i.is_a? Issue - text = "#{i.tracker} #{i.id}: #{i.subject}" - else - text = i.name - end - text = "#{i.project} - #{text}" unless project && project == i.project - pdf.Cell(subject_width-15, 5, text, "LR") - - pdf.SetY(top + 0.2) - pdf.SetX(subject_width) - pdf.SetFillColor(255, 255, 255) - pdf.Cell(g_width, 4.6, "", "LR", 0, "", 1) - pdf.SetY(top+1.5) - - if i.is_a? Issue - i_start_date = (i.start_date >= gantt.date_from ? i.start_date : gantt.date_from ) - i_end_date = (i.due_before <= gantt.date_to ? i.due_before : gantt.date_to ) - - i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor - i_done_date = (i_done_date <= gantt.date_from ? gantt.date_from : i_done_date ) - i_done_date = (i_done_date >= gantt.date_to ? gantt.date_to : i_done_date ) - - i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today - - i_left = ((i_start_date - gantt.date_from)*zoom) - i_width = ((i_end_date - i_start_date + 1)*zoom) - d_width = ((i_done_date - i_start_date)*zoom) - l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date - l_width ||= 0 - - pdf.SetX(subject_width + i_left) - pdf.SetFillColor(200,200,200) - pdf.Cell(i_width, 2, "", 0, 0, "", 1) - - if l_width > 0 - pdf.SetY(top+1.5) - pdf.SetX(subject_width + i_left) - pdf.SetFillColor(255,100,100) - pdf.Cell(l_width, 2, "", 0, 0, "", 1) - end - if d_width > 0 - pdf.SetY(top+1.5) - pdf.SetX(subject_width + i_left) - pdf.SetFillColor(100,100,255) - pdf.Cell(d_width, 2, "", 0, 0, "", 1) - end - - pdf.SetY(top+1.5) - pdf.SetX(subject_width + i_left + i_width) - pdf.Cell(30, 2, "#{i.status} #{i.done_ratio}%") - else - i_left = ((i.start_date - gantt.date_from)*zoom) - - pdf.SetX(subject_width + i_left) - pdf.SetFillColor(50,200,50) - pdf.Cell(2, 2, "", 0, 0, "", 1) - - pdf.SetY(top+1.5) - pdf.SetX(subject_width + i_left + 3) - pdf.Cell(30, 2, "#{i.name}") - end - - top = top + 5 - pdf.SetDrawColor(200, 200, 200) - pdf.Line(15, top, subject_width+g_width, top) - if pdf.GetY() > 180 - pdf.AddPage("L") - top = 20 - pdf.Line(15, top, subject_width+g_width, top) - end - pdf.SetDrawColor(0, 0, 0) - end - - pdf.Line(15, top, subject_width+g_width, top) - pdf.Output - end + end end end diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index 96ba4db7c..33a4e1c2c 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -19,11 +19,28 @@ module Redmine module Helpers # Simple class to handle gantt chart data class Gantt - attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :events - + include ERB::Util + include Redmine::I18n + + # :nodoc: + # Some utility methods for the PDF export + class PDF + MaxCharactorsForSubject = 45 + TotalWidth = 280 + LeftPaneWidth = 100 + + def self.right_pane_width + TotalWidth - LeftPaneWidth + end + end + + attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months + attr_accessor :query + attr_accessor :project + attr_accessor :view + def initialize(options={}) options = options.dup - @events = [] if options[:year] && options[:year].to_i >0 @year_from = options[:year].to_i @@ -52,31 +69,6 @@ module Redmine @date_to = (@date_from >> @months) - 1 end - - def events=(e) - @events = e - # Adds all ancestors - root_ids = e.select {|i| i.is_a?(Issue) && i.parent_id? }.collect(&:root_id).uniq - if root_ids.any? - # Retrieves all nodes - parents = Issue.find_all_by_root_id(root_ids, :conditions => ["rgt - lft > 1"]) - # Only add ancestors - @events += parents.select {|p| @events.detect {|i| i.is_a?(Issue) && p.is_ancestor_of?(i)}} - end - @events.uniq! - # Sort issues by hierarchy and start dates - @events.sort! {|x,y| - if x.is_a?(Issue) && y.is_a?(Issue) - gantt_issue_compare(x, y, @events) - else - gantt_start_compare(x, y) - end - } - # Removes issues that have no start or end date - @events.reject! {|i| i.is_a?(Issue) && (i.start_date.nil? || i.due_before.nil?) } - @events - end - def params { :zoom => zoom, :year => year_from, :month => month_from, :months => months } end @@ -88,10 +80,652 @@ module Redmine def params_next { :year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months } end - + + ### Extracted from the HTML view/helpers + # Returns the number of rows that will be rendered on the Gantt chart + def number_of_rows + if @project + return number_of_rows_on_project(@project) + else + Project.roots.inject(0) do |total, project| + total += number_of_rows_on_project(project) + end + end + end + + # Returns the number of rows that will be used to list a project on + # the Gantt chart. This will recurse for each subproject. + def number_of_rows_on_project(project) + # Remove the project requirement for Versions because it will + # restrict issues to only be on the current project. This + # ends up missing issues which are assigned to shared versions. + @query.project = nil if @query.project + + # One Root project + count = 1 + # Issues without a Version + count += project.issues.for_gantt.without_version.with_query(@query).count + + # Versions + count += project.versions.count + + # Issues on the Versions + project.versions.each do |version| + count += version.fixed_issues.for_gantt.with_query(@query).count + end + + # Subprojects + project.children.each do |subproject| + count += number_of_rows_on_project(subproject) + end + + count + end + + # Renders the subjects of the Gantt chart, the left side. + def subjects(options={}) + options = {:indent => 4, :render => :subject, :format => :html}.merge(options) + + output = '' + if @project + output << render_project(@project, options) + else + Project.roots.each do |project| + output << render_project(project, options) + end + end + + output + end + + # Renders the lines of the Gantt chart, the right side + def lines(options={}) + options = {:indent => 4, :render => :line, :format => :html}.merge(options) + output = '' + + if @project + output << render_project(@project, options) + else + Project.roots.each do |project| + output << render_project(project, options) + end + end + + output + end + + def render_project(project, options={}) + options[:top] = 0 unless options.key? :top + options[:indent_increment] = 20 unless options.key? :indent_increment + options[:top_increment] = 20 unless options.key? :top_increment + + output = '' + # Project Header + project_header = if options[:render] == :subject + subject_for_project(project, options) + else + # :line + line_for_project(project, options) + end + output << project_header if options[:format] == :html + + options[:top] += options[:top_increment] + options[:indent] += options[:indent_increment] + + # Second, Issues without a version + issues = project.issues.for_gantt.without_version.with_query(@query) + if issues + issue_rendering = render_issues(issues, options) + output << issue_rendering if options[:format] == :html + end + + # Third, Versions + project.versions.sort.each do |version| + version_rendering = render_version(version, options) + output << version_rendering if options[:format] == :html + end + + # Fourth, subprojects + project.children.each do |project| + subproject_rendering = render_project(project, options) + output << subproject_rendering if options[:format] == :html + end + + # Remove indent to hit the next sibling + options[:indent] -= options[:indent_increment] + + output + end + + def render_issues(issues, options={}) + output = '' + issues.each do |i| + issue_rendering = if options[:render] == :subject + subject_for_issue(i, options) + else + # :line + line_for_issue(i, options) + end + output << issue_rendering if options[:format] == :html + options[:top] += options[:top_increment] + end + output + end + + def render_version(version, options={}) + output = '' + # Version header + version_rendering = if options[:render] == :subject + subject_for_version(version, options) + else + # :line + line_for_version(version, options) + end + + output << version_rendering if options[:format] == :html + + options[:top] += options[:top_increment] + + # Remove the project requirement for Versions because it will + # restrict issues to only be on the current project. This + # ends up missing issues which are assigned to shared versions. + @query.project = nil if @query.project + + issues = version.fixed_issues.for_gantt.with_query(@query) + if issues + # Indent issues + options[:indent] += options[:indent_increment] + output << render_issues(issues, options) + options[:indent] -= options[:indent_increment] + end + + output + end + + def subject_for_project(project, options) + case options[:format] + when :html + output = '' + + output << "
    " + if project.is_a? Project + output << "" + output << view.link_to_project(project) + output << '' + else + ActiveRecord::Base.logger.debug "Gantt#subject_for_project was not given a project" + '' + end + output << "
    " + + output + when :image + + options[:image].fill('black') + options[:image].stroke('transparent') + options[:image].stroke_width(1) + options[:image].text(options[:indent], options[:top] + 2, project.name) + when :pdf + options[:pdf].SetY(options[:top]) + options[:pdf].SetX(15) + + char_limit = PDF::MaxCharactorsForSubject - options[:indent] + options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{project.name}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR") + + options[:pdf].SetY(options[:top]) + options[:pdf].SetX(options[:subject_width]) + options[:pdf].Cell(options[:g_width], 5, "", "LR") + end + end + + def line_for_project(project, options) + # Skip versions that don't have a start_date + if project.is_a?(Project) && project.start_date + options[:zoom] ||= 1 + options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] + + + case options[:format] + when :html + output = '' + i_left = ((project.start_date - self.date_from)*options[:zoom]).floor + + start_date = project.start_date + start_date ||= self.date_from + start_left = ((start_date - self.date_from)*options[:zoom]).floor + + i_end_date = ((project.due_date <= self.date_to) ? project.due_date : self.date_to ) + i_done_date = start_date + ((project.due_date - start_date+1)* project.completed_percent(:include_subprojects => true)/100).floor + i_done_date = (i_done_date <= self.date_from ? self.date_from : i_done_date ) + i_done_date = (i_done_date >= self.date_to ? self.date_to : i_done_date ) + + i_late_date = [i_end_date, Date.today].min if start_date < Date.today + i_end = ((i_end_date - self.date_from) * options[:zoom]).floor + + i_width = (i_end - i_left + 1).floor - 2 # total width of the issue (- 2 for left and right borders) + d_width = ((i_done_date - start_date)*options[:zoom]).floor - 2 # done width + l_width = i_late_date ? ((i_late_date - start_date+1)*options[:zoom]).floor - 2 : 0 # delay width + + # Bar graphic + + # Make sure that negative i_left and i_width don't + # overflow the subject + if i_end > 0 && i_left <= options[:g_width] + output << "
     
    " + end + + if l_width > 0 && i_left <= options[:g_width] + output << "
     
    " + end + if d_width > 0 && i_left <= options[:g_width] + output<< "
     
    " + end + + + # Starting diamond + if start_left <= options[:g_width] && start_left > 0 + output << "
     
    " + output << "
    " + output << "
    " + end + + # Ending diamond + # Don't show items too far ahead + if i_end <= options[:g_width] && i_end > 0 + output << "
     
    " + end + + # DIsplay the Project name and % + if i_end <= options[:g_width] + # Display the status even if it's floated off to the left + status_px = i_end + 12 # 12px for the diamond + status_px = 0 if status_px <= 0 + + output << "
    " + output << "#{h project } #{h project.completed_percent(:include_subprojects => true).to_i.to_s}%" + output << "
    " + end + + output + when :image + options[:image].stroke('transparent') + i_left = options[:subject_width] + ((project.due_date - self.date_from)*options[:zoom]).floor + + # Make sure negative i_left doesn't overflow the subject + if i_left > options[:subject_width] + options[:image].fill('blue') + options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6) + options[:image].fill('black') + options[:image].text(i_left + 11, options[:top] + 1, project.name) + end + when :pdf + options[:pdf].SetY(options[:top]+1.5) + i_left = ((project.due_date - @date_from)*options[:zoom]) + + # Make sure negative i_left doesn't overflow the subject + if i_left > 0 + options[:pdf].SetX(options[:subject_width] + i_left) + options[:pdf].SetFillColor(50,50,200) + options[:pdf].Cell(2, 2, "", 0, 0, "", 1) + + options[:pdf].SetY(options[:top]+1.5) + options[:pdf].SetX(options[:subject_width] + i_left + 3) + options[:pdf].Cell(30, 2, "#{project.name}") + end + end + else + ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date" + '' + end + end + + def subject_for_version(version, options) + case options[:format] + when :html + output = '' + output << "
    " + if version.is_a? Version + output << "" + output << view.link_to_version(version) + output << '' + else + ActiveRecord::Base.logger.debug "Gantt#subject_for_version was not given a version" + '' + end + output << "
    " + + output + when :image + options[:image].fill('black') + options[:image].stroke('transparent') + options[:image].stroke_width(1) + options[:image].text(options[:indent], options[:top] + 2, version.name) + when :pdf + options[:pdf].SetY(options[:top]) + options[:pdf].SetX(15) + + char_limit = PDF::MaxCharactorsForSubject - options[:indent] + options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{version.name}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR") + + options[:pdf].SetY(options[:top]) + options[:pdf].SetX(options[:subject_width]) + options[:pdf].Cell(options[:g_width], 5, "", "LR") + end + end + + def line_for_version(version, options) + # Skip versions that don't have a start_date + if version.is_a?(Version) && version.start_date + options[:zoom] ||= 1 + options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] + + case options[:format] + when :html + output = '' + i_left = ((version.start_date - self.date_from)*options[:zoom]).floor + # TODO: or version.fixed_issues.collect(&:start_date).min + start_date = version.fixed_issues.minimum('start_date') if version.fixed_issues.present? + start_date ||= self.date_from + start_left = ((start_date - self.date_from)*options[:zoom]).floor + + i_end_date = ((version.due_date <= self.date_to) ? version.due_date : self.date_to ) + i_done_date = start_date + ((version.due_date - start_date+1)* version.completed_pourcent/100).floor + i_done_date = (i_done_date <= self.date_from ? self.date_from : i_done_date ) + i_done_date = (i_done_date >= self.date_to ? self.date_to : i_done_date ) + + i_late_date = [i_end_date, Date.today].min if start_date < Date.today + + i_width = (i_left - start_left + 1).floor - 2 # total width of the issue (- 2 for left and right borders) + d_width = ((i_done_date - start_date)*options[:zoom]).floor - 2 # done width + l_width = i_late_date ? ((i_late_date - start_date+1)*options[:zoom]).floor - 2 : 0 # delay width + + i_end = ((i_end_date - self.date_from) * options[:zoom]).floor # Ending pixel + + # Bar graphic + + # Make sure that negative i_left and i_width don't + # overflow the subject + if i_width > 0 && i_left <= options[:g_width] + output << "
     
    " + end + if l_width > 0 && i_left <= options[:g_width] + output << "
     
    " + end + if d_width > 0 && i_left <= options[:g_width] + output<< "
     
    " + end + + + # Starting diamond + if start_left <= options[:g_width] && start_left > 0 + output << "
     
    " + output << "
    " + output << "
    " + end + + # Ending diamond + # Don't show items too far ahead + if i_left <= options[:g_width] && i_end > 0 + output << "
     
    " + end + + # Display the Version name and % + if i_end <= options[:g_width] + # Display the status even if it's floated off to the left + status_px = i_end + 12 # 12px for the diamond + status_px = 0 if status_px <= 0 + + output << "
    " + output << h("#{version.project} -") unless @project && @project == version.project + output << "#{h version } #{h version.completed_pourcent.to_i.to_s}%" + output << "
    " + end + + output + when :image + options[:image].stroke('transparent') + i_left = options[:subject_width] + ((version.start_date - @date_from)*options[:zoom]).floor + + # Make sure negative i_left doesn't overflow the subject + if i_left > options[:subject_width] + options[:image].fill('green') + options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6) + options[:image].fill('black') + options[:image].text(i_left + 11, options[:top] + 1, version.name) + end + when :pdf + options[:pdf].SetY(options[:top]+1.5) + i_left = ((version.start_date - @date_from)*options[:zoom]) + + # Make sure negative i_left doesn't overflow the subject + if i_left > 0 + options[:pdf].SetX(options[:subject_width] + i_left) + options[:pdf].SetFillColor(50,200,50) + options[:pdf].Cell(2, 2, "", 0, 0, "", 1) + + options[:pdf].SetY(options[:top]+1.5) + options[:pdf].SetX(options[:subject_width] + i_left + 3) + options[:pdf].Cell(30, 2, "#{version.name}") + end + end + else + ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date" + '' + end + end + + def subject_for_issue(issue, options) + case options[:format] + when :html + output = '' + output << "
    " + output << "
    " + if issue.is_a? Issue + css_classes = [] + css_classes << 'issue-overdue' if issue.overdue? + css_classes << 'issue-behind-schedule' if issue.behind_schedule? + css_classes << 'icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to + + if issue.assigned_to.present? + assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name + output << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string) + end + output << "" + output << view.link_to_issue(issue) + output << ":" + output << h(issue.subject) + output << '' + else + ActiveRecord::Base.logger.debug "Gantt#subject_for_issue was not given an issue" + '' + end + output << "
    " + + # Tooltip + if issue.is_a? Issue + output << "" + output << view.render_issue_tooltip(issue) + output << "" + end + + output << "
    " + output + when :image + options[:image].fill('black') + options[:image].stroke('transparent') + options[:image].stroke_width(1) + options[:image].text(options[:indent], options[:top] + 2, issue.subject) + when :pdf + options[:pdf].SetY(options[:top]) + options[:pdf].SetX(15) + + char_limit = PDF::MaxCharactorsForSubject - options[:indent] + options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{issue.tracker} #{issue.id}: #{issue.subject}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR") + + options[:pdf].SetY(options[:top]) + options[:pdf].SetX(options[:subject_width]) + options[:pdf].Cell(options[:g_width], 5, "", "LR") + end + end + + def line_for_issue(issue, options) + # Skip issues that don't have a due_before (due_date or version's due_date) + if issue.is_a?(Issue) && issue.due_before + case options[:format] + when :html + output = '' + # Handle nil start_dates, rare but can happen. + i_start_date = if issue.start_date && issue.start_date >= self.date_from + issue.start_date + else + self.date_from + end + + i_end_date = ((issue.due_before && issue.due_before <= self.date_to) ? issue.due_before : self.date_to ) + i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor + i_done_date = (i_done_date <= self.date_from ? self.date_from : i_done_date ) + i_done_date = (i_done_date >= self.date_to ? self.date_to : i_done_date ) + + i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today + + i_left = ((i_start_date - self.date_from)*options[:zoom]).floor + i_width = ((i_end_date - i_start_date + 1)*options[:zoom]).floor - 2 # total width of the issue (- 2 for left and right borders) + d_width = ((i_done_date - i_start_date)*options[:zoom]).floor - 2 # done width + l_width = i_late_date ? ((i_late_date - i_start_date+1)*options[:zoom]).floor - 2 : 0 # delay width + css = "task " + (issue.leaf? ? 'leaf' : 'parent') + + # Make sure that negative i_left and i_width don't + # overflow the subject + if i_width > 0 + output << "
     
    " + end + if l_width > 0 + output << "
     
    " + end + if d_width > 0 + output<< "
     
    " + end + + # Display the status even if it's floated off to the left + status_px = i_left + i_width + 5 + status_px = 5 if status_px <= 0 + + output << "
    " + output << issue.status.name + output << ' ' + output << (issue.done_ratio).to_i.to_s + output << "%" + output << "
    " + + output << "
    " + output << '' + output << view.render_issue_tooltip(issue) + output << "
    " + output + + when :image + # Handle nil start_dates, rare but can happen. + i_start_date = if issue.start_date && issue.start_date >= @date_from + issue.start_date + else + @date_from + end + + i_end_date = (issue.due_before <= date_to ? issue.due_before : date_to ) + i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor + i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date ) + i_done_date = (i_done_date >= date_to ? date_to : i_done_date ) + i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today + + i_left = options[:subject_width] + ((i_start_date - @date_from)*options[:zoom]).floor + i_width = ((i_end_date - i_start_date + 1)*options[:zoom]).floor # total width of the issue + d_width = ((i_done_date - i_start_date)*options[:zoom]).floor # done width + l_width = i_late_date ? ((i_late_date - i_start_date+1)*options[:zoom]).floor : 0 # delay width + + + # Make sure that negative i_left and i_width don't + # overflow the subject + if i_width > 0 + options[:image].fill('grey') + options[:image].rectangle(i_left, options[:top], i_left + i_width, options[:top] - 6) + options[:image].fill('red') + options[:image].rectangle(i_left, options[:top], i_left + l_width, options[:top] - 6) if l_width > 0 + options[:image].fill('blue') + options[:image].rectangle(i_left, options[:top], i_left + d_width, options[:top] - 6) if d_width > 0 + end + + # Show the status and % done next to the subject if it overflows + options[:image].fill('black') + if i_width > 0 + options[:image].text(i_left + i_width + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%") + else + options[:image].text(options[:subject_width] + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%") + end + + when :pdf + options[:pdf].SetY(options[:top]+1.5) + # Handle nil start_dates, rare but can happen. + i_start_date = if issue.start_date && issue.start_date >= @date_from + issue.start_date + else + @date_from + end + + i_end_date = (issue.due_before <= @date_to ? issue.due_before : @date_to ) + + i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor + i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date ) + i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date ) + + i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today + + i_left = ((i_start_date - @date_from)*options[:zoom]) + i_width = ((i_end_date - i_start_date + 1)*options[:zoom]) + d_width = ((i_done_date - i_start_date)*options[:zoom]) + l_width = ((i_late_date - i_start_date+1)*options[:zoom]) if i_late_date + l_width ||= 0 + + # Make sure that negative i_left and i_width don't + # overflow the subject + if i_width > 0 + options[:pdf].SetX(options[:subject_width] + i_left) + options[:pdf].SetFillColor(200,200,200) + options[:pdf].Cell(i_width, 2, "", 0, 0, "", 1) + end + + if l_width > 0 + options[:pdf].SetY(options[:top]+1.5) + options[:pdf].SetX(options[:subject_width] + i_left) + options[:pdf].SetFillColor(255,100,100) + options[:pdf].Cell(l_width, 2, "", 0, 0, "", 1) + end + if d_width > 0 + options[:pdf].SetY(options[:top]+1.5) + options[:pdf].SetX(options[:subject_width] + i_left) + options[:pdf].SetFillColor(100,100,255) + options[:pdf].Cell(d_width, 2, "", 0, 0, "", 1) + end + + options[:pdf].SetY(options[:top]+1.5) + + # Make sure that negative i_left and i_width don't + # overflow the subject + if (i_left + i_width) >= 0 + options[:pdf].SetX(options[:subject_width] + i_left + i_width) + else + options[:pdf].SetX(options[:subject_width]) + end + options[:pdf].Cell(30, 2, "#{issue.status} #{issue.done_ratio}%") + end + else + ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before" + '' + end + end + # Generates a gantt image # Only defined if RMagick is avalaible - def to_image(project, format='PNG') + def to_image(format='PNG') date_to = (@date_from >> @months)-1 show_weeks = @zoom > 1 show_days = @zoom > 2 @@ -101,7 +735,7 @@ module Redmine # width of one day in pixels zoom = @zoom*2 g_width = (@date_to - @date_from + 1)*zoom - g_height = 20 * events.length + 20 + g_height = 20 * number_of_rows + 30 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth) height = g_height + headers_heigth @@ -110,21 +744,7 @@ module Redmine gc = Magick::Draw.new # Subjects - top = headers_heigth + 20 - gc.fill('black') - gc.stroke('transparent') - gc.stroke_width(1) - events.each do |i| - text = "" - if i.is_a? Issue - text = "#{i.tracker} #{i.id}: #{i.subject}" - else - text = i.name - end - text = "#{i.project} - #{text}" unless project && project == i.project - gc.text(4, top + 2, text) - top = top + 20 - end + subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image) # Months headers month_f = @date_from @@ -202,38 +822,8 @@ module Redmine # content top = headers_heigth + 20 - gc.stroke('transparent') - events.each do |i| - if i.is_a?(Issue) - i_start_date = (i.start_date >= @date_from ? i.start_date : @date_from ) - i_end_date = (i.due_before <= date_to ? i.due_before : date_to ) - i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor - i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date ) - i_done_date = (i_done_date >= date_to ? date_to : i_done_date ) - i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today - - i_left = subject_width + ((i_start_date - @date_from)*zoom).floor - i_width = ((i_end_date - i_start_date + 1)*zoom).floor # total width of the issue - d_width = ((i_done_date - i_start_date)*zoom).floor # done width - l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor : 0 # delay width - - gc.fill('grey') - gc.rectangle(i_left, top, i_left + i_width, top - 6) - gc.fill('red') - gc.rectangle(i_left, top, i_left + l_width, top - 6) if l_width > 0 - gc.fill('blue') - gc.rectangle(i_left, top, i_left + d_width, top - 6) if d_width > 0 - gc.fill('black') - gc.text(i_left + i_width + 5,top + 1, "#{i.status.name} #{i.done_ratio}%") - else - i_left = subject_width + ((i.start_date - @date_from)*zoom).floor - gc.fill('green') - gc.rectangle(i_left, top, i_left + 6, top - 6) - gc.fill('black') - gc.text(i_left + 11, top + 1, i.name) - end - top = top + 20 - end + + lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image) # today red line if Date.today >= @date_from and Date.today <= date_to @@ -246,36 +836,137 @@ module Redmine imgl.format = format imgl.to_blob end if Object.const_defined?(:Magick) + + def to_pdf + pdf = ::Redmine::Export::PDF::IFPDF.new(current_language) + pdf.SetTitle("#{l(:label_gantt)} #{project}") + pdf.AliasNbPages + pdf.footer_date = format_date(Date.today) + pdf.AddPage("L") + pdf.SetFontStyle('B',12) + pdf.SetX(15) + pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s) + pdf.Ln + pdf.SetFontStyle('B',9) + + subject_width = PDF::LeftPaneWidth + header_heigth = 5 + + headers_heigth = header_heigth + show_weeks = false + show_days = false + + if self.months < 7 + show_weeks = true + headers_heigth = 2*header_heigth + if self.months < 3 + show_days = true + headers_heigth = 3*header_heigth + end + end + + g_width = PDF.right_pane_width + zoom = (g_width) / (self.date_to - self.date_from + 1) + g_height = 120 + t_height = g_height + headers_heigth + + y_start = pdf.GetY + + # Months headers + month_f = self.date_from + left = subject_width + height = header_heigth + self.months.times do + width = ((month_f >> 1) - month_f) * zoom + pdf.SetY(y_start) + pdf.SetX(left) + pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C") + left = left + width + month_f = month_f >> 1 + end + + # Weeks headers + if show_weeks + left = subject_width + height = header_heigth + if self.date_from.cwday == 1 + # self.date_from is monday + week_f = self.date_from + else + # find next monday after self.date_from + week_f = self.date_from + (7 - self.date_from.cwday + 1) + width = (7 - self.date_from.cwday + 1) * zoom-1 + pdf.SetY(y_start + header_heigth) + pdf.SetX(left) + pdf.Cell(width + 1, height, "", "LTR") + left = left + width+1 + end + while week_f <= self.date_to + width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom + pdf.SetY(y_start + header_heigth) + pdf.SetX(left) + pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C") + left = left + width + week_f = week_f+7 + end + end + + # Days headers + if show_days + left = subject_width + height = header_heigth + wday = self.date_from.cwday + pdf.SetFontStyle('B',7) + (self.date_to - self.date_from + 1).to_i.times do + width = zoom + pdf.SetY(y_start + 2 * header_heigth) + pdf.SetX(left) + pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C") + left = left + width + wday = wday + 1 + wday = 1 if wday > 7 + end + end + + pdf.SetY(y_start) + pdf.SetX(15) + pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1) + + # Tasks + top = headers_heigth + y_start + pdf_subjects_and_lines(pdf, { + :top => top, + :zoom => zoom, + :subject_width => subject_width, + :g_width => g_width + }) + + + pdf.Line(15, top, subject_width+g_width, top) + pdf.Output + + + end private - - def gantt_issue_compare(x, y, issues) - if x.parent_id == y.parent_id - gantt_start_compare(x, y) - elsif x.is_ancestor_of?(y) - -1 - elsif y.is_ancestor_of?(x) - 1 + + # Renders both the subjects and lines of the Gantt chart for the + # PDF format + def pdf_subjects_and_lines(pdf, options = {}) + subject_options = {:indent => 0, :indent_increment => 5, :top_increment => 3, :render => :subject, :format => :pdf, :pdf => pdf}.merge(options) + line_options = {:indent => 0, :indent_increment => 5, :top_increment => 3, :render => :line, :format => :pdf, :pdf => pdf}.merge(options) + + if @project + render_project(@project, subject_options) + render_project(@project, line_options) else - ax = issues.select {|i| i.is_a?(Issue) && i.is_ancestor_of?(x) && !i.is_ancestor_of?(y) }.sort_by(&:lft).first - ay = issues.select {|i| i.is_a?(Issue) && i.is_ancestor_of?(y) && !i.is_ancestor_of?(x) }.sort_by(&:lft).first - if ax.nil? && ay.nil? - gantt_start_compare(x, y) - else - gantt_issue_compare(ax || x, ay || y, issues) + Project.roots.each do |project| + render_project(project, subject_options) + render_project(project, line_options) end end end - - def gantt_start_compare(x, y) - if x.start_date.nil? - -1 - elsif y.start_date.nil? - 1 - else - x.start_date <=> y.start_date - end - end + end end end diff --git a/public/images/milestone.png b/public/images/milestone.png deleted file mode 100644 index a89791cf5ec644abead1698aac4a09d03e615033..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^93afW3?x5a^xFxf*aCb)T>k?Z#~AJ~C!Gc|7)yfu zf*Bm1-ADs+lssJ=LpZJ{Cjdc83Ins78%Os`sfO)hC6VXcX~FDLatA77@O1TaS?83{1OOFyBV7Oh literal 0 HcmV?d00001 diff --git a/public/images/milestone_late.png b/public/images/milestone_late.png new file mode 100644 index 0000000000000000000000000000000000000000..cf922e95450b05a61cd6d9060552e532739f8c64 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1SGw4HSYi^#^NA%Cx&(BWL^R}Y)RhkE)4%c zaKYZ?lYt_f1s;*b3=G^tAk28_ZrvZCAbW|YuPggaE@>e>8Iza1tUw_JPZ!4!j_b*P w!U7L|DCA?V=aM?v5;~>PzLj(J3y0T0OOqU%yAIS&0IFs1boFyt=akR{0Nu(fk44ofy`glX(f`um$*pxHdF2 z{Qv*|`L*6PAn7D;cNc~ZR#^`qhqJ&VvY3H^TL^?1FWs&C0~BO0@$_|NzsV&nBx5+o sjg=87#OLYa7{YNqIpV0msfHFtMHlA3vpGIZ0?IIWy85}Sb4q9e0Cfc@%>V!Z literal 0 HcmV?d00001 diff --git a/public/images/project_marker.png b/public/images/project_marker.png new file mode 100644 index 0000000000000000000000000000000000000000..4124787d0e1f40ac2a5fee71c985128e3126b393 GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p=efH0%e8j~47LG}_)Usv{<+)U!yD&=aMCIE$eJY5_^BrezX z8}c16;9x#4`&mxxpMA>7>WG(3AyYy=vRL(`1{(1bP0l+XkK@4!E( literal 0 HcmV?d00001 diff --git a/public/images/task_done.png b/public/images/task_done.png index 954ebedcec558388158c8570a0db7fd02a9e0dcd..5fdcb415c0b002cb8853aea98619fb644e473097 100644 GIT binary patch delta 119 zcmcc4*2y?QvVe(!fx$ah^A3<=EDmyaVpw-h<|UBBS>O>_%)r3)0fZTy)|kuy3bL1Y z`ns~;d~=oIJrnw}9TX^@u6{1-oD!M 1 assert_response :success assert_template 'show.html.erb' assert_not_nil assigns(:gantt) - events = assigns(:gantt).events - assert_not_nil events # Issue with start and due dates i = Issue.find(1) assert_not_nil i.due_date - assert events.include?(Issue.find(1)) - # Issue with without due date but targeted to a version with date + assert_select "div a.issue", /##{i.id}/ + # Issue with on a targeted version should not be in the events but loaded in the html i = Issue.find(2) - assert_nil i.due_date - assert events.include?(i) + assert_select "div a.issue", /##{i.id}/ end should "work cross project" do @@ -26,8 +26,8 @@ class GanttsControllerTest < ActionController::TestCase assert_response :success assert_template 'show.html.erb' assert_not_nil assigns(:gantt) - events = assigns(:gantt).events - assert_not_nil events + assert_not_nil assigns(:gantt).query + assert_nil assigns(:gantt).project end should "export to pdf" do diff --git a/test/object_daddy_helpers.rb b/test/object_daddy_helpers.rb index 4a2b85a9e..7b144b18f 100644 --- a/test/object_daddy_helpers.rb +++ b/test/object_daddy_helpers.rb @@ -25,8 +25,9 @@ module ObjectDaddyHelpers def Issue.generate_for_project!(project, attributes={}) issue = Issue.spawn(attributes) do |issue| issue.project = project + issue.tracker = project.trackers.first unless project.trackers.empty? + yield issue if block_given? end - issue.tracker = project.trackers.first unless project.trackers.empty? issue.save! issue end diff --git a/test/unit/helpers/application_helper_test.rb b/test/unit/helpers/application_helper_test.rb index 1936a981b..2906ec6e4 100644 --- a/test/unit/helpers/application_helper_test.rb +++ b/test/unit/helpers/application_helper_test.rb @@ -601,7 +601,7 @@ EXPECTED # turn off avatars Setting.gravatar_enabled = '0' - assert_nil avatar(User.find_by_mail('jsmith@somenet.foo')) + assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo')) end def test_link_to_user diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index e0eb479d9..dd10e0120 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -510,6 +510,28 @@ class IssueTest < ActiveSupport::TestCase assert !Issue.new(:due_date => nil).overdue? assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue? end + + context "#behind_schedule?" do + should "be false if the issue has no start_date" do + assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule? + end + + should "be false if the issue has no end_date" do + assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule? + end + + should "be false if the issue has more done than it's calendar time" do + assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule? + end + + should "be true if the issue hasn't been started at all" do + assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule? + end + + should "be true if the issue has used more calendar time than it's done ratio" do + assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule? + end + end def test_assignable_users assert_kind_of User, Issue.find(1).assignable_users.first diff --git a/test/unit/lib/redmine/helpers/gantt_test.rb b/test/unit/lib/redmine/helpers/gantt_test.rb new file mode 100644 index 000000000..6b97b083f --- /dev/null +++ b/test/unit/lib/redmine/helpers/gantt_test.rb @@ -0,0 +1,703 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.dirname(__FILE__) + '/../../../../test_helper' + +class Redmine::Helpers::GanttTest < ActiveSupport::TestCase + # Utility methods and classes so assert_select can be used. + class GanttViewTest < ActionView::Base + include ActionView::Helpers::UrlHelper + include ActionView::Helpers::TextHelper + include ActionController::UrlWriter + include ApplicationHelper + include ProjectsHelper + include IssuesHelper + + def self.default_url_options + {:only_path => true } + end + + end + + include ActionController::Assertions::SelectorAssertions + + def setup + @response = ActionController::TestResponse.new + # Fixtures + ProjectCustomField.delete_all + Project.destroy_all + + User.current = User.find(1) + end + + def build_view + @view = GanttViewTest.new + end + + def html_document + HTML::Document.new(@response.body) + end + + # Creates a Gantt chart for a 4 week span + def create_gantt(project=Project.generate!) + @project = project + @gantt = Redmine::Helpers::Gantt.new + @gantt.project = @project + @gantt.query = Query.generate_default!(:project => @project) + @gantt.view = build_view + @gantt.instance_variable_set('@date_from', 2.weeks.ago.to_date) + @gantt.instance_variable_set('@date_to', 2.weeks.from_now.to_date) + end + + context "#number_of_rows" do + + context "with one project" do + should "return the number of rows just for that project" + end + + context "with no project" do + should "return the total number of rows for all the projects, resursively" + end + + end + + context "#number_of_rows_on_project" do + setup do + create_gantt + end + + should "clear the @query.project so cross-project issues and versions can be counted" do + assert @gantt.query.project + @gantt.number_of_rows_on_project(@project) + assert_nil @gantt.query.project + end + + should "count 1 for the project itself" do + assert_equal 1, @gantt.number_of_rows_on_project(@project) + end + + should "count the number of issues without a version" do + @project.issues << Issue.generate_for_project!(@project, :fixed_version => nil) + assert_equal 2, @gantt.number_of_rows_on_project(@project) + end + + should "count the number of versions" do + @project.versions << Version.generate! + @project.versions << Version.generate! + assert_equal 3, @gantt.number_of_rows_on_project(@project) + end + + should "count the number of issues on versions, including cross-project" do + version = Version.generate! + @project.versions << version + @project.issues << Issue.generate_for_project!(@project, :fixed_version => version) + + assert_equal 3, @gantt.number_of_rows_on_project(@project) + end + + should "recursive and count the number of rows on each subproject" do + @project.versions << Version.generate! # +1 + + @subproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1 + @subproject.set_parent!(@project) + @subproject.issues << Issue.generate_for_project!(@subproject) # +1 + @subproject.issues << Issue.generate_for_project!(@subproject) # +1 + + @subsubproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1 + @subsubproject.set_parent!(@subproject) + @subsubproject.issues << Issue.generate_for_project!(@subsubproject) # +1 + + assert_equal 7, @gantt.number_of_rows_on_project(@project) # +1 for self + end + end + + # TODO: more of an integration test + context "#subjects" do + setup do + create_gantt + @project.enabled_module_names = [:issue_tracking] + @tracker = Tracker.generate! + @project.trackers << @tracker + @version = Version.generate!(:effective_date => 1.week.from_now.to_date, :sharing => 'none') + @project.versions << @version + + @issue = Issue.generate!(:fixed_version => @version, + :subject => "gantt#line_for_project", + :tracker => @tracker, + :project => @project, + :done_ratio => 30, + :start_date => Date.yesterday, + :due_date => 1.week.from_now.to_date) + @project.issues << @issue + + @response.body = @gantt.subjects + end + + context "project" do + should "be rendered" do + assert_select "div.project-name a", /#{@project.name}/ + end + + should "have an indent of 4" do + assert_select "div.project-name[style*=left:4px]" + end + end + + context "version" do + should "be rendered" do + assert_select "div.version-name a", /#{@version.name}/ + end + + should "be indented 24 (one level)" do + assert_select "div.version-name[style*=left:24px]" + end + end + + context "issue" do + should "be rendered" do + assert_select "div.issue-subject", /#{@issue.subject}/ + end + + should "be indented 44 (two levels)" do + assert_select "div.issue-subject[style*=left:44px]" + end + end + end + + context "#lines" do + setup do + create_gantt + @project.enabled_module_names = [:issue_tracking] + @tracker = Tracker.generate! + @project.trackers << @tracker + @version = Version.generate!(:effective_date => 1.week.from_now.to_date) + @project.versions << @version + @issue = Issue.generate!(:fixed_version => @version, + :subject => "gantt#line_for_project", + :tracker => @tracker, + :project => @project, + :done_ratio => 30, + :start_date => Date.yesterday, + :due_date => 1.week.from_now.to_date) + @project.issues << @issue + + @response.body = @gantt.lines + end + + context "project" do + should "be rendered" do + assert_select "div.project_todo" + assert_select "div.project-line.starting" + assert_select "div.project-line.ending" + assert_select "div.label.project-name", /#{@project.name}/ + end + end + + context "version" do + should "be rendered" do + assert_select "div.milestone_todo" + assert_select "div.milestone.starting" + assert_select "div.milestone.ending" + assert_select "div.label.version-name", /#{@version.name}/ + end + end + + context "issue" do + should "be rendered" do + assert_select "div.task_todo" + assert_select "div.label.issue-name", /#{@issue.done_ratio}/ + assert_select "div.tooltip", /#{@issue.subject}/ + end + end + end + + context "#render_project" do + should "be tested" + end + + context "#render_issues" do + should "be tested" + end + + context "#render_version" do + should "be tested" + end + + context "#subject_for_project" do + setup do + create_gantt + end + + context ":html format" do + should "add an absolute positioned div" do + @response.body = @gantt.subject_for_project(@project, {:format => :html}) + assert_select "div[style*=absolute]" + end + + should "use the indent option to move the div to the right" do + @response.body = @gantt.subject_for_project(@project, {:format => :html, :indent => 40}) + assert_select "div[style*=left:40]" + end + + should "include the project name" do + @response.body = @gantt.subject_for_project(@project, {:format => :html}) + assert_select 'div', :text => /#{@project.name}/ + end + + should "include a link to the project" do + @response.body = @gantt.subject_for_project(@project, {:format => :html}) + assert_select 'a[href=?]', "/projects/#{@project.identifier}", :text => /#{@project.name}/ + end + + should "style overdue projects" do + @project.enabled_module_names = [:issue_tracking] + @project.versions << Version.generate!(:effective_date => Date.yesterday) + + assert @project.overdue?, "Need an overdue project for this test" + @response.body = @gantt.subject_for_project(@project, {:format => :html}) + + assert_select 'div span.project-overdue' + end + + + end + + should "test the PNG format" + should "test the PDF format" + end + + context "#line_for_project" do + setup do + create_gantt + @project.enabled_module_names = [:issue_tracking] + @tracker = Tracker.generate! + @project.trackers << @tracker + @version = Version.generate!(:effective_date => Date.yesterday) + @project.versions << @version + + @project.issues << Issue.generate!(:fixed_version => @version, + :subject => "gantt#line_for_project", + :tracker => @tracker, + :project => @project, + :done_ratio => 30, + :start_date => Date.yesterday, + :due_date => 1.week.from_now.to_date) + end + + context ":html format" do + context "todo line" do + should "start from the starting point on the left" do + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project_todo[style*=left:52px]" + end + + should "be the total width of the project" do + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project_todo[style*=width:31px]" + end + + end + + context "late line" do + should "start from the starting point on the left" do + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project_late[style*=left:52px]" + end + + should "be the total delayed width of the project" do + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project_late[style*=width:6px]" + end + end + + context "done line" do + should "start from the starting point on the left" do + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project_done[style*=left:52px]" + end + + should "Be the total done width of the project" do + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project_done[style*=left:52px]" + end + end + + context "starting marker" do + should "not appear if the starting point is off the gantt chart" do + # Shift the date range of the chart + @gantt.instance_variable_set('@date_from', Date.today) + + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project-line.starting", false + end + + should "appear at the starting point" do + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project-line.starting[style*=left:52px]" + end + end + + context "ending marker" do + should "not appear if the starting point is off the gantt chart" do + # Shift the date range of the chart + @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date) + + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project-line.ending", false + + end + + should "appear at the end of the date range" do + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project-line.ending[style*=left:84px]" + end + end + + context "status content" do + should "appear at the far left, even if it's far in the past" do + @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date) + + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project-name", /#{@project.name}/ + end + + should "show the project name" do + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project-name", /#{@project.name}/ + end + + should "show the percent complete" do + @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) + assert_select "div.project-name", /0%/ + end + end + end + + should "test the PNG format" + should "test the PDF format" + end + + context "#subject_for_version" do + setup do + create_gantt + @project.enabled_module_names = [:issue_tracking] + @tracker = Tracker.generate! + @project.trackers << @tracker + @version = Version.generate!(:effective_date => Date.yesterday) + @project.versions << @version + + @project.issues << Issue.generate!(:fixed_version => @version, + :subject => "gantt#subject_for_version", + :tracker => @tracker, + :project => @project, + :start_date => Date.today) + + end + + context ":html format" do + should "add an absolute positioned div" do + @response.body = @gantt.subject_for_version(@version, {:format => :html}) + assert_select "div[style*=absolute]" + end + + should "use the indent option to move the div to the right" do + @response.body = @gantt.subject_for_version(@version, {:format => :html, :indent => 40}) + assert_select "div[style*=left:40]" + end + + should "include the version name" do + @response.body = @gantt.subject_for_version(@version, {:format => :html}) + assert_select 'div', :text => /#{@version.name}/ + end + + should "include a link to the version" do + @response.body = @gantt.subject_for_version(@version, {:format => :html}) + assert_select 'a[href=?]', Regexp.escape("/versions/show/#{@version.to_param}"), :text => /#{@version.name}/ + end + + should "style late versions" do + assert @version.overdue?, "Need an overdue version for this test" + @response.body = @gantt.subject_for_version(@version, {:format => :html}) + + assert_select 'div span.version-behind-schedule' + end + + should "style behind schedule versions" do + assert @version.behind_schedule?, "Need a behind schedule version for this test" + @response.body = @gantt.subject_for_version(@version, {:format => :html}) + + assert_select 'div span.version-behind-schedule' + end + end + should "test the PNG format" + should "test the PDF format" + end + + context "#line_for_version" do + setup do + create_gantt + @project.enabled_module_names = [:issue_tracking] + @tracker = Tracker.generate! + @project.trackers << @tracker + @version = Version.generate!(:effective_date => 1.week.from_now.to_date) + @project.versions << @version + + @project.issues << Issue.generate!(:fixed_version => @version, + :subject => "gantt#line_for_project", + :tracker => @tracker, + :project => @project, + :done_ratio => 30, + :start_date => Date.yesterday, + :due_date => 1.week.from_now.to_date) + end + + context ":html format" do + context "todo line" do + should "start from the starting point on the left" do + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.milestone_todo[style*=left:52px]" + end + + should "be the total width of the version" do + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.milestone_todo[style*=width:31px]" + end + + end + + context "late line" do + should "start from the starting point on the left" do + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.milestone_late[style*=left:52px]" + end + + should "be the total delayed width of the version" do + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.milestone_late[style*=width:6px]" + end + end + + context "done line" do + should "start from the starting point on the left" do + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.milestone_done[style*=left:52px]" + end + + should "Be the total done width of the version" do + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.milestone_done[style*=left:52px]" + end + end + + context "starting marker" do + should "not appear if the starting point is off the gantt chart" do + # Shift the date range of the chart + @gantt.instance_variable_set('@date_from', Date.today) + + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.milestone.starting", false + end + + should "appear at the starting point" do + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.milestone.starting[style*=left:52px]" + end + end + + context "ending marker" do + should "not appear if the starting point is off the gantt chart" do + # Shift the date range of the chart + @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date) + + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.milestone.ending", false + + end + + should "appear at the end of the date range" do + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.milestone.ending[style*=left:84px]" + end + end + + context "status content" do + should "appear at the far left, even if it's far in the past" do + @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date) + + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.version-name", /#{@version.name}/ + end + + should "show the version name" do + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.version-name", /#{@version.name}/ + end + + should "show the percent complete" do + @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) + assert_select "div.version-name", /30%/ + end + end + end + + should "test the PNG format" + should "test the PDF format" + end + + context "#subject_for_issue" do + setup do + create_gantt + @project.enabled_module_names = [:issue_tracking] + @tracker = Tracker.generate! + @project.trackers << @tracker + + @issue = Issue.generate!(:subject => "gantt#subject_for_issue", + :tracker => @tracker, + :project => @project, + :start_date => 3.days.ago.to_date, + :due_date => Date.yesterday) + @project.issues << @issue + + end + + context ":html format" do + should "add an absolute positioned div" do + @response.body = @gantt.subject_for_issue(@issue, {:format => :html}) + assert_select "div[style*=absolute]" + end + + should "use the indent option to move the div to the right" do + @response.body = @gantt.subject_for_issue(@issue, {:format => :html, :indent => 40}) + assert_select "div[style*=left:40]" + end + + should "include the issue subject" do + @response.body = @gantt.subject_for_issue(@issue, {:format => :html}) + assert_select 'div', :text => /#{@issue.subject}/ + end + + should "include a link to the issue" do + @response.body = @gantt.subject_for_issue(@issue, {:format => :html}) + assert_select 'a[href=?]', Regexp.escape("/issues/#{@issue.to_param}"), :text => /#{@tracker.name} ##{@issue.id}/ + end + + should "style overdue issues" do + assert @issue.overdue?, "Need an overdue issue for this test" + @response.body = @gantt.subject_for_issue(@issue, {:format => :html}) + + assert_select 'div span.issue-overdue' + end + + end + should "test the PNG format" + should "test the PDF format" + end + + context "#line_for_issue" do + setup do + create_gantt + @project.enabled_module_names = [:issue_tracking] + @tracker = Tracker.generate! + @project.trackers << @tracker + @version = Version.generate!(:effective_date => 1.week.from_now.to_date) + @project.versions << @version + @issue = Issue.generate!(:fixed_version => @version, + :subject => "gantt#line_for_project", + :tracker => @tracker, + :project => @project, + :done_ratio => 30, + :start_date => Date.yesterday, + :due_date => 1.week.from_now.to_date) + @project.issues << @issue + end + + context ":html format" do + context "todo line" do + should "start from the starting point on the left" do + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.task_todo[style*=left:52px]" + end + + should "be the total width of the issue" do + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.task_todo[style*=width:34px]" + end + + end + + context "late line" do + should "start from the starting point on the left" do + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.task_late[style*=left:52px]" + end + + should "be the total delayed width of the issue" do + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.task_late[style*=width:6px]" + end + end + + context "done line" do + should "start from the starting point on the left" do + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.task_done[style*=left:52px]" + end + + should "Be the total done width of the issue" do + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.task_done[style*=left:52px]" + end + end + + context "status content" do + should "appear at the far left, even if it's far in the past" do + @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date) + + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.issue-name" + end + + should "show the issue status" do + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.issue-name", /#{@issue.status.name}/ + end + + should "show the percent complete" do + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.issue-name", /30%/ + end + end + end + + should "have an issue tooltip" do + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.tooltip", /#{@issue.subject}/ + end + + should "test the PNG format" + should "test the PDF format" + end + + context "#to_image" do + should "be tested" + end + + context "#to_pdf" do + should "be tested" + end + +end diff --git a/test/unit/project_test.rb b/test/unit/project_test.rb index 7870dc2a5..b0b5a4837 100644 --- a/test/unit/project_test.rb +++ b/test/unit/project_test.rb @@ -842,4 +842,122 @@ class ProjectTest < ActiveSupport::TestCase end + context "#start_date" do + setup do + ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests + @project = Project.generate!(:identifier => 'test0') + @project.trackers << Tracker.generate! + end + + should "be nil if there are no issues on the project" do + assert_nil @project.start_date + end + + should "be nil if issue tracking is disabled" do + Issue.generate_for_project!(@project, :start_date => Date.today) + @project.enabled_modules.find_all_by_name('issue_tracking').each {|m| m.destroy} + @project.reload + + assert_nil @project.start_date + end + + should "be the earliest start date of it's issues" do + early = 7.days.ago.to_date + Issue.generate_for_project!(@project, :start_date => Date.today) + Issue.generate_for_project!(@project, :start_date => early) + + assert_equal early, @project.start_date + end + + end + + context "#due_date" do + setup do + ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests + @project = Project.generate!(:identifier => 'test0') + @project.trackers << Tracker.generate! + end + + should "be nil if there are no issues on the project" do + assert_nil @project.due_date + end + + should "be nil if issue tracking is disabled" do + Issue.generate_for_project!(@project, :due_date => Date.today) + @project.enabled_modules.find_all_by_name('issue_tracking').each {|m| m.destroy} + @project.reload + + assert_nil @project.due_date + end + + should "be the latest due date of it's issues" do + future = 7.days.from_now.to_date + Issue.generate_for_project!(@project, :due_date => future) + Issue.generate_for_project!(@project, :due_date => Date.today) + + assert_equal future, @project.due_date + end + + should "be the latest due date of it's versions" do + future = 7.days.from_now.to_date + @project.versions << Version.generate!(:effective_date => future) + @project.versions << Version.generate!(:effective_date => Date.today) + + + assert_equal future, @project.due_date + + end + + should "pick the latest date from it's issues and versions" do + future = 7.days.from_now.to_date + far_future = 14.days.from_now.to_date + Issue.generate_for_project!(@project, :due_date => far_future) + @project.versions << Version.generate!(:effective_date => future) + + assert_equal far_future, @project.due_date + end + + end + + context "Project#completed_percent" do + setup do + ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests + @project = Project.generate!(:identifier => 'test0') + @project.trackers << Tracker.generate! + end + + context "no versions" do + should "be 100" do + assert_equal 100, @project.completed_percent + end + end + + context "with versions" do + should "return 0 if the versions have no issues" do + Version.generate!(:project => @project) + Version.generate!(:project => @project) + + assert_equal 0, @project.completed_percent + end + + should "return 100 if the version has only closed issues" do + v1 = Version.generate!(:project => @project) + Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1) + v2 = Version.generate!(:project => @project) + Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2) + + assert_equal 100, @project.completed_percent + end + + should "return the averaged completed percent of the versions (not weighted)" do + v1 = Version.generate!(:project => @project) + Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1) + v2 = Version.generate!(:project => @project) + Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2) + + assert_equal 50, @project.completed_percent + end + + end + end end diff --git a/test/unit/version_test.rb b/test/unit/version_test.rb index 1abb4a272..b30eedaea 100644 --- a/test/unit/version_test.rb +++ b/test/unit/version_test.rb @@ -104,7 +104,57 @@ class VersionTest < ActiveSupport::TestCase assert_progress_equal (25.0*0.2 + 25.0*1 + 10.0*0.3 + 40.0*0.1)/100.0*100, v.completed_pourcent assert_progress_equal 25.0/100.0*100, v.closed_pourcent end - + + context "#behind_schedule?" do + setup do + ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests + @project = Project.generate!(:identifier => 'test0') + @project.trackers << Tracker.generate! + + @version = Version.generate!(:project => @project, :effective_date => nil) + end + + should "be false if there are no issues assigned" do + @version.update_attribute(:effective_date, Date.yesterday) + assert_equal false, @version.behind_schedule? + end + + should "be false if there is no effective_date" do + assert_equal false, @version.behind_schedule? + end + + should "be false if all of the issues are ahead of schedule" do + @version.update_attribute(:effective_date, 7.days.from_now.to_date) + @version.fixed_issues = [ + Issue.generate_for_project!(@project, :start_date => 7.days.ago, :done_ratio => 60), # 14 day span, 60% done, 50% time left + Issue.generate_for_project!(@project, :start_date => 7.days.ago, :done_ratio => 60) # 14 day span, 60% done, 50% time left + ] + assert_equal 60, @version.completed_pourcent + assert_equal false, @version.behind_schedule? + end + + should "be true if any of the issues are behind schedule" do + @version.update_attribute(:effective_date, 7.days.from_now.to_date) + @version.fixed_issues = [ + Issue.generate_for_project!(@project, :start_date => 7.days.ago, :done_ratio => 60), # 14 day span, 60% done, 50% time left + Issue.generate_for_project!(@project, :start_date => 7.days.ago, :done_ratio => 20) # 14 day span, 20% done, 50% time left + ] + assert_equal 40, @version.completed_pourcent + assert_equal true, @version.behind_schedule? + end + + should "be false if all of the issues are complete" do + @version.update_attribute(:effective_date, 7.days.from_now.to_date) + @version.fixed_issues = [ + Issue.generate_for_project!(@project, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)), # 7 day span + Issue.generate_for_project!(@project, :start_date => 14.days.ago, :done_ratio => 100, :status => IssueStatus.find(5)) # 7 day span + ] + assert_equal 100, @version.completed_pourcent + assert_equal false, @version.behind_schedule? + + end + end + context "#estimated_hours" do setup do @version = Version.create!(:project_id => 1, :name => '#estimated_hours') diff --git a/vendor/plugins/gravatar/Rakefile b/vendor/plugins/gravatar/Rakefile index 9e4854916..e67e5e7f9 100644 --- a/vendor/plugins/gravatar/Rakefile +++ b/vendor/plugins/gravatar/Rakefile @@ -6,7 +6,7 @@ task :default => :spec desc 'Run all application-specific specs' Spec::Rake::SpecTask.new(:spec) do |t| - t.rcov = true + # t.rcov = true end desc "Report code statistics (KLOCs, etc) from the application" diff --git a/vendor/plugins/gravatar/lib/gravatar.rb b/vendor/plugins/gravatar/lib/gravatar.rb index 6246645bc..9af1fed16 100644 --- a/vendor/plugins/gravatar/lib/gravatar.rb +++ b/vendor/plugins/gravatar/lib/gravatar.rb @@ -26,6 +26,9 @@ module GravatarHelper # decorational picture, the alt text should be empty according to the # XHTML specs. :alt => '', + + # The title text to use for the img tag for the gravatar. + :title => '', # The class to assign to the img tag for the gravatar. :class => 'gravatar', @@ -48,8 +51,8 @@ module GravatarHelper def gravatar(email, options={}) src = h(gravatar_url(email, options)) options = DEFAULT_OPTIONS.merge(options) - [:class, :alt, :size].each { |opt| options[opt] = h(options[opt]) } - "\"#{options[:alt]}\"" + [:class, :alt, :size, :title].each { |opt| options[opt] = h(options[opt]) } + "\"#{options[:alt]}\"" end # Returns the base Gravatar URL for the given email hash. If ssl evaluates to true, @@ -82,4 +85,4 @@ module GravatarHelper end -end \ No newline at end of file +end diff --git a/vendor/plugins/gravatar/spec/gravatar_spec.rb b/vendor/plugins/gravatar/spec/gravatar_spec.rb index a11d2683a..6f78d79ad 100644 --- a/vendor/plugins/gravatar/spec/gravatar_spec.rb +++ b/vendor/plugins/gravatar/spec/gravatar_spec.rb @@ -4,34 +4,40 @@ require 'active_support' # to get "returning" require File.dirname(__FILE__) + '/../lib/gravatar' include GravatarHelper, GravatarHelper::PublicMethods, ERB::Util -context "gravatar_url with a custom default URL" do - setup do +describe "gravatar_url with a custom default URL" do + before(:each) do @original_options = DEFAULT_OPTIONS.dup DEFAULT_OPTIONS[:default] = "no_avatar.png" @url = gravatar_url("somewhere") end - specify "should include the \"default\" argument in the result" do + it "should include the \"default\" argument in the result" do @url.should match(/&default=no_avatar.png/) end - teardown do + after(:each) do DEFAULT_OPTIONS.merge!(@original_options) end end -context "gravatar_url with default settings" do - setup do +describe "gravatar_url with default settings" do + before(:each) do @url = gravatar_url("somewhere") end - specify "should have a nil default URL" do + it "should have a nil default URL" do DEFAULT_OPTIONS[:default].should be_nil end - specify "should not include the \"default\" argument in the result" do + it "should not include the \"default\" argument in the result" do @url.should_not match(/&default=/) end -end \ No newline at end of file +end + +describe "gravatar with a custom title option" do + it "should include the title in the result" do + gravatar('example@example.com', :title => "This is a title attribute").should match(/This is a title attribute/) + end +end From abf3ee4999050bd24ba75a53805bc7e7ac80ed0d Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 10 Sep 2010 03:09:11 +0000 Subject: [PATCH 22/49] Add project names to the Versions in the Gantt export. #5904 git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4073 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/version.rb | 4 ++++ lib/redmine/helpers/gantt.rb | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/version.rb b/app/models/version.rb index c3969fe87..95e6ad5f6 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -135,6 +135,10 @@ class Version < ActiveRecord::Base end def to_s; name end + + def to_s_with_project + "#{project} - #{name}" + end # Versions are sorted by effective_date and "Project Name - Version name" # Those with no effective_date are at the end, sorted by "Project Name - Version name" diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index 33a4e1c2c..7c694022a 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -399,13 +399,13 @@ module Redmine options[:image].fill('black') options[:image].stroke('transparent') options[:image].stroke_width(1) - options[:image].text(options[:indent], options[:top] + 2, version.name) + options[:image].text(options[:indent], options[:top] + 2, version.to_s_with_project) when :pdf options[:pdf].SetY(options[:top]) options[:pdf].SetX(15) char_limit = PDF::MaxCharactorsForSubject - options[:indent] - options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{version.name}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR") + options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{version.to_s_with_project}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR") options[:pdf].SetY(options[:top]) options[:pdf].SetX(options[:subject_width]) From 5e1c29523003f697b4ba88998c1c587881785820 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 10 Sep 2010 03:09:18 +0000 Subject: [PATCH 23/49] Fixed the zoom, previous, and next links on the Gantt chart. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4074 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/helpers/issues_helper.rb | 4 ++-- app/views/gantts/show.html.erb | 4 ++-- lib/redmine/helpers/gantt.rb | 10 +++++++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 284aae91a..f1ddcfcbf 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -245,7 +245,7 @@ module IssuesHelper when :in if gantt.zoom < 4 link_to_remote(l(:text_zoom_in) + image_tag('zoom_in.png', img_attributes.merge(:alt => l(:text_zoom_in))), - {:url => gantt.params.merge(:zoom => (gantt.zoom+1)), :update => 'content'}, + {:url => gantt.params.merge(:zoom => (gantt.zoom+1)), :method => :get, :update => 'content'}, {:href => url_for(gantt.params.merge(:zoom => (gantt.zoom+1)))}) else l(:text_zoom_in) + @@ -255,7 +255,7 @@ module IssuesHelper when :out if gantt.zoom > 1 link_to_remote(l(:text_zoom_out) + image_tag('zoom_out.png', img_attributes.merge(:alt => l(:text_zoom_out))), - {:url => gantt.params.merge(:zoom => (gantt.zoom-1)), :update => 'content'}, + {:url => gantt.params.merge(:zoom => (gantt.zoom-1)), :method => :get, :update => 'content'}, {:href => url_for(gantt.params.merge(:zoom => (gantt.zoom-1)))}) else l(:text_zoom_out) + diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb index ce8c67b26..5a64054af 100644 --- a/app/views/gantts/show.html.erb +++ b/app/views/gantts/show.html.erb @@ -169,8 +169,8 @@ if Date.today >= @gantt.date_from and Date.today <= @gantt.date_to %> - - + +
    <%= link_to_remote ('« ' + l(:label_previous)), {:url => @gantt.params_previous, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(@gantt.params_previous)} %><%= link_to_remote (l(:label_next) + ' »'), {:url => @gantt.params_next, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(@gantt.params_next)} %><%= link_to_remote ('« ' + l(:label_previous)), {:url => @gantt.params_previous, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(@gantt.params_previous)} %><%= link_to_remote (l(:label_next) + ' »'), {:url => @gantt.params_next, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(@gantt.params_next)} %>
    diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index 7c694022a..cec323720 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -68,17 +68,21 @@ module Redmine @date_from = Date.civil(@year_from, @month_from, 1) @date_to = (@date_from >> @months) - 1 end + + def common_params + { :controller => 'gantts', :action => 'show', :project_id => @project } + end def params - { :zoom => zoom, :year => year_from, :month => month_from, :months => months } + common_params.merge({ :zoom => zoom, :year => year_from, :month => month_from, :months => months }) end def params_previous - { :year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months } + common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months }) end def params_next - { :year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months } + common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months }) end ### Extracted from the HTML view/helpers From 3a2efb47572d198c91a09b1bf92e717b843980ed Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 10 Sep 2010 16:00:49 +0000 Subject: [PATCH 24/49] Refactor: convert ProjectEnumerations to a resource on a project. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4075 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/application_controller.rb | 7 ++++++ .../project_enumerations_controller.rb | 6 ++--- app/views/projects/settings/_activities.rhtml | 4 ++-- config/routes.rb | 10 +++------ lib/redmine.rb | 2 +- .../project_enumerations_controller_test.rb | 22 +++++++++---------- test/integration/routing_test.rb | 4 ++-- 7 files changed, 29 insertions(+), 26 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 725bde788..32bb528a3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -169,6 +169,13 @@ class ApplicationController < ActionController::Base render_404 end + # Find project of id params[:project_id] + def find_project_by_project_id + @project = Project.find(params[:project_id]) + rescue ActiveRecord::RecordNotFound + render_404 + end + # Find a project based on params[:project_id] # TODO: some subclasses override this, see about merging their logic def find_optional_project diff --git a/app/controllers/project_enumerations_controller.rb b/app/controllers/project_enumerations_controller.rb index d63e23641..0b15887fa 100644 --- a/app/controllers/project_enumerations_controller.rb +++ b/app/controllers/project_enumerations_controller.rb @@ -1,9 +1,9 @@ class ProjectEnumerationsController < ApplicationController - before_filter :find_project + before_filter :find_project_by_project_id before_filter :authorize - def save - if request.post? && params[:enumerations] + def update + if request.put? && params[:enumerations] Project.transaction do params[:enumerations].each do |id, activity| @project.update_or_create_time_entry_activity(id, activity) diff --git a/app/views/projects/settings/_activities.rhtml b/app/views/projects/settings/_activities.rhtml index 5909ac64c..726e6240c 100644 --- a/app/views/projects/settings/_activities.rhtml +++ b/app/views/projects/settings/_activities.rhtml @@ -1,4 +1,4 @@ -<% form_tag({:controller => 'project_enumerations', :action => 'save', :id => @project}, :class => "tabular") do %> +<% form_tag(project_project_enumerations_path(@project), :method => :put, :class => "tabular") do %> @@ -32,7 +32,7 @@
    -<%= link_to(l(:button_reset), {:controller => 'project_enumerations', :action => 'destroy', :id => @project}, +<%= link_to(l(:button_reset), project_project_enumerations_path(@project), :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del') %> diff --git a/config/routes.rb b/config/routes.rb index 9f12cd454..129c3b456 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -179,7 +179,9 @@ ActionController::Routing::Routes.draw do |map| :modules => :post, :archive => :post, :unarchive => :post - } + } do |project| + project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy] + end # Destroy uses a get request to prompt the user before the actual DELETE request map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get} @@ -195,13 +197,7 @@ ActionController::Routing::Routes.draw do |map| project_mapper.with_options :conditions => {:method => :post} do |project_actions| project_actions.connect 'projects/:id/files/new', :controller => 'files', :action => 'new' - project_actions.connect 'projects/:id/activities/save', :controller => 'project_enumerations', :action => 'save' end - - project_mapper.with_options :conditions => {:method => :delete} do |project_actions| - project_actions.conditions 'projects/:id/reset_activities', :controller => 'project_enumerations', :action => 'destroy' - end - end map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity| diff --git a/lib/redmine.rb b/lib/redmine.rb index 534f85875..526b83e65 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -87,7 +87,7 @@ Redmine::AccessControl.map do |map| map.permission :view_time_entries, :timelog => [:details, :report] map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin - map.permission :manage_project_activities, {:project_enumerations => [:save, :destroy]}, :require => :member + map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member end map.project_module :news do |map| diff --git a/test/functional/project_enumerations_controller_test.rb b/test/functional/project_enumerations_controller_test.rb index c03be04fb..942399a96 100644 --- a/test/functional/project_enumerations_controller_test.rb +++ b/test/functional/project_enumerations_controller_test.rb @@ -8,11 +8,11 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase Setting.default_language = 'en' end - def test_save_to_override_system_activities + def test_update_to_override_system_activities @request.session[:user_id] = 2 # manager billable_field = TimeEntryActivityCustomField.find_by_name("Billable") - post :save, :id => 1, :enumerations => { + put :update, :project_id => 1, :enumerations => { "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design, De-activate "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"}, # Development, Change custom value "14"=>{"parent_id"=>"14", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"}, # Inactive Activity, Activate with custom value @@ -58,7 +58,7 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase assert_equal nil, project.time_entry_activities.find_by_name("QA"), "Custom QA activity created when it wasn't modified" end - def test_save_will_update_project_specific_activities + def test_update_will_update_project_specific_activities @request.session[:user_id] = 2 # manager project_activity = TimeEntryActivity.new({ @@ -77,7 +77,7 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase assert project_activity_two.save - post :save, :id => 1, :enumerations => { + put :update, :project_id => 1, :enumerations => { project_activity.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # De-activate project_activity_two.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"} # De-activate } @@ -100,11 +100,11 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase assert !activity_two.active? end - def test_save_when_creating_new_activities_will_convert_existing_data + def test_update_when_creating_new_activities_will_convert_existing_data assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size @request.session[:user_id] = 2 # manager - post :save, :id => 1, :enumerations => { + put :update, :project_id => 1, :enumerations => { "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"} # Design, De-activate } assert_response :redirect @@ -116,7 +116,7 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(project_specific_activity.id, 1).size, "No Time Entries assigned to the project activity" end - def test_save_when_creating_new_activities_will_not_convert_existing_data_if_an_exception_is_raised + def test_update_when_creating_new_activities_will_not_convert_existing_data_if_an_exception_is_raised # TODO: Need to cause an exception on create but these tests # aren't setup for mocking. Just create a record now so the # second one is a dupicate @@ -128,7 +128,7 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size @request.session[:user_id] = 2 # manager - post :save, :id => 1, :enumerations => { + put :update, :project_id => 1, :enumerations => { "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"} # Development, Change custom value } @@ -140,7 +140,7 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size, "Time Entries are not assigned to system activities" end - def test_destroy + def test_destroy @request.session[:user_id] = 2 # manager project_activity = TimeEntryActivity.new({ :name => 'Project Specific', @@ -157,7 +157,7 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase }) assert project_activity_two.save - delete :destroy, :id => 1 + delete :destroy, :project_id => 1 assert_response :redirect assert_redirected_to 'projects/ecookbook/settings/activities' @@ -177,7 +177,7 @@ class ProjectEnumerationsControllerTest < ActionController::TestCase assert TimeEntry.update_all("activity_id = '#{project_activity.id}'", ["project_id = ? AND activity_id = ?", 1, 9]) assert 3, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size - delete :destroy, :id => 1 + delete :destroy, :project_id => 1 assert_response :redirect assert_redirected_to 'projects/ecookbook/settings/activities' diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index 5cd0b2d39..ac75d4d7c 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -182,14 +182,14 @@ class RoutingTest < ActionController::IntegrationTest should_route :post, "/projects/33/files/new", :controller => 'files', :action => 'new', :id => '33' should_route :post, "/projects/64/archive", :controller => 'projects', :action => 'archive', :id => '64' should_route :post, "/projects/64/unarchive", :controller => 'projects', :action => 'unarchive', :id => '64' - should_route :post, "/projects/64/activities/save", :controller => 'project_enumerations', :action => 'save', :id => '64' + should_route :put, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'update', :project_id => '64' should_route :put, "/projects/4223", :controller => 'projects', :action => 'update', :id => '4223' should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'update', :id => '1', :format => 'xml' should_route :delete, "/projects/64", :controller => 'projects', :action => 'destroy', :id => '64' should_route :delete, "/projects/1.xml", :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml' - should_route :delete, "/projects/64/reset_activities", :controller => 'project_enumerations', :action => 'destroy', :id => '64' + should_route :delete, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'destroy', :project_id => '64' end context "repositories" do From 4c656fcffcfe43482d62065122f6029bba62090d Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 10 Sep 2010 18:46:23 +0000 Subject: [PATCH 25/49] Define Principal#name so all subclasses will have some sort of name when printed. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4076 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/principal.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/principal.rb b/app/models/principal.rb index 58c3f0497..b3e07dda5 100644 --- a/app/models/principal.rb +++ b/app/models/principal.rb @@ -33,7 +33,11 @@ class Principal < ActiveRecord::Base } before_create :set_default_empty_values - + + def name(formatter = nil) + to_s + end + def <=>(principal) if self.class.name == principal.class.name self.to_s.downcase <=> principal.to_s.downcase From 109b42f4828c0966e771809afcfd99d545af3ca0 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 10 Sep 2010 18:46:29 +0000 Subject: [PATCH 26/49] Added a "Member of Group" to the issues filter. #5869 This filter will check an issue's assigned to field for users in (or not in) specific groups. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4077 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/query.rb | 19 +++++++++ config/locales/en.yml | 1 + test/unit/query_test.rb | 90 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 108 insertions(+), 2 deletions(-) diff --git a/app/models/query.rb b/app/models/query.rb index b1f784528..96e99ef94 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -195,6 +195,9 @@ class Query < ActiveRecord::Base end @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty? @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty? + + group_values = Group.all.collect {|g| [g.name, g.id] } + @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty? if User.current.logged? @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] } @@ -432,6 +435,22 @@ class Query < ActiveRecord::Base db_field = 'user_id' sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " sql << sql_for_field(field, '=', v, db_table, db_field) + ')' + elsif field == "member_of_group" # named field + if operator == '*' # Any group + groups = Group.all + members_of_groups = groups.collect(&:user_ids).flatten.compact.collect(&:to_s) + operator = '=' # Override the operator since we want to find by assigned_to + elsif operator == "!*" + groups = Group.all + members_of_groups = groups.collect(&:user_ids).flatten.compact.collect(&:to_s) + operator = '!' # Override the operator since we want to find by assigned_to + else + groups = Group.find_all_by_id(v) + members_of_groups = groups.collect(&:user_ids).flatten.compact.collect(&:to_s) + end + + sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' + else # regular field db_table = Issue.table_name diff --git a/config/locales/en.yml b/config/locales/en.yml index 555043a8f..ba7a2da23 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -293,6 +293,7 @@ en: field_group_by: Group results by field_sharing: Sharing field_parent_issue: Parent task + field_member_of_group: Member of Group setting_app_title: Application title setting_app_subtitle: Application subtitle diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb index 12ab5d932..acab56ffd 100644 --- a/test/unit/query_test.rb +++ b/test/unit/query_test.rb @@ -48,6 +48,16 @@ class QueryTest < ActiveSupport::TestCase :conditions => query.statement end + def assert_find_issues_with_query_is_successful(query) + assert_nothing_raised do + find_issues_with_query(query) + end + end + + def assert_query_statement_includes(query, condition) + assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}" + end + def test_query_should_allow_shared_versions_for_a_project_query subproject_version = Version.find(4) query = Query.new(:project => Project.find(1), :name => '_') @@ -362,11 +372,87 @@ class QueryTest < ActiveSupport::TestCase end context "#available_filters" do + setup do + @query = Query.new(:name => "_") + end + should "include users of visible projects in cross-project view" do - query = Query.new(:name => "_") - users = query.available_filters["assigned_to_id"] + users = @query.available_filters["assigned_to_id"] assert_not_nil users assert users[:values].map{|u|u[1]}.include?("3") end + + context "'member_of_group' filter" do + should "be present" do + assert @query.available_filters.keys.include?("member_of_group") + end + + should "be an optional list" do + assert_equal :list_optional, @query.available_filters["member_of_group"][:type] + end + + should "have a list of the groups as values" do + Group.destroy_all # No fixtures + group1 = Group.generate!.reload + group2 = Group.generate!.reload + + expected_group_list = [ + [group1.name, group1.id], + [group2.name, group2.id] + ] + assert_equal expected_group_list, @query.available_filters["member_of_group"][:values] + end + + end + end + + context "#statement" do + context "with 'member_of_group' filter" do + setup do + Group.destroy_all # No fixtures + @user_in_group = User.generate! + @second_user_in_group = User.generate! + @user_in_group2 = User.generate! + @user_not_in_group = User.generate! + + @group = Group.generate!.reload + @group.users << @user_in_group + @group.users << @second_user_in_group + + @group2 = Group.generate!.reload + @group2.users << @user_in_group2 + + end + + should "search assigned to for users in the group" do + @query = Query.new(:name => '_') + @query.add_filter('member_of_group', '=', [@group.id.to_s]) + + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')" + assert_find_issues_with_query_is_successful @query + end + + should "search not assigned to any group member (none)" do + @query = Query.new(:name => '_') + @query.add_filter('member_of_group', '!*', ['']) + + # Users not in a group + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')" + assert_find_issues_with_query_is_successful @query + + end + + should "search assigned to any group member (all)" do + @query = Query.new(:name => '_') + @query.add_filter('member_of_group', '*', ['']) + + # Only users in a group + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')" + assert_find_issues_with_query_is_successful @query + + end + end + end + end From 41f8d043eb29452ff11baf5e5286cd907cda742e Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 10 Sep 2010 19:44:45 +0000 Subject: [PATCH 27/49] Added a "Member of Role" to the issues filters. #5869 This filter will check an issue's assigned to field for users who have (or don't have) a specific Role(s). git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4078 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/query.rb | 23 ++++++++++++ config/locales/en.yml | 1 + test/object_daddy_helpers.rb | 5 +++ test/unit/query_test.rb | 68 +++++++++++++++++++++++++++++++++++- 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/app/models/query.rb b/app/models/query.rb index 96e99ef94..4b2cc7557 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -198,6 +198,9 @@ class Query < ActiveRecord::Base group_values = Group.all.collect {|g| [g.name, g.id] } @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty? + + role_values = Role.givable.collect {|r| [r.name, r.id] } + @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty? if User.current.logged? @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] } @@ -451,6 +454,26 @@ class Query < ActiveRecord::Base sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' + elsif field == "assigned_to_role" # named field + if operator == "*" # Any Role + roles = Role.givable + operator = '=' # Override the operator since we want to find by assigned_to + elsif operator == "!*" # No role + roles = Role.givable + operator = '!' # Override the operator since we want to find by assigned_to + else + roles = Role.givable.find_all_by_id(v) + end + roles ||= [] + + members_of_roles = roles.inject([]) {|user_ids, role| + if role && role.members + user_ids << role.members.collect(&:user_id) + end + user_ids.flatten.uniq.compact + }.sort.collect(&:to_s) + + sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')' else # regular field db_table = Issue.table_name diff --git a/config/locales/en.yml b/config/locales/en.yml index ba7a2da23..cc4e31185 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -294,6 +294,7 @@ en: field_sharing: Sharing field_parent_issue: Parent task field_member_of_group: Member of Group + field_assigned_to_role: Member of Role setting_app_title: Application title setting_app_subtitle: Application subtitle diff --git a/test/object_daddy_helpers.rb b/test/object_daddy_helpers.rb index 7b144b18f..c94ada229 100644 --- a/test/object_daddy_helpers.rb +++ b/test/object_daddy_helpers.rb @@ -13,6 +13,11 @@ module ObjectDaddyHelpers User.spawn(attributes) end + def User.add_to_project(user, project, roles) + roles = [roles] unless roles.is_a?(Array) + Member.generate!(:principal => user, :project => project, :roles => roles) + end + # Generate the default Query def Query.generate_default!(attributes={}) query = Query.spawn(attributes) diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb index acab56ffd..f737b2f0c 100644 --- a/test/unit/query_test.rb +++ b/test/unit/query_test.rb @@ -404,7 +404,29 @@ class QueryTest < ActiveSupport::TestCase end end - + + context "'assigned_to_role' filter" do + should "be present" do + assert @query.available_filters.keys.include?("assigned_to_role") + end + + should "be an optional list" do + assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type] + end + + should "have a list of the Roles as values" do + assert @query.available_filters["assigned_to_role"][:values].include?(['Manager',1]) + assert @query.available_filters["assigned_to_role"][:values].include?(['Developer',2]) + assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter',3]) + end + + should "not include the built in Roles as values" do + assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member',4]) + assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous',5]) + end + + end + end context "#statement" do @@ -453,6 +475,50 @@ class QueryTest < ActiveSupport::TestCase end end + + context "with 'assigned_to_role' filter" do + setup do + # No fixtures + MemberRole.delete_all + Member.delete_all + Role.delete_all + + @manager_role = Role.generate!(:name => 'Manager') + @developer_role = Role.generate!(:name => 'Developer') + + @project = Project.generate! + @manager = User.generate! + @developer = User.generate! + @boss = User.generate! + User.add_to_project(@manager, @project, @manager_role) + User.add_to_project(@developer, @project, @developer_role) + User.add_to_project(@boss, @project, [@manager_role, @developer_role]) + end + + should "search assigned to for users with the Role" do + @query = Query.new(:name => '_') + @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s]) + + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@boss.id}')" + assert_find_issues_with_query_is_successful @query + end + + should "search assigned to for users not assigned to any Role (none)" do + @query = Query.new(:name => '_') + @query.add_filter('assigned_to_role', '!*', ['']) + + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')" + assert_find_issues_with_query_is_successful @query + end + + should "search assigned to for users assigned to any Role (all)" do + @query = Query.new(:name => '_') + @query.add_filter('assigned_to_role', '*', ['']) + + assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')" + assert_find_issues_with_query_is_successful @query + end + end end end From d36700eeee57dcd5da0078bf891b8186d30eb382 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 10 Sep 2010 19:53:57 +0000 Subject: [PATCH 28/49] Refactor: replace chained finders with an inject. Should handle edge cases better. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4079 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/query.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/models/query.rb b/app/models/query.rb index 4b2cc7557..59131afcd 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -441,16 +441,21 @@ class Query < ActiveRecord::Base elsif field == "member_of_group" # named field if operator == '*' # Any group groups = Group.all - members_of_groups = groups.collect(&:user_ids).flatten.compact.collect(&:to_s) operator = '=' # Override the operator since we want to find by assigned_to elsif operator == "!*" groups = Group.all - members_of_groups = groups.collect(&:user_ids).flatten.compact.collect(&:to_s) operator = '!' # Override the operator since we want to find by assigned_to else groups = Group.find_all_by_id(v) - members_of_groups = groups.collect(&:user_ids).flatten.compact.collect(&:to_s) end + groups ||= [] + + members_of_groups = groups.inject([]) {|user_ids, group| + if group && group.user_ids.present? + user_ids << group.user_ids + end + user_ids.flatten.uniq.compact + }.sort.collect(&:to_s) sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' From 878bb555227628d983997d96320a99ebb53adc7b Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 10 Sep 2010 23:07:10 +0000 Subject: [PATCH 29/49] Refactor: move method to Project#css_classes git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4080 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/helpers/admin_helper.rb | 8 -------- app/models/project.rb | 8 ++++++++ app/views/admin/projects.rhtml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index b49a5674c..8f81f66ba 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -20,12 +20,4 @@ module AdminHelper options_for_select([[l(:label_all), ''], [l(:status_active), 1]], selected) end - - def css_project_classes(project) - s = 'project' - s << ' root' if project.root? - s << ' child' if project.child? - s << (project.leaf? ? ' leaf' : ' parent') - s - end end diff --git a/app/models/project.rb b/app/models/project.rb index 5ef7915de..40898a34e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -413,6 +413,14 @@ class Project < ActiveRecord::Base description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description end + def css_classes + s = 'project' + s << ' root' if root? + s << ' child' if child? + s << (leaf? ? ' leaf' : ' parent') + s + end + # The earliest start date of a project, based on it's issues and versions def start_date if module_enabled?(:issue_tracking) diff --git a/app/views/admin/projects.rhtml b/app/views/admin/projects.rhtml index 6cf933d11..47a2d0583 100644 --- a/app/views/admin/projects.rhtml +++ b/app/views/admin/projects.rhtml @@ -26,7 +26,7 @@
    <%= link_to_project(project, :action => 'settings') %> <%= textilizable project.short_description, :project => project %> <%= checked_image project.is_public? %> <% if version.project == @project %> <%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %> - <%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> + <%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %> <% end %>