##// END OF EJS Templates
Ability to save "sort order" in custom queries (#2899)....
Jean-Philippe Lang -
r2504:c7c8dc71f2fc
parent child
Show More
@@ -0,0 +1,9
1 class AddQueriesSortCriteria < ActiveRecord::Migration
2 def self.up
3 add_column :queries, :sort_criteria, :text
4 end
5
6 def self.down
7 remove_column :queries, :sort_criteria
8 end
9 end
@@ -45,7 +45,7 class IssuesController < ApplicationController
45 45
46 46 def index
47 47 retrieve_query
48 sort_init 'id', 'desc'
48 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
49 49 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
50 50
51 51 if @query.valid?
@@ -471,6 +471,7 private
471 471 @query = Query.find(params[:query_id], :conditions => cond)
472 472 @query.project = @project
473 473 session[:query] = {:id => @query.id, :project_id => @query.project_id}
474 sort_clear
474 475 else
475 476 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
476 477 # Give it a name, required to be valid
@@ -69,6 +69,11 module SortHelper
69 69 normalize!
70 70 end
71 71
72 def criteria=(arg)
73 @criteria = arg
74 normalize!
75 end
76
72 77 def to_param
73 78 @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',')
74 79 end
@@ -102,24 +107,42 module SortHelper
102 107 @criteria.first && @criteria.first.last
103 108 end
104 109
110 def empty?
111 @criteria.empty?
112 end
113
105 114 private
106 115
107 116 def normalize!
108 @criteria = @criteria.collect {|s| [s.first, (s.last == false || s.last == 'desc') ? false : true]}
117 @criteria ||= []
118 @criteria = @criteria.collect {|s| s = s.to_a; [s.first, (s.last == false || s.last == 'desc') ? false : true]}
109 119 @criteria = @criteria.select {|k,o| @available_criteria.has_key?(k)} if @available_criteria
110 120 @criteria.slice!(3)
111 121 self
112 122 end
113 123 end
124
125 def sort_name
126 controller_name + '_' + action_name + '_sort'
127 end
114 128
115 # Initializes the default sort column (default_key) and sort order
116 # (default_order).
117 #
118 # - default_key is a column attribute name.
119 # - default_order is 'asc' or 'desc'.
129 # Initializes the default sort.
130 # Examples:
131 #
132 # sort_init 'name'
133 # sort_init 'id', 'desc'
134 # sort_init ['name', ['id', 'desc']]
135 # sort_init [['name', 'desc'], ['id', 'desc']]
120 136 #
121 def sort_init(default_key, default_order='asc')
122 @sort_default = "#{default_key}:#{default_order}"
137 def sort_init(*args)
138 case args.size
139 when 1
140 @sort_default = args.first.is_a?(Array) ? args.first : [[args.first]]
141 when 2
142 @sort_default = [[args.first, args.last]]
143 else
144 raise ArgumentError
145 end
123 146 end
124 147
125 148 # Updates the sort state. Call this in the controller prior to calling
@@ -127,13 +150,18 module SortHelper
127 150 # - criteria can be either an array or a hash of allowed keys
128 151 #
129 152 def sort_update(criteria)
130 sort_name = controller_name + '_' + action_name + '_sort'
131
132 153 @sort_criteria = SortCriteria.new
133 154 @sort_criteria.available_criteria = criteria
134 @sort_criteria.from_param(params[:sort] || session[sort_name] || @sort_default)
155 @sort_criteria.from_param(params[:sort] || session[sort_name])
156 @sort_criteria.criteria = @sort_default if @sort_criteria.empty?
135 157 session[sort_name] = @sort_criteria.to_param
136 158 end
159
160 # Clears the sort criteria session data
161 #
162 def sort_clear
163 session[sort_name] = nil
164 end
137 165
138 166 # Returns an SQL sort clause corresponding to the current sort state.
139 167 # Use this to sort the controller's table items collection.
@@ -188,13 +216,6 module SortHelper
188 216 #
189 217 # <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %>
190 218 #
191 # Renders:
192 #
193 # <th title="Sort by contact ID" width="40">
194 # <a href="/contact/list?sort_order=desc&amp;sort_key=id">Id</a>
195 # &nbsp;&nbsp;<img alt="Sort_asc" src="/images/sort_asc.png" />
196 # </th>
197 #
198 219 def sort_header_tag(column, options = {})
199 220 caption = options.delete(:caption) || column.to_s.humanize
200 221 default_order = options.delete(:default_order) || 'asc'
@@ -28,6 +28,11 class QueryColumn
28 28 def caption
29 29 l("field_#{name}")
30 30 end
31
32 # Returns true if the column is sortable, otherwise false
33 def sortable?
34 !sortable.nil?
35 end
31 36 end
32 37
33 38 class QueryCustomFieldColumn < QueryColumn
@@ -52,6 +57,7 class Query < ActiveRecord::Base
52 57 belongs_to :user
53 58 serialize :filters
54 59 serialize :column_names
60 serialize :sort_criteria, Array
55 61
56 62 attr_protected :project_id, :user_id
57 63
@@ -261,6 +267,27 class Query < ActiveRecord::Base
261 267 column_names.nil? || column_names.empty?
262 268 end
263 269
270 def sort_criteria=(arg)
271 c = []
272 if arg.is_a?(Hash)
273 arg = arg.keys.sort.collect {|k| arg[k]}
274 end
275 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
276 write_attribute(:sort_criteria, c)
277 end
278
279 def sort_criteria
280 read_attribute(:sort_criteria) || []
281 end
282
283 def sort_criteria_key(arg)
284 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
285 end
286
287 def sort_criteria_order(arg)
288 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
289 end
290
264 291 def project_statement
265 292 project_clauses = []
266 293 if project && !@project.descendants.active.empty?
@@ -25,5 +25,14
25 25 <%= render :partial => 'queries/filters', :locals => {:query => query}%>
26 26 </fieldset>
27 27
28 <fieldset><legend><%= l(:label_sort) %></legend>
29 <% 3.times do |i| %>
30 <%= i+1 %>: <%= select_tag("query[sort_criteria][#{i}][]",
31 options_for_select([[]] + query.available_columns.select(&:sortable?).collect {|column| [column.caption, column.name.to_s]}, @query.sort_criteria_key(i))) %>
32 <%= select_tag("query[sort_criteria][#{i}][]",
33 options_for_select([[], [l(:label_ascending), 'asc'], [l(:label_descending), 'desc']], @query.sort_criteria_order(i))) %><br />
34 <% end %>
35 </fieldset>
36
28 37 <%= render :partial => 'queries/columns', :locals => {:query => query}%>
29 38 </div>
@@ -778,3 +778,6 bg:
778 778 field_identity_url: OpenID URL
779 779 label_login_with_open_id_option: or login with OpenID
780 780 field_content: Content
781 label_descending: Descending
782 label_sort: Sort
783 label_ascending: Ascending
@@ -779,3 +779,6 ca:
779 779 field_identity_url: OpenID URL
780 780 label_login_with_open_id_option: or login with OpenID
781 781 field_content: Content
782 label_descending: Descending
783 label_sort: Sort
784 label_ascending: Ascending
@@ -783,3 +783,6 cs:
783 783 field_identity_url: OpenID URL
784 784 label_login_with_open_id_option: or login with OpenID
785 785 field_content: Content
786 label_descending: Descending
787 label_sort: Sort
788 label_ascending: Ascending
@@ -811,3 +811,6 da:
811 811 setting_per_page_options: Objects per page options
812 812 mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:"
813 813 field_content: Content
814 label_descending: Descending
815 label_sort: Sort
816 label_ascending: Ascending
@@ -810,3 +810,6 de:
810 810 field_identity_url: OpenID URL
811 811 label_login_with_open_id_option: or login with OpenID
812 812 field_content: Content
813 label_descending: Descending
814 label_sort: Sort
815 label_ascending: Ascending
@@ -662,6 +662,9 en:
662 662 label_issue_watchers: Watchers
663 663 label_example: Example
664 664 label_display: Display
665 label_sort: Sort
666 label_ascending: Ascending
667 label_descending: Descending
665 668
666 669 button_login: Login
667 670 button_submit: Submit
@@ -831,3 +831,6 es:
831 831 field_identity_url: OpenID URL
832 832 label_login_with_open_id_option: or login with OpenID
833 833 field_content: Content
834 label_descending: Descending
835 label_sort: Sort
836 label_ascending: Ascending
@@ -821,3 +821,6 fi:
821 821 field_identity_url: OpenID URL
822 822 label_login_with_open_id_option: or login with OpenID
823 823 field_content: Content
824 label_descending: Descending
825 label_sort: Sort
826 label_ascending: Ascending
@@ -694,6 +694,9 fr:
694 694 label_issue_watchers: Observateurs
695 695 label_example: Exemple
696 696 label_display: Affichage
697 label_sort: Tri
698 label_ascending: Croissant
699 label_descending: DΓ©croissant
697 700
698 701 button_login: Connexion
699 702 button_submit: Soumettre
@@ -810,3 +810,6 gl:
810 810 field_identity_url: OpenID URL
811 811 label_login_with_open_id_option: or login with OpenID
812 812 field_content: Content
813 label_descending: Descending
814 label_sort: Sort
815 label_ascending: Ascending
@@ -793,3 +793,6 he:
793 793 field_identity_url: OpenID URL
794 794 label_login_with_open_id_option: or login with OpenID
795 795 field_content: Content
796 label_descending: Descending
797 label_sort: Sort
798 label_ascending: Ascending
@@ -816,3 +816,6
816 816 field_identity_url: OpenID URL
817 817 label_login_with_open_id_option: bejelentkezΓ©s OpenID hasznΓ‘latΓ‘val
818 818 field_content: Content
819 label_descending: Descending
820 label_sort: Sort
821 label_ascending: Ascending
@@ -796,3 +796,6 it:
796 796 field_identity_url: OpenID URL
797 797 label_login_with_open_id_option: or login with OpenID
798 798 field_content: Content
799 label_descending: Descending
800 label_sort: Sort
801 label_ascending: Ascending
@@ -809,3 +809,6 ja:
809 809 field_identity_url: OpenID URL
810 810 label_login_with_open_id_option: or login with OpenID
811 811 field_content: Content
812 label_descending: Descending
813 label_sort: Sort
814 label_ascending: Ascending
@@ -840,3 +840,6 ko:
840 840 field_identity_url: OpenID URL
841 841 label_login_with_open_id_option: or login with OpenID
842 842 field_content: Content
843 label_descending: Descending
844 label_sort: Sort
845 label_ascending: Ascending
@@ -821,3 +821,6 lt:
821 821 field_identity_url: OpenID URL
822 822 label_login_with_open_id_option: or login with OpenID
823 823 field_content: Content
824 label_descending: Descending
825 label_sort: Sort
826 label_ascending: Ascending
@@ -766,3 +766,6 nl:
766 766 field_identity_url: OpenID URL
767 767 label_login_with_open_id_option: or login with OpenID
768 768 field_content: Content
769 label_descending: Descending
770 label_sort: Sort
771 label_ascending: Ascending
@@ -783,3 +783,6
783 783 field_identity_url: OpenID URL
784 784 label_login_with_open_id_option: or login with OpenID
785 785 field_content: Content
786 label_descending: Descending
787 label_sort: Sort
788 label_ascending: Ascending
@@ -814,3 +814,6 pl:
814 814 field_identity_url: OpenID URL
815 815 label_login_with_open_id_option: or login with OpenID
816 816 field_content: Content
817 label_descending: Descending
818 label_sort: Sort
819 label_ascending: Ascending
@@ -816,3 +816,6 pt-BR:
816 816 field_identity_url: OpenID URL
817 817 label_login_with_open_id_option: ou use o OpenID
818 818 field_content: Content
819 label_descending: Descending
820 label_sort: Sort
821 label_ascending: Ascending
@@ -802,3 +802,6 pt:
802 802 field_identity_url: OpenID URL
803 803 label_login_with_open_id_option: or login with OpenID
804 804 field_content: Content
805 label_descending: Descending
806 label_sort: Sort
807 label_ascending: Ascending
@@ -823,3 +823,6 ro:
823 823 field_identity_url: OpenID URL
824 824 label_login_with_open_id_option: or login with OpenID
825 825 field_content: Content
826 label_descending: Descending
827 label_sort: Sort
828 label_ascending: Ascending
@@ -909,3 +909,6 ru:
909 909 field_identity_url: OpenID URL
910 910 label_login_with_open_id_option: or login with OpenID
911 911 field_content: Content
912 label_descending: Descending
913 label_sort: Sort
914 label_ascending: Ascending
@@ -782,3 +782,6 sk:
782 782 field_identity_url: OpenID URL
783 783 label_login_with_open_id_option: or login with OpenID
784 784 field_content: Content
785 label_descending: Descending
786 label_sort: Sort
787 label_ascending: Ascending
@@ -780,3 +780,6 sl:
780 780 field_identity_url: OpenID URL
781 781 label_login_with_open_id_option: or login with OpenID
782 782 field_content: Content
783 label_descending: Descending
784 label_sort: Sort
785 label_ascending: Ascending
@@ -804,3 +804,6
804 804 field_identity_url: OpenID URL
805 805 label_login_with_open_id_option: or login with OpenID
806 806 field_content: Content
807 label_descending: Descending
808 label_sort: Sort
809 label_ascending: Ascending
@@ -838,3 +838,6 sv:
838 838 enumeration_doc_categories: Dokumentkategorier
839 839 enumeration_activities: Aktiviteter (tidsuppfΓΆljning)
840 840 field_content: Content
841 label_descending: Descending
842 label_sort: Sort
843 label_ascending: Ascending
@@ -781,3 +781,6 th:
781 781 field_identity_url: OpenID URL
782 782 label_login_with_open_id_option: or login with OpenID
783 783 field_content: Content
784 label_descending: Descending
785 label_sort: Sort
786 label_ascending: Ascending
@@ -817,3 +817,6 tr:
817 817 field_identity_url: OpenID URL
818 818 label_login_with_open_id_option: or login with OpenID
819 819 field_content: Content
820 label_descending: Descending
821 label_sort: Sort
822 label_ascending: Ascending
@@ -780,3 +780,6 uk:
780 780 field_identity_url: OpenID URL
781 781 label_login_with_open_id_option: or login with OpenID
782 782 field_content: Content
783 label_descending: Descending
784 label_sort: Sort
785 label_ascending: Ascending
@@ -850,3 +850,6 vi:
850 850 field_identity_url: OpenID URL
851 851 label_login_with_open_id_option: or login with OpenID
852 852 field_content: Content
853 label_descending: Descending
854 label_sort: Sort
855 label_ascending: Ascending
@@ -888,3 +888,6
888 888 enumeration_doc_categories: ζ–‡δ»Άεˆ†ι‘ž
889 889 enumeration_activities: ζ΄»ε‹• (ζ™‚ι–“θΏ½θΉ€)
890 890 field_content: Content
891 label_descending: Descending
892 label_sort: Sort
893 label_ascending: Ascending
@@ -813,3 +813,6 zh:
813 813 enumeration_doc_categories: ζ–‡ζ‘£η±»εˆ«
814 814 enumeration_activities: ζ΄»εŠ¨οΌˆζ—Άι—΄θ·ŸθΈͺοΌ‰
815 815 field_content: Content
816 label_descending: Descending
817 label_sort: Sort
818 label_ascending: Ascending
@@ -67,3 +67,23 queries_004:
67 67
68 68 user_id: 2
69 69 column_names:
70 queries_005:
71 id: 5
72 project_id:
73 is_public: true
74 name: Open issues by priority and tracker
75 filters: |
76 status_id:
77 :values:
78 - "1"
79 :operator: o
80
81 user_id: 1
82 column_names:
83 sort_criteria: |
84 ---
85 - - priority
86 - desc
87 - - tracker
88 - asc
89 No newline at end of file
@@ -111,6 +111,22 class QueriesControllerTest < Test::Unit::TestCase
111 111 assert q.valid?
112 112 end
113 113
114 def test_new_with_sort
115 @request.session[:user_id] = 1
116 post :new,
117 :confirm => '1',
118 :default_columns => '1',
119 :operators => {"status_id" => "o"},
120 :values => {"status_id" => ["1"]},
121 :query => {:name => "test_new_with_sort",
122 :is_public => "1",
123 :sort_criteria => {"0" => ["due_date", "desc"], "1" => ["tracker", ""]}}
124
125 query = Query.find_by_name("test_new_with_sort")
126 assert_not_nil query
127 assert_equal [['due_date', 'desc'], ['tracker', 'asc']], query.sort_criteria
128 end
129
114 130 def test_get_edit_global_public_query
115 131 @request.session[:user_id] = 1
116 132 get :edit, :id => 4
@@ -202,6 +218,19 class QueriesControllerTest < Test::Unit::TestCase
202 218 :disabled => 'disabled' }
203 219 end
204 220
221 def test_get_edit_sort_criteria
222 @request.session[:user_id] = 1
223 get :edit, :id => 5
224 assert_response :success
225 assert_template 'edit'
226 assert_tag :tag => 'select', :attributes => { :name => 'query[sort_criteria][0][]' },
227 :child => { :tag => 'option', :attributes => { :value => 'priority',
228 :selected => 'selected' } }
229 assert_tag :tag => 'select', :attributes => { :name => 'query[sort_criteria][0][]' },
230 :child => { :tag => 'option', :attributes => { :value => 'desc',
231 :selected => 'selected' } }
232 end
233
205 234 def test_destroy
206 235 @request.session[:user_id] = 2
207 236 post :destroy, :id => 1
@@ -195,6 +195,31 class QueryTest < Test::Unit::TestCase
195 195 assert q.has_column?(c)
196 196 end
197 197
198 def test_default_sort
199 q = Query.new
200 assert_equal [], q.sort_criteria
201 end
202
203 def test_set_sort_criteria_with_hash
204 q = Query.new
205 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
206 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
207 end
208
209 def test_set_sort_criteria_with_array
210 q = Query.new
211 q.sort_criteria = [['priority', 'desc'], 'tracker']
212 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
213 end
214
215 def test_create_query_with_sort
216 q = Query.new(:name => 'Sorted')
217 q.sort_criteria = [['priority', 'desc'], 'tracker']
218 assert q.save
219 q.reload
220 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
221 end
222
198 223 def test_sort_by_string_custom_field_asc
199 224 q = Query.new
200 225 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
General Comments 0
You need to be logged in to leave comments. Login now