diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 156933d..eac6720 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) + 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,9 @@ 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 +233,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 +244,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 +283,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 +343,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 +518,7 @@ class IssuesController < ApplicationController def update_form @issue = Issue.new(params[:issue]) + @parent_issue = @issue.parent render :action => :new, :layout => false end @@ -423,11 +532,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..b81e4b8 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,29 @@ 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.new do |relation| + relation.issue_from = issue + relation.issue_to = Issue.find(parent_id) + relation.relation_type = IssueRelation::TYPE_PARENTS + relation.save + 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..a9f00a3 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,10 @@ 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 end def validate_on_create @@ -140,7 +139,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 +152,7 @@ class Issue < ActiveRecord::Base } @current_journal.save end + # Save the issue even if the journal is not saved (because empty) true end @@ -176,8 +176,23 @@ class Issue < ActiveRecord::Base duplicate.update_attribute :status, self.status end end + + # Set new status to parent if new status openes the issue. + if parent + if parent.closed? && !self.closed? + parent.update_attribute :status, IssueStatus.default + end + end end - + + def fixed_version + if children? + children.select { |c| c.fixed_version } .collect { |c| c.fixed_version } .max + else + Version.find(self[:fixed_version_id]) if self[:fixed_version_id] && self[:fixed_version_id] != 0 + end + end + def init_journal(user, notes = "") @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes) @issue_before_change = self.clone @@ -243,6 +258,10 @@ class Issue < ActiveRecord::Base due_date || (fixed_version ? fixed_version.effective_date : nil) end + def duration1 + (start_date && due_date) ? (due_date - start_date + 1) : 0 + end + def duration (start_date && due_date) ? due_date - start_date : 0 end @@ -257,6 +276,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..8cb04f4 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,18 @@ 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_status + end set_issue_to_dates end + + def set_issue_to_status + if issue_to.closed? && !issue_from.closed? + issue_to.update_attribute :status, IssueStatus.default + 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/_form.rhtml b/app/views/issues/_form.rhtml index 419536f..d38a2ab 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,29 +24,55 @@
-<% 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), l(:label_issue_category_new), 'category[name]', {:controller => 'projects', :action => 'add_issue_category', :id => @project}, :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %>

+ +<% if @issue.edge? %> <%= content_tag('p', f.select(:fixed_version_id, (@project.versions.sort.collect {|v| [v.name, v.id]}), { :include_blank => true })) unless @project.versions.empty? %> +<% else %> +

<%= @issue.fixed_version ? @issue.fixed_version.name : '-' %>

+<% end %>
+<% 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..79d7d0a 100644 --- a/app/views/issues/_form_update.rhtml +++ b/app/views/issues/_form_update.rhtml @@ -3,8 +3,13 @@

<%= 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] }) %>

<%= content_tag('p', f.select(:fixed_version_id, (@project.versions.sort.collect {|v| [v.name, v.id]}), { :include_blank => true })) unless @project.versions.empty? %> +<% else %> +

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

+

<%= @issue.fixed_version ? @issue.fixed_version.name : '-' %>

+<% end %>
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..7429951 100644 --- a/app/views/issues/context_menu.rhtml +++ b/app/views/issues/context_menu.rhtml @@ -2,6 +2,7 @@ <% if !@issue.nil? -%>
  • <%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon-edit', :disabled => !@can[:edit] %>
  • + <% if @issue.edge? %>
  • <%= l(:field_status) %>
  • + <% end %> <% else %>
  • <%= 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) %>
  • <% end -%> + <% if @issue && @issue.edge? %>
  • <%= l(:field_done_ratio) %>
  • - + <% 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/config/.gitignore b/config/.gitignore new file mode 100644 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/files/.gitignore b/files/.gitignore new file mode 100644 diff --git a/lang/bg.yml b/lang/bg.yml index ae345f9..50d7f29 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/ca.yml b/lang/ca.yml index 5264953..6c409cd 100644 --- a/lang/ca.yml +++ b/lang/ca.yml @@ -693,3 +693,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_won_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). diff --git a/lang/cs.yml b/lang/cs.yml index d195456..7398c4d 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/da.yml b/lang/da.yml index b5dbe6b..53baef6 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/de.yml b/lang/de.yml index a621ecf..4f0f8bc 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,7 @@ 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 diff --git a/lang/en.yml b/lang/en.yml index 245663f..9ab326c 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 @@ -514,6 +515,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 +593,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 +631,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 efef0d5..d8f0b0f 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,21 @@ 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 diff --git a/lang/fi.yml b/lang/fi.yml index 5b2dc0e..6066fd5 100644 --- a/lang/fi.yml +++ b/lang/fi.yml @@ -692,3 +692,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_won_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). diff --git a/lang/fr.yml b/lang/fr.yml index 8b2defb..fbe4ace 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,4 @@ 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 diff --git a/lang/he.yml b/lang/he.yml index e7f70e7..969e838 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,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_won_messages: Delete own messages +button_add_subissue: Add sub-issue +label_parents: child of +label_children: parent of diff --git a/lang/hu.yml b/lang/hu.yml index 26b0237..226f82f 100644 --- a/lang/hu.yml +++ b/lang/hu.yml @@ -693,3 +693,8 @@ 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_won_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). diff --git a/lang/it.yml b/lang/it.yml index f442710..64aea06 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/ja.yml b/lang/ja.yml index 347454b..b791f1c 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/ko.yml b/lang/ko.yml index 498572f..7e7433f 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/lt.yml b/lang/lt.yml index bcec2b3..36a5176 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/nl.yml b/lang/nl.yml index 0353419..dfab8a7 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/no.yml b/lang/no.yml index d677923..4dc40da 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/pl.yml b/lang/pl.yml index 265baee..26f3465 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,8 @@ 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). diff --git a/lang/pt-br.yml b/lang/pt-br.yml index 299a139..806dba2 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,5 @@ 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_won_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). diff --git a/lang/pt.yml b/lang/pt.yml index 310d2c3..4a0e005 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/ro.yml b/lang/ro.yml index cc1d9bc..05f2e58 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/ru.yml b/lang/ru.yml index 8ad4543..d625641 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,7 @@ 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). diff --git a/lang/sk.yml b/lang/sk.yml index 44085db..f617e95 100644 --- a/lang/sk.yml +++ b/lang/sk.yml @@ -698,3 +698,8 @@ label_example: Príklad permission_edit_own_messages: Edit own messages permission_delete_won_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). diff --git a/lang/sr.yml b/lang/sr.yml index 1f555c4..033971c 100644 --- a/lang/sr.yml +++ b/lang/sr.yml @@ -123,6 +123,7 @@ field_due_date: Do datuma field_assigned_to: Dodeljeno field_priority: Prioritet field_fixed_version: Target version +field_parent_issue: Child of field_user: Korisnik field_role: Uloga field_homepage: Homepage @@ -406,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 @@ -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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/sv.yml b/lang/sv.yml index 530838a..b77d39e 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/th.yml b/lang/th.yml index 1d2f4e6..5fc5ef6 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/tr.yml b/lang/tr.yml index 3646baa..b53cb5f 100644 --- a/lang/tr.yml +++ b/lang/tr.yml @@ -693,3 +693,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_won_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). diff --git a/lang/uk.yml b/lang/uk.yml index 55acd3a..daea9cf 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,4 @@ 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_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lang/vn.yml b/lang/vn.yml index bde3113..476ec8e 100644 --- a/lang/vn.yml +++ b/lang/vn.yml @@ -694,3 +694,9 @@ permission_delete_messages: Xóa bài viết 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ạ." + +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). diff --git a/lang/zh-tw.yml b/lang/zh-tw.yml index 1246cb7..2d54a9c 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,4 @@ default_activity_development: 開發 enumeration_issue_priorities: 項目優先權 enumeration_doc_categories: 文件分類 enumeration_activities: 活動 (時間追蹤) +button_add_subissue: Add sub-issue diff --git a/lang/zh.yml b/lang/zh.yml index d9cd622..fd2b90e 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: 您确定? @@ -694,3 +698,5 @@ default_activity_development: 开发 enumeration_issue_priorities: 问题优先级 enumeration_doc_categories: 文档类别 enumeration_activities: 活动(时间跟踪) +permission_delete_won_messages: Delete own messages +button_add_subissue: Add sub-issue diff --git a/lib/redmine.rb b/lib/redmine.rb index 45cfcbd..4ee98b7 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/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..36a7f4f 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,101 @@ 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 + + # set target version for child of child smaller than target + # version of any child of parent or parent. this should decrease + # target version of parent. + @issue3.fixed_version = version_0_1 + assert @issue3.save + assert @issue1.reload.fixed_version == @issue3.reload.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.reload.save + assert @issue1.reload.save + @issue1.reload + @issue2.reload + @issue3.reload + + # 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.reload.save + assert @issue1.reload.status == IssueStatus.default + 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