From 404bfce446915fe9dadcfce7b36d732813523e28 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Fri, 31 Aug 2007 17:02:44 +0000 Subject: [PATCH] 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 --- app/controllers/issues_controller.rb | 45 ++++++++++++++- app/models/project.rb | 6 +- app/models/query.rb | 29 ++++++---- app/views/issues/index.rhtml | 55 +++++++++++++++++++ app/views/my/blocks/_issuesassignedtome.rhtml | 4 +- app/views/my/blocks/_issuesreportedbyme.rhtml | 4 +- test/functional/my_controller_test.rb | 5 +- 7 files changed, 126 insertions(+), 22 deletions(-) create mode 100644 app/views/issues/index.rhtml diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 56a9b11ca..fe1c78884 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -17,7 +17,7 @@ class IssuesController < ApplicationController 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 ] @@ -32,8 +32,27 @@ class IssuesController < ApplicationController helper :watchers include WatchersHelper 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 @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) @@ -149,5 +168,25 @@ private @html_title = "#{@project.name} - #{@issue.tracker.name} ##{@issue.id}" rescue ActiveRecord::RecordNotFound 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 diff --git a/app/models/project.rb b/app/models/project.rb index 241c65817..f994ed6a5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -77,11 +77,11 @@ class Project < ActiveRecord::Base def self.visible_by(user=nil) 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? - 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 - 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 diff --git a/app/models/query.rb b/app/models/query.rb index ff519d71c..c700242ed 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -91,14 +91,20 @@ class Query < ActiveRecord::Base "updated_on" => { :type => :date_past, :order => 10 }, "start_date" => { :type => :date, :order => 11 }, "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 += @project.users.collect{|s| [s.name, s.id.to_s] } - - @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } - @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } + # members of the user's projects + user_values += executed_by.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] } + end + @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["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? @@ -166,7 +172,7 @@ class Query < ActiveRecord::Base def statement # project/subprojects clause clause = '' - if has_filter?("subproject_id") + if project && has_filter?("subproject_id") subproject_ids = [] if operator_for("subproject_id") == "=" 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} end 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 - clause << "#{Issue.table_name}.project_id=%d" % project.id if project + clause << Project.visible_by(executed_by) end # filters clauses @@ -239,7 +247,8 @@ class Query < ActiveRecord::Base filters_clauses << sql 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 end end diff --git a/app/views/issues/index.rhtml b/app/views/issues/index.rhtml new file mode 100644 index 000000000..0f63ba96f --- /dev/null +++ b/app/views/issues/index.rhtml @@ -0,0 +1,55 @@ +

<%=l(:label_issue_plural)%>

+ +<% form_tag({}, :id => 'query_form') do %> +<%= render :partial => 'queries/filters', :locals => {:query => @query} %> +<% end %> +
+<%= 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' %> +
+
  + +<%= error_messages_for 'query' %> +<% if @query.valid? %> +<% if @issues.empty? %> +

<%= l(:label_no_data) %>

+<% else %> +  + + + <%= 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)) %> + + <%= 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)) %> + + + <% for issue in @issues %> + "> + + + + + + + + + + <% end %> + +
<%=l(:field_subject)%>
<%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %><%=h issue.project.name %><%= issue.tracker.name %>
<%= issue.status.name %>
<%= issue.priority.name %><%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %><%= issue.assigned_to.name if issue.assigned_to %><%= format_time(issue.updated_on) %>
+

<%= pagination_links_full @issue_pages %> +[ <%= @issue_pages.current.first_item %> - <%= @issue_pages.current.last_item %> / <%= @issue_count %> ]

+<% end %> +<% end %> diff --git a/app/views/my/blocks/_issuesassignedtome.rhtml b/app/views/my/blocks/_issuesassignedtome.rhtml index 0d49279f4..9b97cb39d 100644 --- a/app/views/my/blocks/_issuesassignedtome.rhtml +++ b/app/views/my/blocks/_issuesassignedtome.rhtml @@ -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], :limit => 10, :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 } %> <% if assigned_issues.length > 0 %> -

<%=lwr(:label_last_updates, assigned_issues.length)%>

+

<%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => 'me' %>

<% end %> diff --git a/app/views/my/blocks/_issuesreportedbyme.rhtml b/app/views/my/blocks/_issuesreportedbyme.rhtml index 250e8265d..a0770846c 100644 --- a/app/views/my/blocks/_issuesreportedbyme.rhtml +++ b/app/views/my/blocks/_issuesreportedbyme.rhtml @@ -6,5 +6,5 @@ :order => "#{Issue.table_name}.updated_on DESC") %> <%= render :partial => 'issues/list_simple', :locals => { :issues => reported_issues } %> <% if reported_issues.length > 0 %> -

<%=lwr(:label_last_updates, reported_issues.length)%>

-<% end %> \ No newline at end of file +

<%= link_to l(:label_issue_view_all), :controller => 'issues', :action => 'index', :set_filter => 1, :author_id => 'me' %>

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