Commit a1d583e1 authored by Vitali Tatarintev's avatar Vitali Tatarintev

Merge branch '332592-add-state-machine-to-members' into 'master'

Add state machine for "GroupMember#state"

See merge request gitlab-org/gitlab!66916
parents 2653948a 6130374e
...@@ -477,7 +477,7 @@ module EE ...@@ -477,7 +477,7 @@ module EE
def user_cap_reached?(requested_hosted_plan = nil) def user_cap_reached?(requested_hosted_plan = nil)
return false unless ::Feature.enabled?(:saas_user_caps, self, default_enabled: :yaml) return false unless ::Feature.enabled?(:saas_user_caps, self, default_enabled: :yaml)
user_cap = root_ancestor.new_user_signups_cap user_cap = root_ancestor.namespace_settings&.new_user_signups_cap
return false unless user_cap return false unless user_cap
user_cap <= billable_members_count(requested_hosted_plan) user_cap <= billable_members_count(requested_hosted_plan)
......
...@@ -8,6 +8,10 @@ module EE ...@@ -8,6 +8,10 @@ module EE
prepended do prepended do
include UsageStatistics include UsageStatistics
STATE_CREATED = 0
STATE_AWAITING = 1
STATE_ACTIVE = 2
validate :sso_enforcement, if: :group validate :sso_enforcement, if: :group
validate :group_domain_limitations, if: :group_has_domain_limitations? validate :group_domain_limitations, if: :group_has_domain_limitations?
...@@ -25,6 +29,24 @@ module EE ...@@ -25,6 +29,24 @@ module EE
scope :guests, -> { where(access_level: ::Gitlab::Access::GUEST) } scope :guests, -> { where(access_level: ::Gitlab::Access::GUEST) }
scope :non_owners, -> { where("members.access_level < ?", ::Gitlab::Access::OWNER) } scope :non_owners, -> { where("members.access_level < ?", ::Gitlab::Access::OWNER) }
scope :by_user_id, ->(user_id) { where(user_id: user_id) } scope :by_user_id, ->(user_id) { where(user_id: user_id) }
state_machine :state, initial: :created do
event :wait do
transition created: :awaiting
transition active: :awaiting
end
event :activate do
transition created: :active
transition awaiting: :active
end
state :created, value: STATE_CREATED
state :awaiting, value: STATE_AWAITING
state :active, value: STATE_ACTIVE
end
before_create :set_membership_activation
end end
class_methods do class_methods do
...@@ -75,6 +97,12 @@ module EE ...@@ -75,6 +97,12 @@ module EE
execute_hooks_for(:destroy) execute_hooks_for(:destroy)
end end
def set_membership_activation
return unless ::Feature.enabled?(:saas_user_caps, group, default_enabled: :yaml)
self.state = group.user_cap_reached? ? STATE_AWAITING : STATE_ACTIVE
end
def execute_hooks_for(event) def execute_hooks_for(event)
return unless self.source.feature_available?(:group_webhooks) return unless self.source.feature_available?(:group_webhooks)
return unless GroupHook.where(group_id: self.source.self_and_ancestors).exists? return unless GroupHook.where(group_id: self.source.self_and_ancestors).exists?
......
...@@ -279,6 +279,53 @@ RSpec.describe GroupMember do ...@@ -279,6 +279,53 @@ RSpec.describe GroupMember do
end end
end end
context 'check if user cap has been reached' do
let_it_be(:group) { create(:group_with_plan, plan: :ultimate_plan) }
let!(:user) { create(:user) }
subject(:add_user_to_group) { group.add_developer(user) }
context 'when the :saas_user_caps feature flag is disabled' do
before do
stub_feature_flags(saas_user_caps: false)
end
it 'leaves the group member state to created' do
add_user_to_group
expect(user.group_members.last).to be_created
end
end
context 'when the :saas_user_caps feature flag is enabled' do
before do
stub_feature_flags(saas_user_caps: group)
allow(group).to receive(:user_cap_reached?).and_return(user_cap_reached)
end
context 'when the user cap for this group has not been reached' do
let(:user_cap_reached) { false }
it 'sets the group member to active' do
add_user_to_group
expect(user.group_members.last).to be_active
end
end
context 'when the user cap for this group has been reached' do
let(:user_cap_reached) { true }
it 'sets the group member to awaiting' do
add_user_to_group
expect(user.group_members.last).to be_awaiting
end
end
end
end
describe '#provisioned_by_this_group?' do describe '#provisioned_by_this_group?' do
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
......
...@@ -1485,13 +1485,10 @@ RSpec.describe GroupPolicy do ...@@ -1485,13 +1485,10 @@ RSpec.describe GroupPolicy do
end end
context 'when parent group has resource access token creation disabled' do context 'when parent group has resource access token creation disabled' do
let(:parent) { create(:group_with_plan, plan: :bronze_plan) } let(:namespace_settings) { create(:namespace_settings, resource_access_token_creation_allowed: false) }
let(:parent) { create(:group_with_plan, plan: :bronze_plan, namespace_settings: namespace_settings) }
let(:group) { create(:group, parent: parent) } let(:group) { create(:group, parent: parent) }
before do
parent.namespace_settings.update_column(:resource_access_token_creation_allowed, false)
end
context 'cannot create resource access tokens' do context 'cannot create resource access tokens' do
it { is_expected.not_to be_allowed(:create_resource_access_tokens) } it { is_expected.not_to be_allowed(:create_resource_access_tokens) }
end end
......
...@@ -1072,7 +1072,7 @@ RSpec.describe API::MergeRequests do ...@@ -1072,7 +1072,7 @@ RSpec.describe API::MergeRequests do
end end
describe "GET /groups/:id/merge_requests" do describe "GET /groups/:id/merge_requests" do
let_it_be(:group) { create(:group, :public) } let_it_be(:group, reload: true) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, :repository, creator: user, namespace: group, only_allow_merge_if_pipeline_succeeds: false) } let_it_be(:project) { create(:project, :public, :repository, creator: user, namespace: group, only_allow_merge_if_pipeline_succeeds: false) }
include_context 'with merge requests' include_context 'with merge requests'
......
...@@ -6,18 +6,20 @@ RSpec.describe Groups::GroupLinks::CreateService, '#execute' do ...@@ -6,18 +6,20 @@ RSpec.describe Groups::GroupLinks::CreateService, '#execute' do
let(:parent_group_user) { create(:user) } let(:parent_group_user) { create(:user) }
let(:group_user) { create(:user) } let(:group_user) { create(:user) }
let(:child_group_user) { create(:user) } let(:child_group_user) { create(:user) }
let(:prevent_sharing) { false }
let_it_be(:group_parent) { create(:group, :private) } let_it_be(:group_parent) { create(:group, :private) }
let_it_be(:group) { create(:group, :private, parent: group_parent) } let_it_be(:group) { create(:group, :private, parent: group_parent) }
let_it_be(:group_child) { create(:group, :private, parent: group) } let_it_be(:group_child) { create(:group, :private, parent: group) }
let_it_be(:shared_group_parent, refind: true) { create(:group, :private) } let(:ns_for_parent) { create(:namespace_settings, prevent_sharing_groups_outside_hierarchy: prevent_sharing) }
let_it_be(:shared_group, refind: true) { create(:group, :private, parent: shared_group_parent) } let(:shared_group_parent) { create(:group, :private, namespace_settings: ns_for_parent) }
let_it_be(:shared_group_child) { create(:group, :private, parent: shared_group) } let(:shared_group) { create(:group, :private, parent: shared_group_parent) }
let(:shared_group_child) { create(:group, :private, parent: shared_group) }
let_it_be(:project_parent) { create(:project, group: shared_group_parent) } let(:project_parent) { create(:project, group: shared_group_parent) }
let_it_be(:project) { create(:project, group: shared_group) } let(:project) { create(:project, group: shared_group) }
let_it_be(:project_child) { create(:project, group: shared_group_child) } let(:project_child) { create(:project, group: shared_group_child) }
let(:opts) do let(:opts) do
{ {
...@@ -129,9 +131,7 @@ RSpec.describe Groups::GroupLinks::CreateService, '#execute' do ...@@ -129,9 +131,7 @@ RSpec.describe Groups::GroupLinks::CreateService, '#execute' do
end end
context 'sharing outside the hierarchy is disabled' do context 'sharing outside the hierarchy is disabled' do
before do let(:prevent_sharing) { true }
shared_group_parent.namespace_settings.update!(prevent_sharing_groups_outside_hierarchy: true)
end
it 'prevents sharing with a group outside the hierarchy' do it 'prevents sharing with a group outside the hierarchy' do
result = subject.execute result = subject.execute
......
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