Commit 838ca714 authored by David O'Regan's avatar David O'Regan

Merge branch '219241-move-teammates-code-to-shared-area' into 'master'

Move invite teammates code to shared area

See merge request gitlab-org/gitlab!48907
parents 9028bd17 4b4eef93
<script> <script>
import { GlFormGroup, GlFormInput, GlButton } from '@gitlab/ui'; import { GlFormGroup, GlFormInput, GlButton, GlSprintf, GlLink } from '@gitlab/ui';
import { __, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
export default { export default {
components: { components: {
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
GlButton, GlButton,
GlSprintf,
GlLink,
}, },
props: { props: {
docsPath: {
type: String,
required: true,
},
emails: { emails: {
type: Array, type: Array,
required: true, required: true,
...@@ -16,7 +22,7 @@ export default { ...@@ -16,7 +22,7 @@ export default {
}, },
data() { data() {
return { return {
numberOfInputs: Math.max(this.emails.length, 2), numberOfInputs: Math.max(this.emails.length, 1),
}; };
}, },
methods: { methods: {
...@@ -37,22 +43,29 @@ export default { ...@@ -37,22 +43,29 @@ export default {
}, },
}, },
i18n: { i18n: {
inviteTeammatesLabel: __('Invite teammates (optional)'), inviteMembersLabel: s__('InviteMember|Invite Members (optional)'),
inviteTeammatesDescription: __( inviteMembersDescription: s__(
'Invited users will be added with developer level permissions. You can always change this later.', 'InviteMember|Invited users will be added with developer level permissions. %{linkStart}View the documentation%{linkEnd} to see how to change this later.',
), ),
emailLabel: __('Email %{number}'), emailLabel: __('Email %{number}'),
emailPlaceholder: __('teammate%{number}@company.com'), emailPlaceholder: __('member%{number}@company.com'),
inviteAnother: __('Invite another teammate'), inviteAnother: s__('InviteMember|Invite another member'),
}, },
}; };
</script> </script>
<template> <template>
<div class="gl-mb-6"> <div class="gl-mb-6">
<gl-form-group <gl-form-group :label="$options.i18n.inviteMembersLabel">
:label="$options.i18n.inviteTeammatesLabel" <template #description>
:description="$options.i18n.inviteTeammatesDescription" <gl-sprintf :message="$options.i18n.inviteMembersDescription">
/> <template #link="{ content }">
<gl-link :href="docsPath" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</template>
</gl-form-group>
<gl-form-group <gl-form-group
v-for="(number, index) in numberOfInputs" v-for="(number, index) in numberOfInputs"
:key="number" :key="number"
......
import Vue from 'vue';
import InviteMembers from './components/invite_members.vue';
export default () => {
const el = document.querySelector('.js-invite-members');
if (!el) {
return null;
}
const { emails, docsPath } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(InviteMembers, {
props: {
emails: JSON.parse(emails),
docsPath,
},
});
},
});
};
import Vue from 'vue'; import Vue from 'vue';
import mountInviteMembers from 'ee/groups/invite';
import { STEPS, ONBOARDING_ISSUES_EXPERIMENT_FLOW_STEPS } from '../../constants'; import { STEPS, ONBOARDING_ISSUES_EXPERIMENT_FLOW_STEPS } from '../../constants';
import ProgressBar from '../../components/progress_bar.vue'; import ProgressBar from '../../components/progress_bar.vue';
import VisibilityLevelDropdown from '../../components/visibility_level_dropdown.vue'; import VisibilityLevelDropdown from '../../components/visibility_level_dropdown.vue';
import InviteTeammates from '../../components/invite_teammates.vue';
function mountProgressBar() { function mountProgressBar() {
const el = document.getElementById('progress-bar'); const el = document.getElementById('progress-bar');
...@@ -37,25 +37,8 @@ function mountVisibilityLevelDropdown() { ...@@ -37,25 +37,8 @@ function mountVisibilityLevelDropdown() {
}); });
} }
function mountInviteTeammates() {
const el = document.querySelector('.js-invite-teammates');
if (!el) return null;
return new Vue({
el,
render(createElement) {
return createElement(InviteTeammates, {
props: {
emails: JSON.parse(el.dataset.emails),
},
});
},
});
}
export default () => { export default () => {
mountProgressBar(); mountProgressBar();
mountVisibilityLevelDropdown(); mountVisibilityLevelDropdown();
mountInviteTeammates(); mountInviteMembers();
}; };
# frozen_string_literal: true
module GroupInviteMembers
private
def invite_members(group)
invite_params = {
user_ids: emails_param[:emails].reject(&:blank?).join(','),
access_level: Gitlab::Access::DEVELOPER
}
Members::CreateService.new(current_user, invite_params).execute(group)
end
def emails_param
params.require(:group).permit(emails: [])
end
end
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module Registrations module Registrations
class GroupsController < ApplicationController class GroupsController < ApplicationController
include GroupInviteMembers
layout 'checkout' layout 'checkout'
before_action :authorize_create_group!, only: :new before_action :authorize_create_group!, only: :new
...@@ -17,7 +19,7 @@ module Registrations ...@@ -17,7 +19,7 @@ module Registrations
@group = Groups::CreateService.new(current_user, group_params).execute @group = Groups::CreateService.new(current_user, group_params).execute
if @group.persisted? if @group.persisted?
invite_teammates invite_members(@group)
redirect_to new_users_sign_up_project_path(namespace_id: @group.id) redirect_to new_users_sign_up_project_path(namespace_id: @group.id)
else else
...@@ -35,21 +37,8 @@ module Registrations ...@@ -35,21 +37,8 @@ module Registrations
access_denied! unless experiment_enabled?(:onboarding_issues) access_denied! unless experiment_enabled?(:onboarding_issues)
end end
def invite_teammates
invite_params = {
user_ids: emails_param[:emails].reject(&:blank?).join(','),
access_level: Gitlab::Access::DEVELOPER
}
Members::CreateService.new(current_user, invite_params).execute(@group)
end
def group_params def group_params
params.require(:group).permit(:name, :path, :visibility_level) params.require(:group).permit(:name, :path, :visibility_level)
end end
def emails_param
params.require(:group).permit(emails: [])
end
end end
end end
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
= f.label :visibility_level, class: 'label-bold' do = f.label :visibility_level, class: 'label-bold' do
= _('Visibility level') = _('Visibility level')
.js-visibility-level-dropdown{ data: { visibility_level_options: visibility_level_options.to_json, default_level: f.object.visibility_level } } .js-visibility-level-dropdown{ data: { visibility_level_options: visibility_level_options.to_json, default_level: f.object.visibility_level } }
.js-invite-teammates{ data: { emails: params.dig(:group, :emails) || [] } } = render partial: 'shared/groups/invite_members'
.row .row
.form-group.col-sm-12.mb-0 .form-group.col-sm-12.mb-0
= button_tag class: %w[btn btn-success w-100] do = button_tag class: %w[btn btn-success w-100] do
......
.js-invite-members{ data: { emails: params.dig(:group, :emails) || [], docs_path: help_page_path('user/permissions') } }
...@@ -68,45 +68,7 @@ RSpec.describe Registrations::GroupsController do ...@@ -68,45 +68,7 @@ RSpec.describe Registrations::GroupsController do
it { is_expected.to have_gitlab_http_status(:redirect) } it { is_expected.to have_gitlab_http_status(:redirect) }
it { is_expected.to redirect_to(new_users_sign_up_project_path(namespace_id: user.groups.last.id)) } it { is_expected.to redirect_to(new_users_sign_up_project_path(namespace_id: user.groups.last.id)) }
context 'inviting teammates' do it_behaves_like GroupInviteMembers
context 'with no valid emails in the params' do
it 'does not add teammates' do
expect { subject }.to change(Member, :count).by(1)
end
it 'does not call the Members::CreateService' do
expect(Members::CreateService).not_to receive(:new)
end
end
context 'with valid emails in the params' do
before do
params[:emails] = ['a@a.a', 'b@b.b', '', '', 'x', 'y']
end
it 'adds users with developer access and ignores blank emails' do
expect_next_instance_of(Group) do |group|
expect(group).to receive(:add_users).with(
['a@a.a', 'b@b.b', 'x', 'y'],
Gitlab::Access::DEVELOPER,
expires_at: nil,
current_user: user
).and_call_original
end
subject
end
it 'sends invitations to valid emails only' do
subject
emails = assigns(:group).members.pluck(:invite_email)
expect(emails).to include('a@a.a', 'b@b.b')
expect(emails).not_to include('x', 'y')
end
end
end
context 'when the group cannot be saved' do context 'when the group cannot be saved' do
let(:params) { { name: '', path: '' } } let(:params) { { name: '', path: '' } }
......
import { GlFormInput, GlButton } from '@gitlab/ui'; import { GlFormInput, GlButton } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils'; import { shallowMount, mount } from '@vue/test-utils';
import Component from 'ee/registrations/components/invite_teammates.vue'; import Component from 'ee/groups/components/invite_members.vue';
describe('User invites', () => { describe('User invites', () => {
let wrapper; let wrapper;
...@@ -12,7 +12,7 @@ describe('User invites', () => { ...@@ -12,7 +12,7 @@ describe('User invites', () => {
}; };
beforeEach(() => { beforeEach(() => {
createComponent({ emails: [] }); createComponent({ emails: [], docsPath: 'https://some.doc.path' });
}); });
afterEach(() => { afterEach(() => {
...@@ -24,14 +24,14 @@ describe('User invites', () => { ...@@ -24,14 +24,14 @@ describe('User invites', () => {
const clickButton = () => wrapper.find(GlButton).vm.$emit('click'); const clickButton = () => wrapper.find(GlButton).vm.$emit('click');
describe('Default state', () => { describe('Default state', () => {
it('creates 2 input fields', () => { it('creates input field', () => {
expect(inputs().length).toBe(2); expect(inputs().length).toBe(1);
}); });
it.each([0, 1])('does not set a value', index => { it('does not set a value', () => {
expect( expect(
inputs() inputs()
.at(index) .at(0)
.attributes('value'), .attributes('value'),
).toBe(undefined); ).toBe(undefined);
}); });
...@@ -41,7 +41,7 @@ describe('User invites', () => { ...@@ -41,7 +41,7 @@ describe('User invites', () => {
const emails = ['a@a', 'b@b', 'c@c']; const emails = ['a@a', 'b@b', 'c@c'];
beforeEach(() => { beforeEach(() => {
createComponent({ emails }); createComponent({ emails, docsPath: 'https://some.doc.path' });
}); });
it('creates 3 input fields', () => { it('creates 3 input fields', () => {
...@@ -60,7 +60,7 @@ describe('User invites', () => { ...@@ -60,7 +60,7 @@ describe('User invites', () => {
describe('Adding an input', () => { describe('Adding an input', () => {
beforeEach(() => { beforeEach(() => {
wrapper = mount(Component, { wrapper = mount(Component, {
propsData: { emails: [] }, propsData: { emails: [], docsPath: 'https://some.doc.path' },
attachToDocument: true, attachToDocument: true,
}); });
...@@ -68,10 +68,10 @@ describe('User invites', () => { ...@@ -68,10 +68,10 @@ describe('User invites', () => {
}); });
it('adds an input field', () => { it('adds an input field', () => {
expect(inputs().length).toBe(3); expect(inputs().length).toBe(2);
}); });
it.each([0, 1, 2])('does not set a value', index => { it.each([0, 1])('does not set a value', index => {
expect( expect(
inputs() inputs()
.at(index) .at(index)
...@@ -80,7 +80,7 @@ describe('User invites', () => { ...@@ -80,7 +80,7 @@ describe('User invites', () => {
}); });
it('sets the focus to the new input field', () => { it('sets the focus to the new input field', () => {
expect(inputs().at(2).element).toBe(document.activeElement); expect(inputs().at(1).element).toBe(document.activeElement);
}); });
}); });
}); });
# frozen_string_literal: true
RSpec.shared_examples GroupInviteMembers do
context 'inviting members' do
context 'with no valid emails in the params' do
it 'does not add members' do
expect { subject }.to change(Member, :count).by(1)
end
it 'does not call the Members::CreateService' do
expect(Members::CreateService).not_to receive(:new)
end
end
context 'with valid emails in the params' do
before do
params[:emails] = ['a@a.a', 'b@b.b', '', '', 'x', 'y']
end
it 'adds users with developer access and ignores blank emails' do
expect_next_instance_of(Group) do |group|
expect(group).to receive(:add_users).with(
%w[a@a.a b@b.b x y],
Gitlab::Access::DEVELOPER,
expires_at: nil,
current_user: user
).and_call_original
end
subject
end
it 'sends invitations to valid emails only' do
subject
emails = assigns(:group).members.pluck(:invite_email)
expect(emails).to include('a@a.a', 'b@b.b')
expect(emails).not_to include('x', 'y')
end
end
end
end
...@@ -15012,9 +15012,6 @@ msgstr "" ...@@ -15012,9 +15012,6 @@ msgstr ""
msgid "Invite Members" msgid "Invite Members"
msgstr "" msgstr ""
msgid "Invite another teammate"
msgstr ""
msgid "Invite group" msgid "Invite group"
msgstr "" msgstr ""
...@@ -15024,9 +15021,6 @@ msgstr "" ...@@ -15024,9 +15021,6 @@ msgstr ""
msgid "Invite team members" msgid "Invite team members"
msgstr "" msgstr ""
msgid "Invite teammates (optional)"
msgstr ""
msgid "Invite your team" msgid "Invite your team"
msgstr "" msgstr ""
...@@ -15096,6 +15090,15 @@ msgstr "" ...@@ -15096,6 +15090,15 @@ msgstr ""
msgid "InviteMembers|Invite team members" msgid "InviteMembers|Invite team members"
msgstr "" msgstr ""
msgid "InviteMember|Invite Members (optional)"
msgstr ""
msgid "InviteMember|Invite another member"
msgstr ""
msgid "InviteMember|Invited users will be added with developer level permissions. %{linkStart}View the documentation%{linkEnd} to see how to change this later."
msgstr ""
msgid "InviteMember|Oops, this feature isn't ready yet" msgid "InviteMember|Oops, this feature isn't ready yet"
msgstr "" msgstr ""
...@@ -15153,9 +15156,6 @@ msgstr "" ...@@ -15153,9 +15156,6 @@ msgstr ""
msgid "Invited" msgid "Invited"
msgstr "" msgstr ""
msgid "Invited users will be added with developer level permissions. You can always change this later."
msgstr ""
msgid "Invocations" msgid "Invocations"
msgstr "" msgstr ""
...@@ -32646,6 +32646,9 @@ msgstr "" ...@@ -32646,6 +32646,9 @@ msgstr ""
msgid "math|There was an error rendering this math block" msgid "math|There was an error rendering this math block"
msgstr "" msgstr ""
msgid "member%{number}@company.com"
msgstr ""
msgid "merge request" msgid "merge request"
msgid_plural "merge requests" msgid_plural "merge requests"
msgstr[0] "" msgstr[0] ""
...@@ -33309,9 +33312,6 @@ msgstr "" ...@@ -33309,9 +33312,6 @@ msgstr ""
msgid "tag name" msgid "tag name"
msgstr "" msgstr ""
msgid "teammate%{number}@company.com"
msgstr ""
msgid "the correct format." msgid "the correct format."
msgstr "" msgstr ""
......
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