Project

General

Profile

Feature #41214 » allow-OR-logical-operatori-in-query.patch

Takenori TAKAKI, 2024-09-03 08:52

View differences:

app/models/query.rb
259 259
  VISIBILITY_PRIVATE = 0
260 260
  VISIBILITY_ROLES   = 1
261 261
  VISIBILITY_PUBLIC  = 2
262
  FILTER_CONNECTION_AND = 'AND'
263
  FILTER_CONNECTION_OR = 'OR'
264
  FILTER_CONNECTIONS = [FILTER_CONNECTION_AND, FILTER_CONNECTION_OR]
262 265

  
263 266
  belongs_to :project
264 267
  belongs_to :user
......
455 458
    end
456 459

  
457 460
    query_params = params[:query] || defaults || {}
461
    self.filter_connection = query_params[:filter_connection] || self.filter_connection
458 462
    self.group_by = params[:group_by] || query_params[:group_by] || self.group_by
459 463
    self.column_names = params[:c] || query_params[:column_names] || self.column_names
460 464
    self.totalable_names = params[:t] || query_params[:totalable_names] || self.totalable_names
......
1029 1033
      end
1030 1034
    end if filters and valid?
1031 1035

  
1036
    filters_clauses.reject!(&:blank?)
1037
    filters_clauses = filters_clauses.any? ? [filters_clauses.join(" #{filter_connection} ")] : []
1038

  
1032 1039
    if (c = group_by_column) && c.is_a?(QueryCustomFieldColumn)
1033 1040
      # Excludes results for which the grouped custom field is not visible
1034 1041
      filters_clauses << c.custom_field.visibility_by_project_condition
......
1080 1087
    end
1081 1088
  end
1082 1089

  
1090
  def filter_connection
1091
    if FILTER_CONNECTIONS.include?(options[:filter_connection].to_s)
1092
      options[:filter_connection]
1093
    else
1094
      FILTER_CONNECTION_AND
1095
    end
1096
  end
1097

  
1098
  def filter_connection=(connection)
1099
    if FILTER_CONNECTIONS.include?(connection)
1100
      options[:filter_connection] = connection
1101
    else
1102
      options.delete(:filter_connection)
1103
    end
1104
  end
1105

  
1083 1106
  def display_type
1084 1107
    options[:display_type] || self.default_display_type
1085 1108
  end
app/views/queries/_filters.html.erb
15 15
<% end %>
16 16

  
17 17
<div id="filters-table">
18
  <div class="filter">
19
    <div class="field"><label for='filter_connection'><%= l(:field_filter_connection) %></label></div>
20
    <%= hidden_field_tag 'selected_filter_connection', @query.filter_connection %>
21
    <%= select_tag 'query[filter_connection]', options_for_select(Query::FILTER_CONNECTIONS.map{|c| [l("label_filter_connection_#{c.downcase}"), c]}, @query.filter_connection) %>
22
  </div>
18 23
</div>
19 24

  
20 25
<div class="add-filter">
config/locales/en.yml
791 791
  label_filter_plural: Filters
792 792
  label_equals: is
793 793
  label_not_equals: is not
794
  field_filter_connection: Filter Connection
795
  label_filter_connection_and: (AND) Match all filters
796
  label_filter_connection_or: (OR) Match any filter
794 797
  label_in_less_than: in less than
795 798
  label_in_more_than: in more than
796 799
  label_in_the_next_days: in the next
config/locales/ja.yml
625 625
  label_filter_plural: フィルタ
626 626
  label_equals: 等しい
627 627
  label_not_equals: 等しくない
628
  field_filter_connection: フィルタの接続条件
629
  label_filter_connection_and: (AND) すべてのフィルタに一致
630
  label_filter_connection_or: (OR) いずれかのフィルタに一致
628 631
  label_in_less_than: N日後以前
629 632
  label_in_more_than: N日後以降
630 633
  label_greater_or_equal: 以上
test/functional/issues_controller_test.rb
344 344
    assert_not_include 4, issues
345 345
  end
346 346

  
347
  def test_index_with_filters_using_filter_connection_and
348
    Issue.destroy_all
349
    issues_filterd = [
350
      Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'Issue created by user 2 and assigned_to user 2', :author_id => 2, :assigned_to_id => 2)
351
    ]
352
    issues_not_filtered = [
353
      Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'Issue created by user 2', :author_id => 2, :assigned_to_id => nil),
354
      Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'issue assisgned to user 2', :author_id => 3, :assigned_to_id => 2)
355
    ]
356

  
357
    get(
358
      :index,
359
      :params => {
360
        :project_id => 1,
361
        :set_filter => 1,
362
        :query => { :filter_connection => 'AND', },
363
        :f => ['author_id', 'assigned_to_id'],
364
        :op => { 'author_id' => '=', 'assigned_to_id' => '=' },
365
        :v => { 'author_id' => ['2'], 'assigned_to_id' => ['2'] }
366
      }
367
    )
368
    assert_response :success
369

  
370
    issues = issues_in_list.map(&:id).uniq.sort
371
    issues_filterd.each do |issue|
372
      assert_include issue.id, issues
373
    end
374
    issues_not_filtered.each do |issue|
375
      assert_not_include issue.id, issues
376
    end
377
  end
378

  
379
  def test_index_with_filters_using_filter_connection_or
380
    Issue.destroy_all
381
    issues_filterd = [
382
      Issue.create!(:project_id => 1, :tracker_id => 1,
383
                    :subject => 'Issue created by user 2',
384
                    :author_id => 2, :assigned_to_id => nil),
385
      Issue.create!(:project_id => 1, :tracker_id => 1,
386
                    :subject => 'issue assisgned to user 2',
387
                    :author_id => 3, :assigned_to_id => 2)
388
    ]
389
    issues_not_filtered = [
390
      Issue.create!(:project_id => 1, :tracker_id => 1,
391
                    :subject => 'Issue created by user 3',
392
                    :author_id => 3, :assigned_to_id => nil)
393
    ]
394

  
395
    get(
396
      :index,
397
      :params => {
398
        :project_id => 1,
399
        :set_filter => 1,
400
        :query => { :filter_connection => 'OR' },
401
        :f => ['author_id', 'assigned_to_id'],
402
        :op => { 'author_id' => '=', 'assigned_to_id' => '=' },
403
        :v => { 'author_id' => ['2'], 'assigned_to_id' => ['2'] }
404
      }
405
    )
406
    assert_response :success
407

  
408
    issues = issues_in_list.map(&:id).uniq.sort
409
    issues_filterd.each do |issue|
410
      assert_include issue.id, issues
411
    end
412
    issues_not_filtered.each do |issue|
413
      assert_not_include issue.id, issues
414
    end
415
  end
416

  
347 417
  def test_index_with_query
348 418
    get(
349 419
      :index,
test/unit/query_test.rb
2031 2031
    assert_nil q.statement
2032 2032
  end
2033 2033

  
2034
  def test_statement_should_connect_filters_with_and_when_filter_connection_is_blank
2035
    q = IssueQuery.new(:name => '_')
2036
    q.filters = {
2037
      'status_id' => {:operator => '=', :values => ['1']},
2038
      'priority_id' => {:operator => '=', :values => ['4']}
2039
    }
2040

  
2041
    assert q.valid?
2042
    assert_equal "(issues.status_id IN ('1')) AND (issues.priority_id IN ('4'))", q.statement
2043
  end
2044

  
2045
  def test_statument_should_connect_filters_with_and_when_filter_connection_is_and
2046
    q = IssueQuery.new(:name => '_', :filter_connection => 'AND')
2047
    q.filters = {
2048
      'status_id' => {:operator => '=', :values => ['1']},
2049
      'priority_id' => {:operator => '=', :values => ['4']}
2050
    }
2051

  
2052
    assert q.valid?
2053
    assert_equal "(issues.status_id IN ('1')) AND (issues.priority_id IN ('4'))", q.statement
2054
  end
2055

  
2056
  def test_statement_should_be_changed_connected_with_or_when_filter_connection_is_or
2057
    q = IssueQuery.new(:name => '_', :filter_connection => 'OR')
2058
    q.filters = {
2059
      'status_id' => {:operator => '=', :values => ['1']},
2060
      'priority_id' => {:operator => '=', :values => ['4']}
2061
    }
2062

  
2063
    assert q.valid?
2064
    assert_equal "(issues.status_id IN ('1')) OR (issues.priority_id IN ('4'))", q.statement
2065
  end
2066

  
2034 2067
  def test_available_filters_as_json_should_include_missing_assigned_to_id_values
2035 2068
    user = User.generate!
2036 2069
    with_current_user User.find(1) do
......
3391 3424
    end
3392 3425
  end
3393 3426

  
3427
  def test_filter_connection_should_accept_known_values
3428
    query = IssueQuery.new(:name => '_')
3429
    query.filter_connection = 'AND'
3430
    assert_equal 'AND', query.filter_connection
3431
    query.filter_connection = 'OR'
3432
    assert_equal 'OR', query.filter_connection
3433
  end
3434

  
3435
  def test_filter_connection_should_not_accept_unknown_values
3436
    query = IssueQuery.new(:name => '_')
3437
    query.filter_connection = 'invalid'
3438
    assert_equal 'AND', query.filter_connection
3439
  end
3440

  
3394 3441
  def test_display_type_should_accept_known_types
3395 3442
    query = ProjectQuery.new(:name => '_')
3396 3443
    query.display_type = 'list'
(2-2/2)