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