From ece1ff77d6b2bde578d3bdaad45589589d96902d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 4 Sep 2023 17:20:35 +0200 Subject: [PATCH] Add `in:library` syntax to search (#26760) Co-authored-by: Claire --- app/lib/search_query_transformer.rb | 88 ++++++++++++++++++++--- app/services/statuses_search_service.rb | 37 +--------- spec/lib/search_query_transformer_spec.rb | 33 ++++----- 3 files changed, 98 insertions(+), 60 deletions(-) diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index af3964fd3..2dc10830d 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -9,23 +9,90 @@ class SearchQueryTransformer < Parslet::Transform before after during + in ).freeze 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) - grouped = clauses.compact.chunk(&:operator).to_h - @must_not_clauses = grouped.fetch(:must_not, []) - @must_clauses = grouped.fetch(:must, []) - @filter_clauses = grouped.fetch(:filter, []) + @clauses = clauses + @options = options + + flags_from_clauses! 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_not_clauses.each { |clause| search = search.query.must_not(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 @@ -108,6 +175,9 @@ class SearchQueryTransformer < Parslet::Transform @filter = :created_at @type = :range @term = { gte: term, lte: term, time_zone: @options[:current_account]&.user_time_zone.presence || 'UTC' } + when 'in' + @operator = :flag + @term = term else raise "Unknown prefix: #{prefix}" end @@ -176,6 +246,6 @@ class SearchQueryTransformer < Parslet::Transform end rule(query: sequence(:clauses)) do - Query.new(clauses) + Query.new(clauses, current_account: current_account) end end diff --git a/app/services/statuses_search_service.rb b/app/services/statuses_search_service.rb index 2317a2a1a..e4b38a9da 100644 --- a/app/services/statuses_search_service.rb +++ b/app/services/statuses_search_service.rb @@ -14,20 +14,8 @@ class StatusesSearchService < BaseService private def status_search_results - definition = parsed_query.apply( - Chewy::Search::Request.new(StatusesIndex, PublicStatusesIndex).filter( - 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 + request = parsed_query.request + results = request.collapse(field: :id).order(id: { order: :desc }).limit(@limit).offset(@offset).objects.compact account_ids = results.map(&:account_id) account_domains = results.map(&:account_domain) preloaded_relations = @account.relations_map(account_ids, account_domains) @@ -37,27 +25,6 @@ class StatusesSearchService < BaseService [] 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 SearchQueryTransformer.new.apply(SearchQueryParser.new.parse(@query), current_account: @account) end diff --git a/spec/lib/search_query_transformer_spec.rb b/spec/lib/search_query_transformer_spec.rb index 17f06d283..4b949b1b8 100644 --- a/spec/lib/search_query_transformer_spec.rb +++ b/spec/lib/search_query_transformer_spec.rb @@ -3,17 +3,18 @@ require 'rails_helper' 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) } context 'with "hello world"' do let(:query) { 'hello world' } it 'transforms clauses' do - expect(subject.must_clauses.map(&:term)).to match_array %w(hello world) - expect(subject.must_not_clauses).to be_empty - expect(subject.filter_clauses).to be_empty + expect(subject.send(:must_clauses).map(&:term)).to match_array %w(hello world) + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses)).to be_empty end end @@ -21,9 +22,9 @@ describe SearchQueryTransformer do let(:query) { 'hello -world' } it 'transforms clauses' do - expect(subject.must_clauses.map(&:term)).to match_array %w(hello) - expect(subject.must_not_clauses.map(&:term)).to match_array %w(world) - expect(subject.filter_clauses).to be_empty + expect(subject.send(:must_clauses).map(&:term)).to match_array %w(hello) + expect(subject.send(:must_not_clauses).map(&:term)).to match_array %w(world) + expect(subject.send(:filter_clauses)).to be_empty end end @@ -31,9 +32,9 @@ describe SearchQueryTransformer do let(:query) { 'hello is:reply' } it 'transforms clauses' do - expect(subject.must_clauses.map(&:term)).to match_array %w(hello) - expect(subject.must_not_clauses).to be_empty - expect(subject.filter_clauses.map(&:term)).to match_array %w(reply) + expect(subject.send(:must_clauses).map(&:term)).to match_array %w(hello) + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses).map(&:term)).to match_array %w(reply) end end @@ -41,9 +42,9 @@ describe SearchQueryTransformer do let(:query) { 'foo: bar' } it 'transforms clauses' do - expect(subject.must_clauses.map(&:term)).to match_array %w(foo bar) - expect(subject.must_not_clauses).to be_empty - expect(subject.filter_clauses).to be_empty + expect(subject.send(:must_clauses).map(&:term)).to match_array %w(foo bar) + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses)).to be_empty end end @@ -51,9 +52,9 @@ describe SearchQueryTransformer do let(:query) { 'foo:bar' } it 'transforms clauses' do - expect(subject.must_clauses.map(&:term)).to contain_exactly('foo bar') - expect(subject.must_not_clauses).to be_empty - expect(subject.filter_clauses).to be_empty + expect(subject.send(:must_clauses).map(&:term)).to contain_exactly('foo bar') + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses)).to be_empty end end end