Project

General

Profile

Feature #38435 » contains-any-of.patch

Go MAEDA, 2023-04-12 05:23

View differences:

app/models/issue_query.rb
791 791
      projects = nil
792 792
    end
793 793

  
794
    is_all_words =
795
      case operator
796
      when '~'        then true
797
      when '|~', '!~' then false
798
      end
799

  
794 800
    fetcher = Redmine::Search::Fetcher.new(
795
      question, User.current, ['issue'], projects, all_words: (operator != '!~'), attachments: '0'
801
      question, User.current, ['issue'], projects, all_words: is_all_words, attachments: '0'
796 802
    )
797 803
    ids = fetcher.result_ids.map(&:last)
798 804
    if ids.present?
app/models/query.rb
306 306
    "t-"  => :label_ago,
307 307
    "~"   => :label_contains,
308 308
    "!~"  => :label_not_contains,
309
    "|~"  => :label_contains_any_of,
309 310
    "^"   => :label_starts_with,
310 311
    "$"   => :label_ends_with,
311 312
    "=p"  => :label_any_issues_in_project,
......
323 324
    :list_subprojects => [ "*", "!*", "=", "!" ],
324 325
    :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "nd", "t", "ld", "nw", "w", "lw", "l2w", "nm", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
325 326
    :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
326
    :string => [ "~", "=", "!~", "!", "^", "$", "!*", "*" ],
327
    :text => [  "~", "!~", "^", "$", "!*", "*" ],
328
    :search => [ "~", "!~" ],
327
    :string => [ "~", "|~", "=", "!~", "!", "^", "$", "!*", "*" ],
328
    :text => [  "~", "|~", "!~", "^", "$", "!*", "*" ],
329
    :search => [ "~", "|~", "!~" ],
329 330
    :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
330 331
    :float => [ "=", ">=", "<=", "><", "!*", "*" ],
331 332
    :relation => ["=", "!", "=p", "=!p", "!p", "*o", "!o", "!*", "*"],
......
1431 1432
      sql = sql_contains("#{db_table}.#{db_field}", value.first)
1432 1433
    when "!~"
1433 1434
      sql = sql_contains("#{db_table}.#{db_field}", value.first, :match => false)
1435
    when "|~"
1436
      sql = sql_contains("#{db_table}.#{db_field}", value.first, :all_words => false)
1434 1437
    when "^"
1435 1438
      sql = sql_contains("#{db_table}.#{db_field}", value.first, :starts_with => true)
1436 1439
    when "$"
......
1443 1446
  end
1444 1447

  
1445 1448
  # Returns a SQL LIKE statement with wildcards
1449
  #
1450
  # valid options:
1451
  # * :match - use NOT LIKE if false
1452
  # * :starts_with - use LIKE 'value%' if true
1453
  # * :ends_with - use LIKE '%value' if true
1454
  # * :all_words - use OR instead of AND if false
1446 1455
  def sql_contains(db_field, value, options={})
1447 1456
    options = {} unless options.is_a?(Hash)
1448 1457
    options.symbolize_keys!
......
1465 1474
  def self.tokenized_like_conditions(db_field, value, **options)
1466 1475
    tokens = Redmine::Search::Tokenizer.new(value).tokens
1467 1476
    tokens = [value] unless tokens.present?
1477
    logical_opr = options.delete(:all_words) == false ? ' OR ' : ' AND '
1468 1478
    sql, values = tokens.map do |token|
1469 1479
      [Redmine::Database.like(db_field, '?', options), "%#{sanitize_sql_like token}%"]
1470 1480
    end.transpose
1471
    [sql.join(" AND "), *values]
1481
    [sql.join(logical_opr), *values]
1472 1482
  end
1473 1483
  # rubocop:enable Lint/IneffectiveAccessModifier
1474 1484

  
config/locales/en.yml
810 810
  label_more_than_ago: more than days ago
811 811
  label_ago: days ago
812 812
  label_contains: contains
813
  label_contains_any_of: contains any of
813 814
  label_not_contains: doesn't contain
814 815
  label_starts_with: starts with
815 816
  label_ends_with: ends with
test/unit/query_test.rb
710 710
    assert_not_include issue, result
711 711
  end
712 712

  
713
  def test_operator_contains_any
714
    User.current = User.find(1)
715
    query = IssueQuery.new(
716
      :name => '_',
717
      :filters => {
718
        'subject' => {
719
          :operator => '|~',
720
          :values => ['close block']
721
        }
722
      }
723
    )
724
    result = find_issues_with_query(query)
725
    assert_equal [8, 9, 10, 11, 12], result.map(&:id).sort
726
    result.each {|issue| assert issue.subject =~ /(close|block)/i}
727
  end
728

  
729
  def test_operator_contains_any_with_any_searchable_text
730
    User.current = User.find(1)
731
    query = IssueQuery.new(
732
      :name => '_',
733
      :filters => {
734
        'any_searchable' => {
735
          :operator => '|~',
736
          :values => ['recipe categories']
737
        }
738
      }
739
    )
740
    result = find_issues_with_query(query)
741
    assert_equal [1, 2, 3], result.map(&:id).sort
742
  end
743

  
713 744
  def test_range_for_this_week_with_week_starting_on_monday
714 745
    I18n.locale = :fr
715 746
    assert_equal '1', I18n.t(:general_first_day_of_week)
(2-2/2)