##// END OF EJS Templates
Added a cross-project issue list. It displays the issues of all the projects visible by the user....
Jean-Philippe Lang -
r673:404bfce44691
parent child
Show More
@@ -0,0 +1,55
1 <h2><%=l(:label_issue_plural)%></h2>
2
3 <% form_tag({}, :id => 'query_form') do %>
4 <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
5 <% end %>
6 <div class="contextual">
7 <%= link_to_remote l(:button_apply),
8 { :url => { :set_filter => 1 },
9 :update => "content",
10 :with => "Form.serialize('query_form')"
11 }, :class => 'icon icon-edit' %>
12
13 <%= link_to_remote l(:button_clear),
14 { :url => { :set_filter => 1 },
15 :update => "content",
16 }, :class => 'icon icon-reload' %>
17 </div>
18 <br />&nbsp;
19
20 <%= error_messages_for 'query' %>
21 <% if @query.valid? %>
22 <% if @issues.empty? %>
23 <p><i><%= l(:label_no_data) %></i></p>
24 <% else %>
25 &nbsp;
26 <table class="list">
27 <thead><tr>
28 <%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %>
29 <%= sort_header_tag("#{Project.table_name}.name", :caption => l(:field_project)) %>
30 <%= sort_header_tag("#{Issue.table_name}.tracker_id", :caption => l(:field_tracker)) %>
31 <%= sort_header_tag("#{IssueStatus.table_name}.name", :caption => l(:field_status)) %>
32 <%= sort_header_tag("#{Issue.table_name}.priority_id", :caption => l(:field_priority)) %>
33 <th><%=l(:field_subject)%></th>
34 <%= sort_header_tag("#{User.table_name}.lastname", :caption => l(:field_assigned_to)) %>
35 <%= sort_header_tag("#{Issue.table_name}.updated_on", :caption => l(:field_updated_on)) %>
36 </tr></thead>
37 <tbody>
38 <% for issue in @issues %>
39 <tr class="<%= cycle("odd", "even") %>">
40 <td align="center" valign="top"><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
41 <td align="center" valign="top" nowrap><%=h issue.project.name %></td>
42 <td align="center" valign="top" nowrap><%= issue.tracker.name %></td>
43 <td valign="top"nowrap><div class="square" style="background:#<%= issue.status.html_color %>;"></div> <%= issue.status.name %></td>
44 <td align="center" valign="top"><%= issue.priority.name %></td>
45 <td><%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %></td>
46 <td align="center" valign="top" nowrap><%= issue.assigned_to.name if issue.assigned_to %></td>
47 <td align="center" valign="top" nowrap><%= format_time(issue.updated_on) %></td>
48 </tr>
49 <% end %>
50 </tbody>
51 </table>
52 <p><%= pagination_links_full @issue_pages %>
53 [ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]</p>
54 <% end %>
55 <% end %>
@@ -1,153 +1,192
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 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 layout 'base', :except => :export_pdf
19 layout 'base', :except => :export_pdf
20 before_filter :find_project, :authorize
20 before_filter :find_project, :authorize, :except => :index
21
21
22 cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
22 cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
23
23
24 helper :projects
24 helper :projects
25 include ProjectsHelper
25 include ProjectsHelper
26 helper :custom_fields
26 helper :custom_fields
27 include CustomFieldsHelper
27 include CustomFieldsHelper
28 helper :ifpdf
28 helper :ifpdf
29 include IfpdfHelper
29 include IfpdfHelper
30 helper :issue_relations
30 helper :issue_relations
31 include IssueRelationsHelper
31 include IssueRelationsHelper
32 helper :watchers
32 helper :watchers
33 include WatchersHelper
33 include WatchersHelper
34 helper :attachments
34 helper :attachments
35 include AttachmentsHelper
35 include AttachmentsHelper
36 helper :queries
37 helper :sort
38 include SortHelper
39
40 def index
41 sort_init "#{Issue.table_name}.id", "desc"
42 sort_update
43 retrieve_query
44 if @query.valid?
45 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
46 @issue_pages = Paginator.new self, @issue_count, 25, params['page']
47 @issues = Issue.find :all, :order => sort_clause,
48 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
49 :conditions => @query.statement,
50 :limit => @issue_pages.items_per_page,
51 :offset => @issue_pages.current.offset
52 end
53 render :layout => false if request.xhr?
54 end
36
55
37 def show
56 def show
38 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
57 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
39 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
58 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
40 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
59 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
41 end
60 end
42
61
43 def export_pdf
62 def export_pdf
44 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
63 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
45 @options_for_rfpdf ||= {}
64 @options_for_rfpdf ||= {}
46 @options_for_rfpdf[:file_name] = "#{@project.name}_#{@issue.id}.pdf"
65 @options_for_rfpdf[:file_name] = "#{@project.name}_#{@issue.id}.pdf"
47 end
66 end
48
67
49 def edit
68 def edit
50 @priorities = Enumeration::get_values('IPRI')
69 @priorities = Enumeration::get_values('IPRI')
51 if request.get?
70 if request.get?
52 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
71 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
53 else
72 else
54 begin
73 begin
55 @issue.init_journal(self.logged_in_user)
74 @issue.init_journal(self.logged_in_user)
56 # Retrieve custom fields and values
75 # Retrieve custom fields and values
57 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
76 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
58 @issue.custom_values = @custom_values
77 @issue.custom_values = @custom_values
59 @issue.attributes = params[:issue]
78 @issue.attributes = params[:issue]
60 if @issue.save
79 if @issue.save
61 flash[:notice] = l(:notice_successful_update)
80 flash[:notice] = l(:notice_successful_update)
62 redirect_to :action => 'show', :id => @issue
81 redirect_to :action => 'show', :id => @issue
63 end
82 end
64 rescue ActiveRecord::StaleObjectError
83 rescue ActiveRecord::StaleObjectError
65 # Optimistic locking exception
84 # Optimistic locking exception
66 flash[:error] = l(:notice_locking_conflict)
85 flash[:error] = l(:notice_locking_conflict)
67 end
86 end
68 end
87 end
69 end
88 end
70
89
71 def add_note
90 def add_note
72 unless params[:notes].empty?
91 unless params[:notes].empty?
73 journal = @issue.init_journal(self.logged_in_user, params[:notes])
92 journal = @issue.init_journal(self.logged_in_user, params[:notes])
74 if @issue.save
93 if @issue.save
75 params[:attachments].each { |file|
94 params[:attachments].each { |file|
76 next unless file.size > 0
95 next unless file.size > 0
77 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
96 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
78 journal.details << JournalDetail.new(:property => 'attachment',
97 journal.details << JournalDetail.new(:property => 'attachment',
79 :prop_key => a.id,
98 :prop_key => a.id,
80 :value => a.filename) unless a.new_record?
99 :value => a.filename) unless a.new_record?
81 } if params[:attachments] and params[:attachments].is_a? Array
100 } if params[:attachments] and params[:attachments].is_a? Array
82 flash[:notice] = l(:notice_successful_update)
101 flash[:notice] = l(:notice_successful_update)
83 Mailer.deliver_issue_edit(journal) #if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
102 Mailer.deliver_issue_edit(journal) #if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
84 redirect_to :action => 'show', :id => @issue
103 redirect_to :action => 'show', :id => @issue
85 return
104 return
86 end
105 end
87 end
106 end
88 show
107 show
89 render :action => 'show'
108 render :action => 'show'
90 end
109 end
91
110
92 def change_status
111 def change_status
93 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
112 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
94 @new_status = IssueStatus.find(params[:new_status_id])
113 @new_status = IssueStatus.find(params[:new_status_id])
95 if params[:confirm]
114 if params[:confirm]
96 begin
115 begin
97 journal = @issue.init_journal(self.logged_in_user, params[:notes])
116 journal = @issue.init_journal(self.logged_in_user, params[:notes])
98 @issue.status = @new_status
117 @issue.status = @new_status
99 if @issue.update_attributes(params[:issue])
118 if @issue.update_attributes(params[:issue])
100 # Save attachments
119 # Save attachments
101 params[:attachments].each { |file|
120 params[:attachments].each { |file|
102 next unless file.size > 0
121 next unless file.size > 0
103 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
122 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
104 journal.details << JournalDetail.new(:property => 'attachment',
123 journal.details << JournalDetail.new(:property => 'attachment',
105 :prop_key => a.id,
124 :prop_key => a.id,
106 :value => a.filename) unless a.new_record?
125 :value => a.filename) unless a.new_record?
107 } if params[:attachments] and params[:attachments].is_a? Array
126 } if params[:attachments] and params[:attachments].is_a? Array
108
127
109 # Log time
128 # Log time
110 if current_role.allowed_to?(:log_time)
129 if current_role.allowed_to?(:log_time)
111 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
130 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
112 @time_entry.attributes = params[:time_entry]
131 @time_entry.attributes = params[:time_entry]
113 @time_entry.save
132 @time_entry.save
114 end
133 end
115
134
116 flash[:notice] = l(:notice_successful_update)
135 flash[:notice] = l(:notice_successful_update)
117 Mailer.deliver_issue_edit(journal) #if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
136 Mailer.deliver_issue_edit(journal) #if Permission.find_by_controller_and_action(params[:controller], params[:action]).mail_enabled?
118 redirect_to :action => 'show', :id => @issue
137 redirect_to :action => 'show', :id => @issue
119 end
138 end
120 rescue ActiveRecord::StaleObjectError
139 rescue ActiveRecord::StaleObjectError
121 # Optimistic locking exception
140 # Optimistic locking exception
122 flash[:error] = l(:notice_locking_conflict)
141 flash[:error] = l(:notice_locking_conflict)
123 end
142 end
124 end
143 end
125 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
144 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
126 @activities = Enumeration::get_values('ACTI')
145 @activities = Enumeration::get_values('ACTI')
127 end
146 end
128
147
129 def destroy
148 def destroy
130 @issue.destroy
149 @issue.destroy
131 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
150 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
132 end
151 end
133
152
134 def destroy_attachment
153 def destroy_attachment
135 a = @issue.attachments.find(params[:attachment_id])
154 a = @issue.attachments.find(params[:attachment_id])
136 a.destroy
155 a.destroy
137 journal = @issue.init_journal(self.logged_in_user)
156 journal = @issue.init_journal(self.logged_in_user)
138 journal.details << JournalDetail.new(:property => 'attachment',
157 journal.details << JournalDetail.new(:property => 'attachment',
139 :prop_key => a.id,
158 :prop_key => a.id,
140 :old_value => a.filename)
159 :old_value => a.filename)
141 journal.save
160 journal.save
142 redirect_to :action => 'show', :id => @issue
161 redirect_to :action => 'show', :id => @issue
143 end
162 end
144
163
145 private
164 private
146 def find_project
165 def find_project
147 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
166 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
148 @project = @issue.project
167 @project = @issue.project
149 @html_title = "#{@project.name} - #{@issue.tracker.name} ##{@issue.id}"
168 @html_title = "#{@project.name} - #{@issue.tracker.name} ##{@issue.id}"
150 rescue ActiveRecord::RecordNotFound
169 rescue ActiveRecord::RecordNotFound
151 render_404
170 render_404
152 end
171 end
172
173 # Retrieve query from session or build a new query
174 def retrieve_query
175 if params[:set_filter] or !session[:query] or session[:query].project_id
176 # Give it a name, required to be valid
177 @query = Query.new(:name => "_", :executed_by => logged_in_user)
178 if params[:fields] and params[:fields].is_a? Array
179 params[:fields].each do |field|
180 @query.add_filter(field, params[:operators][field], params[:values][field])
181 end
182 else
183 @query.available_filters.keys.each do |field|
184 @query.add_short_filter(field, params[field]) if params[field]
185 end
186 end
187 session[:query] = @query
188 else
189 @query = session[:query]
190 end
191 end
153 end
192 end
@@ -1,124 +1,124
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 Project < ActiveRecord::Base
18 class Project < ActiveRecord::Base
19 # Project statuses
19 # Project statuses
20 STATUS_ACTIVE = 1
20 STATUS_ACTIVE = 1
21 STATUS_ARCHIVED = 9
21 STATUS_ARCHIVED = 9
22
22
23 has_many :members, :dependent => :delete_all, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
23 has_many :members, :dependent => :delete_all, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
24 has_many :users, :through => :members
24 has_many :users, :through => :members
25 has_many :custom_values, :dependent => :delete_all, :as => :customized
25 has_many :custom_values, :dependent => :delete_all, :as => :customized
26 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
26 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
27 has_many :issue_changes, :through => :issues, :source => :journals
27 has_many :issue_changes, :through => :issues, :source => :journals
28 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
28 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
29 has_many :time_entries, :dependent => :delete_all
29 has_many :time_entries, :dependent => :delete_all
30 has_many :queries, :dependent => :delete_all
30 has_many :queries, :dependent => :delete_all
31 has_many :documents, :dependent => :destroy
31 has_many :documents, :dependent => :destroy
32 has_many :news, :dependent => :delete_all, :include => :author
32 has_many :news, :dependent => :delete_all, :include => :author
33 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
33 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
34 has_many :boards, :order => "position ASC"
34 has_many :boards, :order => "position ASC"
35 has_one :repository, :dependent => :destroy
35 has_one :repository, :dependent => :destroy
36 has_one :wiki, :dependent => :destroy
36 has_one :wiki, :dependent => :destroy
37 has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
37 has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
38 acts_as_tree :order => "name", :counter_cache => true
38 acts_as_tree :order => "name", :counter_cache => true
39
39
40 attr_protected :status
40 attr_protected :status
41
41
42 validates_presence_of :name, :description, :identifier
42 validates_presence_of :name, :description, :identifier
43 validates_uniqueness_of :name, :identifier
43 validates_uniqueness_of :name, :identifier
44 validates_associated :custom_values, :on => :update
44 validates_associated :custom_values, :on => :update
45 validates_associated :repository, :wiki
45 validates_associated :repository, :wiki
46 validates_length_of :name, :maximum => 30
46 validates_length_of :name, :maximum => 30
47 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
47 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
48 validates_length_of :description, :maximum => 255
48 validates_length_of :description, :maximum => 255
49 validates_length_of :identifier, :in => 3..12
49 validates_length_of :identifier, :in => 3..12
50 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
50 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
51
51
52 def identifier=(identifier)
52 def identifier=(identifier)
53 super unless identifier_frozen?
53 super unless identifier_frozen?
54 end
54 end
55
55
56 def identifier_frozen?
56 def identifier_frozen?
57 errors[:identifier].nil? && !(new_record? || identifier.blank?)
57 errors[:identifier].nil? && !(new_record? || identifier.blank?)
58 end
58 end
59
59
60 def issues_with_subprojects(include_subprojects=false)
60 def issues_with_subprojects(include_subprojects=false)
61 conditions = nil
61 conditions = nil
62 if include_subprojects && !active_children.empty?
62 if include_subprojects && !active_children.empty?
63 ids = [id] + active_children.collect {|c| c.id}
63 ids = [id] + active_children.collect {|c| c.id}
64 conditions = ["#{Issue.table_name}.project_id IN (#{ids.join(',')})"]
64 conditions = ["#{Issue.table_name}.project_id IN (#{ids.join(',')})"]
65 end
65 end
66 conditions ||= ["#{Issue.table_name}.project_id = ?", id]
66 conditions ||= ["#{Issue.table_name}.project_id = ?", id]
67 Issue.with_scope :find => { :conditions => conditions } do
67 Issue.with_scope :find => { :conditions => conditions } do
68 yield
68 yield
69 end
69 end
70 end
70 end
71
71
72 # returns latest created projects
72 # returns latest created projects
73 # non public projects will be returned only if user is a member of those
73 # non public projects will be returned only if user is a member of those
74 def self.latest(user=nil, count=5)
74 def self.latest(user=nil, count=5)
75 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
75 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
76 end
76 end
77
77
78 def self.visible_by(user=nil)
78 def self.visible_by(user=nil)
79 if user && user.admin?
79 if user && user.admin?
80 return ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
80 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
81 elsif user && user.memberships.any?
81 elsif user && user.memberships.any?
82 return ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = ? or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))", true]
82 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
83 else
83 else
84 return ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = ?", true]
84 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
85 end
85 end
86 end
86 end
87
87
88 def active?
88 def active?
89 self.status == STATUS_ACTIVE
89 self.status == STATUS_ACTIVE
90 end
90 end
91
91
92 def archive
92 def archive
93 # Archive subprojects if any
93 # Archive subprojects if any
94 children.each do |subproject|
94 children.each do |subproject|
95 subproject.archive
95 subproject.archive
96 end
96 end
97 update_attribute :status, STATUS_ARCHIVED
97 update_attribute :status, STATUS_ARCHIVED
98 end
98 end
99
99
100 def unarchive
100 def unarchive
101 return false if parent && !parent.active?
101 return false if parent && !parent.active?
102 update_attribute :status, STATUS_ACTIVE
102 update_attribute :status, STATUS_ACTIVE
103 end
103 end
104
104
105 def active_children
105 def active_children
106 children.select {|child| child.active?}
106 children.select {|child| child.active?}
107 end
107 end
108
108
109 # Returns an array of all custom fields enabled for project issues
109 # Returns an array of all custom fields enabled for project issues
110 # (explictly associated custom fields and custom fields enabled for all projects)
110 # (explictly associated custom fields and custom fields enabled for all projects)
111 def custom_fields_for_issues(tracker)
111 def custom_fields_for_issues(tracker)
112 all_custom_fields.select {|c| tracker.custom_fields.include? c }
112 all_custom_fields.select {|c| tracker.custom_fields.include? c }
113 end
113 end
114
114
115 def all_custom_fields
115 def all_custom_fields
116 @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
116 @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
117 end
117 end
118
118
119 protected
119 protected
120 def validate
120 def validate
121 errors.add(parent_id, " must be a root project") if parent and parent.parent
121 errors.add(parent_id, " must be a root project") if parent and parent.parent
122 errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
122 errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
123 end
123 end
124 end
124 end
@@ -1,245 +1,254
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 class Query < ActiveRecord::Base
18 class Query < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 belongs_to :user
20 belongs_to :user
21 serialize :filters
21 serialize :filters
22
22
23 attr_protected :project, :user
23 attr_protected :project, :user
24 attr_accessor :executed_by
24 attr_accessor :executed_by
25
25
26 validates_presence_of :name, :on => :save
26 validates_presence_of :name, :on => :save
27 validates_length_of :name, :maximum => 255
27 validates_length_of :name, :maximum => 255
28
28
29 @@operators = { "=" => :label_equals,
29 @@operators = { "=" => :label_equals,
30 "!" => :label_not_equals,
30 "!" => :label_not_equals,
31 "o" => :label_open_issues,
31 "o" => :label_open_issues,
32 "c" => :label_closed_issues,
32 "c" => :label_closed_issues,
33 "!*" => :label_none,
33 "!*" => :label_none,
34 "*" => :label_all,
34 "*" => :label_all,
35 "<t+" => :label_in_less_than,
35 "<t+" => :label_in_less_than,
36 ">t+" => :label_in_more_than,
36 ">t+" => :label_in_more_than,
37 "t+" => :label_in,
37 "t+" => :label_in,
38 "t" => :label_today,
38 "t" => :label_today,
39 ">t-" => :label_less_than_ago,
39 ">t-" => :label_less_than_ago,
40 "<t-" => :label_more_than_ago,
40 "<t-" => :label_more_than_ago,
41 "t-" => :label_ago,
41 "t-" => :label_ago,
42 "~" => :label_contains,
42 "~" => :label_contains,
43 "!~" => :label_not_contains }
43 "!~" => :label_not_contains }
44
44
45 cattr_reader :operators
45 cattr_reader :operators
46
46
47 @@operators_by_filter_type = { :list => [ "=", "!" ],
47 @@operators_by_filter_type = { :list => [ "=", "!" ],
48 :list_status => [ "o", "=", "!", "c", "*" ],
48 :list_status => [ "o", "=", "!", "c", "*" ],
49 :list_optional => [ "=", "!", "!*", "*" ],
49 :list_optional => [ "=", "!", "!*", "*" ],
50 :list_one_or_more => [ "*", "=" ],
50 :list_one_or_more => [ "*", "=" ],
51 :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
51 :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
52 :date_past => [ ">t-", "<t-", "t-", "t" ],
52 :date_past => [ ">t-", "<t-", "t-", "t" ],
53 :string => [ "=", "~", "!", "!~" ],
53 :string => [ "=", "~", "!", "!~" ],
54 :text => [ "~", "!~" ] }
54 :text => [ "~", "!~" ] }
55
55
56 cattr_reader :operators_by_filter_type
56 cattr_reader :operators_by_filter_type
57
57
58 def initialize(attributes = nil)
58 def initialize(attributes = nil)
59 super attributes
59 super attributes
60 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
60 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
61 end
61 end
62
62
63 def executed_by=(user)
63 def executed_by=(user)
64 @executed_by = user
64 @executed_by = user
65 set_language_if_valid(user.language) if user
65 set_language_if_valid(user.language) if user
66 end
66 end
67
67
68 def validate
68 def validate
69 filters.each_key do |field|
69 filters.each_key do |field|
70 errors.add label_for(field), :activerecord_error_blank unless
70 errors.add label_for(field), :activerecord_error_blank unless
71 # filter requires one or more values
71 # filter requires one or more values
72 (values_for(field) and !values_for(field).first.empty?) or
72 (values_for(field) and !values_for(field).first.empty?) or
73 # filter doesn't require any value
73 # filter doesn't require any value
74 ["o", "c", "!*", "*", "t"].include? operator_for(field)
74 ["o", "c", "!*", "*", "t"].include? operator_for(field)
75 end if filters
75 end if filters
76 end
76 end
77
77
78 def editable_by?(user)
78 def editable_by?(user)
79 return false unless user
79 return false unless user
80 return true if !is_public && self.user_id == user.id
80 return true if !is_public && self.user_id == user.id
81 is_public && user.allowed_to?(:manage_pulic_queries, project)
81 is_public && user.allowed_to?(:manage_pulic_queries, project)
82 end
82 end
83
83
84 def available_filters
84 def available_filters
85 return @available_filters if @available_filters
85 return @available_filters if @available_filters
86 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
86 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
87 "tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
87 "tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
88 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } },
88 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } },
89 "subject" => { :type => :text, :order => 8 },
89 "subject" => { :type => :text, :order => 8 },
90 "created_on" => { :type => :date_past, :order => 9 },
90 "created_on" => { :type => :date_past, :order => 9 },
91 "updated_on" => { :type => :date_past, :order => 10 },
91 "updated_on" => { :type => :date_past, :order => 10 },
92 "start_date" => { :type => :date, :order => 11 },
92 "start_date" => { :type => :date, :order => 11 },
93 "due_date" => { :type => :date, :order => 12 } }
93 "due_date" => { :type => :date, :order => 12 } }
94 unless project.nil?
94
95 # project specific filters
96 user_values = []
95 user_values = []
96 if project
97 user_values += project.users.collect{|s| [s.name, s.id.to_s] }
98 elsif executed_by
97 user_values << ["<< #{l(:label_me)} >>", "me"] if executed_by
99 user_values << ["<< #{l(:label_me)} >>", "me"] if executed_by
98 user_values += @project.users.collect{|s| [s.name, s.id.to_s] }
100 # members of the user's projects
101 user_values += executed_by.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
102 end
103 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
104 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
99
105
100 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values }
106 if project
101 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values }
107 # project specific filters
102 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
108 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
103 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
109 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
104 unless @project.active_children.empty?
110 unless @project.active_children.empty?
105 @available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
111 @available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
106 end
112 end
107 @project.all_custom_fields.select(&:is_filter?).each do |field|
113 @project.all_custom_fields.select(&:is_filter?).each do |field|
108 case field.field_format
114 case field.field_format
109 when "string", "int"
115 when "string", "int"
110 options = { :type => :string, :order => 20 }
116 options = { :type => :string, :order => 20 }
111 when "text"
117 when "text"
112 options = { :type => :text, :order => 20 }
118 options = { :type => :text, :order => 20 }
113 when "list"
119 when "list"
114 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
120 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
115 when "date"
121 when "date"
116 options = { :type => :date, :order => 20 }
122 options = { :type => :date, :order => 20 }
117 when "bool"
123 when "bool"
118 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
124 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
119 end
125 end
120 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
126 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
121 end
127 end
122 # remove category filter if no category defined
128 # remove category filter if no category defined
123 @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
129 @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
124 end
130 end
125 @available_filters
131 @available_filters
126 end
132 end
127
133
128 def add_filter(field, operator, values)
134 def add_filter(field, operator, values)
129 # values must be an array
135 # values must be an array
130 return unless values and values.is_a? Array # and !values.first.empty?
136 return unless values and values.is_a? Array # and !values.first.empty?
131 # check if field is defined as an available filter
137 # check if field is defined as an available filter
132 if available_filters.has_key? field
138 if available_filters.has_key? field
133 filter_options = available_filters[field]
139 filter_options = available_filters[field]
134 # check if operator is allowed for that filter
140 # check if operator is allowed for that filter
135 #if @@operators_by_filter_type[filter_options[:type]].include? operator
141 #if @@operators_by_filter_type[filter_options[:type]].include? operator
136 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
142 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
137 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
143 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
138 #end
144 #end
139 filters[field] = {:operator => operator, :values => values }
145 filters[field] = {:operator => operator, :values => values }
140 end
146 end
141 end
147 end
142
148
143 def add_short_filter(field, expression)
149 def add_short_filter(field, expression)
144 return unless expression
150 return unless expression
145 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
151 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
146 add_filter field, (parms[0] || "="), [parms[1] || ""]
152 add_filter field, (parms[0] || "="), [parms[1] || ""]
147 end
153 end
148
154
149 def has_filter?(field)
155 def has_filter?(field)
150 filters and filters[field]
156 filters and filters[field]
151 end
157 end
152
158
153 def operator_for(field)
159 def operator_for(field)
154 has_filter?(field) ? filters[field][:operator] : nil
160 has_filter?(field) ? filters[field][:operator] : nil
155 end
161 end
156
162
157 def values_for(field)
163 def values_for(field)
158 has_filter?(field) ? filters[field][:values] : nil
164 has_filter?(field) ? filters[field][:values] : nil
159 end
165 end
160
166
161 def label_for(field)
167 def label_for(field)
162 label = @available_filters[field][:name] if @available_filters.has_key?(field)
168 label = @available_filters[field][:name] if @available_filters.has_key?(field)
163 label ||= field.gsub(/\_id$/, "")
169 label ||= field.gsub(/\_id$/, "")
164 end
170 end
165
171
166 def statement
172 def statement
167 # project/subprojects clause
173 # project/subprojects clause
168 clause = ''
174 clause = ''
169 if has_filter?("subproject_id")
175 if project && has_filter?("subproject_id")
170 subproject_ids = []
176 subproject_ids = []
171 if operator_for("subproject_id") == "="
177 if operator_for("subproject_id") == "="
172 subproject_ids = values_for("subproject_id").each(&:to_i)
178 subproject_ids = values_for("subproject_id").each(&:to_i)
173 else
179 else
174 subproject_ids = project.active_children.collect{|p| p.id}
180 subproject_ids = project.active_children.collect{|p| p.id}
175 end
181 end
176 clause << "#{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project
182 clause << "#{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project
183 elsif project
184 clause << "#{Issue.table_name}.project_id=%d" % project.id
177 else
185 else
178 clause << "#{Issue.table_name}.project_id=%d" % project.id if project
186 clause << Project.visible_by(executed_by)
179 end
187 end
180
188
181 # filters clauses
189 # filters clauses
182 filters_clauses = []
190 filters_clauses = []
183 filters.each_key do |field|
191 filters.each_key do |field|
184 next if field == "subproject_id"
192 next if field == "subproject_id"
185 v = values_for(field).clone
193 v = values_for(field).clone
186 next unless v and !v.empty?
194 next unless v and !v.empty?
187
195
188 sql = ''
196 sql = ''
189 if field =~ /^cf_(\d+)$/
197 if field =~ /^cf_(\d+)$/
190 # custom field
198 # custom field
191 db_table = CustomValue.table_name
199 db_table = CustomValue.table_name
192 db_field = 'value'
200 db_field = 'value'
193 sql << "#{Issue.table_name}.id IN (SELECT #{db_table}.customized_id FROM #{db_table} where #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} AND "
201 sql << "#{Issue.table_name}.id IN (SELECT #{db_table}.customized_id FROM #{db_table} where #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} AND "
194 else
202 else
195 # regular field
203 # regular field
196 db_table = Issue.table_name
204 db_table = Issue.table_name
197 db_field = field
205 db_field = field
198 sql << '('
206 sql << '('
199 end
207 end
200
208
201 # "me" value subsitution
209 # "me" value subsitution
202 if %w(assigned_to_id author_id).include?(field)
210 if %w(assigned_to_id author_id).include?(field)
203 v.push(executed_by ? executed_by.id.to_s : "0") if v.delete("me")
211 v.push(executed_by ? executed_by.id.to_s : "0") if v.delete("me")
204 end
212 end
205
213
206 case operator_for field
214 case operator_for field
207 when "="
215 when "="
208 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
216 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
209 when "!"
217 when "!"
210 sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
218 sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
211 when "!*"
219 when "!*"
212 sql = sql + "#{db_table}.#{db_field} IS NULL"
220 sql = sql + "#{db_table}.#{db_field} IS NULL"
213 when "*"
221 when "*"
214 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
222 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
215 when "o"
223 when "o"
216 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
224 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
217 when "c"
225 when "c"
218 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
226 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
219 when ">t-"
227 when ">t-"
220 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
228 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
221 when "<t-"
229 when "<t-"
222 sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
230 sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
223 when "t-"
231 when "t-"
224 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
232 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
225 when ">t+"
233 when ">t+"
226 sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
234 sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
227 when "<t+"
235 when "<t+"
228 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
236 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
229 when "t+"
237 when "t+"
230 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
238 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
231 when "t"
239 when "t"
232 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
240 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
233 when "~"
241 when "~"
234 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
242 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
235 when "!~"
243 when "!~"
236 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
244 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
237 end
245 end
238 sql << ')'
246 sql << ')'
239 filters_clauses << sql
247 filters_clauses << sql
240 end if filters and valid?
248 end if filters and valid?
241
249
242 clause << (' AND ' + filters_clauses.join(' AND ')) unless filters_clauses.empty?
250 clause << ' AND ' unless clause.empty?
251 clause << filters_clauses.join(' AND ') unless filters_clauses.empty?
243 clause
252 clause
244 end
253 end
245 end
254 end
@@ -1,10 +1,10
1 <h3><%=l(:label_assigned_to_me_issues)%></h3>
1 <h3><%=l(:label_assigned_to_me_issues)%></h3>
2 <% assigned_issues = Issue.find(:all,
2 <% assigned_issues = Issue.find(:all,
3 :conditions => ["assigned_to_id=? AND #{IssueStatus.table_name}.is_closed=? AND #{Project.table_name}.status=#{Project::STATUS_ACTIVE}", user.id, false],
3 :conditions => ["assigned_to_id=? AND #{IssueStatus.table_name}.is_closed=? AND #{Project.table_name}.status=#{Project::STATUS_ACTIVE}", user.id, false],
4 :limit => 10,
4 :limit => 10,
5 :include => [ :status, :project, :tracker ],
5 :include => [ :status, :project, :tracker ],
6 :order => "#{Issue.table_name}.updated_on DESC") %>
6 :order => "#{Issue.table_name}.priority_id DESC, #{Issue.table_name}.updated_on DESC") %>
7 <%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %>
7 <%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %>
8 <% if assigned_issues.length > 0 %>
8 <% if assigned_issues.length > 0 %>
9 <p><%=lwr(:label_last_updates, assigned_issues.length)%></p>
9 <p class="small"><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => 'me' %></p>
10 <% end %>
10 <% end %>
@@ -1,10 +1,10
1 <h3><%=l(:label_reported_issues)%></h3>
1 <h3><%=l(:label_reported_issues)%></h3>
2 <% reported_issues = Issue.find(:all,
2 <% reported_issues = Issue.find(:all,
3 :conditions => ["author_id=? AND #{Project.table_name}.status=#{Project::STATUS_ACTIVE}", user.id],
3 :conditions => ["author_id=? AND #{Project.table_name}.status=#{Project::STATUS_ACTIVE}", user.id],
4 :limit => 10,
4 :limit => 10,
5 :include => [ :status, :project, :tracker ],
5 :include => [ :status, :project, :tracker ],
6 :order => "#{Issue.table_name}.updated_on DESC") %>
6 :order => "#{Issue.table_name}.updated_on DESC") %>
7 <%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %>
7 <%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %>
8 <% if reported_issues.length > 0 %>
8 <% if reported_issues.length > 0 %>
9 <p><%=lwr(:label_last_updates, reported_issues.length)%></p>
9 <p class="small"><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :set_filter => 1, :author_id => 'me' %></p>
10 <% end %>
10 <% end %>
@@ -1,89 +1,90
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 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'my_controller'
19 require 'my_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class MyController; def rescue_action(e) raise e end; end
22 class MyController; def rescue_action(e) raise e end; end
23
23
24 class MyControllerTest < Test::Unit::TestCase
24 class MyControllerTest < Test::Unit::TestCase
25 fixtures :users
26
25 def setup
27 def setup
26 @controller = MyController.new
28 @controller = MyController.new
27 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
28 @request.session[:user_id] = 2
30 @request.session[:user_id] = 2
29 @response = ActionController::TestResponse.new
31 @response = ActionController::TestResponse.new
30 end
32 end
31
33
32 def test_index
34 def test_index
33 get :index
35 get :index
34 assert_response :success
36 assert_response :success
35 assert_template 'page'
37 assert_template 'page'
36 end
38 end
37
39
38 def test_page
40 def test_page
39 get :page
41 get :page
40 assert_response :success
42 assert_response :success
41 assert_template 'page'
43 assert_template 'page'
42 end
44 end
43
45
44 def test_get_account
46 def test_get_account
45 get :account
47 get :account
46 assert_response :success
48 assert_response :success
47 assert_template 'account'
49 assert_template 'account'
48 assert_equal User.find(2), assigns(:user)
50 assert_equal User.find(2), assigns(:user)
49 end
51 end
50
52
51 def test_update_account
53 def test_update_account
52 post :account, :user => {:firstname => "Joe", :login => "root", :admin => 1}
54 post :account, :user => {:firstname => "Joe", :login => "root", :admin => 1}
53 assert_response :success
55 assert_redirected_to 'my/account'
54 assert_template 'account'
55 user = User.find(2)
56 user = User.find(2)
56 assert_equal user, assigns(:user)
57 assert_equal user, assigns(:user)
57 assert_equal "Joe", user.firstname
58 assert_equal "Joe", user.firstname
58 assert_equal "jsmith", user.login
59 assert_equal "jsmith", user.login
59 assert !user.admin?
60 assert !user.admin?
60 end
61 end
61
62
62 def test_change_password
63 def test_change_password
63 get :account
64 get :account
64 assert_response :success
65 assert_response :success
65 assert_template 'account'
66 assert_template 'account'
66
67
67 # non matching password confirmation
68 # non matching password confirmation
68 post :change_password, :password => 'jsmith',
69 post :change_password, :password => 'jsmith',
69 :new_password => 'hello',
70 :new_password => 'hello',
70 :new_password_confirmation => 'hello2'
71 :new_password_confirmation => 'hello2'
71 assert_response :success
72 assert_response :success
72 assert_template 'account'
73 assert_template 'account'
73 assert_tag :tag => "div", :attributes => { :class => "errorExplanation" }
74 assert_tag :tag => "div", :attributes => { :class => "errorExplanation" }
74
75
75 # wrong password
76 # wrong password
76 post :change_password, :password => 'wrongpassword',
77 post :change_password, :password => 'wrongpassword',
77 :new_password => 'hello',
78 :new_password => 'hello',
78 :new_password_confirmation => 'hello'
79 :new_password_confirmation => 'hello'
79 assert_redirected_to 'my/account'
80 assert_redirected_to 'my/account'
80 assert_equal 'Wrong password', flash[:error]
81 assert_equal 'Wrong password', flash[:error]
81
82
82 # good password
83 # good password
83 post :change_password, :password => 'jsmith',
84 post :change_password, :password => 'jsmith',
84 :new_password => 'hello',
85 :new_password => 'hello',
85 :new_password_confirmation => 'hello'
86 :new_password_confirmation => 'hello'
86 assert_redirected_to 'my/account'
87 assert_redirected_to 'my/account'
87 assert User.try_to_login('jsmith', 'hello')
88 assert User.try_to_login('jsmith', 'hello')
88 end
89 end
89 end
90 end
General Comments 0
You need to be logged in to leave comments. Login now