Skip to content

Move state machine from GroupMember to Member

What does this MR do and why?

Relates to: #334288 (closed)

After some discussion here (!70675 (comment 687563546)) We decided to move the code introduced in this MR (!66916 (merged)) from GroupMember to Member model, so we can query different states in both GroupMember and ProjectMember.

Screenshots or screen recordings

No visual changes

How to set up and validate locally

1. Creation of GroupMember with saas_user_caps FF turned on - user cap not reached yet

The following scenario shows the creation of a GroupMember for a given group which user cap has not been reached. As a result, we successfully create a GroupMember which is active (state: 2)

[1] pry(main)> group = Group.find(390)
[2] pry(main)> Feature.enable(:saas_user_caps, group)
=> true
[3] pry(main)> Feature.enabled?(:saas_user_caps, group, default_enabled: :yaml)
=> true
[4] pry(main)> group.user_cap_reached?
=> false
[5] pry(main)> admin = User.find_by(username: 'root')
[6] pry(main)> params = { source: group, user_ids: '5', access_level: Gitlab::Access::DEVELOPER, invite_source: 'console' };
[7] pry(main)> result = Members::CreateService.new(admin, params).execute;
[8] pry(main)> result
=> {:status=>:success}
[9] pry(main)> GroupMember.last
=> #<GroupMember:0x00007fee74f1d258
 id: 214,
 access_level: 30,
 source_id: 390,
 source_type: "Namespace",
 user_id: 5,
 notification_level: 3,
 type: "GroupMember",
 created_at: Tue, 28 Sep 2021 08:36:27.420463000 UTC +00:00,
 updated_at: Tue, 28 Sep 2021 08:36:27.420463000 UTC +00:00,
 created_by_id: 1,
 invite_email: nil,
 invite_token: nil,
 invite_accepted_at: nil,
 requested_at: nil,
 expires_at: nil,
 ldap: false,
 override: false,
 invite_email_success: true,
 state: 2>
[10] pry(main)> GroupMember.last.active?
=> true

2. Creation of GroupMember with saas_user_caps FF turned on - user cap already reached

In this second scenario, we first destroy the user created above (so that we can reuse the same params used earlier), then we set the user cap limit to the current number of group members for this group. This implicitly means that the cap has been reached. Then we create a new group member: as expected it's created and awaiting (state: 1)

[11] pry(main)> GroupMember.last.destroy
=> #<GroupMember:0x00007fee74e94ed0
 id: 214,
 access_level: 30,
 source_id: 390,
 source_type: "Namespace",
 user_id: 5,
 notification_level: 3,
 type: "GroupMember",
 created_at: Tue, 28 Sep 2021 08:36:27.420463000 UTC +00:00,
 updated_at: Tue, 28 Sep 2021 08:36:27.420463000 UTC +00:00,
 created_by_id: 1,
 invite_email: nil,
 invite_token: nil,
 invite_accepted_at: nil,
 requested_at: nil,
 expires_at: nil,
 ldap: false,
 override: false,
 invite_email_success: true,
 state: 2>
[12] pry(main)> group.namespace_settings.update!(new_user_signups_cap: group.group_members.count)
=> true
[13] pry(main)> group.user_cap_reached?
=> true
[14] pry(main)> result = Members::CreateService.new(admin, params).execute
[15] pry(main)> result
=> {:status=>:success}
[16] pry(main)> GroupMember.last
=> #<GroupMember:0x00007fee97dc77c8
 id: 215,
 access_level: 30,
 source_id: 390,
 source_type: "Namespace",
 user_id: 5,
 notification_level: 3,
 type: "GroupMember",
 created_at: Tue, 28 Sep 2021 08:44:52.772891000 UTC +00:00,
 updated_at: Tue, 28 Sep 2021 08:44:52.772891000 UTC +00:00,
 created_by_id: 1,
 invite_email: nil,
 invite_token: nil,
 invite_accepted_at: nil,
 requested_at: nil,
 expires_at: nil,
 ldap: false,
 override: false,
 invite_email_success: true,
 state: 1>
[17] pry(main)> GroupMember.last.awaiting?
=> true

3. Creation of GroupMember with saas_user_caps FF turned off

In this last scenario, we create a group member while the saas_user_caps is turned off. The created group member is just created, which is the default state (0).

[18] pry(main)> GroupMember.last.destroy
=> #<GroupMember:0x00007fee97630de8
 id: 215,
 access_level: 30,
 source_id: 390,
 source_type: "Namespace",
 user_id: 5,
 notification_level: 3,
 type: "GroupMember",
 created_at: Tue, 28 Sep 2021 08:44:52.772891000 UTC +00:00,
 updated_at: Tue, 28 Sep 2021 08:44:52.772891000 UTC +00:00,
 created_by_id: 1,
 invite_email: nil,
 invite_token: nil,
 invite_accepted_at: nil,
 requested_at: nil,
 expires_at: nil,
 ldap: false,
 override: false,
 invite_email_success: true,
 state: 1>
[19] pry(main)> Feature.disable(:saas_user_caps, group)
=> true
[20] pry(main)> Feature.enabled?(:saas_user_caps, group, default_enabled: :yaml)
=> false
[21] pry(main)> result = Members::CreateService.new(admin, params).execute
[22] pry(main)> result
=> {:status=>:success}
[23] pry(main)> GroupMember.last
=> #<GroupMember:0x00007fee47efb180
 id: 216,
 access_level: 30,
 source_id: 390,
 source_type: "Namespace",
 user_id: 5,
 notification_level: 3,
 type: "GroupMember",
 created_at: Tue, 28 Sep 2021 08:52:51.477200000 UTC +00:00,
 updated_at: Tue, 28 Sep 2021 08:52:51.477200000 UTC +00:00,
 created_by_id: 1,
 invite_email: nil,
 invite_token: nil,
 invite_accepted_at: nil,
 requested_at: nil,
 expires_at: nil,
 ldap: false,
 override: false,
 invite_email_success: true,
 state: 0>

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Sheldon Led

Merge request reports