##// END OF EJS Templates
Use IssueRelation#to_s....
Jean-Philippe Lang -
r13183:4d6c52a82b44
parent child
Show More
@@ -1,206 +1,204
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module QueriesHelper
20 module QueriesHelper
21 include ApplicationHelper
21 include ApplicationHelper
22
22
23 def filters_options_for_select(query)
23 def filters_options_for_select(query)
24 options_for_select(filters_options(query))
24 options_for_select(filters_options(query))
25 end
25 end
26
26
27 def filters_options(query)
27 def filters_options(query)
28 options = [[]]
28 options = [[]]
29 options += query.available_filters.map do |field, field_options|
29 options += query.available_filters.map do |field, field_options|
30 [field_options[:name], field]
30 [field_options[:name], field]
31 end
31 end
32 end
32 end
33
33
34 def query_filters_hidden_tags(query)
34 def query_filters_hidden_tags(query)
35 tags = ''.html_safe
35 tags = ''.html_safe
36 query.filters.each do |field, options|
36 query.filters.each do |field, options|
37 tags << hidden_field_tag("f[]", field, :id => nil)
37 tags << hidden_field_tag("f[]", field, :id => nil)
38 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
38 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
39 options[:values].each do |value|
39 options[:values].each do |value|
40 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
40 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
41 end
41 end
42 end
42 end
43 tags
43 tags
44 end
44 end
45
45
46 def query_columns_hidden_tags(query)
46 def query_columns_hidden_tags(query)
47 tags = ''.html_safe
47 tags = ''.html_safe
48 query.columns.each do |column|
48 query.columns.each do |column|
49 tags << hidden_field_tag("c[]", column.name, :id => nil)
49 tags << hidden_field_tag("c[]", column.name, :id => nil)
50 end
50 end
51 tags
51 tags
52 end
52 end
53
53
54 def query_hidden_tags(query)
54 def query_hidden_tags(query)
55 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
55 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
56 end
56 end
57
57
58 def available_block_columns_tags(query)
58 def available_block_columns_tags(query)
59 tags = ''.html_safe
59 tags = ''.html_safe
60 query.available_block_columns.each do |column|
60 query.available_block_columns.each do |column|
61 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
61 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
62 end
62 end
63 tags
63 tags
64 end
64 end
65
65
66 def query_available_inline_columns_options(query)
66 def query_available_inline_columns_options(query)
67 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
67 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
68 end
68 end
69
69
70 def query_selected_inline_columns_options(query)
70 def query_selected_inline_columns_options(query)
71 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
71 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
72 end
72 end
73
73
74 def render_query_columns_selection(query, options={})
74 def render_query_columns_selection(query, options={})
75 tag_name = (options[:name] || 'c') + '[]'
75 tag_name = (options[:name] || 'c') + '[]'
76 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
76 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
77 end
77 end
78
78
79 def column_header(column)
79 def column_header(column)
80 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
80 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
81 :default_order => column.default_order) :
81 :default_order => column.default_order) :
82 content_tag('th', h(column.caption))
82 content_tag('th', h(column.caption))
83 end
83 end
84
84
85 def column_content(column, issue)
85 def column_content(column, issue)
86 value = column.value_object(issue)
86 value = column.value_object(issue)
87 if value.is_a?(Array)
87 if value.is_a?(Array)
88 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
88 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
89 else
89 else
90 column_value(column, issue, value)
90 column_value(column, issue, value)
91 end
91 end
92 end
92 end
93
93
94 def column_value(column, issue, value)
94 def column_value(column, issue, value)
95 case column.name
95 case column.name
96 when :id
96 when :id
97 link_to value, issue_path(issue)
97 link_to value, issue_path(issue)
98 when :subject
98 when :subject
99 link_to value, issue_path(issue)
99 link_to value, issue_path(issue)
100 when :parent
100 when :parent
101 value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
101 value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
102 when :description
102 when :description
103 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
103 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
104 when :done_ratio
104 when :done_ratio
105 progress_bar(value, :width => '80px')
105 progress_bar(value, :width => '80px')
106 when :relations
106 when :relations
107 other = value.other_issue(issue)
108 content_tag('span',
107 content_tag('span',
109 (l(value.label_for(issue)) + " " + link_to_issue(other, :subject => false, :tracker => false)).html_safe,
108 value.to_s(issue) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
110 :class => value.css_classes_for(issue))
109 :class => value.css_classes_for(issue))
111 else
110 else
112 format_object(value)
111 format_object(value)
113 end
112 end
114 end
113 end
115
114
116 def csv_content(column, issue)
115 def csv_content(column, issue)
117 value = column.value_object(issue)
116 value = column.value_object(issue)
118 if value.is_a?(Array)
117 if value.is_a?(Array)
119 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
118 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
120 else
119 else
121 csv_value(column, issue, value)
120 csv_value(column, issue, value)
122 end
121 end
123 end
122 end
124
123
125 def csv_value(column, object, value)
124 def csv_value(column, object, value)
126 format_object(value, false) do |value|
125 format_object(value, false) do |value|
127 case value.class.name
126 case value.class.name
128 when 'Float'
127 when 'Float'
129 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
128 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
130 when 'IssueRelation'
129 when 'IssueRelation'
131 other = value.other_issue(object)
130 value.to_s(object)
132 l(value.label_for(object)) + " ##{other.id}"
133 when 'Issue'
131 when 'Issue'
134 if object.is_a?(TimeEntry)
132 if object.is_a?(TimeEntry)
135 "#{value.tracker} ##{value.id}: #{value.subject}"
133 "#{value.tracker} ##{value.id}: #{value.subject}"
136 else
134 else
137 value.id
135 value.id
138 end
136 end
139 else
137 else
140 value
138 value
141 end
139 end
142 end
140 end
143 end
141 end
144
142
145 def query_to_csv(items, query, options={})
143 def query_to_csv(items, query, options={})
146 encoding = l(:general_csv_encoding)
144 encoding = l(:general_csv_encoding)
147 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
145 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
148 query.available_block_columns.each do |column|
146 query.available_block_columns.each do |column|
149 if options[column.name].present?
147 if options[column.name].present?
150 columns << column
148 columns << column
151 end
149 end
152 end
150 end
153
151
154 export = CSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
152 export = CSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
155 # csv header fields
153 # csv header fields
156 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
154 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
157 # csv lines
155 # csv lines
158 items.each do |item|
156 items.each do |item|
159 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) }
157 csv << columns.collect {|c| Redmine::CodesetUtil.from_utf8(csv_content(c, item), encoding) }
160 end
158 end
161 end
159 end
162 export
160 export
163 end
161 end
164
162
165 # Retrieve query from session or build a new query
163 # Retrieve query from session or build a new query
166 def retrieve_query
164 def retrieve_query
167 if !params[:query_id].blank?
165 if !params[:query_id].blank?
168 cond = "project_id IS NULL"
166 cond = "project_id IS NULL"
169 cond << " OR project_id = #{@project.id}" if @project
167 cond << " OR project_id = #{@project.id}" if @project
170 @query = IssueQuery.where(cond).find(params[:query_id])
168 @query = IssueQuery.where(cond).find(params[:query_id])
171 raise ::Unauthorized unless @query.visible?
169 raise ::Unauthorized unless @query.visible?
172 @query.project = @project
170 @query.project = @project
173 session[:query] = {:id => @query.id, :project_id => @query.project_id}
171 session[:query] = {:id => @query.id, :project_id => @query.project_id}
174 sort_clear
172 sort_clear
175 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
173 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
176 # Give it a name, required to be valid
174 # Give it a name, required to be valid
177 @query = IssueQuery.new(:name => "_")
175 @query = IssueQuery.new(:name => "_")
178 @query.project = @project
176 @query.project = @project
179 @query.build_from_params(params)
177 @query.build_from_params(params)
180 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
178 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
181 else
179 else
182 # retrieve from session
180 # retrieve from session
183 @query = nil
181 @query = nil
184 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
182 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
185 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
183 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
186 @query.project = @project
184 @query.project = @project
187 end
185 end
188 end
186 end
189
187
190 def retrieve_query_from_session
188 def retrieve_query_from_session
191 if session[:query]
189 if session[:query]
192 if session[:query][:id]
190 if session[:query][:id]
193 @query = IssueQuery.find_by_id(session[:query][:id])
191 @query = IssueQuery.find_by_id(session[:query][:id])
194 return unless @query
192 return unless @query
195 else
193 else
196 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
194 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
197 end
195 end
198 if session[:query].has_key?(:project_id)
196 if session[:query].has_key?(:project_id)
199 @query.project_id = session[:query][:project_id]
197 @query.project_id = session[:query][:project_id]
200 else
198 else
201 @query.project = @project
199 @query.project = @project
202 end
200 end
203 @query
201 @query
204 end
202 end
205 end
203 end
206 end
204 end
@@ -1,215 +1,215
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssueRelation < ActiveRecord::Base
18 class IssueRelation < ActiveRecord::Base
19 # Class used to represent the relations of an issue
19 # Class used to represent the relations of an issue
20 class Relations < Array
20 class Relations < Array
21 include Redmine::I18n
21 include Redmine::I18n
22
22
23 def initialize(issue, *args)
23 def initialize(issue, *args)
24 @issue = issue
24 @issue = issue
25 super(*args)
25 super(*args)
26 end
26 end
27
27
28 def to_s(*args)
28 def to_s(*args)
29 map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ')
29 map {|relation| relation.to_s(@issue)}.join(', ')
30 end
30 end
31 end
31 end
32
32
33 belongs_to :issue_from, :class_name => 'Issue'
33 belongs_to :issue_from, :class_name => 'Issue'
34 belongs_to :issue_to, :class_name => 'Issue'
34 belongs_to :issue_to, :class_name => 'Issue'
35
35
36 TYPE_RELATES = "relates"
36 TYPE_RELATES = "relates"
37 TYPE_DUPLICATES = "duplicates"
37 TYPE_DUPLICATES = "duplicates"
38 TYPE_DUPLICATED = "duplicated"
38 TYPE_DUPLICATED = "duplicated"
39 TYPE_BLOCKS = "blocks"
39 TYPE_BLOCKS = "blocks"
40 TYPE_BLOCKED = "blocked"
40 TYPE_BLOCKED = "blocked"
41 TYPE_PRECEDES = "precedes"
41 TYPE_PRECEDES = "precedes"
42 TYPE_FOLLOWS = "follows"
42 TYPE_FOLLOWS = "follows"
43 TYPE_COPIED_TO = "copied_to"
43 TYPE_COPIED_TO = "copied_to"
44 TYPE_COPIED_FROM = "copied_from"
44 TYPE_COPIED_FROM = "copied_from"
45
45
46 TYPES = {
46 TYPES = {
47 TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to,
47 TYPE_RELATES => { :name => :label_relates_to, :sym_name => :label_relates_to,
48 :order => 1, :sym => TYPE_RELATES },
48 :order => 1, :sym => TYPE_RELATES },
49 TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by,
49 TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by,
50 :order => 2, :sym => TYPE_DUPLICATED },
50 :order => 2, :sym => TYPE_DUPLICATED },
51 TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates,
51 TYPE_DUPLICATED => { :name => :label_duplicated_by, :sym_name => :label_duplicates,
52 :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
52 :order => 3, :sym => TYPE_DUPLICATES, :reverse => TYPE_DUPLICATES },
53 TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by,
53 TYPE_BLOCKS => { :name => :label_blocks, :sym_name => :label_blocked_by,
54 :order => 4, :sym => TYPE_BLOCKED },
54 :order => 4, :sym => TYPE_BLOCKED },
55 TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks,
55 TYPE_BLOCKED => { :name => :label_blocked_by, :sym_name => :label_blocks,
56 :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
56 :order => 5, :sym => TYPE_BLOCKS, :reverse => TYPE_BLOCKS },
57 TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows,
57 TYPE_PRECEDES => { :name => :label_precedes, :sym_name => :label_follows,
58 :order => 6, :sym => TYPE_FOLLOWS },
58 :order => 6, :sym => TYPE_FOLLOWS },
59 TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes,
59 TYPE_FOLLOWS => { :name => :label_follows, :sym_name => :label_precedes,
60 :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES },
60 :order => 7, :sym => TYPE_PRECEDES, :reverse => TYPE_PRECEDES },
61 TYPE_COPIED_TO => { :name => :label_copied_to, :sym_name => :label_copied_from,
61 TYPE_COPIED_TO => { :name => :label_copied_to, :sym_name => :label_copied_from,
62 :order => 8, :sym => TYPE_COPIED_FROM },
62 :order => 8, :sym => TYPE_COPIED_FROM },
63 TYPE_COPIED_FROM => { :name => :label_copied_from, :sym_name => :label_copied_to,
63 TYPE_COPIED_FROM => { :name => :label_copied_from, :sym_name => :label_copied_to,
64 :order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO }
64 :order => 9, :sym => TYPE_COPIED_TO, :reverse => TYPE_COPIED_TO }
65 }.freeze
65 }.freeze
66
66
67 validates_presence_of :issue_from, :issue_to, :relation_type
67 validates_presence_of :issue_from, :issue_to, :relation_type
68 validates_inclusion_of :relation_type, :in => TYPES.keys
68 validates_inclusion_of :relation_type, :in => TYPES.keys
69 validates_numericality_of :delay, :allow_nil => true
69 validates_numericality_of :delay, :allow_nil => true
70 validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
70 validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
71 validate :validate_issue_relation
71 validate :validate_issue_relation
72
72
73 attr_protected :issue_from_id, :issue_to_id
73 attr_protected :issue_from_id, :issue_to_id
74 before_save :handle_issue_order
74 before_save :handle_issue_order
75 after_create :call_issues_relation_added_callback
75 after_create :call_issues_relation_added_callback
76 after_destroy :call_issues_relation_removed_callback
76 after_destroy :call_issues_relation_removed_callback
77
77
78 def visible?(user=User.current)
78 def visible?(user=User.current)
79 (issue_from.nil? || issue_from.visible?(user)) && (issue_to.nil? || issue_to.visible?(user))
79 (issue_from.nil? || issue_from.visible?(user)) && (issue_to.nil? || issue_to.visible?(user))
80 end
80 end
81
81
82 def deletable?(user=User.current)
82 def deletable?(user=User.current)
83 visible?(user) &&
83 visible?(user) &&
84 ((issue_from.nil? || user.allowed_to?(:manage_issue_relations, issue_from.project)) ||
84 ((issue_from.nil? || user.allowed_to?(:manage_issue_relations, issue_from.project)) ||
85 (issue_to.nil? || user.allowed_to?(:manage_issue_relations, issue_to.project)))
85 (issue_to.nil? || user.allowed_to?(:manage_issue_relations, issue_to.project)))
86 end
86 end
87
87
88 def initialize(attributes=nil, *args)
88 def initialize(attributes=nil, *args)
89 super
89 super
90 if new_record?
90 if new_record?
91 if relation_type.blank?
91 if relation_type.blank?
92 self.relation_type = IssueRelation::TYPE_RELATES
92 self.relation_type = IssueRelation::TYPE_RELATES
93 end
93 end
94 end
94 end
95 end
95 end
96
96
97 def validate_issue_relation
97 def validate_issue_relation
98 if issue_from && issue_to
98 if issue_from && issue_to
99 errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
99 errors.add :issue_to_id, :invalid if issue_from_id == issue_to_id
100 unless issue_from.project_id == issue_to.project_id ||
100 unless issue_from.project_id == issue_to.project_id ||
101 Setting.cross_project_issue_relations?
101 Setting.cross_project_issue_relations?
102 errors.add :issue_to_id, :not_same_project
102 errors.add :issue_to_id, :not_same_project
103 end
103 end
104 # detect circular dependencies depending wether the relation should be reversed
104 # detect circular dependencies depending wether the relation should be reversed
105 if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
105 if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
106 errors.add :base, :circular_dependency if issue_from.all_dependent_issues.include? issue_to
106 errors.add :base, :circular_dependency if issue_from.all_dependent_issues.include? issue_to
107 else
107 else
108 errors.add :base, :circular_dependency if issue_to.all_dependent_issues.include? issue_from
108 errors.add :base, :circular_dependency if issue_to.all_dependent_issues.include? issue_from
109 end
109 end
110 if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
110 if issue_from.is_descendant_of?(issue_to) || issue_from.is_ancestor_of?(issue_to)
111 errors.add :base, :cant_link_an_issue_with_a_descendant
111 errors.add :base, :cant_link_an_issue_with_a_descendant
112 end
112 end
113 end
113 end
114 end
114 end
115
115
116 def other_issue(issue)
116 def other_issue(issue)
117 (self.issue_from_id == issue.id) ? issue_to : issue_from
117 (self.issue_from_id == issue.id) ? issue_to : issue_from
118 end
118 end
119
119
120 # Returns the relation type for +issue+
120 # Returns the relation type for +issue+
121 def relation_type_for(issue)
121 def relation_type_for(issue)
122 if TYPES[relation_type]
122 if TYPES[relation_type]
123 if self.issue_from_id == issue.id
123 if self.issue_from_id == issue.id
124 relation_type
124 relation_type
125 else
125 else
126 TYPES[relation_type][:sym]
126 TYPES[relation_type][:sym]
127 end
127 end
128 end
128 end
129 end
129 end
130
130
131 def label_for(issue)
131 def label_for(issue)
132 TYPES[relation_type] ?
132 TYPES[relation_type] ?
133 TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] :
133 TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] :
134 :unknow
134 :unknow
135 end
135 end
136
136
137 def to_s(issue=nil)
137 def to_s(issue=nil)
138 issue ||= issue_from
138 issue ||= issue_from
139 issue_text = block_given? ? yield(other_issue(issue)) : "##{other_issue(issue).try(:id)}"
139 issue_text = block_given? ? yield(other_issue(issue)) : "##{other_issue(issue).try(:id)}"
140 s = []
140 s = []
141 s << l(label_for(issue))
141 s << l(label_for(issue))
142 s << "(#{l('datetime.distance_in_words.x_days', :count => delay)})" if delay && delay != 0
142 s << "(#{l('datetime.distance_in_words.x_days', :count => delay)})" if delay && delay != 0
143 s << issue_text
143 s << issue_text
144 s.join(' ')
144 s.join(' ')
145 end
145 end
146
146
147 def css_classes_for(issue)
147 def css_classes_for(issue)
148 "rel-#{relation_type_for(issue)}"
148 "rel-#{relation_type_for(issue)}"
149 end
149 end
150
150
151 def handle_issue_order
151 def handle_issue_order
152 reverse_if_needed
152 reverse_if_needed
153
153
154 if TYPE_PRECEDES == relation_type
154 if TYPE_PRECEDES == relation_type
155 self.delay ||= 0
155 self.delay ||= 0
156 else
156 else
157 self.delay = nil
157 self.delay = nil
158 end
158 end
159 set_issue_to_dates
159 set_issue_to_dates
160 end
160 end
161
161
162 def set_issue_to_dates
162 def set_issue_to_dates
163 soonest_start = self.successor_soonest_start
163 soonest_start = self.successor_soonest_start
164 if soonest_start && issue_to
164 if soonest_start && issue_to
165 issue_to.reschedule_on!(soonest_start)
165 issue_to.reschedule_on!(soonest_start)
166 end
166 end
167 end
167 end
168
168
169 def successor_soonest_start
169 def successor_soonest_start
170 if (TYPE_PRECEDES == self.relation_type) && delay && issue_from &&
170 if (TYPE_PRECEDES == self.relation_type) && delay && issue_from &&
171 (issue_from.start_date || issue_from.due_date)
171 (issue_from.start_date || issue_from.due_date)
172 (issue_from.due_date || issue_from.start_date) + 1 + delay
172 (issue_from.due_date || issue_from.start_date) + 1 + delay
173 end
173 end
174 end
174 end
175
175
176 def <=>(relation)
176 def <=>(relation)
177 r = TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
177 r = TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
178 r == 0 ? id <=> relation.id : r
178 r == 0 ? id <=> relation.id : r
179 end
179 end
180
180
181 def init_journals(user)
181 def init_journals(user)
182 issue_from.init_journal(user) if issue_from
182 issue_from.init_journal(user) if issue_from
183 issue_to.init_journal(user) if issue_to
183 issue_to.init_journal(user) if issue_to
184 end
184 end
185
185
186 private
186 private
187
187
188 # Reverses the relation if needed so that it gets stored in the proper way
188 # Reverses the relation if needed so that it gets stored in the proper way
189 # Should not be reversed before validation so that it can be displayed back
189 # Should not be reversed before validation so that it can be displayed back
190 # as entered on new relation form
190 # as entered on new relation form
191 def reverse_if_needed
191 def reverse_if_needed
192 if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
192 if TYPES.has_key?(relation_type) && TYPES[relation_type][:reverse]
193 issue_tmp = issue_to
193 issue_tmp = issue_to
194 self.issue_to = issue_from
194 self.issue_to = issue_from
195 self.issue_from = issue_tmp
195 self.issue_from = issue_tmp
196 self.relation_type = TYPES[relation_type][:reverse]
196 self.relation_type = TYPES[relation_type][:reverse]
197 end
197 end
198 end
198 end
199
199
200 def call_issues_relation_added_callback
200 def call_issues_relation_added_callback
201 call_issues_callback :relation_added
201 call_issues_callback :relation_added
202 end
202 end
203
203
204 def call_issues_relation_removed_callback
204 def call_issues_relation_removed_callback
205 call_issues_callback :relation_removed
205 call_issues_callback :relation_removed
206 end
206 end
207
207
208 def call_issues_callback(name)
208 def call_issues_callback(name)
209 [issue_from, issue_to].each do |issue|
209 [issue_from, issue_to].each do |issue|
210 if issue
210 if issue
211 issue.send name, self
211 issue.send name, self
212 end
212 end
213 end
213 end
214 end
214 end
215 end
215 end
General Comments 0
You need to be logged in to leave comments. Login now