@@ -0,0 +1,53 | |||||
|
1 | # Redmine - project management software | |||
|
2 | # Copyright (C) 2006-2011 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
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 | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | module ActiveRecord | |||
|
19 | class Base | |||
|
20 | def self.find_ids(options={}) | |||
|
21 | find_ids_with_associations(options) | |||
|
22 | end | |||
|
23 | end | |||
|
24 | ||||
|
25 | module Associations | |||
|
26 | module ClassMethods | |||
|
27 | def find_ids_with_associations(options = {}) | |||
|
28 | catch :invalid_query do | |||
|
29 | join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) | |||
|
30 | return connection.select_values(construct_ids_finder_sql_with_included_associations(options, join_dependency)) | |||
|
31 | end | |||
|
32 | [] | |||
|
33 | end | |||
|
34 | ||||
|
35 | def construct_ids_finder_sql_with_included_associations(options, join_dependency) | |||
|
36 | scope = scope(:find) | |||
|
37 | sql = "SELECT #{table_name}.id FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} " | |||
|
38 | sql << join_dependency.join_associations.collect{|join| join.association_join }.join | |||
|
39 | ||||
|
40 | add_joins!(sql, options[:joins], scope) | |||
|
41 | add_conditions!(sql, options[:conditions], scope) | |||
|
42 | add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) | |||
|
43 | ||||
|
44 | add_group!(sql, options[:group], options[:having], scope) | |||
|
45 | add_order!(sql, options[:order], scope) | |||
|
46 | add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections) | |||
|
47 | add_lock!(sql, options, scope) | |||
|
48 | ||||
|
49 | return sanitize_sql(sql) | |||
|
50 | end | |||
|
51 | end | |||
|
52 | end | |||
|
53 | end |
@@ -120,7 +120,10 class IssuesController < ApplicationController | |||||
120 | @priorities = IssuePriority.active |
|
120 | @priorities = IssuePriority.active | |
121 | @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) |
|
121 | @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) | |
122 | respond_to do |format| |
|
122 | respond_to do |format| | |
123 | format.html { render :template => 'issues/show' } |
|
123 | format.html { | |
|
124 | retrieve_previous_and_next_issue_ids | |||
|
125 | render :template => 'issues/show' | |||
|
126 | } | |||
124 | format.api |
|
127 | format.api | |
125 | format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } |
|
128 | format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' } | |
126 | format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } |
|
129 | format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } | |
@@ -277,6 +280,20 private | |||||
277 | render_404 |
|
280 | render_404 | |
278 | end |
|
281 | end | |
279 |
|
282 | |||
|
283 | def retrieve_previous_and_next_issue_ids | |||
|
284 | retrieve_query_from_session | |||
|
285 | if @query | |||
|
286 | sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria) | |||
|
287 | sort_update(@query.sortable_columns, 'issues_index_sort') | |||
|
288 | limit = 500 | |||
|
289 | issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1)) | |||
|
290 | if (idx = issue_ids.index(@issue.id.to_s)) && idx < limit | |||
|
291 | @prev_issue_id = issue_ids[idx - 1].to_i if idx > 0 | |||
|
292 | @next_issue_id = issue_ids[idx + 1].to_i if idx < (issue_ids.size - 1) | |||
|
293 | end | |||
|
294 | end | |||
|
295 | end | |||
|
296 | ||||
280 | # Used by #edit and #update to set some common instance variables |
|
297 | # Used by #edit and #update to set some common instance variables | |
281 | # from the params |
|
298 | # from the params | |
282 | # TODO: Refactor, not everything in here is needed by #edit |
|
299 | # TODO: Refactor, not everything in here is needed by #edit |
@@ -92,6 +92,18 module QueriesHelper | |||||
92 | end |
|
92 | end | |
93 | end |
|
93 | end | |
94 |
|
94 | |||
|
95 | def retrieve_query_from_session | |||
|
96 | if session[:query] | |||
|
97 | @query = Query.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) | |||
|
98 | if session[:query].has_key?(:project_id) | |||
|
99 | @query.project_id = session[:query][:project_id] | |||
|
100 | else | |||
|
101 | @query.project = @project | |||
|
102 | end | |||
|
103 | @query | |||
|
104 | end | |||
|
105 | end | |||
|
106 | ||||
95 | def build_query_from_params |
|
107 | def build_query_from_params | |
96 | if params[:fields] || params[:f] |
|
108 | if params[:fields] || params[:f] | |
97 | @query.filters = {} |
|
109 | @query.filters = {} |
@@ -160,7 +160,8 module SortHelper | |||||
160 | # sort_clause. |
|
160 | # sort_clause. | |
161 | # - criteria can be either an array or a hash of allowed keys |
|
161 | # - criteria can be either an array or a hash of allowed keys | |
162 | # |
|
162 | # | |
163 | def sort_update(criteria) |
|
163 | def sort_update(criteria, sort_name=nil) | |
|
164 | sort_name ||= self.sort_name | |||
164 | @sort_criteria = SortCriteria.new |
|
165 | @sort_criteria = SortCriteria.new | |
165 | @sort_criteria.available_criteria = criteria |
|
166 | @sort_criteria.available_criteria = criteria | |
166 | @sort_criteria.from_param(params[:sort] || session[sort_name]) |
|
167 | @sort_criteria.from_param(params[:sort] || session[sort_name]) |
@@ -3,7 +3,14 | |||||
3 | <h2><%= issue_heading(@issue) %></h2> |
|
3 | <h2><%= issue_heading(@issue) %></h2> | |
4 |
|
4 | |||
5 | <div class="<%= @issue.css_classes %> details"> |
|
5 | <div class="<%= @issue.css_classes %> details"> | |
6 | <%= avatar(@issue.author, :size => "50") %> |
|
6 | <% if @prev_issue_id || @next_issue_id %> | |
|
7 | <div class="next-prev-links contextual"> | |||
|
8 | <%= link_to_if @prev_issue_id, '« Previous', issue_path(@prev_issue_id), :title => "##{@prev_issue_id}" %> | | |||
|
9 | <%= link_to_if @next_issue_id, 'Next »', issue_path(@next_issue_id), :title => "##{@next_issue_id}" %> | |||
|
10 | </div> | |||
|
11 | <% end %> | |||
|
12 | ||||
|
13 | <%= avatar(@issue.author, :size => "50") %> | |||
7 |
|
14 | |||
8 | <div class="subject"> |
|
15 | <div class="subject"> | |
9 | <%= render_issue_subject_with_tree(@issue) %> |
|
16 | <%= render_issue_subject_with_tree(@issue) %> |
@@ -292,6 +292,7 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: | |||||
292 | div.issue div.subject>div>p { margin-top: 0.5em; } |
|
292 | div.issue div.subject>div>p { margin-top: 0.5em; } | |
293 | div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;} |
|
293 | div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;} | |
294 | 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;} |
|
294 | 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;} | |
|
295 | div.issue .next-prev-links {color:#999;} | |||
295 |
|
296 | |||
296 | #issue_tree table.issues, #relations table.issues { border: 0; } |
|
297 | #issue_tree table.issues, #relations table.issues { border: 0; } | |
297 | #issue_tree td.checkbox, #relations td.checkbox {display:none;} |
|
298 | #issue_tree td.checkbox, #relations td.checkbox {display:none;} |
@@ -920,6 +920,77 class IssuesControllerTest < ActionController::TestCase | |||||
920 | :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}} |
|
920 | :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}} | |
921 | end |
|
921 | end | |
922 |
|
922 | |||
|
923 | def test_show_should_not_display_prev_next_links_without_query_in_session | |||
|
924 | get :show, :id => 1 | |||
|
925 | assert_response :success | |||
|
926 | assert_nil assigns(:prev_issue_id) | |||
|
927 | assert_nil assigns(:next_issue_id) | |||
|
928 | end | |||
|
929 | ||||
|
930 | def test_show_should_display_prev_next_links_with_query_in_session | |||
|
931 | @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil} | |||
|
932 | @request.session['issues_index_sort'] = 'id' | |||
|
933 | ||||
|
934 | with_settings :display_subprojects_issues => '0' do | |||
|
935 | get :show, :id => 3 | |||
|
936 | end | |||
|
937 | ||||
|
938 | assert_response :success | |||
|
939 | # Previous and next issues for all projects | |||
|
940 | assert_equal 2, assigns(:prev_issue_id) | |||
|
941 | assert_equal 5, assigns(:next_issue_id) | |||
|
942 | ||||
|
943 | assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/ | |||
|
944 | assert_tag 'a', :attributes => {:href => '/issues/5'}, :content => /Next/ | |||
|
945 | end | |||
|
946 | ||||
|
947 | def test_show_should_display_prev_next_links_with_project_query_in_session | |||
|
948 | @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1} | |||
|
949 | @request.session['issues_index_sort'] = 'id' | |||
|
950 | ||||
|
951 | with_settings :display_subprojects_issues => '0' do | |||
|
952 | get :show, :id => 3 | |||
|
953 | end | |||
|
954 | ||||
|
955 | assert_response :success | |||
|
956 | # Previous and next issues inside project | |||
|
957 | assert_equal 2, assigns(:prev_issue_id) | |||
|
958 | assert_equal 7, assigns(:next_issue_id) | |||
|
959 | ||||
|
960 | assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/ | |||
|
961 | assert_tag 'a', :attributes => {:href => '/issues/7'}, :content => /Next/ | |||
|
962 | end | |||
|
963 | ||||
|
964 | def test_show_should_not_display_prev_link_for_first_issue | |||
|
965 | @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1} | |||
|
966 | @request.session['issues_index_sort'] = 'id' | |||
|
967 | ||||
|
968 | with_settings :display_subprojects_issues => '0' do | |||
|
969 | get :show, :id => 1 | |||
|
970 | end | |||
|
971 | ||||
|
972 | assert_response :success | |||
|
973 | assert_nil assigns(:prev_issue_id) | |||
|
974 | assert_equal 2, assigns(:next_issue_id) | |||
|
975 | ||||
|
976 | assert_no_tag 'a', :content => /Previous/ | |||
|
977 | assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Next/ | |||
|
978 | end | |||
|
979 | ||||
|
980 | def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results | |||
|
981 | @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1} | |||
|
982 | @request.session['issues_index_sort'] = 'id' | |||
|
983 | ||||
|
984 | get :show, :id => 1 | |||
|
985 | ||||
|
986 | assert_response :success | |||
|
987 | assert_nil assigns(:prev_issue_id) | |||
|
988 | assert_nil assigns(:next_issue_id) | |||
|
989 | ||||
|
990 | assert_no_tag 'a', :content => /Previous/ | |||
|
991 | assert_no_tag 'a', :content => /Next/ | |||
|
992 | end | |||
|
993 | ||||
923 | def test_show_atom |
|
994 | def test_show_atom | |
924 | get :show, :id => 2, :format => 'atom' |
|
995 | get :show, :id => 2, :format => 'atom' | |
925 | assert_response :success |
|
996 | assert_response :success |
@@ -612,6 +612,13 class QueryTest < ActiveSupport::TestCase | |||||
612 | assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq |
|
612 | assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq | |
613 | end |
|
613 | end | |
614 |
|
614 | |||
|
615 | def test_issue_ids | |||
|
616 | q = Query.new(:name => '_') | |||
|
617 | order = "issues.subject, issues.id" | |||
|
618 | issues = q.issues(:order => order) | |||
|
619 | assert_equal issues.map(&:id).map(&:to_s), q.issue_ids(:order => order) | |||
|
620 | end | |||
|
621 | ||||
615 | def test_label_for |
|
622 | def test_label_for | |
616 | q = Query.new |
|
623 | q = Query.new | |
617 | assert_equal 'Assignee', q.label_for('assigned_to_id') |
|
624 | assert_equal 'Assignee', q.label_for('assigned_to_id') |
General Comments 0
You need to be logged in to leave comments.
Login now