##// END OF EJS Templates
Replaces acts_as_list with an implementation that handles #position= (#12909)....
Jean-Philippe Lang -
r14953:64afa24a7f72
parent child
Show More
@@ -0,0 +1,13
1 class RemovePositionDefaults < ActiveRecord::Migration
2 def up
3 [Board, CustomField, Enumeration, IssueStatus, Role, Tracker].each do |klass|
4 change_column klass.table_name, :position, :integer, :default => nil
5 end
6 end
7
8 def down
9 [Board, CustomField, Enumeration, IssueStatus, Role, Tracker].each do |klass|
10 change_column klass.table_name, :position, :integer, :default => 1
11 end
12 end
13 end
@@ -0,0 +1,134
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 module Redmine
19 module Acts
20 module Positioned
21 def self.included(base)
22 base.extend ClassMethods
23 end
24
25 # This extension provides the capabilities for reordering objects in a list.
26 # The class needs to have a +position+ column defined as an integer on the
27 # mapped database table.
28 module ClassMethods
29 # Configuration options are:
30 #
31 # * +scope+ - restricts what is to be considered a list. Must be a symbol
32 # or an array of symbols
33 def acts_as_positioned(options = {})
34 class_attribute :positioned_options
35 self.positioned_options = {:scope => Array(options[:scope])}
36
37 send :include, Redmine::Acts::Positioned::InstanceMethods
38
39 before_save :set_default_position
40 after_save :update_position
41 after_destroy :remove_position
42 end
43 end
44
45 module InstanceMethods
46 def self.included(base)
47 base.extend ClassMethods
48 end
49
50 # Move to the given position
51 # For compatibility with the previous way of sorting items
52 def move_to=(pos)
53 case pos.to_s
54 when 'highest'
55 self.position = 1
56 when 'higher'
57 self.position -= 1 if position > 1
58 when 'lower'
59 self.position += 1
60 when 'lowest'
61 self.position = nil
62 set_default_position
63 end
64 end
65
66 private
67
68 def position_scope
69 build_position_scope {|c| send(c)}
70 end
71
72 def position_scope_was
73 build_position_scope {|c| send("#{c}_was")}
74 end
75
76 def build_position_scope
77 condition_hash = self.class.positioned_options[:scope].inject({}) do |h, column|
78 h[column] = yield(column)
79 h
80 end
81 self.class.where(condition_hash)
82 end
83
84 def set_default_position
85 if position.nil?
86 self.position = position_scope.maximum(:position).to_i + (new_record? ? 1 : 0)
87 end
88 end
89
90 def update_position
91 if !new_record? && position_scope_changed?
92 remove_position
93 insert_position
94 elsif position_changed?
95 if position_was.nil?
96 insert_position
97 else
98 shift_positions
99 end
100 end
101 end
102
103 def insert_position
104 position_scope.where("position >= ? AND id <> ?", position, id).update_all("position = position + 1")
105 end
106
107 def remove_position
108 position_scope_was.where("position >= ? AND id <> ?", position_was, id).update_all("position = position - 1")
109 end
110
111 def position_scope_changed?
112 (changed & self.class.positioned_options[:scope].map(&:to_s)).any?
113 end
114
115 def shift_positions
116 offset = position_was <=> position
117 min, max = [position, position_was].sort
118 r = position_scope.where("id <> ? AND position BETWEEN ? AND ?", id, min, max).update_all("position = position + #{offset}")
119 if r != max - min
120 reset_positions_in_list
121 end
122 end
123
124 def reset_positions_in_list
125 position_scope.reorder(:position, :id).pluck(:id).each_with_index do |record_id, p|
126 self.class.where(:id => record_id).update_all(:position => p+1)
127 end
128 end
129 end
130 end
131 end
132 end
133
134 ActiveRecord::Base.send :include, Redmine::Acts::Positioned
@@ -0,0 +1,53
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../../../../test_helper', __FILE__)
19
20 class Redmine::Acts::PositionedWithScopeTest < ActiveSupport::TestCase
21 fixtures :projects, :boards
22
23 def test_create_should_default_to_last_position
24 b = Board.generate!(:project_id => 1)
25 assert_equal 3, b.reload.position
26
27 b = Board.generate!(:project_id => 3)
28 assert_equal 1, b.reload.position
29 end
30
31 def test_create_should_insert_at_given_position
32 b = Board.generate!(:project_id => 1, :position => 2)
33
34 assert_equal 2, b.reload.position
35 assert_equal [1, 3, 1, 2], Board.order(:id).pluck(:position)
36 end
37
38 def test_destroy_should_remove_position
39 b = Board.generate!(:project_id => 1, :position => 2)
40 b.destroy
41
42 assert_equal [1, 2, 1], Board.order(:id).pluck(:position)
43 end
44
45 def test_update_should_update_positions
46 b = Board.generate!(:project_id => 1)
47 assert_equal 3, b.position
48
49 b.position = 2
50 b.save!
51 assert_equal [1, 3, 1, 2], Board.order(:id).pluck(:position)
52 end
53 end
@@ -0,0 +1,55
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../../../../test_helper', __FILE__)
19
20 class Redmine::Acts::PositionedWithoutScopeTest < ActiveSupport::TestCase
21 fixtures :trackers, :issue_statuses
22
23 def test_create_should_default_to_last_position
24 t = Tracker.generate
25 t.save!
26
27 assert_equal 4, t.reload.position
28 end
29
30 def test_create_should_insert_at_given_position
31 t = Tracker.generate
32 t.position = 2
33 t.save!
34
35 assert_equal 2, t.reload.position
36 assert_equal [1, 3, 4, 2], Tracker.order(:id).pluck(:position)
37 end
38
39 def test_destroy_should_remove_position
40 t = Tracker.generate!
41 Tracker.generate!
42 t.destroy
43
44 assert_equal [1, 2, 3, 4], Tracker.order(:id).pluck(:position)
45 end
46
47 def test_update_should_update_positions
48 t = Tracker.generate!
49 assert_equal 4, t.position
50
51 t.position = 2
52 t.save!
53 assert_equal [1, 3, 4, 2], Tracker.order(:id).pluck(:position)
54 end
55 end
@@ -1,96 +1,96
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Board < ActiveRecord::Base
19 19 include Redmine::SafeAttributes
20 20 belongs_to :project
21 21 has_many :messages, lambda {order("#{Message.table_name}.created_on DESC")}, :dependent => :destroy
22 22 belongs_to :last_message, :class_name => 'Message'
23 23 acts_as_tree :dependent => :nullify
24 acts_as_list :scope => '(project_id = #{project_id} AND parent_id #{parent_id ? "= #{parent_id}" : "IS NULL"})'
24 acts_as_positioned :scope => [:project_id, :parent_id]
25 25 acts_as_watchable
26 26
27 27 validates_presence_of :name, :description
28 28 validates_length_of :name, :maximum => 30
29 29 validates_length_of :description, :maximum => 255
30 30 validate :validate_board
31 31 attr_protected :id
32 32
33 33 scope :visible, lambda {|*args|
34 34 joins(:project).
35 35 where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args))
36 36 }
37 37
38 38 safe_attributes 'name', 'description', 'parent_id', 'move_to'
39 39
40 40 def visible?(user=User.current)
41 41 !user.nil? && user.allowed_to?(:view_messages, project)
42 42 end
43 43
44 44 def reload(*args)
45 45 @valid_parents = nil
46 46 super
47 47 end
48 48
49 49 def to_s
50 50 name
51 51 end
52 52
53 53 # Returns a scope for the board topics (messages without parent)
54 54 def topics
55 55 messages.where(:parent_id => nil)
56 56 end
57 57
58 58 def valid_parents
59 59 @valid_parents ||= project.boards - self_and_descendants
60 60 end
61 61
62 62 def reset_counters!
63 63 self.class.reset_counters!(id)
64 64 end
65 65
66 66 # Updates topics_count, messages_count and last_message_id attributes for +board_id+
67 67 def self.reset_counters!(board_id)
68 68 board_id = board_id.to_i
69 69 Board.where(:id => board_id).
70 70 update_all(["topics_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=:id AND parent_id IS NULL)," +
71 71 " messages_count = (SELECT COUNT(*) FROM #{Message.table_name} WHERE board_id=:id)," +
72 72 " last_message_id = (SELECT MAX(id) FROM #{Message.table_name} WHERE board_id=:id)", :id => board_id])
73 73 end
74 74
75 75 def self.board_tree(boards, parent_id=nil, level=0)
76 76 tree = []
77 77 boards.select {|board| board.parent_id == parent_id}.sort_by(&:position).each do |board|
78 78 tree << [board, level]
79 79 tree += board_tree(boards, board.id, level+1)
80 80 end
81 81 if block_given?
82 82 tree.each do |board, level|
83 83 yield board, level
84 84 end
85 85 end
86 86 tree
87 87 end
88 88
89 89 protected
90 90
91 91 def validate_board
92 92 if parent_id && parent_id_changed?
93 93 errors.add(:parent_id, :invalid) unless valid_parents.include?(parent)
94 94 end
95 95 end
96 96 end
@@ -1,284 +1,284
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class CustomField < ActiveRecord::Base
19 19 include Redmine::SubclassFactory
20 20
21 21 has_many :enumerations,
22 22 lambda { order(:position) },
23 23 :class_name => 'CustomFieldEnumeration',
24 24 :dependent => :delete_all
25 25 has_many :custom_values, :dependent => :delete_all
26 26 has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id"
27 acts_as_list :scope => 'type = \'#{self.class}\''
27 acts_as_positioned
28 28 serialize :possible_values
29 29 store :format_store
30 30
31 31 validates_presence_of :name, :field_format
32 32 validates_uniqueness_of :name, :scope => :type
33 33 validates_length_of :name, :maximum => 30
34 34 validates_inclusion_of :field_format, :in => Proc.new { Redmine::FieldFormat.available_formats }
35 35 validate :validate_custom_field
36 36 attr_protected :id
37 37
38 38 before_validation :set_searchable
39 39 before_save do |field|
40 40 field.format.before_custom_field_save(field)
41 41 end
42 42 after_save :handle_multiplicity_change
43 43 after_save do |field|
44 44 if field.visible_changed? && field.visible
45 45 field.roles.clear
46 46 end
47 47 end
48 48
49 49 scope :sorted, lambda { order(:position) }
50 50 scope :visible, lambda {|*args|
51 51 user = args.shift || User.current
52 52 if user.admin?
53 53 # nop
54 54 elsif user.memberships.any?
55 55 where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" +
56 56 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
57 57 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
58 58 " WHERE m.user_id = ?)",
59 59 true, user.id)
60 60 else
61 61 where(:visible => true)
62 62 end
63 63 }
64 64
65 65 def visible_by?(project, user=User.current)
66 66 visible? || user.admin?
67 67 end
68 68
69 69 def format
70 70 @format ||= Redmine::FieldFormat.find(field_format)
71 71 end
72 72
73 73 def field_format=(arg)
74 74 # cannot change format of a saved custom field
75 75 if new_record?
76 76 @format = nil
77 77 super
78 78 end
79 79 end
80 80
81 81 def set_searchable
82 82 # make sure these fields are not searchable
83 83 self.searchable = false unless format.class.searchable_supported
84 84 # make sure only these fields can have multiple values
85 85 self.multiple = false unless format.class.multiple_supported
86 86 true
87 87 end
88 88
89 89 def validate_custom_field
90 90 format.validate_custom_field(self).each do |attribute, message|
91 91 errors.add attribute, message
92 92 end
93 93
94 94 if regexp.present?
95 95 begin
96 96 Regexp.new(regexp)
97 97 rescue
98 98 errors.add(:regexp, :invalid)
99 99 end
100 100 end
101 101
102 102 if default_value.present?
103 103 validate_field_value(default_value).each do |message|
104 104 errors.add :default_value, message
105 105 end
106 106 end
107 107 end
108 108
109 109 def possible_custom_value_options(custom_value)
110 110 format.possible_custom_value_options(custom_value)
111 111 end
112 112
113 113 def possible_values_options(object=nil)
114 114 if object.is_a?(Array)
115 115 object.map {|o| format.possible_values_options(self, o)}.reduce(:&) || []
116 116 else
117 117 format.possible_values_options(self, object) || []
118 118 end
119 119 end
120 120
121 121 def possible_values
122 122 values = read_attribute(:possible_values)
123 123 if values.is_a?(Array)
124 124 values.each do |value|
125 125 value.to_s.force_encoding('UTF-8')
126 126 end
127 127 values
128 128 else
129 129 []
130 130 end
131 131 end
132 132
133 133 # Makes possible_values accept a multiline string
134 134 def possible_values=(arg)
135 135 if arg.is_a?(Array)
136 136 values = arg.compact.map {|a| a.to_s.strip}.reject(&:blank?)
137 137 write_attribute(:possible_values, values)
138 138 else
139 139 self.possible_values = arg.to_s.split(/[\n\r]+/)
140 140 end
141 141 end
142 142
143 143 def cast_value(value)
144 144 format.cast_value(self, value)
145 145 end
146 146
147 147 def value_from_keyword(keyword, customized)
148 148 format.value_from_keyword(self, keyword, customized)
149 149 end
150 150
151 151 # Returns the options hash used to build a query filter for the field
152 152 def query_filter_options(query)
153 153 format.query_filter_options(self, query)
154 154 end
155 155
156 156 def totalable?
157 157 format.totalable_supported
158 158 end
159 159
160 160 # Returns a ORDER BY clause that can used to sort customized
161 161 # objects by their value of the custom field.
162 162 # Returns nil if the custom field can not be used for sorting.
163 163 def order_statement
164 164 return nil if multiple?
165 165 format.order_statement(self)
166 166 end
167 167
168 168 # Returns a GROUP BY clause that can used to group by custom value
169 169 # Returns nil if the custom field can not be used for grouping.
170 170 def group_statement
171 171 return nil if multiple?
172 172 format.group_statement(self)
173 173 end
174 174
175 175 def join_for_order_statement
176 176 format.join_for_order_statement(self)
177 177 end
178 178
179 179 def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil)
180 180 if visible? || user.admin?
181 181 "1=1"
182 182 elsif user.anonymous?
183 183 "1=0"
184 184 else
185 185 project_key ||= "#{self.class.customized_class.table_name}.project_id"
186 186 id_column ||= id
187 187 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
188 188 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
189 189 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
190 190 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id_column})"
191 191 end
192 192 end
193 193
194 194 def self.visibility_condition
195 195 if user.admin?
196 196 "1=1"
197 197 elsif user.anonymous?
198 198 "#{table_name}.visible"
199 199 else
200 200 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
201 201 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
202 202 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
203 203 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
204 204 end
205 205 end
206 206
207 207 def <=>(field)
208 208 position <=> field.position
209 209 end
210 210
211 211 # Returns the class that values represent
212 212 def value_class
213 213 format.target_class if format.respond_to?(:target_class)
214 214 end
215 215
216 216 def self.customized_class
217 217 self.name =~ /^(.+)CustomField$/
218 218 $1.constantize rescue nil
219 219 end
220 220
221 221 # to move in project_custom_field
222 222 def self.for_all
223 223 where(:is_for_all => true).order('position').to_a
224 224 end
225 225
226 226 def type_name
227 227 nil
228 228 end
229 229
230 230 # Returns the error messages for the given value
231 231 # or an empty array if value is a valid value for the custom field
232 232 def validate_custom_value(custom_value)
233 233 value = custom_value.value
234 234 errs = []
235 235 if value.is_a?(Array)
236 236 if !multiple?
237 237 errs << ::I18n.t('activerecord.errors.messages.invalid')
238 238 end
239 239 if is_required? && value.detect(&:present?).nil?
240 240 errs << ::I18n.t('activerecord.errors.messages.blank')
241 241 end
242 242 else
243 243 if is_required? && value.blank?
244 244 errs << ::I18n.t('activerecord.errors.messages.blank')
245 245 end
246 246 end
247 247 errs += format.validate_custom_value(custom_value)
248 248 errs
249 249 end
250 250
251 251 # Returns the error messages for the default custom field value
252 252 def validate_field_value(value)
253 253 validate_custom_value(CustomFieldValue.new(:custom_field => self, :value => value))
254 254 end
255 255
256 256 # Returns true if value is a valid value for the custom field
257 257 def valid_field_value?(value)
258 258 validate_field_value(value).empty?
259 259 end
260 260
261 261 def format_in?(*args)
262 262 args.include?(field_format)
263 263 end
264 264
265 265 protected
266 266
267 267 # Removes multiple values for the custom field after setting the multiple attribute to false
268 268 # We kepp the value with the highest id for each customized object
269 269 def handle_multiplicity_change
270 270 if !new_record? && multiple_was && !multiple
271 271 ids = custom_values.
272 272 where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
273 273 " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
274 274 " AND cve.id > #{CustomValue.table_name}.id)").
275 275 pluck(:id)
276 276
277 277 if ids.any?
278 278 custom_values.where(:id => ids).delete_all
279 279 end
280 280 end
281 281 end
282 282 end
283 283
284 284 require_dependency 'redmine/field_format'
@@ -1,168 +1,173
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Enumeration < ActiveRecord::Base
19 19 include Redmine::SubclassFactory
20 20
21 21 default_scope lambda {order(:position)}
22 22
23 23 belongs_to :project
24 24
25 acts_as_list :scope => 'type = \'#{type}\' AND #{parent_id ? "parent_id = #{parent_id}" : "parent_id IS NULL"}'
25 acts_as_positioned :scope => :parent_id
26 26 acts_as_customizable
27 27 acts_as_tree
28 28
29 29 before_destroy :check_integrity
30 30 before_save :check_default
31 31
32 32 attr_protected :type
33 33
34 34 validates_presence_of :name
35 35 validates_uniqueness_of :name, :scope => [:type, :project_id]
36 36 validates_length_of :name, :maximum => 30
37 37
38 38 scope :shared, lambda { where(:project_id => nil) }
39 39 scope :sorted, lambda { order(:position) }
40 40 scope :active, lambda { where(:active => true) }
41 41 scope :system, lambda { where(:project_id => nil) }
42 42 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
43 43
44 44 def self.default
45 45 # Creates a fake default scope so Enumeration.default will check
46 46 # it's type. STI subclasses will automatically add their own
47 47 # types to the finder.
48 48 if self.descends_from_active_record?
49 49 where(:is_default => true, :type => 'Enumeration').first
50 50 else
51 51 # STI classes are
52 52 where(:is_default => true).first
53 53 end
54 54 end
55 55
56 56 # Overloaded on concrete classes
57 57 def option_name
58 58 nil
59 59 end
60 60
61 61 def check_default
62 62 if is_default? && is_default_changed?
63 63 Enumeration.where({:type => type}).update_all({:is_default => false})
64 64 end
65 65 end
66 66
67 67 # Overloaded on concrete classes
68 68 def objects_count
69 69 0
70 70 end
71 71
72 72 def in_use?
73 73 self.objects_count != 0
74 74 end
75 75
76 76 # Is this enumeration overriding a system level enumeration?
77 77 def is_override?
78 78 !self.parent.nil?
79 79 end
80 80
81 81 alias :destroy_without_reassign :destroy
82 82
83 83 # Destroy the enumeration
84 84 # If a enumeration is specified, objects are reassigned
85 85 def destroy(reassign_to = nil)
86 86 if reassign_to && reassign_to.is_a?(Enumeration)
87 87 self.transfer_relations(reassign_to)
88 88 end
89 89 destroy_without_reassign
90 90 end
91 91
92 92 def <=>(enumeration)
93 93 position <=> enumeration.position
94 94 end
95 95
96 96 def to_s; name end
97 97
98 98 # Returns the Subclasses of Enumeration. Each Subclass needs to be
99 99 # required in development mode.
100 100 #
101 101 # Note: subclasses is protected in ActiveRecord
102 102 def self.get_subclasses
103 103 subclasses
104 104 end
105 105
106 106 # Does the +new+ Hash override the previous Enumeration?
107 107 def self.overriding_change?(new, previous)
108 108 if (same_active_state?(new['active'], previous.active)) && same_custom_values?(new,previous)
109 109 return false
110 110 else
111 111 return true
112 112 end
113 113 end
114 114
115 115 # Does the +new+ Hash have the same custom values as the previous Enumeration?
116 116 def self.same_custom_values?(new, previous)
117 117 previous.custom_field_values.each do |custom_value|
118 118 if custom_value.value != new["custom_field_values"][custom_value.custom_field_id.to_s]
119 119 return false
120 120 end
121 121 end
122 122
123 123 return true
124 124 end
125 125
126 126 # Are the new and previous fields equal?
127 127 def self.same_active_state?(new, previous)
128 128 new = (new == "1" ? true : false)
129 129 return new == previous
130 130 end
131 131
132 # Overrides acts_as_list reset_positions_in_list so that enumeration overrides
133 # get the same position as the overriden enumeration
134 def reset_positions_in_list
135 acts_as_list_class.where(scope_condition).reorder("#{position_column} ASC, id ASC").each_with_index do |item, i|
136 acts_as_list_class.where("id = :id OR parent_id = :id", :id => item.id).
137 update_all({position_column => (i + 1)})
138 end
139 end
132 private
140 133
141 private
142 134 def check_integrity
143 135 raise "Cannot delete enumeration" if self.in_use?
144 136 end
145 137
146 # Overrides acts_as_list add_to_list_bottom so that enumeration overrides
138 # Overrides Redmine::Acts::Positioned#set_default_position so that enumeration overrides
147 139 # get the same position as the overriden enumeration
148 def add_to_list_bottom
149 if parent
150 self[position_column] = parent.position
151 else
152 super
140 def set_default_position
141 if position.nil? && parent
142 self.position = parent.position
143 end
144 super
145 end
146
147 # Overrides Redmine::Acts::Positioned#update_position so that overrides get the same
148 # position as the overriden enumeration
149 def update_position
150 super
151 if position_changed?
152 self.class.where.not(:parent_id => nil).update_all(
153 "position = coalesce((
154 select position
155 from (select id, position from enumerations) as parent
156 where parent_id = parent.id), 1)"
157 )
153 158 end
154 159 end
155 160
156 # Overrides acts_as_list remove_from_list so that enumeration overrides
161 # Overrides Redmine::Acts::Positioned#remove_position so that enumeration overrides
157 162 # get the same position as the overriden enumeration
158 def remove_from_list
163 def remove_position
159 164 if parent_id.blank?
160 165 super
161 166 end
162 167 end
163 168 end
164 169
165 170 # Force load the subclasses in development mode
166 171 require_dependency 'time_entry_activity'
167 172 require_dependency 'document_category'
168 173 require_dependency 'issue_priority'
@@ -1,113 +1,113
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssueStatus < ActiveRecord::Base
19 19 before_destroy :check_integrity
20 20 has_many :workflows, :class_name => 'WorkflowTransition', :foreign_key => "old_status_id"
21 21 has_many :workflow_transitions_as_new_status, :class_name => 'WorkflowTransition', :foreign_key => "new_status_id"
22 acts_as_list
22 acts_as_positioned
23 23
24 24 after_update :handle_is_closed_change
25 25 before_destroy :delete_workflow_rules
26 26
27 27 validates_presence_of :name
28 28 validates_uniqueness_of :name
29 29 validates_length_of :name, :maximum => 30
30 30 validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true
31 31 attr_protected :id
32 32
33 33 scope :sorted, lambda { order(:position) }
34 34 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
35 35
36 36 # Update all the +Issues+ setting their done_ratio to the value of their +IssueStatus+
37 37 def self.update_issue_done_ratios
38 38 if Issue.use_status_for_done_ratio?
39 39 IssueStatus.where("default_done_ratio >= 0").each do |status|
40 40 Issue.where({:status_id => status.id}).update_all({:done_ratio => status.default_done_ratio})
41 41 end
42 42 end
43 43
44 44 return Issue.use_status_for_done_ratio?
45 45 end
46 46
47 47 # Returns an array of all statuses the given role can switch to
48 48 def new_statuses_allowed_to(roles, tracker, author=false, assignee=false)
49 49 self.class.new_statuses_allowed(self, roles, tracker, author, assignee)
50 50 end
51 51 alias :find_new_statuses_allowed_to :new_statuses_allowed_to
52 52
53 53 def self.new_statuses_allowed(status, roles, tracker, author=false, assignee=false)
54 54 if roles.present? && tracker
55 55 status_id = status.try(:id) || 0
56 56
57 57 scope = IssueStatus.
58 58 joins(:workflow_transitions_as_new_status).
59 59 where(:workflows => {:old_status_id => status_id, :role_id => roles.map(&:id), :tracker_id => tracker.id})
60 60
61 61 unless author && assignee
62 62 if author || assignee
63 63 scope = scope.where("author = ? OR assignee = ?", author, assignee)
64 64 else
65 65 scope = scope.where("author = ? AND assignee = ?", false, false)
66 66 end
67 67 end
68 68
69 69 scope.uniq.to_a.sort
70 70 else
71 71 []
72 72 end
73 73 end
74 74
75 75 def <=>(status)
76 76 position <=> status.position
77 77 end
78 78
79 79 def to_s; name end
80 80
81 81 private
82 82
83 83 # Updates issues closed_on attribute when an existing status is set as closed.
84 84 def handle_is_closed_change
85 85 if is_closed_changed? && is_closed == true
86 86 # First we update issues that have a journal for when the current status was set,
87 87 # a subselect is used to update all issues with a single query
88 88 subselect = "SELECT MAX(j.created_on) FROM #{Journal.table_name} j" +
89 89 " JOIN #{JournalDetail.table_name} d ON d.journal_id = j.id" +
90 90 " WHERE j.journalized_type = 'Issue' AND j.journalized_id = #{Issue.table_name}.id" +
91 91 " AND d.property = 'attr' AND d.prop_key = 'status_id' AND d.value = :status_id"
92 92 Issue.where(:status_id => id, :closed_on => nil).
93 93 update_all(["closed_on = (#{subselect})", {:status_id => id.to_s}])
94 94
95 95 # Then we update issues that don't have a journal which means the
96 96 # current status was set on creation
97 97 Issue.where(:status_id => id, :closed_on => nil).update_all("closed_on = created_on")
98 98 end
99 99 end
100 100
101 101 def check_integrity
102 102 if Issue.where(:status_id => id).any?
103 103 raise "This status is used by some issues"
104 104 elsif Tracker.where(:default_status_id => id).any?
105 105 raise "This status is used as the default status by some trackers"
106 106 end
107 107 end
108 108
109 109 # Deletes associated workflows
110 110 def delete_workflow_rules
111 111 WorkflowRule.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}])
112 112 end
113 113 end
@@ -1,233 +1,233
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Role < ActiveRecord::Base
19 19 # Custom coder for the permissions attribute that should be an
20 20 # array of symbols. Rails 3 uses Psych which can be *unbelievably*
21 21 # slow on some platforms (eg. mingw32).
22 22 class PermissionsAttributeCoder
23 23 def self.load(str)
24 24 str.to_s.scan(/:([a-z0-9_]+)/).flatten.map(&:to_sym)
25 25 end
26 26
27 27 def self.dump(value)
28 28 YAML.dump(value)
29 29 end
30 30 end
31 31
32 32 # Built-in roles
33 33 BUILTIN_NON_MEMBER = 1
34 34 BUILTIN_ANONYMOUS = 2
35 35
36 36 ISSUES_VISIBILITY_OPTIONS = [
37 37 ['all', :label_issues_visibility_all],
38 38 ['default', :label_issues_visibility_public],
39 39 ['own', :label_issues_visibility_own]
40 40 ]
41 41
42 42 TIME_ENTRIES_VISIBILITY_OPTIONS = [
43 43 ['all', :label_time_entries_visibility_all],
44 44 ['own', :label_time_entries_visibility_own]
45 45 ]
46 46
47 47 USERS_VISIBILITY_OPTIONS = [
48 48 ['all', :label_users_visibility_all],
49 49 ['members_of_visible_projects', :label_users_visibility_members_of_visible_projects]
50 50 ]
51 51
52 52 scope :sorted, lambda { order(:builtin, :position) }
53 53 scope :givable, lambda { order(:position).where(:builtin => 0) }
54 54 scope :builtin, lambda { |*args|
55 55 compare = (args.first == true ? 'not' : '')
56 56 where("#{compare} builtin = 0")
57 57 }
58 58
59 59 before_destroy :check_deletable
60 60 has_many :workflow_rules, :dependent => :delete_all do
61 61 def copy(source_role)
62 62 WorkflowRule.copy(nil, source_role, nil, proxy_association.owner)
63 63 end
64 64 end
65 65 has_and_belongs_to_many :custom_fields, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "role_id"
66 66
67 67 has_and_belongs_to_many :managed_roles, :class_name => 'Role',
68 68 :join_table => "#{table_name_prefix}roles_managed_roles#{table_name_suffix}",
69 69 :association_foreign_key => "managed_role_id"
70 70
71 71 has_many :member_roles, :dependent => :destroy
72 72 has_many :members, :through => :member_roles
73 acts_as_list
73 acts_as_positioned :scope => :builtin
74 74
75 75 serialize :permissions, ::Role::PermissionsAttributeCoder
76 76 attr_protected :builtin
77 77
78 78 validates_presence_of :name
79 79 validates_uniqueness_of :name
80 80 validates_length_of :name, :maximum => 30
81 81 validates_inclusion_of :issues_visibility,
82 82 :in => ISSUES_VISIBILITY_OPTIONS.collect(&:first),
83 83 :if => lambda {|role| role.respond_to?(:issues_visibility) && role.issues_visibility_changed?}
84 84 validates_inclusion_of :users_visibility,
85 85 :in => USERS_VISIBILITY_OPTIONS.collect(&:first),
86 86 :if => lambda {|role| role.respond_to?(:users_visibility) && role.users_visibility_changed?}
87 87 validates_inclusion_of :time_entries_visibility,
88 88 :in => TIME_ENTRIES_VISIBILITY_OPTIONS.collect(&:first),
89 89 :if => lambda {|role| role.respond_to?(:time_entries_visibility) && role.time_entries_visibility_changed?}
90 90
91 91 # Copies attributes from another role, arg can be an id or a Role
92 92 def copy_from(arg, options={})
93 93 return unless arg.present?
94 94 role = arg.is_a?(Role) ? arg : Role.find_by_id(arg.to_s)
95 95 self.attributes = role.attributes.dup.except("id", "name", "position", "builtin", "permissions")
96 96 self.permissions = role.permissions.dup
97 97 self
98 98 end
99 99
100 100 def permissions=(perms)
101 101 perms = perms.collect {|p| p.to_sym unless p.blank? }.compact.uniq if perms
102 102 write_attribute(:permissions, perms)
103 103 end
104 104
105 105 def add_permission!(*perms)
106 106 self.permissions = [] unless permissions.is_a?(Array)
107 107
108 108 permissions_will_change!
109 109 perms.each do |p|
110 110 p = p.to_sym
111 111 permissions << p unless permissions.include?(p)
112 112 end
113 113 save!
114 114 end
115 115
116 116 def remove_permission!(*perms)
117 117 return unless permissions.is_a?(Array)
118 118 permissions_will_change!
119 119 perms.each { |p| permissions.delete(p.to_sym) }
120 120 save!
121 121 end
122 122
123 123 # Returns true if the role has the given permission
124 124 def has_permission?(perm)
125 125 !permissions.nil? && permissions.include?(perm.to_sym)
126 126 end
127 127
128 128 def consider_workflow?
129 129 has_permission?(:add_issues) || has_permission?(:edit_issues)
130 130 end
131 131
132 132 def <=>(role)
133 133 if role
134 134 if builtin == role.builtin
135 135 position <=> role.position
136 136 else
137 137 builtin <=> role.builtin
138 138 end
139 139 else
140 140 -1
141 141 end
142 142 end
143 143
144 144 def to_s
145 145 name
146 146 end
147 147
148 148 def name
149 149 case builtin
150 150 when 1; l(:label_role_non_member, :default => read_attribute(:name))
151 151 when 2; l(:label_role_anonymous, :default => read_attribute(:name))
152 152 else; read_attribute(:name)
153 153 end
154 154 end
155 155
156 156 # Return true if the role is a builtin role
157 157 def builtin?
158 158 self.builtin != 0
159 159 end
160 160
161 161 # Return true if the role is the anonymous role
162 162 def anonymous?
163 163 builtin == 2
164 164 end
165 165
166 166 # Return true if the role is a project member role
167 167 def member?
168 168 !self.builtin?
169 169 end
170 170
171 171 # Return true if role is allowed to do the specified action
172 172 # action can be:
173 173 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
174 174 # * a permission Symbol (eg. :edit_project)
175 175 def allowed_to?(action)
176 176 if action.is_a? Hash
177 177 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
178 178 else
179 179 allowed_permissions.include? action
180 180 end
181 181 end
182 182
183 183 # Return all the permissions that can be given to the role
184 184 def setable_permissions
185 185 setable_permissions = Redmine::AccessControl.permissions - Redmine::AccessControl.public_permissions
186 186 setable_permissions -= Redmine::AccessControl.members_only_permissions if self.builtin == BUILTIN_NON_MEMBER
187 187 setable_permissions -= Redmine::AccessControl.loggedin_only_permissions if self.builtin == BUILTIN_ANONYMOUS
188 188 setable_permissions
189 189 end
190 190
191 191 # Find all the roles that can be given to a project member
192 192 def self.find_all_givable
193 193 Role.givable.to_a
194 194 end
195 195
196 196 # Return the builtin 'non member' role. If the role doesn't exist,
197 197 # it will be created on the fly.
198 198 def self.non_member
199 199 find_or_create_system_role(BUILTIN_NON_MEMBER, 'Non member')
200 200 end
201 201
202 202 # Return the builtin 'anonymous' role. If the role doesn't exist,
203 203 # it will be created on the fly.
204 204 def self.anonymous
205 205 find_or_create_system_role(BUILTIN_ANONYMOUS, 'Anonymous')
206 206 end
207 207
208 208 private
209 209
210 210 def allowed_permissions
211 211 @allowed_permissions ||= permissions + Redmine::AccessControl.public_permissions.collect {|p| p.name}
212 212 end
213 213
214 214 def allowed_actions
215 215 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
216 216 end
217 217
218 218 def check_deletable
219 219 raise "Cannot delete role" if members.any?
220 220 raise "Cannot delete builtin role" if builtin?
221 221 end
222 222
223 223 def self.find_or_create_system_role(builtin, name)
224 224 role = where(:builtin => builtin).first
225 225 if role.nil?
226 role = create(:name => name, :position => 0) do |r|
226 role = create(:name => name) do |r|
227 227 r.builtin = builtin
228 228 end
229 raise "Unable to create the #{name} role." if role.new_record?
229 raise "Unable to create the #{name} role (#{role.errors.full_messages.join(',')})." if role.new_record?
230 230 end
231 231 role
232 232 end
233 233 end
@@ -1,114 +1,114
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Tracker < ActiveRecord::Base
19 19
20 20 CORE_FIELDS_UNDISABLABLE = %w(project_id tracker_id subject description priority_id is_private).freeze
21 21 # Fields that can be disabled
22 22 # Other (future) fields should be appended, not inserted!
23 23 CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours done_ratio).freeze
24 24 CORE_FIELDS_ALL = (CORE_FIELDS_UNDISABLABLE + CORE_FIELDS).freeze
25 25
26 26 before_destroy :check_integrity
27 27 belongs_to :default_status, :class_name => 'IssueStatus'
28 28 has_many :issues
29 29 has_many :workflow_rules, :dependent => :delete_all do
30 30 def copy(source_tracker)
31 31 WorkflowRule.copy(source_tracker, nil, proxy_association.owner, nil)
32 32 end
33 33 end
34 34
35 35 has_and_belongs_to_many :projects
36 36 has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
37 acts_as_list
37 acts_as_positioned
38 38
39 39 attr_protected :fields_bits
40 40
41 41 validates_presence_of :default_status
42 42 validates_presence_of :name
43 43 validates_uniqueness_of :name
44 44 validates_length_of :name, :maximum => 30
45 45
46 46 scope :sorted, lambda { order(:position) }
47 47 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
48 48
49 49 def to_s; name end
50 50
51 51 def <=>(tracker)
52 52 position <=> tracker.position
53 53 end
54 54
55 55 # Returns an array of IssueStatus that are used
56 56 # in the tracker's workflows
57 57 def issue_statuses
58 58 @issue_statuses ||= IssueStatus.where(:id => issue_status_ids).to_a.sort
59 59 end
60 60
61 61 def issue_status_ids
62 62 if new_record?
63 63 []
64 64 else
65 65 @issue_status_ids ||= WorkflowTransition.where(:tracker_id => id).uniq.pluck(:old_status_id, :new_status_id).flatten.uniq
66 66 end
67 67 end
68 68
69 69 def disabled_core_fields
70 70 i = -1
71 71 @disabled_core_fields ||= CORE_FIELDS.select { i += 1; (fields_bits || 0) & (2 ** i) != 0}
72 72 end
73 73
74 74 def core_fields
75 75 CORE_FIELDS - disabled_core_fields
76 76 end
77 77
78 78 def core_fields=(fields)
79 79 raise ArgumentError.new("Tracker.core_fields takes an array") unless fields.is_a?(Array)
80 80
81 81 bits = 0
82 82 CORE_FIELDS.each_with_index do |field, i|
83 83 unless fields.include?(field)
84 84 bits |= 2 ** i
85 85 end
86 86 end
87 87 self.fields_bits = bits
88 88 @disabled_core_fields = nil
89 89 core_fields
90 90 end
91 91
92 92 # Returns the fields that are disabled for all the given trackers
93 93 def self.disabled_core_fields(trackers)
94 94 if trackers.present?
95 95 trackers.map(&:disabled_core_fields).reduce(:&)
96 96 else
97 97 []
98 98 end
99 99 end
100 100
101 101 # Returns the fields that are enabled for one tracker at least
102 102 def self.core_fields(trackers)
103 103 if trackers.present?
104 104 trackers.uniq.map(&:core_fields).reduce(:|)
105 105 else
106 106 CORE_FIELDS.dup
107 107 end
108 108 end
109 109
110 110 private
111 111 def check_integrity
112 112 raise Exception.new("Cannot delete tracker") if Issue.where(:tracker_id => self.id).any?
113 113 end
114 114 end
@@ -1,276 +1,278
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/core_ext'
19 19
20 20 begin
21 21 require 'rmagick' unless Object.const_defined?(:Magick)
22 22 rescue LoadError
23 23 # RMagick is not available
24 24 end
25 25 begin
26 26 require 'redcarpet' unless Object.const_defined?(:Redcarpet)
27 27 rescue LoadError
28 28 # Redcarpet is not available
29 29 end
30 30
31 require 'redmine/acts/positioned'
32
31 33 require 'redmine/scm/base'
32 34 require 'redmine/access_control'
33 35 require 'redmine/access_keys'
34 36 require 'redmine/activity'
35 37 require 'redmine/activity/fetcher'
36 38 require 'redmine/ciphering'
37 39 require 'redmine/codeset_util'
38 40 require 'redmine/field_format'
39 41 require 'redmine/menu_manager'
40 42 require 'redmine/notifiable'
41 43 require 'redmine/platform'
42 44 require 'redmine/mime_type'
43 45 require 'redmine/notifiable'
44 46 require 'redmine/search'
45 47 require 'redmine/syntax_highlighting'
46 48 require 'redmine/thumbnail'
47 49 require 'redmine/unified_diff'
48 50 require 'redmine/utils'
49 51 require 'redmine/version'
50 52 require 'redmine/wiki_formatting'
51 53
52 54 require 'redmine/default_data/loader'
53 55 require 'redmine/helpers/calendar'
54 56 require 'redmine/helpers/diff'
55 57 require 'redmine/helpers/gantt'
56 58 require 'redmine/helpers/time_report'
57 59 require 'redmine/views/other_formats_builder'
58 60 require 'redmine/views/labelled_form_builder'
59 61 require 'redmine/views/builders'
60 62
61 63 require 'redmine/themes'
62 64 require 'redmine/hook'
63 65 require 'redmine/hook/listener'
64 66 require 'redmine/hook/view_listener'
65 67 require 'redmine/plugin'
66 68
67 69 Redmine::Scm::Base.add "Subversion"
68 70 Redmine::Scm::Base.add "Darcs"
69 71 Redmine::Scm::Base.add "Mercurial"
70 72 Redmine::Scm::Base.add "Cvs"
71 73 Redmine::Scm::Base.add "Bazaar"
72 74 Redmine::Scm::Base.add "Git"
73 75 Redmine::Scm::Base.add "Filesystem"
74 76
75 77 # Permissions
76 78 Redmine::AccessControl.map do |map|
77 79 map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true, :read => true
78 80 map.permission :search_project, {:search => :index}, :public => true, :read => true
79 81 map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
80 82 map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
81 83 map.permission :close_project, {:projects => [:close, :reopen]}, :require => :member, :read => true
82 84 map.permission :select_project_modules, {:projects => :modules}, :require => :member
83 85 map.permission :view_members, {:members => [:index, :show]}, :public => true, :read => true
84 86 map.permission :manage_members, {:projects => :settings, :members => [:index, :show, :new, :create, :update, :destroy, :autocomplete]}, :require => :member
85 87 map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
86 88 map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
87 89
88 90 map.project_module :issue_tracking do |map|
89 91 # Issue categories
90 92 map.permission :manage_categories, {:projects => :settings, :issue_categories => [:index, :show, :new, :create, :edit, :update, :destroy]}, :require => :member
91 93 # Issues
92 94 map.permission :view_issues, {:issues => [:index, :show],
93 95 :auto_complete => [:issues],
94 96 :context_menus => [:issues],
95 97 :versions => [:index, :show, :status_by],
96 98 :journals => [:index, :diff],
97 99 :queries => :index,
98 100 :reports => [:issue_report, :issue_report_details]},
99 101 :read => true
100 102 map.permission :add_issues, {:issues => [:new, :create], :attachments => :upload}
101 103 map.permission :edit_issues, {:issues => [:edit, :update, :bulk_edit, :bulk_update], :journals => [:new], :attachments => :upload}
102 104 map.permission :copy_issues, {:issues => [:new, :create, :bulk_edit, :bulk_update], :attachments => :upload}
103 105 map.permission :manage_issue_relations, {:issue_relations => [:index, :show, :create, :destroy]}
104 106 map.permission :manage_subtasks, {}
105 107 map.permission :set_issues_private, {}
106 108 map.permission :set_own_issues_private, {}, :require => :loggedin
107 109 map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new], :attachments => :upload}
108 110 map.permission :edit_issue_notes, {:journals => [:edit, :update]}, :require => :loggedin
109 111 map.permission :edit_own_issue_notes, {:journals => [:edit, :update]}, :require => :loggedin
110 112 map.permission :view_private_notes, {}, :read => true, :require => :member
111 113 map.permission :set_notes_private, {}, :require => :member
112 114 map.permission :delete_issues, {:issues => :destroy}, :require => :member
113 115 # Queries
114 116 map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member
115 117 map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
116 118 # Watchers
117 119 map.permission :view_issue_watchers, {}, :read => true
118 120 map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]}
119 121 map.permission :delete_issue_watchers, {:watchers => :destroy}
120 122 map.permission :import_issues, {:imports => [:new, :create, :settings, :mapping, :run, :show]}
121 123 end
122 124
123 125 map.project_module :time_tracking do |map|
124 126 map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin
125 127 map.permission :view_time_entries, {:timelog => [:index, :report, :show]}, :read => true
126 128 map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy, :bulk_edit, :bulk_update]}, :require => :member
127 129 map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy,:bulk_edit, :bulk_update]}, :require => :loggedin
128 130 map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
129 131 end
130 132
131 133 map.project_module :news do |map|
132 134 map.permission :manage_news, {:news => [:new, :create, :edit, :update, :destroy], :comments => [:destroy], :attachments => :upload}, :require => :member
133 135 map.permission :view_news, {:news => [:index, :show]}, :public => true, :read => true
134 136 map.permission :comment_news, {:comments => :create}
135 137 end
136 138
137 139 map.project_module :documents do |map|
138 140 map.permission :add_documents, {:documents => [:new, :create, :add_attachment], :attachments => :upload}, :require => :loggedin
139 141 map.permission :edit_documents, {:documents => [:edit, :update, :add_attachment], :attachments => :upload}, :require => :loggedin
140 142 map.permission :delete_documents, {:documents => [:destroy]}, :require => :loggedin
141 143 map.permission :view_documents, {:documents => [:index, :show, :download]}, :read => true
142 144 end
143 145
144 146 map.project_module :files do |map|
145 147 map.permission :manage_files, {:files => [:new, :create], :attachments => :upload}, :require => :loggedin
146 148 map.permission :view_files, {:files => :index, :versions => :download}, :read => true
147 149 end
148 150
149 151 map.project_module :wiki do |map|
150 152 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
151 153 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
152 154 map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member
153 155 map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true
154 156 map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true
155 157 map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true
156 158 map.permission :edit_wiki_pages, :wiki => [:edit, :update, :preview, :add_attachment], :attachments => :upload
157 159 map.permission :delete_wiki_pages_attachments, {}
158 160 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
159 161 end
160 162
161 163 map.project_module :repository do |map|
162 164 map.permission :manage_repository, {:repositories => [:new, :create, :edit, :update, :committers, :destroy]}, :require => :member
163 165 map.permission :browse_repository, {:repositories => [:show, :browse, :entry, :raw, :annotate, :changes, :diff, :stats, :graph]}, :read => true
164 166 map.permission :view_changesets, {:repositories => [:show, :revisions, :revision]}, :read => true
165 167 map.permission :commit_access, {}
166 168 map.permission :manage_related_issues, {:repositories => [:add_related_issue, :remove_related_issue]}
167 169 end
168 170
169 171 map.project_module :boards do |map|
170 172 map.permission :manage_boards, {:boards => [:new, :create, :edit, :update, :destroy]}, :require => :member
171 173 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true, :read => true
172 174 map.permission :add_messages, {:messages => [:new, :reply, :quote], :attachments => :upload}
173 175 map.permission :edit_messages, {:messages => :edit, :attachments => :upload}, :require => :member
174 176 map.permission :edit_own_messages, {:messages => :edit, :attachments => :upload}, :require => :loggedin
175 177 map.permission :delete_messages, {:messages => :destroy}, :require => :member
176 178 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
177 179 end
178 180
179 181 map.project_module :calendar do |map|
180 182 map.permission :view_calendar, {:calendars => [:show, :update]}, :read => true
181 183 end
182 184
183 185 map.project_module :gantt do |map|
184 186 map.permission :view_gantt, {:gantts => [:show, :update]}, :read => true
185 187 end
186 188 end
187 189
188 190 Redmine::MenuManager.map :top_menu do |menu|
189 191 menu.push :home, :home_path
190 192 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
191 193 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
192 194 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
193 195 menu.push :help, Redmine::Info.help_url, :last => true
194 196 end
195 197
196 198 Redmine::MenuManager.map :account_menu do |menu|
197 199 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
198 200 menu.push :register, :register_path, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
199 201 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
200 202 menu.push :logout, :signout_path, :html => {:method => 'post'}, :if => Proc.new { User.current.logged? }
201 203 end
202 204
203 205 Redmine::MenuManager.map :application_menu do |menu|
204 206 # Empty
205 207 end
206 208
207 209 Redmine::MenuManager.map :admin_menu do |menu|
208 210 menu.push :projects, {:controller => 'admin', :action => 'projects'}, :caption => :label_project_plural
209 211 menu.push :users, {:controller => 'users'}, :caption => :label_user_plural
210 212 menu.push :groups, {:controller => 'groups'}, :caption => :label_group_plural
211 213 menu.push :roles, {:controller => 'roles'}, :caption => :label_role_and_permissions
212 214 menu.push :trackers, {:controller => 'trackers'}, :caption => :label_tracker_plural
213 215 menu.push :issue_statuses, {:controller => 'issue_statuses'}, :caption => :label_issue_status_plural,
214 216 :html => {:class => 'issue_statuses'}
215 217 menu.push :workflows, {:controller => 'workflows', :action => 'edit'}, :caption => :label_workflow
216 218 menu.push :custom_fields, {:controller => 'custom_fields'}, :caption => :label_custom_field_plural,
217 219 :html => {:class => 'custom_fields'}
218 220 menu.push :enumerations, {:controller => 'enumerations'}
219 221 menu.push :settings, {:controller => 'settings'}
220 222 menu.push :ldap_authentication, {:controller => 'auth_sources', :action => 'index'},
221 223 :html => {:class => 'server_authentication'}
222 224 menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true
223 225 menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true
224 226 end
225 227
226 228 Redmine::MenuManager.map :project_menu do |menu|
227 229 menu.push :overview, { :controller => 'projects', :action => 'show' }
228 230 menu.push :activity, { :controller => 'activities', :action => 'index' }
229 231 menu.push :roadmap, { :controller => 'versions', :action => 'index' }, :param => :project_id,
230 232 :if => Proc.new { |p| p.shared_versions.any? }
231 233 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
232 234 menu.push :new_issue, { :controller => 'issues', :action => 'new', :copy_from => nil }, :param => :project_id, :caption => :label_issue_new,
233 235 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) },
234 236 :if => Proc.new { |p| p.trackers.any? },
235 237 :permission => :add_issues
236 238 menu.push :gantt, { :controller => 'gantts', :action => 'show' }, :param => :project_id, :caption => :label_gantt
237 239 menu.push :calendar, { :controller => 'calendars', :action => 'show' }, :param => :project_id, :caption => :label_calendar
238 240 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
239 241 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
240 242 menu.push :wiki, { :controller => 'wiki', :action => 'show', :id => nil }, :param => :project_id,
241 243 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
242 244 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
243 245 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
244 246 menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
245 247 menu.push :repository, { :controller => 'repositories', :action => 'show', :repository_id => nil, :path => nil, :rev => nil },
246 248 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
247 249 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
248 250 end
249 251
250 252 Redmine::Activity.map do |activity|
251 253 activity.register :issues, :class_name => %w(Issue Journal)
252 254 activity.register :changesets
253 255 activity.register :news
254 256 activity.register :documents, :class_name => %w(Document Attachment)
255 257 activity.register :files, :class_name => 'Attachment'
256 258 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
257 259 activity.register :messages, :default => false
258 260 activity.register :time_entries, :default => false
259 261 end
260 262
261 263 Redmine::Search.map do |search|
262 264 search.register :issues
263 265 search.register :news
264 266 search.register :documents
265 267 search.register :changesets
266 268 search.register :wiki_pages
267 269 search.register :messages
268 270 search.register :projects
269 271 end
270 272
271 273 Redmine::WikiFormatting.map do |format|
272 274 format.register :textile
273 275 format.register :markdown if Object.const_defined?(:Redcarpet)
274 276 end
275 277
276 278 ActionView::Template.register_template_handler :rsb, Redmine::Views::ApiTemplateHandler
@@ -1,147 +1,148
1 1 ---
2 2 custom_fields_001:
3 3 name: Database
4 4 regexp: ""
5 5 is_for_all: true
6 6 is_filter: true
7 7 type: IssueCustomField
8 8 possible_values:
9 9 - MySQL
10 10 - PostgreSQL
11 11 - Oracle
12 12 id: 1
13 13 is_required: false
14 14 field_format: list
15 15 default_value: ""
16 16 editable: true
17 17 position: 2
18 18 custom_fields_002:
19 19 name: Searchable field
20 20 min_length: 1
21 21 regexp: ""
22 22 is_for_all: true
23 23 is_filter: true
24 24 type: IssueCustomField
25 25 max_length: 100
26 26 possible_values: ""
27 27 id: 2
28 28 is_required: false
29 29 field_format: string
30 30 searchable: true
31 31 default_value: "Default string"
32 32 editable: true
33 33 position: 1
34 34 custom_fields_003:
35 35 name: Development status
36 36 regexp: ""
37 37 is_for_all: false
38 38 is_filter: true
39 39 type: ProjectCustomField
40 40 possible_values:
41 41 - Stable
42 42 - Beta
43 43 - Alpha
44 44 - Planning
45 45 id: 3
46 46 is_required: false
47 47 field_format: list
48 48 default_value: ""
49 49 editable: true
50 50 position: 1
51 51 custom_fields_004:
52 52 name: Phone number
53 53 regexp: ""
54 54 is_for_all: false
55 55 type: UserCustomField
56 56 possible_values: ""
57 57 id: 4
58 58 is_required: false
59 59 field_format: string
60 60 default_value: ""
61 61 editable: true
62 62 position: 1
63 63 custom_fields_005:
64 64 name: Money
65 65 regexp: ""
66 66 is_for_all: false
67 67 type: UserCustomField
68 68 possible_values: ""
69 69 id: 5
70 70 is_required: false
71 71 field_format: float
72 72 default_value: ""
73 73 editable: true
74 74 position: 2
75 75 custom_fields_006:
76 76 name: Float field
77 77 regexp: ""
78 78 is_for_all: true
79 79 type: IssueCustomField
80 80 possible_values: ""
81 81 id: 6
82 82 is_required: false
83 83 field_format: float
84 84 default_value: ""
85 85 editable: true
86 86 position: 3
87 87 custom_fields_007:
88 88 name: Billable
89 89 regexp: ""
90 90 is_for_all: false
91 91 is_filter: true
92 92 type: TimeEntryActivityCustomField
93 93 possible_values: ""
94 94 id: 7
95 95 is_required: false
96 96 field_format: bool
97 97 default_value: ""
98 98 editable: true
99 99 position: 1
100 100 custom_fields_008:
101 101 name: Custom date
102 102 regexp: ""
103 103 is_for_all: true
104 104 is_filter: false
105 105 type: IssueCustomField
106 106 possible_values: ""
107 107 id: 8
108 108 is_required: false
109 109 field_format: date
110 110 default_value: ""
111 111 editable: true
112 112 position: 4
113 113 custom_fields_009:
114 114 name: Project 1 cf
115 115 regexp: ""
116 116 is_for_all: false
117 117 is_filter: true
118 118 type: IssueCustomField
119 119 possible_values: ""
120 120 id: 9
121 121 is_required: false
122 122 field_format: date
123 123 default_value: ""
124 124 editable: true
125 125 position: 5
126 126 custom_fields_010:
127 127 name: Overtime
128 128 regexp: ""
129 129 is_for_all: false
130 130 is_filter: false
131 131 type: TimeEntryCustomField
132 132 possible_values: ""
133 133 id: 10
134 134 is_required: false
135 135 field_format: bool
136 136 default_value: 0
137 137 editable: true
138 138 position: 1
139 139 custom_fields_011:
140 140 id: 11
141 141 name: Binary
142 142 type: CustomField
143 143 possible_values:
144 144 - !binary |
145 145 SGXDqWzDp2prc2Tigqw2NTTDuQ==
146 146 - Other value
147 147 field_format: list
148 position: 1
@@ -1,103 +1,105
1 1 ---
2 2 enumerations_001:
3 3 name: Uncategorized
4 4 id: 1
5 5 type: DocumentCategory
6 6 active: true
7 7 position: 1
8 8 enumerations_002:
9 9 name: User documentation
10 10 id: 2
11 11 type: DocumentCategory
12 12 active: true
13 13 position: 2
14 14 enumerations_003:
15 15 name: Technical documentation
16 16 id: 3
17 17 type: DocumentCategory
18 18 active: true
19 19 position: 3
20 20 enumerations_004:
21 21 name: Low
22 22 id: 4
23 23 type: IssuePriority
24 24 active: true
25 25 position: 1
26 26 position_name: lowest
27 27 enumerations_005:
28 28 name: Normal
29 29 id: 5
30 30 type: IssuePriority
31 31 is_default: true
32 32 active: true
33 33 position: 2
34 34 position_name: default
35 35 enumerations_006:
36 36 name: High
37 37 id: 6
38 38 type: IssuePriority
39 39 active: true
40 40 position: 3
41 41 position_name: high3
42 42 enumerations_007:
43 43 name: Urgent
44 44 id: 7
45 45 type: IssuePriority
46 46 active: true
47 47 position: 4
48 48 position_name: high2
49 49 enumerations_008:
50 50 name: Immediate
51 51 id: 8
52 52 type: IssuePriority
53 53 active: true
54 54 position: 5
55 55 position_name: highest
56 56 enumerations_009:
57 57 name: Design
58 58 id: 9
59 59 type: TimeEntryActivity
60 60 position: 1
61 61 active: true
62 62 enumerations_010:
63 63 name: Development
64 64 id: 10
65 65 type: TimeEntryActivity
66 66 position: 2
67 67 is_default: true
68 68 active: true
69 69 enumerations_011:
70 70 name: QA
71 71 id: 11
72 72 type: TimeEntryActivity
73 73 position: 3
74 74 active: true
75 75 enumerations_012:
76 76 name: Default Enumeration
77 77 id: 12
78 78 type: Enumeration
79 79 is_default: true
80 80 active: true
81 position: 1
81 82 enumerations_013:
82 83 name: Another Enumeration
83 84 id: 13
84 85 type: Enumeration
85 86 active: true
87 position: 2
86 88 enumerations_014:
87 89 name: Inactive Activity
88 90 id: 14
89 91 type: TimeEntryActivity
90 92 position: 4
91 93 active: false
92 94 enumerations_015:
93 95 name: Inactive Priority
94 96 id: 15
95 97 type: IssuePriority
96 98 position: 6
97 99 active: false
98 100 enumerations_016:
99 101 name: Inactive Document Category
100 102 id: 16
101 103 type: DocumentCategory
102 104 active: false
103 105 position: 4
@@ -1,207 +1,207
1 1 ---
2 2 roles_001:
3 3 name: Manager
4 4 id: 1
5 5 builtin: 0
6 6 issues_visibility: all
7 7 users_visibility: all
8 8 permissions: |
9 9 ---
10 10 - :add_project
11 11 - :edit_project
12 12 - :close_project
13 13 - :select_project_modules
14 14 - :manage_members
15 15 - :manage_versions
16 16 - :manage_categories
17 17 - :view_issues
18 18 - :add_issues
19 19 - :edit_issues
20 20 - :copy_issues
21 21 - :manage_issue_relations
22 22 - :manage_subtasks
23 23 - :add_issue_notes
24 24 - :delete_issues
25 25 - :view_issue_watchers
26 26 - :add_issue_watchers
27 27 - :set_issues_private
28 28 - :set_notes_private
29 29 - :view_private_notes
30 30 - :delete_issue_watchers
31 31 - :manage_public_queries
32 32 - :save_queries
33 33 - :view_gantt
34 34 - :view_calendar
35 35 - :log_time
36 36 - :view_time_entries
37 37 - :edit_time_entries
38 38 - :delete_time_entries
39 39 - :manage_news
40 40 - :comment_news
41 41 - :view_documents
42 42 - :add_documents
43 43 - :edit_documents
44 44 - :delete_documents
45 45 - :view_wiki_pages
46 46 - :export_wiki_pages
47 47 - :view_wiki_edits
48 48 - :edit_wiki_pages
49 49 - :delete_wiki_pages_attachments
50 50 - :protect_wiki_pages
51 51 - :delete_wiki_pages
52 52 - :rename_wiki_pages
53 53 - :add_messages
54 54 - :edit_messages
55 55 - :delete_messages
56 56 - :manage_boards
57 57 - :view_files
58 58 - :manage_files
59 59 - :browse_repository
60 60 - :manage_repository
61 61 - :view_changesets
62 62 - :manage_related_issues
63 63 - :manage_project_activities
64 64 - :import_issues
65 65
66 66 position: 1
67 67 roles_002:
68 68 name: Developer
69 69 id: 2
70 70 builtin: 0
71 71 issues_visibility: default
72 72 users_visibility: all
73 73 permissions: |
74 74 ---
75 75 - :edit_project
76 76 - :manage_members
77 77 - :manage_versions
78 78 - :manage_categories
79 79 - :view_issues
80 80 - :add_issues
81 81 - :edit_issues
82 82 - :copy_issues
83 83 - :manage_issue_relations
84 84 - :manage_subtasks
85 85 - :add_issue_notes
86 86 - :delete_issues
87 87 - :view_issue_watchers
88 88 - :save_queries
89 89 - :view_gantt
90 90 - :view_calendar
91 91 - :log_time
92 92 - :view_time_entries
93 93 - :edit_own_time_entries
94 94 - :manage_news
95 95 - :comment_news
96 96 - :view_documents
97 97 - :add_documents
98 98 - :edit_documents
99 99 - :delete_documents
100 100 - :view_wiki_pages
101 101 - :view_wiki_edits
102 102 - :edit_wiki_pages
103 103 - :protect_wiki_pages
104 104 - :delete_wiki_pages
105 105 - :add_messages
106 106 - :edit_own_messages
107 107 - :delete_own_messages
108 108 - :manage_boards
109 109 - :view_files
110 110 - :manage_files
111 111 - :browse_repository
112 112 - :view_changesets
113 113
114 114 position: 2
115 115 roles_003:
116 116 name: Reporter
117 117 id: 3
118 118 builtin: 0
119 119 issues_visibility: default
120 120 users_visibility: all
121 121 permissions: |
122 122 ---
123 123 - :edit_project
124 124 - :manage_members
125 125 - :manage_versions
126 126 - :manage_categories
127 127 - :view_issues
128 128 - :add_issues
129 129 - :edit_issues
130 130 - :manage_issue_relations
131 131 - :add_issue_notes
132 132 - :view_issue_watchers
133 133 - :save_queries
134 134 - :view_gantt
135 135 - :view_calendar
136 136 - :log_time
137 137 - :view_time_entries
138 138 - :manage_news
139 139 - :comment_news
140 140 - :view_documents
141 141 - :add_documents
142 142 - :edit_documents
143 143 - :delete_documents
144 144 - :view_wiki_pages
145 145 - :view_wiki_edits
146 146 - :edit_wiki_pages
147 147 - :delete_wiki_pages
148 148 - :add_messages
149 149 - :manage_boards
150 150 - :view_files
151 151 - :manage_files
152 152 - :browse_repository
153 153 - :view_changesets
154 154
155 155 position: 3
156 156 roles_004:
157 157 name: Non member
158 158 id: 4
159 159 builtin: 1
160 160 issues_visibility: default
161 161 users_visibility: all
162 162 permissions: |
163 163 ---
164 164 - :view_issues
165 165 - :add_issues
166 166 - :edit_issues
167 167 - :manage_issue_relations
168 168 - :add_issue_notes
169 169 - :save_queries
170 170 - :view_gantt
171 171 - :view_calendar
172 172 - :log_time
173 173 - :view_time_entries
174 174 - :comment_news
175 175 - :view_documents
176 176 - :view_wiki_pages
177 177 - :view_wiki_edits
178 178 - :edit_wiki_pages
179 179 - :add_messages
180 180 - :view_files
181 181 - :manage_files
182 182 - :browse_repository
183 183 - :view_changesets
184 184
185 position: 4
185 position: 1
186 186 roles_005:
187 187 name: Anonymous
188 188 id: 5
189 189 builtin: 2
190 190 issues_visibility: default
191 191 users_visibility: all
192 192 permissions: |
193 193 ---
194 194 - :view_issues
195 195 - :add_issue_notes
196 196 - :view_gantt
197 197 - :view_calendar
198 198 - :view_time_entries
199 199 - :view_documents
200 200 - :view_wiki_pages
201 201 - :view_wiki_edits
202 202 - :view_files
203 203 - :browse_repository
204 204 - :view_changesets
205 205
206 position: 5
206 position: 1
207 207
@@ -1,208 +1,208
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class RolesControllerTest < ActionController::TestCase
21 21 fixtures :roles, :users, :members, :member_roles, :workflows, :trackers
22 22
23 23 def setup
24 24 User.current = nil
25 25 @request.session[:user_id] = 1 # admin
26 26 end
27 27
28 28 def test_index
29 29 get :index
30 30 assert_response :success
31 31 assert_template 'index'
32 32
33 33 assert_not_nil assigns(:roles)
34 34 assert_equal Role.order('builtin, position').to_a, assigns(:roles)
35 35
36 36 assert_select 'a[href="/roles/1/edit"]', :text => 'Manager'
37 37 end
38 38
39 39 def test_new
40 40 get :new
41 41 assert_response :success
42 42 assert_template 'new'
43 43 end
44 44
45 45 def test_new_with_copy
46 46 copy_from = Role.find(2)
47 47
48 48 get :new, :copy => copy_from.id.to_s
49 49 assert_response :success
50 50 assert_template 'new'
51 51
52 52 role = assigns(:role)
53 53 assert_equal copy_from.permissions, role.permissions
54 54
55 55 assert_select 'form' do
56 56 # blank name
57 57 assert_select 'input[name=?][value=""]', 'role[name]'
58 58 # edit_project permission checked
59 59 assert_select 'input[type=checkbox][name=?][value=edit_project][checked=checked]', 'role[permissions][]'
60 60 # add_project permission not checked
61 61 assert_select 'input[type=checkbox][name=?][value=add_project]', 'role[permissions][]'
62 62 assert_select 'input[type=checkbox][name=?][value=add_project][checked=checked]', 'role[permissions][]', 0
63 63 # workflow copy selected
64 64 assert_select 'select[name=?]', 'copy_workflow_from' do
65 65 assert_select 'option[value="2"][selected=selected]'
66 66 end
67 67 end
68 68 end
69 69
70 70 def test_create_with_validaton_failure
71 71 post :create, :role => {:name => '',
72 72 :permissions => ['add_issues', 'edit_issues', 'log_time', ''],
73 73 :assignable => '0'}
74 74
75 75 assert_response :success
76 76 assert_template 'new'
77 77 assert_select 'div#errorExplanation'
78 78 end
79 79
80 80 def test_create_without_workflow_copy
81 81 post :create, :role => {:name => 'RoleWithoutWorkflowCopy',
82 82 :permissions => ['add_issues', 'edit_issues', 'log_time', ''],
83 83 :assignable => '0'}
84 84
85 85 assert_redirected_to '/roles'
86 86 role = Role.find_by_name('RoleWithoutWorkflowCopy')
87 87 assert_not_nil role
88 88 assert_equal [:add_issues, :edit_issues, :log_time], role.permissions
89 89 assert !role.assignable?
90 90 end
91 91
92 92 def test_create_with_workflow_copy
93 93 post :create, :role => {:name => 'RoleWithWorkflowCopy',
94 94 :permissions => ['add_issues', 'edit_issues', 'log_time', ''],
95 95 :assignable => '0'},
96 96 :copy_workflow_from => '1'
97 97
98 98 assert_redirected_to '/roles'
99 99 role = Role.find_by_name('RoleWithWorkflowCopy')
100 100 assert_not_nil role
101 101 assert_equal Role.find(1).workflow_rules.size, role.workflow_rules.size
102 102 end
103 103
104 104 def test_edit
105 105 get :edit, :id => 1
106 106 assert_response :success
107 107 assert_template 'edit'
108 108 assert_equal Role.find(1), assigns(:role)
109 109 assert_select 'select[name=?]', 'role[issues_visibility]'
110 110 end
111 111
112 112 def test_edit_anonymous
113 113 get :edit, :id => Role.anonymous.id
114 114 assert_response :success
115 115 assert_template 'edit'
116 116 assert_select 'select[name=?]', 'role[issues_visibility]', 0
117 117 end
118 118
119 119 def test_edit_invalid_should_respond_with_404
120 120 get :edit, :id => 999
121 121 assert_response 404
122 122 end
123 123
124 124 def test_update
125 125 put :update, :id => 1,
126 126 :role => {:name => 'Manager',
127 127 :permissions => ['edit_project', ''],
128 128 :assignable => '0'}
129 129
130 130 assert_redirected_to '/roles'
131 131 role = Role.find(1)
132 132 assert_equal [:edit_project], role.permissions
133 133 end
134 134
135 135 def test_update_with_failure
136 136 put :update, :id => 1, :role => {:name => ''}
137 137 assert_response :success
138 138 assert_template 'edit'
139 139 end
140 140
141 141 def test_destroy
142 142 r = Role.create!(:name => 'ToBeDestroyed', :permissions => [:view_wiki_pages])
143 143
144 144 delete :destroy, :id => r
145 145 assert_redirected_to '/roles'
146 146 assert_nil Role.find_by_id(r.id)
147 147 end
148 148
149 149 def test_destroy_role_in_use
150 150 delete :destroy, :id => 1
151 151 assert_redirected_to '/roles'
152 152 assert_equal 'This role is in use and cannot be deleted.', flash[:error]
153 153 assert_not_nil Role.find_by_id(1)
154 154 end
155 155
156 156 def test_get_permissions
157 157 get :permissions
158 158 assert_response :success
159 159 assert_template 'permissions'
160 160
161 161 assert_not_nil assigns(:roles)
162 162 assert_equal Role.order('builtin, position').to_a, assigns(:roles)
163 163
164 164 assert_select 'input[name=?][type=checkbox][value=add_issues][checked=checked]', 'permissions[3][]'
165 165 assert_select 'input[name=?][type=checkbox][value=delete_issues]:not([checked])', 'permissions[3][]'
166 166 end
167 167
168 168 def test_post_permissions
169 169 post :permissions, :permissions => { '0' => '', '1' => ['edit_issues'], '3' => ['add_issues', 'delete_issues']}
170 170 assert_redirected_to '/roles'
171 171
172 172 assert_equal [:edit_issues], Role.find(1).permissions
173 173 assert_equal [:add_issues, :delete_issues], Role.find(3).permissions
174 174 assert Role.find(2).permissions.empty?
175 175 end
176 176
177 177 def test_clear_all_permissions
178 178 post :permissions, :permissions => { '0' => '' }
179 179 assert_redirected_to '/roles'
180 180 assert Role.find(1).permissions.empty?
181 181 end
182 182
183 183 def test_move_highest
184 184 put :update, :id => 3, :role => {:move_to => 'highest'}
185 185 assert_redirected_to '/roles'
186 186 assert_equal 1, Role.find(3).position
187 187 end
188 188
189 189 def test_move_higher
190 190 position = Role.find(3).position
191 191 put :update, :id => 3, :role => {:move_to => 'higher'}
192 192 assert_redirected_to '/roles'
193 193 assert_equal position - 1, Role.find(3).position
194 194 end
195 195
196 196 def test_move_lower
197 197 position = Role.find(2).position
198 198 put :update, :id => 2, :role => {:move_to => 'lower'}
199 199 assert_redirected_to '/roles'
200 200 assert_equal position + 1, Role.find(2).position
201 201 end
202 202
203 203 def test_move_lowest
204 204 put :update, :id => 2, :role => {:move_to => 'lowest'}
205 205 assert_redirected_to '/roles'
206 assert_equal Role.count, Role.find(2).position
206 assert_equal Role.givable.count, Role.find(2).position
207 207 end
208 208 end
@@ -1,271 +1,276
1 1 module ObjectHelpers
2 2 def User.generate!(attributes={})
3 3 @generated_user_login ||= 'user0'
4 4 @generated_user_login.succ!
5 5 user = User.new(attributes)
6 6 user.login = @generated_user_login.dup if user.login.blank?
7 7 user.mail = "#{@generated_user_login}@example.com" if user.mail.blank?
8 8 user.firstname = "Bob" if user.firstname.blank?
9 9 user.lastname = "Doe" if user.lastname.blank?
10 10 yield user if block_given?
11 11 user.save!
12 12 user
13 13 end
14 14
15 15 def User.add_to_project(user, project, roles=nil)
16 16 roles = Role.find(1) if roles.nil?
17 17 roles = [roles] if roles.is_a?(Role)
18 18 Member.create!(:principal => user, :project => project, :roles => roles)
19 19 end
20 20
21 21 def Group.generate!(attributes={})
22 22 @generated_group_name ||= 'Group 0'
23 23 @generated_group_name.succ!
24 24 group = Group.new(attributes)
25 25 group.name = @generated_group_name.dup if group.name.blank?
26 26 yield group if block_given?
27 27 group.save!
28 28 group
29 29 end
30 30
31 31 def Project.generate!(attributes={})
32 32 @generated_project_identifier ||= 'project-0000'
33 33 @generated_project_identifier.succ!
34 34 project = Project.new(attributes)
35 35 project.name = @generated_project_identifier.dup if project.name.blank?
36 36 project.identifier = @generated_project_identifier.dup if project.identifier.blank?
37 37 yield project if block_given?
38 38 project.save!
39 39 project
40 40 end
41 41
42 42 def Project.generate_with_parent!(*args)
43 43 attributes = args.last.is_a?(Hash) ? args.pop : {}
44 44 parent = args.size > 0 ? args.first : Project.generate!
45 45
46 46 project = Project.generate!(attributes) do |p|
47 47 p.parent = parent
48 48 end
49 49 parent.reload if parent
50 50 project
51 51 end
52 52
53 53 def IssueStatus.generate!(attributes={})
54 54 @generated_status_name ||= 'Status 0'
55 55 @generated_status_name.succ!
56 56 status = IssueStatus.new(attributes)
57 57 status.name = @generated_status_name.dup if status.name.blank?
58 58 yield status if block_given?
59 59 status.save!
60 60 status
61 61 end
62 62
63 def Tracker.generate!(attributes={})
63 def Tracker.generate(attributes={})
64 64 @generated_tracker_name ||= 'Tracker 0'
65 65 @generated_tracker_name.succ!
66 66 tracker = Tracker.new(attributes)
67 67 tracker.name = @generated_tracker_name.dup if tracker.name.blank?
68 68 tracker.default_status ||= IssueStatus.order('position').first || IssueStatus.generate!
69 69 yield tracker if block_given?
70 tracker
71 end
72
73 def Tracker.generate!(attributes={}, &block)
74 tracker = Tracker.generate(attributes, &block)
70 75 tracker.save!
71 76 tracker
72 77 end
73 78
74 79 def Role.generate!(attributes={})
75 80 @generated_role_name ||= 'Role 0'
76 81 @generated_role_name.succ!
77 82 role = Role.new(attributes)
78 83 role.name = @generated_role_name.dup if role.name.blank?
79 84 yield role if block_given?
80 85 role.save!
81 86 role
82 87 end
83 88
84 89 # Generates an unsaved Issue
85 90 def Issue.generate(attributes={})
86 91 issue = Issue.new(attributes)
87 92 issue.project ||= Project.find(1)
88 93 issue.tracker ||= issue.project.trackers.first
89 94 issue.subject = 'Generated' if issue.subject.blank?
90 95 issue.author ||= User.find(2)
91 96 yield issue if block_given?
92 97 issue
93 98 end
94 99
95 100 # Generates a saved Issue
96 101 def Issue.generate!(attributes={}, &block)
97 102 issue = Issue.generate(attributes, &block)
98 103 issue.save!
99 104 issue
100 105 end
101 106
102 107 # Generates an issue with 2 children and a grandchild
103 108 def Issue.generate_with_descendants!(attributes={})
104 109 issue = Issue.generate!(attributes)
105 110 child = Issue.generate!(:project => issue.project, :subject => 'Child1', :parent_issue_id => issue.id)
106 111 Issue.generate!(:project => issue.project, :subject => 'Child2', :parent_issue_id => issue.id)
107 112 Issue.generate!(:project => issue.project, :subject => 'Child11', :parent_issue_id => child.id)
108 113 issue.reload
109 114 end
110 115
111 116 def Issue.generate_with_child!(attributes={})
112 117 issue = Issue.generate!(attributes)
113 118 Issue.generate!(:parent_issue_id => issue.id)
114 119 issue.reload
115 120 end
116 121
117 122 def Journal.generate!(attributes={})
118 123 journal = Journal.new(attributes)
119 124 journal.user ||= User.first
120 125 journal.journalized ||= Issue.first
121 126 yield journal if block_given?
122 127 journal.save!
123 128 journal
124 129 end
125 130
126 131 def Version.generate!(attributes={})
127 132 @generated_version_name ||= 'Version 0'
128 133 @generated_version_name.succ!
129 134 version = Version.new(attributes)
130 135 version.name = @generated_version_name.dup if version.name.blank?
131 136 version.project ||= Project.find(1)
132 137 yield version if block_given?
133 138 version.save!
134 139 version
135 140 end
136 141
137 142 def TimeEntry.generate!(attributes={})
138 143 entry = TimeEntry.new(attributes)
139 144 entry.user ||= User.find(2)
140 145 entry.issue ||= Issue.find(1) unless entry.project
141 146 entry.project ||= entry.issue.project
142 147 entry.activity ||= TimeEntryActivity.first
143 148 entry.spent_on ||= Date.today
144 149 entry.hours ||= 1.0
145 150 entry.save!
146 151 entry
147 152 end
148 153
149 154 def AuthSource.generate!(attributes={})
150 155 @generated_auth_source_name ||= 'Auth 0'
151 156 @generated_auth_source_name.succ!
152 157 source = AuthSource.new(attributes)
153 158 source.name = @generated_auth_source_name.dup if source.name.blank?
154 159 yield source if block_given?
155 160 source.save!
156 161 source
157 162 end
158 163
159 164 def Board.generate!(attributes={})
160 165 @generated_board_name ||= 'Forum 0'
161 166 @generated_board_name.succ!
162 167 board = Board.new(attributes)
163 168 board.name = @generated_board_name.dup if board.name.blank?
164 169 board.description = @generated_board_name.dup if board.description.blank?
165 170 yield board if block_given?
166 171 board.save!
167 172 board
168 173 end
169 174
170 175 def Attachment.generate!(attributes={})
171 176 @generated_filename ||= 'testfile0'
172 177 @generated_filename.succ!
173 178 attributes = attributes.dup
174 179 attachment = Attachment.new(attributes)
175 180 attachment.container ||= Issue.find(1)
176 181 attachment.author ||= User.find(2)
177 182 attachment.filename = @generated_filename.dup if attachment.filename.blank?
178 183 attachment.save!
179 184 attachment
180 185 end
181 186
182 187 def CustomField.generate!(attributes={})
183 188 @generated_custom_field_name ||= 'Custom field 0'
184 189 @generated_custom_field_name.succ!
185 190 field = new(attributes)
186 191 field.name = @generated_custom_field_name.dup if field.name.blank?
187 192 field.field_format = 'string' if field.field_format.blank?
188 193 yield field if block_given?
189 194 field.save!
190 195 field
191 196 end
192 197
193 198 def IssueCustomField.generate!(attributes={})
194 199 super do |field|
195 200 field.is_for_all = true unless attributes.key?(:is_for_all)
196 201 field.tracker_ids = Tracker.all.ids unless attributes.key?(:tracker_ids) || attributes.key?(:trackers)
197 202 end
198 203 end
199 204
200 205 def Changeset.generate!(attributes={})
201 206 @generated_changeset_rev ||= '123456'
202 207 @generated_changeset_rev.succ!
203 208 changeset = new(attributes)
204 209 changeset.repository ||= Project.find(1).repository
205 210 changeset.revision ||= @generated_changeset_rev
206 211 changeset.committed_on ||= Time.now
207 212 yield changeset if block_given?
208 213 changeset.save!
209 214 changeset
210 215 end
211 216
212 217 def Query.generate!(attributes={})
213 218 query = new(attributes)
214 219 query.name = "Generated query" if query.name.blank?
215 220 query.user ||= User.find(1)
216 221 query.save!
217 222 query
218 223 end
219 224
220 225 def generate_import(fixture_name='import_issues.csv')
221 226 import = IssueImport.new
222 227 import.user_id = 2
223 228 import.file = uploaded_test_file(fixture_name, 'text/csv')
224 229 import.save!
225 230 import
226 231 end
227 232
228 233 def generate_import_with_mapping(fixture_name='import_issues.csv')
229 234 import = generate_import(fixture_name)
230 235
231 236 import.settings = {
232 237 'separator' => ";", 'wrapper' => '"', 'encoding' => "UTF-8",
233 238 'mapping' => {'project_id' => '1', 'tracker_id' => '2', 'subject' => '1'}
234 239 }
235 240 import.save!
236 241 import
237 242 end
238 243 end
239 244
240 245 module TrackerObjectHelpers
241 246 def generate_transitions!(*args)
242 247 options = args.last.is_a?(Hash) ? args.pop : {}
243 248 if args.size == 1
244 249 args << args.first
245 250 end
246 251 if options[:clear]
247 252 WorkflowTransition.where(:tracker_id => id).delete_all
248 253 end
249 254 args.each_cons(2) do |old_status_id, new_status_id|
250 255 WorkflowTransition.create!(
251 256 :tracker => self,
252 257 :role_id => (options[:role_id] || 1),
253 258 :old_status_id => old_status_id,
254 259 :new_status_id => new_status_id
255 260 )
256 261 end
257 262 end
258 263 end
259 264 Tracker.send :include, TrackerObjectHelpers
260 265
261 266 module IssueObjectHelpers
262 267 def close!
263 268 self.status = IssueStatus.where(:is_closed => true).first
264 269 save!
265 270 end
266 271
267 272 def generate_child!(attributes={})
268 273 Issue.generate!(attributes.merge(:parent_issue_id => self.id))
269 274 end
270 275 end
271 276 Issue.send :include, IssueObjectHelpers
@@ -1,174 +1,175
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class EnumerationTest < ActiveSupport::TestCase
21 21 fixtures :enumerations, :issues, :custom_fields, :custom_values
22 22
23 23 def test_objects_count
24 24 # low priority
25 25 assert_equal 6, Enumeration.find(4).objects_count
26 26 # urgent
27 27 assert_equal 0, Enumeration.find(7).objects_count
28 28 end
29 29
30 30 def test_in_use
31 31 # low priority
32 32 assert Enumeration.find(4).in_use?
33 33 # urgent
34 34 assert !Enumeration.find(7).in_use?
35 35 end
36 36
37 37 def test_default
38 38 e = Enumeration.default
39 39 assert e.is_a?(Enumeration)
40 40 assert e.is_default?
41 41 assert e.active?
42 42 assert_equal 'Default Enumeration', e.name
43 43 end
44 44
45 45 def test_default_non_active
46 46 e = Enumeration.find(12)
47 47 assert e.is_a?(Enumeration)
48 48 assert e.is_default?
49 49 assert e.active?
50 50 e.update_attributes(:active => false)
51 51 assert e.is_default?
52 52 assert !e.active?
53 53 end
54 54
55 55 def test_create
56 56 e = Enumeration.new(:name => 'Not default', :is_default => false)
57 57 e.type = 'Enumeration'
58 58 assert e.save
59 59 assert_equal 'Default Enumeration', Enumeration.default.name
60 60 end
61 61
62 62 def test_create_as_default
63 63 e = Enumeration.new(:name => 'Very urgent', :is_default => true)
64 64 e.type = 'Enumeration'
65 65 assert e.save
66 66 assert_equal e, Enumeration.default
67 67 end
68 68
69 69 def test_update_default
70 70 e = Enumeration.default
71 71 e.update_attributes(:name => 'Changed', :is_default => true)
72 72 assert_equal e, Enumeration.default
73 73 end
74 74
75 75 def test_update_default_to_non_default
76 76 e = Enumeration.default
77 77 e.update_attributes(:name => 'Changed', :is_default => false)
78 78 assert_nil Enumeration.default
79 79 end
80 80
81 81 def test_change_default
82 82 e = Enumeration.find_by_name('Default Enumeration')
83 83 e.update_attributes(:name => 'Changed Enumeration', :is_default => true)
84 84 assert_equal e, Enumeration.default
85 85 end
86 86
87 87 def test_destroy_with_reassign
88 88 Enumeration.find(4).destroy(Enumeration.find(6))
89 89 assert_nil Issue.where(:priority_id => 4).first
90 90 assert_equal 6, Enumeration.find(6).objects_count
91 91 end
92 92
93 93 def test_should_be_customizable
94 94 assert Enumeration.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods)
95 95 end
96 96
97 97 def test_should_belong_to_a_project
98 98 association = Enumeration.reflect_on_association(:project)
99 99 assert association, "No Project association found"
100 100 assert_equal :belongs_to, association.macro
101 101 end
102 102
103 103 def test_should_act_as_tree
104 104 enumeration = Enumeration.find(4)
105 105
106 106 assert enumeration.respond_to?(:parent)
107 107 assert enumeration.respond_to?(:children)
108 108 end
109 109
110 110 def test_is_override
111 111 # Defaults to off
112 112 enumeration = Enumeration.find(4)
113 113 assert !enumeration.is_override?
114 114
115 115 # Setup as an override
116 116 enumeration.parent = Enumeration.find(5)
117 117 assert enumeration.is_override?
118 118 end
119 119
120 120 def test_get_subclasses
121 121 classes = Enumeration.get_subclasses
122 122 assert_include IssuePriority, classes
123 123 assert_include DocumentCategory, classes
124 124 assert_include TimeEntryActivity, classes
125 125
126 126 classes.each do |klass|
127 127 assert_equal Enumeration, klass.superclass
128 128 end
129 129 end
130 130
131 131 def test_list_should_be_scoped_for_each_type
132 132 Enumeration.delete_all
133 133
134 134 a = IssuePriority.create!(:name => 'A')
135 135 b = IssuePriority.create!(:name => 'B')
136 136 c = DocumentCategory.create!(:name => 'C')
137 137
138 138 assert_equal [1, 2, 1], [a, b, c].map(&:reload).map(&:position)
139 139 end
140 140
141 141 def test_override_should_be_created_with_same_position_as_parent
142 142 Enumeration.delete_all
143 143
144 144 a = IssuePriority.create!(:name => 'A')
145 145 b = IssuePriority.create!(:name => 'B')
146 146 override = IssuePriority.create!(:name => 'BB', :parent_id => b.id)
147 147
148 148 assert_equal [1, 2, 2], [a, b, override].map(&:reload).map(&:position)
149 149 end
150 150
151 151 def test_override_position_should_be_updated_with_parent_position
152 152 Enumeration.delete_all
153 153
154 154 a = IssuePriority.create!(:name => 'A')
155 155 b = IssuePriority.create!(:name => 'B')
156 156 override = IssuePriority.create!(:name => 'BB', :parent_id => b.id)
157 157 b.move_to = 'higher'
158 b.save!
158 159
159 160 assert_equal [2, 1, 1], [a, b, override].map(&:reload).map(&:position)
160 161 end
161 162
162 163 def test_destroying_override_should_not_update_positions
163 164 Enumeration.delete_all
164 165
165 166 a = IssuePriority.create!(:name => 'A')
166 167 b = IssuePriority.create!(:name => 'B')
167 168 c = IssuePriority.create!(:name => 'C')
168 169 override = IssuePriority.create!(:name => 'BB', :parent_id => b.id)
169 170 assert_equal [1, 2, 3, 2], [a, b, c, override].map(&:reload).map(&:position)
170 171
171 172 override.destroy
172 173 assert_equal [1, 2, 3], [a, b, c].map(&:reload).map(&:position)
173 174 end
174 175 end
@@ -1,120 +1,102
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssuePriorityTest < ActiveSupport::TestCase
21 21 fixtures :enumerations, :issues
22 22
23 23 def test_named_scope
24 24 assert_equal Enumeration.find_by_name('Normal'), Enumeration.named('normal').first
25 25 end
26 26
27 27 def test_default_should_return_the_default_priority
28 28 assert_equal Enumeration.find_by_name('Normal'), IssuePriority.default
29 29 end
30 30
31 31 def test_default_should_return_nil_when_no_default_priority
32 32 IssuePriority.update_all :is_default => false
33 33 assert_nil IssuePriority.default
34 34 end
35 35
36 36 def test_should_be_an_enumeration
37 37 assert IssuePriority.ancestors.include?(Enumeration)
38 38 end
39 39
40 40 def test_objects_count
41 41 # low priority
42 42 assert_equal 6, IssuePriority.find(4).objects_count
43 43 # urgent
44 44 assert_equal 0, IssuePriority.find(7).objects_count
45 45 end
46 46
47 47 def test_option_name
48 48 assert_equal :enumeration_issue_priorities, IssuePriority.new.option_name
49 49 end
50 50
51 51 def test_should_be_created_at_last_position
52 52 IssuePriority.delete_all
53 53
54 54 priorities = [1, 2, 3].map {|i| IssuePriority.create!(:name => "P#{i}")}
55 55 assert_equal [1, 2, 3], priorities.map(&:position)
56 56 end
57 57
58 def test_reset_positions_in_list_should_set_sequential_positions
59 IssuePriority.delete_all
60
61 priorities = [1, 2, 3].map {|i| IssuePriority.create!(:name => "P#{i}")}
62 priorities[0].update_attribute :position, 4
63 priorities[1].update_attribute :position, 2
64 priorities[2].update_attribute :position, 7
65 assert_equal [4, 2, 7], priorities.map(&:reload).map(&:position)
66
67 priorities[0].reset_positions_in_list
68 assert_equal [2, 1, 3], priorities.map(&:reload).map(&:position)
69 end
70
71 def test_moving_in_list_should_reset_positions
72 priority = IssuePriority.first
73 priority.expects(:reset_positions_in_list).once
74 priority.move_to = 'higher'
75 end
76
77 58 def test_clear_position_names_should_set_position_names_to_nil
78 59 IssuePriority.clear_position_names
79 60 assert IssuePriority.all.all? {|priority| priority.position_name.nil?}
80 61 end
81 62
82 63 def test_compute_position_names_with_default_priority
83 64 IssuePriority.clear_position_names
84 65
85 66 IssuePriority.compute_position_names
86 67 assert_equal %w(lowest default high3 high2 highest), IssuePriority.active.to_a.sort.map(&:position_name)
87 68 end
88 69
89 70 def test_compute_position_names_without_default_priority_should_split_priorities
90 71 IssuePriority.clear_position_names
91 72 IssuePriority.update_all :is_default => false
92 73
93 74 IssuePriority.compute_position_names
94 75 assert_equal %w(lowest low2 default high2 highest), IssuePriority.active.to_a.sort.map(&:position_name)
95 76 end
96 77
97 78 def test_adding_a_priority_should_update_position_names
98 79 priority = IssuePriority.create!(:name => 'New')
99 80 assert_equal %w(lowest default high4 high3 high2 highest), IssuePriority.active.to_a.sort.map(&:position_name)
100 81 end
101 82
102 83 def test_moving_a_priority_should_update_position_names
103 84 prio = IssuePriority.first
104 85 prio.move_to = 'lowest'
86 prio.save!
105 87 prio.reload
106 88 assert_equal 'highest', prio.position_name
107 89 end
108 90
109 91 def test_deactivating_a_priority_should_update_position_names
110 92 prio = IssuePriority.active.order(:position).last
111 93 prio.active = false
112 94 prio.save
113 95 assert_equal 'highest', IssuePriority.active.order(:position).last.position_name
114 96 end
115 97
116 98 def test_destroying_a_priority_should_update_position_names
117 99 IssuePriority.find_by_position_name('highest').destroy
118 100 assert_equal %w(lowest default high2 highest), IssuePriority.active.to_a.sort.map(&:position_name)
119 101 end
120 102 end
General Comments 0
You need to be logged in to leave comments. Login now