Commit 9fad3ac5 authored by Mark Chao's avatar Mark Chao

Merge branch '249515-global-search-admin-should-use-any' into 'master'

Fix performance issue with global search + refactor

Closes #249515

See merge request gitlab-org/gitlab!42437
parents 46a37904 9b58848a
...@@ -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