##// END OF EJS Templates
Jump to the current tab when using the project quick-jump combo (#2364)....
Jean-Philippe Lang -
r2208:2355324d73e2
parent child
Show More
@@ -1,293 +1,298
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 ProjectsController < ApplicationController
18 class ProjectsController < ApplicationController
19 menu_item :overview
19 menu_item :overview
20 menu_item :activity, :only => :activity
20 menu_item :activity, :only => :activity
21 menu_item :roadmap, :only => :roadmap
21 menu_item :roadmap, :only => :roadmap
22 menu_item :files, :only => [:list_files, :add_file]
22 menu_item :files, :only => [:list_files, :add_file]
23 menu_item :settings, :only => :settings
23 menu_item :settings, :only => :settings
24 menu_item :issues, :only => [:changelog]
24 menu_item :issues, :only => [:changelog]
25
25
26 before_filter :find_project, :except => [ :index, :list, :add, :activity ]
26 before_filter :find_project, :except => [ :index, :list, :add, :activity ]
27 before_filter :find_optional_project, :only => :activity
27 before_filter :find_optional_project, :only => :activity
28 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
28 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
29 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
29 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
30 accept_key_auth :activity
30 accept_key_auth :activity
31
31
32 helper :sort
32 helper :sort
33 include SortHelper
33 include SortHelper
34 helper :custom_fields
34 helper :custom_fields
35 include CustomFieldsHelper
35 include CustomFieldsHelper
36 helper :ifpdf
36 helper :ifpdf
37 include IfpdfHelper
37 include IfpdfHelper
38 helper :issues
38 helper :issues
39 helper IssuesHelper
39 helper IssuesHelper
40 helper :queries
40 helper :queries
41 include QueriesHelper
41 include QueriesHelper
42 helper :repositories
42 helper :repositories
43 include RepositoriesHelper
43 include RepositoriesHelper
44 include ProjectsHelper
44 include ProjectsHelper
45
45
46 # Lists visible projects
46 # Lists visible projects
47 def index
47 def index
48 projects = Project.find :all,
48 projects = Project.find :all,
49 :conditions => Project.visible_by(User.current),
49 :conditions => Project.visible_by(User.current),
50 :include => :parent
50 :include => :parent
51 respond_to do |format|
51 respond_to do |format|
52 format.html {
52 format.html {
53 @project_tree = projects.group_by {|p| p.parent || p}
53 @project_tree = projects.group_by {|p| p.parent || p}
54 @project_tree.keys.each {|p| @project_tree[p] -= [p]}
54 @project_tree.keys.each {|p| @project_tree[p] -= [p]}
55 }
55 }
56 format.atom {
56 format.atom {
57 render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
57 render_feed(projects.sort_by(&:created_on).reverse.slice(0, Setting.feeds_limit.to_i),
58 :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
58 :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
59 }
59 }
60 end
60 end
61 end
61 end
62
62
63 # Add a new project
63 # Add a new project
64 def add
64 def add
65 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
65 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
66 @trackers = Tracker.all
66 @trackers = Tracker.all
67 @root_projects = Project.find(:all,
67 @root_projects = Project.find(:all,
68 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
68 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
69 :order => 'name')
69 :order => 'name')
70 @project = Project.new(params[:project])
70 @project = Project.new(params[:project])
71 if request.get?
71 if request.get?
72 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
72 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
73 @project.trackers = Tracker.all
73 @project.trackers = Tracker.all
74 @project.is_public = Setting.default_projects_public?
74 @project.is_public = Setting.default_projects_public?
75 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
75 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
76 else
76 else
77 @project.enabled_module_names = params[:enabled_modules]
77 @project.enabled_module_names = params[:enabled_modules]
78 if @project.save
78 if @project.save
79 flash[:notice] = l(:notice_successful_create)
79 flash[:notice] = l(:notice_successful_create)
80 redirect_to :controller => 'admin', :action => 'projects'
80 redirect_to :controller => 'admin', :action => 'projects'
81 end
81 end
82 end
82 end
83 end
83 end
84
84
85 # Show @project
85 # Show @project
86 def show
86 def show
87 if params[:jump]
88 # try to redirect to the requested menu item
89 redirect_to_project_menu_item(@project, params[:jump]) && return
90 end
91
87 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
92 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
88 @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
93 @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
89 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
94 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
90 @trackers = @project.rolled_up_trackers
95 @trackers = @project.rolled_up_trackers
91
96
92 cond = @project.project_condition(Setting.display_subprojects_issues?)
97 cond = @project.project_condition(Setting.display_subprojects_issues?)
93 Issue.visible_by(User.current) do
98 Issue.visible_by(User.current) do
94 @open_issues_by_tracker = Issue.count(:group => :tracker,
99 @open_issues_by_tracker = Issue.count(:group => :tracker,
95 :include => [:project, :status, :tracker],
100 :include => [:project, :status, :tracker],
96 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
101 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
97 @total_issues_by_tracker = Issue.count(:group => :tracker,
102 @total_issues_by_tracker = Issue.count(:group => :tracker,
98 :include => [:project, :status, :tracker],
103 :include => [:project, :status, :tracker],
99 :conditions => cond)
104 :conditions => cond)
100 end
105 end
101 TimeEntry.visible_by(User.current) do
106 TimeEntry.visible_by(User.current) do
102 @total_hours = TimeEntry.sum(:hours,
107 @total_hours = TimeEntry.sum(:hours,
103 :include => :project,
108 :include => :project,
104 :conditions => cond).to_f
109 :conditions => cond).to_f
105 end
110 end
106 @key = User.current.rss_key
111 @key = User.current.rss_key
107 end
112 end
108
113
109 def settings
114 def settings
110 @root_projects = Project.find(:all,
115 @root_projects = Project.find(:all,
111 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
116 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
112 :order => 'name')
117 :order => 'name')
113 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
118 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
114 @issue_category ||= IssueCategory.new
119 @issue_category ||= IssueCategory.new
115 @member ||= @project.members.new
120 @member ||= @project.members.new
116 @trackers = Tracker.all
121 @trackers = Tracker.all
117 @repository ||= @project.repository
122 @repository ||= @project.repository
118 @wiki ||= @project.wiki
123 @wiki ||= @project.wiki
119 end
124 end
120
125
121 # Edit @project
126 # Edit @project
122 def edit
127 def edit
123 if request.post?
128 if request.post?
124 @project.attributes = params[:project]
129 @project.attributes = params[:project]
125 if @project.save
130 if @project.save
126 flash[:notice] = l(:notice_successful_update)
131 flash[:notice] = l(:notice_successful_update)
127 redirect_to :action => 'settings', :id => @project
132 redirect_to :action => 'settings', :id => @project
128 else
133 else
129 settings
134 settings
130 render :action => 'settings'
135 render :action => 'settings'
131 end
136 end
132 end
137 end
133 end
138 end
134
139
135 def modules
140 def modules
136 @project.enabled_module_names = params[:enabled_modules]
141 @project.enabled_module_names = params[:enabled_modules]
137 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
142 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
138 end
143 end
139
144
140 def archive
145 def archive
141 @project.archive if request.post? && @project.active?
146 @project.archive if request.post? && @project.active?
142 redirect_to :controller => 'admin', :action => 'projects'
147 redirect_to :controller => 'admin', :action => 'projects'
143 end
148 end
144
149
145 def unarchive
150 def unarchive
146 @project.unarchive if request.post? && !@project.active?
151 @project.unarchive if request.post? && !@project.active?
147 redirect_to :controller => 'admin', :action => 'projects'
152 redirect_to :controller => 'admin', :action => 'projects'
148 end
153 end
149
154
150 # Delete @project
155 # Delete @project
151 def destroy
156 def destroy
152 @project_to_destroy = @project
157 @project_to_destroy = @project
153 if request.post? and params[:confirm]
158 if request.post? and params[:confirm]
154 @project_to_destroy.destroy
159 @project_to_destroy.destroy
155 redirect_to :controller => 'admin', :action => 'projects'
160 redirect_to :controller => 'admin', :action => 'projects'
156 end
161 end
157 # hide project in layout
162 # hide project in layout
158 @project = nil
163 @project = nil
159 end
164 end
160
165
161 # Add a new issue category to @project
166 # Add a new issue category to @project
162 def add_issue_category
167 def add_issue_category
163 @category = @project.issue_categories.build(params[:category])
168 @category = @project.issue_categories.build(params[:category])
164 if request.post? and @category.save
169 if request.post? and @category.save
165 respond_to do |format|
170 respond_to do |format|
166 format.html do
171 format.html do
167 flash[:notice] = l(:notice_successful_create)
172 flash[:notice] = l(:notice_successful_create)
168 redirect_to :action => 'settings', :tab => 'categories', :id => @project
173 redirect_to :action => 'settings', :tab => 'categories', :id => @project
169 end
174 end
170 format.js do
175 format.js do
171 # IE doesn't support the replace_html rjs method for select box options
176 # IE doesn't support the replace_html rjs method for select box options
172 render(:update) {|page| page.replace "issue_category_id",
177 render(:update) {|page| page.replace "issue_category_id",
173 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
178 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
174 }
179 }
175 end
180 end
176 end
181 end
177 end
182 end
178 end
183 end
179
184
180 # Add a new version to @project
185 # Add a new version to @project
181 def add_version
186 def add_version
182 @version = @project.versions.build(params[:version])
187 @version = @project.versions.build(params[:version])
183 if request.post? and @version.save
188 if request.post? and @version.save
184 flash[:notice] = l(:notice_successful_create)
189 flash[:notice] = l(:notice_successful_create)
185 redirect_to :action => 'settings', :tab => 'versions', :id => @project
190 redirect_to :action => 'settings', :tab => 'versions', :id => @project
186 end
191 end
187 end
192 end
188
193
189 def add_file
194 def add_file
190 if request.post?
195 if request.post?
191 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
196 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
192 attachments = attach_files(container, params[:attachments])
197 attachments = attach_files(container, params[:attachments])
193 if !attachments.empty? && Setting.notified_events.include?('file_added')
198 if !attachments.empty? && Setting.notified_events.include?('file_added')
194 Mailer.deliver_attachments_added(attachments)
199 Mailer.deliver_attachments_added(attachments)
195 end
200 end
196 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
201 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
197 return
202 return
198 end
203 end
199 @versions = @project.versions.sort
204 @versions = @project.versions.sort
200 end
205 end
201
206
202 def list_files
207 def list_files
203 sort_init 'filename', 'asc'
208 sort_init 'filename', 'asc'
204 sort_update 'filename' => "#{Attachment.table_name}.filename",
209 sort_update 'filename' => "#{Attachment.table_name}.filename",
205 'created_on' => "#{Attachment.table_name}.created_on",
210 'created_on' => "#{Attachment.table_name}.created_on",
206 'size' => "#{Attachment.table_name}.filesize",
211 'size' => "#{Attachment.table_name}.filesize",
207 'downloads' => "#{Attachment.table_name}.downloads"
212 'downloads' => "#{Attachment.table_name}.downloads"
208
213
209 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
214 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
210 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
215 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
211 render :layout => !request.xhr?
216 render :layout => !request.xhr?
212 end
217 end
213
218
214 # Show changelog for @project
219 # Show changelog for @project
215 def changelog
220 def changelog
216 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
221 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
217 retrieve_selected_tracker_ids(@trackers)
222 retrieve_selected_tracker_ids(@trackers)
218 @versions = @project.versions.sort
223 @versions = @project.versions.sort
219 end
224 end
220
225
221 def roadmap
226 def roadmap
222 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
227 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
223 retrieve_selected_tracker_ids(@trackers)
228 retrieve_selected_tracker_ids(@trackers)
224 @versions = @project.versions.sort
229 @versions = @project.versions.sort
225 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
230 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
226 end
231 end
227
232
228 def activity
233 def activity
229 @days = Setting.activity_days_default.to_i
234 @days = Setting.activity_days_default.to_i
230
235
231 if params[:from]
236 if params[:from]
232 begin; @date_to = params[:from].to_date + 1; rescue; end
237 begin; @date_to = params[:from].to_date + 1; rescue; end
233 end
238 end
234
239
235 @date_to ||= Date.today + 1
240 @date_to ||= Date.today + 1
236 @date_from = @date_to - @days
241 @date_from = @date_to - @days
237 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
242 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
238 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
243 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
239
244
240 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
245 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
241 :with_subprojects => @with_subprojects,
246 :with_subprojects => @with_subprojects,
242 :author => @author)
247 :author => @author)
243 @activity.scope_select {|t| !params["show_#{t}"].nil?}
248 @activity.scope_select {|t| !params["show_#{t}"].nil?}
244 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
249 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
245
250
246 events = @activity.events(@date_from, @date_to)
251 events = @activity.events(@date_from, @date_to)
247
252
248 respond_to do |format|
253 respond_to do |format|
249 format.html {
254 format.html {
250 @events_by_day = events.group_by(&:event_date)
255 @events_by_day = events.group_by(&:event_date)
251 render :layout => false if request.xhr?
256 render :layout => false if request.xhr?
252 }
257 }
253 format.atom {
258 format.atom {
254 title = l(:label_activity)
259 title = l(:label_activity)
255 if @author
260 if @author
256 title = @author.name
261 title = @author.name
257 elsif @activity.scope.size == 1
262 elsif @activity.scope.size == 1
258 title = l("label_#{@activity.scope.first.singularize}_plural")
263 title = l("label_#{@activity.scope.first.singularize}_plural")
259 end
264 end
260 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
265 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
261 }
266 }
262 end
267 end
263
268
264 rescue ActiveRecord::RecordNotFound
269 rescue ActiveRecord::RecordNotFound
265 render_404
270 render_404
266 end
271 end
267
272
268 private
273 private
269 # Find project of id params[:id]
274 # Find project of id params[:id]
270 # if not found, redirect to project list
275 # if not found, redirect to project list
271 # Used as a before_filter
276 # Used as a before_filter
272 def find_project
277 def find_project
273 @project = Project.find(params[:id])
278 @project = Project.find(params[:id])
274 rescue ActiveRecord::RecordNotFound
279 rescue ActiveRecord::RecordNotFound
275 render_404
280 render_404
276 end
281 end
277
282
278 def find_optional_project
283 def find_optional_project
279 return true unless params[:id]
284 return true unless params[:id]
280 @project = Project.find(params[:id])
285 @project = Project.find(params[:id])
281 authorize
286 authorize
282 rescue ActiveRecord::RecordNotFound
287 rescue ActiveRecord::RecordNotFound
283 render_404
288 render_404
284 end
289 end
285
290
286 def retrieve_selected_tracker_ids(selectable_trackers)
291 def retrieve_selected_tracker_ids(selectable_trackers)
287 if ids = params[:tracker_ids]
292 if ids = params[:tracker_ids]
288 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
293 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
289 else
294 else
290 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
295 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
291 end
296 end
292 end
297 end
293 end
298 end
@@ -1,12 +1,12
1 <% user_projects_by_root = User.current.projects.find(:all, :include => :parent).group_by(&:root) %>
1 <% user_projects_by_root = User.current.projects.find(:all, :include => :parent).group_by(&:root) %>
2 <select onchange="if (this.value != '') { window.location = this.value; }">
2 <select onchange="if (this.value != '') { window.location = this.value; }">
3 <option selected="selected"><%= l(:label_jump_to_a_project) %></option>
3 <option selected="selected"><%= l(:label_jump_to_a_project) %></option>
4 <option disabled="disabled">---</option>
4 <option disabled="disabled">---</option>
5 <% user_projects_by_root.keys.sort.each do |root| %>
5 <% user_projects_by_root.keys.sort.each do |root| %>
6 <%= content_tag('option', h(root.name), :value => url_for(:controller => 'projects', :action => 'show', :id => root)) %>
6 <%= content_tag('option', h(root.name), :value => url_for(:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item)) %>
7 <% user_projects_by_root[root].sort.each do |project| %>
7 <% user_projects_by_root[root].sort.each do |project| %>
8 <% next if project == root %>
8 <% next if project == root %>
9 <%= content_tag('option', ('&#187; ' + h(project.name)), :value => url_for(:controller => 'projects', :action => 'show', :id => project)) %>
9 <%= content_tag('option', ('&#187; ' + h(project.name)), :value => url_for(:controller => 'projects', :action => 'show', :id => project, :jump => current_menu_item)) %>
10 <% end %>
10 <% end %>
11 <% end %>
11 <% end %>
12 </select>
12 </select>
@@ -1,208 +1,219
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 'gloc'
18 require 'gloc'
19
19
20 module Redmine
20 module Redmine
21 module MenuManager
21 module MenuManager
22 module MenuController
22 module MenuController
23 def self.included(base)
23 def self.included(base)
24 base.extend(ClassMethods)
24 base.extend(ClassMethods)
25 end
25 end
26
26
27 module ClassMethods
27 module ClassMethods
28 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
28 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
29 mattr_accessor :menu_items
29 mattr_accessor :menu_items
30
30
31 # Set the menu item name for a controller or specific actions
31 # Set the menu item name for a controller or specific actions
32 # Examples:
32 # Examples:
33 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
33 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
34 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
34 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
35 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
35 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
36 #
36 #
37 # The default menu item name for a controller is controller_name by default
37 # The default menu item name for a controller is controller_name by default
38 # Eg. the default menu item name for ProjectsController is :projects
38 # Eg. the default menu item name for ProjectsController is :projects
39 def menu_item(id, options = {})
39 def menu_item(id, options = {})
40 if actions = options[:only]
40 if actions = options[:only]
41 actions = [] << actions unless actions.is_a?(Array)
41 actions = [] << actions unless actions.is_a?(Array)
42 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
42 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
43 else
43 else
44 menu_items[controller_name.to_sym][:default] = id
44 menu_items[controller_name.to_sym][:default] = id
45 end
45 end
46 end
46 end
47 end
47 end
48
48
49 def menu_items
49 def menu_items
50 self.class.menu_items
50 self.class.menu_items
51 end
51 end
52
52
53 # Returns the menu item name according to the current action
53 # Returns the menu item name according to the current action
54 def current_menu_item
54 def current_menu_item
55 menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
55 @current_menu_item ||= menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
56 menu_items[controller_name.to_sym][:default]
56 menu_items[controller_name.to_sym][:default]
57 end
57 end
58
59 # Redirects user to the menu item of the given project
60 # Returns false if user is not authorized
61 def redirect_to_project_menu_item(project, name)
62 item = Redmine::MenuManager.items(:project_menu).detect {|i| i.name.to_s == name.to_s}
63 if item && User.current.allowed_to?(item.url, project) && (item.condition.nil? || item.condition.call(project))
64 redirect_to({item.param => project}.merge(item.url))
65 return true
66 end
67 false
68 end
58 end
69 end
59
70
60 module MenuHelper
71 module MenuHelper
61 # Returns the current menu item name
72 # Returns the current menu item name
62 def current_menu_item
73 def current_menu_item
63 @controller.current_menu_item
74 @controller.current_menu_item
64 end
75 end
65
76
66 # Renders the application main menu
77 # Renders the application main menu
67 def render_main_menu(project)
78 def render_main_menu(project)
68 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
79 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
69 end
80 end
70
81
71 def render_menu(menu, project=nil)
82 def render_menu(menu, project=nil)
72 links = []
83 links = []
73 menu_items_for(menu, project) do |item, caption, url, selected|
84 menu_items_for(menu, project) do |item, caption, url, selected|
74 links << content_tag('li',
85 links << content_tag('li',
75 link_to(h(caption), url, item.html_options(:selected => selected)))
86 link_to(h(caption), url, item.html_options(:selected => selected)))
76 end
87 end
77 links.empty? ? nil : content_tag('ul', links.join("\n"))
88 links.empty? ? nil : content_tag('ul', links.join("\n"))
78 end
89 end
79
90
80 def menu_items_for(menu, project=nil)
91 def menu_items_for(menu, project=nil)
81 items = []
92 items = []
82 Redmine::MenuManager.allowed_items(menu, User.current, project).each do |item|
93 Redmine::MenuManager.allowed_items(menu, User.current, project).each do |item|
83 unless item.condition && !item.condition.call(project)
94 unless item.condition && !item.condition.call(project)
84 url = case item.url
95 url = case item.url
85 when Hash
96 when Hash
86 project.nil? ? item.url : {item.param => project}.merge(item.url)
97 project.nil? ? item.url : {item.param => project}.merge(item.url)
87 when Symbol
98 when Symbol
88 send(item.url)
99 send(item.url)
89 else
100 else
90 item.url
101 item.url
91 end
102 end
92 caption = item.caption(project)
103 caption = item.caption(project)
93 caption = l(caption) if caption.is_a?(Symbol)
104 caption = l(caption) if caption.is_a?(Symbol)
94 if block_given?
105 if block_given?
95 yield item, caption, url, (current_menu_item == item.name)
106 yield item, caption, url, (current_menu_item == item.name)
96 else
107 else
97 items << [item, caption, url, (current_menu_item == item.name)]
108 items << [item, caption, url, (current_menu_item == item.name)]
98 end
109 end
99 end
110 end
100 end
111 end
101 return block_given? ? nil : items
112 return block_given? ? nil : items
102 end
113 end
103 end
114 end
104
115
105 class << self
116 class << self
106 def map(menu_name)
117 def map(menu_name)
107 @items ||= {}
118 @items ||= {}
108 mapper = Mapper.new(menu_name.to_sym, @items)
119 mapper = Mapper.new(menu_name.to_sym, @items)
109 if block_given?
120 if block_given?
110 yield mapper
121 yield mapper
111 else
122 else
112 mapper
123 mapper
113 end
124 end
114 end
125 end
115
126
116 def items(menu_name)
127 def items(menu_name)
117 @items[menu_name.to_sym] || []
128 @items[menu_name.to_sym] || []
118 end
129 end
119
130
120 def allowed_items(menu_name, user, project)
131 def allowed_items(menu_name, user, project)
121 project ? items(menu_name).select {|item| user && user.allowed_to?(item.url, project)} : items(menu_name)
132 project ? items(menu_name).select {|item| user && user.allowed_to?(item.url, project)} : items(menu_name)
122 end
133 end
123 end
134 end
124
135
125 class Mapper
136 class Mapper
126 def initialize(menu, items)
137 def initialize(menu, items)
127 items[menu] ||= []
138 items[menu] ||= []
128 @menu = menu
139 @menu = menu
129 @menu_items = items[menu]
140 @menu_items = items[menu]
130 end
141 end
131
142
132 @@last_items_count = Hash.new {|h,k| h[k] = 0}
143 @@last_items_count = Hash.new {|h,k| h[k] = 0}
133
144
134 # Adds an item at the end of the menu. Available options:
145 # Adds an item at the end of the menu. Available options:
135 # * param: the parameter name that is used for the project id (default is :id)
146 # * param: the parameter name that is used for the project id (default is :id)
136 # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true
147 # * if: a Proc that is called before rendering the item, the item is displayed only if it returns true
137 # * caption that can be:
148 # * caption that can be:
138 # * a localized string Symbol
149 # * a localized string Symbol
139 # * a String
150 # * a String
140 # * a Proc that can take the project as argument
151 # * a Proc that can take the project as argument
141 # * before, after: specify where the menu item should be inserted (eg. :after => :activity)
152 # * before, after: specify where the menu item should be inserted (eg. :after => :activity)
142 # * last: menu item will stay at the end (eg. :last => true)
153 # * last: menu item will stay at the end (eg. :last => true)
143 # * html_options: a hash of html options that are passed to link_to
154 # * html_options: a hash of html options that are passed to link_to
144 def push(name, url, options={})
155 def push(name, url, options={})
145 options = options.dup
156 options = options.dup
146
157
147 # menu item position
158 # menu item position
148 if before = options.delete(:before)
159 if before = options.delete(:before)
149 position = @menu_items.collect(&:name).index(before)
160 position = @menu_items.collect(&:name).index(before)
150 elsif after = options.delete(:after)
161 elsif after = options.delete(:after)
151 position = @menu_items.collect(&:name).index(after)
162 position = @menu_items.collect(&:name).index(after)
152 position += 1 unless position.nil?
163 position += 1 unless position.nil?
153 elsif options.delete(:last)
164 elsif options.delete(:last)
154 position = @menu_items.size
165 position = @menu_items.size
155 @@last_items_count[@menu] += 1
166 @@last_items_count[@menu] += 1
156 end
167 end
157 # default position
168 # default position
158 position ||= @menu_items.size - @@last_items_count[@menu]
169 position ||= @menu_items.size - @@last_items_count[@menu]
159
170
160 @menu_items.insert(position, MenuItem.new(name, url, options))
171 @menu_items.insert(position, MenuItem.new(name, url, options))
161 end
172 end
162
173
163 # Removes a menu item
174 # Removes a menu item
164 def delete(name)
175 def delete(name)
165 @menu_items.delete_if {|i| i.name == name}
176 @menu_items.delete_if {|i| i.name == name}
166 end
177 end
167 end
178 end
168
179
169 class MenuItem
180 class MenuItem
170 include GLoc
181 include GLoc
171 attr_reader :name, :url, :param, :condition
182 attr_reader :name, :url, :param, :condition
172
183
173 def initialize(name, url, options)
184 def initialize(name, url, options)
174 raise "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
185 raise "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
175 raise "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
186 raise "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
176 @name = name
187 @name = name
177 @url = url
188 @url = url
178 @condition = options[:if]
189 @condition = options[:if]
179 @param = options[:param] || :id
190 @param = options[:param] || :id
180 @caption = options[:caption]
191 @caption = options[:caption]
181 @html_options = options[:html] || {}
192 @html_options = options[:html] || {}
182 # Adds a unique class to each menu item based on its name
193 # Adds a unique class to each menu item based on its name
183 @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
194 @html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
184 end
195 end
185
196
186 def caption(project=nil)
197 def caption(project=nil)
187 if @caption.is_a?(Proc)
198 if @caption.is_a?(Proc)
188 c = @caption.call(project).to_s
199 c = @caption.call(project).to_s
189 c = @name.to_s.humanize if c.blank?
200 c = @name.to_s.humanize if c.blank?
190 c
201 c
191 else
202 else
192 # check if localized string exists on first render (after GLoc strings are loaded)
203 # check if localized string exists on first render (after GLoc strings are loaded)
193 @caption_key ||= (@caption || (l_has_string?("label_#{@name}".to_sym) ? "label_#{@name}".to_sym : @name.to_s.humanize))
204 @caption_key ||= (@caption || (l_has_string?("label_#{@name}".to_sym) ? "label_#{@name}".to_sym : @name.to_s.humanize))
194 end
205 end
195 end
206 end
196
207
197 def html_options(options={})
208 def html_options(options={})
198 if options[:selected]
209 if options[:selected]
199 o = @html_options.dup
210 o = @html_options.dup
200 o[:class] += ' selected'
211 o[:class] += ' selected'
201 o
212 o
202 else
213 else
203 @html_options
214 @html_options
204 end
215 end
205 end
216 end
206 end
217 end
207 end
218 end
208 end
219 end
@@ -1,340 +1,357
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 'projects_controller'
19 require 'projects_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class ProjectsController; def rescue_action(e) raise e end; end
22 class ProjectsController; def rescue_action(e) raise e end; end
23
23
24 class ProjectsControllerTest < Test::Unit::TestCase
24 class ProjectsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
27 :attachments
27 :attachments
28
28
29 def setup
29 def setup
30 @controller = ProjectsController.new
30 @controller = ProjectsController.new
31 @request = ActionController::TestRequest.new
31 @request = ActionController::TestRequest.new
32 @response = ActionController::TestResponse.new
32 @response = ActionController::TestResponse.new
33 @request.session[:user_id] = nil
33 @request.session[:user_id] = nil
34 Setting.default_language = 'en'
34 Setting.default_language = 'en'
35 end
35 end
36
36
37 def test_index
37 def test_index
38 get :index
38 get :index
39 assert_response :success
39 assert_response :success
40 assert_template 'index'
40 assert_template 'index'
41 assert_not_nil assigns(:project_tree)
41 assert_not_nil assigns(:project_tree)
42 # Root project as hash key
42 # Root project as hash key
43 assert assigns(:project_tree).keys.include?(Project.find(1))
43 assert assigns(:project_tree).keys.include?(Project.find(1))
44 # Subproject in corresponding value
44 # Subproject in corresponding value
45 assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
45 assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3))
46 end
46 end
47
47
48 def test_index_atom
48 def test_index_atom
49 get :index, :format => 'atom'
49 get :index, :format => 'atom'
50 assert_response :success
50 assert_response :success
51 assert_template 'common/feed.atom.rxml'
51 assert_template 'common/feed.atom.rxml'
52 assert_select 'feed>title', :text => 'Redmine: Latest projects'
52 assert_select 'feed>title', :text => 'Redmine: Latest projects'
53 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
53 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
54 end
54 end
55
55
56 def test_show_by_id
56 def test_show_by_id
57 get :show, :id => 1
57 get :show, :id => 1
58 assert_response :success
58 assert_response :success
59 assert_template 'show'
59 assert_template 'show'
60 assert_not_nil assigns(:project)
60 assert_not_nil assigns(:project)
61 end
61 end
62
62
63 def test_show_by_identifier
63 def test_show_by_identifier
64 get :show, :id => 'ecookbook'
64 get :show, :id => 'ecookbook'
65 assert_response :success
65 assert_response :success
66 assert_template 'show'
66 assert_template 'show'
67 assert_not_nil assigns(:project)
67 assert_not_nil assigns(:project)
68 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
68 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
69 end
69 end
70
70
71 def test_private_subprojects_hidden
71 def test_private_subprojects_hidden
72 get :show, :id => 'ecookbook'
72 get :show, :id => 'ecookbook'
73 assert_response :success
73 assert_response :success
74 assert_template 'show'
74 assert_template 'show'
75 assert_no_tag :tag => 'a', :content => /Private child/
75 assert_no_tag :tag => 'a', :content => /Private child/
76 end
76 end
77
77
78 def test_private_subprojects_visible
78 def test_private_subprojects_visible
79 @request.session[:user_id] = 2 # manager who is a member of the private subproject
79 @request.session[:user_id] = 2 # manager who is a member of the private subproject
80 get :show, :id => 'ecookbook'
80 get :show, :id => 'ecookbook'
81 assert_response :success
81 assert_response :success
82 assert_template 'show'
82 assert_template 'show'
83 assert_tag :tag => 'a', :content => /Private child/
83 assert_tag :tag => 'a', :content => /Private child/
84 end
84 end
85
85
86 def test_settings
86 def test_settings
87 @request.session[:user_id] = 2 # manager
87 @request.session[:user_id] = 2 # manager
88 get :settings, :id => 1
88 get :settings, :id => 1
89 assert_response :success
89 assert_response :success
90 assert_template 'settings'
90 assert_template 'settings'
91 end
91 end
92
92
93 def test_edit
93 def test_edit
94 @request.session[:user_id] = 2 # manager
94 @request.session[:user_id] = 2 # manager
95 post :edit, :id => 1, :project => {:name => 'Test changed name',
95 post :edit, :id => 1, :project => {:name => 'Test changed name',
96 :issue_custom_field_ids => ['']}
96 :issue_custom_field_ids => ['']}
97 assert_redirected_to 'projects/settings/ecookbook'
97 assert_redirected_to 'projects/settings/ecookbook'
98 project = Project.find(1)
98 project = Project.find(1)
99 assert_equal 'Test changed name', project.name
99 assert_equal 'Test changed name', project.name
100 end
100 end
101
101
102 def test_get_destroy
102 def test_get_destroy
103 @request.session[:user_id] = 1 # admin
103 @request.session[:user_id] = 1 # admin
104 get :destroy, :id => 1
104 get :destroy, :id => 1
105 assert_response :success
105 assert_response :success
106 assert_template 'destroy'
106 assert_template 'destroy'
107 assert_not_nil Project.find_by_id(1)
107 assert_not_nil Project.find_by_id(1)
108 end
108 end
109
109
110 def test_post_destroy
110 def test_post_destroy
111 @request.session[:user_id] = 1 # admin
111 @request.session[:user_id] = 1 # admin
112 post :destroy, :id => 1, :confirm => 1
112 post :destroy, :id => 1, :confirm => 1
113 assert_redirected_to 'admin/projects'
113 assert_redirected_to 'admin/projects'
114 assert_nil Project.find_by_id(1)
114 assert_nil Project.find_by_id(1)
115 end
115 end
116
116
117 def test_add_file
117 def test_add_file
118 set_tmp_attachments_directory
118 set_tmp_attachments_directory
119 @request.session[:user_id] = 2
119 @request.session[:user_id] = 2
120 Setting.notified_events = ['file_added']
120 Setting.notified_events = ['file_added']
121 ActionMailer::Base.deliveries.clear
121 ActionMailer::Base.deliveries.clear
122
122
123 assert_difference 'Attachment.count' do
123 assert_difference 'Attachment.count' do
124 post :add_file, :id => 1, :version_id => '',
124 post :add_file, :id => 1, :version_id => '',
125 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
125 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
126 end
126 end
127 assert_redirected_to 'projects/list_files/ecookbook'
127 assert_redirected_to 'projects/list_files/ecookbook'
128 a = Attachment.find(:first, :order => 'created_on DESC')
128 a = Attachment.find(:first, :order => 'created_on DESC')
129 assert_equal 'testfile.txt', a.filename
129 assert_equal 'testfile.txt', a.filename
130 assert_equal Project.find(1), a.container
130 assert_equal Project.find(1), a.container
131
131
132 mail = ActionMailer::Base.deliveries.last
132 mail = ActionMailer::Base.deliveries.last
133 assert_kind_of TMail::Mail, mail
133 assert_kind_of TMail::Mail, mail
134 assert_equal "[eCookbook] New file", mail.subject
134 assert_equal "[eCookbook] New file", mail.subject
135 assert mail.body.include?('testfile.txt')
135 assert mail.body.include?('testfile.txt')
136 end
136 end
137
137
138 def test_add_version_file
138 def test_add_version_file
139 set_tmp_attachments_directory
139 set_tmp_attachments_directory
140 @request.session[:user_id] = 2
140 @request.session[:user_id] = 2
141 Setting.notified_events = ['file_added']
141 Setting.notified_events = ['file_added']
142
142
143 assert_difference 'Attachment.count' do
143 assert_difference 'Attachment.count' do
144 post :add_file, :id => 1, :version_id => '2',
144 post :add_file, :id => 1, :version_id => '2',
145 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
145 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
146 end
146 end
147 assert_redirected_to 'projects/list_files/ecookbook'
147 assert_redirected_to 'projects/list_files/ecookbook'
148 a = Attachment.find(:first, :order => 'created_on DESC')
148 a = Attachment.find(:first, :order => 'created_on DESC')
149 assert_equal 'testfile.txt', a.filename
149 assert_equal 'testfile.txt', a.filename
150 assert_equal Version.find(2), a.container
150 assert_equal Version.find(2), a.container
151 end
151 end
152
152
153 def test_list_files
153 def test_list_files
154 get :list_files, :id => 1
154 get :list_files, :id => 1
155 assert_response :success
155 assert_response :success
156 assert_template 'list_files'
156 assert_template 'list_files'
157 assert_not_nil assigns(:containers)
157 assert_not_nil assigns(:containers)
158
158
159 # file attached to the project
159 # file attached to the project
160 assert_tag :a, :content => 'project_file.zip',
160 assert_tag :a, :content => 'project_file.zip',
161 :attributes => { :href => '/attachments/download/8/project_file.zip' }
161 :attributes => { :href => '/attachments/download/8/project_file.zip' }
162
162
163 # file attached to a project's version
163 # file attached to a project's version
164 assert_tag :a, :content => 'version_file.zip',
164 assert_tag :a, :content => 'version_file.zip',
165 :attributes => { :href => '/attachments/download/9/version_file.zip' }
165 :attributes => { :href => '/attachments/download/9/version_file.zip' }
166 end
166 end
167
167
168 def test_changelog
168 def test_changelog
169 get :changelog, :id => 1
169 get :changelog, :id => 1
170 assert_response :success
170 assert_response :success
171 assert_template 'changelog'
171 assert_template 'changelog'
172 assert_not_nil assigns(:versions)
172 assert_not_nil assigns(:versions)
173 end
173 end
174
174
175 def test_roadmap
175 def test_roadmap
176 get :roadmap, :id => 1
176 get :roadmap, :id => 1
177 assert_response :success
177 assert_response :success
178 assert_template 'roadmap'
178 assert_template 'roadmap'
179 assert_not_nil assigns(:versions)
179 assert_not_nil assigns(:versions)
180 # Version with no date set appears
180 # Version with no date set appears
181 assert assigns(:versions).include?(Version.find(3))
181 assert assigns(:versions).include?(Version.find(3))
182 # Completed version doesn't appear
182 # Completed version doesn't appear
183 assert !assigns(:versions).include?(Version.find(1))
183 assert !assigns(:versions).include?(Version.find(1))
184 end
184 end
185
185
186 def test_roadmap_with_completed_versions
186 def test_roadmap_with_completed_versions
187 get :roadmap, :id => 1, :completed => 1
187 get :roadmap, :id => 1, :completed => 1
188 assert_response :success
188 assert_response :success
189 assert_template 'roadmap'
189 assert_template 'roadmap'
190 assert_not_nil assigns(:versions)
190 assert_not_nil assigns(:versions)
191 # Version with no date set appears
191 # Version with no date set appears
192 assert assigns(:versions).include?(Version.find(3))
192 assert assigns(:versions).include?(Version.find(3))
193 # Completed version appears
193 # Completed version appears
194 assert assigns(:versions).include?(Version.find(1))
194 assert assigns(:versions).include?(Version.find(1))
195 end
195 end
196
196
197 def test_project_activity
197 def test_project_activity
198 get :activity, :id => 1, :with_subprojects => 0
198 get :activity, :id => 1, :with_subprojects => 0
199 assert_response :success
199 assert_response :success
200 assert_template 'activity'
200 assert_template 'activity'
201 assert_not_nil assigns(:events_by_day)
201 assert_not_nil assigns(:events_by_day)
202
202
203 assert_tag :tag => "h3",
203 assert_tag :tag => "h3",
204 :content => /#{2.days.ago.to_date.day}/,
204 :content => /#{2.days.ago.to_date.day}/,
205 :sibling => { :tag => "dl",
205 :sibling => { :tag => "dl",
206 :child => { :tag => "dt",
206 :child => { :tag => "dt",
207 :attributes => { :class => /issue-edit/ },
207 :attributes => { :class => /issue-edit/ },
208 :child => { :tag => "a",
208 :child => { :tag => "a",
209 :content => /(#{IssueStatus.find(2).name})/,
209 :content => /(#{IssueStatus.find(2).name})/,
210 }
210 }
211 }
211 }
212 }
212 }
213 end
213 end
214
214
215 def test_previous_project_activity
215 def test_previous_project_activity
216 get :activity, :id => 1, :from => 3.days.ago.to_date
216 get :activity, :id => 1, :from => 3.days.ago.to_date
217 assert_response :success
217 assert_response :success
218 assert_template 'activity'
218 assert_template 'activity'
219 assert_not_nil assigns(:events_by_day)
219 assert_not_nil assigns(:events_by_day)
220
220
221 assert_tag :tag => "h3",
221 assert_tag :tag => "h3",
222 :content => /#{3.day.ago.to_date.day}/,
222 :content => /#{3.day.ago.to_date.day}/,
223 :sibling => { :tag => "dl",
223 :sibling => { :tag => "dl",
224 :child => { :tag => "dt",
224 :child => { :tag => "dt",
225 :attributes => { :class => /issue/ },
225 :attributes => { :class => /issue/ },
226 :child => { :tag => "a",
226 :child => { :tag => "a",
227 :content => /#{Issue.find(1).subject}/,
227 :content => /#{Issue.find(1).subject}/,
228 }
228 }
229 }
229 }
230 }
230 }
231 end
231 end
232
232
233 def test_global_activity
233 def test_global_activity
234 get :activity
234 get :activity
235 assert_response :success
235 assert_response :success
236 assert_template 'activity'
236 assert_template 'activity'
237 assert_not_nil assigns(:events_by_day)
237 assert_not_nil assigns(:events_by_day)
238
238
239 assert_tag :tag => "h3",
239 assert_tag :tag => "h3",
240 :content => /#{5.day.ago.to_date.day}/,
240 :content => /#{5.day.ago.to_date.day}/,
241 :sibling => { :tag => "dl",
241 :sibling => { :tag => "dl",
242 :child => { :tag => "dt",
242 :child => { :tag => "dt",
243 :attributes => { :class => /issue/ },
243 :attributes => { :class => /issue/ },
244 :child => { :tag => "a",
244 :child => { :tag => "a",
245 :content => /#{Issue.find(5).subject}/,
245 :content => /#{Issue.find(5).subject}/,
246 }
246 }
247 }
247 }
248 }
248 }
249 end
249 end
250
250
251 def test_user_activity
251 def test_user_activity
252 get :activity, :user_id => 2
252 get :activity, :user_id => 2
253 assert_response :success
253 assert_response :success
254 assert_template 'activity'
254 assert_template 'activity'
255 assert_not_nil assigns(:events_by_day)
255 assert_not_nil assigns(:events_by_day)
256
256
257 assert_tag :tag => "h3",
257 assert_tag :tag => "h3",
258 :content => /#{3.day.ago.to_date.day}/,
258 :content => /#{3.day.ago.to_date.day}/,
259 :sibling => { :tag => "dl",
259 :sibling => { :tag => "dl",
260 :child => { :tag => "dt",
260 :child => { :tag => "dt",
261 :attributes => { :class => /issue/ },
261 :attributes => { :class => /issue/ },
262 :child => { :tag => "a",
262 :child => { :tag => "a",
263 :content => /#{Issue.find(1).subject}/,
263 :content => /#{Issue.find(1).subject}/,
264 }
264 }
265 }
265 }
266 }
266 }
267 end
267 end
268
268
269 def test_activity_atom_feed
269 def test_activity_atom_feed
270 get :activity, :format => 'atom'
270 get :activity, :format => 'atom'
271 assert_response :success
271 assert_response :success
272 assert_template 'common/feed.atom.rxml'
272 assert_template 'common/feed.atom.rxml'
273 end
273 end
274
274
275 def test_archive
275 def test_archive
276 @request.session[:user_id] = 1 # admin
276 @request.session[:user_id] = 1 # admin
277 post :archive, :id => 1
277 post :archive, :id => 1
278 assert_redirected_to 'admin/projects'
278 assert_redirected_to 'admin/projects'
279 assert !Project.find(1).active?
279 assert !Project.find(1).active?
280 end
280 end
281
281
282 def test_unarchive
282 def test_unarchive
283 @request.session[:user_id] = 1 # admin
283 @request.session[:user_id] = 1 # admin
284 Project.find(1).archive
284 Project.find(1).archive
285 post :unarchive, :id => 1
285 post :unarchive, :id => 1
286 assert_redirected_to 'admin/projects'
286 assert_redirected_to 'admin/projects'
287 assert Project.find(1).active?
287 assert Project.find(1).active?
288 end
288 end
289
289
290 def test_jump_should_redirect_to_active_tab
291 get :show, :id => 1, :jump => 'issues'
292 assert_redirected_to 'projects/ecookbook/issues'
293 end
294
295 def test_jump_should_not_redirect_to_inactive_tab
296 get :show, :id => 3, :jump => 'documents'
297 assert_response :success
298 assert_template 'show'
299 end
300
301 def test_jump_should_not_redirect_to_unknown_tab
302 get :show, :id => 3, :jump => 'foobar'
303 assert_response :success
304 assert_template 'show'
305 end
306
290 def test_project_menu
307 def test_project_menu
291 assert_no_difference 'Redmine::MenuManager.items(:project_menu).size' do
308 assert_no_difference 'Redmine::MenuManager.items(:project_menu).size' do
292 Redmine::MenuManager.map :project_menu do |menu|
309 Redmine::MenuManager.map :project_menu do |menu|
293 menu.push :foo, { :controller => 'projects', :action => 'show' }, :cation => 'Foo'
310 menu.push :foo, { :controller => 'projects', :action => 'show' }, :cation => 'Foo'
294 menu.push :bar, { :controller => 'projects', :action => 'show' }, :before => :activity
311 menu.push :bar, { :controller => 'projects', :action => 'show' }, :before => :activity
295 menu.push :hello, { :controller => 'projects', :action => 'show' }, :caption => Proc.new {|p| p.name.upcase }, :after => :bar
312 menu.push :hello, { :controller => 'projects', :action => 'show' }, :caption => Proc.new {|p| p.name.upcase }, :after => :bar
296 end
313 end
297
314
298 get :show, :id => 1
315 get :show, :id => 1
299 assert_tag :div, :attributes => { :id => 'main-menu' },
316 assert_tag :div, :attributes => { :id => 'main-menu' },
300 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo',
317 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Foo',
301 :attributes => { :class => 'foo' } } }
318 :attributes => { :class => 'foo' } } }
302
319
303 assert_tag :div, :attributes => { :id => 'main-menu' },
320 assert_tag :div, :attributes => { :id => 'main-menu' },
304 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar',
321 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'Bar',
305 :attributes => { :class => 'bar' } },
322 :attributes => { :class => 'bar' } },
306 :before => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' } } }
323 :before => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK' } } }
307
324
308 assert_tag :div, :attributes => { :id => 'main-menu' },
325 assert_tag :div, :attributes => { :id => 'main-menu' },
309 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK',
326 :descendant => { :tag => 'li', :child => { :tag => 'a', :content => 'ECOOKBOOK',
310 :attributes => { :class => 'hello' } },
327 :attributes => { :class => 'hello' } },
311 :before => { :tag => 'li', :child => { :tag => 'a', :content => 'Activity' } } }
328 :before => { :tag => 'li', :child => { :tag => 'a', :content => 'Activity' } } }
312
329
313 # Remove the menu items
330 # Remove the menu items
314 Redmine::MenuManager.map :project_menu do |menu|
331 Redmine::MenuManager.map :project_menu do |menu|
315 menu.delete :foo
332 menu.delete :foo
316 menu.delete :bar
333 menu.delete :bar
317 menu.delete :hello
334 menu.delete :hello
318 end
335 end
319 end
336 end
320 end
337 end
321
338
322 # A hook that is manually registered later
339 # A hook that is manually registered later
323 class ProjectBasedTemplate < Redmine::Hook::ViewListener
340 class ProjectBasedTemplate < Redmine::Hook::ViewListener
324 def view_layouts_base_html_head(context)
341 def view_layouts_base_html_head(context)
325 # Adds a project stylesheet
342 # Adds a project stylesheet
326 stylesheet_link_tag(context[:project].identifier) if context[:project]
343 stylesheet_link_tag(context[:project].identifier) if context[:project]
327 end
344 end
328 end
345 end
329 # Don't use this hook now
346 # Don't use this hook now
330 Redmine::Hook.clear_listeners
347 Redmine::Hook.clear_listeners
331
348
332 def test_hook_response
349 def test_hook_response
333 Redmine::Hook.add_listener(ProjectBasedTemplate)
350 Redmine::Hook.add_listener(ProjectBasedTemplate)
334 get :show, :id => 1
351 get :show, :id => 1
335 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
352 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
336 :parent => {:tag => 'head'}
353 :parent => {:tag => 'head'}
337
354
338 Redmine::Hook.clear_listeners
355 Redmine::Hook.clear_listeners
339 end
356 end
340 end
357 end
General Comments 0
You need to be logged in to leave comments. Login now