diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 156933d..bfbc6c0 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -18,10 +18,11 @@ class IssuesController < ApplicationController menu_item :new_issue, :only => :new - before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment] + before_filter :find_issue_and_project_id, :only => [ :show_children, :hide_children ] + before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment, :update_subject ] before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] - before_filter :find_project, :only => [:new, :update_form, :preview, :gantt, :calendar] - before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu] + before_filter :find_project, :only => [:new, :auto_complete_for_issue_parent, :add_subissue, :update_form, :preview, :gantt, :calendar] + before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu, :show_children, :hide_children, :auto_complete_for_issue_parent ] before_filter :find_optional_project, :only => [:index, :changes] accept_key_auth :index, :changes @@ -43,6 +44,89 @@ class IssuesController < ApplicationController include SortHelper include IssuesHelper helper :timelog + include ActionView::Helpers::PrototypeHelper + + def update_subject + @notes = params[:notes] + journal = @issue.init_journal(User.current, @notes) + @edit_allowed = User.current.allowed_to?(:edit_issues, @project) + # 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[:subject] + @issue.subject = params[:subject] + end + if @issue.save + if !journal.new_record? + # Only send notification if something was actually changed + flash[:notice] = l(:notice_successful_update) + Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated') + end + render(:text => params[:subject]) + end + rescue ActiveRecord::StaleObjectError + # Optimistic locking exception + flash.now[:error] = l(:notice_locking_conflict) + end + + def show_children + retrieve_query + if @query.valid? + render :update do |page| + page.replace "issue-#{@issue.id}", :partial => 'show_children', :locals => {:issue => @issue, :query => @query} + end + end + end + + def hide_children + retrieve_query #verificar necessidade + if @query.valid? + render :update do |page| + page.select(".issue-#{@issue.id}-child").each do |child| + child.remove + end + page.select("#issue-#{@issue.id} a.contract-link").each do |contractIcon| + contractIcon.replace link_to_remote(image_tag("expand.png", :class=>'expand-icon'), {:url => {:controller => 'issues', :action => "show_children", :id => @issue, :project_id => @project}}, :class=>'expand-link') + end + end + end + end + + def auto_complete_for_issue_parent + @phrase = params[:issue_parent] + @candidates = [] + + # If cross project issue relations is allowed we should get + # candidates from every project + if Setting.cross_project_issue_relations? + projects_to_search = nil + else + projects_to_search = [ @project ] + @project.active_children + end + + # Try to find issue by id. + if @phrase.match(/^#?(\d+)$/) + if Setting.cross_project_issue_relations? + issue = Issue.find_by_id( $1) + else + issue = Issue.find_by_id_and_project_id( $1, projects_to_search.collect { |i| i.id}) + end + @candidates = [ issue ] if issue + end + + # If finding by id is fail, try to find by searching in subject + # and description. + if @candidates.empty? + # extract tokens from the question + # eg. hello "bye bye" => ["hello", "bye bye"] + tokens = @phrase.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')} + # tokens must be at least 3 character long + tokens = tokens.uniq.select {|w| w.length > 2 } + like_tokens = tokens.collect {|w| "%#{w.downcase}%"} + + @candidates, count = Issue.search( like_tokens, projects_to_search, :before => true) + end + + render :inline => "<%= auto_complete_result_parent_issue( @candidates, @phrase) %>" + end def index sort_init "#{Issue.table_name}.id", "desc" @@ -58,11 +142,18 @@ class IssuesController < ApplicationController end @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement) @issue_pages = Paginator.new self, @issue_count, limit, params['page'] - @issues = Issue.find :all, :order => sort_clause, - :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ], - :conditions => @query.statement, - :limit => limit, - :offset => @issue_pages.current.offset + @issues = Issue.find_with_parents( :all, :order => sort_clause, + :include => [ :assigned_to, + :status, + :tracker, + :project, + :priority, + :category, + :fixed_version ], + :conditions => @query.statement, + :limit => limit, + :offset => @issue_pages.current.offset) + respond_to do |format| format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? } format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } @@ -132,7 +223,8 @@ class IssuesController < ApplicationController end @issue.status = default_status @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq - + @parent_issue = Issue.find( params[:issue_parent_issue_id]) unless params[:issue_parent_issue_id].blank? + if request.get? || request.xhr? @issue.start_date ||= Date.today else @@ -140,6 +232,7 @@ class IssuesController < ApplicationController # Check that the user is allowed to apply the requested status @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status if @issue.save + set_parent( @issue, params[:issue_parent_issue_id]) if params[:issue_parent_issue_id] attach_files(@issue, params[:attachments]) flash[:notice] = l(:notice_successful_create) Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added') @@ -150,6 +243,18 @@ class IssuesController < ApplicationController @priorities = Enumeration::get_values('IPRI') render :layout => !request.xhr? end + + def add_subissue + if params[:issue_parent_issue_id].nil? + flash.now[:error] = 'No parent issue specified.' + render :nothing => true, :layout => true + return + else + redirect_to( :controller => 'issues', :action => 'new', + :issue_parent_issue_id => params[:issue_parent_issue_id]) + return + end + end # Attributes that can be updated on workflow transition (without :edit permission) # TODO: make it configurable (at least per role) @@ -177,6 +282,7 @@ class IssuesController < ApplicationController attachments = attach_files(@issue, params[:attachments]) attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)} if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save + set_parent(@issue, params[:issue_parent_issue_id]) if params[:issue_parent_issue_id] # Log spend time if current_role.allowed_to?(:log_time) @time_entry.save @@ -236,6 +342,7 @@ class IssuesController < ApplicationController call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) # Don't save any change to the issue if the user is not authorized to apply the requested status if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save + set_parent(issue, params[:issue_parent_issue_id]) unless params[:issue_parent_issue_id].blank? # Send notification for each issue (if changed) Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated') else @@ -410,6 +517,7 @@ class IssuesController < ApplicationController def update_form @issue = Issue.new(params[:issue]) + @parent_issue = @issue.parent render :action => :new, :layout => false end @@ -423,11 +531,20 @@ class IssuesController < ApplicationController private def find_issue @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) + @parent_issue = @issue.parent @project = @issue.project rescue ActiveRecord::RecordNotFound render_404 end + def find_issue_and_project_id + @issue = Issue.find( params[:id], :include => [:project, :tracker, :status, :author, :priority, :category]) + @parent_issue = @issue.parent + @project = Project.find( params[:project_id]) + rescue ActiveRecord::RecordNotFound + render_404 + end + # Filter for bulk operations def find_issues @issues = Issue.find_all_by_id(params[:id] || params[:ids]) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a7efa46..5fe55ae 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -42,6 +42,8 @@ class ProjectsController < ApplicationController helper :repositories include RepositoriesHelper include ProjectsHelper + helper :versions + include VersionsHelper # Lists visible projects def index @@ -271,4 +273,48 @@ private @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s } end end + + + def sort_as_tree(issues) + issues.sort!{|a,b| a.hierarchical_level <=> b.hierarchical_level} + @sorted_issues = [] + issues.each do |issue| + if @sorted_issues.empty? + @sorted_issues << issue + next + end + @time_to_stop = false #indicates when this task reaches its parent task (important because it has to stop between its parent task and the next aunt task + @sorted_issues.each do |sorted_issue| + #if same parent and smaller date, stop; if same parent, same date and smaller id, stop; after parent and before next parent, stop; + if ((sorted_issue.parent == issue.parent) && (sorted_issue.start_date > issue.start_date)) || + ((sorted_issue.parent == issue.parent) && (sorted_issue.start_date == issue.start_date) && (sorted_issue.id > issue.id)) || + (@time_to_stop && (sorted_issue.hierarchical_level < issue.hierarchical_level)) + @sorted_issues.insert(@sorted_issues.index(sorted_issue), issue) + break + end + @time_to_stop = true if sorted_issue == issue.parent + end + #if this issue's parent is the last element + @sorted_issues << issue if @time_to_stop + end + @sorted_issues + end + + #assumes that first level issues are ordered by date (sort_as_tree) + def integrate_versions_with_issues_tree(issues, versions) + versions.sort! {|x,y| x.start_date <=> y.start_date } + versions.each do |version| + issues << version if issues.empty? + issues.each do |issue| + if ((issue.is_a? Issue && issue.root?) || (issue.is_a? Version)) && version.start_date < issue.start_date + #insert version before a root task or another version whose date is immediately after this task's one + issues.insert(issues.index(issue), version) + elsif issue == issues.last + issues << version + end + end + end + issues + end + end diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 3a22217..d1dfec1 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -20,6 +20,10 @@ class VersionsController < ApplicationController before_filter :find_project, :authorize def show + @issues = @version.fixed_issues.find(:all, + :include => [:status, :tracker], + :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") + @issues = Issue.find_with_parents( @issues.collect { |i| i.id}) end def edit diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 43acabd..94c57ad 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -19,6 +19,12 @@ require 'csv' module IssuesHelper include ApplicationHelper + + def issue_ancestors(issue=@issue) + ancestors = "" + return "" if issue.parent == nil + ancestors += "issue-#{issue.parent.id}-child " + issue_ancestors(issue.parent) + end def render_issue_tooltip(issue) @cached_label_start_date ||= l(:field_start_date) @@ -185,4 +191,31 @@ module IssuesHelper export.rewind export end + + def set_parent(issue, parent_id) + if (issue && parent_id.to_s.size > 0) + issue.relations_from.each do |relation| + if relation.relation_type == IssueRelation::TYPE_PARENTS + relation.destroy + end + end + issue.reload + IssueRelation.create do |relation| + relation.issue_from = issue + relation.issue_to = Issue.find(parent_id) + relation.relation_type = IssueRelation::TYPE_PARENTS + unless relation.save + flash[:error] = "Can't set ##{parent_id} as parent for the issue." + end + end unless parent_id == '0' + end + end + + def auto_complete_result_parent_issue(candidates, phrase) + return "" if candidates.empty? + candidates.map! do |c| + content_tag("li", highlight( c.to_s, phrase), :id => String( c[:id])) + end + content_tag("ul", candidates.uniq) + end end diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb index cf9819f..f5b9042 100644 --- a/app/helpers/queries_helper.rb +++ b/app/helpers/queries_helper.rb @@ -1,3 +1,4 @@ +# -*- coding: mule-utf-8 -*- # redMine - project management software # Copyright (C) 2006-2007 Jean-Philippe Lang # @@ -40,8 +41,7 @@ module QueriesHelper else case column.name when :subject - h((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') + - link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) + subject_in_tree(issue, value) when :done_ratio progress_bar(value, :width => '80px') when :fixed_version @@ -52,4 +52,22 @@ module QueriesHelper end end end + + def subject_in_tree(issue, value) + image = "" + unless issue.edge? #don't show + or - icons for leaf issues + #o controle das tarefas a ser mostradas não está sendo feito por _list? verificar + image = @show_all_issues || + @show_children ? + link_to_remote(image_tag("contract.png", :class=>'contract-icon'), {:url => {:controller => 'issues', :action => "hide_children", :id => issue, :project_id => @project}}, :class=>'contract-link') : + link_to_remote(image_tag("expand.png", :class=>'expand-icon'), {:url => {:controller => 'issues', :action => "show_children", :id => issue, :project_id => @project}}, :class=>'expand-link') + end + content_tag('span', image + content_tag('div', subject_text(issue, value), :class=>'issue-subject'), :class=>"issue-subject-level-#{issue.hierarchical_level}") + end + + def subject_text(issue, value) + subject_text = link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) + h((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') + subject_text + end + end diff --git a/app/helpers/versions_helper.rb b/app/helpers/versions_helper.rb index 0fcc640..c50b20e 100644 --- a/app/helpers/versions_helper.rb +++ b/app/helpers/versions_helper.rb @@ -44,4 +44,31 @@ module VersionsHelper def status_by_options_for_select(value) options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value) end + + def render_list_of_related_issues( issues, version, current_level = 1) + issues_on_current_level = issues.select { |i| i.hierarchical_level == current_level } + issues -= issues_on_current_level + content_tag( 'ul') do + html = '' + issues_on_current_level.each do |issue| + opts_for_issue_li = { } + if !issue.fixed_version or issue.fixed_version != version + opts_for_issue_li[:style] = 'opacity: 0.5;filter: alpha(opacity=50);' + end + html << content_tag( 'li', opts_for_issue_li) do + opts = { } + if issue.done_ratio == 100 + opts[:style] = 'font-weight: bold' + end + link_to_issue(issue, opts) + ": " + h(issue.subject) + end + children_to_print = issues & issue.children + children_to_print += issues.select { |i| i.hierarchical_level >= current_level + 2} + unless children_to_print.empty? + html << render_list_of_related_issues( children_to_print, version, current_level + 1) + end + end + html + end + end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 4701e41..0be2ecc 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -18,20 +18,20 @@ class Issue < ActiveRecord::Base belongs_to :project belongs_to :tracker - belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' - belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' - belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' - belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' - belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id' - belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' + belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' + belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' + belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' + belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' + belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id' + belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' - has_many :journals, :as => :journalized, :dependent => :destroy - has_many :attachments, :as => :container, :dependent => :destroy + has_many :journals, :as => :journalized, :dependent => :destroy + has_many :attachments, :as => :container, :dependent => :destroy has_many :time_entries, :dependent => :delete_all has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC" has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all - has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all + has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all acts_as_customizable acts_as_watchable @@ -44,9 +44,9 @@ class Issue < ActiveRecord::Base acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]} - validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status - validates_length_of :subject, :maximum => 255 - validates_inclusion_of :done_ratio, :in => 0..100 + validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status + validates_length_of :subject, :maximum => 255 + validates_inclusion_of :done_ratio, :in => 0..100 validates_numericality_of :estimated_hours, :allow_nil => true def after_initialize @@ -99,11 +99,6 @@ class Issue < ActiveRecord::Base return true end - def priority_id=(pid) - self.priority = nil - write_attribute(:priority_id, pid) - end - def estimated_hours=(h) write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) end @@ -120,6 +115,19 @@ class Issue < ActiveRecord::Base if start_date && soonest_start && start_date < soonest_start errors.add :start_date, :activerecord_error_invalid end + + if IssueStatus.find_by_id( @attributes['status_id']).is_closed? && children.find { |i| !i.closed? } + errors.add :status, "Can't close parent issue while on of the children still is open." + end + + unless children.empty? + children_max_fixed_version = children.select { |i| i.fixed_version } .max { |a,b| a.fixed_version <=> b.fixed_version } + if @attributes['fixed_version_id'] && children_max_fixed_version + if Version.find_by_id( @attributes['fixed_version_id']) < children_max_fixed_version.fixed_version + errors.add :fixed_version, "Can't set target version of parent issue lower than any of the children." + end + end + end end def validate_on_create @@ -140,7 +148,7 @@ class Issue < ActiveRecord::Base @current_journal.details << JournalDetail.new(:property => 'attr', :prop_key => c, :old_value => @issue_before_change.send(c), - :value => send(c)) unless send(c)==@issue_before_change.send(c) + :value => send(c)) unless send(c)==@issue_before_change.send(c) || (!self.edge? && %w(status_id priority_id fixed_version_id start_date due_date done_ratio estimated_hours).include?(c)) } # custom fields changes custom_values.each {|c| @@ -153,6 +161,7 @@ class Issue < ActiveRecord::Base } @current_journal.save end + # Save the issue even if the journal is not saved (because empty) true end @@ -164,6 +173,14 @@ class Issue < ActiveRecord::Base # Update start/due dates of following issues relations_from.each(&:set_issue_to_dates) + # Set default status of parent if new status openes the issue. + relations_from.each do |relation| + if relation.relation_type == IssueRelation::TYPE_PARENTS + relation.set_issue_to_default_status + relation.set_issue_to_target_version + end + end + # Close duplicates if the issue was closed if @issue_before_change && !@issue_before_change.closed? && self.closed? duplicates.each do |duplicate| @@ -177,7 +194,7 @@ class Issue < ActiveRecord::Base end end end - + def init_journal(user, notes = "") @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes) @issue_before_change = self.clone @@ -189,6 +206,11 @@ class Issue < ActiveRecord::Base @current_journal end + def priority_id=(pid) + self.priority = nil + write_attribute(:priority_id, pid) + end + # Return true if the issue is closed, otherwise false def closed? self.status.is_closed? @@ -240,7 +262,11 @@ class Issue < ActiveRecord::Base # Returns the due date or the target due date if any # Used on gantt chart def due_before - due_date || (fixed_version ? fixed_version.effective_date : nil) + due_date || (fixed_version ? fixed_version.effective_date : 0) + end + + def duration1 + (start_date && due_date) ? (due_date - start_date + 1) : 0 end def duration @@ -257,6 +283,137 @@ class Issue < ActiveRecord::Base end end + def done_ratio + if children? + @total_planned_days ||= 0 + @total_actual_days ||= 0 + children.each do |child| # from every subtask get the total number of days and the number of days already "worked" + planned_days = child.duration1 + actual_days = child.done_ratio ? (planned_days * child.done_ratio / 100).floor : 0 + @total_planned_days += planned_days + @total_actual_days += actual_days + end + @total_done_ratio = @total_planned_days != 0 ? (@total_actual_days * 100 / @total_planned_days).floor : 0 + else + read_attribute(:done_ratio) + end + end + + def estimated_hours + if children? + is_set = false + children.each do |child| + if child.estimated_hours + if is_set + @est_hours += child.estimated_hours + else + @est_hours = child.estimated_hours + is_set = true + end + end + end + @est_hours + else + read_attribute(:estimated_hours) + end + end + + def start_date + calculate 'start_date' + end + + def due_date + calculate 'due_date' + end + + def calculate(field) + if children? + @value = eval "children.first.#{field}" + children.each do |child| + case field + when 'start_date' + if child.start_date && (!@value || @value > child.start_date) + @value = child.start_date + end + when 'due_date' + if child.due_date && (!@value || @value < child.due_date) + @value = child.due_date + end + end + end + @value + else + read_attribute(eval(":#{field}")) + end + end + + def children? + children != [] + end + + def children + children = [] + relations_to.each do |relation| + if relation.relation_type == IssueRelation::TYPE_PARENTS + children << relation.other_issue(self) + end + end + children + end + + def parent + relations_from.each do |relation| + if relation.relation_type == IssueRelation::TYPE_PARENTS + return parent = relation.other_issue(self) + end + end + return nil + end + + def parent? + parent != nil + end + + def root? + !parent? + end + + def ancestors(issue=self) + a = [] + return a if ! issue.parent? + (a << issue.parent) | ancestors(issue.parent) + end + + #First level tasks have hierarchical level = 1 and so on + def hierarchical_level(issue=self) + issue.parent? ? (1 + hierarchical_level(issue.parent)) : 1 + end + + def edge? + relations_to.each do |relation| + if relation.relation_type == IssueRelation::TYPE_PARENTS + return false + end + end + return true + end + + def orphan? + root? && edge? + end + + def self.find_with_parents( *args) + issues = find( *args) + return [] if issues.empty? + issues.each do |i| + while not i.root? + issues += [ i.parent ] + i = i.parent + end + end + issues.uniq + end + def to_s "#{tracker} ##{id}: #{subject}" end diff --git a/app/models/issue_relation.rb b/app/models/issue_relation.rb index 49329e0..bc66321 100644 --- a/app/models/issue_relation.rb +++ b/app/models/issue_relation.rb @@ -17,23 +17,26 @@ class IssueRelation < ActiveRecord::Base belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id' - belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id' + belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id' TYPE_RELATES = "relates" TYPE_DUPLICATES = "duplicates" TYPE_BLOCKS = "blocks" TYPE_PRECEDES = "precedes" + TYPE_PARENTS = "parents" - TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 }, - TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 }, - TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 }, - TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 }, + TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 }, + TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 }, + TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 }, + TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 }, + TYPE_PARENTS => { :name => :label_parents, :sym_name => :label_children, :order => 5 }, }.freeze - validates_presence_of :issue_from, :issue_to, :relation_type - validates_inclusion_of :relation_type, :in => TYPES.keys - validates_numericality_of :delay, :allow_nil => true - validates_uniqueness_of :issue_to_id, :scope => :issue_from_id + validates_presence_of :issue_from, :issue_to, :relation_type + validates_inclusion_of :relation_type, :in => TYPES.keys + validates_uniqueness_of :relation_type, :scope => [:issue_from_id, :relation_type], :message=>l(:error_issue_can_have_only_one_parent) + validates_numericality_of :delay, :allow_nil => true + validates_uniqueness_of :issue_to_id, :scope => :issue_from_id def validate if issue_from && issue_to @@ -57,8 +60,30 @@ class IssueRelation < ActiveRecord::Base else self.delay = nil end + + # Set new status to parent if new status openes the issue. + if TYPE_PARENTS == relation_type + set_issue_to_default_status + set_issue_to_target_version + end + set_issue_to_dates end + + def set_issue_to_default_status + if issue_to.closed? && !issue_from.closed? + issue_to.update_attribute :status, IssueStatus.default + end + end + + def set_issue_to_target_version + if issue_to.fixed_version.nil? && issue_from.fixed_version or + ( issue_to.fixed_version && issue_from.fixed_version and + issue_to.fixed_version.project == issue_from.fixed_version.project and + issue_to.fixed_version < issue_from.fixed_version ) + issue_to.update_attribute :fixed_version, issue_from.fixed_version + end + end def set_issue_to_dates soonest_start = self.successor_soonest_start diff --git a/app/models/version.rb b/app/models/version.rb index e379f4b..554ebd8 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -26,6 +26,8 @@ class Version < ActiveRecord::Base validates_length_of :name, :maximum => 60 validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => 'activerecord_error_not_a_date', :allow_nil => true + include Comparable + def start_date effective_date end diff --git a/app/views/issues/_edit.rhtml b/app/views/issues/_edit.rhtml index 2c7a428..b52940a 100644 --- a/app/views/issues/_edit.rhtml +++ b/app/views/issues/_edit.rhtml @@ -15,7 +15,7 @@ <%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %> <% end %> - <% if authorize_for('timelog', 'edit') %> + <% if authorize_for('timelog', 'edit') && @issue.edge? %>
<%= l(:button_log_time) %> <% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
diff --git a/app/views/issues/_form.rhtml b/app/views/issues/_form.rhtml index 419536f..931d8c6 100644 --- a/app/views/issues/_form.rhtml +++ b/app/views/issues/_form.rhtml @@ -1,3 +1,11 @@ + + <% if @issue.new_record? %>

<%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %>

<%= observe_field :issue_tracker_id, :url => { :action => :new }, @@ -16,13 +24,14 @@
-<% if @issue.new_record? || @allowed_statuses.any? %> +<% if (@issue.new_record? || @allowed_statuses.any?) %>

<%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %>

<% else %>

<%= @issue.status.name %>

<% end %>

<%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %>

+

<%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %>

<%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %> <%= prompt_to_remote(l(:label_issue_category_new), @@ -35,10 +44,30 @@

+<% if @issue.edge? %>

<%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %>

<%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %>

<%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %>

<%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>

+<% else %> +

<%= format_date(@issue.start_date) %>

+

<%= format_date(@issue.due_date) %>

+

<%= "#{@issue.done_ratio}%" %>

+<% end %> +<%= content_tag( :input, {}, + :id => :issue_parent_issue_id, + :type => :hidden, + :name => 'issue_parent_issue_id', :value => @parent_issue ? @parent_issue.id : "") %> +

+<% if authorize_for( 'issues', 'add_subissue') %> + <%= text_field_with_auto_complete( :issue, :parent, + { :name => 'issue_parent', :value => @parent_issue || "" }, + :url => { :action => 'auto_complete_for_issue_parent', :project_id => @project}, + :after_update_element => 'setParentIssueValue') %> +<% else %> + <%= @parent_issue || "-" %> +<% end %> +

diff --git a/app/views/issues/_form_update.rhtml b/app/views/issues/_form_update.rhtml index 25e81a7..4d7b4d6 100644 --- a/app/views/issues/_form_update.rhtml +++ b/app/views/issues/_form_update.rhtml @@ -3,7 +3,11 @@

<%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %>

+<% if @issue.edge? %>

<%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>

+<% else %> +

<%= "#{@issue.done_ratio}%" %>

+<% end %> <%= content_tag('p', f.select(:fixed_version_id, (@project.versions.sort.collect {|v| [v.name, v.id]}), { :include_blank => true })) unless @project.versions.empty? %> diff --git a/app/views/issues/_list.rhtml b/app/views/issues/_list.rhtml index b423578..1eb1b77 100644 --- a/app/views/issues/_list.rhtml +++ b/app/views/issues/_list.rhtml @@ -10,7 +10,8 @@ <% end %> - <% issues.each do |issue| -%> + <% issues.each do |issue| + next if !@show_all_issues && (issue.hierarchical_level != 1) -%> "> <%= check_box_tag("ids[]", issue.id, false, :id => nil) %> <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %> diff --git a/app/views/issues/_show_children.rhtml b/app/views/issues/_show_children.rhtml new file mode 100644 index 0000000..e7e4775 --- /dev/null +++ b/app/views/issues/_show_children.rhtml @@ -0,0 +1,13 @@ +<% +issues = [@issue] +issues += @issue.children +issues.each do |issue| + is_parent = (issue == @issue) + #used in queries_helper.rb when deciding which icon will be shown (if + or -) + @show_children = is_parent -%> + "> + <%= check_box_tag("ids[]", issue.id, false, :id => nil) %> + <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %> + <% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %> + +<% end -%> diff --git a/app/views/issues/context_menu.rhtml b/app/views/issues/context_menu.rhtml index 671655d..ab4bd35 100644 --- a/app/views/issues/context_menu.rhtml +++ b/app/views/issues/context_menu.rhtml @@ -15,7 +15,6 @@
  • <%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)}, :class => 'icon-edit', :disabled => !@can[:edit] %>
  • <% end %> -
  • <%= l(:field_priority) %>
      @@ -64,6 +63,7 @@
  • <% end -%> + <% if @issue && @issue.edge? %>
  • <%= l(:field_done_ratio) %>
      @@ -73,7 +73,7 @@ <% end -%>
  • - + <% end %> <% if !@issue.nil? %>
  • <%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue}, :class => 'icon-copy', :disabled => !@can[:copy] %>
  • diff --git a/app/views/issues/index.rfpdf b/app/views/issues/index.rfpdf index d5a8d3c..15f8282 100644 --- a/app/views/issues/index.rfpdf +++ b/app/views/issues/index.rfpdf @@ -24,6 +24,7 @@ pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1) pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1) pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1) + pdf.Cell(15, row_height, l(:field_parent_issue), 0, 0, 'L', 1) pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1) pdf.Line(10, pdf.GetY, 287, pdf.GetY) pdf.Ln @@ -42,6 +43,7 @@ pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1) pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.name : '', 0, 0, 'L', 1) pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1) + pdf.Cell(15, row_height, issue.parent ? "# #{issue.parent.id}" : '', 0, 0, 'L', 1) pdf.MultiCell(0, row_height, (@project == issue.project ? issue.subject : "#{issue.project.name} - #{issue.subject}")) pdf.Line(10, pdf.GetY, 287, pdf.GetY) pdf.SetY(pdf.GetY() + 1) diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml index 6d6c41a..e518e2d 100644 --- a/app/views/issues/show.rhtml +++ b/app/views/issues/show.rhtml @@ -1,5 +1,9 @@
    -<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %> +<%= link_to_if_authorized(l(:button_add_subissue), + { :controller => 'issues', :action => 'add_subissue', + :project_id => @project.id, :issue_parent_issue_id => @issue.id }, + :class => 'icon icon-add') %> +<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue, :project_id => @project }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %> <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %> <%= watcher_tag(@issue, User.current) %> <%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %> @@ -51,8 +55,8 @@ if (n > 1) n = 0 %> - <%end -end %> + <% end %> +<% end %> <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %> diff --git a/app/views/projects/roadmap.rhtml b/app/views/projects/roadmap.rhtml index 0778d81..cd95e4a 100644 --- a/app/views/projects/roadmap.rhtml +++ b/app/views/projects/roadmap.rhtml @@ -10,19 +10,19 @@ <%= render :partial => 'versions/overview', :locals => {:version => version} %> <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %> - <% issues = version.fixed_issues.find(:all, - :include => [:status, :tracker], - :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"], - :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") unless @selected_tracker_ids.empty? + <% + unless @selected_tracker_ids.empty? + issues = version.fixed_issues.find(:all, + :include => [:status, :tracker], + :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"], + :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") + issues = Issue.find_with_parents( issues.collect { |i| i.id }) + end issues ||= [] %> <% if issues.size > 0 %> <% end %> <% end %> diff --git a/app/views/versions/show.rhtml b/app/views/versions/show.rhtml index 7e52645..ef91b89 100644 --- a/app/views/versions/show.rhtml +++ b/app/views/versions/show.rhtml @@ -31,16 +31,9 @@ <%= render :partial => 'versions/overview', :locals => {:version => @version} %> <%= render(:partial => "wiki/content", :locals => {:content => @version.wiki_page.content}) if @version.wiki_page %> -<% issues = @version.fixed_issues.find(:all, - :include => [:status, :tracker], - :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") %> -<% if issues.size > 0 %> +<% if @issues.size > 0 %> <% end %>
    diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 diff --git a/config/boot.rb b/config/boot.rb index c20cf35..6a30b54 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,7 +1,7 @@ # Don't change this file! # Configure your app in config/environment.rb and config/environments/*.rb -RAILS_ROOT = File.expand_path("#{File.dirname(__FILE__)}/..") unless defined?(RAILS_ROOT) +RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) module Rails class << self @@ -82,14 +82,14 @@ module Rails def load_rubygems require 'rubygems' - - unless rubygems_version >= '0.9.4' - $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.) + min_version = '1.1.1' + unless rubygems_version >= min_version + $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) exit 1 end rescue LoadError - $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org) + $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) exit 1 end diff --git a/config/deploy.rb b/config/deploy.rb new file mode 100644 diff --git a/config/environments/warecorp_redmine.rb b/config/environments/warecorp_redmine.rb new file mode 100644 diff --git a/config/environments/warecorp_redmine_test.rb b/config/environments/warecorp_redmine_test.rb new file mode 100644 diff --git a/db/.gitignore b/db/.gitignore new file mode 100644 diff --git a/db/migrate/20081125140453_add_calendar_startday_pref.rb.rb b/db/migrate/20081125140453_add_calendar_startday_pref.rb.rb new file mode 100644 diff --git a/files/.gitignore b/files/.gitignore new file mode 100644 diff --git a/lang/bg.yml b/lang/bg.yml index 17afe9e..22055cf 100644 --- a/lang/bg.yml +++ b/lang/bg.yml @@ -119,6 +119,7 @@ field_due_date: Крайна дата field_assigned_to: Възложена на field_priority: Приоритет field_fixed_version: Планувана версия +field_parent_issue: Child of field_user: Потребител field_role: Роля field_homepage: Начална страница @@ -396,6 +397,8 @@ label_blocks: блокира label_blocked_by: блокирана от label_precedes: предшества label_follows: изпълнява се след +label_parents: child of +label_children: parent of label_end_to_start: end to start label_end_to_end: end to end label_start_to_start: start to start @@ -618,6 +621,7 @@ label_overall_activity: Цялостна дейност setting_default_projects_public: Новите проекти са публични по подразбиране error_scm_annotate: "Обектът не съществува или не може да бъде анотиран." label_planning: Планиране +error_issue_can_have_only_one_parent: allows only one relationship (a task can have only one parent). text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" @@ -692,3 +696,6 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/ca.yml b/lang/ca.yml index 4179aa4..e4e2ae6 100644 --- a/lang/ca.yml +++ b/lang/ca.yml @@ -693,3 +693,10 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +label_parents: child of +label_children: parent of +field_parent_issue: Child of +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). +field_calendar_firstday: First day of week diff --git a/lang/cs.yml b/lang/cs.yml index 189f472..b6a9410 100644 --- a/lang/cs.yml +++ b/lang/cs.yml @@ -133,6 +133,7 @@ field_due_date: Uzavřít do field_assigned_to: Přiřazeno field_priority: Priorita field_fixed_version: Přiřazeno k verzi +field_parent_issue: Child of field_user: Uživatel field_role: Role field_homepage: Homepage @@ -453,6 +454,8 @@ label_blocks: bloků label_blocked_by: zablokován label_precedes: předchází label_follows: následuje +label_parents: child of +label_children: parent of label_end_to_start: od konce do začátku label_end_to_end: od konce do konce label_start_to_start: od začátku do začátku @@ -623,6 +626,7 @@ enumeration_doc_categories: Kategorie dokumentů enumeration_activities: Aktivity (sledování času) error_scm_annotate: "Položka neexistuje nebo nemůže být komentována." label_planning: Plánování +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" @@ -697,3 +701,6 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/da.yml b/lang/da.yml index d1b5697..597d6fc 100644 --- a/lang/da.yml +++ b/lang/da.yml @@ -133,6 +133,7 @@ field_due_date: Deadline field_assigned_to: Tildelt til field_priority: Prioritet field_fixed_version: Planlagt version +field_parent_issue: Child of field_user: Bruger field_role: Rolle field_homepage: Hjemmeside @@ -463,6 +464,8 @@ label_blocks: blokerer label_blocked_by: blokeret af label_precedes: kommer før label_follows: følger +label_parents: child of +label_children: parent of label_end_to_start: slut til start label_end_to_end: slut til slut label_start_to_start: start til start @@ -524,6 +527,7 @@ label_preferences: Indstillinger label_chronological_order: I kronologisk rækkefølge label_reverse_chronological_order: I omvendt kronologisk rækkefølge label_planning: Planlægning +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). label_incoming_emails: Indkommende e-mails label_generate_key: Generer en nøgle label_issue_watchers: Overvågere @@ -693,3 +697,6 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/de.yml b/lang/de.yml index b23edd9..174e758 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -133,6 +133,7 @@ field_due_date: Abgabedatum field_assigned_to: Zugewiesen an field_priority: Priorität field_fixed_version: Zielversion +field_parent_issue: Child of field_user: Benutzer field_role: Rolle field_homepage: Projekt-Homepage @@ -514,6 +515,8 @@ label_blocks: Blockiert label_blocked_by: Blockiert durch label_precedes: Vorgänger von label_follows: folgt +label_parents: child of +label_children: parent of label_end_to_start: Ende - Anfang label_end_to_end: Ende - Ende label_start_to_start: Anfang - Anfang @@ -694,3 +697,8 @@ default_activity_development: Entwicklung enumeration_issue_priorities: Ticket-Prioritäten enumeration_doc_categories: Dokumentenkategorien enumeration_activities: Aktivitäten (Zeiterfassung) + +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/en.yml b/lang/en.yml index 80751f0..6d1aa38 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -133,6 +133,7 @@ field_due_date: Due date field_assigned_to: Assigned to field_priority: Priority field_fixed_version: Target version +field_parent_issue: Child of field_user: User field_role: Role field_homepage: Homepage @@ -184,7 +185,8 @@ field_searchable: Searchable field_default_value: Default value field_comments_sorting: Display comments field_parent_title: Parent page - +field_calendar_firstday: First day of week + setting_app_title: Application title setting_app_subtitle: Application subtitle setting_welcome_text: Welcome text @@ -514,6 +516,8 @@ label_blocks: blocks label_blocked_by: blocked by label_precedes: precedes label_follows: follows +label_parents: child of +label_children: parent of label_end_to_start: end to start label_end_to_end: end to end label_start_to_start: start to start @@ -590,6 +594,7 @@ button_create: Create button_test: Test button_edit: Edit button_add: Add +button_add_subissue: Add sub-issue button_change: Change button_apply: Apply button_clear: Clear @@ -627,6 +632,7 @@ text_select_mail_notifications: Select actions for which email notifications sho text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 means no restriction text_project_destroy_confirmation: Are you sure you want to delete this project and related data ? +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' text_workflow_edit: Select a role and a tracker to edit the workflow text_are_you_sure: Are you sure ? diff --git a/lang/es.yml b/lang/es.yml index 74c3d49..b16d0ab 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -137,8 +137,6 @@ field_hours: Horas field_identifier: Identificador field_is_closed: Petición resuelta field_is_default: Estado por defecto -field_is_filter: Usado como filtro -field_is_for_all: Para todos los proyectos field_is_in_chlog: Consultar las peticiones en el histórico field_is_in_roadmap: Consultar las peticiones en el roadmap field_is_public: Público @@ -185,6 +183,8 @@ field_url: URL field_user: Usuario field_value: Valor field_version: Versión +field_parent: Proyecto padre +field_parent_issue: Child of general_csv_decimal_separator: ',' general_csv_encoding: ISO-8859-15 general_csv_separator: ';' @@ -501,6 +501,8 @@ label_wiki_page_plural: Wiki páginas label_workflow: Flujo de trabajo label_year: Año label_yesterday: ayer +label_parents: child of +label_children: parent of mail_body_account_activation_request: "Un nuevo usuario (%s) ha sido registrado. Esta cuenta está pendiende de aprobación" mail_body_account_information: Información sobre su cuenta mail_body_account_information_external: Puede usar su cuenta "%s" para conectarse. @@ -592,21 +594,9 @@ project_module_repository: Repositorio project_module_time_tracking: Control de tiempo project_module_wiki: Wiki setting_activity_days_default: Días a mostrar en la actividad de proyecto -setting_app_subtitle: Subtítulo de la aplicación -setting_app_title: Título de la aplicación -setting_attachment_max_size: Tamaño máximo del fichero -setting_autofetch_changesets: Autorellenar los commits del repositorio -setting_autologin: Conexión automática -setting_bcc_recipients: Ocultar las copias de carbón (bcc) -setting_commit_fix_keywords: Palabras clave para la corrección -setting_commit_logs_encoding: Codificación de los mensajes de commit -setting_commit_ref_keywords: Palabras clave para la referencia -setting_cross_project_issue_relations: Permitir relacionar peticiones de distintos proyectos -setting_date_format: Formato de la fecha -setting_default_language: Idioma por defecto -setting_default_projects_public: Los proyectos nuevos son públicos por defecto -setting_display_subprojects_issues: Mostrar peticiones de un subproyecto en el proyecto padre por defecto -setting_emails_footer: Pie de mensajes + +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). + setting_enabled_scm: Activar SCM setting_feeds_limit: Límite de contenido para sindicación setting_gravatar_enabled: Usar iconos de usuario (Gravatar) @@ -677,3 +667,22 @@ text_user_mail_option: "En los proyectos no seleccionados, sólo recibirá notif text_user_wrote: '%s escribió:' text_wiki_destroy_confirmation: ¿Seguro que quiere borrar el wiki y todo su contenido? text_workflow_edit: Seleccionar un flujo de trabajo para actualizar +field_is_filter: Used as a filter +setting_default_language: Default language +setting_emails_footer: Emails footer +setting_app_subtitle: Application subtitle +button_add_subissue: Add sub-issue +setting_cross_project_issue_relations: Allow cross-project issue relations +setting_autofetch_changesets: Autofetch commits +setting_attachment_max_size: Attachment max. size +setting_app_title: Application title +setting_date_format: Date format +setting_default_projects_public: New projects are public by default +setting_commit_fix_keywords: Fixing keywords +setting_autologin: Autologin +setting_display_subprojects_issues: Display subprojects issues on main projects by default +setting_bcc_recipients: Blind carbon copy recipients (bcc) +setting_commit_ref_keywords: Referencing keywords +field_is_for_all: For all projects +setting_commit_logs_encoding: Commit messages encoding +field_calendar_firstday: First day of week diff --git a/lang/fi.yml b/lang/fi.yml index b028fd1..55677ea 100644 --- a/lang/fi.yml +++ b/lang/fi.yml @@ -692,3 +692,10 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +label_parents: child of +label_children: parent of +field_parent_issue: Child of +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). +field_calendar_firstday: First day of week diff --git a/lang/fr.yml b/lang/fr.yml index 1c2bce3..05351b1 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -133,6 +133,7 @@ field_due_date: Date d'échéance field_assigned_to: Assigné à field_priority: Priorité field_fixed_version: Version cible +field_parent_issue: Child of field_user: Utilisateur field_role: Rôle field_homepage: Site web @@ -514,6 +515,8 @@ label_blocks: bloque label_blocked_by: bloqué par label_precedes: précède label_follows: suit +label_parents: child of +label_children: parent of label_end_to_start: fin à début label_end_to_end: fin à fin label_start_to_start: début à début @@ -627,6 +630,7 @@ text_select_mail_notifications: Actions pour lesquelles une notification par e-m text_regexp_info: ex. ^[A-Z0-9]+$ text_min_max_length_info: 0 pour aucune restriction text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et toutes ses données ? +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Ses sous-projets: %s seront également supprimés.' text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow text_are_you_sure: Etes-vous sûr ? @@ -694,3 +698,5 @@ default_activity_development: Développement enumeration_issue_priorities: Priorités des demandes enumeration_doc_categories: Catégories des documents enumeration_activities: Activités (suivi du temps) +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/he.yml b/lang/he.yml index e32259b..7f3dd68 100644 --- a/lang/he.yml +++ b/lang/he.yml @@ -121,6 +121,7 @@ field_due_date: תאריך סיום field_assigned_to: מוצב ל field_priority: עדיפות field_fixed_version: גירסאת יעד +field_parent_issue: Child of field_user: מתשמש field_role: תפקיד field_homepage: דף הבית @@ -618,6 +619,7 @@ label_overall_activity: פעילות כוללת setting_default_projects_public: פרויקטים חדשים הינם פומביים כברירת מחדל error_scm_annotate: "הכניסה לא קיימת או שלא ניתן לתאר אותה." label_planning: תכנון +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" @@ -692,3 +694,8 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +label_parents: child of +label_children: parent of +field_calendar_firstday: First day of week diff --git a/lang/hu.yml b/lang/hu.yml index 61831ce..d4b7d2d 100644 --- a/lang/hu.yml +++ b/lang/hu.yml @@ -693,3 +693,10 @@ label_example: Példa text_repository_usernames_mapping: "Állítsd be a felhasználó összerendeléseket a Redmine, és a tároló logban található felhasználók között.\nAz azonos felhasználó nevek összerendelése automatikusan megtörténik." permission_edit_own_messages: Saját üzenetek szerkesztése permission_delete_own_messages: Saját üzenetek törlése + +button_add_subissue: Add sub-issue +label_parents: child of +label_children: parent of +field_parent_issue: Child of +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). +field_calendar_firstday: First day of week diff --git a/lang/it.yml b/lang/it.yml index 2e25987..0b7cd70 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -119,6 +119,7 @@ field_due_date: Data ultima field_assigned_to: Assegnato a field_priority: Priorita' field_fixed_version: Versione prevista +field_parent_issue: Child of field_user: Utente field_role: Ruolo field_homepage: Homepage @@ -396,6 +397,8 @@ label_blocks: blocchi label_blocked_by: bloccato da label_precedes: precede label_follows: segue +label_parents: child of +label_children: parent of label_end_to_start: end to start label_end_to_end: end to end label_start_to_start: start to start @@ -618,6 +621,7 @@ label_overall_activity: Attività generale setting_default_projects_public: I nuovi progetti sono pubblici per default error_scm_annotate: "L'oggetto non esiste o non può essere annotato." label_planning: Pianificazione +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Anche i suoi sottoprogetti: %s verranno eliminati.' label_and_its_subprojects: %s ed i suoi sottoprogetti mail_body_reminder: "%d segnalazioni che ti sono state assegnate scadranno nei prossimi %d giorni:" @@ -692,3 +696,6 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/ja.yml b/lang/ja.yml index 8d106bf..cfad484 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -120,6 +120,7 @@ field_due_date: 期限日 field_assigned_to: 担当者 field_priority: 優先度 field_fixed_version: Target version +field_parent_issue: Child of field_user: ユーザ field_role: 役割 field_homepage: ホームページ @@ -397,6 +398,8 @@ label_blocks: ブロックしている label_blocked_by: ブロックされている label_precedes: 先行する label_follows: 後続する +label_parents: child of +label_children: parent of label_end_to_start: end to start label_end_to_end: end to end label_start_to_start: start to start @@ -620,6 +623,7 @@ label_overall_activity: 全ての活動 setting_default_projects_public: デフォルトで新しいプロジェクトは公開にする error_scm_annotate: "エントリが存在しない、もしくはアノテートできません。" label_planning: 計画 +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'サブプロジェクト %s も削除されます。' label_and_its_subprojects: %s とサブプロジェクト mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" @@ -693,3 +697,6 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/ko.yml b/lang/ko.yml index 65a5f44..966c050 100644 --- a/lang/ko.yml +++ b/lang/ko.yml @@ -121,6 +121,7 @@ field_due_date: 완료 기한 field_assigned_to: 담당자 field_priority: 우선순위 field_fixed_version: 목표 버전 +field_parent_issue: Child of field_user: 유저 field_role: 역할 field_homepage: 홈페이지 @@ -403,6 +404,8 @@ label_blocks: 다음 이슈가 해결을 막고 있음. label_blocked_by: 막고 있는 이슈 label_precedes: 다음 이슈보다 앞서서 처리해야 함. label_follows: 선처리 이슈 +label_parents: child of +label_children: parent of label_end_to_start: end to start label_end_to_end: end to end label_start_to_start: start to start @@ -618,6 +621,7 @@ label_overall_activity: 전체 진행 상황 setting_default_projects_public: 신규 프로젝트를 Public 으로 설정 error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: 계획(Planning) +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" @@ -692,3 +696,6 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/lt.yml b/lang/lt.yml index 535f041..0975efe 100644 --- a/lang/lt.yml +++ b/lang/lt.yml @@ -127,6 +127,7 @@ field_due_date: Užbaigimo data field_assigned_to: Paskirtas field_priority: Prioritetas field_fixed_version: Target version +field_parent_issue: Child of field_user: Vartotojas field_role: Vaidmuo field_homepage: Pagrindinis puslapis @@ -415,6 +416,8 @@ label_blocks: blokai label_blocked_by: blokuotas label_precedes: įvyksta pirma label_follows: seka +label_parents: child of +label_children: parent of label_end_to_start: užbaigti, kad pradėti label_end_to_end: užbaigti, kad pabaigti label_start_to_start: pradėkite pradėti @@ -619,6 +622,7 @@ label_overall_activity: Visa veikla setting_default_projects_public: Naujas projektas viešas pagal nutylėjimą error_scm_annotate: "Įrašas neegzituoja arba negalima jo atvaizduoti." label_planning: Planavimas +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Šis(ie) subprojektas(ai): %s taip pat bus ištrintas(i).' label_and_its_subprojects: %s projektas ir jo subprojektai @@ -694,3 +698,8 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week + diff --git a/lang/nl.yml b/lang/nl.yml index f138fc8..24bba2c 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -119,6 +119,7 @@ field_due_date: Verwachte datum gereed field_assigned_to: Toegewezen aan field_priority: Prioriteit field_fixed_version: Doel versie +field_parent_issue: Child of field_user: Gebruiker field_role: Rol field_homepage: Homepage @@ -396,6 +397,8 @@ label_blocks: blokkeert label_blocked_by: geblokkeerd door label_precedes: gaat vooraf aan label_follows: volgt op +label_parents: child of +label_children: parent of label_end_to_start: eind tot start label_end_to_end: eind tot eind label_start_to_start: start tot start @@ -620,6 +623,7 @@ label_overall_activity: Activiteit setting_default_projects_public: Nieuwe projecten zijn standaard publiek error_scm_annotate: "Er kan geen commentaar toegevoegd worden." label_planning: Planning +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'De subprojecten: %s zullen ook verwijderd worden.' label_and_its_subprojects: %s en zijn subprojecten. mail_body_reminder: "%d issue(s) die aan u toegewezen zijn en voldaan moeten zijn in de komende %d dagen:" @@ -694,3 +698,7 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week + diff --git a/lang/no.yml b/lang/no.yml index db4a320..9512b60 100644 --- a/lang/no.yml +++ b/lang/no.yml @@ -132,6 +132,7 @@ field_due_date: Frist field_assigned_to: Tildelt til field_priority: Prioritet field_fixed_version: Mål-versjon +field_parent_issue: Child of field_user: Bruker field_role: Rolle field_homepage: Hjemmeside @@ -455,6 +456,8 @@ label_blocks: blokkerer label_blocked_by: blokkert av label_precedes: kommer før label_follows: følger +label_parents: child of +label_children: parent of label_end_to_start: slutt til start label_end_to_end: slutt til slutt label_start_to_start: start til start @@ -563,6 +566,7 @@ text_select_mail_notifications: Velg hendelser som skal varsles med e-post. text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 betyr ingen begrensning text_project_destroy_confirmation: Er du sikker på at du vil slette dette prosjekter og alle relatert data ? +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Underprojekt(ene): %s vil også bli slettet.' text_workflow_edit: Velg en rolle og en sakstype for å endre arbeidsflyten text_are_you_sure: Er du sikker ? @@ -693,3 +697,6 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/pl.yml b/lang/pl.yml index 64457b5..7cba1e4 100644 --- a/lang/pl.yml +++ b/lang/pl.yml @@ -221,6 +221,7 @@ gui_validation_error: 1 błąd gui_validation_error_plural234: %d błędy gui_validation_error_plural5: %d błędów gui_validation_error_plural: %d błędów + label_activity: Aktywność label_add_another_file: Dodaj kolejny plik label_add_note: Dodaj notatkę @@ -711,3 +712,9 @@ text_user_wrote: '%s 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 +field_parent_issue: Child of +label_parents: child of +label_children: parent of +button_add_subissue: Add sub-issue +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). +field_calendar_firstday: First day of week diff --git a/lang/pt-br.yml b/lang/pt-br.yml index b6fdf9d..13e74ac 100644 --- a/lang/pt-br.yml +++ b/lang/pt-br.yml @@ -132,6 +132,7 @@ field_due_date: Data prevista field_assigned_to: Atribuído para field_priority: Prioridade field_fixed_version: Versão +field_parent_issue: Filha de field_user: Usuário field_role: Papel field_homepage: Página inicial @@ -460,6 +461,8 @@ label_blocks: bloqueia label_blocked_by: bloqueado por label_precedes: precede label_follows: segue +label_parents: filha de +label_children: mãe de label_end_to_start: fim para o início label_end_to_end: fim para fim label_start_to_start: início para início @@ -693,3 +696,7 @@ label_example: Exemplo text_repository_usernames_mapping: "Seleciona ou atualiza os usuários do Redmine mapeando para cada usuário encontrado no log do repositório.\nUsuários com o mesmo login ou email no Redmine e no repositório serão mapeados automaticamente." permission_edit_own_messages: Editar próprias mensagens permission_delete_own_messages: Excluir próprias mensagens + +button_add_subissue: Add sub-issue +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). +field_calendar_firstday: First day of week diff --git a/lang/pt.yml b/lang/pt.yml index 604f5da..5b193b7 100644 --- a/lang/pt.yml +++ b/lang/pt.yml @@ -134,6 +134,7 @@ field_due_date: Data final field_assigned_to: Atribuído a field_priority: Prioridade field_fixed_version: Versão +field_parent_issue: Child of field_user: Utilizador field_role: Papel field_homepage: Página @@ -464,6 +465,8 @@ label_blocks: bloqueia label_blocked_by: bloqueado por label_precedes: precede label_follows: segue +label_parents: child of +label_children: parent of label_end_to_start: fim a início label_end_to_end: fim a fim label_start_to_start: início a início @@ -525,6 +528,7 @@ label_preferences: Preferências label_chronological_order: Em ordem cronológica label_reverse_chronological_order: Em ordem cronológica inversa label_planning: Planeamento +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). label_incoming_emails: E-mails a chegar label_generate_key: Gerar uma chave label_issue_watchers: Observadores @@ -694,3 +698,8 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week + diff --git a/lang/ro.yml b/lang/ro.yml index 6527442..c269a2a 100644 --- a/lang/ro.yml +++ b/lang/ro.yml @@ -119,6 +119,7 @@ field_due_date: Data finalizarii field_assigned_to: Atribuit pentru field_priority: Prioritate field_fixed_version: Target version +field_parent_issue: Child of field_user: Utilizator field_role: Rol field_homepage: Pagina principala @@ -394,6 +395,8 @@ label_blocks: blocuri label_blocked_by: blocat de label_precedes: precedes label_follows: follows +label_parents: child of +label_children: parent of label_end_to_start: de la sfarsit la capat label_end_to_end: de la sfarsit la sfarsit label_start_to_start: de la capat la capat @@ -618,6 +621,7 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" @@ -692,3 +696,8 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week + diff --git a/lang/ru.yml b/lang/ru.yml index 24b580d..5728c50 100644 --- a/lang/ru.yml +++ b/lang/ru.yml @@ -145,6 +145,7 @@ field_filename: Файл field_filesize: Размер field_firstname: Имя field_fixed_version: Версия +field_parent_issue: Child of field_hide_mail: Скрывать мой email field_homepage: Стартовая страница field_host: Компьютер @@ -725,3 +726,8 @@ text_user_mail_option: "Для невыбранных проектов, Вы б text_user_wrote: '%s написал(а):' text_wiki_destroy_confirmation: Вы уверены, что хотите удалить данную Wiki и все ее содержимое? text_workflow_edit: Выберите роль и трекер для редактирования последовательности состояний +button_add_subissue: Add sub-issue +label_parents: child of +label_children: parent of +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). +field_calendar_firstday: First day of week diff --git a/lang/sk.yml b/lang/sk.yml index 2237be5..ec0cd4f 100644 --- a/lang/sk.yml +++ b/lang/sk.yml @@ -698,3 +698,9 @@ label_example: Príklad permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages 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." +button_add_subissue: Add sub-issue +label_parents: child of +label_children: parent of +field_parent_issue: Child of +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). +field_calendar_firstday: First day of week diff --git a/lang/sr.yml b/lang/sr.yml index fdfa411..b129821 100644 --- a/lang/sr.yml +++ b/lang/sr.yml @@ -407,6 +407,8 @@ label_blocks: blokira label_blocked_by: blokiran od strane label_precedes: prethodi label_follows: sledi +label_parents: child of +label_children: parent of label_end_to_start: od kraja do početka label_end_to_end: od kraja do kraja label_start_to_start: od početka do pocetka @@ -693,3 +695,9 @@ setting_gravatar_enabled: Use Gravatar user icons permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages 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." + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week +field_parent_issue: Child of +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). + diff --git a/lang/sv.yml b/lang/sv.yml index b50fc0c..1e3290b 100644 --- a/lang/sv.yml +++ b/lang/sv.yml @@ -119,6 +119,7 @@ field_due_date: Färdigdatum field_assigned_to: Tilldelad field_priority: Prioritet field_fixed_version: Target version +field_parent_issue: Child of field_user: Användare field_role: Roll field_homepage: Hemsida @@ -396,6 +397,8 @@ label_blocks: blocks label_blocked_by: blocked by label_precedes: precedes label_follows: follows +label_parents: child of +label_children: parent of label_end_to_start: end to start label_end_to_end: end to end label_start_to_start: start to start @@ -619,6 +622,7 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" @@ -693,3 +697,7 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week + diff --git a/lang/th.yml b/lang/th.yml index 129982d..7bc4011 100644 --- a/lang/th.yml +++ b/lang/th.yml @@ -130,6 +130,7 @@ field_due_date: วันครบกำหนด field_assigned_to: มอบหมายให้ field_priority: ความสำคัญ field_fixed_version: รุ่น +field_parent_issue: Child of field_user: ผู้ใช้ field_role: บทบาท field_homepage: หน้าแรก @@ -452,6 +453,8 @@ label_blocks: กีดกัน label_blocked_by: กีดกันโดย label_precedes: นำหน้า label_follows: ตามหลัง +label_parents: child of +label_children: parent of label_end_to_start: จบ-เริ่ม label_end_to_end: จบ-จบ label_start_to_start: เริ่ม-เริ่ม @@ -560,6 +563,7 @@ text_select_mail_notifications: เลือกการกระทำที่ text_regexp_info: ตัวอย่าง ^[A-Z0-9]+$ text_min_max_length_info: 0 หมายถึงไม่จำกัด text_project_destroy_confirmation: คุณแน่ใจไหมว่าต้องการลบโครงการและข้อมูลที่เกี่ยวข้่อง ? +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'โครงการย่อย: %s จะถูกลบด้วย.' text_workflow_edit: เลือกบทบาทและการติดตาม เพื่อแก้ไขลำดับงาน text_are_you_sure: คุณแน่ใจไหม ? @@ -695,3 +699,7 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week + diff --git a/lang/tr.yml b/lang/tr.yml index ba9b4b6..2a29a02 100644 --- a/lang/tr.yml +++ b/lang/tr.yml @@ -693,3 +693,11 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +label_parents: child of +label_children: parent of +field_parent_issue: Child of +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). +field_calendar_firstday: First day of week + diff --git a/lang/uk.yml b/lang/uk.yml index 3934e9a..ebd467b 100644 --- a/lang/uk.yml +++ b/lang/uk.yml @@ -124,6 +124,7 @@ field_due_date: Дата виконання field_assigned_to: Призначена до field_priority: Пріоритет field_fixed_version: Target version +field_parent_issue: Child of field_user: Користувач field_role: Роль field_homepage: Домашня сторінка @@ -412,6 +413,8 @@ label_blocks: блокує label_blocked_by: заблоковане label_precedes: передує label_follows: наступний за +label_parents: child of +label_children: parent of label_end_to_start: з кінця до початку label_end_to_end: з кінця до кінця label_start_to_start: з початку до початку @@ -620,6 +623,7 @@ label_overall_activity: Overall activity setting_default_projects_public: New projects are public by default error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" @@ -694,3 +698,7 @@ label_example: Example text_repository_usernames_mapping: "Select ou 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." permission_edit_own_messages: Edit own messages permission_delete_own_messages: Delete own messages + + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/vn.yml b/lang/vn.yml index d1885d9..89ec6e9 100644 --- a/lang/vn.yml +++ b/lang/vn.yml @@ -695,3 +695,12 @@ permission_delete_own_messages: Xóa bài viết cá nhân label_example: Ví dụ text_repository_usernames_mapping: "Chọn hoặc cập nhật ánh xạ người dùng hệ thống với người dùng trong kho lưu trữ.\nNhững trường hợp trùng hợp về tên và email sẽ được tự động ánh xạ." permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +label_parents: child of +label_children: parent of +field_parent_issue: Child of +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). +permission_delete_won_messages: Delete own messages +field_calendar_firstday: First day of week + diff --git a/lang/zh-tw.yml b/lang/zh-tw.yml index cdc5417..17c425b 100644 --- a/lang/zh-tw.yml +++ b/lang/zh-tw.yml @@ -133,6 +133,7 @@ field_due_date: 完成日期 field_assigned_to: 分派給 field_priority: 優先權 field_fixed_version: 版本 +field_parent_issue: Child of field_user: 用戶 field_role: 角色 field_homepage: 網站首頁 @@ -514,6 +515,8 @@ label_blocks: 阻擋 label_blocked_by: 被阻擋 label_precedes: 優先於 label_follows: 跟隨於 +label_parents: child of +label_children: parent of label_end_to_start: 結束─開始 label_end_to_end: 結束─結束 label_start_to_start: 開始─開始 @@ -627,6 +630,7 @@ text_select_mail_notifications: 選擇欲寄送提醒通知郵件之動作 text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 代表「不限制」 text_project_destroy_confirmation: 您確定要刪除這個專案和其他相關資料? +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: '下列子專案: %s 將一併被刪除。' text_workflow_edit: 選擇角色與追蹤標籤以設定其工作流程 text_are_you_sure: 確定執行? @@ -694,3 +698,5 @@ default_activity_development: 開發 enumeration_issue_priorities: 項目優先權 enumeration_doc_categories: 文件分類 enumeration_activities: 活動 (時間追蹤) +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week diff --git a/lang/zh.yml b/lang/zh.yml index 6c04990..0ed9b6f 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -133,6 +133,7 @@ field_due_date: 完成日期 field_assigned_to: 指派给 field_priority: 优先级 field_fixed_version: 目标版本 +field_parent_issue: Child of field_user: 用户 field_role: 角色 field_homepage: 主页 @@ -514,6 +515,8 @@ label_blocks: 阻挡 label_blocked_by: 被阻挡 label_precedes: 优先于 label_follows: 跟随于 +label_parents: child of +label_children: parent of label_end_to_start: 结束-开始 label_end_to_end: 结束-结束 label_start_to_start: 开始-开始 @@ -627,6 +630,7 @@ text_select_mail_notifications: 选择需要发送邮件通知的动作 text_regexp_info: 例如:^[A-Z0-9]+$ text_min_max_length_info: 0 表示没有限制 text_project_destroy_confirmation: 您确信要删除这个项目以及所有相关的数据吗? +error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). text_subprojects_destroy_warning: '以下子项目也将被同时删除:%s' text_workflow_edit: 选择角色和跟踪标签来编辑工作流程 text_are_you_sure: 您确定? @@ -695,3 +699,7 @@ enumeration_issue_priorities: 问题优先级 enumeration_doc_categories: 文档类别 enumeration_activities: 活动(时间跟踪) permission_delete_own_messages: Delete own messages + +button_add_subissue: Add sub-issue +field_calendar_firstday: First day of week + diff --git a/lib/redmine.rb b/lib/redmine.rb index 42b074a..7117ed4 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -35,8 +35,8 @@ Redmine::AccessControl.map do |map| :queries => :index, :reports => :issue_report}, :public => true map.permission :add_issues, {:issues => :new} - map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment]} - map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]} + map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit, :destroy_attachment, :update_subject]} + map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy], :issues => :add_subissue} map.permission :add_issue_notes, {:issues => [:edit, :reply]} map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin diff --git a/lib/tasks/warecorp.rake b/lib/tasks/warecorp.rake new file mode 100644 diff --git a/log/.gitignore b/log/.gitignore new file mode 100644 diff --git a/public/.gitignore b/public/.gitignore new file mode 100644 diff --git a/public/images/contract.png b/public/images/contract.png new file mode 100644 index 0000000..69566ae Binary files /dev/null and b/public/images/contract.png differ diff --git a/public/images/expand.png b/public/images/expand.png new file mode 100644 index 0000000..ca4f30f Binary files /dev/null and b/public/images/expand.png differ diff --git a/public/images/white.png b/public/images/white.png new file mode 100644 index 0000000..6dab21b Binary files /dev/null and b/public/images/white.png differ diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 6256531..66832d7 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -669,3 +669,46 @@ td.username img.gravatar { #main { background: #fff; } #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;} } + +/***** Subtasks *****/ +/*blocks composed of icon (+, - or none), project identification (if there is one) and link to the task*/ +.issue-subject-level-1, .issue-subject-level-1 .issue-subject { +margin-left: 0em; +} + +.issue-subject-level-2, .issue-subject-level-2 .issue-subject { +margin-left: 2em; +} + +.issue-subject-level-3, .issue-subject-level-3 .issue-subject { +margin-left: 4em; +} + +.issue-subject-level-4, .issue-subject-level-4 .issue-subject { +margin-left: 6em; +} + +.issue-subject-level-5, .issue-subject-level-5 .issue-subject { +margin-left: 8em; +} + +.expanded-issue { + background-image: url(contract.png); + background-repeat: no-repeat; +} + +.contracted-issue { + background-image: url(expand.png); + background-repeat: no-repeat; +} + +.expand-icon, .contract-icon{ +/* position: absolute; */ + vertical-align: middle; +} + +/*text after the icon, which needs to be indented so that all its lines stay completely after the icon*/ +.issue-subject{ + padding-left: 1em; +} + diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 diff --git a/test/fixtures/versions.yml b/test/fixtures/versions.yml index 62c5e6f..cad63a6 100644 --- a/test/fixtures/versions.yml +++ b/test/fixtures/versions.yml @@ -23,4 +23,12 @@ versions_003: id: 3 description: Future version effective_date: +onlinestore_1_0: + created_on: 2006-07-19 21:00:33 +02:00 + name: "1.0" + project_id: 1 + updated_on: 2006-07-19 21:00:33 +02:00 + id: 4 + description: Future version + effective_date: \ No newline at end of file diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 6a4775c..1dbed33 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -22,10 +22,12 @@ require 'issues_controller' class IssuesController; def rescue_action(e) raise e end; end class IssuesControllerTest < Test::Unit::TestCase + fixtures :projects, :users, :roles, :members, + :queries, :issues, :issue_statuses, :versions, @@ -102,9 +104,9 @@ class IssuesControllerTest < Test::Unit::TestCase assert_response :success assert_template 'index.rhtml' assert_not_nil assigns(:issues) - assert_tag :tag => 'a', :content => /Can't print recipes/ - assert_tag :tag => 'a', :content => /Subproject issue/ - assert_tag :tag => 'a', :content => /Issue of a private subproject/ + assert_tag :tag => 'span', :content => /Can't print recipes/ + assert_tag :tag => 'span', :content => /Subproject issue/ + assert_tag :tag => 'span', :content => /Issue of a private subproject/ end def test_index_with_project_and_filter @@ -702,4 +704,24 @@ class IssuesControllerTest < Test::Unit::TestCase j = issue.journals.find(:first, :order => 'created_on DESC') assert_equal 'attachment', j.details.first.property end + + def test_new_child_issue + child_issue_subject = 'This is the test_new child issue' + parent_issue = Issue.find(1) + @request.session[:user_id] = 2 + post :new, :project_id => 1, + :issue_parent_issue_id => parent_issue.id, + :issue => {:tracker_id => 3, + :subject => 'This is the test_new child issue', + :description => 'This is the description', + :priority_id => 5, + :estimated_hours => '', + :custom_field_values => {'2' => 'Value for field 2'} } + + assert_redirected_to 'issues/show' + follow_redirect + assert_tag :tag => 'p', :content => "Related issues" + assert_tag :tag => 'a', :content => "#{parent_issue.tracker.name} ##{parent_issue.id}" + end + end diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index 12b4da3..b6ddca8 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -22,7 +22,7 @@ class IssueTest < Test::Unit::TestCase :trackers, :projects_trackers, :issue_statuses, :issue_categories, :enumerations, - :issues, + :issues, :versions, :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, :time_entries @@ -190,4 +190,146 @@ class IssueTest < Test::Unit::TestCase assert_nil Issue.find_by_id(1) assert_nil TimeEntry.find_by_issue_id(1) end + + def test_should_update_target_version_of_parent_issue + create_family_of_issues 'Target version of parent updates test' + + version_0_1 = Version.find( versions( :versions_001).id) + version_1_0 = Version.find( versions( :versions_002).id) + + # set target version for child + @issue2.fixed_version = version_0_1 + assert @issue2.save + assert @issue1.reload.fixed_version == @issue2.fixed_version + + # set target version for child of child larger than target version + # of child. so, target version of parents should be updated. + @issue3.fixed_version = version_1_0 + assert @issue3.save + assert @issue1.reload.fixed_version == @issue3.fixed_version + end + + def test_should_not_allowed_close_parent_issue_while_one_of_children_open + create_family_of_issues 'Closing parent issue when some children is open test.' + + closed_status = issue_statuses( :issue_statuses_005) + @issue3.status = closed_status + assert @issue3.save + + assert_raise ActiveRecord::RecordInvalid do + @issue1.reload.status = closed_status + @issue1.save! + end + + end + + def test_should_change_status_of_parent_when_some_children_is_open + create_family_of_issues 'Changing status of parent from closed to open when some of children is open.' + + open_status = issue_statuses( :issue_statuses_001) + closed_status = issue_statuses( :issue_statuses_005) + + @issue3.status = closed_status + @issue2.status = closed_status + @issue1.status = closed_status + assert @issue3.save + assert @issue2.save + assert @issue1.save + assert @issue1.reload.closed? + assert @issue2.reload.closed? + assert @issue3.reload.closed? + + # set status of children to open status. this should update status + # of parent and set it to open state. + @issue2.status = open_status + assert @issue2.save + assert !@issue2.reload.closed? + assert !@issue1.reload.closed? + end + + def test_should_update_targetversion_of_parent_if_children_have_bigger_targetversion + create_family_of_issues 'Update target version of parent if children have bigger target version.' + + # set parent version to 1. + @issue1.fixed_version = versions( :versions_001) + assert @issue1.save + assert @issue1.reload.fixed_version == versions( :versions_001) + + # set children to version higher that parent. + @issue2.fixed_version = versions( :versions_002) + assert @issue2.save + assert @issue1.reload.fixed_version == versions( :versions_002) + end + + def test_should_set_target_version_of_parent_if_children_have_a_target_version + create_family_of_issues 'Update target version of parent if children have a target version.' + + @issue2.fixed_version = versions( :versions_001) + assert @issue2.save + assert @issue2.reload.fixed_version == versions( :versions_001) + assert @issue1.reload.fixed_version == @issue2.fixed_version + end + + def test_should_not_allow_to_set_targetversion_of_parent_lower_than_any_of_the_children + create_family_of_issues 'Not allowing to set target version of parent lower than any of the children.' + + [ @issue1, @issue2, @issue3 ].each do |issue| + issue.update_attribute :fixed_version, versions( :versions_002) + end + + assert_raise ActiveRecord::RecordInvalid do + @issue1.fixed_version = versions( :versions_001) + @issue1.save! + end + end + + def test_should_not_set_target_version_of_parent_if_child_on_another_project + create_family_of_issues 'Should not set target version of parnet if child on another project.' + + @issue2.fixed_version = versions( :versions_001) + assert @issue2.save + assert @issue1.reload.fixed_version = versions( :versions_001) + + online_store = projects( :projects_002) + assert @issue2.move_to( online_store) + @issue2.reload.fixed_version = versions( :onlinestore_1_0) + assert @issue2.save + assert @issue1.reload.fixed_version = versions( :versions_001) + assert @issue2.reload.fixed_version = versions( :onlinestore_1_0) + end + + private + + def create_family_of_issues( subject) + # Create 3 issues + @issue1 = Issue.new( :project_id => 1, :tracker_id => 1, + :author_id => 1, :status_id => 1, + :priority => Enumeration.get_values('IPRI').first, + :subject => subject, + :description => subject) + assert @issue1.save + @issue2 = @issue1.clone + assert @issue2.save + @issue3 = @issue1.clone + assert @issue3.save + + # 2 is a child of 1 + IssueRelation.create( :issue_from => @issue2, :issue_to => @issue1, + :relation_type => IssueRelation::TYPE_PARENTS) + # And 3 is a child of 2 + IssueRelation.create( :issue_from => @issue3, :issue_to => @issue2, + :relation_type => IssueRelation::TYPE_PARENTS) + + assert @issue2.reload.parent == @issue1 + assert @issue2.reload.children.include?( @issue3) + + assert @issue1.save + assert @issue2.save + assert @issue3.save + + @issue1.reload + @issue2.reload + @issue3.reload + end + end diff --git a/tmp/.gitignore b/tmp/.gitignore new file mode 100644 diff --git a/tmp/cache/.gitignore b/tmp/cache/.gitignore new file mode 100644 diff --git a/vendor/plugins/auto_complete b/vendor/plugins/auto_complete new file mode 160000 diff --git a/vendor/plugins/redmine_timesheet_plugin b/vendor/plugins/redmine_timesheet_plugin new file mode 160000