Commit 13727686 authored by Doug Stull's avatar Doug Stull Committed by Igor Drozdov

Add OnboardingProgress to Invite Service

- aligning with standard member creation process.
parent 990fd614
...@@ -7,7 +7,7 @@ class GroupMember < Member ...@@ -7,7 +7,7 @@ class GroupMember < Member
SOURCE_TYPE = 'Namespace' SOURCE_TYPE = 'Namespace'
belongs_to :group, foreign_key: 'source_id' belongs_to :group, foreign_key: 'source_id'
alias_attribute :namespace_id, :source_id
delegate :update_two_factor_requirement, to: :user delegate :update_two_factor_requirement, to: :user
# Make sure group member points only to group as it source # Make sure group member points only to group as it source
......
...@@ -5,6 +5,8 @@ class ProjectMember < Member ...@@ -5,6 +5,8 @@ class ProjectMember < Member
belongs_to :project, foreign_key: 'source_id' belongs_to :project, foreign_key: 'source_id'
delegate :namespace_id, to: :project
# Make sure project member points only to project as it source # Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE default_value_for :source_type, SOURCE_TYPE
validates :source_type, format: { with: /\AProject\z/ } validates :source_type, format: { with: /\AProject\z/ }
......
...@@ -10,13 +10,14 @@ module Members ...@@ -10,13 +10,14 @@ module Members
@errors = {} @errors = {}
@emails = params[:email]&.split(',')&.uniq&.flatten @emails = params[:email]&.split(',')&.uniq&.flatten
@source = params[:source]
end end
def execute(source) def execute
@source = source
validate_emails! validate_emails!
emails.each(&method(:process_email)) emails.each(&method(:process_email))
enqueue_onboarding_progress_action
result result
rescue BlankEmailsError, TooManyEmailsError => e rescue BlankEmailsError, TooManyEmailsError => e
error(e.message) error(e.message)
...@@ -24,7 +25,7 @@ module Members ...@@ -24,7 +25,7 @@ module Members
private private
attr_reader :source, :errors, :emails attr_reader :source, :errors, :emails, :member_created_namespace_id
def validate_emails! def validate_emails!
raise BlankEmailsError, s_('AddMember|Email cannot be blank') if emails.blank? raise BlankEmailsError, s_('AddMember|Email cannot be blank') if emails.blank?
...@@ -88,6 +89,7 @@ module Members ...@@ -88,6 +89,7 @@ module Members
errors[email] = new_member.errors.full_messages.to_sentence errors[email] = new_member.errors.full_messages.to_sentence
else else
after_execute(member: new_member) after_execute(member: new_member)
@member_created_namespace_id ||= new_member.namespace_id
end end
end end
...@@ -98,6 +100,12 @@ module Members ...@@ -98,6 +100,12 @@ module Members
success success
end end
end end
def enqueue_onboarding_progress_action
return unless member_created_namespace_id
Namespaces::OnboardingUserAddedWorker.perform_async(member_created_namespace_id)
end
end end
end end
......
---
title: Add enqueueing of Onboarding Progress to the Invite Service
merge_request: 57372
author:
type: other
...@@ -9,9 +9,10 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -9,9 +9,10 @@ RSpec.describe Members::InviteService, :aggregate_failures do
let_it_be(:subgroup) { create(:group, parent: root_ancestor) } let_it_be(:subgroup) { create(:group, parent: root_ancestor) }
let_it_be(:subgroup_project) { create(:project, group: subgroup) } let_it_be(:subgroup_project) { create(:project, group: subgroup) }
let(:params) { { email: %w[email@example.org email2@example.org], access_level: Gitlab::Access::GUEST } } let(:base_params) { { access_level: Gitlab::Access::GUEST, source: project } }
let(:params) { { email: %w[email@example.org email2@example.org] } }
subject(:result) { described_class.new(user, params).execute(project) } subject(:result) { described_class.new(user, base_params.merge(params)).execute }
before_all do before_all do
project.add_maintainer(user) project.add_maintainer(user)
...@@ -90,7 +91,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -90,7 +91,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
end end
context 'when there are some invalid members' do context 'when there are some invalid members' do
let(:params) { { email: %w[_bogus_ email2@example.org], access_level: Gitlab::Access::GUEST } } let(:params) { { email: %w[_bogus_ email2@example.org] } }
it 'only creates Audit Events for valid members' do it 'only creates Audit Events for valid members' do
expect { result }.to change { AuditEvent.count }.by(1) expect { result }.to change { AuditEvent.count }.by(1)
......
...@@ -25,11 +25,11 @@ module API ...@@ -25,11 +25,11 @@ module API
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
end end
post ":id/invitations" do post ":id/invitations" do
source = find_source(source_type, params[:id]) params[:source] = find_source(source_type, params[:id])
authorize_admin_source!(source_type, source) authorize_admin_source!(source_type, params[:source])
::Members::InviteService.new(current_user, params).execute(source) ::Members::InviteService.new(current_user, params).execute
end end
desc 'Get a list of group or project invitations viewable by the authenticated user' do desc 'Get a list of group or project invitations viewable by the authenticated user' do
......
...@@ -66,6 +66,12 @@ RSpec.describe GroupMember do ...@@ -66,6 +66,12 @@ RSpec.describe GroupMember do
it_behaves_like 'members notifications', :group it_behaves_like 'members notifications', :group
describe '#namespace_id' do
subject { build(:group_member, source_id: 1).namespace_id }
it { is_expected.to eq 1 }
end
describe '#real_source_type' do describe '#real_source_type' do
subject { create(:group_member).real_source_type } subject { create(:group_member).real_source_type }
......
...@@ -13,6 +13,10 @@ RSpec.describe ProjectMember do ...@@ -13,6 +13,10 @@ RSpec.describe ProjectMember do
it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) } it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
end end
describe 'delegations' do
it { is_expected.to delegate_method(:namespace_id).to(:project) }
end
describe '.access_level_roles' do describe '.access_level_roles' do
it 'returns Gitlab::Access.options' do it 'returns Gitlab::Access.options' do
expect(described_class.access_level_roles).to eq(Gitlab::Access.options) expect(described_class.access_level_roles).to eq(Gitlab::Access.options)
......
...@@ -2,29 +2,43 @@ ...@@ -2,29 +2,43 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Members::InviteService, :aggregate_failures do RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_shared_state, :sidekiq_inline do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:user) { project.owner } let_it_be(:user) { project.owner }
let_it_be(:project_user) { create(:user) } let_it_be(:project_user) { create(:user) }
let_it_be(:namespace) { project.namespace }
let(:params) { {} } let(:params) { {} }
let(:base_params) { { access_level: Gitlab::Access::GUEST } } let(:base_params) { { access_level: Gitlab::Access::GUEST, source: project } }
subject(:result) { described_class.new(user, base_params.merge(params)).execute(project) } subject(:result) { described_class.new(user, base_params.merge(params) ).execute }
context 'when email is previously unused by current members' do context 'when there is a valid member invited' do
let(:params) { { email: 'email@example.org' } } let(:params) { { email: 'email@example.org' } }
it 'successfully creates a member' do it 'successfully creates a member' do
expect { result }.to change(ProjectMember, :count).by(1) expect_to_create_members(count: 1)
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
end end
it_behaves_like 'records an onboarding progress action', :user_added
end
context 'when email is not a valid email' do
let(:params) { { email: '_bogus_' } }
it 'returns an error' do
expect_not_to_create_members
expect(result[:message]['_bogus_']).to eq("Invite email is invalid")
end
it_behaves_like 'does not record an onboarding progress action'
end end
context 'when emails are passed as an array' do context 'when emails are passed as an array' do
let(:params) { { email: %w[email@example.org email2@example.org] } } let(:params) { { email: %w[email@example.org email2@example.org] } }
it 'successfully creates members' do it 'successfully creates members' do
expect { result }.to change(ProjectMember, :count).by(2) expect_to_create_members(count: 2)
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
end end
end end
...@@ -33,33 +47,23 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -33,33 +47,23 @@ RSpec.describe Members::InviteService, :aggregate_failures do
let(:params) { { email: '' } } let(:params) { { email: '' } }
it 'returns an error' do it 'returns an error' do
expect(result[:status]).to eq(:error) expect_not_to_create_members
expect(result[:message]).to eq('Email cannot be blank') expect(result[:message]).to eq('Email cannot be blank')
end end
end end
context 'when email param is not included' do context 'when email param is not included' do
it 'returns an error' do it 'returns an error' do
expect(result[:status]).to eq(:error) expect_not_to_create_members
expect(result[:message]).to eq('Email cannot be blank') expect(result[:message]).to eq('Email cannot be blank')
end end
end end
context 'when email is not a valid email' do
let(:params) { { email: '_bogus_' } }
it 'returns an error' do
expect { result }.not_to change(ProjectMember, :count)
expect(result[:status]).to eq(:error)
expect(result[:message]['_bogus_']).to eq("Invite email is invalid")
end
end
context 'when duplicate email addresses are passed' do context 'when duplicate email addresses are passed' do
let(:params) { { email: 'email@example.org,email@example.org' } } let(:params) { { email: 'email@example.org,email@example.org' } }
it 'only creates one member per unique address' do it 'only creates one member per unique address' do
expect { result }.to change(ProjectMember, :count).by(1) expect_to_create_members(count: 1)
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
end end
end end
...@@ -71,8 +75,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -71,8 +75,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
let(:params) { { email: emails } } let(:params) { { email: emails } }
it 'limits the number of emails to 100' do it 'limits the number of emails to 100' do
expect { result }.not_to change(ProjectMember, :count) expect_not_to_create_members
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('Too many users specified (limit is 100)') expect(result[:message]).to eq('Too many users specified (limit is 100)')
end end
end end
...@@ -81,8 +84,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -81,8 +84,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
let(:params) { { email: 'email@example.org,email2@example.org', limit: 1 } } let(:params) { { email: 'email@example.org,email2@example.org', limit: 1 } }
it 'limits the number of emails to the limit supplied' do it 'limits the number of emails to the limit supplied' do
expect { result }.not_to change(ProjectMember, :count) expect_not_to_create_members
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('Too many users specified (limit is 1)') expect(result[:message]).to eq('Too many users specified (limit is 1)')
end end
end end
...@@ -91,7 +93,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -91,7 +93,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
let(:params) { { email: emails, limit: -1 } } let(:params) { { email: emails, limit: -1 } }
it 'does not limit number of emails' do it 'does not limit number of emails' do
expect { result }.to change(ProjectMember, :count).by(101) expect_to_create_members(count: 101)
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
end end
end end
...@@ -101,7 +103,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -101,7 +103,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
let(:params) { { email: project_user.email } } let(:params) { { email: project_user.email } }
it 'adds an existing user to members' do it 'adds an existing user to members' do
expect { result }.to change(ProjectMember, :count).by(1) expect_to_create_members(count: 1)
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
expect(project.users).to include project_user expect(project.users).to include project_user
end end
...@@ -111,8 +113,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -111,8 +113,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
let(:params) { { email: project_user.email, access_level: -1 } } let(:params) { { email: project_user.email, access_level: -1 } }
it 'returns an error' do it 'returns an error' do
expect { result }.not_to change(ProjectMember, :count) expect_not_to_create_members
expect(result[:status]).to eq(:error)
expect(result[:message][project_user.email]).to eq("Access level is not included in the list") expect(result[:message][project_user.email]).to eq("Access level is not included in the list")
end end
end end
...@@ -122,7 +123,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -122,7 +123,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
let(:params) { { email: "#{invited_member.invite_email},#{project_user.email}" } } let(:params) { { email: "#{invited_member.invite_email},#{project_user.email}" } }
it 'adds new email and returns an error for the already invited email' do it 'adds new email and returns an error for the already invited email' do
expect { result }.to change(ProjectMember, :count).by(1) expect_to_create_members(count: 1)
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
expect(result[:message][invited_member.invite_email]).to eq("Member already invited to #{project.name}") expect(result[:message][invited_member.invite_email]).to eq("Member already invited to #{project.name}")
expect(project.users).to include project_user expect(project.users).to include project_user
...@@ -134,7 +135,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -134,7 +135,7 @@ RSpec.describe Members::InviteService, :aggregate_failures do
let(:params) { { email: "#{requested_member.user.email},#{project_user.email}" } } let(:params) { { email: "#{requested_member.user.email},#{project_user.email}" } }
it 'adds new email and returns an error for the already invited email' do it 'adds new email and returns an error for the already invited email' do
expect { result }.to change(ProjectMember, :count).by(1) expect_to_create_members(count: 1)
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
expect(result[:message][requested_member.user.email]) expect(result[:message][requested_member.user.email])
.to eq("Member cannot be invited because they already requested to join #{project.name}") .to eq("Member cannot be invited because they already requested to join #{project.name}")
...@@ -147,10 +148,19 @@ RSpec.describe Members::InviteService, :aggregate_failures do ...@@ -147,10 +148,19 @@ RSpec.describe Members::InviteService, :aggregate_failures do
let(:params) { { email: "#{existing_member.user.email},#{project_user.email}" } } let(:params) { { email: "#{existing_member.user.email},#{project_user.email}" } }
it 'adds new email and returns an error for the already invited email' do it 'adds new email and returns an error for the already invited email' do
expect { result }.to change(ProjectMember, :count).by(1) expect_to_create_members(count: 1)
expect(result[:status]).to eq(:error) expect(result[:status]).to eq(:error)
expect(result[:message][existing_member.user.email]).to eq("Already a member of #{project.name}") expect(result[:message][existing_member.user.email]).to eq("Already a member of #{project.name}")
expect(project.users).to include project_user expect(project.users).to include project_user
end end
end end
def expect_to_create_members(count:)
expect { result }.to change(ProjectMember, :count).by(count)
end
def expect_not_to_create_members
expect { result }.not_to change(ProjectMember, :count)
expect(result[:status]).to eq(:error)
end
end end
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