##// END OF EJS Templates
Fixed: unchecking status filter on the issue list has no effect (#6844)....
Jean-Philippe Lang -
r4273:63866407f123
parent child
Show More
@@ -1,100 +1,99
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 module QueriesHelper
18 module QueriesHelper
19
19
20 def operators_for_select(filter_type)
20 def operators_for_select(filter_type)
21 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
21 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
22 end
22 end
23
23
24 def column_header(column)
24 def column_header(column)
25 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
25 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
26 :default_order => column.default_order) :
26 :default_order => column.default_order) :
27 content_tag('th', column.caption)
27 content_tag('th', column.caption)
28 end
28 end
29
29
30 def column_content(column, issue)
30 def column_content(column, issue)
31 value = column.value(issue)
31 value = column.value(issue)
32
32
33 case value.class.name
33 case value.class.name
34 when 'String'
34 when 'String'
35 if column.name == :subject
35 if column.name == :subject
36 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
36 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
37 else
37 else
38 h(value)
38 h(value)
39 end
39 end
40 when 'Time'
40 when 'Time'
41 format_time(value)
41 format_time(value)
42 when 'Date'
42 when 'Date'
43 format_date(value)
43 format_date(value)
44 when 'Fixnum', 'Float'
44 when 'Fixnum', 'Float'
45 if column.name == :done_ratio
45 if column.name == :done_ratio
46 progress_bar(value, :width => '80px')
46 progress_bar(value, :width => '80px')
47 else
47 else
48 value.to_s
48 value.to_s
49 end
49 end
50 when 'User'
50 when 'User'
51 link_to_user value
51 link_to_user value
52 when 'Project'
52 when 'Project'
53 link_to_project value
53 link_to_project value
54 when 'Version'
54 when 'Version'
55 link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
55 link_to(h(value), :controller => 'versions', :action => 'show', :id => value)
56 when 'TrueClass'
56 when 'TrueClass'
57 l(:general_text_Yes)
57 l(:general_text_Yes)
58 when 'FalseClass'
58 when 'FalseClass'
59 l(:general_text_No)
59 l(:general_text_No)
60 when 'Issue'
60 when 'Issue'
61 link_to_issue(value, :subject => false)
61 link_to_issue(value, :subject => false)
62 else
62 else
63 h(value)
63 h(value)
64 end
64 end
65 end
65 end
66
66
67 # Retrieve query from session or build a new query
67 # Retrieve query from session or build a new query
68 def retrieve_query
68 def retrieve_query
69 if !params[:query_id].blank?
69 if !params[:query_id].blank?
70 cond = "project_id IS NULL"
70 cond = "project_id IS NULL"
71 cond << " OR project_id = #{@project.id}" if @project
71 cond << " OR project_id = #{@project.id}" if @project
72 @query = Query.find(params[:query_id], :conditions => cond)
72 @query = Query.find(params[:query_id], :conditions => cond)
73 @query.project = @project
73 @query.project = @project
74 session[:query] = {:id => @query.id, :project_id => @query.project_id}
74 session[:query] = {:id => @query.id, :project_id => @query.project_id}
75 sort_clear
75 sort_clear
76 else
76 else
77 if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
77 if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
78 # Give it a name, required to be valid
78 # Give it a name, required to be valid
79 @query = Query.new(:name => "_")
79 @query = Query.new(:name => "_")
80 @query.project = @project
80 @query.project = @project
81 if params[:fields] and params[:fields].is_a? Array
81 if params[:fields]
82 params[:fields].each do |field|
82 @query.filters = {}
83 @query.add_filter(field, params[:operators][field], params[:values][field])
83 @query.add_filters(params[:fields], params[:operators], params[:values])
84 end
85 else
84 else
86 @query.available_filters.keys.each do |field|
85 @query.available_filters.keys.each do |field|
87 @query.add_short_filter(field, params[field]) if params[field]
86 @query.add_short_filter(field, params[field]) if params[field]
88 end
87 end
89 end
88 end
90 @query.group_by = params[:group_by]
89 @query.group_by = params[:group_by]
91 @query.column_names = params[:query] && params[:query][:column_names]
90 @query.column_names = params[:query] && params[:query][:column_names]
92 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
91 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
93 else
92 else
94 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
93 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
95 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
94 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
96 @query.project = @project
95 @query.project = @project
97 end
96 end
98 end
97 end
99 end
98 end
100 end
99 end
@@ -1,643 +1,645
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :groupable, :default_order
19 attr_accessor :name, :sortable, :groupable, :default_order
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.groupable = options[:groupable] || false
25 self.groupable = options[:groupable] || false
26 if groupable == true
26 if groupable == true
27 self.groupable = name.to_s
27 self.groupable = name.to_s
28 end
28 end
29 self.default_order = options[:default_order]
29 self.default_order = options[:default_order]
30 @caption_key = options[:caption] || "field_#{name}"
30 @caption_key = options[:caption] || "field_#{name}"
31 end
31 end
32
32
33 def caption
33 def caption
34 l(@caption_key)
34 l(@caption_key)
35 end
35 end
36
36
37 # Returns true if the column is sortable, otherwise false
37 # Returns true if the column is sortable, otherwise false
38 def sortable?
38 def sortable?
39 !sortable.nil?
39 !sortable.nil?
40 end
40 end
41
41
42 def value(issue)
42 def value(issue)
43 issue.send name
43 issue.send name
44 end
44 end
45 end
45 end
46
46
47 class QueryCustomFieldColumn < QueryColumn
47 class QueryCustomFieldColumn < QueryColumn
48
48
49 def initialize(custom_field)
49 def initialize(custom_field)
50 self.name = "cf_#{custom_field.id}".to_sym
50 self.name = "cf_#{custom_field.id}".to_sym
51 self.sortable = custom_field.order_statement || false
51 self.sortable = custom_field.order_statement || false
52 if %w(list date bool int).include?(custom_field.field_format)
52 if %w(list date bool int).include?(custom_field.field_format)
53 self.groupable = custom_field.order_statement
53 self.groupable = custom_field.order_statement
54 end
54 end
55 self.groupable ||= false
55 self.groupable ||= false
56 @cf = custom_field
56 @cf = custom_field
57 end
57 end
58
58
59 def caption
59 def caption
60 @cf.name
60 @cf.name
61 end
61 end
62
62
63 def custom_field
63 def custom_field
64 @cf
64 @cf
65 end
65 end
66
66
67 def value(issue)
67 def value(issue)
68 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
68 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
69 cv && @cf.cast_value(cv.value)
69 cv && @cf.cast_value(cv.value)
70 end
70 end
71 end
71 end
72
72
73 class Query < ActiveRecord::Base
73 class Query < ActiveRecord::Base
74 class StatementInvalid < ::ActiveRecord::StatementInvalid
74 class StatementInvalid < ::ActiveRecord::StatementInvalid
75 end
75 end
76
76
77 belongs_to :project
77 belongs_to :project
78 belongs_to :user
78 belongs_to :user
79 serialize :filters
79 serialize :filters
80 serialize :column_names
80 serialize :column_names
81 serialize :sort_criteria, Array
81 serialize :sort_criteria, Array
82
82
83 attr_protected :project_id, :user_id
83 attr_protected :project_id, :user_id
84
84
85 validates_presence_of :name, :on => :save
85 validates_presence_of :name, :on => :save
86 validates_length_of :name, :maximum => 255
86 validates_length_of :name, :maximum => 255
87
87
88 @@operators = { "=" => :label_equals,
88 @@operators = { "=" => :label_equals,
89 "!" => :label_not_equals,
89 "!" => :label_not_equals,
90 "o" => :label_open_issues,
90 "o" => :label_open_issues,
91 "c" => :label_closed_issues,
91 "c" => :label_closed_issues,
92 "!*" => :label_none,
92 "!*" => :label_none,
93 "*" => :label_all,
93 "*" => :label_all,
94 ">=" => :label_greater_or_equal,
94 ">=" => :label_greater_or_equal,
95 "<=" => :label_less_or_equal,
95 "<=" => :label_less_or_equal,
96 "<t+" => :label_in_less_than,
96 "<t+" => :label_in_less_than,
97 ">t+" => :label_in_more_than,
97 ">t+" => :label_in_more_than,
98 "t+" => :label_in,
98 "t+" => :label_in,
99 "t" => :label_today,
99 "t" => :label_today,
100 "w" => :label_this_week,
100 "w" => :label_this_week,
101 ">t-" => :label_less_than_ago,
101 ">t-" => :label_less_than_ago,
102 "<t-" => :label_more_than_ago,
102 "<t-" => :label_more_than_ago,
103 "t-" => :label_ago,
103 "t-" => :label_ago,
104 "~" => :label_contains,
104 "~" => :label_contains,
105 "!~" => :label_not_contains }
105 "!~" => :label_not_contains }
106
106
107 cattr_reader :operators
107 cattr_reader :operators
108
108
109 @@operators_by_filter_type = { :list => [ "=", "!" ],
109 @@operators_by_filter_type = { :list => [ "=", "!" ],
110 :list_status => [ "o", "=", "!", "c", "*" ],
110 :list_status => [ "o", "=", "!", "c", "*" ],
111 :list_optional => [ "=", "!", "!*", "*" ],
111 :list_optional => [ "=", "!", "!*", "*" ],
112 :list_subprojects => [ "*", "!*", "=" ],
112 :list_subprojects => [ "*", "!*", "=" ],
113 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
113 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
114 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
114 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
115 :string => [ "=", "~", "!", "!~" ],
115 :string => [ "=", "~", "!", "!~" ],
116 :text => [ "~", "!~" ],
116 :text => [ "~", "!~" ],
117 :integer => [ "=", ">=", "<=", "!*", "*" ] }
117 :integer => [ "=", ">=", "<=", "!*", "*" ] }
118
118
119 cattr_reader :operators_by_filter_type
119 cattr_reader :operators_by_filter_type
120
120
121 @@available_columns = [
121 @@available_columns = [
122 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
122 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
123 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
123 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
124 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
124 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
125 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
125 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
126 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
126 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
127 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
127 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
128 QueryColumn.new(:author),
128 QueryColumn.new(:author),
129 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
129 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
130 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
130 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
131 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
131 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
132 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
132 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
133 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
133 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
134 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
134 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
135 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
135 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
136 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
136 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
137 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
137 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
138 ]
138 ]
139 cattr_reader :available_columns
139 cattr_reader :available_columns
140
140
141 def initialize(attributes = nil)
141 def initialize(attributes = nil)
142 super attributes
142 super attributes
143 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
143 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
144 end
144 end
145
145
146 def after_initialize
146 def after_initialize
147 # Store the fact that project is nil (used in #editable_by?)
147 # Store the fact that project is nil (used in #editable_by?)
148 @is_for_all = project.nil?
148 @is_for_all = project.nil?
149 end
149 end
150
150
151 def validate
151 def validate
152 filters.each_key do |field|
152 filters.each_key do |field|
153 errors.add label_for(field), :blank unless
153 errors.add label_for(field), :blank unless
154 # filter requires one or more values
154 # filter requires one or more values
155 (values_for(field) and !values_for(field).first.blank?) or
155 (values_for(field) and !values_for(field).first.blank?) or
156 # filter doesn't require any value
156 # filter doesn't require any value
157 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
157 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
158 end if filters
158 end if filters
159 end
159 end
160
160
161 def editable_by?(user)
161 def editable_by?(user)
162 return false unless user
162 return false unless user
163 # Admin can edit them all and regular users can edit their private queries
163 # Admin can edit them all and regular users can edit their private queries
164 return true if user.admin? || (!is_public && self.user_id == user.id)
164 return true if user.admin? || (!is_public && self.user_id == user.id)
165 # Members can not edit public queries that are for all project (only admin is allowed to)
165 # Members can not edit public queries that are for all project (only admin is allowed to)
166 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
166 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
167 end
167 end
168
168
169 def available_filters
169 def available_filters
170 return @available_filters if @available_filters
170 return @available_filters if @available_filters
171
171
172 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
172 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
173
173
174 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
174 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
175 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
175 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
176 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
176 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
177 "subject" => { :type => :text, :order => 8 },
177 "subject" => { :type => :text, :order => 8 },
178 "created_on" => { :type => :date_past, :order => 9 },
178 "created_on" => { :type => :date_past, :order => 9 },
179 "updated_on" => { :type => :date_past, :order => 10 },
179 "updated_on" => { :type => :date_past, :order => 10 },
180 "start_date" => { :type => :date, :order => 11 },
180 "start_date" => { :type => :date, :order => 11 },
181 "due_date" => { :type => :date, :order => 12 },
181 "due_date" => { :type => :date, :order => 12 },
182 "estimated_hours" => { :type => :integer, :order => 13 },
182 "estimated_hours" => { :type => :integer, :order => 13 },
183 "done_ratio" => { :type => :integer, :order => 14 }}
183 "done_ratio" => { :type => :integer, :order => 14 }}
184
184
185 user_values = []
185 user_values = []
186 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
186 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
187 if project
187 if project
188 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
188 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
189 else
189 else
190 project_ids = Project.all(:conditions => Project.visible_by(User.current)).collect(&:id)
190 project_ids = Project.all(:conditions => Project.visible_by(User.current)).collect(&:id)
191 if project_ids.any?
191 if project_ids.any?
192 # members of the user's projects
192 # members of the user's projects
193 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
193 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
194 end
194 end
195 end
195 end
196 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
196 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
197 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
197 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
198
198
199 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
199 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
200 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
200 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
201
201
202 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
202 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
203 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
203 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
204
204
205 if User.current.logged?
205 if User.current.logged?
206 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
206 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
207 end
207 end
208
208
209 if project
209 if project
210 # project specific filters
210 # project specific filters
211 unless @project.issue_categories.empty?
211 unless @project.issue_categories.empty?
212 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
212 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
213 end
213 end
214 unless @project.shared_versions.empty?
214 unless @project.shared_versions.empty?
215 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
215 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
216 end
216 end
217 unless @project.descendants.active.empty?
217 unless @project.descendants.active.empty?
218 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
218 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
219 end
219 end
220 add_custom_fields_filters(@project.all_issue_custom_fields)
220 add_custom_fields_filters(@project.all_issue_custom_fields)
221 else
221 else
222 # global filters for cross project issue list
222 # global filters for cross project issue list
223 system_shared_versions = Version.visible.find_all_by_sharing('system')
223 system_shared_versions = Version.visible.find_all_by_sharing('system')
224 unless system_shared_versions.empty?
224 unless system_shared_versions.empty?
225 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
225 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
226 end
226 end
227 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
227 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
228 # project filter
228 # project filter
229 project_values = Project.all(:conditions => Project.visible_by(User.current), :order => 'lft').map do |p|
229 project_values = Project.all(:conditions => Project.visible_by(User.current), :order => 'lft').map do |p|
230 pre = (p.level > 0 ? ('--' * p.level + ' ') : '')
230 pre = (p.level > 0 ? ('--' * p.level + ' ') : '')
231 ["#{pre}#{p.name}",p.id.to_s]
231 ["#{pre}#{p.name}",p.id.to_s]
232 end
232 end
233 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values}
233 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values}
234 end
234 end
235 @available_filters
235 @available_filters
236 end
236 end
237
237
238 def add_filter(field, operator, values)
238 def add_filter(field, operator, values)
239 # values must be an array
239 # values must be an array
240 return unless values and values.is_a? Array # and !values.first.empty?
240 return unless values and values.is_a? Array # and !values.first.empty?
241 # check if field is defined as an available filter
241 # check if field is defined as an available filter
242 if available_filters.has_key? field
242 if available_filters.has_key? field
243 filter_options = available_filters[field]
243 filter_options = available_filters[field]
244 # check if operator is allowed for that filter
244 # check if operator is allowed for that filter
245 #if @@operators_by_filter_type[filter_options[:type]].include? operator
245 #if @@operators_by_filter_type[filter_options[:type]].include? operator
246 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
246 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
247 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
247 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
248 #end
248 #end
249 filters[field] = {:operator => operator, :values => values }
249 filters[field] = {:operator => operator, :values => values }
250 end
250 end
251 end
251 end
252
252
253 def add_short_filter(field, expression)
253 def add_short_filter(field, expression)
254 return unless expression
254 return unless expression
255 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
255 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
256 add_filter field, (parms[0] || "="), [parms[1] || ""]
256 add_filter field, (parms[0] || "="), [parms[1] || ""]
257 end
257 end
258
258
259 # Add multiple filters using +add_filter+
259 # Add multiple filters using +add_filter+
260 def add_filters(fields, operators, values)
260 def add_filters(fields, operators, values)
261 if fields.is_a?(Array) && operators.is_a?(Hash) && values.is_a?(Hash)
261 fields.each do |field|
262 fields.each do |field|
262 add_filter(field, operators[field], values[field])
263 add_filter(field, operators[field], values[field])
263 end
264 end
264 end
265 end
266 end
265
267
266 def has_filter?(field)
268 def has_filter?(field)
267 filters and filters[field]
269 filters and filters[field]
268 end
270 end
269
271
270 def operator_for(field)
272 def operator_for(field)
271 has_filter?(field) ? filters[field][:operator] : nil
273 has_filter?(field) ? filters[field][:operator] : nil
272 end
274 end
273
275
274 def values_for(field)
276 def values_for(field)
275 has_filter?(field) ? filters[field][:values] : nil
277 has_filter?(field) ? filters[field][:values] : nil
276 end
278 end
277
279
278 def label_for(field)
280 def label_for(field)
279 label = available_filters[field][:name] if available_filters.has_key?(field)
281 label = available_filters[field][:name] if available_filters.has_key?(field)
280 label ||= field.gsub(/\_id$/, "")
282 label ||= field.gsub(/\_id$/, "")
281 end
283 end
282
284
283 def available_columns
285 def available_columns
284 return @available_columns if @available_columns
286 return @available_columns if @available_columns
285 @available_columns = Query.available_columns
287 @available_columns = Query.available_columns
286 @available_columns += (project ?
288 @available_columns += (project ?
287 project.all_issue_custom_fields :
289 project.all_issue_custom_fields :
288 IssueCustomField.find(:all)
290 IssueCustomField.find(:all)
289 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
291 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
290 end
292 end
291
293
292 def self.available_columns=(v)
294 def self.available_columns=(v)
293 self.available_columns = (v)
295 self.available_columns = (v)
294 end
296 end
295
297
296 def self.add_available_column(column)
298 def self.add_available_column(column)
297 self.available_columns << (column) if column.is_a?(QueryColumn)
299 self.available_columns << (column) if column.is_a?(QueryColumn)
298 end
300 end
299
301
300 # Returns an array of columns that can be used to group the results
302 # Returns an array of columns that can be used to group the results
301 def groupable_columns
303 def groupable_columns
302 available_columns.select {|c| c.groupable}
304 available_columns.select {|c| c.groupable}
303 end
305 end
304
306
305 # Returns a Hash of columns and the key for sorting
307 # Returns a Hash of columns and the key for sorting
306 def sortable_columns
308 def sortable_columns
307 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
309 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
308 h[column.name.to_s] = column.sortable
310 h[column.name.to_s] = column.sortable
309 h
311 h
310 })
312 })
311 end
313 end
312
314
313 def columns
315 def columns
314 if has_default_columns?
316 if has_default_columns?
315 available_columns.select do |c|
317 available_columns.select do |c|
316 # Adds the project column by default for cross-project lists
318 # Adds the project column by default for cross-project lists
317 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
319 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
318 end
320 end
319 else
321 else
320 # preserve the column_names order
322 # preserve the column_names order
321 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
323 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
322 end
324 end
323 end
325 end
324
326
325 def column_names=(names)
327 def column_names=(names)
326 if names
328 if names
327 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
329 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
328 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
330 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
329 # Set column_names to nil if default columns
331 # Set column_names to nil if default columns
330 if names.map(&:to_s) == Setting.issue_list_default_columns
332 if names.map(&:to_s) == Setting.issue_list_default_columns
331 names = nil
333 names = nil
332 end
334 end
333 end
335 end
334 write_attribute(:column_names, names)
336 write_attribute(:column_names, names)
335 end
337 end
336
338
337 def has_column?(column)
339 def has_column?(column)
338 column_names && column_names.include?(column.name)
340 column_names && column_names.include?(column.name)
339 end
341 end
340
342
341 def has_default_columns?
343 def has_default_columns?
342 column_names.nil? || column_names.empty?
344 column_names.nil? || column_names.empty?
343 end
345 end
344
346
345 def sort_criteria=(arg)
347 def sort_criteria=(arg)
346 c = []
348 c = []
347 if arg.is_a?(Hash)
349 if arg.is_a?(Hash)
348 arg = arg.keys.sort.collect {|k| arg[k]}
350 arg = arg.keys.sort.collect {|k| arg[k]}
349 end
351 end
350 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
352 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
351 write_attribute(:sort_criteria, c)
353 write_attribute(:sort_criteria, c)
352 end
354 end
353
355
354 def sort_criteria
356 def sort_criteria
355 read_attribute(:sort_criteria) || []
357 read_attribute(:sort_criteria) || []
356 end
358 end
357
359
358 def sort_criteria_key(arg)
360 def sort_criteria_key(arg)
359 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
361 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
360 end
362 end
361
363
362 def sort_criteria_order(arg)
364 def sort_criteria_order(arg)
363 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
365 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
364 end
366 end
365
367
366 # Returns the SQL sort order that should be prepended for grouping
368 # Returns the SQL sort order that should be prepended for grouping
367 def group_by_sort_order
369 def group_by_sort_order
368 if grouped? && (column = group_by_column)
370 if grouped? && (column = group_by_column)
369 column.sortable.is_a?(Array) ?
371 column.sortable.is_a?(Array) ?
370 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
372 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
371 "#{column.sortable} #{column.default_order}"
373 "#{column.sortable} #{column.default_order}"
372 end
374 end
373 end
375 end
374
376
375 # Returns true if the query is a grouped query
377 # Returns true if the query is a grouped query
376 def grouped?
378 def grouped?
377 !group_by.blank?
379 !group_by.blank?
378 end
380 end
379
381
380 def group_by_column
382 def group_by_column
381 groupable_columns.detect {|c| c.name.to_s == group_by}
383 groupable_columns.detect {|c| c.name.to_s == group_by}
382 end
384 end
383
385
384 def group_by_statement
386 def group_by_statement
385 group_by_column.groupable
387 group_by_column.groupable
386 end
388 end
387
389
388 def project_statement
390 def project_statement
389 project_clauses = []
391 project_clauses = []
390 if project && !@project.descendants.active.empty?
392 if project && !@project.descendants.active.empty?
391 ids = [project.id]
393 ids = [project.id]
392 if has_filter?("subproject_id")
394 if has_filter?("subproject_id")
393 case operator_for("subproject_id")
395 case operator_for("subproject_id")
394 when '='
396 when '='
395 # include the selected subprojects
397 # include the selected subprojects
396 ids += values_for("subproject_id").each(&:to_i)
398 ids += values_for("subproject_id").each(&:to_i)
397 when '!*'
399 when '!*'
398 # main project only
400 # main project only
399 else
401 else
400 # all subprojects
402 # all subprojects
401 ids += project.descendants.collect(&:id)
403 ids += project.descendants.collect(&:id)
402 end
404 end
403 elsif Setting.display_subprojects_issues?
405 elsif Setting.display_subprojects_issues?
404 ids += project.descendants.collect(&:id)
406 ids += project.descendants.collect(&:id)
405 end
407 end
406 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
408 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
407 elsif project
409 elsif project
408 project_clauses << "#{Project.table_name}.id = %d" % project.id
410 project_clauses << "#{Project.table_name}.id = %d" % project.id
409 end
411 end
410 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
412 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
411 project_clauses.join(' AND ')
413 project_clauses.join(' AND ')
412 end
414 end
413
415
414 def statement
416 def statement
415 # filters clauses
417 # filters clauses
416 filters_clauses = []
418 filters_clauses = []
417 filters.each_key do |field|
419 filters.each_key do |field|
418 next if field == "subproject_id"
420 next if field == "subproject_id"
419 v = values_for(field).clone
421 v = values_for(field).clone
420 next unless v and !v.empty?
422 next unless v and !v.empty?
421 operator = operator_for(field)
423 operator = operator_for(field)
422
424
423 # "me" value subsitution
425 # "me" value subsitution
424 if %w(assigned_to_id author_id watcher_id).include?(field)
426 if %w(assigned_to_id author_id watcher_id).include?(field)
425 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
427 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
426 end
428 end
427
429
428 sql = ''
430 sql = ''
429 if field =~ /^cf_(\d+)$/
431 if field =~ /^cf_(\d+)$/
430 # custom field
432 # custom field
431 db_table = CustomValue.table_name
433 db_table = CustomValue.table_name
432 db_field = 'value'
434 db_field = 'value'
433 is_custom_filter = true
435 is_custom_filter = true
434 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
436 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
435 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
437 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
436 elsif field == 'watcher_id'
438 elsif field == 'watcher_id'
437 db_table = Watcher.table_name
439 db_table = Watcher.table_name
438 db_field = 'user_id'
440 db_field = 'user_id'
439 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
441 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
440 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
442 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
441 elsif field == "member_of_group" # named field
443 elsif field == "member_of_group" # named field
442 if operator == '*' # Any group
444 if operator == '*' # Any group
443 groups = Group.all
445 groups = Group.all
444 operator = '=' # Override the operator since we want to find by assigned_to
446 operator = '=' # Override the operator since we want to find by assigned_to
445 elsif operator == "!*"
447 elsif operator == "!*"
446 groups = Group.all
448 groups = Group.all
447 operator = '!' # Override the operator since we want to find by assigned_to
449 operator = '!' # Override the operator since we want to find by assigned_to
448 else
450 else
449 groups = Group.find_all_by_id(v)
451 groups = Group.find_all_by_id(v)
450 end
452 end
451 groups ||= []
453 groups ||= []
452
454
453 members_of_groups = groups.inject([]) {|user_ids, group|
455 members_of_groups = groups.inject([]) {|user_ids, group|
454 if group && group.user_ids.present?
456 if group && group.user_ids.present?
455 user_ids << group.user_ids
457 user_ids << group.user_ids
456 end
458 end
457 user_ids.flatten.uniq.compact
459 user_ids.flatten.uniq.compact
458 }.sort.collect(&:to_s)
460 }.sort.collect(&:to_s)
459
461
460 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
462 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
461
463
462 elsif field == "assigned_to_role" # named field
464 elsif field == "assigned_to_role" # named field
463 if operator == "*" # Any Role
465 if operator == "*" # Any Role
464 roles = Role.givable
466 roles = Role.givable
465 operator = '=' # Override the operator since we want to find by assigned_to
467 operator = '=' # Override the operator since we want to find by assigned_to
466 elsif operator == "!*" # No role
468 elsif operator == "!*" # No role
467 roles = Role.givable
469 roles = Role.givable
468 operator = '!' # Override the operator since we want to find by assigned_to
470 operator = '!' # Override the operator since we want to find by assigned_to
469 else
471 else
470 roles = Role.givable.find_all_by_id(v)
472 roles = Role.givable.find_all_by_id(v)
471 end
473 end
472 roles ||= []
474 roles ||= []
473
475
474 members_of_roles = roles.inject([]) {|user_ids, role|
476 members_of_roles = roles.inject([]) {|user_ids, role|
475 if role && role.members
477 if role && role.members
476 user_ids << role.members.collect(&:user_id)
478 user_ids << role.members.collect(&:user_id)
477 end
479 end
478 user_ids.flatten.uniq.compact
480 user_ids.flatten.uniq.compact
479 }.sort.collect(&:to_s)
481 }.sort.collect(&:to_s)
480
482
481 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
483 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
482 else
484 else
483 # regular field
485 # regular field
484 db_table = Issue.table_name
486 db_table = Issue.table_name
485 db_field = field
487 db_field = field
486 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
488 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
487 end
489 end
488 filters_clauses << sql
490 filters_clauses << sql
489
491
490 end if filters and valid?
492 end if filters and valid?
491
493
492 (filters_clauses << project_statement).join(' AND ')
494 (filters_clauses << project_statement).join(' AND ')
493 end
495 end
494
496
495 # Returns the issue count
497 # Returns the issue count
496 def issue_count
498 def issue_count
497 Issue.count(:include => [:status, :project], :conditions => statement)
499 Issue.count(:include => [:status, :project], :conditions => statement)
498 rescue ::ActiveRecord::StatementInvalid => e
500 rescue ::ActiveRecord::StatementInvalid => e
499 raise StatementInvalid.new(e.message)
501 raise StatementInvalid.new(e.message)
500 end
502 end
501
503
502 # Returns the issue count by group or nil if query is not grouped
504 # Returns the issue count by group or nil if query is not grouped
503 def issue_count_by_group
505 def issue_count_by_group
504 r = nil
506 r = nil
505 if grouped?
507 if grouped?
506 begin
508 begin
507 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
509 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
508 r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
510 r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
509 rescue ActiveRecord::RecordNotFound
511 rescue ActiveRecord::RecordNotFound
510 r = {nil => issue_count}
512 r = {nil => issue_count}
511 end
513 end
512 c = group_by_column
514 c = group_by_column
513 if c.is_a?(QueryCustomFieldColumn)
515 if c.is_a?(QueryCustomFieldColumn)
514 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
516 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
515 end
517 end
516 end
518 end
517 r
519 r
518 rescue ::ActiveRecord::StatementInvalid => e
520 rescue ::ActiveRecord::StatementInvalid => e
519 raise StatementInvalid.new(e.message)
521 raise StatementInvalid.new(e.message)
520 end
522 end
521
523
522 # Returns the issues
524 # Returns the issues
523 # Valid options are :order, :offset, :limit, :include, :conditions
525 # Valid options are :order, :offset, :limit, :include, :conditions
524 def issues(options={})
526 def issues(options={})
525 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
527 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
526 order_option = nil if order_option.blank?
528 order_option = nil if order_option.blank?
527
529
528 Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
530 Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
529 :conditions => Query.merge_conditions(statement, options[:conditions]),
531 :conditions => Query.merge_conditions(statement, options[:conditions]),
530 :order => order_option,
532 :order => order_option,
531 :limit => options[:limit],
533 :limit => options[:limit],
532 :offset => options[:offset]
534 :offset => options[:offset]
533 rescue ::ActiveRecord::StatementInvalid => e
535 rescue ::ActiveRecord::StatementInvalid => e
534 raise StatementInvalid.new(e.message)
536 raise StatementInvalid.new(e.message)
535 end
537 end
536
538
537 # Returns the journals
539 # Returns the journals
538 # Valid options are :order, :offset, :limit
540 # Valid options are :order, :offset, :limit
539 def journals(options={})
541 def journals(options={})
540 Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
542 Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
541 :conditions => statement,
543 :conditions => statement,
542 :order => options[:order],
544 :order => options[:order],
543 :limit => options[:limit],
545 :limit => options[:limit],
544 :offset => options[:offset]
546 :offset => options[:offset]
545 rescue ::ActiveRecord::StatementInvalid => e
547 rescue ::ActiveRecord::StatementInvalid => e
546 raise StatementInvalid.new(e.message)
548 raise StatementInvalid.new(e.message)
547 end
549 end
548
550
549 # Returns the versions
551 # Returns the versions
550 # Valid options are :conditions
552 # Valid options are :conditions
551 def versions(options={})
553 def versions(options={})
552 Version.find :all, :include => :project,
554 Version.find :all, :include => :project,
553 :conditions => Query.merge_conditions(project_statement, options[:conditions])
555 :conditions => Query.merge_conditions(project_statement, options[:conditions])
554 rescue ::ActiveRecord::StatementInvalid => e
556 rescue ::ActiveRecord::StatementInvalid => e
555 raise StatementInvalid.new(e.message)
557 raise StatementInvalid.new(e.message)
556 end
558 end
557
559
558 private
560 private
559
561
560 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
562 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
561 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
563 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
562 sql = ''
564 sql = ''
563 case operator
565 case operator
564 when "="
566 when "="
565 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
567 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
566 when "!"
568 when "!"
567 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
569 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
568 when "!*"
570 when "!*"
569 sql = "#{db_table}.#{db_field} IS NULL"
571 sql = "#{db_table}.#{db_field} IS NULL"
570 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
572 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
571 when "*"
573 when "*"
572 sql = "#{db_table}.#{db_field} IS NOT NULL"
574 sql = "#{db_table}.#{db_field} IS NOT NULL"
573 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
575 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
574 when ">="
576 when ">="
575 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
577 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
576 when "<="
578 when "<="
577 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
579 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
578 when "o"
580 when "o"
579 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
581 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
580 when "c"
582 when "c"
581 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
583 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
582 when ">t-"
584 when ">t-"
583 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
585 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
584 when "<t-"
586 when "<t-"
585 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
587 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
586 when "t-"
588 when "t-"
587 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
589 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
588 when ">t+"
590 when ">t+"
589 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
591 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
590 when "<t+"
592 when "<t+"
591 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
593 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
592 when "t+"
594 when "t+"
593 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
595 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
594 when "t"
596 when "t"
595 sql = date_range_clause(db_table, db_field, 0, 0)
597 sql = date_range_clause(db_table, db_field, 0, 0)
596 when "w"
598 when "w"
597 from = l(:general_first_day_of_week) == '7' ?
599 from = l(:general_first_day_of_week) == '7' ?
598 # week starts on sunday
600 # week starts on sunday
599 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
601 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
600 # week starts on monday (Rails default)
602 # week starts on monday (Rails default)
601 Time.now.at_beginning_of_week
603 Time.now.at_beginning_of_week
602 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
604 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
603 when "~"
605 when "~"
604 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
606 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
605 when "!~"
607 when "!~"
606 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
608 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
607 end
609 end
608
610
609 return sql
611 return sql
610 end
612 end
611
613
612 def add_custom_fields_filters(custom_fields)
614 def add_custom_fields_filters(custom_fields)
613 @available_filters ||= {}
615 @available_filters ||= {}
614
616
615 custom_fields.select(&:is_filter?).each do |field|
617 custom_fields.select(&:is_filter?).each do |field|
616 case field.field_format
618 case field.field_format
617 when "text"
619 when "text"
618 options = { :type => :text, :order => 20 }
620 options = { :type => :text, :order => 20 }
619 when "list"
621 when "list"
620 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
622 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
621 when "date"
623 when "date"
622 options = { :type => :date, :order => 20 }
624 options = { :type => :date, :order => 20 }
623 when "bool"
625 when "bool"
624 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
626 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
625 else
627 else
626 options = { :type => :string, :order => 20 }
628 options = { :type => :string, :order => 20 }
627 end
629 end
628 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
630 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
629 end
631 end
630 end
632 end
631
633
632 # Returns a SQL clause for a date or datetime field.
634 # Returns a SQL clause for a date or datetime field.
633 def date_range_clause(table, field, from, to)
635 def date_range_clause(table, field, from, to)
634 s = []
636 s = []
635 if from
637 if from
636 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
638 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
637 end
639 end
638 if to
640 if to
639 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
641 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
640 end
642 end
641 s.join(' AND ')
643 s.join(' AND ')
642 end
644 end
643 end
645 end
@@ -1,116 +1,117
1 <script type="text/javascript">
1 <script type="text/javascript">
2 //<![CDATA[
2 //<![CDATA[
3 function add_filter() {
3 function add_filter() {
4 select = $('add_filter_select');
4 select = $('add_filter_select');
5 field = select.value
5 field = select.value
6 Element.show('tr_' + field);
6 Element.show('tr_' + field);
7 check_box = $('cb_' + field);
7 check_box = $('cb_' + field);
8 check_box.checked = true;
8 check_box.checked = true;
9 toggle_filter(field);
9 toggle_filter(field);
10 select.selectedIndex = 0;
10 select.selectedIndex = 0;
11
11
12 for (i=0; i<select.options.length; i++) {
12 for (i=0; i<select.options.length; i++) {
13 if (select.options[i].value == field) {
13 if (select.options[i].value == field) {
14 select.options[i].disabled = true;
14 select.options[i].disabled = true;
15 }
15 }
16 }
16 }
17 }
17 }
18
18
19 function toggle_filter(field) {
19 function toggle_filter(field) {
20 check_box = $('cb_' + field);
20 check_box = $('cb_' + field);
21
21
22 if (check_box.checked) {
22 if (check_box.checked) {
23 Element.show("operators_" + field);
23 Element.show("operators_" + field);
24 toggle_operator(field);
24 toggle_operator(field);
25 } else {
25 } else {
26 Element.hide("operators_" + field);
26 Element.hide("operators_" + field);
27 Element.hide("div_values_" + field);
27 Element.hide("div_values_" + field);
28 }
28 }
29 }
29 }
30
30
31 function toggle_operator(field) {
31 function toggle_operator(field) {
32 operator = $("operators_" + field);
32 operator = $("operators_" + field);
33 switch (operator.value) {
33 switch (operator.value) {
34 case "!*":
34 case "!*":
35 case "*":
35 case "*":
36 case "t":
36 case "t":
37 case "w":
37 case "w":
38 case "o":
38 case "o":
39 case "c":
39 case "c":
40 Element.hide("div_values_" + field);
40 Element.hide("div_values_" + field);
41 break;
41 break;
42 default:
42 default:
43 Element.show("div_values_" + field);
43 Element.show("div_values_" + field);
44 break;
44 break;
45 }
45 }
46 }
46 }
47
47
48 function toggle_multi_select(field) {
48 function toggle_multi_select(field) {
49 select = $('values_' + field);
49 select = $('values_' + field);
50 if (select.multiple == true) {
50 if (select.multiple == true) {
51 select.multiple = false;
51 select.multiple = false;
52 } else {
52 } else {
53 select.multiple = true;
53 select.multiple = true;
54 }
54 }
55 }
55 }
56
56
57 function apply_filters_observer() {
57 function apply_filters_observer() {
58 $$("#query_form input[type=text]").invoke("observe", "keypress", function(e){
58 $$("#query_form input[type=text]").invoke("observe", "keypress", function(e){
59 if(e.keyCode == Event.KEY_RETURN) {
59 if(e.keyCode == Event.KEY_RETURN) {
60 <%= remote_function(:url => { :set_filter => 1},
60 <%= remote_function(:url => { :set_filter => 1},
61 :update => "content",
61 :update => "content",
62 :with => "Form.serialize('query_form')",
62 :with => "Form.serialize('query_form')",
63 :complete => "e.stop(); apply_filters_observer()") %>
63 :complete => "e.stop(); apply_filters_observer()") %>
64 }
64 }
65 });
65 });
66 }
66 }
67 Event.observe(document,"dom:loaded", apply_filters_observer);
67 Event.observe(document,"dom:loaded", apply_filters_observer);
68 //]]>
68 //]]>
69 </script>
69 </script>
70
70
71 <table width="100%">
71 <table width="100%">
72 <tr>
72 <tr>
73 <td>
73 <td>
74 <table>
74 <table>
75 <% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %>
75 <% query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.each do |filter| %>
76 <% field = filter[0]
76 <% field = filter[0]
77 options = filter[1] %>
77 options = filter[1] %>
78 <tr <%= 'style="display:none;"' unless query.has_filter?(field) %> id="tr_<%= field %>" class="filter">
78 <tr <%= 'style="display:none;"' unless query.has_filter?(field) %> id="tr_<%= field %>" class="filter">
79 <td style="width:200px;">
79 <td style="width:200px;">
80 <%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %>
80 <%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %>
81 <label for="cb_<%= field %>"><%= filter[1][:name] || l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label>
81 <label for="cb_<%= field %>"><%= filter[1][:name] || l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label>
82 </td>
82 </td>
83 <td style="width:150px;">
83 <td style="width:150px;">
84 <%= select_tag "operators[#{field}]", options_for_select(operators_for_select(options[:type]), query.operator_for(field)), :id => "operators_#{field}", :onchange => "toggle_operator('#{field}');", :class => "select-small", :style => "vertical-align: top;" %>
84 <%= select_tag "operators[#{field}]", options_for_select(operators_for_select(options[:type]), query.operator_for(field)), :id => "operators_#{field}", :onchange => "toggle_operator('#{field}');", :class => "select-small", :style => "vertical-align: top;" %>
85 </td>
85 </td>
86 <td>
86 <td>
87 <div id="div_values_<%= field %>" style="display:none;">
87 <div id="div_values_<%= field %>" style="display:none;">
88 <% case options[:type]
88 <% case options[:type]
89 when :list, :list_optional, :list_status, :list_subprojects %>
89 when :list, :list_optional, :list_status, :list_subprojects %>
90 <select <%= "multiple=true" if query.values_for(field) and query.values_for(field).length > 1 %> name="values[<%= field %>][]" id="values_<%= field %>" class="select-small" style="vertical-align: top;">
90 <select <%= "multiple=true" if query.values_for(field) and query.values_for(field).length > 1 %> name="values[<%= field %>][]" id="values_<%= field %>" class="select-small" style="vertical-align: top;">
91 <%= options_for_select options[:values], query.values_for(field) %>
91 <%= options_for_select options[:values], query.values_for(field) %>
92 </select>
92 </select>
93 <%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('#{field}');", :style => "vertical-align: bottom;" %>
93 <%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('#{field}');", :style => "vertical-align: bottom;" %>
94 <% when :date, :date_past %>
94 <% when :date, :date_past %>
95 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %>
95 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %>
96 <% when :string, :text %>
96 <% when :string, :text %>
97 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 30, :class => "select-small" %>
97 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 30, :class => "select-small" %>
98 <% when :integer %>
98 <% when :integer %>
99 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %>
99 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %>
100 <% end %>
100 <% end %>
101 </div>
101 </div>
102 <script type="text/javascript">toggle_filter('<%= field %>');</script>
102 <script type="text/javascript">toggle_filter('<%= field %>');</script>
103 </td>
103 </td>
104 </tr>
104 </tr>
105 <% end %>
105 <% end %>
106 </table>
106 </table>
107 </td>
107 </td>
108 <td class="add-filter">
108 <td class="add-filter">
109 <%= label_tag('add_filter_select', l(:label_filter_add)) %>:
109 <%= label_tag('add_filter_select', l(:label_filter_add)) %>:
110 <%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [ field[1][:name] || l(("field_"+field[0].to_s.gsub(/_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact),
110 <%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [ field[1][:name] || l(("field_"+field[0].to_s.gsub(/_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact),
111 :onchange => "add_filter();",
111 :onchange => "add_filter();",
112 :class => "select-small",
112 :class => "select-small",
113 :name => nil %>
113 :name => nil %>
114 </td>
114 </td>
115 </tr>
115 </tr>
116 </table>
116 </table>
117 <%= hidden_field_tag 'fields[]', '' %>
@@ -1,1141 +1,1172
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class IssuesController; def rescue_action(e) raise e end; end
22 class IssuesController; def rescue_action(e) raise e end; end
23
23
24 class IssuesControllerTest < ActionController::TestCase
24 class IssuesControllerTest < ActionController::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :member_roles,
29 :member_roles,
30 :issues,
30 :issues,
31 :issue_statuses,
31 :issue_statuses,
32 :versions,
32 :versions,
33 :trackers,
33 :trackers,
34 :projects_trackers,
34 :projects_trackers,
35 :issue_categories,
35 :issue_categories,
36 :enabled_modules,
36 :enabled_modules,
37 :enumerations,
37 :enumerations,
38 :attachments,
38 :attachments,
39 :workflows,
39 :workflows,
40 :custom_fields,
40 :custom_fields,
41 :custom_values,
41 :custom_values,
42 :custom_fields_projects,
42 :custom_fields_projects,
43 :custom_fields_trackers,
43 :custom_fields_trackers,
44 :time_entries,
44 :time_entries,
45 :journals,
45 :journals,
46 :journal_details,
46 :journal_details,
47 :queries
47 :queries
48
48
49 def setup
49 def setup
50 @controller = IssuesController.new
50 @controller = IssuesController.new
51 @request = ActionController::TestRequest.new
51 @request = ActionController::TestRequest.new
52 @response = ActionController::TestResponse.new
52 @response = ActionController::TestResponse.new
53 User.current = nil
53 User.current = nil
54 end
54 end
55
55
56 def test_index
56 def test_index
57 Setting.default_language = 'en'
57 Setting.default_language = 'en'
58
58
59 get :index
59 get :index
60 assert_response :success
60 assert_response :success
61 assert_template 'index.rhtml'
61 assert_template 'index.rhtml'
62 assert_not_nil assigns(:issues)
62 assert_not_nil assigns(:issues)
63 assert_nil assigns(:project)
63 assert_nil assigns(:project)
64 assert_tag :tag => 'a', :content => /Can't print recipes/
64 assert_tag :tag => 'a', :content => /Can't print recipes/
65 assert_tag :tag => 'a', :content => /Subproject issue/
65 assert_tag :tag => 'a', :content => /Subproject issue/
66 # private projects hidden
66 # private projects hidden
67 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
67 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
68 assert_no_tag :tag => 'a', :content => /Issue on project 2/
68 assert_no_tag :tag => 'a', :content => /Issue on project 2/
69 # project column
69 # project column
70 assert_tag :tag => 'th', :content => /Project/
70 assert_tag :tag => 'th', :content => /Project/
71 end
71 end
72
72
73 def test_index_should_not_list_issues_when_module_disabled
73 def test_index_should_not_list_issues_when_module_disabled
74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 get :index
75 get :index
76 assert_response :success
76 assert_response :success
77 assert_template 'index.rhtml'
77 assert_template 'index.rhtml'
78 assert_not_nil assigns(:issues)
78 assert_not_nil assigns(:issues)
79 assert_nil assigns(:project)
79 assert_nil assigns(:project)
80 assert_no_tag :tag => 'a', :content => /Can't print recipes/
80 assert_no_tag :tag => 'a', :content => /Can't print recipes/
81 assert_tag :tag => 'a', :content => /Subproject issue/
81 assert_tag :tag => 'a', :content => /Subproject issue/
82 end
82 end
83
83
84 def test_index_should_not_list_issues_when_module_disabled
84 def test_index_should_not_list_issues_when_module_disabled
85 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
85 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
86 get :index
86 get :index
87 assert_response :success
87 assert_response :success
88 assert_template 'index.rhtml'
88 assert_template 'index.rhtml'
89 assert_not_nil assigns(:issues)
89 assert_not_nil assigns(:issues)
90 assert_nil assigns(:project)
90 assert_nil assigns(:project)
91 assert_no_tag :tag => 'a', :content => /Can't print recipes/
91 assert_no_tag :tag => 'a', :content => /Can't print recipes/
92 assert_tag :tag => 'a', :content => /Subproject issue/
92 assert_tag :tag => 'a', :content => /Subproject issue/
93 end
93 end
94
94
95 def test_index_with_project
95 def test_index_with_project
96 Setting.display_subprojects_issues = 0
96 Setting.display_subprojects_issues = 0
97 get :index, :project_id => 1
97 get :index, :project_id => 1
98 assert_response :success
98 assert_response :success
99 assert_template 'index.rhtml'
99 assert_template 'index.rhtml'
100 assert_not_nil assigns(:issues)
100 assert_not_nil assigns(:issues)
101 assert_tag :tag => 'a', :content => /Can't print recipes/
101 assert_tag :tag => 'a', :content => /Can't print recipes/
102 assert_no_tag :tag => 'a', :content => /Subproject issue/
102 assert_no_tag :tag => 'a', :content => /Subproject issue/
103 end
103 end
104
104
105 def test_index_with_project_and_subprojects
105 def test_index_with_project_and_subprojects
106 Setting.display_subprojects_issues = 1
106 Setting.display_subprojects_issues = 1
107 get :index, :project_id => 1
107 get :index, :project_id => 1
108 assert_response :success
108 assert_response :success
109 assert_template 'index.rhtml'
109 assert_template 'index.rhtml'
110 assert_not_nil assigns(:issues)
110 assert_not_nil assigns(:issues)
111 assert_tag :tag => 'a', :content => /Can't print recipes/
111 assert_tag :tag => 'a', :content => /Can't print recipes/
112 assert_tag :tag => 'a', :content => /Subproject issue/
112 assert_tag :tag => 'a', :content => /Subproject issue/
113 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
113 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
114 end
114 end
115
115
116 def test_index_with_project_and_subprojects_should_show_private_subprojects
116 def test_index_with_project_and_subprojects_should_show_private_subprojects
117 @request.session[:user_id] = 2
117 @request.session[:user_id] = 2
118 Setting.display_subprojects_issues = 1
118 Setting.display_subprojects_issues = 1
119 get :index, :project_id => 1
119 get :index, :project_id => 1
120 assert_response :success
120 assert_response :success
121 assert_template 'index.rhtml'
121 assert_template 'index.rhtml'
122 assert_not_nil assigns(:issues)
122 assert_not_nil assigns(:issues)
123 assert_tag :tag => 'a', :content => /Can't print recipes/
123 assert_tag :tag => 'a', :content => /Can't print recipes/
124 assert_tag :tag => 'a', :content => /Subproject issue/
124 assert_tag :tag => 'a', :content => /Subproject issue/
125 assert_tag :tag => 'a', :content => /Issue of a private subproject/
125 assert_tag :tag => 'a', :content => /Issue of a private subproject/
126 end
126 end
127
127
128 def test_index_with_project_and_filter
128 def test_index_with_project_and_default_filter
129 get :index, :project_id => 1, :set_filter => 1
129 get :index, :project_id => 1, :set_filter => 1
130 assert_response :success
130 assert_response :success
131 assert_template 'index.rhtml'
131 assert_template 'index.rhtml'
132 assert_not_nil assigns(:issues)
132 assert_not_nil assigns(:issues)
133
134 query = assigns(:query)
135 assert_not_nil query
136 # default filter
137 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
138 end
139
140 def test_index_with_project_and_filter
141 get :index, :project_id => 1, :set_filter => 1,
142 :fields => ['tracker_id'],
143 :operators => {'tracker_id' => '='},
144 :values => {'tracker_id' => ['1']}
145 assert_response :success
146 assert_template 'index.rhtml'
147 assert_not_nil assigns(:issues)
148
149 query = assigns(:query)
150 assert_not_nil query
151 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
152 end
153
154 def test_index_with_project_and_empty_filters
155 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
156 assert_response :success
157 assert_template 'index.rhtml'
158 assert_not_nil assigns(:issues)
159
160 query = assigns(:query)
161 assert_not_nil query
162 # no filter
163 assert_equal({}, query.filters)
133 end
164 end
134
165
135 def test_index_with_query
166 def test_index_with_query
136 get :index, :project_id => 1, :query_id => 5
167 get :index, :project_id => 1, :query_id => 5
137 assert_response :success
168 assert_response :success
138 assert_template 'index.rhtml'
169 assert_template 'index.rhtml'
139 assert_not_nil assigns(:issues)
170 assert_not_nil assigns(:issues)
140 assert_nil assigns(:issue_count_by_group)
171 assert_nil assigns(:issue_count_by_group)
141 end
172 end
142
173
143 def test_index_with_query_grouped_by_tracker
174 def test_index_with_query_grouped_by_tracker
144 get :index, :project_id => 1, :query_id => 6
175 get :index, :project_id => 1, :query_id => 6
145 assert_response :success
176 assert_response :success
146 assert_template 'index.rhtml'
177 assert_template 'index.rhtml'
147 assert_not_nil assigns(:issues)
178 assert_not_nil assigns(:issues)
148 assert_not_nil assigns(:issue_count_by_group)
179 assert_not_nil assigns(:issue_count_by_group)
149 end
180 end
150
181
151 def test_index_with_query_grouped_by_list_custom_field
182 def test_index_with_query_grouped_by_list_custom_field
152 get :index, :project_id => 1, :query_id => 9
183 get :index, :project_id => 1, :query_id => 9
153 assert_response :success
184 assert_response :success
154 assert_template 'index.rhtml'
185 assert_template 'index.rhtml'
155 assert_not_nil assigns(:issues)
186 assert_not_nil assigns(:issues)
156 assert_not_nil assigns(:issue_count_by_group)
187 assert_not_nil assigns(:issue_count_by_group)
157 end
188 end
158
189
159 def test_index_sort_by_field_not_included_in_columns
190 def test_index_sort_by_field_not_included_in_columns
160 Setting.issue_list_default_columns = %w(subject author)
191 Setting.issue_list_default_columns = %w(subject author)
161 get :index, :sort => 'tracker'
192 get :index, :sort => 'tracker'
162 end
193 end
163
194
164 def test_index_csv_with_project
195 def test_index_csv_with_project
165 Setting.default_language = 'en'
196 Setting.default_language = 'en'
166
197
167 get :index, :format => 'csv'
198 get :index, :format => 'csv'
168 assert_response :success
199 assert_response :success
169 assert_not_nil assigns(:issues)
200 assert_not_nil assigns(:issues)
170 assert_equal 'text/csv', @response.content_type
201 assert_equal 'text/csv', @response.content_type
171 assert @response.body.starts_with?("#,")
202 assert @response.body.starts_with?("#,")
172
203
173 get :index, :project_id => 1, :format => 'csv'
204 get :index, :project_id => 1, :format => 'csv'
174 assert_response :success
205 assert_response :success
175 assert_not_nil assigns(:issues)
206 assert_not_nil assigns(:issues)
176 assert_equal 'text/csv', @response.content_type
207 assert_equal 'text/csv', @response.content_type
177 end
208 end
178
209
179 def test_index_pdf
210 def test_index_pdf
180 get :index, :format => 'pdf'
211 get :index, :format => 'pdf'
181 assert_response :success
212 assert_response :success
182 assert_not_nil assigns(:issues)
213 assert_not_nil assigns(:issues)
183 assert_equal 'application/pdf', @response.content_type
214 assert_equal 'application/pdf', @response.content_type
184
215
185 get :index, :project_id => 1, :format => 'pdf'
216 get :index, :project_id => 1, :format => 'pdf'
186 assert_response :success
217 assert_response :success
187 assert_not_nil assigns(:issues)
218 assert_not_nil assigns(:issues)
188 assert_equal 'application/pdf', @response.content_type
219 assert_equal 'application/pdf', @response.content_type
189
220
190 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
221 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
191 assert_response :success
222 assert_response :success
192 assert_not_nil assigns(:issues)
223 assert_not_nil assigns(:issues)
193 assert_equal 'application/pdf', @response.content_type
224 assert_equal 'application/pdf', @response.content_type
194 end
225 end
195
226
196 def test_index_pdf_with_query_grouped_by_list_custom_field
227 def test_index_pdf_with_query_grouped_by_list_custom_field
197 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
228 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
198 assert_response :success
229 assert_response :success
199 assert_not_nil assigns(:issues)
230 assert_not_nil assigns(:issues)
200 assert_not_nil assigns(:issue_count_by_group)
231 assert_not_nil assigns(:issue_count_by_group)
201 assert_equal 'application/pdf', @response.content_type
232 assert_equal 'application/pdf', @response.content_type
202 end
233 end
203
234
204 def test_index_sort
235 def test_index_sort
205 get :index, :sort => 'tracker,id:desc'
236 get :index, :sort => 'tracker,id:desc'
206 assert_response :success
237 assert_response :success
207
238
208 sort_params = @request.session['issues_index_sort']
239 sort_params = @request.session['issues_index_sort']
209 assert sort_params.is_a?(String)
240 assert sort_params.is_a?(String)
210 assert_equal 'tracker,id:desc', sort_params
241 assert_equal 'tracker,id:desc', sort_params
211
242
212 issues = assigns(:issues)
243 issues = assigns(:issues)
213 assert_not_nil issues
244 assert_not_nil issues
214 assert !issues.empty?
245 assert !issues.empty?
215 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
246 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
216 end
247 end
217
248
218 def test_index_with_columns
249 def test_index_with_columns
219 columns = ['tracker', 'subject', 'assigned_to']
250 columns = ['tracker', 'subject', 'assigned_to']
220 get :index, :set_filter => 1, :query => { 'column_names' => columns}
251 get :index, :set_filter => 1, :query => { 'column_names' => columns}
221 assert_response :success
252 assert_response :success
222
253
223 # query should use specified columns
254 # query should use specified columns
224 query = assigns(:query)
255 query = assigns(:query)
225 assert_kind_of Query, query
256 assert_kind_of Query, query
226 assert_equal columns, query.column_names.map(&:to_s)
257 assert_equal columns, query.column_names.map(&:to_s)
227
258
228 # columns should be stored in session
259 # columns should be stored in session
229 assert_kind_of Hash, session[:query]
260 assert_kind_of Hash, session[:query]
230 assert_kind_of Array, session[:query][:column_names]
261 assert_kind_of Array, session[:query][:column_names]
231 assert_equal columns, session[:query][:column_names].map(&:to_s)
262 assert_equal columns, session[:query][:column_names].map(&:to_s)
232 end
263 end
233
264
234 def test_show_by_anonymous
265 def test_show_by_anonymous
235 get :show, :id => 1
266 get :show, :id => 1
236 assert_response :success
267 assert_response :success
237 assert_template 'show.rhtml'
268 assert_template 'show.rhtml'
238 assert_not_nil assigns(:issue)
269 assert_not_nil assigns(:issue)
239 assert_equal Issue.find(1), assigns(:issue)
270 assert_equal Issue.find(1), assigns(:issue)
240
271
241 # anonymous role is allowed to add a note
272 # anonymous role is allowed to add a note
242 assert_tag :tag => 'form',
273 assert_tag :tag => 'form',
243 :descendant => { :tag => 'fieldset',
274 :descendant => { :tag => 'fieldset',
244 :child => { :tag => 'legend',
275 :child => { :tag => 'legend',
245 :content => /Notes/ } }
276 :content => /Notes/ } }
246 end
277 end
247
278
248 def test_show_by_manager
279 def test_show_by_manager
249 @request.session[:user_id] = 2
280 @request.session[:user_id] = 2
250 get :show, :id => 1
281 get :show, :id => 1
251 assert_response :success
282 assert_response :success
252
283
253 assert_tag :tag => 'form',
284 assert_tag :tag => 'form',
254 :descendant => { :tag => 'fieldset',
285 :descendant => { :tag => 'fieldset',
255 :child => { :tag => 'legend',
286 :child => { :tag => 'legend',
256 :content => /Change properties/ } },
287 :content => /Change properties/ } },
257 :descendant => { :tag => 'fieldset',
288 :descendant => { :tag => 'fieldset',
258 :child => { :tag => 'legend',
289 :child => { :tag => 'legend',
259 :content => /Log time/ } },
290 :content => /Log time/ } },
260 :descendant => { :tag => 'fieldset',
291 :descendant => { :tag => 'fieldset',
261 :child => { :tag => 'legend',
292 :child => { :tag => 'legend',
262 :content => /Notes/ } }
293 :content => /Notes/ } }
263 end
294 end
264
295
265 def test_show_should_deny_anonymous_access_without_permission
296 def test_show_should_deny_anonymous_access_without_permission
266 Role.anonymous.remove_permission!(:view_issues)
297 Role.anonymous.remove_permission!(:view_issues)
267 get :show, :id => 1
298 get :show, :id => 1
268 assert_response :redirect
299 assert_response :redirect
269 end
300 end
270
301
271 def test_show_should_deny_non_member_access_without_permission
302 def test_show_should_deny_non_member_access_without_permission
272 Role.non_member.remove_permission!(:view_issues)
303 Role.non_member.remove_permission!(:view_issues)
273 @request.session[:user_id] = 9
304 @request.session[:user_id] = 9
274 get :show, :id => 1
305 get :show, :id => 1
275 assert_response 403
306 assert_response 403
276 end
307 end
277
308
278 def test_show_should_deny_member_access_without_permission
309 def test_show_should_deny_member_access_without_permission
279 Role.find(1).remove_permission!(:view_issues)
310 Role.find(1).remove_permission!(:view_issues)
280 @request.session[:user_id] = 2
311 @request.session[:user_id] = 2
281 get :show, :id => 1
312 get :show, :id => 1
282 assert_response 403
313 assert_response 403
283 end
314 end
284
315
285 def test_show_should_not_disclose_relations_to_invisible_issues
316 def test_show_should_not_disclose_relations_to_invisible_issues
286 Setting.cross_project_issue_relations = '1'
317 Setting.cross_project_issue_relations = '1'
287 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
318 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
288 # Relation to a private project issue
319 # Relation to a private project issue
289 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
320 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
290
321
291 get :show, :id => 1
322 get :show, :id => 1
292 assert_response :success
323 assert_response :success
293
324
294 assert_tag :div, :attributes => { :id => 'relations' },
325 assert_tag :div, :attributes => { :id => 'relations' },
295 :descendant => { :tag => 'a', :content => /#2$/ }
326 :descendant => { :tag => 'a', :content => /#2$/ }
296 assert_no_tag :div, :attributes => { :id => 'relations' },
327 assert_no_tag :div, :attributes => { :id => 'relations' },
297 :descendant => { :tag => 'a', :content => /#4$/ }
328 :descendant => { :tag => 'a', :content => /#4$/ }
298 end
329 end
299
330
300 def test_show_atom
331 def test_show_atom
301 get :show, :id => 2, :format => 'atom'
332 get :show, :id => 2, :format => 'atom'
302 assert_response :success
333 assert_response :success
303 assert_template 'journals/index.rxml'
334 assert_template 'journals/index.rxml'
304 # Inline image
335 # Inline image
305 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
336 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
306 end
337 end
307
338
308 def test_show_export_to_pdf
339 def test_show_export_to_pdf
309 get :show, :id => 3, :format => 'pdf'
340 get :show, :id => 3, :format => 'pdf'
310 assert_response :success
341 assert_response :success
311 assert_equal 'application/pdf', @response.content_type
342 assert_equal 'application/pdf', @response.content_type
312 assert @response.body.starts_with?('%PDF')
343 assert @response.body.starts_with?('%PDF')
313 assert_not_nil assigns(:issue)
344 assert_not_nil assigns(:issue)
314 end
345 end
315
346
316 def test_get_new
347 def test_get_new
317 @request.session[:user_id] = 2
348 @request.session[:user_id] = 2
318 get :new, :project_id => 1, :tracker_id => 1
349 get :new, :project_id => 1, :tracker_id => 1
319 assert_response :success
350 assert_response :success
320 assert_template 'new'
351 assert_template 'new'
321
352
322 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
353 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
323 :value => 'Default string' }
354 :value => 'Default string' }
324 end
355 end
325
356
326 def test_get_new_without_tracker_id
357 def test_get_new_without_tracker_id
327 @request.session[:user_id] = 2
358 @request.session[:user_id] = 2
328 get :new, :project_id => 1
359 get :new, :project_id => 1
329 assert_response :success
360 assert_response :success
330 assert_template 'new'
361 assert_template 'new'
331
362
332 issue = assigns(:issue)
363 issue = assigns(:issue)
333 assert_not_nil issue
364 assert_not_nil issue
334 assert_equal Project.find(1).trackers.first, issue.tracker
365 assert_equal Project.find(1).trackers.first, issue.tracker
335 end
366 end
336
367
337 def test_get_new_with_no_default_status_should_display_an_error
368 def test_get_new_with_no_default_status_should_display_an_error
338 @request.session[:user_id] = 2
369 @request.session[:user_id] = 2
339 IssueStatus.delete_all
370 IssueStatus.delete_all
340
371
341 get :new, :project_id => 1
372 get :new, :project_id => 1
342 assert_response 500
373 assert_response 500
343 assert_error_tag :content => /No default issue/
374 assert_error_tag :content => /No default issue/
344 end
375 end
345
376
346 def test_get_new_with_no_tracker_should_display_an_error
377 def test_get_new_with_no_tracker_should_display_an_error
347 @request.session[:user_id] = 2
378 @request.session[:user_id] = 2
348 Tracker.delete_all
379 Tracker.delete_all
349
380
350 get :new, :project_id => 1
381 get :new, :project_id => 1
351 assert_response 500
382 assert_response 500
352 assert_error_tag :content => /No tracker/
383 assert_error_tag :content => /No tracker/
353 end
384 end
354
385
355 def test_update_new_form
386 def test_update_new_form
356 @request.session[:user_id] = 2
387 @request.session[:user_id] = 2
357 xhr :post, :new, :project_id => 1,
388 xhr :post, :new, :project_id => 1,
358 :issue => {:tracker_id => 2,
389 :issue => {:tracker_id => 2,
359 :subject => 'This is the test_new issue',
390 :subject => 'This is the test_new issue',
360 :description => 'This is the description',
391 :description => 'This is the description',
361 :priority_id => 5}
392 :priority_id => 5}
362 assert_response :success
393 assert_response :success
363 assert_template 'attributes'
394 assert_template 'attributes'
364
395
365 issue = assigns(:issue)
396 issue = assigns(:issue)
366 assert_kind_of Issue, issue
397 assert_kind_of Issue, issue
367 assert_equal 1, issue.project_id
398 assert_equal 1, issue.project_id
368 assert_equal 2, issue.tracker_id
399 assert_equal 2, issue.tracker_id
369 assert_equal 'This is the test_new issue', issue.subject
400 assert_equal 'This is the test_new issue', issue.subject
370 end
401 end
371
402
372 def test_post_create
403 def test_post_create
373 @request.session[:user_id] = 2
404 @request.session[:user_id] = 2
374 assert_difference 'Issue.count' do
405 assert_difference 'Issue.count' do
375 post :create, :project_id => 1,
406 post :create, :project_id => 1,
376 :issue => {:tracker_id => 3,
407 :issue => {:tracker_id => 3,
377 :status_id => 2,
408 :status_id => 2,
378 :subject => 'This is the test_new issue',
409 :subject => 'This is the test_new issue',
379 :description => 'This is the description',
410 :description => 'This is the description',
380 :priority_id => 5,
411 :priority_id => 5,
381 :start_date => '2010-11-07',
412 :start_date => '2010-11-07',
382 :estimated_hours => '',
413 :estimated_hours => '',
383 :custom_field_values => {'2' => 'Value for field 2'}}
414 :custom_field_values => {'2' => 'Value for field 2'}}
384 end
415 end
385 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
416 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
386
417
387 issue = Issue.find_by_subject('This is the test_new issue')
418 issue = Issue.find_by_subject('This is the test_new issue')
388 assert_not_nil issue
419 assert_not_nil issue
389 assert_equal 2, issue.author_id
420 assert_equal 2, issue.author_id
390 assert_equal 3, issue.tracker_id
421 assert_equal 3, issue.tracker_id
391 assert_equal 2, issue.status_id
422 assert_equal 2, issue.status_id
392 assert_equal Date.parse('2010-11-07'), issue.start_date
423 assert_equal Date.parse('2010-11-07'), issue.start_date
393 assert_nil issue.estimated_hours
424 assert_nil issue.estimated_hours
394 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
425 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
395 assert_not_nil v
426 assert_not_nil v
396 assert_equal 'Value for field 2', v.value
427 assert_equal 'Value for field 2', v.value
397 end
428 end
398
429
399 def test_post_create_without_start_date
430 def test_post_create_without_start_date
400 @request.session[:user_id] = 2
431 @request.session[:user_id] = 2
401 assert_difference 'Issue.count' do
432 assert_difference 'Issue.count' do
402 post :create, :project_id => 1,
433 post :create, :project_id => 1,
403 :issue => {:tracker_id => 3,
434 :issue => {:tracker_id => 3,
404 :status_id => 2,
435 :status_id => 2,
405 :subject => 'This is the test_new issue',
436 :subject => 'This is the test_new issue',
406 :description => 'This is the description',
437 :description => 'This is the description',
407 :priority_id => 5,
438 :priority_id => 5,
408 :start_date => '',
439 :start_date => '',
409 :estimated_hours => '',
440 :estimated_hours => '',
410 :custom_field_values => {'2' => 'Value for field 2'}}
441 :custom_field_values => {'2' => 'Value for field 2'}}
411 end
442 end
412 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
443 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
413
444
414 issue = Issue.find_by_subject('This is the test_new issue')
445 issue = Issue.find_by_subject('This is the test_new issue')
415 assert_not_nil issue
446 assert_not_nil issue
416 assert_nil issue.start_date
447 assert_nil issue.start_date
417 end
448 end
418
449
419 def test_post_create_and_continue
450 def test_post_create_and_continue
420 @request.session[:user_id] = 2
451 @request.session[:user_id] = 2
421 post :create, :project_id => 1,
452 post :create, :project_id => 1,
422 :issue => {:tracker_id => 3,
453 :issue => {:tracker_id => 3,
423 :subject => 'This is first issue',
454 :subject => 'This is first issue',
424 :priority_id => 5},
455 :priority_id => 5},
425 :continue => ''
456 :continue => ''
426 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook',
457 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook',
427 :issue => {:tracker_id => 3}
458 :issue => {:tracker_id => 3}
428 end
459 end
429
460
430 def test_post_create_without_custom_fields_param
461 def test_post_create_without_custom_fields_param
431 @request.session[:user_id] = 2
462 @request.session[:user_id] = 2
432 assert_difference 'Issue.count' do
463 assert_difference 'Issue.count' do
433 post :create, :project_id => 1,
464 post :create, :project_id => 1,
434 :issue => {:tracker_id => 1,
465 :issue => {:tracker_id => 1,
435 :subject => 'This is the test_new issue',
466 :subject => 'This is the test_new issue',
436 :description => 'This is the description',
467 :description => 'This is the description',
437 :priority_id => 5}
468 :priority_id => 5}
438 end
469 end
439 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
470 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
440 end
471 end
441
472
442 def test_post_create_with_required_custom_field_and_without_custom_fields_param
473 def test_post_create_with_required_custom_field_and_without_custom_fields_param
443 field = IssueCustomField.find_by_name('Database')
474 field = IssueCustomField.find_by_name('Database')
444 field.update_attribute(:is_required, true)
475 field.update_attribute(:is_required, true)
445
476
446 @request.session[:user_id] = 2
477 @request.session[:user_id] = 2
447 post :create, :project_id => 1,
478 post :create, :project_id => 1,
448 :issue => {:tracker_id => 1,
479 :issue => {:tracker_id => 1,
449 :subject => 'This is the test_new issue',
480 :subject => 'This is the test_new issue',
450 :description => 'This is the description',
481 :description => 'This is the description',
451 :priority_id => 5}
482 :priority_id => 5}
452 assert_response :success
483 assert_response :success
453 assert_template 'new'
484 assert_template 'new'
454 issue = assigns(:issue)
485 issue = assigns(:issue)
455 assert_not_nil issue
486 assert_not_nil issue
456 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
487 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
457 end
488 end
458
489
459 def test_post_create_with_watchers
490 def test_post_create_with_watchers
460 @request.session[:user_id] = 2
491 @request.session[:user_id] = 2
461 ActionMailer::Base.deliveries.clear
492 ActionMailer::Base.deliveries.clear
462
493
463 assert_difference 'Watcher.count', 2 do
494 assert_difference 'Watcher.count', 2 do
464 post :create, :project_id => 1,
495 post :create, :project_id => 1,
465 :issue => {:tracker_id => 1,
496 :issue => {:tracker_id => 1,
466 :subject => 'This is a new issue with watchers',
497 :subject => 'This is a new issue with watchers',
467 :description => 'This is the description',
498 :description => 'This is the description',
468 :priority_id => 5,
499 :priority_id => 5,
469 :watcher_user_ids => ['2', '3']}
500 :watcher_user_ids => ['2', '3']}
470 end
501 end
471 issue = Issue.find_by_subject('This is a new issue with watchers')
502 issue = Issue.find_by_subject('This is a new issue with watchers')
472 assert_not_nil issue
503 assert_not_nil issue
473 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
504 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
474
505
475 # Watchers added
506 # Watchers added
476 assert_equal [2, 3], issue.watcher_user_ids.sort
507 assert_equal [2, 3], issue.watcher_user_ids.sort
477 assert issue.watched_by?(User.find(3))
508 assert issue.watched_by?(User.find(3))
478 # Watchers notified
509 # Watchers notified
479 mail = ActionMailer::Base.deliveries.last
510 mail = ActionMailer::Base.deliveries.last
480 assert_kind_of TMail::Mail, mail
511 assert_kind_of TMail::Mail, mail
481 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
512 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
482 end
513 end
483
514
484 def test_post_create_subissue
515 def test_post_create_subissue
485 @request.session[:user_id] = 2
516 @request.session[:user_id] = 2
486
517
487 assert_difference 'Issue.count' do
518 assert_difference 'Issue.count' do
488 post :create, :project_id => 1,
519 post :create, :project_id => 1,
489 :issue => {:tracker_id => 1,
520 :issue => {:tracker_id => 1,
490 :subject => 'This is a child issue',
521 :subject => 'This is a child issue',
491 :parent_issue_id => 2}
522 :parent_issue_id => 2}
492 end
523 end
493 issue = Issue.find_by_subject('This is a child issue')
524 issue = Issue.find_by_subject('This is a child issue')
494 assert_not_nil issue
525 assert_not_nil issue
495 assert_equal Issue.find(2), issue.parent
526 assert_equal Issue.find(2), issue.parent
496 end
527 end
497
528
498 def test_post_create_should_send_a_notification
529 def test_post_create_should_send_a_notification
499 ActionMailer::Base.deliveries.clear
530 ActionMailer::Base.deliveries.clear
500 @request.session[:user_id] = 2
531 @request.session[:user_id] = 2
501 assert_difference 'Issue.count' do
532 assert_difference 'Issue.count' do
502 post :create, :project_id => 1,
533 post :create, :project_id => 1,
503 :issue => {:tracker_id => 3,
534 :issue => {:tracker_id => 3,
504 :subject => 'This is the test_new issue',
535 :subject => 'This is the test_new issue',
505 :description => 'This is the description',
536 :description => 'This is the description',
506 :priority_id => 5,
537 :priority_id => 5,
507 :estimated_hours => '',
538 :estimated_hours => '',
508 :custom_field_values => {'2' => 'Value for field 2'}}
539 :custom_field_values => {'2' => 'Value for field 2'}}
509 end
540 end
510 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
541 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
511
542
512 assert_equal 1, ActionMailer::Base.deliveries.size
543 assert_equal 1, ActionMailer::Base.deliveries.size
513 end
544 end
514
545
515 def test_post_create_should_preserve_fields_values_on_validation_failure
546 def test_post_create_should_preserve_fields_values_on_validation_failure
516 @request.session[:user_id] = 2
547 @request.session[:user_id] = 2
517 post :create, :project_id => 1,
548 post :create, :project_id => 1,
518 :issue => {:tracker_id => 1,
549 :issue => {:tracker_id => 1,
519 # empty subject
550 # empty subject
520 :subject => '',
551 :subject => '',
521 :description => 'This is a description',
552 :description => 'This is a description',
522 :priority_id => 6,
553 :priority_id => 6,
523 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
554 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
524 assert_response :success
555 assert_response :success
525 assert_template 'new'
556 assert_template 'new'
526
557
527 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
558 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
528 :content => 'This is a description'
559 :content => 'This is a description'
529 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
560 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
530 :child => { :tag => 'option', :attributes => { :selected => 'selected',
561 :child => { :tag => 'option', :attributes => { :selected => 'selected',
531 :value => '6' },
562 :value => '6' },
532 :content => 'High' }
563 :content => 'High' }
533 # Custom fields
564 # Custom fields
534 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
565 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
535 :child => { :tag => 'option', :attributes => { :selected => 'selected',
566 :child => { :tag => 'option', :attributes => { :selected => 'selected',
536 :value => 'Oracle' },
567 :value => 'Oracle' },
537 :content => 'Oracle' }
568 :content => 'Oracle' }
538 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
569 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
539 :value => 'Value for field 2'}
570 :value => 'Value for field 2'}
540 end
571 end
541
572
542 def test_post_create_should_ignore_non_safe_attributes
573 def test_post_create_should_ignore_non_safe_attributes
543 @request.session[:user_id] = 2
574 @request.session[:user_id] = 2
544 assert_nothing_raised do
575 assert_nothing_raised do
545 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
576 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
546 end
577 end
547 end
578 end
548
579
549 context "without workflow privilege" do
580 context "without workflow privilege" do
550 setup do
581 setup do
551 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
582 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
552 Role.anonymous.add_permission! :add_issues
583 Role.anonymous.add_permission! :add_issues
553 end
584 end
554
585
555 context "#new" do
586 context "#new" do
556 should "propose default status only" do
587 should "propose default status only" do
557 get :new, :project_id => 1
588 get :new, :project_id => 1
558 assert_response :success
589 assert_response :success
559 assert_template 'new'
590 assert_template 'new'
560 assert_tag :tag => 'select',
591 assert_tag :tag => 'select',
561 :attributes => {:name => 'issue[status_id]'},
592 :attributes => {:name => 'issue[status_id]'},
562 :children => {:count => 1},
593 :children => {:count => 1},
563 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
594 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
564 end
595 end
565
596
566 should "accept default status" do
597 should "accept default status" do
567 assert_difference 'Issue.count' do
598 assert_difference 'Issue.count' do
568 post :create, :project_id => 1,
599 post :create, :project_id => 1,
569 :issue => {:tracker_id => 1,
600 :issue => {:tracker_id => 1,
570 :subject => 'This is an issue',
601 :subject => 'This is an issue',
571 :status_id => 1}
602 :status_id => 1}
572 end
603 end
573 issue = Issue.last(:order => 'id')
604 issue = Issue.last(:order => 'id')
574 assert_equal IssueStatus.default, issue.status
605 assert_equal IssueStatus.default, issue.status
575 end
606 end
576
607
577 should "ignore unauthorized status" do
608 should "ignore unauthorized status" do
578 assert_difference 'Issue.count' do
609 assert_difference 'Issue.count' do
579 post :create, :project_id => 1,
610 post :create, :project_id => 1,
580 :issue => {:tracker_id => 1,
611 :issue => {:tracker_id => 1,
581 :subject => 'This is an issue',
612 :subject => 'This is an issue',
582 :status_id => 3}
613 :status_id => 3}
583 end
614 end
584 issue = Issue.last(:order => 'id')
615 issue = Issue.last(:order => 'id')
585 assert_equal IssueStatus.default, issue.status
616 assert_equal IssueStatus.default, issue.status
586 end
617 end
587 end
618 end
588 end
619 end
589
620
590 def test_copy_issue
621 def test_copy_issue
591 @request.session[:user_id] = 2
622 @request.session[:user_id] = 2
592 get :new, :project_id => 1, :copy_from => 1
623 get :new, :project_id => 1, :copy_from => 1
593 assert_template 'new'
624 assert_template 'new'
594 assert_not_nil assigns(:issue)
625 assert_not_nil assigns(:issue)
595 orig = Issue.find(1)
626 orig = Issue.find(1)
596 assert_equal orig.subject, assigns(:issue).subject
627 assert_equal orig.subject, assigns(:issue).subject
597 end
628 end
598
629
599 def test_get_edit
630 def test_get_edit
600 @request.session[:user_id] = 2
631 @request.session[:user_id] = 2
601 get :edit, :id => 1
632 get :edit, :id => 1
602 assert_response :success
633 assert_response :success
603 assert_template 'edit'
634 assert_template 'edit'
604 assert_not_nil assigns(:issue)
635 assert_not_nil assigns(:issue)
605 assert_equal Issue.find(1), assigns(:issue)
636 assert_equal Issue.find(1), assigns(:issue)
606 end
637 end
607
638
608 def test_get_edit_with_params
639 def test_get_edit_with_params
609 @request.session[:user_id] = 2
640 @request.session[:user_id] = 2
610 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
641 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
611 assert_response :success
642 assert_response :success
612 assert_template 'edit'
643 assert_template 'edit'
613
644
614 issue = assigns(:issue)
645 issue = assigns(:issue)
615 assert_not_nil issue
646 assert_not_nil issue
616
647
617 assert_equal 5, issue.status_id
648 assert_equal 5, issue.status_id
618 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
649 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
619 :child => { :tag => 'option',
650 :child => { :tag => 'option',
620 :content => 'Closed',
651 :content => 'Closed',
621 :attributes => { :selected => 'selected' } }
652 :attributes => { :selected => 'selected' } }
622
653
623 assert_equal 7, issue.priority_id
654 assert_equal 7, issue.priority_id
624 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
655 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
625 :child => { :tag => 'option',
656 :child => { :tag => 'option',
626 :content => 'Urgent',
657 :content => 'Urgent',
627 :attributes => { :selected => 'selected' } }
658 :attributes => { :selected => 'selected' } }
628 end
659 end
629
660
630 def test_update_edit_form
661 def test_update_edit_form
631 @request.session[:user_id] = 2
662 @request.session[:user_id] = 2
632 xhr :post, :new, :project_id => 1,
663 xhr :post, :new, :project_id => 1,
633 :id => 1,
664 :id => 1,
634 :issue => {:tracker_id => 2,
665 :issue => {:tracker_id => 2,
635 :subject => 'This is the test_new issue',
666 :subject => 'This is the test_new issue',
636 :description => 'This is the description',
667 :description => 'This is the description',
637 :priority_id => 5}
668 :priority_id => 5}
638 assert_response :success
669 assert_response :success
639 assert_template 'attributes'
670 assert_template 'attributes'
640
671
641 issue = assigns(:issue)
672 issue = assigns(:issue)
642 assert_kind_of Issue, issue
673 assert_kind_of Issue, issue
643 assert_equal 1, issue.id
674 assert_equal 1, issue.id
644 assert_equal 1, issue.project_id
675 assert_equal 1, issue.project_id
645 assert_equal 2, issue.tracker_id
676 assert_equal 2, issue.tracker_id
646 assert_equal 'This is the test_new issue', issue.subject
677 assert_equal 'This is the test_new issue', issue.subject
647 end
678 end
648
679
649 def test_update_using_invalid_http_verbs
680 def test_update_using_invalid_http_verbs
650 @request.session[:user_id] = 2
681 @request.session[:user_id] = 2
651 subject = 'Updated by an invalid http verb'
682 subject = 'Updated by an invalid http verb'
652
683
653 get :update, :id => 1, :issue => {:subject => subject}
684 get :update, :id => 1, :issue => {:subject => subject}
654 assert_not_equal subject, Issue.find(1).subject
685 assert_not_equal subject, Issue.find(1).subject
655
686
656 post :update, :id => 1, :issue => {:subject => subject}
687 post :update, :id => 1, :issue => {:subject => subject}
657 assert_not_equal subject, Issue.find(1).subject
688 assert_not_equal subject, Issue.find(1).subject
658
689
659 delete :update, :id => 1, :issue => {:subject => subject}
690 delete :update, :id => 1, :issue => {:subject => subject}
660 assert_not_equal subject, Issue.find(1).subject
691 assert_not_equal subject, Issue.find(1).subject
661 end
692 end
662
693
663 def test_put_update_without_custom_fields_param
694 def test_put_update_without_custom_fields_param
664 @request.session[:user_id] = 2
695 @request.session[:user_id] = 2
665 ActionMailer::Base.deliveries.clear
696 ActionMailer::Base.deliveries.clear
666
697
667 issue = Issue.find(1)
698 issue = Issue.find(1)
668 assert_equal '125', issue.custom_value_for(2).value
699 assert_equal '125', issue.custom_value_for(2).value
669 old_subject = issue.subject
700 old_subject = issue.subject
670 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
701 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
671
702
672 assert_difference('Journal.count') do
703 assert_difference('Journal.count') do
673 assert_difference('JournalDetail.count', 2) do
704 assert_difference('JournalDetail.count', 2) do
674 put :update, :id => 1, :issue => {:subject => new_subject,
705 put :update, :id => 1, :issue => {:subject => new_subject,
675 :priority_id => '6',
706 :priority_id => '6',
676 :category_id => '1' # no change
707 :category_id => '1' # no change
677 }
708 }
678 end
709 end
679 end
710 end
680 assert_redirected_to :action => 'show', :id => '1'
711 assert_redirected_to :action => 'show', :id => '1'
681 issue.reload
712 issue.reload
682 assert_equal new_subject, issue.subject
713 assert_equal new_subject, issue.subject
683 # Make sure custom fields were not cleared
714 # Make sure custom fields were not cleared
684 assert_equal '125', issue.custom_value_for(2).value
715 assert_equal '125', issue.custom_value_for(2).value
685
716
686 mail = ActionMailer::Base.deliveries.last
717 mail = ActionMailer::Base.deliveries.last
687 assert_kind_of TMail::Mail, mail
718 assert_kind_of TMail::Mail, mail
688 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
719 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
689 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
720 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
690 end
721 end
691
722
692 def test_put_update_with_custom_field_change
723 def test_put_update_with_custom_field_change
693 @request.session[:user_id] = 2
724 @request.session[:user_id] = 2
694 issue = Issue.find(1)
725 issue = Issue.find(1)
695 assert_equal '125', issue.custom_value_for(2).value
726 assert_equal '125', issue.custom_value_for(2).value
696
727
697 assert_difference('Journal.count') do
728 assert_difference('Journal.count') do
698 assert_difference('JournalDetail.count', 3) do
729 assert_difference('JournalDetail.count', 3) do
699 put :update, :id => 1, :issue => {:subject => 'Custom field change',
730 put :update, :id => 1, :issue => {:subject => 'Custom field change',
700 :priority_id => '6',
731 :priority_id => '6',
701 :category_id => '1', # no change
732 :category_id => '1', # no change
702 :custom_field_values => { '2' => 'New custom value' }
733 :custom_field_values => { '2' => 'New custom value' }
703 }
734 }
704 end
735 end
705 end
736 end
706 assert_redirected_to :action => 'show', :id => '1'
737 assert_redirected_to :action => 'show', :id => '1'
707 issue.reload
738 issue.reload
708 assert_equal 'New custom value', issue.custom_value_for(2).value
739 assert_equal 'New custom value', issue.custom_value_for(2).value
709
740
710 mail = ActionMailer::Base.deliveries.last
741 mail = ActionMailer::Base.deliveries.last
711 assert_kind_of TMail::Mail, mail
742 assert_kind_of TMail::Mail, mail
712 assert mail.body.include?("Searchable field changed from 125 to New custom value")
743 assert mail.body.include?("Searchable field changed from 125 to New custom value")
713 end
744 end
714
745
715 def test_put_update_with_status_and_assignee_change
746 def test_put_update_with_status_and_assignee_change
716 issue = Issue.find(1)
747 issue = Issue.find(1)
717 assert_equal 1, issue.status_id
748 assert_equal 1, issue.status_id
718 @request.session[:user_id] = 2
749 @request.session[:user_id] = 2
719 assert_difference('TimeEntry.count', 0) do
750 assert_difference('TimeEntry.count', 0) do
720 put :update,
751 put :update,
721 :id => 1,
752 :id => 1,
722 :issue => { :status_id => 2, :assigned_to_id => 3 },
753 :issue => { :status_id => 2, :assigned_to_id => 3 },
723 :notes => 'Assigned to dlopper',
754 :notes => 'Assigned to dlopper',
724 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
755 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
725 end
756 end
726 assert_redirected_to :action => 'show', :id => '1'
757 assert_redirected_to :action => 'show', :id => '1'
727 issue.reload
758 issue.reload
728 assert_equal 2, issue.status_id
759 assert_equal 2, issue.status_id
729 j = Journal.find(:first, :order => 'id DESC')
760 j = Journal.find(:first, :order => 'id DESC')
730 assert_equal 'Assigned to dlopper', j.notes
761 assert_equal 'Assigned to dlopper', j.notes
731 assert_equal 2, j.details.size
762 assert_equal 2, j.details.size
732
763
733 mail = ActionMailer::Base.deliveries.last
764 mail = ActionMailer::Base.deliveries.last
734 assert mail.body.include?("Status changed from New to Assigned")
765 assert mail.body.include?("Status changed from New to Assigned")
735 # subject should contain the new status
766 # subject should contain the new status
736 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
767 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
737 end
768 end
738
769
739 def test_put_update_with_note_only
770 def test_put_update_with_note_only
740 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
771 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
741 # anonymous user
772 # anonymous user
742 put :update,
773 put :update,
743 :id => 1,
774 :id => 1,
744 :notes => notes
775 :notes => notes
745 assert_redirected_to :action => 'show', :id => '1'
776 assert_redirected_to :action => 'show', :id => '1'
746 j = Journal.find(:first, :order => 'id DESC')
777 j = Journal.find(:first, :order => 'id DESC')
747 assert_equal notes, j.notes
778 assert_equal notes, j.notes
748 assert_equal 0, j.details.size
779 assert_equal 0, j.details.size
749 assert_equal User.anonymous, j.user
780 assert_equal User.anonymous, j.user
750
781
751 mail = ActionMailer::Base.deliveries.last
782 mail = ActionMailer::Base.deliveries.last
752 assert mail.body.include?(notes)
783 assert mail.body.include?(notes)
753 end
784 end
754
785
755 def test_put_update_with_note_and_spent_time
786 def test_put_update_with_note_and_spent_time
756 @request.session[:user_id] = 2
787 @request.session[:user_id] = 2
757 spent_hours_before = Issue.find(1).spent_hours
788 spent_hours_before = Issue.find(1).spent_hours
758 assert_difference('TimeEntry.count') do
789 assert_difference('TimeEntry.count') do
759 put :update,
790 put :update,
760 :id => 1,
791 :id => 1,
761 :notes => '2.5 hours added',
792 :notes => '2.5 hours added',
762 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
793 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
763 end
794 end
764 assert_redirected_to :action => 'show', :id => '1'
795 assert_redirected_to :action => 'show', :id => '1'
765
796
766 issue = Issue.find(1)
797 issue = Issue.find(1)
767
798
768 j = Journal.find(:first, :order => 'id DESC')
799 j = Journal.find(:first, :order => 'id DESC')
769 assert_equal '2.5 hours added', j.notes
800 assert_equal '2.5 hours added', j.notes
770 assert_equal 0, j.details.size
801 assert_equal 0, j.details.size
771
802
772 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
803 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
773 assert_not_nil t
804 assert_not_nil t
774 assert_equal 2.5, t.hours
805 assert_equal 2.5, t.hours
775 assert_equal spent_hours_before + 2.5, issue.spent_hours
806 assert_equal spent_hours_before + 2.5, issue.spent_hours
776 end
807 end
777
808
778 def test_put_update_with_attachment_only
809 def test_put_update_with_attachment_only
779 set_tmp_attachments_directory
810 set_tmp_attachments_directory
780
811
781 # Delete all fixtured journals, a race condition can occur causing the wrong
812 # Delete all fixtured journals, a race condition can occur causing the wrong
782 # journal to get fetched in the next find.
813 # journal to get fetched in the next find.
783 Journal.delete_all
814 Journal.delete_all
784
815
785 # anonymous user
816 # anonymous user
786 put :update,
817 put :update,
787 :id => 1,
818 :id => 1,
788 :notes => '',
819 :notes => '',
789 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
820 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
790 assert_redirected_to :action => 'show', :id => '1'
821 assert_redirected_to :action => 'show', :id => '1'
791 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
822 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
792 assert j.notes.blank?
823 assert j.notes.blank?
793 assert_equal 1, j.details.size
824 assert_equal 1, j.details.size
794 assert_equal 'testfile.txt', j.details.first.value
825 assert_equal 'testfile.txt', j.details.first.value
795 assert_equal User.anonymous, j.user
826 assert_equal User.anonymous, j.user
796
827
797 mail = ActionMailer::Base.deliveries.last
828 mail = ActionMailer::Base.deliveries.last
798 assert mail.body.include?('testfile.txt')
829 assert mail.body.include?('testfile.txt')
799 end
830 end
800
831
801 def test_put_update_with_attachment_that_fails_to_save
832 def test_put_update_with_attachment_that_fails_to_save
802 set_tmp_attachments_directory
833 set_tmp_attachments_directory
803
834
804 # Delete all fixtured journals, a race condition can occur causing the wrong
835 # Delete all fixtured journals, a race condition can occur causing the wrong
805 # journal to get fetched in the next find.
836 # journal to get fetched in the next find.
806 Journal.delete_all
837 Journal.delete_all
807
838
808 # Mock out the unsaved attachment
839 # Mock out the unsaved attachment
809 Attachment.any_instance.stubs(:create).returns(Attachment.new)
840 Attachment.any_instance.stubs(:create).returns(Attachment.new)
810
841
811 # anonymous user
842 # anonymous user
812 put :update,
843 put :update,
813 :id => 1,
844 :id => 1,
814 :notes => '',
845 :notes => '',
815 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
846 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
816 assert_redirected_to :action => 'show', :id => '1'
847 assert_redirected_to :action => 'show', :id => '1'
817 assert_equal '1 file(s) could not be saved.', flash[:warning]
848 assert_equal '1 file(s) could not be saved.', flash[:warning]
818
849
819 end if Object.const_defined?(:Mocha)
850 end if Object.const_defined?(:Mocha)
820
851
821 def test_put_update_with_no_change
852 def test_put_update_with_no_change
822 issue = Issue.find(1)
853 issue = Issue.find(1)
823 issue.journals.clear
854 issue.journals.clear
824 ActionMailer::Base.deliveries.clear
855 ActionMailer::Base.deliveries.clear
825
856
826 put :update,
857 put :update,
827 :id => 1,
858 :id => 1,
828 :notes => ''
859 :notes => ''
829 assert_redirected_to :action => 'show', :id => '1'
860 assert_redirected_to :action => 'show', :id => '1'
830
861
831 issue.reload
862 issue.reload
832 assert issue.journals.empty?
863 assert issue.journals.empty?
833 # No email should be sent
864 # No email should be sent
834 assert ActionMailer::Base.deliveries.empty?
865 assert ActionMailer::Base.deliveries.empty?
835 end
866 end
836
867
837 def test_put_update_should_send_a_notification
868 def test_put_update_should_send_a_notification
838 @request.session[:user_id] = 2
869 @request.session[:user_id] = 2
839 ActionMailer::Base.deliveries.clear
870 ActionMailer::Base.deliveries.clear
840 issue = Issue.find(1)
871 issue = Issue.find(1)
841 old_subject = issue.subject
872 old_subject = issue.subject
842 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
873 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
843
874
844 put :update, :id => 1, :issue => {:subject => new_subject,
875 put :update, :id => 1, :issue => {:subject => new_subject,
845 :priority_id => '6',
876 :priority_id => '6',
846 :category_id => '1' # no change
877 :category_id => '1' # no change
847 }
878 }
848 assert_equal 1, ActionMailer::Base.deliveries.size
879 assert_equal 1, ActionMailer::Base.deliveries.size
849 end
880 end
850
881
851 def test_put_update_with_invalid_spent_time
882 def test_put_update_with_invalid_spent_time
852 @request.session[:user_id] = 2
883 @request.session[:user_id] = 2
853 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
884 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
854
885
855 assert_no_difference('Journal.count') do
886 assert_no_difference('Journal.count') do
856 put :update,
887 put :update,
857 :id => 1,
888 :id => 1,
858 :notes => notes,
889 :notes => notes,
859 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
890 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
860 end
891 end
861 assert_response :success
892 assert_response :success
862 assert_template 'edit'
893 assert_template 'edit'
863
894
864 assert_tag :textarea, :attributes => { :name => 'notes' },
895 assert_tag :textarea, :attributes => { :name => 'notes' },
865 :content => notes
896 :content => notes
866 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
897 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
867 end
898 end
868
899
869 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
900 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
870 issue = Issue.find(2)
901 issue = Issue.find(2)
871 @request.session[:user_id] = 2
902 @request.session[:user_id] = 2
872
903
873 put :update,
904 put :update,
874 :id => issue.id,
905 :id => issue.id,
875 :issue => {
906 :issue => {
876 :fixed_version_id => 4
907 :fixed_version_id => 4
877 }
908 }
878
909
879 assert_response :redirect
910 assert_response :redirect
880 issue.reload
911 issue.reload
881 assert_equal 4, issue.fixed_version_id
912 assert_equal 4, issue.fixed_version_id
882 assert_not_equal issue.project_id, issue.fixed_version.project_id
913 assert_not_equal issue.project_id, issue.fixed_version.project_id
883 end
914 end
884
915
885 def test_put_update_should_redirect_back_using_the_back_url_parameter
916 def test_put_update_should_redirect_back_using_the_back_url_parameter
886 issue = Issue.find(2)
917 issue = Issue.find(2)
887 @request.session[:user_id] = 2
918 @request.session[:user_id] = 2
888
919
889 put :update,
920 put :update,
890 :id => issue.id,
921 :id => issue.id,
891 :issue => {
922 :issue => {
892 :fixed_version_id => 4
923 :fixed_version_id => 4
893 },
924 },
894 :back_url => '/issues'
925 :back_url => '/issues'
895
926
896 assert_response :redirect
927 assert_response :redirect
897 assert_redirected_to '/issues'
928 assert_redirected_to '/issues'
898 end
929 end
899
930
900 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
931 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
901 issue = Issue.find(2)
932 issue = Issue.find(2)
902 @request.session[:user_id] = 2
933 @request.session[:user_id] = 2
903
934
904 put :update,
935 put :update,
905 :id => issue.id,
936 :id => issue.id,
906 :issue => {
937 :issue => {
907 :fixed_version_id => 4
938 :fixed_version_id => 4
908 },
939 },
909 :back_url => 'http://google.com'
940 :back_url => 'http://google.com'
910
941
911 assert_response :redirect
942 assert_response :redirect
912 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
943 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
913 end
944 end
914
945
915 def test_get_bulk_edit
946 def test_get_bulk_edit
916 @request.session[:user_id] = 2
947 @request.session[:user_id] = 2
917 get :bulk_edit, :ids => [1, 2]
948 get :bulk_edit, :ids => [1, 2]
918 assert_response :success
949 assert_response :success
919 assert_template 'bulk_edit'
950 assert_template 'bulk_edit'
920
951
921 # Project specific custom field, date type
952 # Project specific custom field, date type
922 field = CustomField.find(9)
953 field = CustomField.find(9)
923 assert !field.is_for_all?
954 assert !field.is_for_all?
924 assert_equal 'date', field.field_format
955 assert_equal 'date', field.field_format
925 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
956 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
926
957
927 # System wide custom field
958 # System wide custom field
928 assert CustomField.find(1).is_for_all?
959 assert CustomField.find(1).is_for_all?
929 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
960 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
930 end
961 end
931
962
932 def test_get_bulk_edit_on_different_projects
963 def test_get_bulk_edit_on_different_projects
933 @request.session[:user_id] = 2
964 @request.session[:user_id] = 2
934 get :bulk_edit, :ids => [1, 2, 6]
965 get :bulk_edit, :ids => [1, 2, 6]
935 assert_response :success
966 assert_response :success
936 assert_template 'bulk_edit'
967 assert_template 'bulk_edit'
937
968
938 # Project specific custom field, date type
969 # Project specific custom field, date type
939 field = CustomField.find(9)
970 field = CustomField.find(9)
940 assert !field.is_for_all?
971 assert !field.is_for_all?
941 assert !field.project_ids.include?(Issue.find(6).project_id)
972 assert !field.project_ids.include?(Issue.find(6).project_id)
942 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
973 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
943 end
974 end
944
975
945 def test_bulk_update
976 def test_bulk_update
946 @request.session[:user_id] = 2
977 @request.session[:user_id] = 2
947 # update issues priority
978 # update issues priority
948 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
979 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
949 :issue => {:priority_id => 7,
980 :issue => {:priority_id => 7,
950 :assigned_to_id => '',
981 :assigned_to_id => '',
951 :custom_field_values => {'2' => ''}}
982 :custom_field_values => {'2' => ''}}
952
983
953 assert_response 302
984 assert_response 302
954 # check that the issues were updated
985 # check that the issues were updated
955 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
986 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
956
987
957 issue = Issue.find(1)
988 issue = Issue.find(1)
958 journal = issue.journals.find(:first, :order => 'created_on DESC')
989 journal = issue.journals.find(:first, :order => 'created_on DESC')
959 assert_equal '125', issue.custom_value_for(2).value
990 assert_equal '125', issue.custom_value_for(2).value
960 assert_equal 'Bulk editing', journal.notes
991 assert_equal 'Bulk editing', journal.notes
961 assert_equal 1, journal.details.size
992 assert_equal 1, journal.details.size
962 end
993 end
963
994
964 def test_bulk_update_on_different_projects
995 def test_bulk_update_on_different_projects
965 @request.session[:user_id] = 2
996 @request.session[:user_id] = 2
966 # update issues priority
997 # update issues priority
967 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
998 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
968 :issue => {:priority_id => 7,
999 :issue => {:priority_id => 7,
969 :assigned_to_id => '',
1000 :assigned_to_id => '',
970 :custom_field_values => {'2' => ''}}
1001 :custom_field_values => {'2' => ''}}
971
1002
972 assert_response 302
1003 assert_response 302
973 # check that the issues were updated
1004 # check that the issues were updated
974 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1005 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
975
1006
976 issue = Issue.find(1)
1007 issue = Issue.find(1)
977 journal = issue.journals.find(:first, :order => 'created_on DESC')
1008 journal = issue.journals.find(:first, :order => 'created_on DESC')
978 assert_equal '125', issue.custom_value_for(2).value
1009 assert_equal '125', issue.custom_value_for(2).value
979 assert_equal 'Bulk editing', journal.notes
1010 assert_equal 'Bulk editing', journal.notes
980 assert_equal 1, journal.details.size
1011 assert_equal 1, journal.details.size
981 end
1012 end
982
1013
983 def test_bulk_update_on_different_projects_without_rights
1014 def test_bulk_update_on_different_projects_without_rights
984 @request.session[:user_id] = 3
1015 @request.session[:user_id] = 3
985 user = User.find(3)
1016 user = User.find(3)
986 action = { :controller => "issues", :action => "bulk_update" }
1017 action = { :controller => "issues", :action => "bulk_update" }
987 assert user.allowed_to?(action, Issue.find(1).project)
1018 assert user.allowed_to?(action, Issue.find(1).project)
988 assert ! user.allowed_to?(action, Issue.find(6).project)
1019 assert ! user.allowed_to?(action, Issue.find(6).project)
989 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
1020 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
990 :issue => {:priority_id => 7,
1021 :issue => {:priority_id => 7,
991 :assigned_to_id => '',
1022 :assigned_to_id => '',
992 :custom_field_values => {'2' => ''}}
1023 :custom_field_values => {'2' => ''}}
993 assert_response 403
1024 assert_response 403
994 assert_not_equal "Bulk should fail", Journal.last.notes
1025 assert_not_equal "Bulk should fail", Journal.last.notes
995 end
1026 end
996
1027
997 def test_bullk_update_should_send_a_notification
1028 def test_bullk_update_should_send_a_notification
998 @request.session[:user_id] = 2
1029 @request.session[:user_id] = 2
999 ActionMailer::Base.deliveries.clear
1030 ActionMailer::Base.deliveries.clear
1000 post(:bulk_update,
1031 post(:bulk_update,
1001 {
1032 {
1002 :ids => [1, 2],
1033 :ids => [1, 2],
1003 :notes => 'Bulk editing',
1034 :notes => 'Bulk editing',
1004 :issue => {
1035 :issue => {
1005 :priority_id => 7,
1036 :priority_id => 7,
1006 :assigned_to_id => '',
1037 :assigned_to_id => '',
1007 :custom_field_values => {'2' => ''}
1038 :custom_field_values => {'2' => ''}
1008 }
1039 }
1009 })
1040 })
1010
1041
1011 assert_response 302
1042 assert_response 302
1012 assert_equal 2, ActionMailer::Base.deliveries.size
1043 assert_equal 2, ActionMailer::Base.deliveries.size
1013 end
1044 end
1014
1045
1015 def test_bulk_update_status
1046 def test_bulk_update_status
1016 @request.session[:user_id] = 2
1047 @request.session[:user_id] = 2
1017 # update issues priority
1048 # update issues priority
1018 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1049 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1019 :issue => {:priority_id => '',
1050 :issue => {:priority_id => '',
1020 :assigned_to_id => '',
1051 :assigned_to_id => '',
1021 :status_id => '5'}
1052 :status_id => '5'}
1022
1053
1023 assert_response 302
1054 assert_response 302
1024 issue = Issue.find(1)
1055 issue = Issue.find(1)
1025 assert issue.closed?
1056 assert issue.closed?
1026 end
1057 end
1027
1058
1028 def test_bulk_update_custom_field
1059 def test_bulk_update_custom_field
1029 @request.session[:user_id] = 2
1060 @request.session[:user_id] = 2
1030 # update issues priority
1061 # update issues priority
1031 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1062 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1032 :issue => {:priority_id => '',
1063 :issue => {:priority_id => '',
1033 :assigned_to_id => '',
1064 :assigned_to_id => '',
1034 :custom_field_values => {'2' => '777'}}
1065 :custom_field_values => {'2' => '777'}}
1035
1066
1036 assert_response 302
1067 assert_response 302
1037
1068
1038 issue = Issue.find(1)
1069 issue = Issue.find(1)
1039 journal = issue.journals.find(:first, :order => 'created_on DESC')
1070 journal = issue.journals.find(:first, :order => 'created_on DESC')
1040 assert_equal '777', issue.custom_value_for(2).value
1071 assert_equal '777', issue.custom_value_for(2).value
1041 assert_equal 1, journal.details.size
1072 assert_equal 1, journal.details.size
1042 assert_equal '125', journal.details.first.old_value
1073 assert_equal '125', journal.details.first.old_value
1043 assert_equal '777', journal.details.first.value
1074 assert_equal '777', journal.details.first.value
1044 end
1075 end
1045
1076
1046 def test_bulk_update_unassign
1077 def test_bulk_update_unassign
1047 assert_not_nil Issue.find(2).assigned_to
1078 assert_not_nil Issue.find(2).assigned_to
1048 @request.session[:user_id] = 2
1079 @request.session[:user_id] = 2
1049 # unassign issues
1080 # unassign issues
1050 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1081 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1051 assert_response 302
1082 assert_response 302
1052 # check that the issues were updated
1083 # check that the issues were updated
1053 assert_nil Issue.find(2).assigned_to
1084 assert_nil Issue.find(2).assigned_to
1054 end
1085 end
1055
1086
1056 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1087 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1057 @request.session[:user_id] = 2
1088 @request.session[:user_id] = 2
1058
1089
1059 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1090 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1060
1091
1061 assert_response :redirect
1092 assert_response :redirect
1062 issues = Issue.find([1,2])
1093 issues = Issue.find([1,2])
1063 issues.each do |issue|
1094 issues.each do |issue|
1064 assert_equal 4, issue.fixed_version_id
1095 assert_equal 4, issue.fixed_version_id
1065 assert_not_equal issue.project_id, issue.fixed_version.project_id
1096 assert_not_equal issue.project_id, issue.fixed_version.project_id
1066 end
1097 end
1067 end
1098 end
1068
1099
1069 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1100 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1070 @request.session[:user_id] = 2
1101 @request.session[:user_id] = 2
1071 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1102 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1072
1103
1073 assert_response :redirect
1104 assert_response :redirect
1074 assert_redirected_to '/issues'
1105 assert_redirected_to '/issues'
1075 end
1106 end
1076
1107
1077 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1108 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1078 @request.session[:user_id] = 2
1109 @request.session[:user_id] = 2
1079 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1110 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1080
1111
1081 assert_response :redirect
1112 assert_response :redirect
1082 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1113 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1083 end
1114 end
1084
1115
1085 def test_destroy_issue_with_no_time_entries
1116 def test_destroy_issue_with_no_time_entries
1086 assert_nil TimeEntry.find_by_issue_id(2)
1117 assert_nil TimeEntry.find_by_issue_id(2)
1087 @request.session[:user_id] = 2
1118 @request.session[:user_id] = 2
1088 post :destroy, :id => 2
1119 post :destroy, :id => 2
1089 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1120 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1090 assert_nil Issue.find_by_id(2)
1121 assert_nil Issue.find_by_id(2)
1091 end
1122 end
1092
1123
1093 def test_destroy_issues_with_time_entries
1124 def test_destroy_issues_with_time_entries
1094 @request.session[:user_id] = 2
1125 @request.session[:user_id] = 2
1095 post :destroy, :ids => [1, 3]
1126 post :destroy, :ids => [1, 3]
1096 assert_response :success
1127 assert_response :success
1097 assert_template 'destroy'
1128 assert_template 'destroy'
1098 assert_not_nil assigns(:hours)
1129 assert_not_nil assigns(:hours)
1099 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1130 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1100 end
1131 end
1101
1132
1102 def test_destroy_issues_and_destroy_time_entries
1133 def test_destroy_issues_and_destroy_time_entries
1103 @request.session[:user_id] = 2
1134 @request.session[:user_id] = 2
1104 post :destroy, :ids => [1, 3], :todo => 'destroy'
1135 post :destroy, :ids => [1, 3], :todo => 'destroy'
1105 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1136 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1106 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1137 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1107 assert_nil TimeEntry.find_by_id([1, 2])
1138 assert_nil TimeEntry.find_by_id([1, 2])
1108 end
1139 end
1109
1140
1110 def test_destroy_issues_and_assign_time_entries_to_project
1141 def test_destroy_issues_and_assign_time_entries_to_project
1111 @request.session[:user_id] = 2
1142 @request.session[:user_id] = 2
1112 post :destroy, :ids => [1, 3], :todo => 'nullify'
1143 post :destroy, :ids => [1, 3], :todo => 'nullify'
1113 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1144 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1114 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1145 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1115 assert_nil TimeEntry.find(1).issue_id
1146 assert_nil TimeEntry.find(1).issue_id
1116 assert_nil TimeEntry.find(2).issue_id
1147 assert_nil TimeEntry.find(2).issue_id
1117 end
1148 end
1118
1149
1119 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1150 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1120 @request.session[:user_id] = 2
1151 @request.session[:user_id] = 2
1121 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1152 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1122 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1153 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1123 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1154 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1124 assert_equal 2, TimeEntry.find(1).issue_id
1155 assert_equal 2, TimeEntry.find(1).issue_id
1125 assert_equal 2, TimeEntry.find(2).issue_id
1156 assert_equal 2, TimeEntry.find(2).issue_id
1126 end
1157 end
1127
1158
1128 def test_destroy_issues_from_different_projects
1159 def test_destroy_issues_from_different_projects
1129 @request.session[:user_id] = 2
1160 @request.session[:user_id] = 2
1130 post :destroy, :ids => [1, 2, 6], :todo => 'destroy'
1161 post :destroy, :ids => [1, 2, 6], :todo => 'destroy'
1131 assert_redirected_to :controller => 'issues', :action => 'index'
1162 assert_redirected_to :controller => 'issues', :action => 'index'
1132 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
1163 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
1133 end
1164 end
1134
1165
1135 def test_default_search_scope
1166 def test_default_search_scope
1136 get :index
1167 get :index
1137 assert_tag :div, :attributes => {:id => 'quick-search'},
1168 assert_tag :div, :attributes => {:id => 'quick-search'},
1138 :child => {:tag => 'form',
1169 :child => {:tag => 'form',
1139 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1170 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1140 end
1171 end
1141 end
1172 end
General Comments 0
You need to be logged in to leave comments. Login now