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