Commit e6a0f5b9 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'dz/348486-add-users-overage-check-to-groups' into 'master'

Add overage check on groups invite

See merge request gitlab-org/gitlab!81039
parents 66f9d9b7 d596fe6e
......@@ -2,11 +2,11 @@
import { uniqueId } from 'lodash';
import Api from '~/api';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import InviteModalBase from 'ee_else_ce/invite_members/components/invite_modal_base.vue';
import { GROUP_FILTERS, GROUP_MODAL_LABELS } from '../constants';
import eventHub from '../event_hub';
import { getInvalidFeedbackMessage } from '../utils/get_invalid_feedback_message';
import GroupSelect from './group_select.vue';
import InviteModalBase from './invite_modal_base.vue';
export default {
name: 'InviteMembersModal',
......@@ -19,6 +19,10 @@ export default {
type: String,
required: true,
},
rootId: {
type: String,
required: true,
},
isProject: {
type: Boolean,
required: true,
......@@ -147,6 +151,8 @@ export default {
:label-intro-text="labelIntroText"
:label-search-field="$options.labels.searchField"
:submit-disabled="inviteDisabled"
:new-group-to-invite="groupToBeSharedWith.id"
:root-group-id="rootId"
:invalid-feedback-message="invalidFeedbackMessage"
:is-loading="isLoading"
@reset="resetFields"
......
......@@ -36,6 +36,7 @@ module InviteMembersHelper
def common_invite_group_modal_data(source, member_class, is_project)
{
id: source.id,
root_id: source.root_ancestor&.id,
name: source.name,
default_access_level: Gitlab::Access::GUEST,
invalid_groups: source.related_group_ids,
......@@ -48,7 +49,7 @@ module InviteMembersHelper
def common_invite_modal_dataset(source)
dataset = {
id: source.id,
root_id: source&.root_ancestor&.id,
root_id: source.root_ancestor&.id,
name: source.name,
default_access_level: Gitlab::Access::GUEST
}
......
......@@ -14,6 +14,7 @@ import {
} from '../constants';
import { checkOverage } from '../check_overage';
import { fetchSubscription } from '../get_subscription_data';
import { fetchUserIdsFromGroup } from '../utils';
const OVERAGE_CONTENT_SLOT = 'overage-content';
const EXTRA_SLOTS = [
......@@ -53,6 +54,11 @@ export default {
required: false,
default: () => [],
},
newGroupToInvite: {
type: Number,
required: false,
default: null,
},
},
data() {
return {
......@@ -120,11 +126,19 @@ export default {
}
},
async checkAndSubmit(args) {
let usersToAddById = [];
let usersToInviteByEmail = [];
this.isLoading = true;
const [usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite();
const subscriptionData = await fetchSubscription(this.namespaceId);
this.subscriptionSeats = subscriptionData.subscriptionSeats;
if (this.newGroupToInvite) {
usersToAddById = await fetchUserIdsFromGroup(this.newGroupToInvite);
} else {
[usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite();
}
const { hasOverage, usersOverage } = checkOverage(
subscriptionData,
usersToAddById,
......
import { memoize } from 'lodash';
import Api from '~/api';
const fetchGroupMembers = memoize((id) => Api.groupMembers(id).then((response) => response.data));
export const fetchUserIdsFromGroup = async (groupIdToInvite) => {
const groupMembers = await fetchGroupMembers(groupIdToInvite);
return groupMembers.map((user) => user.id);
};
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Groups > Members > Manage groups', :js, :saas do
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
include Spec::Support::Helpers::ModalHelpers
let_it_be(:user) { create(:user) }
let_it_be(:user2) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:group_to_add) { create(:group) }
let(:premium_plan) { create(:premium_plan) }
shared_examples "adding a group doesn't trigger an overage modal" do
it do
group.add_owner(user)
group_to_add.add_owner(user)
visit group_group_members_path(group)
add_group(group_to_add.name, role: 'Reporter')
wait_for_requests
expect(page).not_to have_button 'Continue'
page.refresh
click_groups_tab
page.within(first_row) do
expect(page).to have_content(group_to_add.name)
expect(page).to have_content('Reporter')
end
end
end
before do
sign_in(user)
stub_application_setting(check_namespace_plan: true)
end
context 'for a free group' do
before do
allow(group).to receive(:paid?).and_return(false)
end
it_behaves_like "adding a group doesn't trigger an overage modal"
end
context 'for a premium group', :aggregate_failures do
before do
create(:gitlab_subscription, namespace: group, hosted_plan: premium_plan, seats: 1, seats_in_use: 0)
end
context 'when there is an not yet billed user in the additional group' do
it 'triggers overage modal' do
add_group_with_one_extra_user
click_button 'Continue'
wait_for_requests
page.refresh
click_groups_tab
page.within(first_row) do
expect(page).to have_content(group_to_add.name)
expect(page).to have_content('Reporter')
end
end
end
context 'when overage modal is shown' do
it 'goes back to the initial modal if not confirmed' do
add_group_with_one_extra_user
click_button 'Back'
expect(page).to have_content("You're inviting a group to the #{group.name} group.")
click_button 'Cancel'
expect(page).not_to have_link 'Groups'
end
end
end
def add_group(name, role: 'Guest', expires_at: nil)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
click_button name
choose_options(role, expires_at)
click_button 'Invite'
end
def add_group_with_one_extra_user
group.add_owner(user)
group_to_add.add_owner(user)
group_to_add.add_developer(user2)
visit group_group_members_path(group)
add_group(group_to_add.name, role: 'Reporter')
wait_for_requests
expect(page).to have_content("Your subscription includes 1 seat. If you continue, the #{group.name} group will have 2 seats in use and will be billed for the overage. Learn more.")
end
end
......@@ -11,6 +11,7 @@ import {
OVERAGE_MODAL_BACK_BUTTON,
} from 'ee/invite_members/constants';
import { propsData } from 'jest/invite_members/mock_data/modal_base';
import { fetchUserIdsFromGroup } from 'ee/invite_members/utils';
import { noFreePlacesSubscription as mockSubscription } from '../mock_data';
jest.mock('ee/invite_members/check_overage', () => ({
......@@ -21,6 +22,10 @@ jest.mock('ee/invite_members/get_subscription_data', () => ({
fetchSubscription: jest.fn().mockImplementation(() => mockSubscription),
}));
jest.mock('ee/invite_members/utils', () => ({
fetchUserIdsFromGroup: jest.fn().mockImplementation(() => [123, 256]),
}));
describe('EEInviteModalBase', () => {
let wrapper;
let listenerSpy;
......@@ -119,6 +124,19 @@ describe('EEInviteModalBase', () => {
});
});
describe('with overageMembersModal feature flag and a group to invite, and invite is clicked', () => {
beforeEach(async () => {
createComponent({ newGroupToInvite: 123 }, { glFeatures: { overageMembersModal: true } });
clickInviteButton();
await nextTick();
});
it('calls fetchUserIdsFromGroup and passes correct parameter', () => {
expect(fetchUserIdsFromGroup).toHaveBeenCalledTimes(1);
expect(fetchUserIdsFromGroup).toHaveBeenCalledWith(123);
});
});
describe('with overageMembersModal feature flag, and invite is clicked ', () => {
beforeEach(async () => {
createComponent({}, { glFeatures: { overageMembersModal: true } });
......
import Api from '~/api';
import { fetchUserIdsFromGroup } from 'ee/invite_members/utils';
jest.mock('~/api.js', () => ({
groupMembers: jest.fn().mockResolvedValue({ data: [{ id: 123 }, { id: 256 }] }),
}));
describe('fetchUserIdsFromGroup', () => {
it('caches the response for the same input', async () => {
await fetchUserIdsFromGroup(1);
await fetchUserIdsFromGroup(1);
expect(Api.groupMembers).toHaveBeenCalledTimes(1);
});
it('returns ids of the users in the group', async () => {
const result = await fetchUserIdsFromGroup(1);
expect(result).toEqual([123, 256]);
});
});
......@@ -256,9 +256,4 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
end
end
end
def click_groups_tab
expect(page).to have_link 'Groups'
click_link "Groups"
end
end
......@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe 'Projects > Members > Groups with access list', :js do
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::ModalHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
......@@ -95,8 +96,4 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
expect(members_table).to have_content(group.full_name)
end
end
def click_groups_tab
click_link 'Groups'
end
end
export const propsData = {
id: '1',
rootId: '1',
name: 'test name',
isProject: false,
invalidGroups: [],
......
......@@ -19,6 +19,7 @@ RSpec.describe InviteMembersHelper do
it 'has expected common attributes' do
attributes = {
id: project.id,
root_id: project.root_ancestor.id,
name: project.name,
default_access_level: Gitlab::Access::GUEST,
invalid_groups: project.related_group_ids,
......
......@@ -44,6 +44,11 @@ module Spec
fill_in 'YYYY-MM-DD', with: expires_at.strftime('%Y-%m-%d') if expires_at
end
def click_groups_tab
expect(page).to have_link 'Groups'
click_link "Groups"
end
def group_dropdown_selector
'[data-testid="group-select-dropdown"]'
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