diff --git a/app/events/repositories/default_branch_changed_event.rb b/app/events/repositories/default_branch_changed_event.rb new file mode 100644 index 0000000000000000000000000000000000000000..3519fb4be86b94cac13c97687552f5ae4cc74071 --- /dev/null +++ b/app/events/repositories/default_branch_changed_event.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Repositories + class DefaultBranchChangedEvent < ::Gitlab::EventStore::Event + def schema + { + 'type' => 'object', + 'properties' => { + 'container_id' => { 'type' => 'integer' }, + 'container_type' => { 'type' => 'string' } + }, + 'required' => %w[container_id container_type] + } + end + end +end diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb index d614d6c4584ec7bf551e318ada3708add4eeac9c..0e7381882b5a4476a23e9945e34f1a497c63b801 100644 --- a/app/models/concerns/has_repository.rb +++ b/app/models/concerns/has_repository.rb @@ -119,6 +119,9 @@ def repository_size_checker def after_repository_change_head reload_default_branch + + Gitlab::EventStore.publish( + Repositories::DefaultBranchChangedEvent.new(data: { container_id: id, container_type: self.class.name })) end def after_change_head_branch_does_not_exist(branch) diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 10da221b6d9ac9b32525680b62ed653bde5bee1c..b54653c52377b96cee03d1ce803fd696245a0338 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -565,6 +565,8 @@ - 1 - - sbom_reports - 1 +- - search_elastic_default_branch_changed + - 1 - - search_elastic_group_association_deletion - 1 - - search_namespace_index_integrity diff --git a/ee/app/models/concerns/elastic/wiki_repositories_search.rb b/ee/app/models/concerns/elastic/wiki_repositories_search.rb index e82b78914962a933108841368de516b594206824..85534ca9dce34bc73d0e1643fc30307a4112c482 100644 --- a/ee/app/models/concerns/elastic/wiki_repositories_search.rb +++ b/ee/app/models/concerns/elastic/wiki_repositories_search.rb @@ -9,6 +9,8 @@ module WikiRepositoriesSearch delegate(:delete_index_for_commits_and_blobs, :elastic_search, to: :__elasticsearch__) def index_wiki_blobs + return unless is_a?(::ProjectWiki) || ::Wiki.use_separate_indices? + ElasticWikiIndexerWorker.perform_async(container.id, container.class.name) end end diff --git a/ee/app/services/ee/git/wiki_push_service.rb b/ee/app/services/ee/git/wiki_push_service.rb index dd6a511df60f1d121d88f5684be6900e4a2c0552..501c73361a394075d7fb413649d39a4d4db0838d 100644 --- a/ee/app/services/ee/git/wiki_push_service.rb +++ b/ee/app/services/ee/git/wiki_push_service.rb @@ -9,8 +9,6 @@ module WikiPushService def execute super - return unless wiki.is_a?(::ProjectWiki) || ::Wiki.use_separate_indices? - return unless wiki.container.use_elasticsearch? && default_branch_changes.any? wiki.index_wiki_blobs diff --git a/ee/app/workers/all_queues.yml b/ee/app/workers/all_queues.yml index a5cd2294230757fb2dae857891a89b27cdc8ed12..2a1ebe99894dd5cb04f03aeece389b5dffd55451 100644 --- a/ee/app/workers/all_queues.yml +++ b/ee/app/workers/all_queues.yml @@ -1794,6 +1794,15 @@ :weight: 1 :idempotent: true :tags: [] +- :name: search_elastic_default_branch_changed + :worker_name: Search::ElasticDefaultBranchChangedWorker + :feature_category: :global_search + :has_external_dependencies: false + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: search_elastic_group_association_deletion :worker_name: Search::ElasticGroupAssociationDeletionWorker :feature_category: :global_search diff --git a/ee/app/workers/search/elastic_default_branch_changed_worker.rb b/ee/app/workers/search/elastic_default_branch_changed_worker.rb new file mode 100644 index 0000000000000000000000000000000000000000..1534e015dd3d294ea9034aa34f1afa9dfd511d42 --- /dev/null +++ b/ee/app/workers/search/elastic_default_branch_changed_worker.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Search + class ElasticDefaultBranchChangedWorker + include ApplicationWorker + include Gitlab::EventStore::Subscriber + + data_consistency :delayed + feature_category :global_search + urgency :low + idempotent! + + def handle_event(event) + return unless ::Gitlab::CurrentSettings.elasticsearch_indexing? + + klass = event.data[:container_type].safe_constantize + object = klass.find_by_id(event.data[:container_id]) + return unless object&.try(:use_elasticsearch?) + + enqueue_indexing(object) + end + + private + + def enqueue_indexing(object) + case object + when Project + object.repository.index_commits_and_blobs + when GroupWiki, ProjectWiki + object.index_wiki_blobs + end + end + end +end diff --git a/ee/lib/ee/gitlab/event_store.rb b/ee/lib/ee/gitlab/event_store.rb index e11013262fed532f451ca65f05babf060faaf7df..5e1eee8725b13d0cc3fb3c80f61f0e1e42e015cd 100644 --- a/ee/lib/ee/gitlab/event_store.rb +++ b/ee/lib/ee/gitlab/event_store.rb @@ -30,6 +30,9 @@ def configure!(store) store.subscribe ::MergeRequests::StreamApprovalAuditEventWorker, to: ::MergeRequests::ApprovedEvent store.subscribe ::MergeRequests::ProcessApprovalAutoMergeWorker, to: ::MergeRequests::ApprovedEvent store.subscribe ::PullMirrors::ReenableConfigurationWorker, to: ::GitlabSubscriptions::RenewedEvent + store.subscribe ::Search::ElasticDefaultBranchChangedWorker, + to: ::Repositories::DefaultBranchChangedEvent, + if: -> (_) { ::Gitlab::CurrentSettings.elasticsearch_indexing? } end end end diff --git a/ee/spec/lib/ee/gitlab/event_store_spec.rb b/ee/spec/lib/ee/gitlab/event_store_spec.rb index 0f22c3a9deea91fa542756470323573c12be5f9c..2ac8ec15937f35aad6ee15d768712268ab43ba03 100644 --- a/ee/spec/lib/ee/gitlab/event_store_spec.rb +++ b/ee/spec/lib/ee/gitlab/event_store_spec.rb @@ -12,7 +12,8 @@ ::Ci::PipelineCreatedEvent, ::Repositories::KeepAroundRefsCreatedEvent, ::MergeRequests::ApprovedEvent, - ::GitlabSubscriptions::RenewedEvent + ::GitlabSubscriptions::RenewedEvent, + ::Repositories::DefaultBranchChangedEvent ) end end diff --git a/ee/spec/workers/search/elastic_default_branch_changed_worker_spec.rb b/ee/spec/workers/search/elastic_default_branch_changed_worker_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..225de71a493bdb653ef650d9fbdcf334f9d91ee0 --- /dev/null +++ b/ee/spec/workers/search/elastic_default_branch_changed_worker_spec.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Search::ElasticDefaultBranchChangedWorker, feature_category: :global_search do + let_it_be_with_reload(:project) { create(:project, :repository) } + + let(:default_branch_changed_event) { Repositories::DefaultBranchChangedEvent.new(data: data) } + let(:container) { project } + let(:data) { { container_id: container.id, container_type: container.class.name } } + + before do + stub_ee_application_setting(elasticsearch_indexing: true) + allow(ElasticCommitIndexerWorker).to receive(:perform_async).and_return(true) + allow(ElasticWikiIndexerWorker).to receive(:perform_async).and_return(true) + end + + it_behaves_like 'subscribes to event' do + let(:event) { default_branch_changed_event } + end + + context 'when passed a project' do + it 'schedules ElasticCommitIndexerWorker' do + expect(ElasticCommitIndexerWorker).to receive(:perform_async).with(project.id) + + consume_event(subscriber: described_class, event: default_branch_changed_event) + end + + context 'when project does not exist' do + let(:data) { { container_id: non_existing_record_id, container_type: container.class.name } } + + it 'does not schedule ElasticCommitIndexerWorker and does not raise an exception' do + expect(ElasticCommitIndexerWorker).not_to receive(:perform_async) + + expect { consume_event(subscriber: described_class, event: default_branch_changed_event) } + .not_to raise_exception + end + end + + context 'when project does not use elasticsearch' do + before do + stub_ee_application_setting(elasticsearch_limit_indexing: true) + end + + it 'does not schedule ElasticCommitIndexerWorker' do + expect(ElasticCommitIndexerWorker).not_to receive(:perform_async) + + consume_event(subscriber: described_class, event: default_branch_changed_event) + end + end + + context 'when elasticsearch_indexing is not enabled' do + before do + stub_ee_application_setting(elasticsearch_indexing: false) + end + + it 'does not schedule ElasticCommitIndexerWorker' do + expect(ElasticCommitIndexerWorker).not_to receive(:perform_async) + + consume_event(subscriber: described_class, event: default_branch_changed_event) + end + end + end + + context 'when passed a group wiki' do + let_it_be(:group_wiki) { create(:group_wiki) } + + let(:container) { group_wiki } + + before do + allow(::Wiki).to receive(:use_separate_indices?).and_return(true) + end + + it 'schedules ElasticWikiIndexerWorker' do + expect(ElasticWikiIndexerWorker) + .to receive(:perform_async).with(group_wiki.group.id, 'Group') + + consume_event(subscriber: described_class, event: default_branch_changed_event) + end + + context 'when group does not exist' do + let(:data) { { container_id: non_existing_record_id, container_type: container.class.name } } + + it 'does not schedule ElasticWikiIndexerWorker and does not raise an exception' do + expect(ElasticWikiIndexerWorker).not_to receive(:perform_async) + + expect { consume_event(subscriber: described_class, event: default_branch_changed_event) } + .not_to raise_exception + end + end + + context 'when group does not use elasticsearch' do + before do + stub_ee_application_setting(elasticsearch_limit_indexing: true) + end + + it 'does not schedule ElasticWikiIndexerWorker' do + expect(ElasticWikiIndexerWorker).not_to receive(:perform_async) + + consume_event(subscriber: described_class, event: default_branch_changed_event) + end + end + + context 'when Wiki.use_separate_indices? is false' do + before do + allow(::Wiki).to receive(:use_separate_indices?).and_return(false) + end + + it 'does not schedule ElasticWikiIndexerWorker' do + expect(ElasticWikiIndexerWorker).not_to receive(:perform_async) + + consume_event(subscriber: described_class, event: default_branch_changed_event) + end + end + end + + context 'when passed a project wiki' do + let_it_be(:project_wiki) { create(:project_wiki, project: project) } + + let(:container) { project_wiki } + + it 'schedules ElasticWikiIndexerWorker' do + expect(ElasticWikiIndexerWorker).to receive(:perform_async).with(project.id, 'Project') + + consume_event(subscriber: described_class, event: default_branch_changed_event) + end + + context 'when project wiki does not exist' do + let(:data) { { container_id: non_existing_record_id, container_type: container.class.name } } + + it 'does not schedule ElasticWikiIndexerWorker and does not raise an exception' do + expect(ElasticWikiIndexerWorker).not_to receive(:perform_async) + + expect { consume_event(subscriber: described_class, event: default_branch_changed_event) } + .not_to raise_exception + end + end + + context 'when project wiki does not use elasticsearch' do + before do + stub_ee_application_setting(elasticsearch_limit_indexing: true) + end + + it 'does not schedule ElasticWikiIndexerWorker' do + expect(ElasticWikiIndexerWorker).not_to receive(:perform_async) + + consume_event(subscriber: described_class, event: default_branch_changed_event) + end + end + end +end diff --git a/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb b/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb index 0a07c9d677b41d8b396d4e7ed52abaf8e99c6d0b..187c0b3ab43fd2175466320a35d61c34a3864a5c 100644 --- a/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb @@ -162,10 +162,24 @@ end describe '#after_repository_change_head' do + let(:event) { instance_double('Repositories::DefaultBranchChangedEvent') } + let(:event_data) { { container_id: stubbed_container.id, container_type: stubbed_container.class.name } } + it 'calls #reload_default_branch' do expect(stubbed_container).to receive(:reload_default_branch) stubbed_container.after_repository_change_head end + + it 'publishes an Repositories::DefaultBranchChangedEvent event' do + allow(Repositories::DefaultBranchChangedEvent) + .to receive(:new) + .with(data: event_data) + .and_return(event) + + expect(Gitlab::EventStore).to receive(:publish).with(event).once + + stubbed_container.after_repository_change_head + end end end