Add `in:library` syntax to search (#26760)

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
Eugen Rochko 2023-09-04 17:20:35 +02:00 committed by GitHub
parent ac3f310f4b
commit ece1ff77d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 98 additions and 60 deletions

View File

@ -9,23 +9,90 @@ class SearchQueryTransformer < Parslet::Transform
before before
after after
during during
in
).freeze ).freeze
class Query class Query
attr_reader :must_not_clauses, :must_clauses, :filter_clauses def initialize(clauses, options = {})
raise ArgumentError if options[:current_account].nil?
def initialize(clauses) @clauses = clauses
grouped = clauses.compact.chunk(&:operator).to_h @options = options
@must_not_clauses = grouped.fetch(:must_not, [])
@must_clauses = grouped.fetch(:must, []) flags_from_clauses!
@filter_clauses = grouped.fetch(:filter, [])
end end
def apply(search) def request
search = Chewy::Search::Request.new(*indexes).filter(default_filter)
must_clauses.each { |clause| search = search.query.must(clause.to_query) } must_clauses.each { |clause| search = search.query.must(clause.to_query) }
must_not_clauses.each { |clause| search = search.query.must_not(clause.to_query) } must_not_clauses.each { |clause| search = search.query.must_not(clause.to_query) }
filter_clauses.each { |clause| search = search.filter(**clause.to_query) } filter_clauses.each { |clause| search = search.filter(**clause.to_query) }
search.query.minimum_should_match(1)
search
end
private
def clauses_by_operator
@clauses_by_operator ||= @clauses.compact.chunk(&:operator).to_h
end
def flags_from_clauses!
@flags = clauses_by_operator.fetch(:flag, []).to_h { |clause| [clause.prefix, clause.term] }
end
def must_clauses
clauses_by_operator.fetch(:must, [])
end
def must_not_clauses
clauses_by_operator.fetch(:must_not, [])
end
def filter_clauses
clauses_by_operator.fetch(:filter, [])
end
def indexes
case @flags['in']
when 'library'
[StatusesIndex]
else
[PublicStatusesIndex, StatusesIndex]
end
end
def default_filter
{
bool: {
should: [
{
term: {
_index: PublicStatusesIndex.index_name,
},
},
{
bool: {
must: [
{
term: {
_index: StatusesIndex.index_name,
},
},
{
term: {
searchable_by: @options[:current_account].id,
},
},
],
},
},
],
minimum_should_match: 1,
},
}
end end
end end
@ -108,6 +175,9 @@ class SearchQueryTransformer < Parslet::Transform
@filter = :created_at @filter = :created_at
@type = :range @type = :range
@term = { gte: term, lte: term, time_zone: @options[:current_account]&.user_time_zone.presence || 'UTC' } @term = { gte: term, lte: term, time_zone: @options[:current_account]&.user_time_zone.presence || 'UTC' }
when 'in'
@operator = :flag
@term = term
else else
raise "Unknown prefix: #{prefix}" raise "Unknown prefix: #{prefix}"
end end
@ -176,6 +246,6 @@ class SearchQueryTransformer < Parslet::Transform
end end
rule(query: sequence(:clauses)) do rule(query: sequence(:clauses)) do
Query.new(clauses) Query.new(clauses, current_account: current_account)
end end
end end

View File

@ -14,20 +14,8 @@ class StatusesSearchService < BaseService
private private
def status_search_results def status_search_results
definition = parsed_query.apply( request = parsed_query.request
Chewy::Search::Request.new(StatusesIndex, PublicStatusesIndex).filter( results = request.collapse(field: :id).order(id: { order: :desc }).limit(@limit).offset(@offset).objects.compact
bool: {
should: [
publicly_searchable,
non_publicly_searchable,
],
minimum_should_match: 1,
}
)
)
results = definition.collapse(field: :id).order(id: { order: :desc }).limit(@limit).offset(@offset).objects.compact
account_ids = results.map(&:account_id) account_ids = results.map(&:account_id)
account_domains = results.map(&:account_domain) account_domains = results.map(&:account_domain)
preloaded_relations = @account.relations_map(account_ids, account_domains) preloaded_relations = @account.relations_map(account_ids, account_domains)
@ -37,27 +25,6 @@ class StatusesSearchService < BaseService
[] []
end end
def publicly_searchable
{
term: { _index: PublicStatusesIndex.index_name },
}
end
def non_publicly_searchable
{
bool: {
must: [
{
term: { _index: StatusesIndex.index_name },
},
{
term: { searchable_by: @account.id },
},
],
},
}
end
def parsed_query def parsed_query
SearchQueryTransformer.new.apply(SearchQueryParser.new.parse(@query), current_account: @account) SearchQueryTransformer.new.apply(SearchQueryParser.new.parse(@query), current_account: @account)
end end

View File

@ -3,17 +3,18 @@
require 'rails_helper' require 'rails_helper'
describe SearchQueryTransformer do describe SearchQueryTransformer do
subject { described_class.new.apply(parser, current_account: nil) } subject { described_class.new.apply(parser, current_account: account) }
let(:account) { Fabricate(:account) }
let(:parser) { SearchQueryParser.new.parse(query) } let(:parser) { SearchQueryParser.new.parse(query) }
context 'with "hello world"' do context 'with "hello world"' do
let(:query) { 'hello world' } let(:query) { 'hello world' }
it 'transforms clauses' do it 'transforms clauses' do
expect(subject.must_clauses.map(&:term)).to match_array %w(hello world) expect(subject.send(:must_clauses).map(&:term)).to match_array %w(hello world)
expect(subject.must_not_clauses).to be_empty expect(subject.send(:must_not_clauses)).to be_empty
expect(subject.filter_clauses).to be_empty expect(subject.send(:filter_clauses)).to be_empty
end end
end end
@ -21,9 +22,9 @@ describe SearchQueryTransformer do
let(:query) { 'hello -world' } let(:query) { 'hello -world' }
it 'transforms clauses' do it 'transforms clauses' do
expect(subject.must_clauses.map(&:term)).to match_array %w(hello) expect(subject.send(:must_clauses).map(&:term)).to match_array %w(hello)
expect(subject.must_not_clauses.map(&:term)).to match_array %w(world) expect(subject.send(:must_not_clauses).map(&:term)).to match_array %w(world)
expect(subject.filter_clauses).to be_empty expect(subject.send(:filter_clauses)).to be_empty
end end
end end
@ -31,9 +32,9 @@ describe SearchQueryTransformer do
let(:query) { 'hello is:reply' } let(:query) { 'hello is:reply' }
it 'transforms clauses' do it 'transforms clauses' do
expect(subject.must_clauses.map(&:term)).to match_array %w(hello) expect(subject.send(:must_clauses).map(&:term)).to match_array %w(hello)
expect(subject.must_not_clauses).to be_empty expect(subject.send(:must_not_clauses)).to be_empty
expect(subject.filter_clauses.map(&:term)).to match_array %w(reply) expect(subject.send(:filter_clauses).map(&:term)).to match_array %w(reply)
end end
end end
@ -41,9 +42,9 @@ describe SearchQueryTransformer do
let(:query) { 'foo: bar' } let(:query) { 'foo: bar' }
it 'transforms clauses' do it 'transforms clauses' do
expect(subject.must_clauses.map(&:term)).to match_array %w(foo bar) expect(subject.send(:must_clauses).map(&:term)).to match_array %w(foo bar)
expect(subject.must_not_clauses).to be_empty expect(subject.send(:must_not_clauses)).to be_empty
expect(subject.filter_clauses).to be_empty expect(subject.send(:filter_clauses)).to be_empty
end end
end end
@ -51,9 +52,9 @@ describe SearchQueryTransformer do
let(:query) { 'foo:bar' } let(:query) { 'foo:bar' }
it 'transforms clauses' do it 'transforms clauses' do
expect(subject.must_clauses.map(&:term)).to contain_exactly('foo bar') expect(subject.send(:must_clauses).map(&:term)).to contain_exactly('foo bar')
expect(subject.must_not_clauses).to be_empty expect(subject.send(:must_not_clauses)).to be_empty
expect(subject.filter_clauses).to be_empty expect(subject.send(:filter_clauses)).to be_empty
end end
end end
end end