Added a cross-project issue list. It displays the issues of all the projects visible by the user.

The users list available in the filters ('assigned to' / 'created by') is made of the members of all projects the current user belongs to.
For now, this view is only accessible from 'My page' ('issues assigned to me' or 'issues reported by me' blocks, to view the full lists)

On 'My page', assigned issue are now sorted by priority.

git-svn-id: http://redmine.rubyforge.org/svn/trunk@684 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2007-08-31 17:02:44 +00:00
parent 1187ad96ac
commit 404bfce446
7 changed files with 126 additions and 22 deletions

View File

@ -17,7 +17,7 @@
class IssuesController < ApplicationController class IssuesController < ApplicationController
layout 'base', :except => :export_pdf layout 'base', :except => :export_pdf
before_filter :find_project, :authorize before_filter :find_project, :authorize, :except => :index
cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ] cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
@ -32,8 +32,27 @@ class IssuesController < ApplicationController
helper :watchers helper :watchers
include WatchersHelper include WatchersHelper
helper :attachments helper :attachments
include AttachmentsHelper include AttachmentsHelper
helper :queries
helper :sort
include SortHelper
def index
sort_init "#{Issue.table_name}.id", "desc"
sort_update
retrieve_query
if @query.valid?
@issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
@issue_pages = Paginator.new self, @issue_count, 25, params['page']
@issues = Issue.find :all, :order => sort_clause,
:include => [ :assigned_to, :status, :tracker, :project, :priority ],
:conditions => @query.statement,
:limit => @issue_pages.items_per_page,
:offset => @issue_pages.current.offset
end
render :layout => false if request.xhr?
end
def show def show
@status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
@custom_values = @issue.custom_values.find(:all, :include => :custom_field) @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
@ -149,5 +168,25 @@ private
@html_title = "#{@project.name} - #{@issue.tracker.name} ##{@issue.id}" @html_title = "#{@project.name} - #{@issue.tracker.name} ##{@issue.id}"
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404
end end
# Retrieve query from session or build a new query
def retrieve_query
if params[:set_filter] or !session[:query] or session[:query].project_id
# Give it a name, required to be valid
@query = Query.new(:name => "_", :executed_by => logged_in_user)
if params[:fields] and params[:fields].is_a? Array
params[:fields].each do |field|
@query.add_filter(field, params[:operators][field], params[:values][field])
end
else
@query.available_filters.keys.each do |field|
@query.add_short_filter(field, params[field]) if params[field]
end
end
session[:query] = @query
else
@query = session[:query]
end
end
end end

View File

@ -77,11 +77,11 @@ class Project < ActiveRecord::Base
def self.visible_by(user=nil) def self.visible_by(user=nil)
if user && user.admin? if user && user.admin?
return ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"] return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
elsif user && user.memberships.any? elsif user && user.memberships.any?
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] 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(',')}))"
else else
return ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = ?", true] return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
end end
end end

View File

@ -91,14 +91,20 @@ class Query < ActiveRecord::Base
"updated_on" => { :type => :date_past, :order => 10 }, "updated_on" => { :type => :date_past, :order => 10 },
"start_date" => { :type => :date, :order => 11 }, "start_date" => { :type => :date, :order => 11 },
"due_date" => { :type => :date, :order => 12 } } "due_date" => { :type => :date, :order => 12 } }
unless project.nil?
# project specific filters user_values = []
user_values = [] if project
user_values += project.users.collect{|s| [s.name, s.id.to_s] }
elsif executed_by
user_values << ["<< #{l(:label_me)} >>", "me"] if executed_by user_values << ["<< #{l(:label_me)} >>", "me"] if executed_by
user_values += @project.users.collect{|s| [s.name, s.id.to_s] } # members of the user's projects
user_values += executed_by.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } end
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
if project
# project specific filters
@available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } } @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
unless @project.active_children.empty? unless @project.active_children.empty?
@ -166,7 +172,7 @@ class Query < ActiveRecord::Base
def statement def statement
# project/subprojects clause # project/subprojects clause
clause = '' clause = ''
if has_filter?("subproject_id") if project && has_filter?("subproject_id")
subproject_ids = [] subproject_ids = []
if operator_for("subproject_id") == "=" if operator_for("subproject_id") == "="
subproject_ids = values_for("subproject_id").each(&:to_i) subproject_ids = values_for("subproject_id").each(&:to_i)
@ -174,8 +180,10 @@ class Query < ActiveRecord::Base
subproject_ids = project.active_children.collect{|p| p.id} subproject_ids = project.active_children.collect{|p| p.id}
end end
clause << "#{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project clause << "#{Issue.table_name}.project_id IN (%d,%s)" % [project.id, subproject_ids.join(",")] if project
elsif project
clause << "#{Issue.table_name}.project_id=%d" % project.id
else else
clause << "#{Issue.table_name}.project_id=%d" % project.id if project clause << Project.visible_by(executed_by)
end end
# filters clauses # filters clauses
@ -239,7 +247,8 @@ class Query < ActiveRecord::Base
filters_clauses << sql filters_clauses << sql
end if filters and valid? end if filters and valid?
clause << (' AND ' + filters_clauses.join(' AND ')) unless filters_clauses.empty? clause << ' AND ' unless clause.empty?
clause << filters_clauses.join(' AND ') unless filters_clauses.empty?
clause clause
end end
end end

View File

@ -0,0 +1,55 @@
<h2><%=l(:label_issue_plural)%></h2>
<% form_tag({}, :id => 'query_form') do %>
<%= render :partial => 'queries/filters', :locals => {:query => @query} %>
<% end %>
<div class="contextual">
<%= link_to_remote l(:button_apply),
{ :url => { :set_filter => 1 },
:update => "content",
:with => "Form.serialize('query_form')"
}, :class => 'icon icon-edit' %>
<%= link_to_remote l(:button_clear),
{ :url => { :set_filter => 1 },
:update => "content",
}, :class => 'icon icon-reload' %>
</div>
<br />&nbsp;
<%= error_messages_for 'query' %>
<% if @query.valid? %>
<% if @issues.empty? %>
<p><i><%= l(:label_no_data) %></i></p>
<% else %>
&nbsp;
<table class="list">
<thead><tr>
<%= sort_header_tag("#{Issue.table_name}.id", :caption => '#') %>
<%= sort_header_tag("#{Project.table_name}.name", :caption => l(:field_project)) %>
<%= sort_header_tag("#{Issue.table_name}.tracker_id", :caption => l(:field_tracker)) %>
<%= sort_header_tag("#{IssueStatus.table_name}.name", :caption => l(:field_status)) %>
<%= sort_header_tag("#{Issue.table_name}.priority_id", :caption => l(:field_priority)) %>
<th><%=l(:field_subject)%></th>
<%= sort_header_tag("#{User.table_name}.lastname", :caption => l(:field_assigned_to)) %>
<%= sort_header_tag("#{Issue.table_name}.updated_on", :caption => l(:field_updated_on)) %>
</tr></thead>
<tbody>
<% for issue in @issues %>
<tr class="<%= cycle("odd", "even") %>">
<td align="center" valign="top"><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
<td align="center" valign="top" nowrap><%=h issue.project.name %></td>
<td align="center" valign="top" nowrap><%= issue.tracker.name %></td>
<td valign="top"nowrap><div class="square" style="background:#<%= issue.status.html_color %>;"></div> <%= issue.status.name %></td>
<td align="center" valign="top"><%= issue.priority.name %></td>
<td><%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %></td>
<td align="center" valign="top" nowrap><%= issue.assigned_to.name if issue.assigned_to %></td>
<td align="center" valign="top" nowrap><%= format_time(issue.updated_on) %></td>
</tr>
<% end %>
</tbody>
</table>
<p><%= pagination_links_full @issue_pages %>
[ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]</p>
<% end %>
<% end %>

View File

@ -3,8 +3,8 @@
:conditions => ["assigned_to_id=? AND #{IssueStatus.table_name}.is_closed=? AND #{Project.table_name}.status=#{Project::STATUS_ACTIVE}", user.id, false], :conditions => ["assigned_to_id=? AND #{IssueStatus.table_name}.is_closed=? AND #{Project.table_name}.status=#{Project::STATUS_ACTIVE}", user.id, false],
:limit => 10, :limit => 10,
:include => [ :status, :project, :tracker ], :include => [ :status, :project, :tracker ],
:order => "#{Issue.table_name}.updated_on DESC") %> :order => "#{Issue.table_name}.priority_id DESC, #{Issue.table_name}.updated_on DESC") %>
<%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %> <%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %>
<% if assigned_issues.length > 0 %> <% if assigned_issues.length > 0 %>
<p><%=lwr(:label_last_updates, assigned_issues.length)%></p> <p class="small"><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => 'me' %></p>
<% end %> <% end %>

View File

@ -6,5 +6,5 @@
:order => "#{Issue.table_name}.updated_on DESC") %> :order => "#{Issue.table_name}.updated_on DESC") %>
<%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %> <%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %>
<% if reported_issues.length > 0 %> <% if reported_issues.length > 0 %>
<p><%=lwr(:label_last_updates, reported_issues.length)%></p> <p class="small"><%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :set_filter => 1, :author_id => 'me' %></p>
<% end %> <% end %>

View File

@ -22,6 +22,8 @@ require 'my_controller'
class MyController; def rescue_action(e) raise e end; end class MyController; def rescue_action(e) raise e end; end
class MyControllerTest < Test::Unit::TestCase class MyControllerTest < Test::Unit::TestCase
fixtures :users
def setup def setup
@controller = MyController.new @controller = MyController.new
@request = ActionController::TestRequest.new @request = ActionController::TestRequest.new
@ -50,8 +52,7 @@ class MyControllerTest < Test::Unit::TestCase
def test_update_account def test_update_account
post :account, :user => {:firstname => "Joe", :login => "root", :admin => 1} post :account, :user => {:firstname => "Joe", :login => "root", :admin => 1}
assert_response :success assert_redirected_to 'my/account'
assert_template 'account'
user = User.find(2) user = User.find(2)
assert_equal user, assigns(:user) assert_equal user, assigns(:user)
assert_equal "Joe", user.firstname assert_equal "Joe", user.firstname