Project

General

Profile

« Previous | Next » 

Revision 2572

Ability to save "sort order" in custom queries (#2899).

View differences:

trunk/app/controllers/issues_controller.rb
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 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
trunk/app/helpers/sort_helper.rb
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 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).
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']]
117 136
  #
118
  # - default_key is a column attribute name.
119
  # - default_order is 'asc' or 'desc'.
120
  #
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 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 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'
trunk/app/models/query.rb
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 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 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 && [email protected]?
trunk/app/views/queries/_form.rhtml
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>
trunk/config/locales/bg.yml
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
trunk/config/locales/ca.yml
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
trunk/config/locales/cs.yml
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
trunk/config/locales/da.yml
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
trunk/config/locales/de.yml
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
trunk/config/locales/en.yml
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
trunk/config/locales/es.yml
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
trunk/config/locales/fi.yml
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
trunk/config/locales/fr.yml
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
trunk/config/locales/gl.yml
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
trunk/config/locales/he.yml
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
trunk/config/locales/hu.yml
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
trunk/config/locales/it.yml
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
trunk/config/locales/ja.yml
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
trunk/config/locales/ko.yml
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
trunk/config/locales/lt.yml
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
trunk/config/locales/nl.yml
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
trunk/config/locales/no.yml
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
trunk/config/locales/pl.yml
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
trunk/config/locales/pt-BR.yml
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
trunk/config/locales/pt.yml
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
trunk/config/locales/ro.yml
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
trunk/config/locales/ru.yml
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
trunk/config/locales/sk.yml
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
trunk/config/locales/sl.yml
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
trunk/config/locales/sr.yml
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
trunk/config/locales/sv.yml
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
trunk/config/locales/th.yml
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
trunk/config/locales/tr.yml
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
trunk/config/locales/uk.yml
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
trunk/config/locales/vi.yml
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
trunk/config/locales/zh-TW.yml
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
trunk/config/locales/zh.yml
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
trunk/db/migrate/20090312172426_add_queries_sort_criteria.rb
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
0 10

  
trunk/test/fixtures/queries.yml
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
  
trunk/test/functional/queries_controller_test.rb
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 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
trunk/test/unit/query_test.rb
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' }

Also available in: Unified diff