Commit 7caba595 authored by Fabio Pitino's avatar Fabio Pitino

Merge branch '342049-update-namespace-stats-when-project-deleted' into 'master'

Update namespace statistics when a project is deleted

See merge request gitlab-org/gitlab!78862
parents 42ef30b8 1e78b395
# frozen_string_literal: true
module Projects
class ProjectDeletedEvent < ::Gitlab::EventStore::Event
def schema
{
'type' => 'object',
'properties' => {
'project_id' => { 'type' => 'integer' },
'namespace_id' => { 'type' => 'integer' }
},
'required' => %w[project_id namespace_id]
}
end
end
end
...@@ -37,6 +37,8 @@ module Projects ...@@ -37,6 +37,8 @@ module Projects
system_hook_service.execute_hooks_for(project, :destroy) system_hook_service.execute_hooks_for(project, :destroy)
log_info("Project \"#{project.full_path}\" was deleted") log_info("Project \"#{project.full_path}\" was deleted")
publish_project_deleted_event_for(project) if Feature.enabled?(:publish_project_deleted_event, default_enabled: :yaml)
current_user.invalidate_personal_projects_count current_user.invalidate_personal_projects_count
true true
...@@ -260,6 +262,12 @@ module Projects ...@@ -260,6 +262,12 @@ module Projects
def flush_caches(project) def flush_caches(project)
Projects::ForksCountService.new(project).delete_cache Projects::ForksCountService.new(project).delete_cache
end end
def publish_project_deleted_event_for(project)
data = { project_id: project.id, namespace_id: project.namespace_id }
event = Projects::ProjectDeletedEvent.new(data: data)
Gitlab::EventStore.publish(event)
end
end end
end end
......
...@@ -2569,6 +2569,15 @@ ...@@ -2569,6 +2569,15 @@
:weight: 1 :weight: 1
:idempotent: true :idempotent: true
:tags: [] :tags: []
- :name: namespaces_update_root_statistics
:worker_name: Namespaces::UpdateRootStatisticsWorker
:feature_category: :source_code_management
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: new_issue - :name: new_issue
:worker_name: NewIssueWorker :worker_name: NewIssueWorker
:feature_category: :team_planning :feature_category: :team_planning
......
# frozen_string_literal: true
module Namespaces
class UpdateRootStatisticsWorker
include Gitlab::EventStore::Subscriber
data_consistency :always
idempotent!
feature_category :source_code_management
def handle_event(event)
ScheduleAggregationWorker.perform_async(event.data[:namespace_id])
end
end
end
---
name: publish_project_deleted_event
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78862
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351073
milestone: '14.8'
type: development
group: group::pipeline insights
default_enabled: false
...@@ -295,6 +295,8 @@ ...@@ -295,6 +295,8 @@
- 1 - 1
- - namespaces_sync_namespace_name - - namespaces_sync_namespace_name
- 1 - 1
- - namespaces_update_root_statistics
- 1
- - new_epic - - new_epic
- 2 - 2
- - new_issue - - new_issue
......
...@@ -34,6 +34,7 @@ module Gitlab ...@@ -34,6 +34,7 @@ module Gitlab
# Add subscriptions here: # Add subscriptions here:
store.subscribe ::MergeRequests::UpdateHeadPipelineWorker, to: ::Ci::PipelineCreatedEvent store.subscribe ::MergeRequests::UpdateHeadPipelineWorker, to: ::Ci::PipelineCreatedEvent
store.subscribe ::Namespaces::UpdateRootStatisticsWorker, to: ::Projects::ProjectDeletedEvent
end end
private_class_method :configure! private_class_method :configure!
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::ProjectDeletedEvent do
where(:data, :valid) do
[
[{ project_id: 1, namespace_id: 2 }, true],
[{ project_id: 1 }, false],
[{ namespace_id: 1 }, false],
[{ project_id: 'foo', namespace_id: 2 }, false],
[{ project_id: 1, namespace_id: 'foo' }, false],
[{ project_id: [], namespace_id: 2 }, false],
[{ project_id: 1, namespace_id: [] }, false],
[{ project_id: {}, namespace_id: 2 }, false],
[{ project_id: 1, namespace_id: {} }, false],
['foo', false],
[123, false],
[[], false]
]
end
with_them do
it 'validates data' do
constructor = -> { described_class.new(data: data) }
if valid
expect { constructor.call }.not_to raise_error
else
expect { constructor.call }.to raise_error(Gitlab::EventStore::InvalidEvent)
end
end
end
end
...@@ -18,17 +18,35 @@ RSpec.describe Projects::DestroyService, :aggregate_failures do ...@@ -18,17 +18,35 @@ RSpec.describe Projects::DestroyService, :aggregate_failures do
end end
shared_examples 'deleting the project' do shared_examples 'deleting the project' do
before do it 'deletes the project', :sidekiq_inline do
# Run sidekiq immediately to check that renamed repository will be removed
destroy_project(project, user, {}) destroy_project(project, user, {})
end
it 'deletes the project', :sidekiq_inline do
expect(Project.unscoped.all).not_to include(project) expect(Project.unscoped.all).not_to include(project)
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_falsey
expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey expect(project.gitlab_shell.repository_exists?(project.repository_storage, remove_path + '.git')).to be_falsey
end end
it 'publishes a ProjectDeleted event with project id and namespace id' do
expected_data = { project_id: project.id, namespace_id: project.namespace_id }
expect(Gitlab::EventStore)
.to receive(:publish)
.with(event_type(Projects::ProjectDeletedEvent).containing(expected_data))
destroy_project(project, user, {})
end
context 'when feature flag publish_project_deleted_event is disabled' do
before do
stub_feature_flags(publish_project_deleted_event: false)
end
it 'does not publish an event' do
expect(Gitlab::EventStore).not_to receive(:publish)
destroy_project(project, user, {})
end
end
end end
shared_examples 'deleting the project with pipeline and build' do shared_examples 'deleting the project with pipeline and build' do
......
# frozen_string_literal: true
RSpec::Matchers.define :event_type do |event_class|
match do |actual|
actual.instance_of?(event_class) &&
actual.data == @expected_data
end
chain :containing do |expected_data|
@expected_data = expected_data
end
end
...@@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do ...@@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do context 'when level is unit' do
it 'returns a pattern' do it 'returns a pattern' do
expect(subject.pattern(:unit)) expect(subject.pattern(:unit))
.to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb") .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,events,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb")
end end
end end
...@@ -110,7 +110,7 @@ RSpec.describe Quality::TestLevel do ...@@ -110,7 +110,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do context 'when level is unit' do
it 'returns a regexp' do it 'returns a regexp' do
expect(subject.regexp(:unit)) expect(subject.regexp(:unit))
.to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)}) .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|events|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)})
end end
end end
......
...@@ -349,6 +349,7 @@ RSpec.describe 'Every Sidekiq worker' do ...@@ -349,6 +349,7 @@ RSpec.describe 'Every Sidekiq worker' do
'Namespaces::OnboardingPipelineCreatedWorker' => 3, 'Namespaces::OnboardingPipelineCreatedWorker' => 3,
'Namespaces::OnboardingProgressWorker' => 3, 'Namespaces::OnboardingProgressWorker' => 3,
'Namespaces::OnboardingUserAddedWorker' => 3, 'Namespaces::OnboardingUserAddedWorker' => 3,
'Namespaces::RefreshRootStatisticsWorker' => 3,
'Namespaces::RootStatisticsWorker' => 3, 'Namespaces::RootStatisticsWorker' => 3,
'Namespaces::ScheduleAggregationWorker' => 3, 'Namespaces::ScheduleAggregationWorker' => 3,
'NetworkPolicyMetricsWorker' => 3, 'NetworkPolicyMetricsWorker' => 3,
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Namespaces::UpdateRootStatisticsWorker do
let(:namespace_id) { 123 }
let(:event) do
Projects::ProjectDeletedEvent.new(data: { project_id: 1, namespace_id: namespace_id })
end
subject { consume_event(event) }
def consume_event(event)
described_class.new.perform(event.class.name, event.data)
end
it 'enqueues ScheduleAggregationWorker' do
expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async).with(namespace_id)
subject
end
end
...@@ -24,6 +24,7 @@ module Quality ...@@ -24,6 +24,7 @@ module Quality
elastic elastic
elastic_integration elastic_integration
experiments experiments
events
factories factories
finders finders
frontend frontend
......
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