Skip to content
Snippets Groups Projects
Verified Commit 57d959b0 authored by Madelein van Niekerk's avatar Madelein van Niekerk :one: Committed by GitLab
Browse files

Merge branch '457724-add-support-for-multi-match-ff' into 'master'

Add es query builder support for multi-match

See merge request !153040



Merged-by: default avatarMadelein van Niekerk <mvanniekerk@gitlab.com>
Approved-by: default avatarMadelein van Niekerk <mvanniekerk@gitlab.com>
Reviewed-by: Terri Chu's avatarTerri Chu <tchu@gitlab.com>
Co-authored-by: Terri Chu's avatarTerri Chu <tchu@gitlab.com>
parents 4208c662 69d75a4e
No related branches found
No related tags found
2 merge requests!162233Draft: Script to update Topology Service Gem,!153040Add es query builder support for multi-match
Pipeline #1295429548 passed
# rubocop:disable Naming/FileName
# frozen_string_literal: true
module Gitlab
module Elastic
BoolExpr = Struct.new(:must, :must_not, :should, :filter) do # rubocop:disable Lint/StructNewOverride
BoolExpr = Struct.new(:must, :must_not, :should, :filter, :minimum_should_match) do # rubocop:disable Lint/StructNewOverride -- existing implementation
def initialize
super
reset!
......@@ -14,6 +13,7 @@ def reset!
self.must_not = []
self.should = []
self.filter = []
self.minimum_should_match = nil
end
def to_h
......@@ -26,5 +26,3 @@ def eql?(other)
end
end
end
# rubocop:enable Naming/FileName
......@@ -15,7 +15,13 @@ def build
# iid field can be added here as lenient option will
# pardon format errors, like integer out of range.
fields = %w[iid^3 title^2 description]
::Search::Elastic::Queries.by_simple_query_string(fields: fields, query: query, options: options)
if Feature.enabled?(:search_uses_match_queries, options[:current_user]) &&
!::Search::Elastic::Queries::ADVANCED_QUERY_SYNTAX_REGEX.match?(query)
::Search::Elastic::Queries.by_multi_match_query(fields: fields, query: query, options: options)
else
::Search::Elastic::Queries.by_simple_query_string(fields: fields, query: query, options: options)
end
end
query_hash = ::Search::Elastic::Filters.by_authorization(query_hash: query_hash, options: options)
......
......@@ -3,11 +3,11 @@
module Search
module Elastic
module Queries
ADVANCED_QUERY_SYNTAX_REGEX = /[+*"\-|()~\\]/
class << self
include ::Elastic::Latest::QueryContext::Aware
AGGREGATION_LIMIT = 500
def by_iid(iid:, doc_type:)
bool_expr = Gitlab::Elastic::BoolExpr.new
bool_expr.filter = [
......@@ -22,27 +22,81 @@ def by_iid(iid:, doc_type:)
}
end
def by_simple_query_string(fields:, query:, options:)
def by_multi_match_query(fields:, query:, options:)
fields = ::Elastic::Latest::CustomLanguageAnalyzers.add_custom_analyzers_fields(fields)
fields = remove_fields_boost(fields) if options[:count_only]
query_hash =
if query.present?
simple_query_string = simple_query_string(fields, query, options)
bool_expr = Gitlab::Elastic::BoolExpr.new
if query.present?
bool_expr = Gitlab::Elastic::BoolExpr.new
unless options[:no_join_project]
bool_expr.filter << {
term: {
type: {
_name: context.name(:doc, :is_a, options[:doc_type]),
value: options[:doc_type]
}
}
}
end
multi_match_bool = Gitlab::Elastic::BoolExpr.new
multi_match_bool.should << multi_match_query(fields, query, options.merge(operator: :or))
multi_match_bool.should << multi_match_query(fields, query, options.merge(operator: :and))
multi_match_bool.should << multi_match_phrase_query(fields, query, options)
multi_match_bool.minimum_should_match = 1
build_bool_query(simple_query_string, options)
if options[:count_only]
bool_expr.filter << { bool: multi_match_bool }
else
bool_expr = Gitlab::Elastic::BoolExpr.new
bool_expr.must = { match_all: {} }
{
query: {
bool: bool_expr
},
track_scores: true
bool_expr.must << { bool: multi_match_bool }
end
else
bool_expr.must = { match_all: {} }
end
query_hash = { query: { bool: bool_expr } }
query_hash[:track_scores] = true unless query.present?
if options[:count_only]
query_hash[:size] = 0
else
query_hash[:highlight] = apply_highlight(fields)
end
query_hash
end
def by_simple_query_string(fields:, query:, options:)
fields = ::Elastic::Latest::CustomLanguageAnalyzers.add_custom_analyzers_fields(fields)
fields = remove_fields_boost(fields) if options[:count_only]
bool_expr = Gitlab::Elastic::BoolExpr.new
if query.present?
unless options[:no_join_project]
bool_expr.filter << {
term: {
type: {
_name: context.name(:doc, :is_a, options[:doc_type]),
value: options[:doc_type]
}
}
}
end
if options[:count_only]
bool_expr.filter << simple_query_string(fields, query, options)
else
bool_expr.must << simple_query_string(fields, query, options)
end
else
bool_expr.must = { match_all: {} }
end
query_hash = { query: { bool: bool_expr } }
query_hash[:track_scores] = true unless query.present?
if options[:count_only]
query_hash[:size] = 0
else
......@@ -70,29 +124,26 @@ def simple_query_string(fields, query, options)
}
end
def build_bool_query(simple_query_string, options)
bool_expr = Gitlab::Elastic::BoolExpr.new
unless options[:no_join_project]
bool_expr.filter << {
term: {
type: {
_name: context.name(:doc, :is_a, options[:doc_type]),
value: options[:doc_type]
}
}
def multi_match_phrase_query(fields, query, options)
{
multi_match: {
_name: context.name(options[:doc_type], :multi_match_phrase, :search_terms),
type: :phrase,
fields: fields,
query: query,
lenient: true
}
end
if options[:count_only]
bool_expr.filter << simple_query_string
else
bool_expr.must << simple_query_string
end
}
end
def multi_match_query(fields, query, options)
{
query: {
bool: bool_expr
multi_match: {
_name: context.name(options[:doc_type], :multi_match, options[:operator], :search_terms),
fields: fields,
query: query,
operator: options[:operator],
lenient: true
}
}
end
......
......@@ -86,6 +86,7 @@
def set_old_schema_version_in_three_documents!
client.update_by_query(index: Elastic::Latest::WikiConfig.index_name, max_docs: 3, refresh: true,
wait_for_completion: true,
body: { script: { lang: 'painless', source: 'ctx._source.schema_version = 2305' } }
)
end
......
......@@ -5,16 +5,16 @@
RSpec.describe Elastic::Latest::IssueClassProxy, :elastic, :sidekiq_inline, feature_category: :global_search do
before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
stub_feature_flags(search_uses_match_queries: false)
end
subject { described_class.new(Issue, use_separate_indices: true) }
subject(:proxy) { described_class.new(Issue, use_separate_indices: true) }
let!(:group) { create(:group) }
let!(:project) { create(:project, :public, group: group) }
let!(:user) { create(:user, developer_of: project) }
let!(:label) { create(:label, project: project) }
let!(:issue) { create(:labeled_issue, title: 'test', project: project, labels: [label]) }
let(:query) { 'test' }
let(:options) do
{
......@@ -33,7 +33,7 @@
shared_examples 'returns aggregations' do
it 'filters by labels' do
result = subject.issue_aggregations('test', options)
result = proxy.issue_aggregations('test', options)
expect(result.first.name).to eq('labels')
expect(result.first.buckets.first.symbolize_keys).to match(
......@@ -59,7 +59,7 @@
end
describe '#elastic_search' do
let(:result) { subject.elastic_search('test', options: options) }
let(:result) { proxy.elastic_search(query, options: options) }
describe 'search on basis of hidden attribute' do
context 'when author of the issue is banned' do
......@@ -79,7 +79,7 @@
it 'current_user is empty then user can not see the issue' do
options[:current_user] = nil
result = subject.elastic_search('test', options: options)
result = proxy.elastic_search('test', options: options)
expect(elasticsearch_hit_ids(result)).not_to include issue.id
end
end
......@@ -100,31 +100,54 @@
it 'current_user is empty then user can see the issue' do
options[:current_user] = nil
result = subject.elastic_search('test', options: options)
result = proxy.elastic_search('test', options: options)
expect(elasticsearch_hit_ids(result)).to include issue.id
end
end
end
describe 'named queries' do
let(:project_ids) { [project.id] }
let(:group_ids) { [] } # TODO - group.id
let(:options) do
{
current_user: user,
project_ids: project_ids,
group_ids: group_ids,
public_and_internal_projects: false,
order_by: nil,
sort: nil,
labels: [label.id]
}
using RSpec::Parameterized::TableSyntax
where(:projects, :groups) do
[] | []
[ref(:project)] | []
[] | [ref(:group)]
[ref(:project)] | [ref(:group)]
end
describe 'hidden filter' do
context 'when user can admin all resources' do
before do
allow(user).to receive(:can_admin_all_resources?).and_return(true)
with_them do
let(:project_ids) { projects.map(&:id) }
let(:group_ids) { groups.map(&:id) }
let(:options) { base_options }
let(:base_options) do
{
current_user: user,
project_ids: project_ids,
group_ids: group_ids,
public_and_internal_projects: false,
order_by: nil,
sort: nil
}
end
describe 'base query' do
shared_examples 'a query that uses simple_query_string' do
it 'includes the correct base query name' do
result.response
assert_named_queries('issue:match:search_terms')
end
end
shared_examples 'a query that uses multi_match' do
it 'includes the correct base query name' do
result.response
assert_named_queries('issue:multi_match:or:search_terms', 'issue:multi_match:and:search_terms',
'issue:multi_match_phrase:search_terms')
end
end
context 'when search_query_builder feature flag is false' do
......@@ -132,106 +155,222 @@
stub_feature_flags(search_query_builder: false)
end
it 'does not filter hidden issues' do
context 'when search_uses_match_queries feature flag is false' do
before do
stub_feature_flags(search_uses_match_queries: false)
end
it_behaves_like 'a query that uses simple_query_string'
end
it_behaves_like 'a query that uses multi_match'
end
context 'when querying by iid' do
let(:query) { '#1' }
it 'includes the correct base query name' do
result.response
assert_named_queries(without: ['issue:hidden:non_hidden'])
assert_named_queries('issue:related:iid', 'doc:is_a:issue')
end
end
it 'does not filter hidden issues' do
result.response
context 'when search_uses_match_queries feature flag is false' do
before do
stub_feature_flags(search_uses_match_queries: false)
end
assert_named_queries(without: ['filters:non_hidden'])
it_behaves_like 'a query that uses simple_query_string'
end
end
context 'when user cannot admin all resources' do
before do
allow(user).to receive(:can_admin_all_resources?).and_return(false)
context 'when using advanced search syntax' do
let(:query) { 'test -banner' }
it_behaves_like 'a query that uses simple_query_string'
end
it_behaves_like 'a query that uses multi_match'
end
describe 'state filter' do
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
it 'filters hidden issues' do
it 'does not filter by state in the query' do
result.response
assert_named_queries('issue:hidden:non_hidden')
assert_named_queries(without: ['issue:match:state'])
end
end
it 'filters hidden issues' do
it 'does not filter by state in the query' do
result.response
assert_named_queries('filters:not_hidden')
assert_named_queries(without: ['filters:state'])
end
end
end
context 'when label filters are passed' do
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
context 'when state option is provided' do
let(:options) { base_options.merge(state: 'opened') }
it 'filters the labels in the query' do
result.response
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
it 'filters by state in the query' do
result.response
assert_named_queries('issue:match:state')
end
end
it 'filters by state in the query' do
result.response
assert_named_queries('issue:match:search_terms', 'issue:filter:label_ids', 'issue:archived:non_archived')
assert_named_queries('filters:state')
end
end
end
it 'filters the labels in the query' do
result.response
describe 'hidden filter' do
context 'when user can admin all resources' do
before do
allow(user).to receive(:can_admin_all_resources?).and_return(true)
end
assert_named_queries('issue:match:search_terms', 'filters:label_ids', 'filters:non_archived')
end
end
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
context 'when include_archived is set' do
let(:options) { { include_archived: true } }
it 'does not filter hidden issues' do
result.response
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
assert_named_queries(without: ['issue:hidden:non_hidden'])
end
end
it 'does not have a filter for archived' do
result.response
it 'does not filter hidden issues' do
result.response
assert_named_queries(without: ['issue:archived:non_archived'])
assert_named_queries(without: ['filters:non_hidden'])
end
end
end
it 'does not have a filter for archived' do
result.response
context 'when user cannot admin all resources' do
before do
allow(user).to receive(:can_admin_all_resources?).and_return(false)
end
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
it 'filters hidden issues' do
result.response
assert_named_queries('issue:hidden:non_hidden')
end
end
assert_named_queries(without: ['filters:non_archived'])
it 'filters hidden issues' do
result.response
assert_named_queries('filters:not_hidden')
end
end
end
end
context 'when include_archived is not set' do
let(:options) { {} }
describe 'label filter' do
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
it 'filters the labels in the query' do
result.response
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
assert_named_queries(without: ['issue:filter:label_ids'])
end
end
it 'does have a filter for archived' do
it 'filters the labels in the query' do
result.response
assert_named_queries('issue:match:search_terms', 'issue:archived:non_archived')
assert_named_queries(without: ['filters:label_ids'])
end
context 'when labels option is provided' do
let(:options) { base_options.merge(labels: [label.id]) }
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
it 'filters the labels in the query' do
result.response
assert_named_queries('issue:filter:label_ids')
end
end
it 'filters the labels in the query' do
result.response
assert_named_queries('filters:label_ids')
end
end
end
it 'does have a filter for archived' do
result.response
describe 'archived filter' do
context 'when include_archived is set' do
let(:options) { { include_archived: true } }
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
it 'does not have a filter for archived' do
result.response
assert_named_queries(without: ['issue:archived:non_archived'])
end
end
it 'does not have a filter for archived' do
result.response
assert_named_queries(without: ['filters:non_archived'])
end
end
context 'when include_archived is not set' do
let(:options) { {} }
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
it 'does have a filter for archived' do
result.response
assert_named_queries('issue:match:search_terms', 'filters:non_archived')
assert_named_queries('issue:archived:non_archived')
end
end
it 'does have a filter for archived' do
result.response
assert_named_queries('filters:non_archived')
end
end
end
end
end
......
......@@ -19,19 +19,44 @@
end
context 'for issues search', :sidekiq_inline do
let_it_be_with_reload(:project) { create(:project, :public, group: group, developers: user) }
let_it_be_with_reload(:closed_result) { create(:issue, :closed, project: project, title: 'foo closed') }
let_it_be_with_reload(:opened_result) { create(:issue, :opened, project: project, title: 'foo opened') }
let_it_be_with_reload(:confidential_result) { create(:issue, :confidential, project: project, title: 'foo confidential') }
let_it_be(:project) { create(:project, :public, group: group, developers: user) }
let_it_be(:closed_result) { create(:issue, :closed, project: project, title: 'foo closed') }
let_it_be(:opened_result) { create(:issue, :opened, project: project, title: 'foo opened') }
let_it_be(:confidential_result) { create(:issue, :confidential, project: project, title: 'foo confidential') }
let(:query) { 'foo' }
let(:scope) { 'issues' }
before do
stub_feature_flags(search_uses_match_queries: true)
::Elastic::ProcessInitialBookkeepingService.backfill_projects!(project)
ensure_elasticsearch_index!
end
context 'when advanced search query syntax is used' do
let(:query) { 'foo -banner' }
include_examples 'search results filtered by state'
include_examples 'search results filtered by confidential'
include_examples 'search results filtered by labels'
it_behaves_like 'namespace ancestry_filter for aggregations' do
let(:query_name) { 'filters:namespace:ancestry_filter:descendants' }
end
end
context 'when search_uses_match_queries flag is false' do
before do
stub_feature_flags(search_uses_match_queries: false)
end
include_examples 'search results filtered by state'
include_examples 'search results filtered by confidential'
include_examples 'search results filtered by labels'
it_behaves_like 'namespace ancestry_filter for aggregations' do
let(:query_name) { 'filters:namespace:ancestry_filter:descendants' }
end
end
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
......
......@@ -93,6 +93,34 @@
ensure_elasticsearch_index!
end
context 'when advanced search query syntax is used' do
let(:query) { 'foo -banner' }
include_examples 'search results filtered by state'
include_examples 'search results filtered by confidential'
include_examples 'search results filtered by labels'
end
context 'when search_uses_match_queries flag is false' do
before do
stub_feature_flags(search_uses_match_queries: false)
end
include_examples 'search results filtered by state'
include_examples 'search results filtered by confidential'
include_examples 'search results filtered by labels'
end
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
include_examples 'search results filtered by state'
include_examples 'search results filtered by confidential'
include_examples 'search results filtered by labels'
end
include_examples 'search results filtered by state'
include_examples 'search results filtered by confidential'
include_examples 'search results filtered by labels'
......
......@@ -1259,61 +1259,81 @@ def search_for(term)
private_project2.project_members.create!(user: user, access_level: ProjectMember::DEVELOPER)
end
context 'issues' do
it 'finds right set of issues' do
issue_1 = create :issue, project: internal_project, title: "Internal project"
create :issue, project: private_project1, title: "Private project"
issue_3 = create :issue, project: private_project2, title: "Private project where I'm a member"
issue_4 = create :issue, project: public_project, title: "Public project"
context 'for issues' do
shared_examples 'issues respect visibility' do
it 'finds right set of issues' do
issue_1 = create :issue, project: internal_project, title: "Internal project"
create :issue, project: private_project1, title: "Private project"
issue_3 = create :issue, project: private_project2, title: "Private project where I'm a member"
issue_4 = create :issue, project: public_project, title: "Public project"
ensure_elasticsearch_index!
# Authenticated search
results = described_class.new(user, 'project', limit_project_ids)
issues = results.objects('issues')
ensure_elasticsearch_index!
expect(issues).to include issue_1
expect(issues).to include issue_3
expect(issues).to include issue_4
expect(results.issues_count).to eq 3
# Authenticated search
results = described_class.new(user, 'project', limit_project_ids)
issues = results.objects('issues')
# Unauthenticated search
results = described_class.new(nil, 'project', [])
issues = results.objects('issues')
expect(issues).to include issue_1
expect(issues).to include issue_3
expect(issues).to include issue_4
expect(results.issues_count).to eq 3
expect(issues).to include issue_4
expect(results.issues_count).to eq 1
end
# Unauthenticated search
results = described_class.new(nil, 'project', [])
issues = results.objects('issues')
context 'when different issue descriptions', :aggregate_failures do
let(:examples) do
code_examples.merge(
'screen' => 'Screenshots or screen recordings',
'problem' => 'Problem to solve'
)
expect(issues).to include issue_4
expect(results.issues_count).to eq 1
end
include_context 'with code examples' do
before do
examples.values.uniq.each do |description|
sha = Digest::SHA256.hexdigest(description)
create :issue, project: private_project2, title: sha, description: description
end
ensure_elasticsearch_index!
context 'when different issue descriptions', :aggregate_failures do
let(:examples) do
code_examples.merge(
'screen' => 'Screenshots or screen recordings',
'problem' => 'Problem to solve'
)
end
it 'finds all examples' do
examples.each do |search_term, description|
sha = Digest::SHA256.hexdigest(description)
include_context 'with code examples' do
before do
examples.values.uniq.each do |description|
sha = Digest::SHA256.hexdigest(description)
create :issue, project: private_project2, title: sha, description: description
end
results = described_class.new(user, search_term, limit_project_ids)
issues = results.objects('issues')
expect(issues.map(&:title)).to include(sha), "failed to find #{search_term}"
ensure_elasticsearch_index!
end
it 'finds all examples' do
examples.each do |search_term, description|
sha = Digest::SHA256.hexdigest(description)
results = described_class.new(user, search_term, limit_project_ids)
issues = results.objects('issues')
expect(issues.map(&:title)).to include(sha), "failed to find #{search_term}"
end
end
end
end
end
it_behaves_like 'issues respect visibility'
context 'when search_uses_match_queries flag is false' do
before do
stub_feature_flags(search_uses_match_queries: false)
end
it_behaves_like 'issues respect visibility'
end
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
it_behaves_like 'issues respect visibility'
end
end
context 'milestones' do
......
......@@ -42,7 +42,7 @@
query: 'foo bar', lenient: true, default_operator: :and } }
]
expect(by_simple_query_string[:query][:bool][:must]).to eq(expected_must)
expect(by_simple_query_string[:query][:bool][:must]).to eql(expected_must)
end
end
......@@ -109,4 +109,127 @@
end
end
end
describe '#by_multi_match_query' do
let(:query) { 'foo bar' }
let(:options) { base_options }
let(:base_options) { { doc_type: 'my_type' } }
let(:fields) { %w[iid^3 title^2 description] }
subject(:by_multi_match_query) do
described_class.by_multi_match_query(fields: fields, query: query, options: options)
end
context 'when custom elasticsearch analyzers are enabled' do
before do
stub_ee_application_setting(elasticsearch_analyzers_smartcn_enabled: true,
elasticsearch_analyzers_smartcn_search: true)
end
it 'applies custom analyzer fields to multi_match_query' do
expected_must = [{ bool: {
should: [
{ multi_match: { _name: 'my_type:multi_match:or:search_terms',
fields: %w[iid^3 title^2 description title.smartcn description.smartcn],
query: 'foo bar', operator: :or, lenient: true } },
{ multi_match: { _name: 'my_type:multi_match:and:search_terms',
fields: %w[iid^3 title^2 description title.smartcn description.smartcn],
query: 'foo bar', operator: :and, lenient: true } },
{ multi_match: { _name: 'my_type:multi_match_phrase:search_terms',
type: :phrase, fields: %w[iid^3 title^2 description title.smartcn description.smartcn],
query: 'foo bar', lenient: true } }
],
minimum_should_match: 1
} }]
expect(by_multi_match_query[:query][:bool][:must]).to eql(expected_must)
end
end
it 'applies highlight in query' do
expected = { fields: { iid: {}, title: {}, description: {} },
number_of_fragments: 0, pre_tags: ['gitlabelasticsearch→'], post_tags: ['←gitlabelasticsearch'] }
expect(by_multi_match_query[:highlight]).to eq(expected)
end
context 'when query is provided' do
it 'returns a by_multi_match_query query as a should and adds doc type as a filter' do
expected_must = [{ bool: {
should: [
{ multi_match: { _name: 'my_type:multi_match:or:search_terms',
fields: %w[iid^3 title^2 description],
query: 'foo bar', operator: :or, lenient: true } },
{ multi_match: { _name: 'my_type:multi_match:and:search_terms',
fields: %w[iid^3 title^2 description],
query: 'foo bar', operator: :and, lenient: true } },
{ multi_match: { _name: 'my_type:multi_match_phrase:search_terms',
type: :phrase, fields: %w[iid^3 title^2 description],
query: 'foo bar', lenient: true } }
],
minimum_should_match: 1
} }]
expected_filter = [
{ term: { type: { _name: 'doc:is_a:my_type', value: 'my_type' } } }
]
expect(by_multi_match_query[:query][:bool][:must]).to eql(expected_must)
expect(by_multi_match_query[:query][:bool][:must_not]).to eq([])
expect(by_multi_match_query[:query][:bool][:should]).to eq([])
expect(by_multi_match_query[:query][:bool][:filter]).to eq(expected_filter)
end
end
context 'when query is not provided' do
let(:query) { nil }
it 'returns a match_all query' do
expected_must = { match_all: {} }
expect(by_multi_match_query[:query][:bool][:must]).to eq(expected_must)
expect(by_multi_match_query[:query][:bool][:must_not]).to eq([])
expect(by_multi_match_query[:query][:bool][:should]).to eq([])
expect(by_multi_match_query[:query][:bool][:filter]).to eq([])
expect(by_multi_match_query[:track_scores]).to eq(true)
end
end
context 'when options[:count_only] is true' do
let(:options) { base_options.merge(count_only: true) }
it 'adds size set to 0 in query' do
expect(by_multi_match_query[:size]).to eq(0)
end
it 'does not apply highlight in query' do
expect(by_multi_match_query[:highlight]).to be_nil
end
it 'removes field boosts and returns a by_multi_match_query as a filter' do
expected_filter = [
{ term: { type: { _name: 'doc:is_a:my_type', value: 'my_type' } } },
{ bool: {
should: [
{ multi_match: { _name: 'my_type:multi_match:or:search_terms',
fields: %w[iid title description],
query: 'foo bar', operator: :or, lenient: true } },
{ multi_match: { _name: 'my_type:multi_match:and:search_terms',
fields: %w[iid title description],
query: 'foo bar', operator: :and, lenient: true } },
{ multi_match: { _name: 'my_type:multi_match_phrase:search_terms',
type: :phrase, fields: %w[iid title description],
query: 'foo bar', lenient: true } }
],
minimum_should_match: 1
} }
]
expect(by_multi_match_query[:query][:bool][:must]).to eq([])
expect(by_multi_match_query[:query][:bool][:must_not]).to eq([])
expect(by_multi_match_query[:query][:bool][:should]).to eq([])
expect(by_multi_match_query[:query][:bool][:filter]).to eql(expected_filter)
end
end
end
end
......@@ -33,24 +33,6 @@
expect(described_class.elastic_search('bla-bla', options: { project_ids: :any, public_and_internal_projects: true }).total_count).to eq(3)
end
context 'when search_query_builder feature flag is false' do
before do
stub_feature_flags(search_query_builder: false)
end
it 'names elasticsearch queries' do
described_class.elastic_search('*').total_count
assert_named_queries('issue:match:search_terms', 'issue:authorized:project')
end
end
it 'names elasticsearch queries' do
described_class.elastic_search('*').total_count
assert_named_queries('issue:match:search_terms', 'filters:project')
end
it 'searches by iid and scopes to type: issue only', :sidekiq_inline do
issue = create :issue, title: 'bla-bla issue', project: project
create :issue, description: 'term2 in description', project: project
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment