Adds previous/next links to issue (#2850).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@8488 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
parent
bbe1ff3ec9
commit
b1504ceb43
|
@ -120,7 +120,10 @@ class IssuesController < ApplicationController
|
||||||
@priorities = IssuePriority.active
|
@priorities = IssuePriority.active
|
||||||
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
|
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { render :template => 'issues/show' }
|
format.html {
|
||||||
|
retrieve_previous_and_next_issue_ids
|
||||||
|
render :template => 'issues/show'
|
||||||
|
}
|
||||||
format.api
|
format.api
|
||||||
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
|
format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
|
||||||
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
|
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
|
||||||
|
@ -277,6 +280,20 @@ private
|
||||||
render_404
|
render_404
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def retrieve_previous_and_next_issue_ids
|
||||||
|
retrieve_query_from_session
|
||||||
|
if @query
|
||||||
|
sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
|
||||||
|
sort_update(@query.sortable_columns, 'issues_index_sort')
|
||||||
|
limit = 500
|
||||||
|
issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1))
|
||||||
|
if (idx = issue_ids.index(@issue.id.to_s)) && idx < limit
|
||||||
|
@prev_issue_id = issue_ids[idx - 1].to_i if idx > 0
|
||||||
|
@next_issue_id = issue_ids[idx + 1].to_i if idx < (issue_ids.size - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Used by #edit and #update to set some common instance variables
|
# Used by #edit and #update to set some common instance variables
|
||||||
# from the params
|
# from the params
|
||||||
# TODO: Refactor, not everything in here is needed by #edit
|
# TODO: Refactor, not everything in here is needed by #edit
|
||||||
|
|
|
@ -92,6 +92,18 @@ module QueriesHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def retrieve_query_from_session
|
||||||
|
if session[:query]
|
||||||
|
@query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
|
||||||
|
if session[:query].has_key?(:project_id)
|
||||||
|
@query.project_id = session[:query][:project_id]
|
||||||
|
else
|
||||||
|
@query.project = @project
|
||||||
|
end
|
||||||
|
@query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def build_query_from_params
|
def build_query_from_params
|
||||||
if params[:fields] || params[:f]
|
if params[:fields] || params[:f]
|
||||||
@query.filters = {}
|
@query.filters = {}
|
||||||
|
|
|
@ -160,7 +160,8 @@ module SortHelper
|
||||||
# sort_clause.
|
# sort_clause.
|
||||||
# - criteria can be either an array or a hash of allowed keys
|
# - criteria can be either an array or a hash of allowed keys
|
||||||
#
|
#
|
||||||
def sort_update(criteria)
|
def sort_update(criteria, sort_name=nil)
|
||||||
|
sort_name ||= self.sort_name
|
||||||
@sort_criteria = SortCriteria.new
|
@sort_criteria = SortCriteria.new
|
||||||
@sort_criteria.available_criteria = criteria
|
@sort_criteria.available_criteria = criteria
|
||||||
@sort_criteria.from_param(params[:sort] || session[sort_name])
|
@sort_criteria.from_param(params[:sort] || session[sort_name])
|
||||||
|
|
|
@ -3,7 +3,14 @@
|
||||||
<h2><%= issue_heading(@issue) %></h2>
|
<h2><%= issue_heading(@issue) %></h2>
|
||||||
|
|
||||||
<div class="<%= @issue.css_classes %> details">
|
<div class="<%= @issue.css_classes %> details">
|
||||||
<%= avatar(@issue.author, :size => "50") %>
|
<% if @prev_issue_id || @next_issue_id %>
|
||||||
|
<div class="next-prev-links contextual">
|
||||||
|
<%= link_to_if @prev_issue_id, '« Previous', issue_path(@prev_issue_id), :title => "##{@prev_issue_id}" %> |
|
||||||
|
<%= link_to_if @next_issue_id, 'Next »', issue_path(@next_issue_id), :title => "##{@next_issue_id}" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= avatar(@issue.author, :size => "50") %>
|
||||||
|
|
||||||
<div class="subject">
|
<div class="subject">
|
||||||
<%= render_issue_subject_with_tree(@issue) %>
|
<%= render_issue_subject_with_tree(@issue) %>
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Redmine - project management software
|
||||||
|
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
module ActiveRecord
|
||||||
|
class Base
|
||||||
|
def self.find_ids(options={})
|
||||||
|
find_ids_with_associations(options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Associations
|
||||||
|
module ClassMethods
|
||||||
|
def find_ids_with_associations(options = {})
|
||||||
|
catch :invalid_query do
|
||||||
|
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
|
||||||
|
return connection.select_values(construct_ids_finder_sql_with_included_associations(options, join_dependency))
|
||||||
|
end
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
def construct_ids_finder_sql_with_included_associations(options, join_dependency)
|
||||||
|
scope = scope(:find)
|
||||||
|
sql = "SELECT #{table_name}.id FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
|
||||||
|
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
||||||
|
|
||||||
|
add_joins!(sql, options[:joins], scope)
|
||||||
|
add_conditions!(sql, options[:conditions], scope)
|
||||||
|
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
||||||
|
|
||||||
|
add_group!(sql, options[:group], options[:having], scope)
|
||||||
|
add_order!(sql, options[:order], scope)
|
||||||
|
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
||||||
|
add_lock!(sql, options, scope)
|
||||||
|
|
||||||
|
return sanitize_sql(sql)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -292,6 +292,7 @@ div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color:
|
||||||
div.issue div.subject>div>p { margin-top: 0.5em; }
|
div.issue div.subject>div>p { margin-top: 0.5em; }
|
||||||
div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
|
div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
|
||||||
div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px; -moz-border-radius: 2px;}
|
div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px; -moz-border-radius: 2px;}
|
||||||
|
div.issue .next-prev-links {color:#999;}
|
||||||
|
|
||||||
#issue_tree table.issues, #relations table.issues { border: 0; }
|
#issue_tree table.issues, #relations table.issues { border: 0; }
|
||||||
#issue_tree td.checkbox, #relations td.checkbox {display:none;}
|
#issue_tree td.checkbox, #relations td.checkbox {display:none;}
|
||||||
|
|
|
@ -920,6 +920,77 @@ class IssuesControllerTest < ActionController::TestCase
|
||||||
:descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
|
:descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_show_should_not_display_prev_next_links_without_query_in_session
|
||||||
|
get :show, :id => 1
|
||||||
|
assert_response :success
|
||||||
|
assert_nil assigns(:prev_issue_id)
|
||||||
|
assert_nil assigns(:next_issue_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_show_should_display_prev_next_links_with_query_in_session
|
||||||
|
@request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
|
||||||
|
@request.session['issues_index_sort'] = 'id'
|
||||||
|
|
||||||
|
with_settings :display_subprojects_issues => '0' do
|
||||||
|
get :show, :id => 3
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
# Previous and next issues for all projects
|
||||||
|
assert_equal 2, assigns(:prev_issue_id)
|
||||||
|
assert_equal 5, assigns(:next_issue_id)
|
||||||
|
|
||||||
|
assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
|
||||||
|
assert_tag 'a', :attributes => {:href => '/issues/5'}, :content => /Next/
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_show_should_display_prev_next_links_with_project_query_in_session
|
||||||
|
@request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
|
||||||
|
@request.session['issues_index_sort'] = 'id'
|
||||||
|
|
||||||
|
with_settings :display_subprojects_issues => '0' do
|
||||||
|
get :show, :id => 3
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
# Previous and next issues inside project
|
||||||
|
assert_equal 2, assigns(:prev_issue_id)
|
||||||
|
assert_equal 7, assigns(:next_issue_id)
|
||||||
|
|
||||||
|
assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
|
||||||
|
assert_tag 'a', :attributes => {:href => '/issues/7'}, :content => /Next/
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_show_should_not_display_prev_link_for_first_issue
|
||||||
|
@request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
|
||||||
|
@request.session['issues_index_sort'] = 'id'
|
||||||
|
|
||||||
|
with_settings :display_subprojects_issues => '0' do
|
||||||
|
get :show, :id => 1
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
assert_nil assigns(:prev_issue_id)
|
||||||
|
assert_equal 2, assigns(:next_issue_id)
|
||||||
|
|
||||||
|
assert_no_tag 'a', :content => /Previous/
|
||||||
|
assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Next/
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
|
||||||
|
@request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
|
||||||
|
@request.session['issues_index_sort'] = 'id'
|
||||||
|
|
||||||
|
get :show, :id => 1
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
assert_nil assigns(:prev_issue_id)
|
||||||
|
assert_nil assigns(:next_issue_id)
|
||||||
|
|
||||||
|
assert_no_tag 'a', :content => /Previous/
|
||||||
|
assert_no_tag 'a', :content => /Next/
|
||||||
|
end
|
||||||
|
|
||||||
def test_show_atom
|
def test_show_atom
|
||||||
get :show, :id => 2, :format => 'atom'
|
get :show, :id => 2, :format => 'atom'
|
||||||
assert_response :success
|
assert_response :success
|
||||||
|
|
|
@ -612,6 +612,13 @@ class QueryTest < ActiveSupport::TestCase
|
||||||
assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
|
assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_issue_ids
|
||||||
|
q = Query.new(:name => '_')
|
||||||
|
order = "issues.subject, issues.id"
|
||||||
|
issues = q.issues(:order => order)
|
||||||
|
assert_equal issues.map(&:id).map(&:to_s), q.issue_ids(:order => order)
|
||||||
|
end
|
||||||
|
|
||||||
def test_label_for
|
def test_label_for
|
||||||
q = Query.new
|
q = Query.new
|
||||||
assert_equal 'Assignee', q.label_for('assigned_to_id')
|
assert_equal 'Assignee', q.label_for('assigned_to_id')
|
||||||
|
|
Loading…
Reference in New Issue