subissues-v1.8.diff
| b/app/controllers/issues_controller.rb | ||
|---|---|---|
| 18 | 18 |
class IssuesController < ApplicationController |
| 19 | 19 |
menu_item :new_issue, :only => :new |
| 20 | 20 |
|
| 21 |
before_filter :find_issue, :only => [:show, :edit, :reply] |
|
| 21 |
before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment ]
|
|
| 22 | 22 |
before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] |
| 23 |
before_filter :find_project, :only => [:new, :update_form, :preview] |
|
| 24 |
before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu] |
|
| 25 |
before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar] |
|
| 23 |
before_filter :find_parent_issue, :only => [:add_subissue] |
|
| 24 |
before_filter :find_optional_parent_issue, :only => [:new] |
|
| 25 |
before_filter :find_project, :only => [:new, :auto_complete_for_issue_parent, :add_subissue, :update_form, :preview ] |
|
| 26 |
before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar ] |
|
| 27 |
before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu, :auto_complete_for_issue_parent ] |
|
| 26 | 28 |
accept_key_auth :index, :changes |
| 27 | 29 | |
| 28 | 30 |
helper :journals |
| ... | ... | |
| 41 | 43 |
include SortHelper |
| 42 | 44 |
include IssuesHelper |
| 43 | 45 |
helper :timelog |
| 46 |
include ActionView::Helpers::PrototypeHelper |
|
| 44 | 47 |
include Redmine::Export::PDF |
| 45 | 48 | |
| 49 |
def auto_complete_for_issue_parent |
|
| 50 |
@phrase = params[:issue_parent] |
|
| 51 |
@candidates = [] |
|
| 52 | ||
| 53 |
# If cross project issue relations is allowed we should get |
|
| 54 |
# candidates from every project |
|
| 55 |
if Setting.cross_project_issue_relations? |
|
| 56 |
projects_to_search = nil |
|
| 57 |
else |
|
| 58 |
projects_to_search = [ @project ] + @project.active_children |
|
| 59 |
end |
|
| 60 | ||
| 61 |
# Try to find issue by id. |
|
| 62 |
if @phrase.match(/^#?(\d+)$/) |
|
| 63 |
if Setting.cross_project_issue_relations? |
|
| 64 |
issue = Issue.find_by_id( $1) |
|
| 65 |
else |
|
| 66 |
issue = Issue.find_by_id_and_project_id( $1, projects_to_search.collect { |i| i.id})
|
|
| 67 |
end |
|
| 68 |
@candidates = [ issue ] if issue |
|
| 69 |
end |
|
| 70 | ||
| 71 |
# If finding by id is fail, try to find by searching in subject |
|
| 72 |
# and description. |
|
| 73 |
if @candidates.empty? |
|
| 74 |
# extract tokens from the question |
|
| 75 |
# eg. hello "bye bye" => ["hello", "bye bye"] |
|
| 76 |
tokens = @phrase.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
|
|
| 77 |
# tokens must be at least 3 character long |
|
| 78 |
tokens = tokens.uniq.select {|w| w.length > 2 }
|
|
| 79 |
like_tokens = tokens.collect {|w| "%#{w.downcase}%"}
|
|
| 80 | ||
| 81 |
@candidates, count = Issue.search( like_tokens, projects_to_search, :before => true) |
|
| 82 |
end |
|
| 83 | ||
| 84 |
render :inline => "<%= auto_complete_result_parent_issue( @candidates, @phrase) %>" |
|
| 85 |
end |
|
| 86 | ||
| 46 | 87 |
def index |
| 47 | 88 |
retrieve_query |
| 48 | 89 |
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) |
| ... | ... | |
| 58 | 99 |
end |
| 59 | 100 |
@issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement) |
| 60 | 101 |
@issue_pages = Paginator.new self, @issue_count, limit, params['page'] |
| 61 |
@issues = Issue.find :all, :order => sort_clause, |
|
| 62 |
:include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ], |
|
| 63 |
:conditions => @query.statement, |
|
| 64 |
:limit => limit, |
|
| 65 |
:offset => @issue_pages.current.offset |
|
| 102 |
@issues = Issue.find( :all, :order => sort_clause, |
|
| 103 |
:include => [ :assigned_to, |
|
| 104 |
:status, |
|
| 105 |
:tracker, |
|
| 106 |
:project, |
|
| 107 |
:priority, |
|
| 108 |
:category, |
|
| 109 |
:fixed_version ], |
|
| 110 |
:conditions => @query.statement, |
|
| 111 |
:limit => limit, |
|
| 112 |
:offset => @issue_pages.current.offset) |
|
| 113 |
|
|
| 66 | 114 |
respond_to do |format| |
| 67 | 115 |
format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
|
| 68 | 116 |
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
|
| ... | ... | |
| 136 | 184 |
end |
| 137 | 185 |
@issue.status = default_status |
| 138 | 186 |
@allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq |
| 139 |
|
|
| 187 | ||
| 188 | ||
| 140 | 189 |
if request.get? || request.xhr? |
| 141 | 190 |
@issue.start_date ||= Date.today |
| 142 | 191 |
else |
| ... | ... | |
| 147 | 196 |
attach_files(@issue, params[:attachments]) |
| 148 | 197 |
flash[:notice] = l(:notice_successful_create) |
| 149 | 198 |
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
|
| 199 |
@issue.move_to_child_of @parent_issue if @parent_issue |
|
| 150 | 200 |
redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } :
|
| 151 | 201 |
{ :action => 'show', :id => @issue })
|
| 152 | 202 |
return |
| ... | ... | |
| 155 | 205 |
@priorities = Enumeration.priorities |
| 156 | 206 |
render :layout => !request.xhr? |
| 157 | 207 |
end |
| 208 | ||
| 209 |
def add_subissue |
|
| 210 |
redirect_to :action => 'new', :issue => { :parent_issue_id => @parent_issue.id }
|
|
| 211 |
end |
|
| 158 | 212 |
|
| 159 | 213 |
# Attributes that can be updated on workflow transition (without :edit permission) |
| 160 | 214 |
# TODO: make it configurable (at least per role) |
| ... | ... | |
| 449 | 503 |
rescue ActiveRecord::RecordNotFound |
| 450 | 504 |
render_404 |
| 451 | 505 |
end |
| 452 |
|
|
| 506 | ||
| 453 | 507 |
def find_optional_project |
| 454 | 508 |
@project = Project.find(params[:project_id]) unless params[:project_id].blank? |
| 455 | 509 |
allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
|
| ... | ... | |
| 458 | 512 |
render_404 |
| 459 | 513 |
end |
| 460 | 514 |
|
| 515 |
def find_parent_issue |
|
| 516 |
@parent_issue = Issue.find( params[:parent_issue_id]) |
|
| 517 |
rescue ActiveRecord::RecordNotFound |
|
| 518 |
render_404 |
|
| 519 |
end |
|
| 520 |
|
|
| 521 |
def find_optional_parent_issue |
|
| 522 |
if params[:issue] && !params[:issue][:parent_id].blank? |
|
| 523 |
@parent_issue = Issue.find( params[:issue][:parent_id]) |
|
| 524 |
end |
|
| 525 |
rescue ActiveRecord::RecordNotFound |
|
| 526 |
render_404 |
|
| 527 |
end |
|
| 528 |
|
|
| 461 | 529 |
# Retrieve query from session or build a new query |
| 462 | 530 |
def retrieve_query |
| 463 | 531 |
if !params[:query_id].blank? |
| ... | ... | |
| 481 | 549 |
@query.add_short_filter(field, params[field]) if params[field] |
| 482 | 550 |
end |
| 483 | 551 |
end |
| 484 |
session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
|
|
| 552 |
if params[:view_options] and params[:view_options].is_a? Hash |
|
| 553 |
params[:view_options].each_pair do |name, value| |
|
| 554 |
@query.set_view_option( name, value) |
|
| 555 |
end |
|
| 556 |
end |
|
| 557 |
session[:query] = {
|
|
| 558 |
:project_id => @query.project_id, |
|
| 559 |
:filters => @query.filters, |
|
| 560 |
:view_options => @query.view_options |
|
| 561 |
} |
|
| 485 | 562 |
else |
| 486 | 563 |
@query = Query.find_by_id(session[:query][:id]) if session[:query][:id] |
| 487 |
@query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters]) |
|
| 564 |
@query ||= Query.new(:name => "_", |
|
| 565 |
:project => @project, |
|
| 566 |
:filters => session[:query][:filters], |
|
| 567 |
:view_options => session[:query][:view_options]) |
|
| 488 | 568 |
@query.project = @project |
| 489 | 569 |
end |
| 490 | 570 |
end |
| b/app/controllers/projects_controller.rb | ||
|---|---|---|
| 46 | 46 |
helper :repositories |
| 47 | 47 |
include RepositoriesHelper |
| 48 | 48 |
include ProjectsHelper |
| 49 |
helper :versions |
|
| 50 |
include VersionsHelper |
|
| 49 | 51 |
|
| 50 | 52 |
# Lists visible projects |
| 51 | 53 |
def index |
| ... | ... | |
| 292 | 294 |
@selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
|
| 293 | 295 |
end |
| 294 | 296 |
end |
| 297 |
|
|
| 298 |
|
|
| 299 |
def sort_as_tree(issues) |
|
| 300 |
issues.sort!{|a,b| a.hierarchical_level <=> b.hierarchical_level}
|
|
| 301 |
@sorted_issues = [] |
|
| 302 |
issues.each do |issue| |
|
| 303 |
if @sorted_issues.empty? |
|
| 304 |
@sorted_issues << issue |
|
| 305 |
next |
|
| 306 |
end |
|
| 307 |
@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 |
|
| 308 |
@sorted_issues.each do |sorted_issue| |
|
| 309 |
#if same parent and smaller date, stop; if same parent, same date and smaller id, stop; after parent and before next parent, stop; |
|
| 310 |
if ((sorted_issue.parent == issue.parent) && (sorted_issue.start_date > issue.start_date)) || |
|
| 311 |
((sorted_issue.parent == issue.parent) && (sorted_issue.start_date == issue.start_date) && (sorted_issue.id > issue.id)) || |
|
| 312 |
(@time_to_stop && (sorted_issue.hierarchical_level < issue.hierarchical_level)) |
|
| 313 |
@sorted_issues.insert(@sorted_issues.index(sorted_issue), issue) |
|
| 314 |
break |
|
| 315 |
end |
|
| 316 |
@time_to_stop = true if sorted_issue == issue.parent |
|
| 317 |
end |
|
| 318 |
#if this issue's parent is the last element |
|
| 319 |
@sorted_issues << issue if @time_to_stop |
|
| 320 |
end |
|
| 321 |
@sorted_issues |
|
| 322 |
end |
|
| 323 |
|
|
| 324 |
#assumes that first level issues are ordered by date (sort_as_tree) |
|
| 325 |
def integrate_versions_with_issues_tree(issues, versions) |
|
| 326 |
versions.sort! {|x,y| x.start_date <=> y.start_date }
|
|
| 327 |
versions.each do |version| |
|
| 328 |
issues << version if issues.empty? |
|
| 329 |
issues.each do |issue| |
|
| 330 |
if ((issue.is_a? Issue && issue.root?) || (issue.is_a? Version)) && version.start_date < issue.start_date |
|
| 331 |
#insert version before a root task or another version whose date is immediately after this task's one |
|
| 332 |
issues.insert(issues.index(issue), version) |
|
| 333 |
elsif issue == issues.last |
|
| 334 |
issues << version |
|
| 335 |
end |
|
| 336 |
end |
|
| 337 |
end |
|
| 338 |
issues |
|
| 339 |
end |
|
| 340 |
|
|
| 295 | 341 |
end |
| b/app/controllers/queries_controller.rb | ||
|---|---|---|
| 30 | 30 |
params[:fields].each do |field| |
| 31 | 31 |
@query.add_filter(field, params[:operators][field], params[:values][field]) |
| 32 | 32 |
end if params[:fields] |
| 33 |
|
|
| 33 | ||
| 34 |
params[:view_options].each_pair do |name, value| |
|
| 35 |
@query.set_view_option( name, value) |
|
| 36 |
end if params[:view_options] |
|
| 37 | ||
| 34 | 38 |
if request.post? && params[:confirm] && @query.save |
| 35 | 39 |
flash[:notice] = l(:notice_successful_create) |
| 36 | 40 |
redirect_to :controller => 'issues', :action => 'index', :project_id => @project, :query_id => @query |
| ... | ... | |
| 45 | 49 |
params[:fields].each do |field| |
| 46 | 50 |
@query.add_filter(field, params[:operators][field], params[:values][field]) |
| 47 | 51 |
end if params[:fields] |
| 52 |
params[:view_options].each_pair do |name, value| |
|
| 53 |
@query.set_view_option( name, value) |
|
| 54 |
end if params[:view_options] |
|
| 48 | 55 |
@query.attributes = params[:query] |
| 49 | 56 |
@query.project = nil if params[:query_is_for_all] |
| 50 | 57 |
@query.is_public = false unless (@query.project && current_role.allowed_to?(:manage_public_queries)) || User.current.admin? |
| b/app/controllers/versions_controller.rb | ||
|---|---|---|
| 20 | 20 |
before_filter :find_project, :authorize |
| 21 | 21 | |
| 22 | 22 |
def show |
| 23 |
@issues = @version.fixed_issues.find(:all, |
|
| 24 |
:include => [:status, :tracker], |
|
| 25 |
:order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
|
|
| 26 |
@issues = Issue.find_with_parents( @issues.collect { |i| i.id})
|
|
| 23 | 27 |
end |
| 24 | 28 |
|
| 25 | 29 |
def edit |
| b/app/helpers/issues_helper.rb | ||
|---|---|---|
| 19 | 19 | |
| 20 | 20 |
module IssuesHelper |
| 21 | 21 |
include ApplicationHelper |
| 22 |
|
|
| 23 |
def issue_ancestors(issue=@issue) |
|
| 24 |
ancestors = "" |
|
| 25 |
return "" if issue.parent == nil |
|
| 26 |
ancestors += "issue-#{issue.parent.id}-child " + issue_ancestors(issue.parent)
|
|
| 27 |
end |
|
| 22 | 28 | |
| 23 | 29 |
def render_issue_tooltip(issue) |
| 24 | 30 |
@cached_label_start_date ||= l(:field_start_date) |
| ... | ... | |
| 196 | 202 |
export.rewind |
| 197 | 203 |
export |
| 198 | 204 |
end |
| 205 |
|
|
| 206 |
def auto_complete_result_parent_issue(candidates, phrase) |
|
| 207 |
return "" if candidates.empty? |
|
| 208 |
candidates.map! do |c| |
|
| 209 |
content_tag("li", highlight( c.to_s, phrase), :id => String( c[:id]))
|
|
| 210 |
end |
|
| 211 |
content_tag("ul", candidates.uniq)
|
|
| 212 |
end |
|
| 199 | 213 |
end |
| b/app/helpers/queries_helper.rb | ||
|---|---|---|
| 1 |
# -*- coding: mule-utf-8 -*- |
|
| 1 | 2 |
# redMine - project management software |
| 2 | 3 |
# Copyright (C) 2006-2007 Jean-Philippe Lang |
| 3 | 4 |
# |
| ... | ... | |
| 27 | 28 |
content_tag('th', column.caption)
|
| 28 | 29 |
end |
| 29 | 30 |
|
| 30 |
def column_content(column, issue) |
|
| 31 |
def column_content(column, issue, query)
|
|
| 31 | 32 |
if column.is_a?(QueryCustomFieldColumn) |
| 32 | 33 |
cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
|
| 33 | 34 |
show_value(cv) |
| ... | ... | |
| 40 | 41 |
else |
| 41 | 42 |
case column.name |
| 42 | 43 |
when :subject |
| 43 |
h((!@project.nil? && @project != issue.project) ? "#{issue.project.name} - " : '') +
|
|
| 44 |
link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) |
|
| 44 |
subject_in_tree(issue, value, query) |
|
| 45 | 45 |
when :done_ratio |
| 46 | 46 |
progress_bar(value, :width => '80px') |
| 47 | 47 |
when :fixed_version |
| ... | ... | |
| 52 | 52 |
end |
| 53 | 53 |
end |
| 54 | 54 |
end |
| 55 |
|
|
| 56 |
def subject_in_tree(issue, value, query) |
|
| 57 |
case query.view_options['show_parents'] |
|
| 58 |
when Query::VIEW_OPTIONS_SHOW_PARENTS_NEVER |
|
| 59 |
content_tag('div', subject_text(issue, value), :class=>'issue-subject')
|
|
| 60 |
else |
|
| 61 |
content_tag('span', content_tag('div', subject_text(issue, value), :class=>'issue-subject'), :class=>"issue-subject-level-#{issue.hierarchical_level}")
|
|
| 62 |
end |
|
| 63 |
end |
|
| 64 |
|
|
| 65 |
def subject_text(issue, value) |
|
| 66 |
subject_text = link_to(h(value), :controller => 'issues', :action => 'show', :id => issue) |
|
| 67 |
h((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') + subject_text
|
|
| 68 |
end |
|
| 69 | ||
| 70 |
def issue_content(issue, query, options = { })
|
|
| 71 |
html = "" |
|
| 72 |
html << "<tr id=\"issue-#{issue.id}\" class=\"issue hascontextmenu " +
|
|
| 73 |
( options[:unfiltered] ? 'issue-unfiltered ' : '') + |
|
| 74 |
"status-#{issue.status.position} priority-#{issue.priority.position} " +
|
|
| 75 |
cycle('odd', 'even') + '">'
|
|
| 76 |
html << '<td class="checkbox">' + check_box_tag( "ids[]", issue.id, false, :id => nil) + '</td>' |
|
| 77 |
html << '<td>' + link_to( issue.id, :controller => 'issues', :action => 'show', :id => issue) + '</td>' |
|
| 78 |
query.columns.each do |column| |
|
| 79 |
html << content_tag( 'td', column_content(column, issue, query), :class => column.name) |
|
| 80 |
end |
|
| 81 |
html << "</tr>" |
|
| 82 |
html |
|
| 83 |
end |
|
| 84 | ||
| 85 |
def issues_family_content( parent, issues_to_show, query) |
|
| 86 |
html = "" |
|
| 87 |
html << issue_content( parent, query, :unfiltered => !( issues_to_show.include? parent)) |
|
| 88 |
unless parent.children.empty? |
|
| 89 |
parent.children.each do |child| |
|
| 90 |
if issues_to_show.include?( child) || issues_to_show.detect { |i| i.ancestors.include? child }
|
|
| 91 |
html << issues_family_content( child, issues_to_show, query) |
|
| 92 |
end |
|
| 93 |
end |
|
| 94 |
end |
|
| 95 |
html |
|
| 96 |
end |
|
| 97 |
|
|
| 55 | 98 |
end |
| b/app/helpers/versions_helper.rb | ||
|---|---|---|
| 44 | 44 |
def status_by_options_for_select(value) |
| 45 | 45 |
options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value)
|
| 46 | 46 |
end |
| 47 | ||
| 48 |
def render_list_of_related_issues( issues, version, current_level = 1) |
|
| 49 |
issues_on_current_level = issues.select { |i| i.hierarchical_level == current_level }
|
|
| 50 |
issues -= issues_on_current_level |
|
| 51 |
content_tag( 'ul') do |
|
| 52 |
html = '' |
|
| 53 |
issues_on_current_level.each do |issue| |
|
| 54 |
opts_for_issue_li = { }
|
|
| 55 |
if !issue.fixed_version or issue.fixed_version != version |
|
| 56 |
opts_for_issue_li[:class] = 'issue-unfiltered' |
|
| 57 |
end |
|
| 58 |
html << content_tag( 'li', opts_for_issue_li) do |
|
| 59 |
opts = { }
|
|
| 60 |
if issue.done_ratio == 100 |
|
| 61 |
opts[:style] = 'font-weight: bold' |
|
| 62 |
end |
|
| 63 |
link_to_issue(issue, opts) + ": " + h(issue.subject) |
|
| 64 |
end |
|
| 65 |
children_to_print = issues & issue.children |
|
| 66 |
children_to_print += issues.select { |i| i.hierarchical_level >= current_level + 2}
|
|
| 67 |
unless children_to_print.empty? |
|
| 68 |
html << render_list_of_related_issues( children_to_print, version, current_level + 1) |
|
| 69 |
end |
|
| 70 |
end |
|
| 71 |
html |
|
| 72 |
end |
|
| 73 |
end |
|
| 47 | 74 |
end |
| b/app/models/issue.rb | ||
|---|---|---|
| 18 | 18 |
class Issue < ActiveRecord::Base |
| 19 | 19 |
belongs_to :project |
| 20 | 20 |
belongs_to :tracker |
| 21 |
belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
|
|
| 22 |
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
|
|
| 23 |
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
|
|
| 24 |
belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' |
|
| 25 |
belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
|
|
| 26 |
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' |
|
| 21 |
belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
|
|
| 22 |
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
|
|
| 23 |
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
|
|
| 24 |
belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
|
|
| 25 |
belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
|
|
| 26 |
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
|
|
| 27 | 27 | |
| 28 | 28 |
has_many :journals, :as => :journalized, :dependent => :destroy |
| 29 | 29 |
has_many :time_entries, :dependent => :delete_all |
| 30 | 30 |
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
|
| 31 | 31 |
|
| 32 | 32 |
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all |
| 33 |
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
|
|
| 33 |
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
|
|
| 34 | 34 |
|
| 35 | 35 |
acts_as_attachable :after_remove => :attachment_removed |
| 36 | 36 |
acts_as_customizable |
| ... | ... | |
| 46 | 46 |
acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
|
| 47 | 47 |
:author_key => :author_id |
| 48 | 48 |
|
| 49 |
acts_as_nested_set |
|
| 50 | ||
| 51 |
alias_method :nested_set_move_to, :move_to |
|
| 52 | ||
| 53 |
# Move the node to the left of another node (you can pass id only) |
|
| 54 |
def move_to_left_of(node) |
|
| 55 |
nested_set_move_to node, :left |
|
| 56 |
end |
|
| 57 | ||
| 58 |
# Move the node to the left of another node (you can pass id only) |
|
| 59 |
def move_to_right_of(node) |
|
| 60 |
nested_set_move_to node, :right |
|
| 61 |
end |
|
| 62 | ||
| 63 |
# Move the node to the child of another node (you can pass id only) |
|
| 64 |
def move_to_child_of(node) |
|
| 65 |
nested_set_move_to node, :child |
|
| 66 |
end |
|
| 67 |
|
|
| 68 |
# Move the node to root nodes |
|
| 69 |
def move_to_root |
|
| 70 |
nested_set_move_to nil, :root |
|
| 71 |
end |
|
| 72 | ||
| 49 | 73 |
validates_presence_of :subject, :priority, :project, :tracker, :author, :status |
| 50 | 74 |
validates_length_of :subject, :maximum => 255 |
| 51 | 75 |
validates_inclusion_of :done_ratio, :in => 0..100 |
| ... | ... | |
| 55 | 79 |
:conditions => Project.allowed_to_condition(args.first || User.current, :view_issues) } } |
| 56 | 80 |
|
| 57 | 81 |
named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
|
| 58 |
|
|
| 59 |
# Returns true if usr or current user is allowed to view the issue |
|
| 60 |
def visible?(usr=nil) |
|
| 61 |
(usr || User.current).allowed_to?(:view_issues, self.project) |
|
| 62 |
end |
|
| 63 |
|
|
| 64 |
def after_initialize |
|
| 65 |
if new_record? |
|
| 66 |
# set default values for new records only |
|
| 67 |
self.status ||= IssueStatus.default |
|
| 68 |
self.priority ||= Enumeration.priorities.default |
|
| 69 |
end |
|
| 70 |
end |
|
| 71 |
|
|
| 72 |
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields |
|
| 73 |
def available_custom_fields |
|
| 74 |
(project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : []
|
|
| 75 |
end |
|
| 76 |
|
|
| 77 |
def copy_from(arg) |
|
| 78 |
issue = arg.is_a?(Issue) ? arg : Issue.find(arg) |
|
| 79 |
self.attributes = issue.attributes.dup |
|
| 80 |
self.custom_values = issue.custom_values.collect {|v| v.clone}
|
|
| 81 |
self |
|
| 82 |
end |
|
| 83 |
|
|
| 84 |
# Moves/copies an issue to a new project and tracker |
|
| 85 |
# Returns the moved/copied issue on success, false on failure |
|
| 86 |
def move_to(new_project, new_tracker = nil, options = {})
|
|
| 87 |
options ||= {}
|
|
| 88 |
issue = options[:copy] ? self.clone : self |
|
| 89 |
transaction do |
|
| 90 |
if new_project && issue.project_id != new_project.id |
|
| 91 |
# delete issue relations |
|
| 92 |
unless Setting.cross_project_issue_relations? |
|
| 93 |
issue.relations_from.clear |
|
| 94 |
issue.relations_to.clear |
|
| 95 |
end |
|
| 96 |
# issue is moved to another project |
|
| 97 |
# reassign to the category with same name if any |
|
| 98 |
new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name) |
|
| 99 |
issue.category = new_category |
|
| 100 |
issue.fixed_version = nil |
|
| 101 |
issue.project = new_project |
|
| 102 |
end |
|
| 103 |
if new_tracker |
|
| 104 |
issue.tracker = new_tracker |
|
| 105 |
end |
|
| 106 |
if options[:copy] |
|
| 107 |
issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
|
|
| 108 |
issue.status = self.status |
|
| 109 |
end |
|
| 110 |
if issue.save |
|
| 111 |
unless options[:copy] |
|
| 112 |
# Manually update project_id on related time entries |
|
| 113 |
TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
|
|
| 114 |
end |
|
| 115 |
else |
|
| 116 |
Issue.connection.rollback_db_transaction |
|
| 117 |
return false |
|
| 118 |
end |
|
| 119 |
end |
|
| 120 |
return issue |
|
| 121 |
end |
|
| 122 |
|
|
| 123 |
def priority_id=(pid) |
|
| 124 |
self.priority = nil |
|
| 125 |
write_attribute(:priority_id, pid) |
|
| 126 |
end |
|
| 127 |
|
|
| 128 |
def estimated_hours=(h) |
|
| 129 |
write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) |
|
| 130 |
end |
|
| 131 |
|
|
| 82 | ||
| 132 | 83 |
def validate |
| 84 |
# FIXME: I do not know what actually this should do, but this |
|
| 85 |
# validation does not allow me to change due_date in hook when |
|
| 86 |
# fixed_version is set. |
|
| 133 | 87 |
if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty? |
| 134 | 88 |
errors.add :due_date, :not_a_date |
| 135 | 89 |
end |
| ... | ... | |
| 141 | 95 |
if start_date && soonest_start && start_date < soonest_start |
| 142 | 96 |
errors.add :start_date, :invalid |
| 143 | 97 |
end |
| 98 | ||
| 99 |
unless children.empty? |
|
| 100 |
if IssueStatus.find_by_id( @attributes['status_id']).is_closed? && |
|
| 101 |
children.detect { |i| !i.closed? }
|
|
| 102 |
errors.add( :status, |
|
| 103 |
"Can't close parent issue " + |
|
| 104 |
"while one of the children is still open.") |
|
| 105 |
end |
|
| 106 | ||
| 107 |
children_max_fixed_version = children.select { |i| i.fixed_version } .max { |a,b| a.fixed_version <=> b.fixed_version }
|
|
| 108 |
if @attributes['fixed_version_id'] && children_max_fixed_version |
|
| 109 |
if Version.find_by_id( @attributes['fixed_version_id']) < children_max_fixed_version.fixed_version |
|
| 110 |
errors.add :fixed_version, "Can't set target version of parent issue lower than any of the children." |
|
| 111 |
end |
|
| 112 |
end |
|
| 113 |
end |
|
| 144 | 114 |
end |
| 145 | 115 |
|
| 146 | 116 |
def validate_on_create |
| ... | ... | |
| 161 | 131 |
@current_journal.details << JournalDetail.new(:property => 'attr', |
| 162 | 132 |
:prop_key => c, |
| 163 | 133 |
:old_value => @issue_before_change.send(c), |
| 164 |
:value => send(c)) unless send(c)==@issue_before_change.send(c) |
|
| 134 |
:value => send(c)) unless send(c)==@issue_before_change.send(c) || (!self.leaf? && %w(status_id priority_id fixed_version_id start_date due_date done_ratio estimated_hours).include?(c))
|
|
| 165 | 135 |
} |
| 166 | 136 |
# custom fields changes |
| 167 | 137 |
custom_values.each {|c|
|
| ... | ... | |
| 174 | 144 |
} |
| 175 | 145 |
@current_journal.save |
| 176 | 146 |
end |
| 147 | ||
| 177 | 148 |
# Save the issue even if the journal is not saved (because empty) |
| 178 | 149 |
true |
| 150 | ||
| 179 | 151 |
end |
| 180 | 152 |
|
| 181 | 153 |
def after_save |
| 182 | 154 |
# Reload is needed in order to get the right status |
| 183 | 155 |
reload |
| 184 |
|
|
| 156 | ||
| 185 | 157 |
# Update start/due dates of following issues |
| 186 | 158 |
relations_from.each(&:set_issue_to_dates) |
| 187 |
|
|
| 159 | ||
| 160 |
if parent |
|
| 161 |
# Set default status of parent if new status opened the issue. |
|
| 162 |
if !status.is_closed? && parent.status.is_closed? |
|
| 163 |
parent.update_attribute :status, IssueStatus.default |
|
| 164 |
end |
|
| 165 | ||
| 166 |
# Set 'Target version' of parent if one was set on one of the |
|
| 167 |
# children issue and parent have no 'Target version'. Do the same |
|
| 168 |
# if 'Target version of the parent issue lower (by the release |
|
| 169 |
# date or by the version number). |
|
| 170 |
if parent.fixed_version.nil? && fixed_version or |
|
| 171 |
( parent.fixed_version && fixed_version and |
|
| 172 |
parent.fixed_version.project == fixed_version.project and |
|
| 173 |
parent.fixed_version < fixed_version ) |
|
| 174 |
parent.update_attribute :fixed_version, fixed_version |
|
| 175 |
end |
|
| 176 |
end |
|
| 177 | ||
| 178 |
# If target version is set, but "Due to" date is not, set it as |
|
| 179 |
# the same as the date of target version. |
|
| 180 |
if due_date.nil? && fixed_version && fixed_version.due_date |
|
| 181 |
self.update_attribute :due_date, fixed_version.due_date |
|
| 182 |
end |
|
| 183 | ||
| 188 | 184 |
# Close duplicates if the issue was closed |
| 189 | 185 |
if @issue_before_change && !@issue_before_change.closed? && self.closed? |
| 190 | 186 |
duplicates.each do |duplicate| |
| ... | ... | |
| 198 | 194 |
end |
| 199 | 195 |
end |
| 200 | 196 |
end |
| 197 | ||
| 198 |
# Returns true if usr or current user is allowed to view the issue |
|
| 199 |
def visible?(usr=nil) |
|
| 200 |
(usr || User.current).allowed_to?(:view_issues, self.project) |
|
| 201 |
end |
|
| 202 |
|
|
| 203 |
def after_initialize |
|
| 204 |
if new_record? |
|
| 205 |
# set default values for new records only |
|
| 206 |
self.status ||= IssueStatus.default |
|
| 207 |
self.priority ||= Enumeration.priorities.default |
|
| 208 |
end |
|
| 209 |
end |
|
| 210 |
|
|
| 211 |
# Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields |
|
| 212 |
def available_custom_fields |
|
| 213 |
(project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : []
|
|
| 214 |
end |
|
| 215 |
|
|
| 216 |
def copy_from(arg) |
|
| 217 |
issue = arg.is_a?(Issue) ? arg : Issue.find(arg) |
|
| 218 |
self.attributes = issue.attributes.dup |
|
| 219 |
self.custom_values = issue.custom_values.collect {|v| v.clone}
|
|
| 220 |
self |
|
| 221 |
end |
|
| 222 |
|
|
| 223 |
# Moves/copies an issue to a new project and tracker |
|
| 224 |
# Returns the moved/copied issue on success, false on failure |
|
| 225 |
def move_to(new_project, new_tracker = nil, options = {})
|
|
| 226 |
options ||= {}
|
|
| 227 |
issue = if options[:copy] |
|
| 228 |
Issue.new( self.attributes.reject { |k,v| k == 'parent_id' })
|
|
| 229 |
else |
|
| 230 |
self |
|
| 231 |
end |
|
| 232 |
transaction do |
|
| 233 |
if new_project && issue.project_id != new_project.id |
|
| 234 |
unless Setting.cross_project_issue_relations? |
|
| 235 |
# delete issue relations |
|
| 236 |
issue.relations_from.clear |
|
| 237 |
issue.relations_to.clear |
|
| 238 | ||
| 239 |
issue.children.each(&:move_to_root) unless options[:copy] |
|
| 240 |
end |
|
| 241 |
# issue is moved to another project |
|
| 242 |
# reassign to the category with same name if any |
|
| 243 |
new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name) |
|
| 244 |
issue.category = new_category |
|
| 245 |
issue.fixed_version = nil |
|
| 246 |
issue.project = new_project |
|
| 247 |
end |
|
| 248 |
if new_tracker |
|
| 249 |
issue.tracker = new_tracker |
|
| 250 |
end |
|
| 251 |
if options[:copy] |
|
| 252 |
issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
|
|
| 253 |
issue.status = self.status |
|
| 254 |
end |
|
| 255 |
if issue.save |
|
| 256 |
unless options[:copy] |
|
| 257 |
# Manually update project_id on related time entries |
|
| 258 |
TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
|
|
| 259 |
end |
|
| 260 |
if new_project && issue.project_id != new_project.id && |
|
| 261 |
!Setting.cross_project_issue_relations? |
|
| 262 |
issue.move_to_root |
|
| 263 |
end |
|
| 264 |
else |
|
| 265 |
Issue.connection.rollback_db_transaction |
|
| 266 |
return false |
|
| 267 |
end |
|
| 268 |
end |
|
| 269 |
return issue |
|
| 270 |
end |
|
| 271 |
|
|
| 272 |
def estimated_hours=(h) |
|
| 273 |
write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) |
|
| 274 |
end |
|
| 201 | 275 |
|
| 202 | 276 |
def init_journal(user, notes = "") |
| 203 | 277 |
@current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes) |
| ... | ... | |
| 210 | 284 |
@current_journal |
| 211 | 285 |
end |
| 212 | 286 |
|
| 287 |
def priority_id=(pid) |
|
| 288 |
self.priority = nil |
|
| 289 |
write_attribute(:priority_id, pid) |
|
| 290 |
end |
|
| 291 | ||
| 213 | 292 |
# Return true if the issue is closed, otherwise false |
| 214 | 293 |
def closed? |
| 215 | 294 |
self.status.is_closed? |
| ... | ... | |
| 274 | 353 |
due_date || (fixed_version ? fixed_version.effective_date : nil) |
| 275 | 354 |
end |
| 276 | 355 |
|
| 356 |
def duration1 |
|
| 357 |
(start_date && due_date) ? (due_date - start_date + 1) : 0 |
|
| 358 |
end |
|
| 359 |
|
|
| 277 | 360 |
# Returns the time scheduled for this issue. |
| 278 | 361 |
# |
| 279 | 362 |
# Example: |
| ... | ... | |
| 287 | 370 |
@soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
|
| 288 | 371 |
end |
| 289 | 372 |
|
| 373 |
def self.visible_by(usr) |
|
| 374 |
with_scope(:find => { :conditions => Project.visible_by(usr) }) do
|
|
| 375 |
yield |
|
| 376 |
end |
|
| 377 |
end |
|
| 378 |
|
|
| 379 |
def done_ratio |
|
| 380 |
if children? |
|
| 381 |
@total_planned_days ||= 0 |
|
| 382 |
@total_actual_days ||= 0 |
|
| 383 |
children.each do |child| # from every subtask get the total number of days and the number of days already "worked" |
|
| 384 |
planned_days = child.duration1 |
|
| 385 |
actual_days = child.done_ratio ? (planned_days * child.done_ratio / 100).floor : 0 |
|
| 386 |
@total_planned_days += planned_days |
|
| 387 |
@total_actual_days += actual_days |
|
| 388 |
end |
|
| 389 |
@total_done_ratio = @total_planned_days != 0 ? (@total_actual_days * 100 / @total_planned_days).floor : 0 |
|
| 390 |
else |
|
| 391 |
read_attribute(:done_ratio) |
|
| 392 |
end |
|
| 393 |
end |
|
| 394 |
|
|
| 395 |
def estimated_hours |
|
| 396 |
if children? |
|
| 397 |
is_set = false |
|
| 398 |
children.each do |child| |
|
| 399 |
if child.estimated_hours |
|
| 400 |
if is_set |
|
| 401 |
@est_hours += child.estimated_hours |
|
| 402 |
else |
|
| 403 |
@est_hours = child.estimated_hours |
|
| 404 |
is_set = true |
|
| 405 |
end |
|
| 406 |
end |
|
| 407 |
end |
|
| 408 |
@est_hours |
|
| 409 |
else |
|
| 410 |
read_attribute(:estimated_hours) |
|
| 411 |
end |
|
| 412 |
end |
|
| 413 |
|
|
| 414 |
def due_date |
|
| 415 |
if children? |
|
| 416 |
children_date = children.find_all { |i| i.due_date }
|
|
| 417 |
unless children_date.empty? |
|
| 418 |
children_date.sort { |a,b| a.due_date <=> b.due_date} .max
|
|
| 419 |
else |
|
| 420 |
read_attribute(:due_date) |
|
| 421 |
end |
|
| 422 |
else |
|
| 423 |
read_attribute(:due_date) |
|
| 424 |
end |
|
| 425 |
end |
|
| 426 | ||
| 427 |
def children? |
|
| 428 |
children != [] |
|
| 429 |
end |
|
| 430 |
|
|
| 431 |
#First level tasks have hierarchical level = 1 and so on |
|
| 432 |
def hierarchical_level(issue=self) |
|
| 433 |
1 + level |
|
| 434 |
end |
|
| 435 |
|
|
| 436 |
# FIXME: remove this method. |
|
| 437 |
def self.find_with_parents( *args) |
|
| 438 |
issues = find( *args) |
|
| 439 |
return [] if issues.empty? |
|
| 440 |
issues.each do |i| |
|
| 441 |
while not i.root? |
|
| 442 |
issues += [ i.parent ] |
|
| 443 |
i = i.parent |
|
| 444 |
end |
|
| 445 |
end |
|
| 446 |
issues.uniq |
|
| 447 |
end |
|
| 448 |
|
|
| 290 | 449 |
def to_s |
| 291 | 450 |
"#{tracker} ##{id}: #{subject}"
|
| 292 | 451 |
end |
| b/app/models/issue_relation.rb | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 |
class IssueRelation < ActiveRecord::Base |
| 19 | 19 |
belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id' |
| 20 |
belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id' |
|
| 20 |
belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id'
|
|
| 21 | 21 |
|
| 22 | 22 |
TYPE_RELATES = "relates" |
| 23 | 23 |
TYPE_DUPLICATES = "duplicates" |
| 24 | 24 |
TYPE_BLOCKS = "blocks" |
| 25 | 25 |
TYPE_PRECEDES = "precedes" |
| 26 | 26 |
|
| 27 |
TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 },
|
|
| 28 |
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 },
|
|
| 29 |
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 },
|
|
| 30 |
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 },
|
|
| 27 |
TYPES = { TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 },
|
|
| 28 |
TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 },
|
|
| 29 |
TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 },
|
|
| 30 |
TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows, :order => 4 },
|
|
| 31 | 31 |
}.freeze |
| 32 | 32 |
|
| 33 |
validates_presence_of :issue_from, :issue_to, :relation_type
|
|
| 34 |
validates_inclusion_of :relation_type, :in => TYPES.keys
|
|
| 35 |
validates_numericality_of :delay, :allow_nil => true |
|
| 36 |
validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
|
|
| 37 |
|
|
| 33 |
validates_presence_of :issue_from, :issue_to, :relation_type
|
|
| 34 |
validates_inclusion_of :relation_type, :in => TYPES.keys
|
|
| 35 |
validates_numericality_of :delay, :allow_nil => true
|
|
| 36 |
validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
|
|
| 37 | ||
| 38 | 38 |
attr_protected :issue_from_id, :issue_to_id |
| 39 | 39 |
|
| 40 | 40 |
def validate |
| ... | ... | |
| 59 | 59 |
else |
| 60 | 60 |
self.delay = nil |
| 61 | 61 |
end |
| 62 | ||
| 62 | 63 |
set_issue_to_dates |
| 63 | 64 |
end |
| 64 |
|
|
| 65 | ||
| 65 | 66 |
def set_issue_to_dates |
| 66 | 67 |
soonest_start = self.successor_soonest_start |
| 67 | 68 |
if soonest_start && (!issue_to.start_date || issue_to.start_date < soonest_start) |
| b/app/models/query.rb | ||
|---|---|---|
| 52 | 52 |
end |
| 53 | 53 |
end |
| 54 | 54 | |
| 55 |
class ViewOption |
|
| 56 |
attr_accessor :name, :available_values |
|
| 57 |
include Redmine::I18n |
|
| 58 |
|
|
| 59 |
def initialize( name, available_values) |
|
| 60 |
self.name = name |
|
| 61 |
self.available_values = available_values |
|
| 62 |
end |
|
| 63 | ||
| 64 |
def caption |
|
| 65 |
l("label_view_option_#{name}")
|
|
| 66 |
end |
|
| 67 |
end |
|
| 68 | ||
| 55 | 69 |
class Query < ActiveRecord::Base |
| 56 | 70 |
belongs_to :project |
| 57 | 71 |
belongs_to :user |
| 58 | 72 |
serialize :filters |
| 59 | 73 |
serialize :column_names |
| 74 |
serialize :view_options |
|
| 60 | 75 |
serialize :sort_criteria, Array |
| 61 | 76 |
|
| 62 | 77 |
attr_protected :project_id, :user_id |
| ... | ... | |
| 115 | 130 |
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
|
| 116 | 131 |
] |
| 117 | 132 |
cattr_reader :available_columns |
| 133 | ||
| 134 |
VIEW_OPTIONS_SHOW_PARENTS_NEVER = 'do_not_show' |
|
| 135 |
VIEW_OPTIONS_SHOW_PARENTS_ALWAYS = 'show_always' |
|
| 136 |
VIEW_OPTIONS_SHOW_PARENTS_ORGANIZE_BY_PARENT = 'organize_by_parent' |
|
| 137 | ||
| 138 |
@@available_view_options = [ |
|
| 139 |
ViewOption.new( 'show_parents', [ [ l(:label_view_option_parents_do_not_show), VIEW_OPTIONS_SHOW_PARENTS_NEVER ], |
|
| 140 |
[ l(:label_view_option_parents_show_always), VIEW_OPTIONS_SHOW_PARENTS_ALWAYS ], |
|
| 141 |
[ l(:label_view_option_parents_show_and_group), VIEW_OPTIONS_SHOW_PARENTS_ORGANIZE_BY_PARENT ] ]) |
|
| 142 |
] |
|
| 143 |
cattr_reader :available_view_options |
|
| 118 | 144 |
|
| 119 | 145 |
def initialize(attributes = nil) |
| 120 | 146 |
super attributes |
| 121 | 147 |
self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
|
| 148 |
self.view_options ||= { 'show_parents' => 'do_not_show' }
|
|
| 122 | 149 |
end |
| 123 | 150 |
|
| 124 | 151 |
def after_initialize |
| ... | ... | |
| 353 | 380 |
|
| 354 | 381 |
(filters_clauses << project_statement).join(' AND ')
|
| 355 | 382 |
end |
| 356 |
|
|
| 383 | ||
| 384 |
def set_view_option( option, value) |
|
| 385 |
self.view_options[option] = value |
|
| 386 |
end |
|
| 387 | ||
| 388 |
def values_for_view_option( option) |
|
| 389 |
@@available_view_options.find { |vo| vo.name == option }.available_values
|
|
| 390 |
end |
|
| 391 | ||
| 392 |
def caption_for_view_option( option) |
|
| 393 |
@@available_view_options.find { |vo| vo.name == option }.caption
|
|
| 394 |
end |
|
| 395 | ||
| 357 | 396 |
private |
| 358 | 397 |
|
| 359 | 398 |
# Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ |
| b/app/models/version.rb | ||
|---|---|---|
| 27 | 27 |
validates_length_of :name, :maximum => 60 |
| 28 | 28 |
validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => :not_a_date, :allow_nil => true
|
| 29 | 29 |
|
| 30 |
include Comparable |
|
| 31 |
|
|
| 30 | 32 |
def start_date |
| 31 | 33 |
effective_date |
| 32 | 34 |
end |
| b/app/views/issues/_edit.rhtml | ||
|---|---|---|
| 15 | 15 |
<%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %>
|
| 16 | 16 |
</fieldset> |
| 17 | 17 |
<% end %> |
| 18 |
<% if authorize_for('timelog', 'edit') %>
|
|
| 18 |
<% if authorize_for('timelog', 'edit') && @issue.leaf? %>
|
|
| 19 | 19 |
<fieldset class="tabular"><legend><%= l(:button_log_time) %></legend> |
| 20 | 20 |
<% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
|
| 21 | 21 |
<div class="splitcontentleft"> |
| b/app/views/issues/_form.rhtml | ||
|---|---|---|
| 1 |
<!-- This function is needed for get ID from the text field with parent issue selection. --> |
|
| 2 |
<script type="text/javascript"> |
|
| 3 |
//<![CDATA[ |
|
| 4 |
function setParentIssueValue(element, value) {
|
|
| 5 |
document.getElementById('parent_issue_id').value = value.id;
|
|
| 6 |
} |
|
| 7 |
//]]> |
|
| 8 |
</script> |
|
| 1 | 9 |
<% if @issue.new_record? %> |
| 2 | 10 |
<p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
|
| 3 | 11 |
<%= observe_field :issue_tracker_id, :url => { :action => :new },
|
| ... | ... | |
| 17 | 25 | |
| 18 | 26 |
<div class="attributes"> |
| 19 | 27 |
<div class="splitcontentleft"> |
| 20 |
<% if @issue.new_record? || @allowed_statuses.any? %>
|
|
| 28 |
<% if (@issue.new_record? || @allowed_statuses.any?) %>
|
|
| 21 | 29 |
<p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
|
| 22 | 30 |
<% else %> |
| 23 | 31 |
<p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p> |
| 24 | 32 |
<% end %> |
| 25 | 33 | |
| 26 | 34 |
<p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p>
|
| 35 | ||
| 27 | 36 |
<p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
|
| 28 | 37 |
<% unless @project.issue_categories.empty? %> |
| 29 | 38 |
<p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
|
| ... | ... | |
| 38 | 47 |
</div> |
| 39 | 48 | |
| 40 | 49 |
<div class="splitcontentright"> |
| 50 |
<% if @issue.new_record? || @issue.leaf? %> |
|
| 41 | 51 |
<p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p>
|
| 42 | 52 |
<p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p>
|
| 43 | 53 |
<p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p> |
| 44 | 54 |
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
|
| 55 |
<% else %> |
|
| 56 |
<p><label><%= l(:field_start_date) %></label> <%= format_date(@issue.start_date) %></p> |
|
| 57 |
<p><label><%= l(:field_due_date) %></label> <%= format_date(@issue.due_date) %></p> |
|
| 58 |
<p><label><%= l(:field_done_ratio) %></label> <%= "#{@issue.done_ratio}%" %></p>
|
|
| 59 |
<% end %> |
|
| 60 |
<%= content_tag( :input, {},
|
|
| 61 |
:id => :parent_issue_id, |
|
| 62 |
:type => :hidden, |
|
| 63 |
:name => 'parent_issue_id', :value => @parent_issue ? @parent_issue.id : "") %> |
|
| 64 |
<p><label><%= l(:field_parent_issue) %></label> |
|
| 65 |
<% if authorize_for( 'issues', 'add_subissue') %> |
|
| 66 |
<%= text_field_with_auto_complete( :issue, :parent, |
|
| 67 |
{ :name => 'issue_parent', :value => @parent_issue || "" },
|
|
| 68 |
:url => { :action => 'auto_complete_for_issue_parent', :project_id => @project},
|
|
| 69 |
:after_update_element => 'setParentIssueValue') %> |
|
| 70 |
<% else %> |
|
| 71 |
<%= @parent_issue || "-" %> |
|
| 72 |
<% end %> |
|
| 73 |
</p> |
|
| 45 | 74 |
</div> |
| 46 | 75 | |
| 47 | 76 |
<div style="clear:both;"> </div> |
| b/app/views/issues/_form_update.rhtml | ||
|---|---|---|
| 4 | 4 |
<p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
|
| 5 | 5 |
</div> |
| 6 | 6 |
<div class="splitcontentright"> |
| 7 |
<% if @issue.leaf? %> |
|
| 7 | 8 |
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
|
| 9 |
<% else %> |
|
| 10 |
<p><label><%= l(:field_done_ratio) %></label> <%= "#{@issue.done_ratio}%" %></p>
|
|
| 11 |
<% end %> |
|
| 8 | 12 |
<%= content_tag('p', f.select(:fixed_version_id,
|
| 9 | 13 |
(@project.versions.sort.collect {|v| [v.name, v.id]}),
|
| 10 | 14 |
{ :include_blank => true })) unless @project.versions.empty? %>
|
| b/app/views/issues/_list.rhtml | ||
|---|---|---|
| 1 |
<% form_tag({}) do -%>
|
|
| 2 |
<table class="list issues"> |
|
| 1 |
<% form_tag({}) do -%>
|
|
| 2 |
<table class="list issues">
|
|
| 3 | 3 |
<thead><tr> |
| 4 | 4 |
<th><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;',
|
| 5 |
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
|
|
| 5 |
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
|
|
| 6 | 6 |
</th> |
| 7 |
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
|
|
| 7 |
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
|
|
| 8 | 8 |
<% query.columns.each do |column| %> |
| 9 | 9 |
<%= column_header(column) %> |
| 10 | 10 |
<% end %> |
| 11 |
</tr></thead> |
|
| 12 |
<tbody> |
|
| 11 |
</tr></thead> |
|
| 12 |
<tbody> |
|
| 13 |
<% if query.view_options['show_parents'] == Query::VIEW_OPTIONS_SHOW_PARENTS_NEVER -%> |
|
| 13 | 14 |
<% issues.each do |issue| -%> |
| 14 |
<tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= css_issue_classes(issue) %>">
|
|
| 15 |
<td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
|
|
| 16 |
<td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td> |
|
| 17 |
<% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %> |
|
| 18 |
</tr> |
|
| 15 |
<%= issue_content( issue, query) %> |
|
| 19 | 16 |
<% end -%> |
| 20 |
</tbody> |
|
| 21 |
</table> |
|
| 17 |
<% elsif query.view_options['show_parents'] == Query::VIEW_OPTIONS_SHOW_PARENTS_ALWAYS -%> |
|
| 18 |
<% issues.each do |issue| -%> |
|
| 19 |
<% issue.ancestors.reverse.each do |parent_issue| -%> |
|
| 20 |
<%= issue_content( parent_issue, query, :unfiltered => true) %> |
|
| 21 |
<% end -%> |
|
| 22 |
<%= issue_content( issue, query) %> |
|
| 23 |
<% end -%> |
|
| 24 |
<% elsif query.view_options['show_parents'] == Query::VIEW_OPTIONS_SHOW_PARENTS_ORGANIZE_BY_PARENT -%> |
|
| 25 |
<% parents_on_first_lvl = [] |
|
| 26 |
issues.each do |i| |
|
| 27 |
if i.parent |
|
| 28 |
first_parent = i.root |
|
| 29 |
else |
|
| 30 |
first_parent = i |
|
| 31 |
end |
|
| 32 |
parents_on_first_lvl += [ first_parent ] unless parents_on_first_lvl.include?( first_parent) |
|
| 33 |
end -%> |
|
| 34 |
<% parents_on_first_lvl.each do |parent| -%> |
|
| 35 |
<%= issues_family_content( parent, issues, query) %> |
|
| 36 |
<% end -%> |
|
| 37 |
<% end -%> |
|
| 38 |
</tbody> |
|
| 39 |
</table> |
|
| 22 | 40 |
<% end -%> |
| b/app/views/issues/context_menu.rhtml | ||
|---|---|---|
| 17 | 17 |
<li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)},
|
| 18 | 18 |
:class => 'icon-edit', :disabled => !@can[:edit] %></li> |
| 19 | 19 |
<% end %> |
| 20 | ||
| 21 | 20 |
<li class="folder"> |
| 22 | 21 |
<a href="#" class="submenu"><%= l(:field_priority) %></a> |
| 23 | 22 |
<ul> |
| ... | ... | |
| 66 | 65 |
</ul> |
| 67 | 66 |
</li> |
| 68 | 67 |
<% end -%> |
| 68 |
<% if @issue && @issue.leaf? %> |
|
| 69 | 69 |
<li class="folder"> |
| 70 | 70 |
<a href="#" class="submenu"><%= l(:field_done_ratio) %></a> |
| 71 | 71 |
<ul> |
| ... | ... | |
| 75 | 75 |
<% end -%> |
| 76 | 76 |
</ul> |
| 77 | 77 |
</li> |
| 78 |
|
|
| 78 |
<% end %> |
|
| 79 | 79 |
<% if !@issue.nil? %> |
| 80 | 80 |
<li><%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
|
| 81 | 81 |
:class => 'icon-copy', :disabled => !@can[:copy] %></li> |
| b/app/views/issues/index.rhtml | ||
|---|---|---|
| 4 | 4 |
|
| 5 | 5 |
<% form_tag({ :controller => 'queries', :action => 'new' }, :id => 'query_form') do %>
|
| 6 | 6 |
<%= hidden_field_tag('project_id', @project.to_param) if @project %>
|
| 7 |
<fieldset id="view"><legend><%= l(:label_view) %></legend> |
|
| 8 |
<%= render :partial => 'queries/view_options', :locals => {:query => @query } %>
|
|
| 9 |
</fieldset> |
|
| 7 | 10 |
<fieldset id="filters"><legend><%= l(:label_filter_plural) %></legend> |
| 8 |
<%= render :partial => 'queries/filters', :locals => {:query => @query} %>
|
|
| 11 |
<%= render :partial => 'queries/filters', :locals => {:query => @query} %>
|
|
| 12 |
</fieldset> |
|
| 9 | 13 |
<p class="buttons"> |
| 10 | 14 |
<%= link_to_remote l(:button_apply), |
| 11 | 15 |
{ :url => { :set_filter => 1 },
|
| ... | ... | |
| 23 | 27 |
<%= link_to l(:button_save), {}, :onclick => "$('query_form').submit(); return false;", :class => 'icon icon-save' %>
|
| 24 | 28 |
<% end %> |
| 25 | 29 |
</p> |
| 26 |
</fieldset> |
|
| 27 | 30 |
<% end %> |
| 28 | 31 |
<% else %> |
| 29 | 32 |
<div class="contextual"> |
| b/app/views/issues/show.rhtml | ||
|---|---|---|
| 1 | 1 |
<div class="contextual"> |
| 2 |
<%= link_to_if_authorized(l(:button_add_subissue), |
|
| 3 |
{ :controller => 'issues', :action => 'add_subissue',
|
|
| 4 |
:project_id => @project.id, :issue => { :parent_id => @issue.id }},
|
|
| 5 |
:class => 'icon icon-add') %> |
|
| 2 | 6 |
<%= 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)) %>
|
| 3 | 7 |
<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time-add' %>
|
| 4 | 8 |
<%= watcher_tag(@issue, User.current) %> |
| ... | ... | |
| 51 | 55 |
if (n > 1) |
| 52 | 56 |
n = 0 %> |
| 53 | 57 |
</tr><tr> |
| 54 |
<%end
|
|
| 55 |
end %> |
|
| 58 |
<% end %>
|
|
| 59 |
<% end %>
|
|
| 56 | 60 |
</tr> |
| 57 | 61 |
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %> |
| 58 | 62 |
</table> |
| b/app/views/projects/roadmap.rhtml | ||
|---|---|---|
| 10 | 10 |
<%= render :partial => 'versions/overview', :locals => {:version => version} %>
|
| 11 | 11 |
<%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
|
| 12 | 12 | |
| 13 |
<% issues = version.fixed_issues.find(:all, |
|
| 14 |
:include => [:status, :tracker], |
|
| 15 |
:conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"],
|
|
| 16 |
:order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") unless @selected_tracker_ids.empty?
|
|
| 13 |
<% |
|
| 14 |
unless @selected_tracker_ids.empty? |
|
| 15 |
issues = version.fixed_issues.find(:all, |
|
| 16 |
:include => [:status, :tracker], |
|
| 17 |
:conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"],
|
|
| 18 |
:order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
|
|
| 19 |
issues = Issue.find_with_parents( issues.collect { |i| i.id })
|
|
| 20 |
end |
|
| 17 | 21 |
issues ||= [] |
| 18 | 22 |
%> |
| 19 | 23 |
<% if issues.size > 0 %> |
| 20 | 24 |
<fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend> |
| 21 |
<ul> |
|
| 22 |
<%- issues.each do |issue| -%> |
|
| 23 |
<li><%= link_to_issue(issue) %>: <%=h issue.subject %></li> |
|
| 24 |
<%- end -%> |
|
| 25 |
</ul> |
|
| 25 |
<%= render_list_of_related_issues( issues, version) %> |
|
| 26 | 26 |
</fieldset> |
| 27 | 27 |
<% end %> |
| 28 | 28 |
<%= call_hook :view_projects_roadmap_version_bottom, :version => version %> |
| b/app/views/queries/_form.rhtml | ||
|---|---|---|
| 21 | 21 |
:onclick => 'if (this.checked) {Element.hide("columns")} else {Element.show("columns")}' %></p>
|
| 22 | 22 |
</div> |
| 23 | 23 | |
| 24 |
<fieldset><legend><%= l(:label_view) %></legend> |
|
| 25 |
<%= render :partial => 'queries/view_options', :locals => {:query => query } %>
|
|
| 26 |
</fieldset> |
|
| 27 | ||
| 24 | 28 |
<fieldset><legend><%= l(:label_filter_plural) %></legend> |
| 25 | 29 |
<%= render :partial => 'queries/filters', :locals => {:query => query}%>
|
| 26 | 30 |
</fieldset> |
| b/app/views/queries/_view_options.rhtml | ||
|---|---|---|
| 1 |
<% query.view_options.each_key do |voption| -%> |
|
| 2 |
<%= query.caption_for_view_option( voption) %>: |
|
| 3 |
<%= select_tag( "view_options[#{voption}]",
|
|
| 4 |
options_for_select( query.values_for_view_option( voption), |
|
| 5 |
query.view_options[voption])) %> |
|
| 6 |
<% end %> |
|
| b/app/views/versions/show.rhtml | ||
|---|---|---|
| 31 | 31 |
<%= render :partial => 'versions/overview', :locals => {:version => @version} %>
|
| 32 | 32 |
<%= render(:partial => "wiki/content", :locals => {:content => @version.wiki_page.content}) if @version.wiki_page %>
|
| 33 | 33 | |
| 34 |
<% issues = @version.fixed_issues.find(:all, |
|
| 35 |
:include => [:status, :tracker], |
|
| 36 |
:order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") %>
|
|
| 37 |
<% if issues.size > 0 %> |
|
| 34 |
<% if @issues.size > 0 %> |
|
| 38 | 35 |
<fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend> |
| 39 |
<ul> |
|
| 40 |
<% issues.each do |issue| -%> |
|
| 41 |
<li><%= link_to_issue(issue) %>: <%=h issue.subject %></li> |
|
| 42 |
<% end -%> |
|
| 43 |
</ul> |
|
| 36 |
<%= render_list_of_related_issues( @issues, @version) %> |
|
| 44 | 37 |
</fieldset> |
| 45 | 38 |
<% end %> |
| 46 | 39 |
</div> |
| b/config/locales/en.yml | ||
|---|---|---|
| 136 | 136 |
error_scm_command_failed: "An error occurred when trying to access the repository: {{value}}"
|
| 137 | 137 |
error_scm_annotate: "The entry does not exist or can not be annotated." |
| 138 | 138 |
error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' |
| 139 |
|
|
| 139 |
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent). |
|
| 140 | ||
| 140 | 141 |
warning_attachments_not_saved: "{{count}} file(s) could not be saved."
|
| 141 | 142 |
|
| 142 | 143 |
mail_subject_lost_password: "Your {{value}} password"
|
| ... | ... | |
| 242 | 243 |
field_watcher: Watcher |
| 243 | 244 |
field_identity_url: OpenID URL |
| 244 | 245 |
field_content: Content |
| 246 |
field_calendar_firstday: First day of week |
|
| 247 |
field_parent: Subproject of |
|
| 248 |
field_parent_issue: Child of |
|
| 249 |
field_parent_title: Parent page |
|
| 245 | 250 |
|
| 246 | 251 |
setting_app_title: Application title |
| 247 | 252 |
setting_app_subtitle: Application subtitle |
| ... | ... | |
| 664 | 669 |
label_issue_watchers: Watchers |
| 665 | 670 |
label_example: Example |
| 666 | 671 |
label_display: Display |
| 672 |
label_children: parent of |
|
| 673 |
label_parents: child of |
|
| 674 |
label_view_option_parents_do_not_show: Never |
|
| 675 |
label_view_option_parents_show_always: Always |
|
| 676 |
label_view_option_parents_show_and_group: Organize by parent |
|
| 677 |
label_view_option_show_parents: Show parents |
|
| 667 | 678 |
label_sort: Sort |
| 668 | 679 |
label_ascending: Ascending |
| 669 | 680 |
label_descending: Descending |
| ... | ... | |
| 708 | 719 |
button_update: Update |
| 709 | 720 |
button_configure: Configure |
| 710 | 721 |
button_quote: Quote |
| 711 |
|
|
| 722 |
button_add_subissue: Add sub-issue |
|
| 723 | ||
| 712 | 724 |
status_active: active |
| 713 | 725 |
status_registered: registered |
| 714 | 726 |
status_locked: locked |
| b/db/migrate/20090115162651_add_queries_view_options.rb | ||
|---|---|---|
| 1 |
class AddQueriesViewOptions < ActiveRecord::Migration |
|
| 2 |
def self.up |
|
| 3 |
add_column :queries, :view_options, :text |
|
| 4 |
end |
|
| 5 | ||
| 6 |
def self.down |
|
| 7 |
remove_column :queries, :view_options |
|
| 8 |
end |
|
| 9 |
end |
|
| b/db/migrate/20090121172432_add_default_value_of_view_option_queries.rb | ||
|---|---|---|
| 1 |
class AddDefaultValueOfViewOptionQueries < ActiveRecord::Migration |
|
| 2 |
def self.up |
|
| 3 |
Query.find(:all).each do |q| |
|
| 4 |
q.view_options ||= { 'show_parents' => 'do_not_show' }
|
|
| 5 |
q.save! |
|
| 6 |
end |
|
| 7 |
end |
|
| 8 | ||
| 9 |
def self.down |
|
| 10 |
end |
|
| 11 |
end |
|
| b/db/migrate/20090406213813_add_issues_nested_sets.rb | ||
|---|---|---|
| 1 |
class AddIssuesNestedSets < ActiveRecord::Migration |
|
| 2 |
# IssueRelation::TYPE_PARENTS was deleted |
|
| 3 |
TYPE_PARENTS = "parents" |
|
| 4 |
|
|
| 5 |
def self.up |
|
| 6 |
add_column :issues, :parent_id, :integer, :default => nil |
|
| 7 |
add_column :issues, :lft, :integer |
|
| 8 |
add_column :issues, :rgt, :integer |
|
| 9 |
Issue.reset_column_information |
|
| 10 | ||
| 11 |
say_with_time "fixing invalid issues" do |
|
| 12 |
Issue.find( :all).each do |issue| |
|
| 13 |
if !issue.valid? && issue.errors.on( :due_date) |
|
| 14 |
issue.due_date = issue.start_date |
|
| 15 |
issue.save! |
|
| 16 |
end |
|
| 17 |
end |
|
| 18 |
end |
|
| 19 |
|
|
| 20 |
say_with_time "rebuilding left & right indexes" do |
|
| 21 |
Issue.rebuild! |
|
| 22 |
end |
|
| 23 | ||
| 24 |
say_with_time( |
|
| 25 |
"converting subissues for using parent_id instead of IssueRelation") do |
|
| 26 |
IssueRelation.find_all_by_relation_type( TYPE_PARENTS).each do |rel| |
|
| 27 |
rel.issue_from.move_to_child_of rel.issue_to.id |
|
| 28 |
rel.delete |
|
| 29 |
end |
|
| 30 |
end |
|
| 31 |
end |
|
| 32 | ||
| 33 |
def self.down |
|
| 34 |
raise ActiveRecord::IrreversibleMigration |
|
| 35 |
end |
|
| 36 |
end |
|
| b/lang/en.yml | ||
|---|---|---|
| 1 |
_gloc_rule_default: '|n| n==1 ? "" : "_plural" ' |
|
| 2 | ||
| 3 |
actionview_datehelper_select_day_prefix: |
|
| 4 |
actionview_datehelper_select_month_names: January,February,March,April,May,June,July,August,September,October,November,December |
|
| 5 |
actionview_datehelper_select_month_names_abbr: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec |
|
| 6 |
actionview_datehelper_select_month_prefix: |
|
| 7 |
actionview_datehelper_select_year_prefix: |
|
| 8 |
actionview_datehelper_time_in_words_day: 1 day |
|
| 9 |
actionview_datehelper_time_in_words_day_plural: %d days |
|
| 10 |
actionview_datehelper_time_in_words_hour_about: about an hour |
|
| 11 |
actionview_datehelper_time_in_words_hour_about_plural: about %d hours |
|
| 12 |
actionview_datehelper_time_in_words_hour_about_single: about an hour |
|
| 13 |
actionview_datehelper_time_in_words_minute: 1 minute |
|
| 14 |
actionview_datehelper_time_in_words_minute_half: half a minute |
|
| 15 |
actionview_datehelper_time_in_words_minute_less_than: less than a minute |
|
| 16 |
actionview_datehelper_time_in_words_minute_plural: %d minutes |
|
| 17 |
actionview_datehelper_time_in_words_minute_single: 1 minute |
|
| 18 |
actionview_datehelper_time_in_words_second_less_than: less than a second |
|
| 19 |
actionview_datehelper_time_in_words_second_less_than_plural: less than %d seconds |
|
| 20 |
actionview_instancetag_blank_option: Please select |
|
| 21 | ||
| 22 |
activerecord_error_inclusion: is not included in the list |
|
| 23 |
activerecord_error_exclusion: is reserved |
|
| 24 |
activerecord_error_invalid: is invalid |
|
| 25 |
activerecord_error_confirmation: doesn't match confirmation |
|
| 26 |
activerecord_error_accepted: must be accepted |
|
| 27 |
activerecord_error_empty: can't be empty |
|
| 28 |
activerecord_error_blank: can't be blank |
|
| 29 |
activerecord_error_too_long: is too long |
|
| 30 |
activerecord_error_too_short: is too short |
|
| 31 |
activerecord_error_wrong_length: is the wrong length |
|
| 32 |
activerecord_error_taken: has already been taken |
|
| 33 |
activerecord_error_not_a_number: is not a number |
|
| 34 |
activerecord_error_not_a_date: is not a valid date |
|
| 35 |
activerecord_error_greater_than_start_date: must be greater than start date |
|
| 36 |
activerecord_error_not_same_project: doesn't belong to the same project |
|
| 37 |
activerecord_error_circular_dependency: This relation would create a circular dependency |
|
| 38 | ||
| 39 |
general_fmt_age: %d yr |
|
| 40 |
general_fmt_age_plural: %d yrs |
|
| 41 |
general_fmt_date: %%m/%%d/%%Y |
|
| 42 |
general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p |
|
| 43 |
general_fmt_datetime_short: %%b %%d, %%I:%%M %%p |
|
| 44 |
general_fmt_time: %%I:%%M %%p |
|
| 45 |
general_text_No: 'No' |
|
| 46 |
general_text_Yes: 'Yes' |
|
| 47 |
general_text_no: 'no' |
|
| 48 |
general_text_yes: 'yes' |
|
| 49 |
general_lang_name: 'English' |
|
| 50 |
general_csv_separator: ',' |
|
| 51 |
general_csv_decimal_separator: '.' |
|
| 52 |
general_csv_encoding: ISO-8859-1 |
|
| 53 |
general_pdf_encoding: ISO-8859-1 |
|
| 54 |
general_day_names: Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday |
|
| 55 |
general_first_day_of_week: '7' |
|
| 56 | ||
| 57 |
notice_account_updated: Account was successfully updated. |
|
| 58 |
notice_account_invalid_creditentials: Invalid user or password |
|
| 59 |
notice_account_password_updated: Password was successfully updated. |
|
| 60 |
notice_account_wrong_password: Wrong password |
|
| 61 |
notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you. |
|
| 62 |
notice_account_unknown_email: Unknown user. |
|
| 63 |
notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. |
|
| 64 |
notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. |
|
| 65 |
notice_account_activated: Your account has been activated. You can now log in. |
|
| 66 |
notice_successful_create: Successful creation. |
|
| 67 |
notice_successful_update: Successful update. |
|
| 68 |
notice_successful_delete: Successful deletion. |
|
| 69 |
notice_successful_connection: Successful connection. |
|
| 70 |
notice_file_not_found: The page you were trying to access doesn't exist or has been removed. |
|
| 71 |
notice_locking_conflict: Data has been updated by another user. |
|
| 72 |
notice_not_authorized: You are not authorized to access this page. |
|
| 73 |
notice_email_sent: An email was sent to %s |
|
| 74 |
notice_email_error: An error occurred while sending mail (%s) |
|
| 75 |
notice_feeds_access_key_reseted: Your RSS access key was reset. |
|
| 76 |
notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s." |
|
| 77 |
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." |
|
| 78 |
notice_account_pending: "Your account was created and is now pending administrator approval." |
|
| 79 |
notice_default_data_loaded: Default configuration successfully loaded. |
|
| 80 |
notice_unable_delete_version: Unable to delete version. |
|
| 81 | ||