Commit b937fb31 authored by Dmitry Gruzd's avatar Dmitry Gruzd Committed by Terri Chu

Advanced Search: Use namespace_ancestry for issues

parent 3ce20ca4
...@@ -404,6 +404,11 @@ module EE ...@@ -404,6 +404,11 @@ module EE
::Gitlab::CurrentSettings.invalidate_elasticsearch_indexes_cache_for_namespace!(self.id) ::Gitlab::CurrentSettings.invalidate_elasticsearch_indexes_cache_for_namespace!(self.id)
end end
def elastic_namespace_ancestry
separator = '-'
self_and_ancestor_ids(hierarchy_order: :desc).join(separator) + separator
end
def enable_temporary_storage_increase! def enable_temporary_storage_increase!
update(temporary_storage_increase_ends_on: TEMPORARY_STORAGE_INCREASE_DAYS.days.from_now) update(temporary_storage_increase_ends_on: TEMPORARY_STORAGE_INCREASE_DAYS.days.from_now)
end end
......
---
name: elasticsearch_use_group_level_optimization
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69741
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340596
milestone: '14.4'
type: development
group: group::global search
default_enabled: false
...@@ -381,6 +381,37 @@ module Elastic ...@@ -381,6 +381,37 @@ module Elastic
authorized_project_ids.to_a authorized_project_ids.to_a
end end
def authorized_namespace_ids(current_user, options = {})
return [] unless current_user && options[:group_ids].present?
authorized_ids = current_user.authorized_groups.pluck_primary_key.to_set
authorized_ids.intersection(options[:group_ids].to_set).to_a
end
def ancestry_filter(current_user, namespace_ancestry)
return {} unless current_user
return {} if namespace_ancestry.blank?
context.name(:ancestry_filter) do
filters = namespace_ancestry.map do |namespace_ids|
{
prefix: {
namespace_ancestry_ids: {
_name: context.name(:descendants),
value: namespace_ids
}
}
}
end
{
bool: {
should: filters
}
}
end
end
end end
end end
end end
...@@ -5,8 +5,6 @@ module Elastic ...@@ -5,8 +5,6 @@ module Elastic
class ApplicationInstanceProxy < Elasticsearch::Model::Proxy::InstanceMethodsProxy class ApplicationInstanceProxy < Elasticsearch::Model::Proxy::InstanceMethodsProxy
include InstanceProxyUtil include InstanceProxyUtil
NAMESPACE_ANCESTRY_SEPARATOR = '-'
def es_parent def es_parent
"project_#{target.project_id}" unless target.is_a?(Project) || target&.project_id.nil? "project_#{target.project_id}" unless target.is_a?(Project) || target&.project_id.nil?
end end
...@@ -21,8 +19,7 @@ module Elastic ...@@ -21,8 +19,7 @@ module Elastic
def namespace_ancestry def namespace_ancestry
project = target.is_a?(Project) ? target : target.project project = target.is_a?(Project) ? target : target.project
namespace = project.namespace project.namespace.elastic_namespace_ancestry
namespace.self_and_ancestor_ids(hierarchy_order: :desc).join(NAMESPACE_ANCESTRY_SEPARATOR) + NAMESPACE_ANCESTRY_SEPARATOR
end end
private private
......
...@@ -19,7 +19,7 @@ module Elastic ...@@ -19,7 +19,7 @@ module Elastic
options[:features] = 'issues' options[:features] = 'issues'
options[:no_join_project] = true options[:no_join_project] = true
context.name(:issue) do context.name(:issue) do
query_hash = context.name(:authorized) { project_ids_filter(query_hash, options) } query_hash = context.name(:authorized) { authorization_filter(query_hash, options) }
query_hash = context.name(:confidentiality) { confidentiality_filter(query_hash, options) } query_hash = context.name(:confidentiality) { confidentiality_filter(query_hash, options) }
query_hash = context.name(:match) { state_filter(query_hash, options) } query_hash = context.name(:match) { state_filter(query_hash, options) }
end end
...@@ -56,6 +56,30 @@ module Elastic ...@@ -56,6 +56,30 @@ module Elastic
end end
end end
def should_use_project_ids_filter?(options)
options[:project_ids] == :any ||
options[:group_ids].blank? ||
Feature.disabled?(:elasticsearch_use_group_level_optimization) ||
!Elastic::DataMigrationService.migration_has_finished?(:redo_backfill_namespace_ancestry_ids_for_issues)
end
def authorization_filter(query_hash, options)
return project_ids_filter(query_hash, options) if should_use_project_ids_filter?(options)
current_user = options[:current_user]
namespace_ancestry = Namespace.find(authorized_namespace_ids(current_user, options))
.map(&:elastic_namespace_ancestry)
return project_ids_filter(query_hash, options) if namespace_ancestry.blank?
context.name(:namespace) do
query_hash[:query][:bool][:filter] ||= []
query_hash[:query][:bool][:filter] << ancestry_filter(current_user, namespace_ancestry)
end
query_hash
end
# Builds an elasticsearch query that will select documents from a # Builds an elasticsearch query that will select documents from a
# set of projects for Group and Project searches, taking user access # set of projects for Group and Project searches, taking user access
# rules for issues into account. Relies upon super for Global searches # rules for issues into account. Relies upon super for Global searches
......
...@@ -6,6 +6,8 @@ module Gitlab ...@@ -6,6 +6,8 @@ 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
extend Gitlab::Utils::Override
attr_reader :group, :default_project_filter, :filters attr_reader :group, :default_project_filter, :filters
# rubocop:disable Metrics/ParameterLists # rubocop:disable Metrics/ParameterLists
...@@ -17,6 +19,16 @@ module Gitlab ...@@ -17,6 +19,16 @@ module Gitlab
super(current_user, query, limit_project_ids, public_and_internal_projects: public_and_internal_projects, order_by: order_by, sort: sort, filters: filters) super(current_user, query, limit_project_ids, public_and_internal_projects: public_and_internal_projects, order_by: order_by, sort: sort, filters: filters)
end end
# rubocop:enable Metrics/ParameterLists # rubocop:enable Metrics/ParameterLists
override :scope_options
def scope_options(scope)
case scope
when :issues
super.merge(group_ids: [group.id])
else
super
end
end
end end
end end
end end
...@@ -206,11 +206,27 @@ RSpec.describe Search::GroupService do ...@@ -206,11 +206,27 @@ RSpec.describe Search::GroupService do
permission_table_for_guest_feature_access permission_table_for_guest_feature_access
end end
context 'elasticsearch_use_group_level_optimization is enabled' do
before do
stub_feature_flags(elasticsearch_use_group_level_optimization: true)
end
with_them do with_them do
it_behaves_like 'search respects visibility' it_behaves_like 'search respects visibility'
end end
end end
context 'elasticsearch_use_group_level_optimization is disabled' do
before do
stub_feature_flags(elasticsearch_use_group_level_optimization: false)
end
with_them do
it_behaves_like 'search respects visibility'
end
end
end
context 'wiki' do context 'wiki' do
let!(:project) { create(:project, project_level, :wiki_repo) } let!(:project) { create(:project, project_level, :wiki_repo) }
let(:group) { project.namespace } let(:group) { project.namespace }
...@@ -296,13 +312,27 @@ RSpec.describe Search::GroupService do ...@@ -296,13 +312,27 @@ RSpec.describe Search::GroupService do
let!(:new_updated) { create(:issue, project: project, title: 'updated recent', updated_at: 1.day.ago) } let!(:new_updated) { create(:issue, project: project, title: 'updated recent', updated_at: 1.day.ago) }
let!(:very_old_updated) { create(:issue, project: project, title: 'updated very old', updated_at: 1.year.ago) } let!(:very_old_updated) { create(:issue, project: project, title: 'updated very old', updated_at: 1.year.ago) }
let(:results_created) { described_class.new(nil, group, search: 'sorted', sort: sort).execute }
let(:results_updated) { described_class.new(nil, group, search: 'updated', sort: sort).execute }
before do before do
ensure_elasticsearch_index! ensure_elasticsearch_index!
end end
include_examples 'search results sorted' do context 'elasticsearch_use_group_level_optimization is enabled' do
let(:results_created) { described_class.new(nil, group, search: 'sorted', sort: sort).execute } before do
let(:results_updated) { described_class.new(nil, group, search: 'updated', sort: sort).execute } stub_feature_flags(elasticsearch_use_group_level_optimization: true)
end
include_examples 'search results sorted'
end
context 'elasticsearch_use_group_level_optimization is disabled' do
before do
stub_feature_flags(elasticsearch_use_group_level_optimization: false)
end
include_examples 'search results sorted'
end end
end end
......
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