diff --git a/app/controllers/activities_controller.rb b/app/controllers/activities_controller.rb new file mode 100644 index 000000000..ae6a67369 --- /dev/null +++ b/app/controllers/activities_controller.rb @@ -0,0 +1,59 @@ +class ActivitiesController < ApplicationController + menu_item :activity + before_filter :find_optional_project + accept_key_auth :index + + def index + @days = Setting.activity_days_default.to_i + + if params[:from] + begin; @date_to = params[:from].to_date + 1; rescue; end + end + + @date_to ||= Date.today + 1 + @date_from = @date_to - @days + @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') + @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id])) + + @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, + :with_subprojects => @with_subprojects, + :author => @author) + @activity.scope_select {|t| !params["show_#{t}"].nil?} + @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty? + + events = @activity.events(@date_from, @date_to) + + if events.empty? || stale?(:etag => [events.first, User.current]) + respond_to do |format| + format.html { + @events_by_day = events.group_by(&:event_date) + render :layout => false if request.xhr? + } + format.atom { + title = l(:label_activity) + if @author + title = @author.name + elsif @activity.scope.size == 1 + title = l("label_#{@activity.scope.first.singularize}_plural") + end + render_feed(events, :title => "#{@project || Setting.app_title}: #{title}") + } + end + end + + rescue ActiveRecord::RecordNotFound + render_404 + end + + private + + # TODO: refactor, duplicated in projects_controller + def find_optional_project + return true unless params[:id] + @project = Project.find(params[:id]) + authorize + rescue ActiveRecord::RecordNotFound + render_404 + end + +end 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/calendars_controller.rb b/app/controllers/calendars_controller.rb index 1115691a1..febacd075 100644 --- a/app/controllers/calendars_controller.rb +++ b/app/controllers/calendars_controller.rb @@ -32,8 +32,11 @@ class CalendarsController < ApplicationController @calendar.events = events end - render :layout => false if request.xhr? + render :action => 'show', :layout => false if request.xhr? end + def update + show + end end diff --git a/app/controllers/context_menus_controller.rb b/app/controllers/context_menus_controller.rb index 442f85b37..5f4b02ca2 100644 --- a/app/controllers/context_menus_controller.rb +++ b/app/controllers/context_menus_controller.rb @@ -6,9 +6,15 @@ 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 + @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/controllers/files_controller.rb b/app/controllers/files_controller.rb new file mode 100644 index 000000000..72a4a2411 --- /dev/null +++ b/app/controllers/files_controller.rb @@ -0,0 +1,36 @@ +class FilesController < ApplicationController + menu_item :files + + before_filter :find_project_by_project_id + before_filter :authorize + + helper :sort + include SortHelper + + def index + sort_init 'filename', 'asc' + sort_update 'filename' => "#{Attachment.table_name}.filename", + 'created_on' => "#{Attachment.table_name}.created_on", + 'size' => "#{Attachment.table_name}.filesize", + 'downloads' => "#{Attachment.table_name}.downloads" + + @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)] + @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse + render :layout => !request.xhr? + end + + def new + @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 project_files_path(@project) + end +end diff --git a/app/controllers/gantts_controller.rb b/app/controllers/gantts_controller.rb index cdfef5238..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,33 +15,22 @@ 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 + def update + show + end + end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index dc1cef492..385bfa757 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -20,7 +20,7 @@ class IssuesController < ApplicationController default_search_scope :issues before_filter :find_issue, :only => [:show, :edit, :update] - before_filter :find_issues, :only => [:bulk_edit, :move, :perform_move, :destroy] + before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :move, :perform_move, :destroy] before_filter :find_project, :only => [:new, :create] before_filter :authorize, :except => [:index] before_filter :find_optional_project, :only => [:index] @@ -47,6 +47,7 @@ class IssuesController < ApplicationController include SortHelper include IssuesHelper helper :timelog + helper :gantt include Redmine::Export::PDF verify :method => [:post, :delete], @@ -54,6 +55,7 @@ class IssuesController < ApplicationController :render => { :nothing => true, :status => :method_not_allowed } verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } + verify :method => :post, :only => :bulk_update, :render => {:nothing => true, :status => :method_not_allowed } verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed } def index @@ -132,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) } @@ -191,29 +193,28 @@ class IssuesController < ApplicationController # Bulk edit a set of issues def bulk_edit @issues.sort! - if request.post? - attributes = (params[:issue] || {}).reject {|k,v| v.blank?} - attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} - attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values] - - unsaved_issue_ids = [] - @issues.each do |issue| - issue.reload - journal = issue.init_journal(User.current, params[:notes]) - issue.safe_attributes = attributes - call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) - unless issue.save - # Keep unsaved issue ids to display them in flash error - unsaved_issue_ids << issue.id - end - end - set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) - redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project}) - return - end @available_statuses = Workflow.available_statuses(@project) @custom_fields = @project.all_issue_custom_fields end + + def bulk_update + @issues.sort! + attributes = parse_params_for_bulk_issue_attributes(params) + + unsaved_issue_ids = [] + @issues.each do |issue| + issue.reload + journal = issue.init_journal(User.current, params[:notes]) + issue.safe_attributes = attributes + call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) + unless issue.save + # Keep unsaved issue ids to display them in flash error + unsaved_issue_ids << issue.id + end + end + set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) + redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project}) + end def destroy @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f @@ -270,7 +271,7 @@ private @edit_allowed = User.current.allowed_to?(:edit_issues, @project) @time_entry = TimeEntry.new - @notes = params[:notes] + @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil) @issue.init_journal(User.current, @notes) # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue] @@ -315,4 +316,11 @@ private return false end end + + def parse_params_for_bulk_issue_attributes(params) + attributes = (params[:issue] || {}).reject {|k,v| v.blank?} + attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'} + attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values] + attributes + end end diff --git a/app/controllers/news_controller.rb b/app/controllers/news_controller.rb index 4d44158a4..f4e4b6559 100644 --- a/app/controllers/news_controller.rb +++ b/app/controllers/news_controller.rb @@ -18,9 +18,9 @@ class NewsController < ApplicationController default_search_scope :news model_object News - before_filter :find_model_object, :except => [:new, :index, :preview] - before_filter :find_project_from_association, :except => [:new, :index, :preview] - before_filter :find_project, :only => [:new, :preview] + before_filter :find_model_object, :except => [:new, :create, :index, :preview] + before_filter :find_project_from_association, :except => [:new, :create, :index, :preview] + before_filter :find_project, :only => [:new, :create, :preview] before_filter :authorize, :except => [:index, :preview] before_filter :find_optional_project, :only => :index accept_key_auth :index @@ -46,11 +46,17 @@ class NewsController < ApplicationController def new @news = News.new(:project => @project, :author => User.current) + end + + def create + @news = News.new(:project => @project, :author => User.current) if request.post? @news.attributes = params[:news] if @news.save flash[:notice] = l(:notice_successful_create) redirect_to :controller => 'news', :action => 'index', :project_id => @project + else + render :action => 'new' end end end diff --git a/app/controllers/project_enumerations_controller.rb b/app/controllers/project_enumerations_controller.rb new file mode 100644 index 000000000..0b15887fa --- /dev/null +++ b/app/controllers/project_enumerations_controller.rb @@ -0,0 +1,26 @@ +class ProjectEnumerationsController < ApplicationController + before_filter :find_project_by_project_id + before_filter :authorize + + 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) + end + end + flash[:notice] = l(:notice_successful_update) + end + + 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 44071d214..f16349329 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -17,24 +17,24 @@ class ProjectsController < ApplicationController menu_item :overview - menu_item :activity, :only => :activity menu_item :roadmap, :only => :roadmap - menu_item :files, :only => [:list_files, :add_file] menu_item :settings, :only => :settings - before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ] - before_filter :find_optional_project, :only => :activity - before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ] - before_filter :authorize_global, :only => :add + 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 :activity, :index - - after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller| + accept_key_auth :index + + 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 @@ -63,40 +63,45 @@ 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]) - 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 => 'new' } + format.xml { render :xml => @project.errors, :status => :unprocessable_entity } + end + end + end def copy @@ -177,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 @@ -241,120 +245,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 => 'projects', :action => 'list_files', :id => @project - return - end - @versions = @project.versions.sort - 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) - end - flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project - end - - def list_files - sort_init 'filename', 'asc' - sort_update 'filename' => "#{Attachment.table_name}.filename", - 'created_on' => "#{Attachment.table_name}.created_on", - 'size' => "#{Attachment.table_name}.filesize", - 'downloads' => "#{Attachment.table_name}.downloads" - - @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)] - @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse - render :layout => !request.xhr? - end - - def roadmap - @trackers = @project.trackers.find(:all, :order => 'position') - retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?}) - @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') - project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id] - - @versions = @project.shared_versions || [] - @versions += @project.rolled_up_versions.visible if @with_subprojects - @versions = @versions.uniq.sort - @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed] - - @issues_by_version = {} - unless @selected_tracker_ids.empty? - @versions.each do |version| - issues = version.fixed_issues.visible.find(:all, - :include => [:project, :status, :tracker, :priority], - :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids}, - :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id") - @issues_by_version[version] = issues - end - end - @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} - end - - def activity - @days = Setting.activity_days_default.to_i - - if params[:from] - begin; @date_to = params[:from].to_date + 1; rescue; end - end - - @date_to ||= Date.today + 1 - @date_from = @date_to - @days - @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') - @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id])) - - @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project, - :with_subprojects => @with_subprojects, - :author => @author) - @activity.scope_select {|t| !params["show_#{t}"].nil?} - @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty? - - events = @activity.events(@date_from, @date_to) - - if events.empty? || stale?(:etag => [events.first, User.current]) - respond_to do |format| - format.html { - @events_by_day = events.group_by(&:event_date) - render :layout => false if request.xhr? - } - format.atom { - title = l(:label_activity) - if @author - title = @author.name - elsif @activity.scope.size == 1 - title = l("label_#{@activity.scope.first.singularize}_plural") - end - render_feed(events, :title => "#{@project || Setting.app_title}: #{title}") - } - end - end - - rescue ActiveRecord::RecordNotFound - render_404 - end - private def find_optional_project return true unless params[:id] @@ -364,14 +254,6 @@ private render_404 end - def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil) - if ids = params[:tracker_ids] - @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s } - else - @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s } - end - end - # Validates parent_id param according to user's permissions # TODO: move it to Project model in a validation that depends on User.current def validate_parent_id diff --git a/app/controllers/timelog_controller.rb b/app/controllers/timelog_controller.rb index e234848d0..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.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(@project) || Date.today) + @to ||= (TimeEntry.latest_date_for_project(@project) || Date.today) end def load_available_criterias 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/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 46b4778d4..48612c7b8 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -18,13 +18,37 @@ class VersionsController < ApplicationController menu_item :roadmap model_object Version - before_filter :find_model_object, :except => [:new, :close_completed] - before_filter :find_project_from_association, :except => [:new, :close_completed] - before_filter :find_project, :only => [: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 helper :projects + + def index + @trackers = @project.trackers.find(:all, :order => 'position') + retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?}) + @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1') + project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id] + + @versions = @project.shared_versions || [] + @versions += @project.rolled_up_versions.visible if @with_subprojects + @versions = @versions.uniq.sort + @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed] + + @issues_by_version = {} + unless @selected_tracker_ids.empty? + @versions.each do |version| + issues = version.fixed_issues.visible.find(:all, + :include => [:project, :status, :tracker, :priority], + :conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids}, + :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id") + @issues_by_version[version] = issues + end + end + @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} + end def show @issues = @version.fixed_issues.visible.find(:all, @@ -39,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| @@ -55,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 @@ -63,9 +98,12 @@ class VersionsController < ApplicationController end end end - + def edit - if request.post? && params[:version] + end + + def update + if request.put? && params[:version] attributes = params[:version].dup attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing']) if @version.update_attributes(attributes) @@ -76,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 @@ -105,4 +143,13 @@ private rescue ActiveRecord::RecordNotFound render_404 end + + def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil) + if ids = params[:tracker_ids] + @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s } + else + @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s } + end + end + end 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/helpers/application_helper.rb b/app/helpers/application_helper.rb index 19dd654db..6ba40eb45 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 @@ -102,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: @@ -302,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 @@ -805,7 +829,7 @@ module ApplicationHelper # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe ') def avatar(user, options = { }) if Setting.gravatar_enabled? - options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default}) + options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default}) email = nil if user.respond_to?(:mail) email = user.mail @@ -813,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/calendars_helper.rb b/app/helpers/calendars_helper.rb new file mode 100644 index 000000000..08e665dcd --- /dev/null +++ b/app/helpers/calendars_helper.rb @@ -0,0 +1,45 @@ +module CalendarsHelper + def link_to_previous_month(year, month, options={}) + target_year, target_month = if month == 1 + [year - 1, 12] + else + [year, month - 1] + end + + name = if target_month == 12 + "#{month_name(target_month)} #{target_year}" + else + "#{month_name(target_month)}" + end + + link_to_month(('« ' + name), target_year, target_month, options) + end + + def link_to_next_month(year, month, options={}) + target_year, target_month = if month == 12 + [year + 1, 1] + else + [year, month + 1] + end + + name = if target_month == 1 + "#{month_name(target_month)} #{target_year}" + else + "#{month_name(target_month)}" + end + + link_to_month((name + ' »'), target_year, target_month, options) + end + + def link_to_month(link_name, year, month, options={}) + project_id = options[:project].present? ? options[:project].to_param : nil + + link_target = calendar_path(:year => year, :month => month, :project_id => project_id) + + link_to_remote(link_name, + {:update => "content", :url => link_target, :method => :put}, + {:href => link_target}) + + 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..f1ddcfcbf 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)}
" + @@ -243,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) + @@ -253,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/models/issue.rb b/app/models/issue.rb index 7d0682df1..c49952306 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,18 @@ 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 + + # Does this issue have children? + def children? + !leaf? + end # Users the issue can be assigned to def assignable_users @@ -821,7 +851,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} 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/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 diff --git a/app/models/project.rb b/app/models/project.rb index 931f89b55..4b0236b37 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -412,6 +412,58 @@ class Project < ActiveRecord::Base def short_description(length = 255) 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) + [ + 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: @@ -441,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/query.rb b/app/models/query.rb index b1f784528..59131afcd 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -195,6 +195,12 @@ 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? + + 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"]] } @@ -432,6 +438,47 @@ 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 + operator = '=' # Override the operator since we want to find by assigned_to + elsif operator == "!*" + groups = Group.all + operator = '!' # Override the operator since we want to find by assigned_to + else + groups = Group.find_all_by_id(v) + 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) + ')' + + 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/app/models/time_entry.rb b/app/models/time_entry.rb index 73f39f949..9bf970891 100644 --- a/app/models/time_entry.rb +++ b/app/models/time_entry.rb @@ -81,4 +81,20 @@ class TimeEntry < ActiveRecord::Base yield end end + + 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(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/app/models/version.rb b/app/models/version.rb index 07e66434d..95e6ad5f6 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. @@ -123,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/app/views/projects/activity.rhtml b/app/views/activities/index.html.erb similarity index 100% rename from app/views/projects/activity.rhtml rename to app/views/activities/index.html.erb diff --git a/app/views/admin/projects.rhtml b/app/views/admin/projects.rhtml index 46b68e4cc..47a2d0583 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)%>

@@ -26,7 +26,7 @@ <% 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 %>"> <%= link_to_project(project, :action => 'settings') %> <%= textilizable project.short_description, :project => project %> <%= checked_image project.is_public? %> @@ -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/boards/index.rhtml b/app/views/boards/index.rhtml index 7cc6a0e2f..6310f942e 100644 --- a/app/views/boards/index.rhtml +++ b/app/views/boards/index.rhtml @@ -30,11 +30,11 @@ <% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => @project, :show_messages => 1, :key => User.current.rss_key} %> + <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_messages => 1, :key => User.current.rss_key} %> <% end %> <% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, {:controller => 'projects', :action => 'activity', :id => @project, :format => 'atom', :show_messages => 1, :key => User.current.rss_key}) %> + <%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :id => @project, :format => 'atom', :show_messages => 1, :key => User.current.rss_key}) %> <% end %> <% html_title l(:label_board_plural) %> diff --git a/app/views/calendars/show.html.erb b/app/views/calendars/show.html.erb index 4541cc0c8..530416661 100644 --- a/app/views/calendars/show.html.erb +++ b/app/views/calendars/show.html.erb @@ -1,6 +1,7 @@

<%= l(:label_calendar) %>

-<% form_tag({}, :id => 'query_form') do %> +<% form_tag(calendar_path, :method => :put, :id => 'query_form') do %> + <%= hidden_field_tag('project_id', @project.to_param) if @project%>
<%= l(:label_filter_plural) %>
@@ -9,14 +10,7 @@

-<%= link_to_remote ('« ' + (@month==1 ? "#{month_name(12)} #{@year-1}" : "#{month_name(@month-1)}")), - {:update => "content", :url => { :year => (@month==1 ? @year-1 : @year), :month =>(@month==1 ? 12 : @month-1) }}, - {:href => url_for(:action => 'show', :year => (@month==1 ? @year-1 : @year), :month =>(@month==1 ? 12 : @month-1))} - %> | -<%= link_to_remote ((@month==12 ? "#{month_name(1)} #{@year+1}" : "#{month_name(@month+1)}") + ' »'), - {:update => "content", :url => { :year => (@month==12 ? @year+1 : @year), :month =>(@month==12 ? 1 : @month+1) }}, - {:href => url_for(:action => 'show', :year => (@month==12 ? @year+1 : @year), :month =>(@month==12 ? 1 : @month+1))} - %> + <%= link_to_previous_month(@year, @month, :project => @project) %> | <%= link_to_next_month(@year, @month, :project => @project) %>

diff --git a/app/views/context_menus/issues.html.erb b/app/views/context_menus/issues.html.erb index dc11b5fdb..94d4e802f 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) %> - -
  • <% 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) %> + +
  • + <% end %> + <% unless @trackers.nil? %>
  • <%= l(:field_tracker) %> @@ -29,6 +32,8 @@
  • <% end %> + + <% if @projects.size == 1 %>
  • <%= l(:field_priority) %>
  • + <% end %> + <% unless @project.nil? || @project.shared_versions.open.empty? -%>
  • <%= l(:field_fixed_version) %> @@ -77,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) %>
  • <% end %> + <% if !@issue.nil? %> <% if @can[:log_time] -%>
  • <%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, diff --git a/app/views/projects/list_files.rhtml b/app/views/files/index.html.erb similarity index 91% rename from app/views/projects/list_files.rhtml rename to app/views/files/index.html.erb index 2b2e5e870..20710402b 100644 --- a/app/views/projects/list_files.rhtml +++ 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), new_project_file_path(@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..d9d1b6ee1 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(project_files_path(@project), :multipart => true, :class => "tabular") do %> <% if @versions.any? %>

    diff --git a/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb index 653a41d41..5a64054af 100644 --- a/app/views/gantts/show.html.erb +++ b/app/views/gantts/show.html.erb @@ -1,6 +1,8 @@ +<% @gantt.view = self %>

    <%= l(:label_gantt) %>

    -<% form_tag(params.merge(:month => nil, :year => nil, :months => nil), :id => 'query_form') do %> +<% form_tag(gantt_path(:month => params[:month], :year => params[:year], :months => params[:months]), :method => :put, :id => 'query_form') do %> + <%= hidden_field_tag('project_id', @project.to_param) if @project%>
    <%= l(:label_filter_plural) %>
    @@ -54,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 %> - @@ -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/projects/show.rhtml b/app/views/projects/show.rhtml index 62ea27848..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 %>
    @@ -74,7 +74,7 @@ <% end %> <% content_for :header_tags do %> -<%= auto_discovery_link_tag(:atom, {:action => 'activity', :id => @project, :format => 'atom', :key => User.current.rss_key}) %> +<%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :id => @project, :format => 'atom', :key => User.current.rss_key}) %> <% end %> <% html_title(l(:label_overview)) -%> 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); //]]> 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] %>

    diff --git a/app/views/users/show.rhtml b/app/views/users/show.rhtml index df5aec825..a2f90226c 100644 --- a/app/views/users/show.rhtml +++ b/app/views/users/show.rhtml @@ -35,7 +35,7 @@

    <% unless @events_by_day.empty? %> -

    <%= link_to l(:label_activity), :controller => 'projects', :action => 'activity', :id => nil, :user_id => @user, :from => @events_by_day.keys.first %>

    +

    <%= link_to l(:label_activity), :controller => 'activities', :action => 'index', :id => nil, :user_id => @user, :from => @events_by_day.keys.first %>

    <%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %> @@ -57,11 +57,11 @@

    <% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => nil, :user_id => @user, :key => User.current.rss_key} %> + <%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => nil, :user_id => @user, :key => User.current.rss_key} %> <% end %> <% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :user_id => @user, :format => :atom, :key => User.current.rss_key) %> + <%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :user_id => @user, :format => :atom, :key => User.current.rss_key) %> <% end %> <% end %> <%= call_hook :view_account_right_bottom, :user => @user %> 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/app/views/versions/edit.rhtml b/app/views/versions/edit.rhtml index 1556ebba1..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 => 'edit' } 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/projects/roadmap.rhtml b/app/views/versions/index.html.erb similarity index 96% rename from app/views/projects/roadmap.rhtml rename to app/views/versions/index.html.erb index 100282a7a..d0c5dcac1 100644 --- a/app/views/projects/roadmap.rhtml +++ 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/app/views/versions/new.html.erb b/app/views/versions/new.html.erb index a7ef03e76..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 => 'new' } 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 %> \ No newline at end of file +<% end %> diff --git a/app/views/versions/show.rhtml b/app/views/versions/show.rhtml index 79de5984b..6b2b09d0f 100644 --- a/app/views/versions/show.rhtml +++ b/app/views/versions/show.rhtml @@ -1,5 +1,6 @@
    <%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => @version}, :class => 'icon icon-edit' %> +<%= link_to_if_authorized(l(:button_edit_associated_wikipage, :page_title => @version.wiki_page_title), {:controller => 'wiki', :action => 'edit', :page => Wiki.titleize(@version.wiki_page_title)}, :class => 'icon icon-edit') unless @version.wiki_page_title.blank? || @project.wiki.nil? %> <%= call_hook(:view_versions_show_contextual, { :version => @version, :project => @project }) %>
    diff --git a/app/views/welcome/index.rhtml b/app/views/welcome/index.rhtml index 6ac09c153..982d6da52 100644 --- a/app/views/welcome/index.rhtml +++ b/app/views/welcome/index.rhtml @@ -34,6 +34,6 @@ <% content_for :header_tags do %> <%= auto_discovery_link_tag(:atom, {:controller => 'news', :action => 'index', :key => User.current.rss_key, :format => 'atom'}, :title => "#{Setting.app_title}: #{l(:label_news_latest)}") %> -<%= auto_discovery_link_tag(:atom, {:controller => 'projects', :action => 'activity', :key => User.current.rss_key, :format => 'atom'}, +<%= auto_discovery_link_tag(:atom, {:controller => 'activities', :action => 'index', :key => User.current.rss_key, :format => 'atom'}, :title => "#{Setting.app_title}: #{l(:label_activity)}") %> <% end %> diff --git a/app/views/wiki/special_date_index.rhtml b/app/views/wiki/special_date_index.rhtml index 228737a5e..b34fb8464 100644 --- a/app/views/wiki/special_date_index.rhtml +++ b/app/views/wiki/special_date_index.rhtml @@ -23,11 +23,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/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/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 diff --git a/config/locales/bg.yml b/config/locales/bg.yml index d4fd50c93..e99da7062 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: Изберете @@ -906,3 +907,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/bs.yml b/config/locales/bs.yml index 6d729b0ce..bef17900e 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 @@ -926,3 +927,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 80d857e9e..38092a2a7 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,9 @@ 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 + + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/cs.yml b/config/locales/cs.yml index f1abeff5b..d3cdae5ef 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 @@ -912,3 +913,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/da.yml b/config/locales/da.yml index 45b33d810..25d288336 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: @@ -928,3 +929,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/de.yml b/config/locales/de.yml index e4d1de93d..f9847c3f5 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -68,10 +68,11 @@ de: other: "fast {{count}} Jahren" number: + # Default format for numbers format: - precision: 2 separator: ',' delimiter: '.' + precision: 2 currency: format: unit: '€' @@ -214,6 +215,7 @@ de: mail_body_wiki_content_added: "Die Wiki-Seite '{{page}}' wurde von {{author}} hinzugefügt." mail_subject_wiki_content_updated: "Wiki-Seite '{{page}}' erfolgreich aktualisiert" mail_body_wiki_content_updated: "Die Wiki-Seite '{{page}}' wurde von {{author}} aktualisiert." + gui_validation_error: 1 Fehler gui_validation_error_plural: "{{count}} Fehler" @@ -252,7 +254,7 @@ de: field_priority: Priorität field_fixed_version: Zielversion field_user: Benutzer - field_principal: Principal + field_principal: Auftraggeber field_role: Rolle field_homepage: Projekt-Homepage field_is_public: Öffentlich @@ -297,6 +299,7 @@ de: field_redirect_existing_links: Existierende Links umleiten field_estimated_hours: Geschätzter Aufwand field_column_names: Spalten + field_time_entries: Logzeit field_time_zone: Zeitzone field_searchable: Durchsuchbar field_default_value: Standardwert @@ -428,6 +431,8 @@ de: project_module_wiki: Wiki project_module_repository: Projektarchiv project_module_boards: Foren + project_module_calendar: Kalender + project_module_gantt: Gantt label_user: Benutzer label_user_plural: Benutzer @@ -485,7 +490,7 @@ de: label_my_page: Meine Seite label_my_account: Mein Konto label_my_projects: Meine Projekte - label_my_page_block: My page block + label_my_page_block: Bereich "Meine Seite" label_administration: Administration label_login: Anmelden label_logout: Abmelden @@ -499,7 +504,7 @@ de: label_user_activity: "Aktivität von {{value}}" label_new: Neu label_logged_as: Angemeldet als - label_environment: Environment + label_environment: Umgebung label_authentication: Authentifizierung label_auth_source: Authentifizierungs-Modus label_auth_source_new: Neuer Authentifizierungs-Modus @@ -798,6 +803,7 @@ de: button_create_and_continue: Anlegen + nächstes Ticket button_test: Testen button_edit: Bearbeiten + button_edit_associated_wikipage: "Zugehörige Wikiseite bearbeiten: {{page_title}}" button_add: Hinzufügen button_change: Wechseln button_apply: Anwenden @@ -831,7 +837,7 @@ de: button_quote: Zitieren button_duplicate: Duplizieren button_show: Anzeigen - + status_active: aktiv status_registered: angemeldet status_locked: gesperrt @@ -927,6 +933,6 @@ de: enumeration_activities: Aktivitäten (Zeiterfassung) enumeration_system_activity: System-Aktivität - 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 + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/el.yml b/config/locales/el.yml index d9e224e9f..2b6e3493d 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: Παρακαλώ επιλέξτε @@ -912,3 +913,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index bc4cb08e8..4f5aec111 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 @@ -916,3 +917,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/en.yml b/config/locales/en.yml index 081b97e7c..716a067ee 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -293,6 +293,8 @@ en: field_group_by: Group results by 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 @@ -787,6 +789,7 @@ en: button_create_and_continue: Create and continue button_test: Test button_edit: Edit + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" button_add: Add button_change: Change button_apply: Apply @@ -838,6 +841,7 @@ en: text_subprojects_destroy_warning: "Its subproject(s): {{value}} will be also deleted." text_workflow_edit: Select a role and a tracker to edit the workflow text_are_you_sure: Are you sure ? + text_are_you_sure_with_children: "Delete issue and all child issues?" text_journal_changed: "{{label}} changed from {{old}} to {{new}}" text_journal_set_to: "{{label}} set to {{value}}" text_journal_deleted: "{{label}} deleted ({{old}})" diff --git a/config/locales/es.yml b/config/locales/es.yml index 4783db721..18d56ae3e 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. @@ -952,3 +953,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 80fcc63d0..983fe1482 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 @@ -916,3 +917,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 3bde5975b..ea14a5c0d 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ä @@ -938,3 +938,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 24b70ad21..0654ee736 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -931,3 +931,7 @@ 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 + button_edit_associated_wikipage: "Modifier la page de Wiki associée: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 7daebd074..5360095f6 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 @@ -928,3 +929,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/he.yml b/config/locales/he.yml index 59fa6888d..0129eff88 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: בחר בבקשה @@ -917,3 +918,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/hr.yml b/config/locales/hr.yml index 1c6d523c3..9219ac660 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 @@ -919,3 +920,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 974ddf76c..e099cb9bc 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 @@ -935,3 +936,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/id.yml b/config/locales/id.yml index 345fba454..185d4f21d 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 @@ -920,3 +921,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/it.yml b/config/locales/it.yml index f5fb21fd2..be0da6a48 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 @@ -916,3 +917,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 75d29df28..2ea6b009f 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: "指定したチケットとは親子関係になっているため関連づけられません" actionview_instancetag_blank_option: 選んでください @@ -305,6 +306,7 @@ ja: field_redirect_existing_links: 既存のリンクをリダイレクトする field_estimated_hours: 予定工数 field_column_names: 項目 + field_time_entries: 時間を記録 field_time_zone: タイムゾーン field_searchable: 検索条件に設定可能とする field_default_value: デフォルト値 @@ -317,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: アプリケーションのサブタイトル @@ -436,6 +440,8 @@ ja: project_module_wiki: Wiki project_module_repository: リポジトリ project_module_boards: フォーラム + project_module_gantt: ガントチャート + project_module_calendar: カレンダー label_user: ユーザ label_user_plural: ユーザ @@ -934,6 +940,5 @@ ja: enumeration_doc_categories: 文書カテゴリ enumeration_activities: 作業分類 (時間トラッキング) enumeration_system_activity: システム作業分類 - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/ko.yml b/config/locales/ko.yml index b30d7518f..e4dc36fb6 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: @@ -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: 선택하세요 @@ -958,13 +959,17 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/lt.yml b/config/locales/lt.yml index f0c9551a3..40e58b980 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 @@ -976,3 +977,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 4b2421fe4..6162f60ef 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 @@ -907,3 +908,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/mk.yml b/config/locales/mk.yml new file mode 100644 index 000000000..fb2f78d29 --- /dev/null +++ b/config/locales/mk.yml @@ -0,0 +1,919 @@ +mk: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d/%m/%Y" + short: "%d %b" + long: "%d %B, %Y" + + day_names: [недела, понеделник, вторник, среда, четврток, петок, сабота] + abbr_day_names: [нед, пон, вто, сре, чет, пет, саб] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, јануари, февруари, март, април, мај, јуни, јули, август, септември, октомври, ноември, декември] + abbr_month_names: [~, јан, фев, мар, апр, мај, јун, јул, авг, сеп, окт, ное, дек] + # Used in date_select and datime_select. + order: [ :day, :month, :year ] + + time: + formats: + default: "%d/%m/%Y %H:%M" + time: "%H:%M" + short: "%d %b %H:%M" + long: "%d %B, %Y %H:%M" + am: "предпладне" + pm: "попладне" + + datetime: + distance_in_words: + half_a_minute: "пола минута" + less_than_x_seconds: + one: "помалку од 1 секунда" + other: "помалку од {{count}} секунди" + x_seconds: + one: "1 секунда" + other: "{{count}} секунди" + less_than_x_minutes: + one: "помалку од 1 минута" + other: "помалку од {{count}} минути" + x_minutes: + one: "1 минута" + other: "{{count}} минути" + about_x_hours: + one: "околу 1 час" + other: "околу {{count}} часа" + x_days: + one: "1 ден" + other: "{{count}} дена" + about_x_months: + one: "околу 1 месец" + other: "околу {{count}} месеци" + x_months: + one: "1 месец" + other: "{{count}} месеци" + about_x_years: + one: "околу 1 година" + other: "околу {{count}} години" + over_x_years: + one: "преку 1 година" + other: "преку {{count}} години" + almost_x_years: + one: "скоро 1 година" + other: "скоро {{count}} години" + + number: + # Default format for numbers + format: + separator: "." + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 1 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + + +# Used in array.to_sentence. + support: + array: + sentence_connector: "и" + skip_last_comma: false + + activerecord: + errors: + messages: + inclusion: "не е вклучено во листата" + exclusion: "е резервирано" + invalid: "е невалидно" + confirmation: "не се совпаѓа со потврдата" + accepted: "мора да е прифатено" + empty: "неможе да е празно" + blank: "неможе да е празно" + too_long: "е предолго (макс. {{count}} знаци)" + too_short: "е прекратко (мин. {{count}} знаци)" + wrong_length: "е погрешна должина (треба да е {{count}} знаци)" + taken: "е веќе зафатено" + not_a_number: "не е број" + not_a_date: "не е валидна дата" + greater_than: "мора да е поголемо од {{count}}" + greater_than_or_equal_to: "мора да е поголемо или еднакво на {{count}}" + equal_to: "мора да е еднакво на {{count}}" + less_than: "мора да е помало од {{count}}" + less_than_or_equal_to: "мора да е помало или еднакво на {{count}}" + odd: "мора да е непарно" + even: "мора да е парно" + greater_than_start_date: "мора да е поголема од почетната дата" + not_same_project: "не припаѓа на истиот проект" + circular_dependency: "Оваа врска ќе креира кружна зависност" + cant_link_an_issue_with_a_descendant: "Задача неможе да се поврзе со една од нејзините подзадачи" + + actionview_instancetag_blank_option: Изберете + + general_text_No: 'Не' + general_text_Yes: 'Да' + general_text_no: 'не' + general_text_yes: 'да' + general_lang_name: 'Macedonian (Македонски)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '1' + + notice_account_updated: Профилот е успешно ажуриран. + notice_account_invalid_creditentials: Неточен корисник или лозинка + notice_account_password_updated: Лозинката е успешно ажурирана. + notice_account_wrong_password: Погрешна лозинка + notice_account_register_done: Профилот е успешно креиран. За активација, клкнете на врската што ви е пратена по е-пошта. + notice_account_unknown_email: Непознат корисник. + notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. + notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. + notice_account_activated: Your account has been activated. You can now log in. + notice_successful_create: Успешно креирање. + notice_successful_update: Успешно ажурирање. + notice_successful_delete: Успешно бришење. + notice_successful_connection: Успешна конекција. + notice_file_not_found: The page you were trying to access doesn't exist or has been removed. + notice_locking_conflict: Data has been updated by another user. + notice_not_authorized: You are not authorized to access this page. + notice_email_sent: "Е-порака е пратена на {{value}}" + notice_email_error: "Се случи грешка при праќање на е-пораката ({{value}})" + notice_feeds_access_key_reseted: Вашиот RSS клуч за пристап е reset. + notice_api_access_key_reseted: Вашиот API клуч за пристап е reset. + notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}." + notice_failed_to_save_members: "Failed to save member(s): {{errors}}." + notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." + notice_account_pending: "Your account was created and is now pending administrator approval." + notice_default_data_loaded: Default configuration successfully loaded. + notice_unable_delete_version: Unable to delete version. + notice_unable_delete_time_entry: Unable to delete time log entry. + notice_issue_done_ratios_updated: Issue done ratios updated. + + error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}" + error_scm_not_found: "The entry or revision was not found in the repository." + error_scm_command_failed: "An error occurred when trying to access the repository: {{value}}" + error_scm_annotate: "The entry does not exist or can not be annotated." + error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' + 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").' + error_can_not_delete_custom_field: Unable to delete custom field + error_can_not_delete_tracker: "This tracker contains issues and can't be deleted." + error_can_not_remove_role: "This role is in use and can not be deleted." + error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' + error_can_not_archive_project: This project can not be archived + error_issue_done_ratios_not_updated: "Issue done ratios not updated." + error_workflow_copy_source: 'Please select a source tracker or role' + error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + error_unable_delete_issue_status: 'Unable to delete issue status' + error_unable_to_connect: "Unable to connect ({{value}})" + warning_attachments_not_saved: "{{count}} file(s) could not be saved." + + mail_subject_lost_password: "Вашата {{value}} лозинка" + mail_body_lost_password: 'To change your password, click on the following link:' + mail_subject_register: "Your {{value}} account activation" + mail_body_register: 'To activate your account, click on the following link:' + mail_body_account_information_external: "You can use your {{value}} account to log in." + mail_body_account_information: Your account information + mail_subject_account_activation_request: "{{value}} account activation request" + mail_body_account_activation_request: "Нов корисник ({{value}}) е регистриран. The account is pending your approval:" + mail_subject_reminder: "{{count}} issue(s) due in the next {{days}} days" + mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:" + 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}}." + mail_subject_wiki_content_updated: "'{{page}}' wiki page has been updated" + mail_body_wiki_content_updated: "The '{{page}}' wiki page has been updated by {{author}}." + + gui_validation_error: 1 грешка + gui_validation_error_plural: "{{count}} грешки" + + field_name: Име + field_description: Опис + field_summary: Краток опис + field_is_required: Задолжително + field_firstname: Име + field_lastname: Презиме + field_mail: Е-пошта + field_filename: Датотека + field_filesize: Големина + field_downloads: Превземања + field_author: Автор + field_created_on: Креиран + field_updated_on: Ажурирано + field_field_format: Формат + field_is_for_all: За сите проекти + field_possible_values: Можни вредности + field_regexp: Regular expression + field_min_length: Минимална должина + field_max_length: Максимална должина + field_value: Вредност + field_category: Категорија + field_title: Наслов + field_project: Проект + field_issue: Задача + field_status: Статус + field_notes: Белешки + field_is_closed: Задачата е затворена + field_is_default: Default value + field_tracker: Tracker + field_subject: Наслов + field_due_date: Краен рок + field_assigned_to: Доделена на + field_priority: Приоритет + field_fixed_version: Target version + field_user: Корисник + field_principal: Principal + field_role: Улога + field_homepage: Веб страна + field_is_public: Јавен + field_parent: Подпроект на + field_is_in_roadmap: Issues displayed in roadmap + field_login: Корисник + field_mail_notification: Известувања по e-пошта + field_admin: Администратор + field_last_login_on: Последна најава + field_language: Јазик + field_effective_date: Дата + field_password: Лозинка + field_new_password: Нова лозинка + field_password_confirmation: Потврда + field_version: Верзија + field_type: Тип + field_host: Хост + field_port: Порт + field_account: Account + field_base_dn: Base DN + field_attr_login: Login attribute + field_attr_firstname: Firstname attribute + field_attr_lastname: Lastname attribute + field_attr_mail: Email attribute + field_onthefly: Моментално (On-the-fly) креирање на корисници + field_start_date: Почеток + field_done_ratio: % Завршено + field_auth_source: Режим на автентикација + field_hide_mail: Криј ја мојата адреса на е-пошта + field_comments: Коментар + field_url: URL + field_start_page: Почетна страна + field_subproject: Подпроект + field_hours: Часови + field_activity: Активност + field_spent_on: Дата + field_identifier: Идентификатор + field_is_filter: Користи како филтер + field_issue_to: Поврзана задача + field_delay: Доцнење + field_assignable: На оваа улога може да се доделуваат задачи + field_redirect_existing_links: Пренасочи ги постоечките врски + field_estimated_hours: Проценето време + field_column_names: Колони + field_time_entries: Бележи време + field_time_zone: Временска зона + field_searchable: Може да се пребарува + field_default_value: Default value + field_comments_sorting: Прикажувај коментари + field_parent_title: Parent page + field_editable: Може да се уредува + field_watcher: Watcher + field_identity_url: OpenID URL + field_content: Содржина + field_group_by: Групирај ги резултатите според + field_sharing: Споделување + field_parent_issue: Parent task + + setting_app_title: Наслов на апликацијата + setting_app_subtitle: Поднаслов на апликацијата + setting_welcome_text: Текст за добредојде + setting_default_language: Default јазик + setting_login_required: Задолжителна автентикација + setting_self_registration: Само-регистрација + setting_attachment_max_size: Макс. големина на прилог + setting_issues_export_limit: Issues export limit + setting_mail_from: Emission email address + setting_bcc_recipients: Blind carbon copy recipients (bcc) + setting_plain_text_mail: Текстуални е-пораки (без HTML) + setting_host_name: Име на хост и патека + setting_text_formatting: Форматирање на текст + setting_wiki_compression: Компресија на историјата на вики + setting_feeds_limit: Feed content limit + setting_default_projects_public: Новите проекти се иницијално јавни + setting_autofetch_changesets: Autofetch commits + setting_sys_api_enabled: Enable WS for repository management + setting_commit_ref_keywords: Referencing keywords + setting_commit_fix_keywords: Fixing keywords + setting_autologin: Автоматска најава + setting_date_format: Формат на дата + setting_time_format: Формат на време + setting_cross_project_issue_relations: Дозволи релации на задачи меѓу проекти + setting_issue_list_default_columns: Default columns displayed on the issue list + setting_repositories_encodings: Repositories encodings + setting_commit_logs_encoding: Commit messages encoding + setting_emails_footer: Emails footer + setting_protocol: Протокол + setting_per_page_options: Objects per page options + setting_user_format: Приказ на корисниците + setting_activity_days_default: Денови прикажана во активноста на проектот + setting_display_subprojects_issues: Прикажи ги задачите на подпроектите во главните проекти + setting_enabled_scm: Овозможи SCM + setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" + setting_mail_handler_api_enabled: Enable WS for incoming emails + setting_mail_handler_api_key: API клуч + setting_sequential_project_identifiers: Генерирај последователни идентификатори на проекти + setting_gravatar_enabled: Користи Gravatar кориснички икони + setting_gravatar_default: Default Gravatar image + setting_diff_max_lines_displayed: Max number of diff lines displayed + setting_file_max_size_displayed: Max size of text files displayed inline + setting_repository_log_display_limit: Maximum number of revisions displayed on file log + setting_openid: Дозволи OpenID најава и регистрација + setting_password_min_length: Мин. должина на лозинка + setting_new_project_user_role_id: Улога доделена на неадминистраторски корисник кој креира проект + setting_default_projects_modules: Default enabled modules for new projects + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_field: Use the issue field + setting_issue_done_ratio_issue_status: Use the issue status + setting_start_of_week: Start calendars on + setting_rest_api_enabled: Enable REST web service + setting_cache_formatted_text: Cache formatted text + + permission_add_project: Креирај проекти + permission_add_subprojects: Креирај подпроекти + permission_edit_project: Уреди проект + permission_select_project_modules: Изберете модули за проект + permission_manage_members: Manage members + permission_manage_project_activities: Manage project activities + permission_manage_versions: Manage versions + permission_manage_categories: Manage issue categories + permission_view_issues: Прегледај задачи + permission_add_issues: Додавај задачи + permission_edit_issues: Уредувај задачи + permission_manage_issue_relations: Manage issue relations + permission_add_issue_notes: Додавај белешки + permission_edit_issue_notes: Уредувај белешки + permission_edit_own_issue_notes: Уредувај сопствени белешки + permission_move_issues: Преместувај задачи + permission_delete_issues: Бриши задачи + permission_manage_public_queries: Manage public queries + permission_save_queries: Save queries + permission_view_gantt: View gantt chart + permission_view_calendar: View calendar + permission_view_issue_watchers: View watchers list + permission_add_issue_watchers: Add watchers + permission_delete_issue_watchers: Delete watchers + permission_log_time: Бележи потрошено време + permission_view_time_entries: Прегледај потрошено време + permission_edit_time_entries: Уредувај белешки за потрошено време + permission_edit_own_time_entries: Уредувај сопствени белешки за потрошено време + permission_manage_news: Manage news + permission_comment_news: Коментирај на вести + permission_manage_documents: Manage documents + permission_view_documents: Прегледувај документи + permission_manage_files: Manage files + permission_view_files: Прегледувај датотеки + permission_manage_wiki: Manage wiki + permission_rename_wiki_pages: Преименувај вики страници + permission_delete_wiki_pages: Бриши вики страници + permission_view_wiki_pages: Прегледувај вики + permission_view_wiki_edits: Прегледувај вики историја + permission_edit_wiki_pages: Уредувај вики страници + permission_delete_wiki_pages_attachments: Бриши прилози + permission_protect_wiki_pages: Заштитувај вики страници + permission_manage_repository: Manage repository + permission_browse_repository: Browse repository + permission_view_changesets: View changesets + permission_commit_access: Commit access + permission_manage_boards: Manage boards + permission_view_messages: View messages + permission_add_messages: Post messages + permission_edit_messages: Уредувај пораки + permission_edit_own_messages: Уредувај сопствени пораки + permission_delete_messages: Бриши пораки + permission_delete_own_messages: Бриши сопствени пораки + permission_export_wiki_pages: Export wiki pages + permission_manage_subtasks: Manage subtasks + + project_module_issue_tracking: Следење на задачи + project_module_time_tracking: Следење на време + project_module_news: Вести + project_module_documents: Документи + project_module_files: Датотеки + project_module_wiki: Вики + project_module_repository: Repository + project_module_boards: Форуми + project_module_calendar: Календар + project_module_gantt: Gantt + + label_user: Корисник + label_user_plural: Корисници + label_user_new: Нов корисник + label_user_anonymous: Анонимен + label_project: Проект + label_project_new: Нов проект + label_project_plural: Проекти + label_x_projects: + zero: нема проекти + one: 1 проект + other: "{{count}} проекти" + label_project_all: Сите проекти + label_project_latest: Последните проекти + label_issue: Задача + label_issue_new: Нова задача + label_issue_plural: Задачи + label_issue_view_all: Прегледај ги сите задачи + label_issues_by: "Задачи по {{value}}" + label_issue_added: Задачата е додадена + label_issue_updated: Задачата е ажурирана + label_document: Документ + label_document_new: Нов документ + label_document_plural: Документи + label_document_added: Документот е додаден + label_role: Улога + label_role_plural: Улоги + label_role_new: Нова улога + label_role_and_permissions: Улоги и овластувања + label_member: Член + label_member_new: Нов член + label_member_plural: Членови + label_tracker: Tracker + label_tracker_plural: Trackers + label_tracker_new: New tracker + label_workflow: Workflow + label_issue_status: Статус на задача + label_issue_status_plural: Статуси на задачи + label_issue_status_new: Нов статус + label_issue_category: Категорија на задача + label_issue_category_plural: Категории на задачи + label_issue_category_new: Нова категорија + label_custom_field: Прилагодено поле + label_custom_field_plural: Прилагодени полиња + label_custom_field_new: Ново прилагодено поле + label_enumerations: Enumerations + label_enumeration_new: Нова вредност + label_information: Информација + label_information_plural: Информации + label_please_login: Најави се + label_register: Регистрирај се + label_login_with_open_id_option: или најави се со OpenID + label_password_lost: Изгубена лозинка + label_home: Почетна + label_my_page: Мојата страна + label_my_account: Мојот профил + label_my_projects: Мои проекти + label_my_page_block: Блок елемент + label_administration: Администрација + label_login: Најави се + label_logout: Одјави се + label_help: Помош + label_reported_issues: Пријавени задачи + label_assigned_to_me_issues: Задачи доделени на мене + label_last_login: Последна најава + label_registered_on: Регистриран на + label_activity: Активност + label_overall_activity: Севкупна активност + label_user_activity: "Активност на {{value}}" + label_new: Нова + label_logged_as: Најавени сте како + label_environment: Опкружување + label_authentication: Автентикација + label_auth_source: Режим на автентикација + label_auth_source_new: Нов режим на автентикација + label_auth_source_plural: Режими на автентикација + label_subproject_plural: Подпроекти + label_subproject_new: Нов подпроект + label_and_its_subprojects: "{{value}} и неговите подпроекти" + label_min_max_length: Мин. - Макс. должина + label_list: Листа + label_date: Дата + label_integer: Integer + label_float: Float + label_boolean: Boolean + label_string: Текст + label_text: Долг текст + label_attribute: Атрибут + label_attribute_plural: Атрибути + label_download: "{{count}} превземање" + label_download_plural: "{{count}} превземања" + label_no_data: Нема податоци за прикажување + label_change_status: Промени статус + label_history: Историја + label_attachment: Датотека + label_attachment_new: Нова датотека + label_attachment_delete: Избриши датотека + label_attachment_plural: Датотеки + label_file_added: Датотеката е додадена + label_report: Извештај + label_report_plural: Извештаи + label_news: Новост + label_news_new: Додади новост + label_news_plural: Новости + label_news_latest: Последни новости + label_news_view_all: Прегледај ги сите новости + label_news_added: Новостта е додадена + label_settings: Settings + label_overview: Преглед + label_version: Верзија + label_version_new: Нова верзија + label_version_plural: Верзии + label_close_versions: Затвори ги завршените врзии + label_confirmation: Потврда + label_export_to: 'Достапно и во:' + label_read: Прочитај... + label_public_projects: Јавни проекти + label_open_issues: отворена + label_open_issues_plural: отворени + label_closed_issues: затворена + label_closed_issues_plural: затворени + label_x_open_issues_abbr_on_total: + zero: 0 отворени / {{total}} + one: 1 отворена / {{total}} + other: "{{count}} отворени / {{total}}" + label_x_open_issues_abbr: + zero: 0 отворени + one: 1 отворена + other: "{{count}} отворени" + label_x_closed_issues_abbr: + zero: 0 затворени + one: 1 затворена + other: "{{count}} затворени" + label_total: Вкупно + label_permissions: Овластувања + label_current_status: Моментален статус + label_new_statuses_allowed: Дозволени нови статуси + label_all: сите + label_none: ниеден + label_nobody: никој + label_next: Следно + label_previous: Претходно + label_used_by: Користено од + label_details: Детали + label_add_note: Додади белешка + label_per_page: По страна + label_calendar: Календар + label_months_from: месеци од + label_gantt: Gantt + label_internal: Internal + label_last_changes: "последни {{count}} промени" + label_change_view_all: Прегледај ги сите промени + label_personalize_page: Прилагоди ја странава + label_comment: Коментар + label_comment_plural: Коментари + label_x_comments: + zero: нема коментари + one: 1 коментар + other: "{{count}} коментари" + label_comment_add: Додади коментар + label_comment_added: Коментарот е додаден + label_comment_delete: Избриши коментари + label_query: Custom query + label_query_plural: Custom queries + label_query_new: New query + label_filter_add: Додади филтер + label_filter_plural: Филтри + label_equals: е + label_not_equals: не е + label_in_less_than: за помалку од + label_in_more_than: за повеќе од + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: во + label_today: денес + label_all_time: цело време + label_yesterday: вчера + label_this_week: оваа недела + label_last_week: минатата недела + label_last_n_days: "последните {{count}} дена" + label_this_month: овој месец + label_last_month: минатиот месец + label_this_year: оваа година + label_date_range: Date range + label_less_than_ago: пред помалку од денови + label_more_than_ago: пред повеќе од денови + label_ago: пред денови + label_contains: содржи + label_not_contains: не содржи + label_day_plural: денови + label_repository: Складиште + label_repository_plural: Складишта + label_browse: Прелистувај + label_modification: "{{count}} промени" + label_modification_plural: "{{count}} промени" + label_branch: Гранка + label_tag: Tag + label_revision: Ревизија + label_revision_plural: Ревизии + label_revision_id: "Ревизија {{value}}" + label_associated_revisions: Associated revisions + label_added: added + label_modified: modified + label_copied: copied + label_renamed: renamed + label_deleted: deleted + label_latest_revision: Последна ревизија + label_latest_revision_plural: Последни ревизии + label_view_revisions: Прегледај ги ревизиите + label_view_all_revisions: Прегледај ги сите ревизии + label_max_size: Макс. големина + label_sort_highest: Премести најгоре + label_sort_higher: Премести нагоре + label_sort_lower: Премести надоле + label_sort_lowest: Премести најдоле + label_roadmap: Roadmap + label_roadmap_due_in: "Due in {{value}}" + label_roadmap_overdue: "Касни {{value}}" + label_roadmap_no_issues: Нема задачи за оваа верзија + label_search: Барај + label_result_plural: Резултати + label_all_words: Сите зборови + label_wiki: Вики + label_wiki_edit: Вики уредување + label_wiki_edit_plural: Вики уредувања + label_wiki_page: Вики страница + label_wiki_page_plural: Вики страници + label_index_by_title: Индекс по наслов + label_index_by_date: Индекс по дата + label_current_version: Current version + label_preview: Preview + label_feed_plural: Feeds + label_changes_details: Детали за сите промени + label_issue_tracking: Следење на задачи + label_spent_time: Потрошено време + label_overall_spent_time: Вкупно потрошено време + label_f_hour: "{{value}} час" + label_f_hour_plural: "{{value}} часа" + label_time_tracking: Следење на време + label_change_plural: Промени + label_statistics: Статистики + label_commits_per_month: Commits per month + label_commits_per_author: Commits per author + label_view_diff: View differences + label_diff_inline: inline + label_diff_side_by_side: side by side + label_options: Опции + label_copy_workflow_from: Copy workflow from + label_permissions_report: Permissions report + label_watched_issues: Watched issues + label_related_issues: Поврзани задачи + label_applied_status: Applied status + label_loading: Loading... + label_relation_new: Нова релација + label_relation_delete: Избриши релација + label_relates_to: related to + label_duplicates: дупликати + label_duplicated_by: duplicated by + label_blocks: blocks + label_blocked_by: блокирано од + label_precedes: претходи + label_follows: следи + label_end_to_start: крај до почеток + label_end_to_end: крај до крај + label_start_to_start: почеток до почеток + label_start_to_end: почеток до крај + label_stay_logged_in: Останете најавени + label_disabled: disabled + label_show_completed_versions: Show completed versions + label_me: јас + label_board: Форум + label_board_new: Нов форум + label_board_plural: Форуми + label_board_locked: Заклучен + label_board_sticky: Sticky + label_topic_plural: Теми + label_message_plural: Пораки + label_message_last: Последна порака + label_message_new: Нова порака + label_message_posted: Поракате е додадена + label_reply_plural: Одговори + label_send_information: Испрати ги информациите за профилот на корисникот + label_year: Година + label_month: Месец + label_week: Недела + label_date_from: Од + label_date_to: До + label_language_based: Според јазикот на корисникот + label_sort_by: "Подреди според {{value}}" + label_send_test_email: Испрати тест е-порака + label_feeds_access_key: RSS клуч за пристап + label_missing_feeds_access_key: Недостика RSS клуч за пристап + label_feeds_access_key_created_on: "RSS клучот за пристап креиран пред {{value}}" + label_module_plural: Модули + label_added_time_by: "Додадено од {{author}} пред {{age}}" + label_updated_time_by: "Ажурирано од {{author}} пред {{age}}" + label_updated_time: "Ажурирано пред {{value}}" + label_jump_to_a_project: Префрли се на проект... + label_file_plural: Датотеки + label_changeset_plural: Changesets + label_default_columns: Основни колони + label_no_change_option: (Без промена) + label_bulk_edit_selected_issues: Групно уредување на задачи + label_theme: Тема + label_default: Default + label_search_titles_only: Пребарувај само наслови + label_user_mail_option_all: "За било кој настан во сите мои проекти" + label_user_mail_option_selected: "За било кој настан само во избраните проекти..." + label_user_mail_option_none: "Само за работите кои ги следам или од кои сум дел" + label_user_mail_no_self_notified: "Не ме известувај за промените што јас ги правам" + label_registration_activation_by_email: активација на профил преку е-пошта + label_registration_manual_activation: мануелна активација на профил + label_registration_automatic_activation: автоматска активација на профил + label_display_per_page: "По страна: {{value}}" + label_age: Age + label_change_properties: Change properties + label_general: Општо + label_more: Повеќе + label_scm: SCM + label_plugins: Додатоци + label_ldap_authentication: LDAP автентикација + label_downloads_abbr: Превземања + label_optional_description: Опис (незадолжително) + label_add_another_file: Додади уште една датотека + label_preferences: Preferences + label_chronological_order: Во хронолошки ред + label_reverse_chronological_order: In reverse chronological order + label_planning: Планирање + label_incoming_emails: Дојдовни е-пораки + label_generate_key: Генерирај клуч + label_issue_watchers: Watchers + label_example: Пример + label_display: Прикажи + label_sort: Подреди + label_ascending: Растечки + label_descending: Опаѓачки + label_date_from_to: Од {{start}} до {{end}} + label_wiki_content_added: Вики страница додадена + label_wiki_content_updated: Вики страница ажурирана + label_group: Група + label_group_plural: Групи + label_group_new: Нова група + label_time_entry_plural: Потрошено време + label_version_sharing_none: Не споделено + label_version_sharing_descendants: Со сите подпроекти + label_version_sharing_hierarchy: Со хиерархијата на проектот + label_version_sharing_tree: Со дрвото на проектот + label_version_sharing_system: Со сите проекти + label_update_issue_done_ratios: Update issue done ratios + label_copy_source: Извор + label_copy_target: Дестинација + label_copy_same_as_target: Исто како дестинацијата + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_api_access_key: API клуч за пристап + label_missing_api_access_key: Недостига API клуч за пристап + label_api_access_key_created_on: "API клучот за пристап е креиран пред {{value}}" + label_profile: Профил + label_subtask_plural: Подзадачи + label_project_copy_notifications: Праќај известувања по е-пошта при копирање на проект + + button_login: Најави се + button_submit: Испрати + button_save: Зачувај + button_check_all: Штиклирај ги сите + button_uncheck_all: Одштиклирај ги сите + button_delete: Избриши + button_create: Креирај + button_create_and_continue: Креирај и продолжи + button_test: Тест + button_edit: Уреди + button_add: Додади + button_change: Промени + button_apply: Примени + button_clear: Избриши + button_lock: Заклучи + button_unlock: Отклучи + button_download: Превземи + button_list: List + button_view: Прегледај + button_move: Премести + button_move_and_follow: Премести и следи + button_back: Back + button_cancel: Откажи + button_activate: Активирај + button_sort: Подреди + button_log_time: Бележи време + button_rollback: Rollback to this version + button_watch: Следи + button_unwatch: Не следи + button_reply: Одговори + button_archive: Архивирај + button_unarchive: Одархивирај + button_reset: Reset + button_rename: Преименувај + button_change_password: Промени лозинка + button_copy: Копирај + button_copy_and_follow: Копирај и следи + button_annotate: Annotate + button_update: Ажурирај + button_configure: Конфигурирај + button_quote: Цитирај + button_duplicate: Копирај + button_show: Show + + status_active: активни + status_registered: регистрирани + status_locked: заклучени + + version_status_open: отворени + version_status_locked: заклучени + version_status_closed: затворени + + field_active: Active + + text_select_mail_notifications: Изберете за кои настани да се праќаат известувања по е-пошта да се праќаат. + text_regexp_info: eg. ^[A-Z0-9]+$ + text_min_max_length_info: 0 значи без ограничување + text_project_destroy_confirmation: Дали сте сигурни дека сакате да го избришете проектот и сите поврзани податоци? + text_subprojects_destroy_warning: "Неговите подпроекти: {{value}} исто така ќе бидат избришани." + text_workflow_edit: Select a role and a tracker to edit the workflow + text_are_you_sure: Дали сте сигурни? + text_journal_changed: "{{label}} променето од {{old}} во {{new}}" + text_journal_set_to: "{{label}} set to {{value}}" + text_journal_deleted: "{{label}} избришан ({{old}})" + text_journal_added: "{{label}} {{value}} додаден" + text_tip_task_begin_day: задачи што почнуваат овој ден + text_tip_task_end_day: задачи што завршуваат овој ден + text_tip_task_begin_end_day: задачи што почнуваат и завршуваат овој ден + text_project_identifier_info: 'Само мали букви (a-z), бројки и dashes се дозволени
    По зачувувањето, идентификаторот неможе да се смени.' + text_caracters_maximum: "{{count}} знаци максимум." + text_caracters_minimum: "Мора да е најмалку {{count}} знаци долго." + text_length_between: "Должина помеѓу {{min}} и {{max}} знаци." + text_tracker_no_workflow: No workflow defined for this tracker + text_unallowed_characters: Недозволени знаци + text_comma_separated: Дозволени се повеќе вредности (разделени со запирка). + text_line_separated: Дозволени се повеќе вредности (една линија за секоја вредност). + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_issue_added: "Задачата {{id}} е пријавена од {{author}}." + text_issue_updated: "Зачата {{id}} е ажурирана од {{author}}." + text_wiki_destroy_confirmation: Дали сте сигурни дека сакате да го избришете ова вики и целата негова содржина? + text_issue_category_destroy_question: "Некои задачи ({{count}}) се доделени на оваа категорија. Што сакате да правите?" + text_issue_category_destroy_assignments: Remove category assignments + text_issue_category_reassign_to: Додели ги задачите на оваа категорија + text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." + text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." + text_load_default_configuration: Load the default configuration + text_status_changed_by_changeset: "Applied in changeset {{value}}." + text_issues_destroy_confirmation: 'Дали сте сигурни дека сакате да ги избришете избраните задачи?' + text_select_project_modules: 'Изберете модули за овој проект:' + text_default_administrator_account_changed: Default administrator account changed + text_file_repository_writable: Во папката за прилози може да се запишува + text_plugin_assets_writable: Во папката за додатоци може да се запишува + text_rmagick_available: RMagick available (незадолжително) + text_destroy_time_entries_question: "{{hours}} hours were reported on the issues you are about to delete. What do you want to do ?" + text_destroy_time_entries: Delete reported hours + text_assign_time_entries_to_project: Додели ги пријавените часови на проектот + text_reassign_time_entries: 'Reassign reported hours to this issue:' + text_user_wrote: "{{value}} напиша:" + text_enumeration_destroy_question: "{{count}} objects are assigned to this value." + text_enumeration_category_reassign_to: 'Reassign them to this value:' + text_email_delivery_not_configured: "Доставата по е-пошта не е конфигурирана, и известувањата се оневозможени.\nКонфигурирајте го Вашиот SMTP сервер во config/email.yml и рестартирајте ја апликацијата." + text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." + text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' + text_custom_field_possible_values_info: 'One line for each value' + text_wiki_page_destroy_question: "This page has {{descendants}} child page(s) and descendant(s). What do you want to do?" + 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_reassign_children: "Reassign child pages to this parent page" + 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.\nAre you sure you want to continue?" + text_zoom_in: Zoom in + text_zoom_out: Zoom out + + default_role_manager: Менаџер + default_role_developer: Developer + default_role_reporter: Reporter + default_tracker_bug: Грешка + default_tracker_feature: Функционалност + default_tracker_support: Поддршка + default_issue_status_new: Нова + default_issue_status_in_progress: Во прогрес + default_issue_status_resolved: Разрешена + default_issue_status_feedback: Feedback + default_issue_status_closed: Затворена + default_issue_status_rejected: Одбиена + default_doc_category_user: Корисничка документација + default_doc_category_tech: Техничка документација + default_priority_low: Низок + default_priority_normal: Нормален + default_priority_high: Висок + default_priority_urgent: Итно + default_priority_immediate: Веднаш + default_activity_design: Дизајн + default_activity_development: Развој + + enumeration_issue_priorities: Приоритети на задача + enumeration_doc_categories: Категории на документ + enumeration_activities: Активности (следење на време) + enumeration_system_activity: Системска активност + + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/mn.yml b/config/locales/mn.yml index 55d7cf168..5358657eb 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: Сонгоно уу @@ -913,3 +914,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 1e1489940..071b3919e 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 @@ -701,7 +702,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 +710,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 +728,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 +754,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 +779,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 +825,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 +834,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 +874,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 +892,10 @@ 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 + field_member_of_group: Member of Group + field_assigned_to_role: Member of Role + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/no.yml b/config/locales/no.yml index 8845d12b8..4b76683fc 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 @@ -903,3 +904,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/pl.yml b/config/locales/pl.yml index f36bb1277..b56fd71eb 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,13 +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: "Zagadnienie nie może zostać powiązane z jednym z własnych podzagadnień" support: array: @@ -159,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 @@ -538,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 @@ -589,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 @@ -601,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 @@ -683,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 @@ -711,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 @@ -720,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 @@ -742,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 @@ -764,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?' @@ -786,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) @@ -799,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 @@ -817,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) @@ -829,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" @@ -844,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ń). @@ -854,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 @@ -912,24 +913,28 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index d5b54e509..ee1aefac5 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: "Uma tarefa não pode ser relaciona a uma de suas subtarefas" actionview_instancetag_blank_option: Selecione @@ -936,3 +937,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/pt.yml b/config/locales/pt.yml index b2a2c08c8..cb336e25a 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 @@ -920,3 +921,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/ro.yml b/config/locales/ro.yml index fd6e2b942..2e8f7a1ca 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 @@ -905,3 +906,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 913c81c81..08e1acb95 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: @@ -229,6 +230,7 @@ ru: button_delete: Удалить button_download: Загрузить button_edit: Редактировать + button_edit_associated_wikipage: "Редактировать связанную wiki-страницу: {{page_title}}" button_list: Список button_lock: Заблокировать button_login: Вход @@ -293,6 +295,7 @@ ru: field_admin: Администратор field_assignable: Задача может быть назначена этой роли field_assigned_to: Назначена + field_assigned_to_role: Роль участника field_attr_firstname: Имя field_attr_lastname: Фамилия field_attr_login: Атрибут Регистрация @@ -342,6 +345,7 @@ ru: field_mail: Email field_mail_notification: Уведомления по email field_max_length: Максимальная длина + field_member_of_group: Группа участника field_min_length: Минимальная длина field_name: Имя field_new_password: Новый пароль @@ -1029,3 +1033,4 @@ ru: notice_unable_delete_time_entry: Невозможно удалить запись журнала. label_overall_spent_time: Всего затрачено времени + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 94facc047..37c5ea6b1 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 @@ -907,3 +908,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 7307d0d93..6732ef98d 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 @@ -908,3 +909,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/sr-YU.yml b/config/locales/sr-YU.yml index e5df0e427..d73fd0340 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,10 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 3641c7091..1b6a8d334 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,11 @@ 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 + + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/sv.yml b/config/locales/sv.yml index d6a9454fb..439f9c7cd 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: @@ -325,6 +326,7 @@ sv: field_redirect_existing_links: Omdirigera existerande länkar field_estimated_hours: Estimerad tid field_column_names: Kolumner + field_time_entries: Spenderad tid field_time_zone: Tidszon field_searchable: Sökbar field_default_value: Standardvärde @@ -456,6 +458,8 @@ sv: project_module_wiki: Wiki project_module_repository: Versionsarkiv project_module_boards: Forum + project_module_calendar: Kalender + project_module_gantt: Gantt label_user: Användare label_user_plural: Användare @@ -857,7 +861,7 @@ sv: button_update: Uppdatera button_configure: Konfigurera button_quote: Citera - button_duplicate: Duplisera + button_duplicate: Duplicera button_show: Visa status_active: aktiv @@ -954,6 +958,7 @@ sv: enumeration_doc_categories: Dokumentkategorier enumeration_activities: Aktiviteter (tidsuppföljning) enumeration_system_activity: Systemaktivitet - 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/th.yml b/config/locales/th.yml index 01d19c4d0..bc936f709 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: กรุณาเลือก @@ -909,3 +910,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/tr.yml b/config/locales/tr.yml index f3f9788b1..4b0e6022c 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 @@ -935,3 +936,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/uk.yml b/config/locales/uk.yml index c5846db2a..146454da4 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: Оберіть @@ -908,3 +909,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 5e964896d..8cf978d09 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: @@ -967,3 +968,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 7d40c2ece..38efcfa9d 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: "項目無法被連結至自己的子項目" # You can define own errors for models or model attributes. # The values :model, :attribute and :value are always available for interpolation. @@ -365,6 +366,7 @@ field_redirect_existing_links: 重新導向現有連結 field_estimated_hours: 預估工時 field_column_names: 欄位 + field_time_entries: 耗用工時 field_time_zone: 時區 field_searchable: 可用做搜尋條件 field_default_value: 預設值 @@ -377,8 +379,9 @@ field_group_by: 結果分組方式 field_sharing: 共用 field_parent_issue: 父工作項目 - field_time_entries: 耗用工時 - + field_member_of_group: 所屬群組 + field_assigned_to_role: 所屬角色 + setting_app_title: 標題 setting_app_subtitle: 副標題 setting_welcome_text: 歡迎詞 @@ -497,6 +500,8 @@ project_module_wiki: Wiki project_module_repository: 版本控管 project_module_boards: 討論區 + project_module_calendar: 日曆 + project_module_gantt: 甘特圖 label_user: 用戶 label_user_plural: 用戶清單 @@ -514,7 +519,7 @@ label_issue: 項目 label_issue_new: 建立新項目 label_issue_plural: 項目清單 - label_issue_view_all: 檢視全部的項目 + label_issue_view_all: 檢視所有項目 label_issues_by: "項目按 {{value}} 分組顯示" label_issue_added: 項目已新增 label_issue_updated: 項目已更新 @@ -564,7 +569,7 @@ label_last_login: 最近一次連線 label_registered_on: 註冊於 label_activity: 活動 - label_overall_activity: 檢視整體活動 + label_overall_activity: 整體活動 label_user_activity: "{{value}} 的活動" label_new: 建立新的... label_logged_as: 目前登入 @@ -867,6 +872,7 @@ button_create_and_continue: 繼續建立 button_test: 測試 button_edit: 編輯 + button_edit_associated_wikipage: "編輯相關 Wiki 頁面: {{page_title}}" button_add: 新增 button_change: 修改 button_apply: 套用 @@ -996,5 +1002,4 @@ enumeration_activities: 活動 (時間追蹤) enumeration_system_activity: 系統活動 - project_module_gantt: Gantt - project_module_calendar: Calendar + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 0ffdcdb3b..a7611bf1d 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: 请选择 @@ -930,3 +931,7 @@ 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 + button_edit_associated_wikipage: "Edit associated Wiki page: {{page_title}}" + text_are_you_sure_with_children: Delete issue and all child issues? diff --git a/config/routes.rb b/config/routes.rb index b480a4900..ceaaf6219 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -104,57 +104,39 @@ ActionController::Routing::Routes.draw do |map| end map.resources :issue_moves, :only => [:new, :create], :path_prefix => '/issues', :as => 'move' + + # Misc issue routes. TODO: move into resources map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues' - # TODO: would look nicer as /issues/:id/preview - map.preview_issue '/issues/preview/:id', :controller => 'previews', :action => 'issue' + map.preview_issue '/issues/preview/:id', :controller => 'previews', :action => 'issue' # TODO: would look nicer as /issues/:id/preview map.issues_context_menu '/issues/context_menu', :controller => 'context_menus', :action => 'issues' map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index' - - map.with_options :controller => 'issues' do |issues_routes| - issues_routes.with_options :conditions => {:method => :get} do |issues_views| - issues_views.connect 'issues', :action => 'index' - issues_views.connect 'issues.:format', :action => 'index' - issues_views.connect 'projects/:project_id/issues', :action => 'index' - issues_views.connect 'projects/:project_id/issues.:format', :action => 'index' - issues_views.connect 'projects/:project_id/issues/new', :action => 'new' - issues_views.connect 'projects/:project_id/issues/gantt', :controller => 'gantts', :action => 'show' - issues_views.connect 'projects/:project_id/issues/calendar', :controller => 'calendars', :action => 'show' - issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new' - issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/ - issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/ - issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/ - end - issues_routes.with_options :conditions => {:method => :post} do |issues_actions| - issues_actions.connect 'issues', :action => 'index' - issues_actions.connect 'projects/:project_id/issues', :action => 'create' - issues_actions.connect 'projects/:project_id/issues/gantt', :controller => 'gantts', :action => 'show' - issues_actions.connect 'projects/:project_id/issues/calendar', :controller => 'calendars', :action => 'show' - issues_actions.connect 'issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/ - issues_actions.connect 'issues/:id/:action', :action => /edit|destroy/, :id => /\d+/ - issues_actions.connect 'issues.:format', :action => 'create', :format => /xml/ - end - issues_routes.with_options :conditions => {:method => :put} do |issues_actions| - issues_actions.connect 'issues/:id/edit', :action => 'update', :id => /\d+/ - issues_actions.connect 'issues/:id.:format', :action => 'update', :id => /\d+/, :format => /xml/ - end - issues_routes.with_options :conditions => {:method => :delete} do |issues_actions| - issues_actions.connect 'issues/:id.:format', :action => 'destroy', :id => /\d+/, :format => /xml/ - end - issues_routes.connect 'issues/gantt', :controller => 'gantts', :action => 'show' - issues_routes.connect 'issues/calendar', :controller => 'calendars', :action => 'show' - issues_routes.connect 'issues/:action' + map.bulk_edit_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_edit', :conditions => { :method => :get } + map.bulk_update_issue 'issues/bulk_edit', :controller => 'issues', :action => 'bulk_update', :conditions => { :method => :post } + map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new', :id => /\d+/, :conditions => { :method => :post } + map.connect '/issues/:id/destroy', :controller => 'issues', :action => 'destroy', :conditions => { :method => :post } # legacy + + map.resource :gantt, :path_prefix => '/issues', :controller => 'gantts', :only => [:show, :update] + map.resource :gantt, :path_prefix => '/projects/:project_id/issues', :controller => 'gantts', :only => [:show, :update] + map.resource :calendar, :path_prefix => '/issues', :controller => 'calendars', :only => [:show, :update] + map.resource :calendar, :path_prefix => '/projects/:project_id/issues', :controller => 'calendars', :only => [:show, :update] + + map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports| + reports.connect 'projects/:id/issues/report', :action => 'issue_report' + reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details' end + # Following two routes conflict with the resources because #index allows POST + map.connect '/issues', :controller => 'issues', :action => 'index', :conditions => { :method => :post } + map.connect '/issues/create', :controller => 'issues', :action => 'index', :conditions => { :method => :post } + + map.resources :issues, :member => { :edit => :post }, :collection => {} + map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } + map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations| relations.connect 'issues/:issue_id/relations/:id', :action => 'new' relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy' end - map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports| - reports.connect 'projects/:id/issues/report', :action => 'issue_report' - reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details' - end - map.with_options :controller => 'news' do |news_routes| news_routes.with_options :conditions => {:method => :get} do |news_views| news_views.connect 'news', :action => 'index' @@ -166,7 +148,7 @@ ActionController::Routing::Routes.draw do |map| news_views.connect 'news/:id/edit', :action => 'edit' end news_routes.with_options do |news_actions| - news_actions.connect 'projects/:project_id/news', :action => 'new' + news_actions.connect 'projects/:project_id/news', :action => 'create', :conditions => {:method => :post} news_actions.connect 'news/:id/edit', :action => 'edit' news_actions.connect 'news/:id/destroy', :action => 'destroy' end @@ -190,53 +172,41 @@ 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 => 'add' - project_views.connect 'projects/:id', :action => 'show' - project_views.connect 'projects/:id.:format', :action => 'show' - project_views.connect 'projects/:id/:action', :action => /roadmap|destroy|settings/ - project_views.connect 'projects/:id/files', :action => 'list_files' - project_views.connect 'projects/:id/files/new', :action => 'add_file' - project_views.connect 'projects/:id/settings/:tab', :action => 'settings' - end - projects.with_options :action => 'activity', :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 + # 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, + :modules => :post, + :archive => :post, + :unarchive => :post + } 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}, :member => {:status_by => :post} + 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} + + # 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/settings/:tab', :controller => 'projects', :action => 'settings' + project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new' 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 + - 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/:id/:action', :action => /edit|destroy|archive|unarchive/ - project_actions.connect 'projects/:id/files/new', :action => 'add_file' - project_actions.connect 'projects/:id/activities/save', :action => 'save_activities' - end - - projects.with_options :conditions => {:method => :put} do |project_actions| - project_actions.conditions 'projects/:id.:format', :action => 'edit', :format => /xml/ - end - - 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' - end - end - - map.with_options :controller => 'versions' do |versions| - versions.connect 'projects/:project_id/versions/new', :action => 'new' - versions.with_options :conditions => {:method => :post} do |version_actions| - 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/lib/redmine.rb b/lib/redmine.rb index b0a5a9f09..946701a8d 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -44,29 +44,28 @@ end # Permissions Redmine::AccessControl.map do |map| - map.permission :view_project, {:projects => [:show, :activity]}, :public => true + 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 :edit_project, {:projects => [:settings, :edit]}, :require => :member + map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin + 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 :add_subprojects, {:projects => :add}, :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| # Issue categories map.permission :manage_categories, {:projects => :settings, :issue_categories => [:new, :edit, :destroy]}, :require => :member # Issues - map.permission :view_issues, {:projects => :roadmap, - :issues => [:index, :show], + map.permission :view_issues, {:issues => [:index, :show], :auto_complete => [:issues], :context_menus => [:issues], - :versions => [:show, :status_by], + :versions => [:index, :show, :status_by], :journals => :index, :queries => :index, :reports => [:issue_report, :issue_report_details]} map.permission :add_issues, {:issues => [:new, :create, :update_form]} - map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :update_form], :journals => [:new]} + map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update, :update_form], :journals => [:new]} map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]} map.permission :manage_subtasks, {} map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new]} @@ -88,11 +87,11 @@ 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, {:project_enumerations => [:update, :destroy]}, :require => :member end map.project_module :news do |map| - map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member + map.permission :manage_news, {:news => [:new, :create, :edit, :destroy, :destroy_comment]}, :require => :member map.permission :view_news, {:news => [:index, :show]}, :public => true map.permission :comment_news, {:news => :add_comment} end @@ -103,8 +102,8 @@ Redmine::AccessControl.map do |map| end map.project_module :files do |map| - map.permission :manage_files, {:projects => :add_file}, :require => :loggedin - map.permission :view_files, :projects => :list_files, :versions => :download + map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin + map.permission :view_files, :files => :index, :versions => :download end map.project_module :wiki do |map| @@ -137,11 +136,11 @@ Redmine::AccessControl.map do |map| end map.project_module :calendar do |map| - map.permission :view_calendar, :calendars => :show + map.permission :view_calendar, :calendars => [:show, :update] end map.project_module :gantt do |map| - map.permission :view_gantt, :gantts => :show + map.permission :view_gantt, :gantts => [:show, :update] end end @@ -185,8 +184,8 @@ end Redmine::MenuManager.map :project_menu do |menu| menu.push :overview, { :controller => 'projects', :action => 'show' } - menu.push :activity, { :controller => 'projects', :action => 'activity' } - menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' }, + menu.push :activity, { :controller => 'activities', :action => 'index' } + menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id, :if => Proc.new { |p| p.shared_versions.any? } menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new, @@ -199,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 => 'projects', :action => 'list_files' }, :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/lib/redmine/export/pdf.rb b/lib/redmine/export/pdf.rb index 9b1f2b8a2..c4a2c3d90 100644 --- a/lib/redmine/export/pdf.rb +++ b/lib/redmine/export/pdf.rb @@ -154,7 +154,7 @@ module Redmine if query.grouped? && (group = query.group_by_column.value(issue)) != previous_group pdf.SetFontStyle('B',9) pdf.Cell(277, row_height, - (group.blank? ? 'None' : group.to_s) + " (#{@issue_count_by_group[group]})", + (group.blank? ? 'None' : group.to_s) + " (#{query.issue_count_by_group[group]})", 1, 1, 'L') pdf.SetFontStyle('',8) previous_group = group @@ -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) @@ -255,7 +255,7 @@ module Redmine pdf.SetFontStyle('B',9) pdf.Cell(35,5, l(:field_description) + ":") pdf.SetFontStyle('',9) - pdf.MultiCell(155,5, @issue.description,"BR") + pdf.MultiCell(155,5, issue.description,"BR") pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY) pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY) @@ -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..cec323720 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 @@ -51,47 +68,668 @@ module Redmine @date_from = Date.civil(@year_from, @month_from, 1) @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 + + 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 + # 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.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.to_s_with_project}".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 +739,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 +748,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 +826,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 +840,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 a89791cf5..000000000 Binary files a/public/images/milestone.png and /dev/null differ diff --git a/public/images/milestone_done.png b/public/images/milestone_done.png new file mode 100644 index 000000000..5fdcb415c Binary files /dev/null and b/public/images/milestone_done.png differ diff --git a/public/images/milestone_late.png b/public/images/milestone_late.png new file mode 100644 index 000000000..cf922e954 Binary files /dev/null and b/public/images/milestone_late.png differ diff --git a/public/images/milestone_todo.png b/public/images/milestone_todo.png new file mode 100644 index 000000000..4c051c857 Binary files /dev/null and b/public/images/milestone_todo.png differ diff --git a/public/images/project_marker.png b/public/images/project_marker.png new file mode 100644 index 000000000..4124787d0 Binary files /dev/null and b/public/images/project_marker.png differ diff --git a/public/images/task_done.png b/public/images/task_done.png index 954ebedce..5fdcb415c 100644 Binary files a/public/images/task_done.png and b/public/images/task_done.png differ diff --git a/public/images/version_marker.png b/public/images/version_marker.png new file mode 100644 index 000000000..0368ca290 Binary files /dev/null and b/public/images/version_marker.png differ 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"; diff --git a/public/javascripts/calendar/lang/calendar-mk.js b/public/javascripts/calendar/lang/calendar-mk.js index 34fcaaee7..863e3bf2b 100644 --- a/public/javascripts/calendar/lang/calendar-mk.js +++ b/public/javascripts/calendar/lang/calendar-mk.js @@ -1,7 +1,7 @@ // ** I18N // Calendar МК language -// Author: Илин Татабитовски, +// Author: Ilin Tatabitovski, // Encoding: UTF-8 // Distributed under the same terms as the calendar itself. @@ -84,26 +84,26 @@ Calendar._TT["INFO"] = "За календарот"; Calendar._TT["ABOUT"] = "DHTML Date/Time Selector\n" + "(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) -"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + -"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"За последна верзија посети: http://www.dynarch.com/projects/calendar/\n" + +"Дистрибуирано под GNU LGPL. Види http://gnu.org/licenses/lgpl.html за детали." + "\n\n" + -"Date selection:\n" + -"- Use the \xab, \xbb buttons to select year\n" + -"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + -"- Hold mouse button on any of the above buttons for faster selection."; +"Бирање на дата:\n" + +"- Користи ги \xab, \xbb копчињата за да избереш година\n" + +"- Користи ги " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " копчињата за да избере месеци\n" + +"- Држи го притиснато копчето на глувчето на било кое копче за побрзо бирање."; Calendar._TT["ABOUT_TIME"] = "\n\n" + -"Time selection:\n" + -"- Click on any of the time parts to increase it\n" + -"- or Shift-click to decrease it\n" + -"- or click and drag for faster selection."; +"Бирање на време:\n" + +"- Клик на временските делови за да го зголемиш\n" + +"- или Shift-клик да го намалиш\n" + +"- или клик и влечи за побрзо бирање."; -Calendar._TT["PREV_YEAR"] = "Претходна година (hold for menu)"; -Calendar._TT["PREV_MONTH"] = "Претходен месец (hold for menu)"; +Calendar._TT["PREV_YEAR"] = "Претходна година (држи за мени)"; +Calendar._TT["PREV_MONTH"] = "Претходен месец (држи за мени)"; Calendar._TT["GO_TODAY"] = "Go Today"; -Calendar._TT["NEXT_MONTH"] = "Следен месец (hold for menu)"; -Calendar._TT["NEXT_YEAR"] = "Следна година (hold for menu)"; -Calendar._TT["SEL_DATE"] = "Изберете дата"; -Calendar._TT["DRAG_TO_MOVE"] = "Drag to move"; +Calendar._TT["NEXT_MONTH"] = "Следен месец (држи за мени)"; +Calendar._TT["NEXT_YEAR"] = "Следна година (држи за мени)"; +Calendar._TT["SEL_DATE"] = "Избери дата"; +Calendar._TT["DRAG_TO_MOVE"] = "Влечи да поместиш"; Calendar._TT["PART_TODAY"] = " (денес)"; // 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"] = "Затвори"; Calendar._TT["TODAY"] = "Денес"; -Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; +Calendar._TT["TIME_PART"] = "(Shift-)Клик или влечи за да промениш вредност"; // date formats Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; @@ -125,3 +125,4 @@ Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b"; Calendar._TT["WK"] = "нед"; Calendar._TT["TIME"] = "Време:"; + diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-mk.js b/public/javascripts/jstoolbar/lang/jstoolbar-mk.js index 3af63a1cc..30c68ec6c 100644 --- a/public/javascripts/jstoolbar/lang/jstoolbar-mk.js +++ b/public/javascripts/jstoolbar/lang/jstoolbar-mk.js @@ -1,16 +1,17 @@ jsToolBar.strings = {}; -jsToolBar.strings['Strong'] = 'Strong'; -jsToolBar.strings['Italic'] = 'Italic'; -jsToolBar.strings['Underline'] = 'Underline'; -jsToolBar.strings['Deleted'] = 'Deleted'; -jsToolBar.strings['Code'] = 'Inline Code'; -jsToolBar.strings['Heading 1'] = 'Heading 1'; -jsToolBar.strings['Heading 2'] = 'Heading 2'; -jsToolBar.strings['Heading 3'] = 'Heading 3'; -jsToolBar.strings['Unordered list'] = 'Unordered list'; +jsToolBar.strings['Strong'] = 'Задебелен'; +jsToolBar.strings['Italic'] = 'Закосен'; +jsToolBar.strings['Underline'] = 'Подвлечен'; +jsToolBar.strings['Deleted'] = 'Прецртан'; +jsToolBar.strings['Code'] = 'Код'; +jsToolBar.strings['Heading 1'] = 'Заглавје 1'; +jsToolBar.strings['Heading 2'] = 'Заглавје 2'; +jsToolBar.strings['Heading 3'] = 'Заглавје 3'; +jsToolBar.strings['Unordered list'] = 'Неподредена листа'; jsToolBar.strings['Ordered list'] = 'Подредена листа'; jsToolBar.strings['Quote'] = 'Цитат'; jsToolBar.strings['Unquote'] = 'Отстрани цитат'; -jsToolBar.strings['Preformatted text'] = 'Preformatted text'; -jsToolBar.strings['Wiki link'] = 'Линк до вики страна'; +jsToolBar.strings['Preformatted text'] = 'Форматиран текст'; +jsToolBar.strings['Wiki link'] = 'Врска до вики страна'; jsToolBar.strings['Image'] = 'Слика'; + diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 520ed8371..233d94e45 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -787,8 +787,10 @@ background-image:url('../images/close_hl.png'); white-space:nowrap; } +.task.label {width:100%;} + .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; } -.task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; } +.task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; } .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; } .task_todo.parent { background: #888; border: 1px solid #888; height: 6px;} @@ -796,7 +798,17 @@ background-image:url('../images/close_hl.png'); .task_todo.parent .left { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -5px; left: 0px; top: -1px;} .task_todo.parent .right { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-right: -5px; right: 0px; top: -1px;} -.milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; } +.milestone { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; } +.milestone_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;} +.milestone_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;} +.milestone_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;} +.project-line { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; } +.project_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;} +.project_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;} +.project_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;} + +.version-behind-schedule a, .issue-behind-schedule a {color: #f66914;} +.version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;} /***** Icons *****/ .icon { @@ -840,6 +852,7 @@ padding-bottom: 3px; .icon-comment { background-image: url(../images/comment.png); } .icon-summary { background-image: url(../images/lightning.png); } .icon-server-authentication { background-image: url(../images/server_key.png); } +.icon-issue { background-image: url(../images/ticket.png); } .icon-file { background-image: url(../images/files/default.png); } .icon-file.text-plain { background-image: url(../images/files/text.png); } @@ -898,6 +911,12 @@ td.username img.gravatar { margin: 0 1em 1em 0; } +/* Used on 12px Gravatar img tags without the icon background */ +.icon-gravatar { + float: left; + margin-right: 4px; +} + #activity dt, .journal { clear: left; diff --git a/test/functional/activities_controller_test.rb b/test/functional/activities_controller_test.rb new file mode 100644 index 000000000..ba9c33985 --- /dev/null +++ b/test/functional/activities_controller_test.rb @@ -0,0 +1,87 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ActivitiesControllerTest < ActionController::TestCase + fixtures :all + + def test_project_index + get :index, :id => 1, :with_subprojects => 0 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:events_by_day) + + assert_tag :tag => "h3", + :content => /#{2.days.ago.to_date.day}/, + :sibling => { :tag => "dl", + :child => { :tag => "dt", + :attributes => { :class => /issue-edit/ }, + :child => { :tag => "a", + :content => /(#{IssueStatus.find(2).name})/, + } + } + } + end + + def test_previous_project_index + get :index, :id => 1, :from => 3.days.ago.to_date + assert_response :success + assert_template 'index' + assert_not_nil assigns(:events_by_day) + + assert_tag :tag => "h3", + :content => /#{3.day.ago.to_date.day}/, + :sibling => { :tag => "dl", + :child => { :tag => "dt", + :attributes => { :class => /issue/ }, + :child => { :tag => "a", + :content => /#{Issue.find(1).subject}/, + } + } + } + end + + def test_global_index + get :index + assert_response :success + assert_template 'index' + assert_not_nil assigns(:events_by_day) + + assert_tag :tag => "h3", + :content => /#{5.day.ago.to_date.day}/, + :sibling => { :tag => "dl", + :child => { :tag => "dt", + :attributes => { :class => /issue/ }, + :child => { :tag => "a", + :content => /#{Issue.find(5).subject}/, + } + } + } + end + + def test_user_index + get :index, :user_id => 2 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:events_by_day) + + assert_tag :tag => "h3", + :content => /#{3.day.ago.to_date.day}/, + :sibling => { :tag => "dl", + :child => { :tag => "dt", + :attributes => { :class => /issue/ }, + :child => { :tag => "a", + :content => /#{Issue.find(1).subject}/, + } + } + } + end + + def test_index_atom_feed + get :index, :format => 'atom' + assert_response :success + assert_template 'common/feed.atom.rxml' + assert_tag :tag => 'entry', :child => { + :tag => 'link', + :attributes => {:href => 'http://test.host/issues/11'}} + end + +end diff --git a/test/functional/context_menus_controller_test.rb b/test/functional/context_menus_controller_test.rb index eee9dc608..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/edit?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 => '' } diff --git a/test/functional/files_controller_test.rb b/test/functional/files_controller_test.rb new file mode 100644 index 000000000..6035c4be5 --- /dev/null +++ b/test/functional/files_controller_test.rb @@ -0,0 +1,67 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class FilesControllerTest < ActionController::TestCase + fixtures :all + + def setup + @controller = FilesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @request.session[:user_id] = nil + Setting.default_language = 'en' + end + + def test_index + get :index, :project_id => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:containers) + + # file attached to the project + assert_tag :a, :content => 'project_file.zip', + :attributes => { :href => '/attachments/download/8/project_file.zip' } + + # file attached to a project's version + assert_tag :a, :content => 'version_file.zip', + :attributes => { :href => '/attachments/download/9/version_file.zip' } + end + + 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 :create, :project_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_create_version_file + set_tmp_attachments_directory + @request.session[:user_id] = 2 + Setting.notified_events = ['file_added'] + + assert_difference 'Attachment.count' do + post :create, :project_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/gantts_controller_test.rb b/test/functional/gantts_controller_test.rb index 24c84f2ae..9aba1d5aa 100644 --- a/test/functional/gantts_controller_test.rb +++ b/test/functional/gantts_controller_test.rb @@ -5,20 +5,20 @@ class GanttsControllerTest < ActionController::TestCase context "#gantt" do should "work" do + i2 = Issue.find(2) + i2.update_attribute(:due_date, 1.month.from_now) + get :show, :project_id => 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/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 7c00db708..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 @@ -910,10 +911,10 @@ class IssuesControllerTest < ActionController::TestCase assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'} end - def test_bulk_edit + def test_bulk_update @request.session[:user_id] = 2 # update issues priority - post :bulk_edit, :ids => [1, 2], :notes => 'Bulk editing', + post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing', :issue => {:priority_id => 7, :assigned_to_id => '', :custom_field_values => {'2' => ''}} @@ -929,10 +930,10 @@ class IssuesControllerTest < ActionController::TestCase assert_equal 1, journal.details.size end - def test_bullk_edit_should_send_a_notification + def test_bullk_update_should_send_a_notification @request.session[:user_id] = 2 ActionMailer::Base.deliveries.clear - post(:bulk_edit, + post(:bulk_update, { :ids => [1, 2], :notes => 'Bulk editing', @@ -947,10 +948,10 @@ class IssuesControllerTest < ActionController::TestCase assert_equal 2, ActionMailer::Base.deliveries.size end - def test_bulk_edit_status + def test_bulk_update_status @request.session[:user_id] = 2 # update issues priority - post :bulk_edit, :ids => [1, 2], :notes => 'Bulk editing status', + post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status', :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '5'} @@ -960,10 +961,10 @@ class IssuesControllerTest < ActionController::TestCase assert issue.closed? end - def test_bulk_edit_custom_field + def test_bulk_update_custom_field @request.session[:user_id] = 2 # update issues priority - post :bulk_edit, :ids => [1, 2], :notes => 'Bulk editing custom field', + post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field', :issue => {:priority_id => '', :assigned_to_id => '', :custom_field_values => {'2' => '777'}} @@ -978,20 +979,20 @@ class IssuesControllerTest < ActionController::TestCase assert_equal '777', journal.details.first.value end - def test_bulk_unassign + def test_bulk_update_unassign assert_not_nil Issue.find(2).assigned_to @request.session[:user_id] = 2 # unassign issues - post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'} + post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'} assert_response 302 # check that the issues were updated assert_nil Issue.find(2).assigned_to end - def test_post_bulk_edit_should_allow_fixed_version_to_be_set_to_a_subproject + def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject @request.session[:user_id] = 2 - post :bulk_edit, :ids => [1,2], :issue => {:fixed_version_id => 4} + post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4} assert_response :redirect issues = Issue.find([1,2]) @@ -1001,17 +1002,17 @@ class IssuesControllerTest < ActionController::TestCase end end - def test_post_bulk_edit_should_redirect_back_using_the_back_url_parameter + def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter @request.session[:user_id] = 2 - post :bulk_edit, :ids => [1,2], :back_url => '/issues' + post :bulk_update, :ids => [1,2], :back_url => '/issues' assert_response :redirect assert_redirected_to '/issues' end - def test_post_bulk_edit_should_not_redirect_back_using_the_back_url_parameter_off_the_host + def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host @request.session[:user_id] = 2 - post :bulk_edit, :ids => [1,2], :back_url => 'http://google.com' + post :bulk_update, :ids => [1,2], :back_url => 'http://google.com' assert_response :redirect assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier diff --git a/test/functional/news_controller_test.rb b/test/functional/news_controller_test.rb index 3abb9a7fb..dd782fa3b 100644 --- a/test/functional/news_controller_test.rb +++ b/test/functional/news_controller_test.rb @@ -65,12 +65,12 @@ class NewsControllerTest < ActionController::TestCase assert_template 'new' end - def test_post_new + def test_post_create ActionMailer::Base.deliveries.clear Setting.notified_events << 'news_added' @request.session[:user_id] = 2 - post :new, :project_id => 1, :news => { :title => 'NewsControllerTest', + post :create, :project_id => 1, :news => { :title => 'NewsControllerTest', :description => 'This is the description', :summary => '' } assert_redirected_to 'projects/ecookbook/news' @@ -98,9 +98,9 @@ class NewsControllerTest < ActionController::TestCase assert_equal 'Description changed by test_post_edit', news.description end - def test_post_new_with_validation_failure + def test_post_create_with_validation_failure @request.session[:user_id] = 2 - post :new, :project_id => 1, :news => { :title => '', + post :create, :project_id => 1, :news => { :title => '', :description => 'This is the description', :summary => '' } assert_response :success diff --git a/test/functional/project_enumerations_controller_test.rb b/test/functional/project_enumerations_controller_test.rb new file mode 100644 index 000000000..942399a96 --- /dev/null +++ b/test/functional/project_enumerations_controller_test.rb @@ -0,0 +1,189 @@ +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_update_to_override_system_activities + @request.session[:user_id] = 2 # manager + billable_field = TimeEntryActivityCustomField.find_by_name("Billable") + + 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 + "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_update_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 + + + 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 + } + + 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_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 + put :update, :project_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_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 + 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 + 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 + } + 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 + + 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, :project_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, :project_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 988496cec..636cb1751 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -87,20 +87,64 @@ 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 + + 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 :new + assert_response :success + assert_template 'new' + 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 :new, :parent_id => 'ecookbook' + assert_response :success + assert_template 'new' + # 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, @@ -293,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) @@ -317,170 +342,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_list_files - get :list_files, :id => 1 - assert_response :success - assert_template 'list_files' - assert_not_nil assigns(:containers) - - # file attached to the project - assert_tag :a, :content => 'project_file.zip', - :attributes => { :href => '/attachments/download/8/project_file.zip' } - - # file attached to a project's version - assert_tag :a, :content => 'version_file.zip', - :attributes => { :href => '/attachments/download/9/version_file.zip' } - end - - def test_roadmap - get :roadmap, :id => 1 - assert_response :success - assert_template 'roadmap' - assert_not_nil assigns(:versions) - # Version with no date set appears - assert assigns(:versions).include?(Version.find(3)) - # Completed version doesn't appear - assert !assigns(:versions).include?(Version.find(1)) - end - - def test_roadmap_with_completed_versions - get :roadmap, :id => 1, :completed => 1 - assert_response :success - assert_template 'roadmap' - assert_not_nil assigns(:versions) - # Version with no date set appears - assert assigns(:versions).include?(Version.find(3)) - # Completed version appears - assert assigns(:versions).include?(Version.find(1)) - end - - def test_roadmap_showing_subprojects_versions - @subproject_version = Version.generate!(:project => Project.find(3)) - get :roadmap, :id => 1, :with_subprojects => 1 - assert_response :success - assert_template 'roadmap' - assert_not_nil assigns(:versions) - - assert assigns(:versions).include?(Version.find(4)), "Shared version not found" - assert assigns(:versions).include?(@subproject_version), "Subproject version not found" - end - def test_project_activity - get :activity, :id => 1, :with_subprojects => 0 - assert_response :success - assert_template 'activity' - assert_not_nil assigns(:events_by_day) - - assert_tag :tag => "h3", - :content => /#{2.days.ago.to_date.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue-edit/ }, - :child => { :tag => "a", - :content => /(#{IssueStatus.find(2).name})/, - } - } - } - end - - def test_previous_project_activity - get :activity, :id => 1, :from => 3.days.ago.to_date - assert_response :success - assert_template 'activity' - assert_not_nil assigns(:events_by_day) - - assert_tag :tag => "h3", - :content => /#{3.day.ago.to_date.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue/ }, - :child => { :tag => "a", - :content => /#{Issue.find(1).subject}/, - } - } - } - end - - def test_global_activity - get :activity - assert_response :success - assert_template 'activity' - assert_not_nil assigns(:events_by_day) - - assert_tag :tag => "h3", - :content => /#{5.day.ago.to_date.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue/ }, - :child => { :tag => "a", - :content => /#{Issue.find(5).subject}/, - } - } - } - end - - def test_user_activity - get :activity, :user_id => 2 - assert_response :success - assert_template 'activity' - assert_not_nil assigns(:events_by_day) - - assert_tag :tag => "h3", - :content => /#{3.day.ago.to_date.day}/, - :sibling => { :tag => "dl", - :child => { :tag => "dt", - :attributes => { :class => /issue/ }, - :child => { :tag => "a", - :content => /#{Issue.find(1).subject}/, - } - } - } - end - - def test_activity_atom_feed - get :activity, :format => 'atom' - assert_response :success - assert_template 'common/feed.atom.rxml' - assert_tag :tag => 'entry', :child => { - :tag => 'link', - :attributes => {:href => 'http://test.host/issues/11'}} - end - def test_archive @request.session[:user_id] = 1 # admin post :archive, :id => 1 @@ -545,184 +406,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 - - 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/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/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, diff --git a/test/functional/versions_controller_test.rb b/test/functional/versions_controller_test.rb index e4864c908..e4ac5c068 100644 --- a/test/functional/versions_controller_test.rb +++ b/test/functional/versions_controller_test.rb @@ -31,6 +31,41 @@ class VersionsControllerTest < ActionController::TestCase User.current = nil end + def test_index + get :index, :project_id => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:versions) + # Version with no date set appears + 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 + get :index, :project_id => 1, :completed => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:versions) + # Version with no date set appears + assert assigns(:versions).include?(Version.find(3)) + # Completed version appears + assert assigns(:versions).include?(Version.find(1)) + end + + def test_index_showing_subprojects_versions + @subproject_version = Version.generate!(:project => Project.find(3)) + get :index, :project_id => 1, :with_subprojects => 1 + assert_response :success + assert_template 'index' + assert_not_nil assigns(:versions) + + assert assigns(:versions).include?(Version.find(4)), "Shared version not found" + assert assigns(:versions).include?(@subproject_version), "Subproject version not found" + end + def test_show get :show, :id => 2 assert_response :success @@ -40,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') @@ -51,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' @@ -73,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_edit + def test_post_update @request.session[:user_id] = 2 - post :edit, :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' @@ -91,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 diff --git a/test/integration/issues_api_test.rb b/test/integration/issues_api_test.rb index a1e2cb703..e26cf643b 100644 --- a/test/integration/issues_api_test.rb +++ b/test/integration/issues_api_test.rb @@ -198,7 +198,7 @@ class IssuesApiTest < ActionController::IntegrationTest setup do @issue_count = Issue.count @journal_count = Journal.count - @attributes = {:subject => 'API update'} + @attributes = {:subject => 'API update', :notes => 'A new note'} put '/issues/1.xml', {:issue => @attributes}, :authorization => credentials('jsmith') end @@ -214,10 +214,15 @@ class IssuesApiTest < ActionController::IntegrationTest assert_equal Journal.count, @journal_count + 1 end + should "add the note to the journal" do + journal = Journal.last + assert_equal "A new note", journal.notes + end + should "update the issue" do issue = Issue.find(1) @attributes.each do |attribute, value| - assert_equal value, issue.send(attribute) + assert_equal value, issue.send(attribute) unless attribute == :notes end end @@ -252,7 +257,7 @@ class IssuesApiTest < ActionController::IntegrationTest setup do @issue_count = Issue.count @journal_count = Journal.count - @attributes = {:subject => 'API update'} + @attributes = {:subject => 'API update', :notes => 'A new note'} put '/issues/1.json', {:issue => @attributes}, :authorization => credentials('jsmith') end @@ -268,13 +273,18 @@ class IssuesApiTest < ActionController::IntegrationTest assert_equal Journal.count, @journal_count + 1 end + should "add the note to the journal" do + journal = Journal.last + assert_equal "A new note", journal.notes + end + should "update the issue" do issue = Issue.find(1) @attributes.each do |attribute, value| - assert_equal value, issue.send(attribute) + assert_equal value, issue.send(attribute) unless attribute == :notes end end - + end context "PUT /issues/1.json with failed update" do diff --git a/test/integration/issues_test.rb b/test/integration/issues_test.rb index 0c1a36d34..c8a151cfd 100644 --- a/test/integration/issues_test.rb +++ b/test/integration/issues_test.rb @@ -69,7 +69,7 @@ class IssuesTest < ActionController::IntegrationTest log_user('jsmith', 'jsmith') set_tmp_attachments_directory - put 'issues/1/edit', + put 'issues/1', :notes => 'Some notes', :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}} assert_redirected_to "issues/1" diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb index b05a1285d..1fc3f82d4 100644 --- a/test/integration/routing_test.rb +++ b/test/integration/routing_test.rb @@ -19,8 +19,8 @@ require "#{File.dirname(__FILE__)}/../test_helper" class RoutingTest < ActionController::IntegrationTest context "activities" do - should_route :get, "/activity", :controller => 'projects', :action => 'activity', :id => nil - should_route :get, "/activity.atom", :controller => 'projects', :action => 'activity', :id => nil, :format => 'atom' + should_route :get, "/activity", :controller => 'activities', :action => 'index', :id => nil + should_route :get, "/activity.atom", :controller => 'activities', :action => 'index', :id => nil, :format => 'atom' end context "attachments" do @@ -91,14 +91,14 @@ class RoutingTest < ActionController::IntegrationTest should_route :post, "/issues/1/quoted", :controller => 'journals', :action => 'new', :id => '1' should_route :get, "/issues/calendar", :controller => 'calendars', :action => 'show' - should_route :post, "/issues/calendar", :controller => 'calendars', :action => 'show' + should_route :put, "/issues/calendar", :controller => 'calendars', :action => 'update' should_route :get, "/projects/project-name/issues/calendar", :controller => 'calendars', :action => 'show', :project_id => 'project-name' - should_route :post, "/projects/project-name/issues/calendar", :controller => 'calendars', :action => 'show', :project_id => 'project-name' + should_route :put, "/projects/project-name/issues/calendar", :controller => 'calendars', :action => 'update', :project_id => 'project-name' should_route :get, "/issues/gantt", :controller => 'gantts', :action => 'show' - should_route :post, "/issues/gantt", :controller => 'gantts', :action => 'show' + should_route :put, "/issues/gantt", :controller => 'gantts', :action => 'update' should_route :get, "/projects/project-name/issues/gantt", :controller => 'gantts', :action => 'show', :project_id => 'project-name' - should_route :post, "/projects/project-name/issues/gantt", :controller => 'gantts', :action => 'show', :project_id => 'project-name' + should_route :put, "/projects/project-name/issues/gantt", :controller => 'gantts', :action => 'update', :project_id => 'project-name' should_route :get, "/issues/auto_complete", :controller => 'auto_completes', :action => 'issues' @@ -108,6 +108,9 @@ class RoutingTest < ActionController::IntegrationTest should_route :post, "/issues/context_menu", :controller => 'context_menus', :action => 'issues' should_route :get, "/issues/changes", :controller => 'journals', :action => 'index' + + should_route :get, "/issues/bulk_edit", :controller => 'issues', :action => 'bulk_edit' + should_route :post, "/issues/bulk_edit", :controller => 'issues', :action => 'bulk_update' end context "issue categories" do @@ -154,7 +157,7 @@ class RoutingTest < ActionController::IntegrationTest should_route :get, "/projects/567/news/new", :controller => 'news', :action => 'new', :project_id => '567' should_route :get, "/news/234", :controller => 'news', :action => 'show', :id => '234' - should_route :post, "/projects/567/news/new", :controller => 'news', :action => 'new', :project_id => '567' + should_route :post, "/projects/567/news", :controller => 'news', :action => 'create', :project_id => '567' should_route :post, "/news/567/edit", :controller => 'news', :action => 'edit', :id => '567' should_route :post, "/news/567/destroy", :controller => 'news', :action => 'destroy', :id => '567' end @@ -163,31 +166,30 @@ 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' 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 => 'projects', :action => 'list_files', :id => '33' - should_route :get, "/projects/33/files/new", :controller => 'projects', :action => 'add_file', :id => '33' - should_route :get, "/projects/33/roadmap", :controller => 'projects', :action => 'roadmap', :id => '33' - should_route :get, "/projects/33/activity", :controller => 'projects', :action => 'activity', :id => '33' - should_route :get, "/projects/33/activity.atom", :controller => 'projects', :action => 'activity', :id => '33', :format => 'atom' + 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/new", :controller => 'projects', :action => 'add' - 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", :controller => 'projects', :action => 'create' + should_route :post, "/projects.xml", :controller => 'projects', :action => 'create', :format => 'xml' + 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' - should_route :post, "/projects/64/activities/save", :controller => 'projects', :action => 'save_activities', :id => '64' - should_route :put, "/projects/1.xml", :controller => 'projects', :action => 'edit', :id => '1', :format => 'xml' + 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 => 'projects', :action => 'reset_activities', :id => '64' + should_route :delete, "/projects/64/enumerations", :controller => 'project_enumerations', :action => 'destroy', :project_id => '64' end context "repositories" do @@ -249,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, "/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' end context "wiki (singular, project's pages)" do diff --git a/test/object_daddy_helpers.rb b/test/object_daddy_helpers.rb index 4a2b85a9e..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) @@ -25,8 +30,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 6fd21fe37..2906ec6e4 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, @@ -33,6 +30,35 @@ class ApplicationHelperTest < HelperTestCase 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 = { @@ -575,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/query_test.rb b/test/unit/query_test.rb index 12ab5d932..db6173bae 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,153 @@ 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.sort, @query.available_filters["member_of_group"][:values].sort + 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 + 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 + + 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 diff --git a/test/unit/time_entry_test.rb b/test/unit/time_entry_test.rb index 3c135510a..3069f9368 100644 --- a/test/unit/time_entry_test.rb +++ b/test/unit/time_entry_test.rb @@ -48,4 +48,52 @@ class TimeEntryTest < ActiveSupport::TestCase def test_hours_should_default_to_nil assert_nil TimeEntry.new.hours end + + context "#earilest_date_for_project" do + setup do + User.current = nil + @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 + setup do + User.current = nil + @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 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 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 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/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 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
    @@ -66,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) %> +
    @@ -163,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 ) %> <% # @@ -226,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/app/views/issues/_action_menu.rhtml b/app/views/issues/_action_menu.rhtml index c5d17511a..30c63ec66 100644 --- a/app/views/issues/_action_menu.rhtml +++ b/app/views/issues/_action_menu.rhtml @@ -6,5 +6,5 @@ <%= link_to_if_authorized l(:button_duplicate), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue }, :class => 'icon icon-duplicate' %> <%= link_to_if_authorized l(:button_copy), new_issue_move_path(:id => @issue, :copy_options => {:copy => 't'}), :class => 'icon icon-copy' %> <%= link_to_if_authorized l(:button_move), new_issue_move_path(:id => @issue), :class => 'icon icon-move' %> -<%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> +<%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => (@issue.leaf? ? l(:text_are_you_sure) : l(:text_are_you_sure_with_children)), :method => :post, :class => 'icon icon-del' %> diff --git a/app/views/issues/_attributes.rhtml b/app/views/issues/_attributes.rhtml index 455eb77b2..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') %>

    @@ -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? %> diff --git a/app/views/issues/_history.rhtml b/app/views/issues/_history.rhtml index a95cbf81c..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}")%> 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 %> 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 %> diff --git a/app/views/issues/bulk_edit.rhtml b/app/views/issues/bulk_edit.rhtml index b01128840..5fdfd58a6 100644 --- a/app/views/issues/bulk_edit.rhtml +++ b/app/views/issues/bulk_edit.rhtml @@ -2,7 +2,7 @@
      <%= @issues.collect {|i| content_tag('li', link_to(h("#{i.tracker} ##{i.id}"), { :action => 'show', :id => i }) + h(": #{i.subject}")) }.join("\n") %>
    -<% form_tag() do %> +<% form_tag(:action => 'bulk_update') do %> <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
    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/news/edit.rhtml b/app/views/news/edit.rhtml index 4be566e0b..04d64df1f 100644 --- a/app/views/news/edit.rhtml +++ b/app/views/news/edit.rhtml @@ -12,3 +12,7 @@ }, :accesskey => accesskey(:preview) %> <% end %>
    + +<% content_for :header_tags do %> + <%= stylesheet_link_tag 'scm' %> +<% end %> diff --git a/app/views/news/index.rhtml b/app/views/news/index.rhtml index 8b7cc66e1..2b53b7f72 100644 --- a/app/views/news/index.rhtml +++ b/app/views/news/index.rhtml @@ -7,7 +7,7 @@
    <%= 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 %>
    @@ -32,7 +32,7 @@
    -<%= link_to(l(:button_reset), {:controller => 'projects', :action => 'reset_activities', :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/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 @@

    <% 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 %>