1
0
mirror of https://github.com/meineerde/redmine.git synced 2026-02-01 03:57:15 +00:00

"contains any of" operator for text filters to perform OR search of multiple terms (#38435).

Patch by Go MAEDA.


git-svn-id: https://svn.redmine.org/redmine/trunk@22188 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Go MAEDA 2023-04-14 23:50:26 +00:00
parent c54070cf18
commit 404a5b1de0
4 changed files with 53 additions and 5 deletions

View File

@ -791,8 +791,14 @@ class IssueQuery < Query
projects = nil
end
is_all_words =
case operator
when '~' then true
when '|~', '!~' then false
end
fetcher = Redmine::Search::Fetcher.new(
question, User.current, ['issue'], projects, all_words: (operator != '!~'), attachments: '0'
question, User.current, ['issue'], projects, all_words: is_all_words, attachments: '0'
)
ids = fetcher.result_ids.map(&:last)
if ids.present?

View File

@ -306,6 +306,7 @@ class Query < ActiveRecord::Base
"t-" => :label_ago,
"~" => :label_contains,
"!~" => :label_not_contains,
"|~" => :label_contains_any_of,
"^" => :label_starts_with,
"$" => :label_ends_with,
"=p" => :label_any_issues_in_project,
@ -323,9 +324,9 @@ class Query < ActiveRecord::Base
:list_subprojects => [ "*", "!*", "=", "!" ],
:date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "nd", "t", "ld", "nw", "w", "lw", "l2w", "nm", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
:date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
:string => [ "~", "=", "!~", "!", "^", "$", "!*", "*" ],
:text => [ "~", "!~", "^", "$", "!*", "*" ],
:search => [ "~", "!~" ],
:string => [ "~", "|~", "=", "!~", "!", "^", "$", "!*", "*" ],
:text => [ "~", "|~", "!~", "^", "$", "!*", "*" ],
:search => [ "~", "|~", "!~" ],
:integer => [ "=", ">=", "<=", "><", "!*", "*" ],
:float => [ "=", ">=", "<=", "><", "!*", "*" ],
:relation => ["=", "!", "=p", "=!p", "!p", "*o", "!o", "!*", "*"],
@ -1431,6 +1432,8 @@ class Query < ActiveRecord::Base
sql = sql_contains("#{db_table}.#{db_field}", value.first)
when "!~"
sql = sql_contains("#{db_table}.#{db_field}", value.first, :match => false)
when "|~"
sql = sql_contains("#{db_table}.#{db_field}", value.first, :all_words => false)
when "^"
sql = sql_contains("#{db_table}.#{db_field}", value.first, :starts_with => true)
when "$"
@ -1443,6 +1446,12 @@ class Query < ActiveRecord::Base
end
# Returns a SQL LIKE statement with wildcards
#
# valid options:
# * :match - use NOT LIKE if false
# * :starts_with - use LIKE 'value%' if true
# * :ends_with - use LIKE '%value' if true
# * :all_words - use OR instead of AND if false
def sql_contains(db_field, value, options={})
options = {} unless options.is_a?(Hash)
options.symbolize_keys!
@ -1465,10 +1474,11 @@ class Query < ActiveRecord::Base
def self.tokenized_like_conditions(db_field, value, **options)
tokens = Redmine::Search::Tokenizer.new(value).tokens
tokens = [value] unless tokens.present?
logical_opr = options.delete(:all_words) == false ? ' OR ' : ' AND '
sql, values = tokens.map do |token|
[Redmine::Database.like(db_field, '?', options), "%#{sanitize_sql_like token}%"]
end.transpose
[sql.join(" AND "), *values]
[sql.join(logical_opr), *values]
end
# rubocop:enable Lint/IneffectiveAccessModifier

View File

@ -810,6 +810,7 @@ en:
label_more_than_ago: more than days ago
label_ago: days ago
label_contains: contains
label_contains_any_of: contains any of
label_not_contains: doesn't contain
label_starts_with: starts with
label_ends_with: ends with

View File

@ -736,6 +736,37 @@ class QueryTest < ActiveSupport::TestCase
assert_not_include issue, result
end
def test_operator_contains_any_of
User.current = User.find(1)
query = IssueQuery.new(
:name => '_',
:filters => {
'subject' => {
:operator => '|~',
:values => ['close block']
}
}
)
result = find_issues_with_query(query)
assert_equal [8, 9, 10, 11, 12], result.map(&:id).sort
result.each {|issue| assert issue.subject =~ /(close|block)/i}
end
def test_operator_contains_any_of_with_any_searchable_text
User.current = User.find(1)
query = IssueQuery.new(
:name => '_',
:filters => {
'any_searchable' => {
:operator => '|~',
:values => ['recipe categories']
}
}
)
result = find_issues_with_query(query)
assert_equal [1, 2, 3], result.map(&:id).sort
end
def test_range_for_this_week_with_week_starting_on_monday
I18n.locale = :fr
assert_equal '1', I18n.t(:general_first_day_of_week)