diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 84cbf015a16e963da9cbc557f85f27d4b7c90a0a..6f04bb70a6ba2b71737ce3683ce57d5bbe3d3de3 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -479,6 +479,8 @@ - 1 - - gitlab_subscriptions_members_record_last_activity - 1 +- - gitlab_subscriptions_members_updated + - 1 - - gitlab_subscriptions_refresh_seats - 1 - - gitlab_subscriptions_seat_assignments_group_links_create_or_update_seats diff --git a/ee/app/events/members/updated_event.rb b/ee/app/events/members/updated_event.rb new file mode 100644 index 0000000000000000000000000000000000000000..608f3e1a13d7e06280b16397adc808f4d2686e1b --- /dev/null +++ b/ee/app/events/members/updated_event.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Members + class UpdatedEvent < ::Gitlab::EventStore::Event + def schema + { + 'type' => 'object', + 'required' => %w[source_id source_type user_id], + 'properties' => { + 'root_namespace_id' => { 'type' => 'integer' }, + 'source_id' => { 'type' => 'integer' }, + 'source_type' => { 'type' => 'string' }, + 'user_id' => { 'type' => %w[integer null] } + } + } + end + end +end diff --git a/ee/app/models/ee/member.rb b/ee/app/models/ee/member.rb index e5d1cd7523c9adc676f67b9b643d254c205cc0b7..523c18a94b8311ee28a7e3e1be75b5124d4eb5e2 100644 --- a/ee/app/models/ee/member.rb +++ b/ee/app/models/ee/member.rb @@ -77,6 +77,8 @@ module Member end scope :order_access_level_desc, -> { order(access_level: :desc) } + + after_commit :publish_member_updated_event, on: [:create, :update], if: -> { user_id.present? } end class_methods do @@ -328,5 +330,15 @@ def after_accept_request update_user_group_member_roles end + + def publish_member_updated_event + ::Gitlab::EventStore.publish( + ::Members::UpdatedEvent.new(data: { + source_id: source.id, + source_type: source.class.name, + user_id: user_id + }) + ) + end end end diff --git a/ee/app/services/gitlab_subscriptions/seat_assignment_service.rb b/ee/app/services/gitlab_subscriptions/seat_assignment_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..3089cfc5dbc67023aaf5c0ebfe8b16164f2e3447 --- /dev/null +++ b/ee/app/services/gitlab_subscriptions/seat_assignment_service.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module GitlabSubscriptions + class SeatAssignmentService + def initialize(user, namespace) + @user = user + @namespace = namespace&.root_ancestor + end + + def execute + return ServiceResponse.error(message: 'Invalid params') unless user && namespace&.group_namespace? + return ServiceResponse.error(message: 'User is not a member') unless user_is_a_member? + + seat_assignment = find_seat_assignment || build_seat_assignment + + seat_type = GitlabSubscriptions::SeatTypeCalculator.execute(user, namespace) + seat_assignment.update(seat_type: seat_type) + + if seat_assignment.save + ServiceResponse.success(message: 'Seat assignment updated') + else + ServiceResponse.error(message: 'Unable to update seat assignment') + end + end + + private + + attr_reader :user, :namespace + + def find_seat_assignment + GitlabSubscriptions::SeatAssignment.find_by_namespace_and_user(namespace, user) + end + + def build_seat_assignment + GitlabSubscriptions::SeatAssignment.new( + namespace: namespace, + user: user, + organization_id: namespace.organization_id || Organizations::Organization::DEFAULT_ORGANIZATION_ID + ) + end + + def user_is_a_member? + ::Member.in_hierarchy(namespace).with_user(user).exists? + end + end +end diff --git a/ee/app/workers/all_queues.yml b/ee/app/workers/all_queues.yml index 4e687b14e2ccb3bfe80c07139bf58222b91c073b..152241fdeb14004a79fa4b7c5dff7f39e5fd7d8c 100644 --- a/ee/app/workers/all_queues.yml +++ b/ee/app/workers/all_queues.yml @@ -2443,6 +2443,16 @@ :idempotent: true :tags: [] :queue_namespace: +- :name: gitlab_subscriptions_members_updated + :worker_name: GitlabSubscriptions::Members::UpdatedWorker + :feature_category: :seat_cost_management + :has_external_dependencies: false + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] + :queue_namespace: - :name: gitlab_subscriptions_refresh_seats :worker_name: GitlabSubscriptions::RefreshSeatsWorker :feature_category: :seat_cost_management diff --git a/ee/app/workers/gitlab_subscriptions/members/updated_worker.rb b/ee/app/workers/gitlab_subscriptions/members/updated_worker.rb new file mode 100644 index 0000000000000000000000000000000000000000..a1e759237eb167a29d3409aeff77a11486f19483 --- /dev/null +++ b/ee/app/workers/gitlab_subscriptions/members/updated_worker.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module GitlabSubscriptions + module Members + class UpdatedWorker + include Gitlab::EventStore::Subscriber + + DEFER_ON_HEALTH_DELAY = 1.minute + + data_consistency :delayed + feature_category :seat_cost_management + urgency :low + + defer_on_database_health_signal :gitlab_main, [:users, :namespaces, :members], DEFER_ON_HEALTH_DELAY + + idempotent! + deduplicate :until_executing, including_scheduled: true + + def handle_event(event) + user = ::User.find_by_id(event.data[:user_id]) + namespace = ::Namespace.find_by_id(event.data[:root_namespace_id]) + + return unless user && namespace&.group_namespace? + + ::GitlabSubscriptions::SeatAssignmentService.new(user, namespace).execute + end + end + end +end diff --git a/ee/lib/ee/gitlab/event_store.rb b/ee/lib/ee/gitlab/event_store.rb index 6e7dd3fca0fc57771e7ec63b9d74ac9ea194e3e4..0938896f4b1d41e11a73fc7693197eed0f5bf405 100644 --- a/ee/lib/ee/gitlab/event_store.rb +++ b/ee/lib/ee/gitlab/event_store.rb @@ -69,6 +69,7 @@ def configure!(store) if: ->(_) { ::Gitlab::CurrentSettings.enable_member_promotion_management? } + store.subscribe ::GitlabSubscriptions::Members::UpdatedWorker, to: ::Members::UpdatedEvent register_threat_insights_subscribers(store) register_security_policy_subscribers(store)