Commit 9b58848a authored by Dylan Griffith's avatar Dylan Griffith

Fix performance issue with global search + refactor

A performance regression was introduced in
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38095#note_412661217
. This reverts the relevant code that was necessarily passing through a
limited set of project IDs for Elasticsearch.

This also takes an extra refactoring step. Since we are passing project
IDs through to this method it no longer has the projects which was
necessary for the `#generic_search_results` method. But this was only
ever used for user search because users are not indexed in Elasticsearch
and as such cannot actually search using Elasticsearch. I decided it
kept things simpler to move this logic further up the calling stack so
we don't need the `Elastic::SearchResults` objects to be delegating back
again to the normal `SearchResults` objects. This is much simpler
because the calling code is already responsible for determining whether
or not Elasticsearch should be used anyway.
parent eb8418a8
...@@ -4,6 +4,7 @@ module Search ...@@ -4,6 +4,7 @@ module Search
module Elasticsearchable module Elasticsearchable
def use_elasticsearch? def use_elasticsearch?
return false if params[:basic_search] return false if params[:basic_search]
return false if params[:scope] == 'users'
::Gitlab::CurrentSettings.search_using_elasticsearch?(scope: elasticsearchable_scope) ::Gitlab::CurrentSettings.search_using_elasticsearch?(scope: elasticsearchable_scope)
end end
......
...@@ -14,7 +14,7 @@ module EE ...@@ -14,7 +14,7 @@ module EE
::Gitlab::Elastic::SearchResults.new( ::Gitlab::Elastic::SearchResults.new(
current_user, current_user,
params[:search], params[:search],
projects, elastic_projects,
public_and_internal_projects: elastic_global, public_and_internal_projects: elastic_global,
filters: { state: params[:state] } filters: { state: params[:state] }
) )
...@@ -28,6 +28,25 @@ module EE ...@@ -28,6 +28,25 @@ module EE
true true
end end
def elastic_projects
# For elasticsearch we need the list of projects to be as small as
# possible since they are loaded from the DB and sent in the
# Elasticsearch query. It should only be strictly the project IDs the
# user has been given authorization for. The Elasticsearch query will
# additionally take care of public projects. This behaves differently
# to the searching Postgres case in which this list of projects is
# intended to be all projects that should appear in the results.
strong_memoize(:elastic_projects) do
if current_user&.can_read_all_resources?
:any
elsif current_user
current_user.authorized_projects.pluck(:id) # rubocop: disable CodeReuse/ActiveRecord
else
[]
end
end
end
override :allowed_scopes override :allowed_scopes
def allowed_scopes def allowed_scopes
return super unless use_elasticsearch? return super unless use_elasticsearch?
......
...@@ -15,6 +15,11 @@ module EE ...@@ -15,6 +15,11 @@ module EE
false false
end end
override :elastic_projects
def elastic_projects
@elastic_projects ||= projects.pluck_primary_key
end
override :execute override :execute
def execute def execute
return super unless use_elasticsearch? return super unless use_elasticsearch?
...@@ -22,7 +27,7 @@ module EE ...@@ -22,7 +27,7 @@ module EE
::Gitlab::Elastic::GroupSearchResults.new( ::Gitlab::Elastic::GroupSearchResults.new(
current_user, current_user,
params[:search], params[:search],
projects, elastic_projects,
group: group, group: group,
public_and_internal_projects: elastic_global, public_and_internal_projects: elastic_global,
filters: { state: params[:state] } filters: { state: params[:state] }
......
---
title: Fix poor performance with global search across entire instance
merge_request: 42437
author:
type: performance
...@@ -6,27 +6,14 @@ module Gitlab ...@@ -6,27 +6,14 @@ module Gitlab
# superclass inside a module, because autoloading can occur in a # superclass inside a module, because autoloading can occur in a
# different order between execution environments. # different order between execution environments.
class GroupSearchResults < Gitlab::Elastic::SearchResults class GroupSearchResults < Gitlab::Elastic::SearchResults
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
attr_reader :group, :default_project_filter, :filters attr_reader :group, :default_project_filter, :filters
def initialize(current_user, query, limit_projects = nil, group:, public_and_internal_projects: false, default_project_filter: false, filters: {}) def initialize(current_user, query, limit_project_ids = nil, group:, public_and_internal_projects: false, default_project_filter: false, filters: {})
@group = group @group = group
@default_project_filter = default_project_filter @default_project_filter = default_project_filter
@filters = filters @filters = filters
super(current_user, query, limit_projects, public_and_internal_projects: public_and_internal_projects, filters: filters) super(current_user, query, limit_project_ids, public_and_internal_projects: public_and_internal_projects, filters: filters)
end
def generic_search_results
@generic_search_results ||= Gitlab::GroupSearchResults.new(
current_user,
query,
limit_projects,
group: group,
filters: filters
)
end end
end end
end end
......
...@@ -8,24 +8,11 @@ module Gitlab ...@@ -8,24 +8,11 @@ module Gitlab
class ProjectSearchResults < Gitlab::Elastic::SearchResults class ProjectSearchResults < Gitlab::Elastic::SearchResults
attr_reader :project, :repository_ref, :filters attr_reader :project, :repository_ref, :filters
delegate :users, to: :generic_search_results
delegate :limited_users_count, to: :generic_search_results
def initialize(current_user, query, project:, repository_ref: nil, filters: {}) def initialize(current_user, query, project:, repository_ref: nil, filters: {})
@project = project @project = project
@repository_ref = repository_ref.presence || project.default_branch @repository_ref = repository_ref.presence || project.default_branch
super(current_user, query, [project], public_and_internal_projects: false, filters: filters) super(current_user, query, [project.id], public_and_internal_projects: false, filters: filters)
end
def generic_search_results
@generic_search_results ||= Gitlab::ProjectSearchResults.new(
current_user,
query,
project: project,
repository_ref: repository_ref,
filters: filters
)
end end
private private
......
...@@ -11,15 +11,12 @@ module Gitlab ...@@ -11,15 +11,12 @@ module Gitlab
# Limit search results by passed projects # Limit search results by passed projects
# It allows us to search only for projects user has access to # It allows us to search only for projects user has access to
attr_reader :limit_projects attr_reader :limit_project_ids
delegate :users, to: :generic_search_results def initialize(current_user, query, limit_project_ids = nil, public_and_internal_projects: true, filters: {})
delegate :limited_users_count, to: :generic_search_results
def initialize(current_user, query, limit_projects = nil, public_and_internal_projects: true, filters: {})
@current_user = current_user @current_user = current_user
@query = query @query = query
@limit_projects = limit_projects @limit_project_ids = limit_project_ids
@public_and_internal_projects = public_and_internal_projects @public_and_internal_projects = public_and_internal_projects
@filters = filters @filters = filters
end end
...@@ -44,17 +41,11 @@ module Gitlab ...@@ -44,17 +41,11 @@ module Gitlab
wiki_blobs(page: page, per_page: per_page) wiki_blobs(page: page, per_page: per_page)
when 'commits' when 'commits'
commits(page: page, per_page: per_page, preload_method: preload_method) commits(page: page, per_page: per_page, preload_method: preload_method)
when 'users'
users.page(page).per(per_page)
else else
Kaminari.paginate_array([]) Kaminari.paginate_array([])
end end
end end
def generic_search_results
@generic_search_results ||= Gitlab::SearchResults.new(current_user, query, limit_projects)
end
def formatted_count(scope) def formatted_count(scope)
case scope case scope
when 'projects' when 'projects'
...@@ -73,8 +64,6 @@ module Gitlab ...@@ -73,8 +64,6 @@ module Gitlab
merge_requests_count.to_s merge_requests_count.to_s
when 'milestones' when 'milestones'
milestones_count.to_s milestones_count.to_s
when 'users'
generic_search_results.formatted_count('users')
end end
end end
...@@ -176,20 +165,6 @@ module Gitlab ...@@ -176,20 +165,6 @@ module Gitlab
private private
# Convert the `limit_projects` to a list of ids for Elasticsearch
def limit_project_ids
strong_memoize(:limit_project_ids) do
case limit_projects
when :any then :any
when ActiveRecord::Relation
limit_projects.pluck_primary_key if limit_projects.model == Project
when Array
limit_projects.all? { |x| x.is_a?(Project) } ? limit_projects.map(&:id) : []
else []
end
end
end
# Apply some eager loading to the `records` of an ES result object without # Apply some eager loading to the `records` of an ES result object without
# losing pagination information. Also, take advantage of preload method if # losing pagination information. Also, take advantage of preload method if
# provided by the caller. # provided by the caller.
......
...@@ -9,7 +9,7 @@ RSpec.describe Gitlab::Elastic::GroupSearchResults, :elastic do ...@@ -9,7 +9,7 @@ RSpec.describe Gitlab::Elastic::GroupSearchResults, :elastic do
let(:filters) { {} } let(:filters) { {} }
let(:query) { '*' } let(:query) { '*' }
subject(:results) { described_class.new(user, query, Project.all, group: group, filters: filters) } subject(:results) { described_class.new(user, query, Project.all.pluck_primary_key, group: group, filters: filters) }
before do before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true) stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
...@@ -45,32 +45,6 @@ RSpec.describe Gitlab::Elastic::GroupSearchResults, :elastic do ...@@ -45,32 +45,6 @@ RSpec.describe Gitlab::Elastic::GroupSearchResults, :elastic do
end end
end end
context 'user search' do
let(:query) { guest.username }
before do
expect(Gitlab::GroupSearchResults).to receive(:new).and_call_original
end
it { expect(results.objects('users')).to contain_exactly(guest) }
it { expect(results.limited_users_count).to eq(1) }
describe 'pagination' do
let(:query) {}
let_it_be(:user2) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::REPORTER) } }
it 'returns the correct page of results' do
expect(results.objects('users', page: 1, per_page: 1)).to contain_exactly(user2)
expect(results.objects('users', page: 2, per_page: 1)).to contain_exactly(guest)
end
it 'returns the correct number of results for one page' do
expect(results.objects('users', page: 1, per_page: 2).count).to eq(2)
end
end
end
context 'query performance' do context 'query performance' do
include_examples 'does not hit Elasticsearch twice for objects and counts', %w|projects notes blobs wiki_blobs commits issues merge_requests milestones| include_examples 'does not hit Elasticsearch twice for objects and counts', %w|projects notes blobs wiki_blobs commits issues merge_requests milestones|
end end
......
...@@ -167,33 +167,6 @@ RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do ...@@ -167,33 +167,6 @@ RSpec.describe Gitlab::Elastic::ProjectSearchResults, :elastic do
end end
end end
context 'user search' do
let(:query) { project.owner.username }
before do
expect(Gitlab::ProjectSearchResults).to receive(:new).and_call_original
end
it { expect(results.objects('users')).to eq([project.owner]) }
it { expect(results.limited_users_count).to eq(1) }
describe 'pagination' do
let(:query) { }
let_it_be(:user2) { create(:user).tap { |u| project.add_user(u, Gitlab::Access::REPORTER) } }
it 'returns the correct page of results' do
# UsersFinder defaults to order_id_desc, the newer result will be first
expect(results.objects('users', page: 1, per_page: 1)).to eq([user2])
expect(results.objects('users', page: 2, per_page: 1)).to eq([project.owner])
end
it 'returns the correct number of results for one page' do
expect(results.objects('users', page: 1, per_page: 2).count).to eq(2)
end
end
end
context 'query performance' do context 'query performance' do
let(:project) { create(:project, :public, :repository, :wiki_repo) } let(:project) { create(:project, :public, :repository, :wiki_repo) }
let(:query) { '*' } let(:query) { '*' }
......
...@@ -10,12 +10,12 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -10,12 +10,12 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project_1) { create(:project, :public, :repository, :wiki_repo) } let(:project_1) { create(:project, :public, :repository, :wiki_repo) }
let(:project_2) { create(:project, :public, :repository, :wiki_repo) } let(:project_2) { create(:project, :public, :repository, :wiki_repo) }
let(:limit_projects) { [project_1] } let(:limit_project_ids) { [project_1.id] }
describe '#formatted_count' do describe '#formatted_count' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
let(:results) { described_class.new(user, 'hello world', limit_projects) } let(:results) { described_class.new(user, 'hello world', limit_project_ids) }
where(:scope, :count_method, :expected) do where(:scope, :count_method, :expected) do
'projects' | :projects_count | '1234' 'projects' | :projects_count | '1234'
...@@ -35,15 +35,10 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -35,15 +35,10 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
expect(results.formatted_count(scope)).to eq(expected) expect(results.formatted_count(scope)).to eq(expected)
end end
end end
it 'delegates to generic_search_results for users' do
expect(results.generic_search_results).to receive(:formatted_count).with('users').and_return('1000+')
expect(results.formatted_count('users')).to eq('1000+')
end
end end
shared_examples_for 'a paginated object' do |object_type| shared_examples_for 'a paginated object' do |object_type|
let(:results) { described_class.new(user, 'hello world', limit_projects) } let(:results) { described_class.new(user, 'hello world', limit_project_ids) }
it 'does not explode when given a page as a string' do it 'does not explode when given a page as a string' do
expect { results.objects(object_type, page: "2") }.not_to raise_error expect { results.objects(object_type, page: "2") }.not_to raise_error
...@@ -135,7 +130,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -135,7 +130,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
it_behaves_like 'a paginated object', 'issues' it_behaves_like 'a paginated object', 'issues'
it 'lists found issues' do it 'lists found issues' do
results = described_class.new(user, 'hello world', limit_projects) results = described_class.new(user, 'hello world', limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to contain_exactly(@issue_1, @issue_2) expect(issues).to contain_exactly(@issue_1, @issue_2)
...@@ -143,14 +138,14 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -143,14 +138,14 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'returns empty list when issues are not found' do it 'returns empty list when issues are not found' do
results = described_class.new(user, 'security', limit_projects) results = described_class.new(user, 'security', limit_project_ids)
expect(results.objects('issues')).to be_empty expect(results.objects('issues')).to be_empty
expect(results.issues_count).to eq 0 expect(results.issues_count).to eq 0
end end
it 'lists issue when search by a valid iid' do it 'lists issue when search by a valid iid' do
results = described_class.new(user, '#2', limit_projects, public_and_internal_projects: false) results = described_class.new(user, '#2', limit_project_ids, public_and_internal_projects: false)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to contain_exactly(@issue_2) expect(issues).to contain_exactly(@issue_2)
...@@ -158,7 +153,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -158,7 +153,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'can also find an issue by iid without the prefixed #' do it 'can also find an issue by iid without the prefixed #' do
results = described_class.new(user, '2', limit_projects, public_and_internal_projects: false) results = described_class.new(user, '2', limit_project_ids, public_and_internal_projects: false)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to contain_exactly(@issue_2) expect(issues).to contain_exactly(@issue_2)
...@@ -166,7 +161,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -166,7 +161,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'returns empty list when search by invalid iid' do it 'returns empty list when search by invalid iid' do
results = described_class.new(user, '#222', limit_projects) results = described_class.new(user, '#222', limit_project_ids)
expect(results.objects('issues')).to be_empty expect(results.objects('issues')).to be_empty
expect(results.issues_count).to eq 0 expect(results.issues_count).to eq 0
...@@ -217,7 +212,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -217,7 +212,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
it_behaves_like 'a paginated object', 'notes' it_behaves_like 'a paginated object', 'notes'
it 'lists found notes' do it 'lists found notes' do
results = described_class.new(user, 'foo', limit_projects) results = described_class.new(user, 'foo', limit_project_ids)
notes = results.objects('notes') notes = results.objects('notes')
expect(notes).to include @note_1 expect(notes).to include @note_1
...@@ -227,7 +222,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -227,7 +222,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'returns empty list when notes are not found' do it 'returns empty list when notes are not found' do
results = described_class.new(user, 'security', limit_projects) results = described_class.new(user, 'security', limit_project_ids)
expect(results.objects('notes')).to be_empty expect(results.objects('notes')).to be_empty
expect(results.notes_count).to eq 0 expect(results.notes_count).to eq 0
...@@ -237,7 +232,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -237,7 +232,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
describe 'confidential issues' do describe 'confidential issues' do
let(:project_3) { create(:project, :public) } let(:project_3) { create(:project, :public) }
let(:project_4) { create(:project, :public) } let(:project_4) { create(:project, :public) }
let(:limit_projects) { [project_1, project_2, project_3] } let(:limit_project_ids) { [project_1.id, project_2.id, project_3.id] }
let(:author) { create(:user) } let(:author) { create(:user) }
let(:assignee) { create(:user) } let(:assignee) { create(:user) }
let(:non_member) { create(:user) } let(:non_member) { create(:user) }
...@@ -259,7 +254,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -259,7 +254,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
let(:query) { 'issue' } let(:query) { 'issue' }
it 'does not list confidential issues for guests' do it 'does not list confidential issues for guests' do
results = described_class.new(nil, query, limit_projects) results = described_class.new(nil, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -272,7 +267,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -272,7 +267,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'does not list confidential issues for non project members' do it 'does not list confidential issues for non project members' do
results = described_class.new(non_member, query, limit_projects) results = described_class.new(non_member, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -285,7 +280,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -285,7 +280,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'lists confidential issues for author' do it 'lists confidential issues for author' do
results = described_class.new(author, query, limit_projects) results = described_class.new(author, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -298,7 +293,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -298,7 +293,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'lists confidential issues for assignee' do it 'lists confidential issues for assignee' do
results = described_class.new(assignee, query, limit_projects) results = described_class.new(assignee, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -314,7 +309,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -314,7 +309,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
project_1.add_developer(member) project_1.add_developer(member)
project_2.add_developer(member) project_2.add_developer(member)
results = described_class.new(member, query, limit_projects) results = described_class.new(member, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -327,7 +322,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -327,7 +322,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'lists all issues for admin' do it 'lists all issues for admin' do
results = described_class.new(admin, query, limit_projects) results = described_class.new(admin, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -344,7 +339,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -344,7 +339,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
let(:query) { '#1' } let(:query) { '#1' }
it 'does not list confidential issues for guests' do it 'does not list confidential issues for guests' do
results = described_class.new(nil, query, limit_projects) results = described_class.new(nil, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -357,7 +352,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -357,7 +352,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'does not list confidential issues for non project members' do it 'does not list confidential issues for non project members' do
results = described_class.new(non_member, query, limit_projects) results = described_class.new(non_member, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -370,7 +365,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -370,7 +365,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'lists confidential issues for author' do it 'lists confidential issues for author' do
results = described_class.new(author, query, limit_projects) results = described_class.new(author, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -383,7 +378,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -383,7 +378,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'lists confidential issues for assignee' do it 'lists confidential issues for assignee' do
results = described_class.new(assignee, query, limit_projects) results = described_class.new(assignee, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -399,7 +394,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -399,7 +394,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
project_2.add_developer(member) project_2.add_developer(member)
project_3.add_developer(member) project_3.add_developer(member)
results = described_class.new(member, query, limit_projects) results = described_class.new(member, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -412,7 +407,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -412,7 +407,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'lists all issues for admin' do it 'lists all issues for admin' do
results = described_class.new(admin, query, limit_projects) results = described_class.new(admin, query, limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include @issue expect(issues).to include @issue
...@@ -458,7 +453,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -458,7 +453,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
it_behaves_like 'a paginated object', 'merge_requests' it_behaves_like 'a paginated object', 'merge_requests'
it 'lists found merge requests' do it 'lists found merge requests' do
results = described_class.new(user, 'hello world', limit_projects, public_and_internal_projects: false) results = described_class.new(user, 'hello world', limit_project_ids, public_and_internal_projects: false)
merge_requests = results.objects('merge_requests') merge_requests = results.objects('merge_requests')
expect(merge_requests).to contain_exactly(@merge_request_1, @merge_request_2) expect(merge_requests).to contain_exactly(@merge_request_1, @merge_request_2)
...@@ -466,14 +461,14 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -466,14 +461,14 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'returns empty list when merge requests are not found' do it 'returns empty list when merge requests are not found' do
results = described_class.new(user, 'security', limit_projects) results = described_class.new(user, 'security', limit_project_ids)
expect(results.objects('merge_requests')).to be_empty expect(results.objects('merge_requests')).to be_empty
expect(results.merge_requests_count).to eq 0 expect(results.merge_requests_count).to eq 0
end end
it 'lists merge request when search by a valid iid' do it 'lists merge request when search by a valid iid' do
results = described_class.new(user, '!2', limit_projects, public_and_internal_projects: false) results = described_class.new(user, '!2', limit_project_ids, public_and_internal_projects: false)
merge_requests = results.objects('merge_requests') merge_requests = results.objects('merge_requests')
expect(merge_requests).to contain_exactly(@merge_request_2) expect(merge_requests).to contain_exactly(@merge_request_2)
...@@ -481,7 +476,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -481,7 +476,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'can also find an issue by iid without the prefixed !' do it 'can also find an issue by iid without the prefixed !' do
results = described_class.new(user, '2', limit_projects, public_and_internal_projects: false) results = described_class.new(user, '2', limit_project_ids, public_and_internal_projects: false)
merge_requests = results.objects('merge_requests') merge_requests = results.objects('merge_requests')
expect(merge_requests).to contain_exactly(@merge_request_2) expect(merge_requests).to contain_exactly(@merge_request_2)
...@@ -489,7 +484,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -489,7 +484,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'returns empty list when search by invalid iid' do it 'returns empty list when search by invalid iid' do
results = described_class.new(user, '#222', limit_projects) results = described_class.new(user, '#222', limit_project_ids)
expect(results.objects('merge_requests')).to be_empty expect(results.objects('merge_requests')).to be_empty
expect(results.merge_requests_count).to eq 0 expect(results.merge_requests_count).to eq 0
...@@ -501,7 +496,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -501,7 +496,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
let!(:closed_result) { create(:merge_request, :closed, source_project: project, title: 'foo closed') } let!(:closed_result) { create(:merge_request, :closed, source_project: project, title: 'foo closed') }
let(:scope) { 'merge_requests' } let(:scope) { 'merge_requests' }
let(:results) { described_class.new(user, 'foo', [project], filters: filters) } let(:results) { described_class.new(user, 'foo', [project.id], filters: filters) }
include_examples 'search results filtered by state' do include_examples 'search results filtered by state' do
before do before do
...@@ -538,7 +533,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -538,7 +533,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
ensure_elasticsearch_index! ensure_elasticsearch_index!
result = described_class.new(user, 'term', [project]) result = described_class.new(user, 'term', [project.id])
expect(result.issues_count).to eq(2) expect(result.issues_count).to eq(2)
expect(result.merge_requests_count).to eq(2) expect(result.merge_requests_count).to eq(2)
...@@ -555,13 +550,13 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -555,13 +550,13 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
def search_for(term) def search_for(term)
described_class.new(user, term, [project_1]).objects('blobs').map(&:path) described_class.new(user, term, [project_1.id]).objects('blobs').map(&:path)
end end
it_behaves_like 'a paginated object', 'blobs' it_behaves_like 'a paginated object', 'blobs'
it 'finds blobs' do it 'finds blobs' do
results = described_class.new(user, 'def', limit_projects) results = described_class.new(user, 'def', limit_project_ids)
blobs = results.objects('blobs') blobs = results.objects('blobs')
expect(blobs.first.data).to include('def') expect(blobs.first.data).to include('def')
...@@ -569,7 +564,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -569,7 +564,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
it 'finds blobs by prefix search' do it 'finds blobs by prefix search' do
results = described_class.new(user, 'defau*', limit_projects) results = described_class.new(user, 'defau*', limit_project_ids)
blobs = results.objects('blobs') blobs = results.objects('blobs')
expect(blobs.first.data).to include('default') expect(blobs.first.data).to include('default')
...@@ -582,18 +577,18 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -582,18 +577,18 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
project_2.add_reporter(user) project_2.add_reporter(user)
ensure_elasticsearch_index! ensure_elasticsearch_index!
results = described_class.new(user, 'def', [project_1]) results = described_class.new(user, 'def', [project_1.id])
expect(results.blobs_count).to eq 5 expect(results.blobs_count).to eq 5
result_project_ids = results.objects('blobs').map(&:project_id) result_project_ids = results.objects('blobs').map(&:project_id)
expect(result_project_ids.uniq).to eq([project_1.id]) expect(result_project_ids.uniq).to eq([project_1.id])
results = described_class.new(user, 'def', [project_1, project_2]) results = described_class.new(user, 'def', [project_1.id, project_2.id])
expect(results.blobs_count).to eq 10 expect(results.blobs_count).to eq 10
end end
it 'returns zero when blobs are not found' do it 'returns zero when blobs are not found' do
results = described_class.new(user, 'asdfg', limit_projects) results = described_class.new(user, 'asdfg', limit_project_ids)
expect(results.blobs_count).to eq 0 expect(results.blobs_count).to eq 0
end end
...@@ -758,7 +753,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -758,7 +753,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
describe 'Wikis' do describe 'Wikis' do
let(:results) { described_class.new(user, 'term', limit_projects) } let(:results) { described_class.new(user, 'term', limit_project_ids) }
subject(:wiki_blobs) { results.objects('wiki_blobs') } subject(:wiki_blobs) { results.objects('wiki_blobs') }
...@@ -797,12 +792,12 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -797,12 +792,12 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
expect(results.wiki_blobs_count).to eq 1 expect(results.wiki_blobs_count).to eq 1
results = described_class.new(user, 'term', [project_1, project_2]) results = described_class.new(user, 'term', [project_1.id, project_2.id])
expect(results.wiki_blobs_count).to eq 2 expect(results.wiki_blobs_count).to eq 2
end end
it 'returns zero when wiki blobs are not found' do it 'returns zero when wiki blobs are not found' do
results = described_class.new(user, 'asdfg', limit_projects) results = described_class.new(user, 'asdfg', limit_project_ids)
expect(results.wiki_blobs_count).to eq 0 expect(results.wiki_blobs_count).to eq 0
end end
...@@ -811,13 +806,13 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -811,13 +806,13 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
let(:project_1) { create(:project, :public, :repository, :wiki_disabled) } let(:project_1) { create(:project, :public, :repository, :wiki_disabled) }
context 'search by member' do context 'search by member' do
let(:limit_projects) { [project_1] } let(:limit_project_ids) { [project_1.id] }
it { is_expected.to be_empty } it { is_expected.to be_empty }
end end
context 'search by non-member' do context 'search by non-member' do
let(:limit_projects) { [] } let(:limit_project_ids) { [] }
it { is_expected.to be_empty } it { is_expected.to be_empty }
end end
...@@ -827,7 +822,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -827,7 +822,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
let(:project_1) { create(:project, :public, :repository, :wiki_private, :wiki_repo) } let(:project_1) { create(:project, :public, :repository, :wiki_private, :wiki_repo) }
context 'search by member' do context 'search by member' do
let(:limit_projects) { [project_1] } let(:limit_project_ids) { [project_1.id] }
before do before do
project_1.add_guest(user) project_1.add_guest(user)
...@@ -837,7 +832,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -837,7 +832,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
context 'search by non-member' do context 'search by non-member' do
let(:limit_projects) { [] } let(:limit_project_ids) { [] }
it { is_expected.to be_empty } it { is_expected.to be_empty }
end end
...@@ -853,7 +848,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -853,7 +848,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
it_behaves_like 'a paginated object', 'commits' it_behaves_like 'a paginated object', 'commits'
it 'finds commits' do it 'finds commits' do
results = described_class.new(user, 'add', limit_projects) results = described_class.new(user, 'add', limit_project_ids)
commits = results.objects('commits') commits = results.objects('commits')
expect(commits.first.message.downcase).to include("add") expect(commits.first.message.downcase).to include("add")
...@@ -866,15 +861,15 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -866,15 +861,15 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
project_2.add_reporter(user) project_2.add_reporter(user)
ensure_elasticsearch_index! ensure_elasticsearch_index!
results = described_class.new(user, 'add', [project_1]) results = described_class.new(user, 'add', [project_1.id])
expect(results.commits_count).to eq 24 expect(results.commits_count).to eq 24
results = described_class.new(user, 'add', [project_1, project_2]) results = described_class.new(user, 'add', [project_1.id, project_2.id])
expect(results.commits_count).to eq 48 expect(results.commits_count).to eq 48
end end
it 'returns zero when commits are not found' do it 'returns zero when commits are not found' do
results = described_class.new(user, 'asdfg', limit_projects) results = described_class.new(user, 'asdfg', limit_project_ids)
expect(results.commits_count).to eq 0 expect(results.commits_count).to eq 0
end end
...@@ -885,7 +880,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -885,7 +880,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
let(:private_project1) { create(:project, :private, :repository, :wiki_repo, description: "Private project") } let(:private_project1) { create(:project, :private, :repository, :wiki_repo, description: "Private project") }
let(:private_project2) { create(:project, :private, :repository, :wiki_repo, description: "Private project where I'm a member") } let(:private_project2) { create(:project, :private, :repository, :wiki_repo, description: "Private project where I'm a member") }
let(:public_project) { create(:project, :public, :repository, :wiki_repo, description: "Public project") } let(:public_project) { create(:project, :public, :repository, :wiki_repo, description: "Public project") }
let(:limit_projects) { [private_project2] } let(:limit_project_ids) { [private_project2.id] }
before do before do
private_project2.project_members.create(user: user, access_level: ProjectMember::DEVELOPER) private_project2.project_members.create(user: user, access_level: ProjectMember::DEVELOPER)
...@@ -901,7 +896,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -901,7 +896,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
ensure_elasticsearch_index! ensure_elasticsearch_index!
# Authenticated search # Authenticated search
results = described_class.new(user, 'project', limit_projects) results = described_class.new(user, 'project', limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
expect(issues).to include issue_1 expect(issues).to include issue_1
...@@ -942,7 +937,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -942,7 +937,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
ensure_elasticsearch_index! ensure_elasticsearch_index!
projects = user.authorized_projects projects = user.authorized_projects
results = described_class.new(user, 'project', projects) results = described_class.new(user, 'project', projects.pluck_primary_key)
milestones = results.objects('milestones') milestones = results.objects('milestones')
expect(milestones).to match_array([milestone_1, milestone_3]) expect(milestones).to match_array([milestone_1, milestone_3])
...@@ -969,7 +964,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -969,7 +964,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
it 'returns right set of milestones' do it 'returns right set of milestones' do
# Authenticated search # Authenticated search
projects = user.authorized_projects projects = user.authorized_projects
results = described_class.new(user, 'project', projects) results = described_class.new(user, 'project', projects.pluck_primary_key)
milestones = results.objects('milestones') milestones = results.objects('milestones')
expect(milestones).to match_array([milestone_1, milestone_3, milestone_4]) expect(milestones).to match_array([milestone_1, milestone_3, milestone_4])
...@@ -1050,7 +1045,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1050,7 +1045,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
ensure_elasticsearch_index! ensure_elasticsearch_index!
# Authenticated search # Authenticated search
results = described_class.new(user, 'project', limit_projects) results = described_class.new(user, 'project', limit_project_ids)
milestones = results.objects('projects') milestones = results.objects('projects')
expect(milestones).to include internal_project expect(milestones).to include internal_project
...@@ -1077,7 +1072,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1077,7 +1072,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
ensure_elasticsearch_index! ensure_elasticsearch_index!
# Authenticated search # Authenticated search
results = described_class.new(user, 'project', limit_projects) results = described_class.new(user, 'project', limit_project_ids)
merge_requests = results.objects('merge_requests') merge_requests = results.objects('merge_requests')
expect(merge_requests).to include merge_request_1 expect(merge_requests).to include merge_request_1
...@@ -1106,7 +1101,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1106,7 +1101,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
it 'finds the right set of wiki blobs' do it 'finds the right set of wiki blobs' do
# Authenticated search # Authenticated search
results = described_class.new(user, 'term', limit_projects) results = described_class.new(user, 'term', limit_project_ids)
blobs = results.objects('wiki_blobs') blobs = results.objects('wiki_blobs')
expect(blobs.map(&:project)).to match_array [internal_project, private_project2, public_project] expect(blobs.map(&:project)).to match_array [internal_project, private_project2, public_project]
...@@ -1138,7 +1133,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1138,7 +1133,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
ensure_elasticsearch_index! ensure_elasticsearch_index!
# Authenticated search # Authenticated search
results = described_class.new(user, 'search', limit_projects) results = described_class.new(user, 'search', limit_project_ids)
commits = results.objects('commits') commits = results.objects('commits')
expect(commits.map(&:project)).to match_array [internal_project, private_project2, public_project] expect(commits.map(&:project)).to match_array [internal_project, private_project2, public_project]
...@@ -1170,7 +1165,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1170,7 +1165,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
ensure_elasticsearch_index! ensure_elasticsearch_index!
# Authenticated search # Authenticated search
results = described_class.new(user, 'tesla', limit_projects) results = described_class.new(user, 'tesla', limit_project_ids)
blobs = results.objects('blobs') blobs = results.objects('blobs')
expect(blobs.map(&:project)).to match_array [internal_project, private_project2, public_project] expect(blobs.map(&:project)).to match_array [internal_project, private_project2, public_project]
...@@ -1187,7 +1182,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need ...@@ -1187,7 +1182,7 @@ RSpec.describe Gitlab::Elastic::SearchResults, :elastic, :sidekiq_might_not_need
end end
context 'query performance' do context 'query performance' do
let(:results) { described_class.new(user, 'hello world', limit_projects) } let(:results) { described_class.new(user, 'hello world', limit_project_ids) }
include_examples 'does not hit Elasticsearch twice for objects and counts', %w|projects notes blobs wiki_blobs commits issues merge_requests milestones| include_examples 'does not hit Elasticsearch twice for objects and counts', %w|projects notes blobs wiki_blobs commits issues merge_requests milestones|
end end
......
...@@ -210,6 +210,41 @@ RSpec.describe Search::GlobalService do ...@@ -210,6 +210,41 @@ RSpec.describe Search::GlobalService do
end end
end end
describe '#elastic_projects' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, namespace: group) }
let_it_be(:another_project) { create(:project) }
let_it_be(:non_admin_user) { create_user_from_membership(project, :developer) }
let_it_be(:admin) { create(:admin) }
let(:service) { described_class.new(user, {}) }
let(:elastic_projects) { service.elastic_projects }
context 'when the user is an admin' do
let(:user) { admin }
it 'returns :any' do
expect(elastic_projects).to eq(:any)
end
end
context 'when the user is not an admin' do
let(:user) { non_admin_user }
it 'returns the projects the user has access to' do
expect(elastic_projects).to eq([project.id])
end
end
context 'when there is no user' do
let(:user) { nil }
it 'returns empty array' do
expect(elastic_projects).to eq([])
end
end
end
context 'confidential notes' do context 'confidential notes' do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment