Commit d25b4bf4 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch...

Merge branch '300351-remove-joins-from-the-elasticsearch-query-for-project-group-scoped-notes-comments-search-3' into 'master'

Add permission for indexed notes and update when permissions change

See merge request gitlab-org/gitlab!53415
parents 3897865b 335baf56
......@@ -108,6 +108,7 @@ module EE
has_one :security_orchestration_policy_configuration, class_name: 'Security::OrchestrationPolicyConfiguration', foreign_key: :project_id, inverse_of: :project
elastic_index_dependant_association :issues, on_change: :visibility_level
elastic_index_dependant_association :notes, on_change: :visibility_level
scope :with_shared_runners_limit_enabled, -> do
if ::Ci::Runner.has_shared_runners_with_non_zero_public_cost?
......
......@@ -5,6 +5,7 @@ module EE
extend ActiveSupport::Concern
EE_FEATURES = %i(requirements security_and_compliance).freeze
NOTES_PERMISSION_TRACKED_FIELDS = %w(issues_access_level repository_access_level merge_requests_access_level snippets_access_level).freeze
prepended do
set_available_features(EE_FEATURES)
......@@ -14,7 +15,11 @@ module EE
if project.maintaining_elasticsearch?
project.maintain_elasticsearch_update
ElasticAssociationIndexerWorker.perform_async(self.project.class.name, project_id, ['issues']) if elasticsearch_project_associations_need_updating?
associations_to_update = []
associations_to_update << 'issues' if elasticsearch_project_issues_need_updating?
associations_to_update << 'notes' if elasticsearch_project_notes_need_updating?
ElasticAssociationIndexerWorker.perform_async(self.project.class.name, project_id, associations_to_update) if associations_to_update.any?
end
end
......@@ -23,7 +28,11 @@ module EE
private
def elasticsearch_project_associations_need_updating?
def elasticsearch_project_notes_need_updating?
self.previous_changes.keys.any? { |key| NOTES_PERMISSION_TRACKED_FIELDS.include?(key) }
end
def elasticsearch_project_issues_need_updating?
self.previous_changes.key?(:issues_access_level)
end
end
......
......@@ -54,6 +54,22 @@ module Elastic
nil
end
# protect against missing project_feature and set visibility to PRIVATE
# if the project_feature is missing on a project
def safely_read_project_feature_for_elasticsearch(feature)
if target.project.project_feature
target.project.project_feature.access_level(feature)
else
logger.warn(
message: 'Project is missing ProjectFeature',
project_id: target.project_id,
id: target.id,
class: target.class
)
ProjectFeature::PRIVATE
end
end
def apply_field_limit(result)
return result unless result.is_a? String
......
......@@ -14,15 +14,7 @@ module Elastic
data['assignee_id'] = safely_read_attribute_for_elasticsearch(:assignee_ids)
data['visibility_level'] = target.project.visibility_level
# protect against missing project_feature and set visibility to PRIVATE
# if the project_feature is missing on a project
begin
data['issues_access_level'] = target.project.project_feature.issues_access_level
rescue NoMethodError => e
Gitlab::ErrorTracking.track_exception(e, project_id: target.project_id, issue_id: target.id)
data['issues_access_level'] = ProjectFeature::PRIVATE
end
data['issues_access_level'] = safely_read_project_feature_for_elasticsearch(:issues)
data.merge(generic_attributes)
end
......
......@@ -22,8 +22,31 @@ module Elastic
}
end
# do not add the permission fields unless the `remove_permissions_data_from_notes_documents`
# migration has completed otherwise the migration will never finish
if Elastic::DataMigrationService.migration_has_finished?(:remove_permissions_data_from_notes_documents)
data['visibility_level'] = target.project.visibility_level
merge_project_feature_access_level(data, noteable)
end
data.merge(generic_attributes)
end
private
def merge_project_feature_access_level(data, noteable)
return unless noteable
case noteable
when Snippet
data['snippets_access_level'] = safely_read_project_feature_for_elasticsearch(:snippets)
when Commit
data['repository_access_level'] = safely_read_project_feature_for_elasticsearch(:repository)
else
access_level_attribute = ProjectFeature.access_level_attribute(noteable)
data[access_level_attribute.to_s] = safely_read_project_feature_for_elasticsearch(noteable)
end
end
end
end
end
......@@ -11,6 +11,13 @@ RSpec.describe RemovePermissionsDataFromNotesDocuments, :elastic, :sidekiq_inlin
before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
allow(migration).to receive(:helper).and_return(helper)
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?).and_call_original
# migrations are completed by default in test environments
# required to prevent the `as_indexed_json` method from populating the permissions fields
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:remove_permissions_data_from_notes_documents)
.and_return(false)
end
describe 'migration_options' do
......
......@@ -132,12 +132,11 @@ RSpec.describe Issue, :elastic do
expect(issue.__elasticsearch__.as_indexed_json).to eq(expected_hash)
end
it 'handles a project missing project_feature' do
it 'handles a project missing project_feature', :aggregate_failures do
issue = create :issue, project: project
allow(issue.project).to receive(:project_feature).and_return(nil)
expect(Gitlab::ErrorTracking).to receive(:track_exception)
expect { issue.__elasticsearch__.as_indexed_json }.not_to raise_error
expect(issue.__elasticsearch__.as_indexed_json['issues_access_level']).to eq(ProjectFeature::PRIVATE)
end
......
......@@ -85,10 +85,14 @@ RSpec.describe Note, :elastic do
expect(described_class.elastic_search('term', options: options).total_count).to eq(4)
end
describe 'json serialization' do
using RSpec::Parameterized::TableSyntax
it "returns json with all needed elements" do
assignee = create(:user)
issue = create(:issue, assignees: [assignee])
note = create(:note, noteable: issue, project: issue.project)
project = create(:project)
issue = create(:issue, project: project, assignees: [assignee])
note = create(:note, noteable: issue, project: project)
expected_hash = note.attributes.extract!(
'id',
......@@ -109,12 +113,54 @@ RSpec.describe Note, :elastic do
'join_field' => {
'name' => note.es_type,
'parent' => note.es_parent
}
},
'visibility_level' => project.visibility_level,
'issues_access_level' => project.issues_access_level
})
expect(note.__elasticsearch__.as_indexed_json).to eq(expected_hash)
end
it 'does not raise error for notes with null noteable references' do
note = create(:note_on_issue)
allow(note).to receive(:noteable).and_return(nil)
expect { note.__elasticsearch__.as_indexed_json }.not_to raise_error
end
where(:note_type, :permission, :access_level) do
:note_on_issue | ProjectFeature::ENABLED | 'issues_access_level'
:note_on_project_snippet | ProjectFeature::DISABLED | 'snippets_access_level'
:note_on_merge_request | ProjectFeature::PUBLIC | 'merge_requests_access_level'
:note_on_commit | ProjectFeature::PRIVATE | 'repository_access_level'
end
with_them do
let_it_be(:project) { create(:project, :repository) }
let!(:note) { create(note_type, project: project) } # rubocop:disable Rails/SaveBang
let(:note_json) { note.__elasticsearch__.as_indexed_json }
before do
project.project_feature.update_attribute(access_level.to_sym, permission)
end
it 'does not contain permissions if remove_permissions_data_from_notes_documents is not finished' do
allow(Elastic::DataMigrationService).to receive(:migration_has_finished?)
.with(:remove_permissions_data_from_notes_documents)
.and_return(false)
expect(note_json).not_to have_key(access_level)
expect(note_json).not_to have_key('visibility_level')
end
it 'contains the correct permissions', :aggregate_failures do
expect(note_json).to have_key(access_level)
expect(note_json[access_level]).to eq(permission)
expect(note_json).to have_key('visibility_level')
end
end
end
it 'does not track system note updates' do
note = create(:note, :system)
......
......@@ -28,13 +28,14 @@ RSpec.describe ProjectFeature do
allow(project).to receive(:maintaining_elasticsearch?).and_return(true)
end
where(:feature, :worker_expected) do
'issues' | true
'wiki' | false
'builds' | false
'merge_requests' | false
'repository' | false
'pages' | false
where(:feature, :worker_expected, :associations) do
'issues' | true | %w[issues notes]
'wiki' | false | nil
'builds' | false | nil
'merge_requests' | true | %w[notes]
'repository' | true | %w[notes]
'snippets' | true | %w[notes]
'pages' | false | nil
end
with_them do
......@@ -42,7 +43,7 @@ RSpec.describe ProjectFeature do
expect(project).to receive(:maintain_elasticsearch_update)
if worker_expected
expect(ElasticAssociationIndexerWorker).to receive(:perform_async).with('Project', project.id, ['issues'])
expect(ElasticAssociationIndexerWorker).to receive(:perform_async).with('Project', project.id, associations)
else
expect(ElasticAssociationIndexerWorker).not_to receive(:perform_async)
end
......
......@@ -2663,8 +2663,8 @@ RSpec.describe Project do
let!(:issue) { create(:issue, project: project) }
context 'when updating the visibility_level' do
it 'triggers ElasticAssociationIndexerWorker to update issues' do
expect(ElasticAssociationIndexerWorker).to receive(:perform_async).with('Project', project.id, ['issues'])
it 'triggers ElasticAssociationIndexerWorker to update issues and notes' do
expect(ElasticAssociationIndexerWorker).to receive(:perform_async).with('Project', project.id, %w[issues notes])
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
......
......@@ -67,11 +67,11 @@ RSpec.describe Groups::TransferService, '#execute' do
expect(::Gitlab::CurrentSettings).not_to receive(:invalidate_elasticsearch_indexes_cache_for_project!)
expect(Elastic::ProcessBookkeepingService).to receive(:track!).with(project1)
expect(ElasticAssociationIndexerWorker).to receive(:perform_async).with('Project', project1.id, ['issues'])
expect(ElasticAssociationIndexerWorker).to receive(:perform_async).with('Project', project1.id, %w[issues notes])
expect(Elastic::ProcessBookkeepingService).to receive(:track!).with(project2)
expect(ElasticAssociationIndexerWorker).to receive(:perform_async).with('Project', project2.id, ['issues'])
expect(ElasticAssociationIndexerWorker).to receive(:perform_async).with('Project', project2.id, %w[issues notes])
expect(Elastic::ProcessBookkeepingService).not_to receive(:track!).with(project3)
expect(ElasticAssociationIndexerWorker).not_to receive(:perform_async).with('Project', project3.id, ['issues'])
expect(ElasticAssociationIndexerWorker).not_to receive(:perform_async).with('Project', project3.id, %w[issues notes])
transfer_service.execute(new_group)
......
......@@ -72,9 +72,9 @@ RSpec.describe Projects::TransferService do
context 'when visibility level changes' do
let_it_be(:group) { create(:group, :private) }
it 'reindexes the project and associated issues' do
it 'reindexes the project and associated issues and notes' do
expect(Elastic::ProcessBookkeepingService).to receive(:track!).with(project)
expect(ElasticAssociationIndexerWorker).to receive(:perform_async).with('Project', project.id, ['issues'])
expect(ElasticAssociationIndexerWorker).to receive(:perform_async).with('Project', project.id, %w[issues notes])
subject.execute(group)
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