Commit e9e9967e authored by Diana Zubova's avatar Diana Zubova Committed by Miguel Rincon

Add guests check for overage

Adding guests doesn't trigger overage
for ultimate-like plans
parent 06b9dcb8
...@@ -14,20 +14,32 @@ import { difference } from 'lodash'; ...@@ -14,20 +14,32 @@ import { difference } from 'lodash';
* @param {Array} billedUserIds Array of ids of already billed users * @param {Array} billedUserIds Array of ids of already billed users
* @param {Array} billedUserEmails Array of emails of already billed users * @param {Array} billedUserEmails Array of emails of already billed users
* @param {Boolean} isFreePlan * @param {Boolean} isFreePlan
* @param {Array} usersToInviteByEmail Array emails of users to be invited by email * @param {Boolean} excludeGuests Doesn't calculate guests as part of billed users
* @param {Array} usersToAddById Array ids of users to be invited by id * @param {Object} invitedMembersData Data of the invited members
* @param {Number} groupIdToInvite namespaceId of the added group * @param {Boolean} isGuestRole Is true if the chosen role is Guest
* @param {Array} usersToInviteByEmail Array emails of users to be invited by email
* @param {Array} usersToAddById Array ids of users to be invited by id
* *
* @returns {Object} * @returns {Object}
*/ */
export const checkOverage = ( export const checkOverage = (
{ subscriptionSeats, maxSeatsUsed, seatsInUse, billedUserIds, billedUserEmails, isFreeGroup }, {
usersToAddById, subscriptionSeats,
usersToInviteByEmail, maxSeatsUsed,
seatsInUse,
billedUserIds,
billedUserEmails,
isFreeGroup,
excludeGuests,
},
{ isGuestRole, usersToAddById, usersToInviteByEmail },
) => { ) => {
// overage is not calculate when adding guests to ultimate-like groups
const isExcludingGuests = isGuestRole && excludeGuests;
// overage only possible on paid plans // overage only possible on paid plans
if (isFreeGroup) { if (isFreeGroup || isExcludingGuests) {
return { usersOverage: null, hasOverage: false }; return { usersOverage: null, hasOverage: false };
} }
......
...@@ -154,11 +154,13 @@ export default { ...@@ -154,11 +154,13 @@ export default {
[usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite(); [usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite();
} }
const { hasOverage, usersOverage } = checkOverage( const isGuestRole = args.accessLevel === this.$attrs['access-levels'].Guest;
subscriptionData,
const { hasOverage, usersOverage } = checkOverage(subscriptionData, {
isGuestRole,
usersToAddById, usersToAddById,
usersToInviteByEmail, usersToInviteByEmail,
); });
this.isLoading = false; this.isLoading = false;
this.hasOverage = hasOverage; this.hasOverage = hasOverage;
......
...@@ -18,5 +18,6 @@ export const fetchSubscription = async (namespaceId) => { ...@@ -18,5 +18,6 @@ export const fetchSubscription = async (namespaceId) => {
maxSeatsUsed: data.usage.max_seats_used, maxSeatsUsed: data.usage.max_seats_used,
seatsInUse: data.usage.seats_in_use, seatsInUse: data.usage.seats_in_use,
isFreeGroup: isFreeGroup(data.plan), isFreeGroup: isFreeGroup(data.plan),
excludeGuests: data.plan.exclude_guests || false,
}; };
}; };
...@@ -13,14 +13,15 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do ...@@ -13,14 +13,15 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do
let_it_be(:group_to_add) { create(:group) } let_it_be(:group_to_add) { create(:group) }
let(:premium_plan) { create(:premium_plan) } let(:premium_plan) { create(:premium_plan) }
let(:ultimate_plan) { create(:premium_plan) }
shared_examples "adding a group doesn't trigger an overage modal" do shared_examples "doesn't trigger an overage modal when adding a group with a given role" do |role|
it do it do
group.add_owner(user) group.add_owner(user)
group_to_add.add_owner(user) group_to_add.add_owner(user)
visit group_group_members_path(group) visit group_group_members_path(group)
add_group(group_to_add.name, role: 'Reporter') add_group(group_to_add.name, role)
wait_for_requests wait_for_requests
...@@ -30,6 +31,23 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do ...@@ -30,6 +31,23 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do
click_groups_tab click_groups_tab
page.within(first_row) do
expect(page).to have_content(group_to_add.name)
expect(page).to have_content(role)
end
end
end
shared_examples "triggers an overage modal when adding a group as Reporter" do
it do
add_group_with_one_extra_user
click_button 'Continue'
wait_for_requests
page.refresh
click_groups_tab
page.within(first_row) do page.within(first_row) do
expect(page).to have_content(group_to_add.name) expect(page).to have_content(group_to_add.name)
expect(page).to have_content('Reporter') expect(page).to have_content('Reporter')
...@@ -47,7 +65,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do ...@@ -47,7 +65,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do
allow(group).to receive(:paid?).and_return(false) allow(group).to receive(:paid?).and_return(false)
end end
it_behaves_like "adding a group doesn't trigger an overage modal" it_behaves_like "doesn't trigger an overage modal when adding a group with a given role", 'Reporter'
end end
context 'for a premium group', :aggregate_failures do context 'for a premium group', :aggregate_failures do
...@@ -55,22 +73,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do ...@@ -55,22 +73,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do
create(:gitlab_subscription, namespace: group, hosted_plan: premium_plan, seats: 1, seats_in_use: 0) create(:gitlab_subscription, namespace: group, hosted_plan: premium_plan, seats: 1, seats_in_use: 0)
end end
context 'when there is an not yet billed user in the additional group' do it_behaves_like "triggers an overage modal when adding a group as Reporter"
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 context 'when overage modal is shown' do
it 'goes back to the initial modal if not confirmed' do it 'goes back to the initial modal if not confirmed' do
...@@ -86,7 +89,16 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do ...@@ -86,7 +89,16 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do
end end
end end
def add_group(name, role: 'Guest', expires_at: nil) context 'for an ultimate group', :aggregate_failures do
before do
create(:gitlab_subscription, namespace: group, hosted_plan: ultimate_plan, seats: 1, seats_in_use: 0)
end
it_behaves_like "triggers an overage modal when adding a group as Reporter"
it_behaves_like "doesn't trigger an overage modal when adding a group with a given role", 'Guest'
end
def add_group(name, role, expires_at: nil)
click_on 'Invite a group' click_on 'Invite a group'
click_on 'Select a group' click_on 'Select a group'
...@@ -103,7 +115,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do ...@@ -103,7 +115,7 @@ RSpec.describe 'Groups > Members > Manage groups', :js, :saas do
group_to_add.add_developer(user2) group_to_add.add_developer(user2)
visit group_group_members_path(group) visit group_group_members_path(group)
add_group(group_to_add.name, role: 'Reporter') add_group(group_to_add.name, 'Reporter')
wait_for_requests wait_for_requests
......
...@@ -13,17 +13,37 @@ RSpec.describe 'Groups > Members > Manage members', :saas, :js do ...@@ -13,17 +13,37 @@ RSpec.describe 'Groups > Members > Manage members', :saas, :js do
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:premium_plan) { create(:premium_plan) } let(:premium_plan) { create(:premium_plan) }
let(:ultimate_plan) { create(:ultimate_plan) }
shared_examples "adding one user doesn't trigger an overage modal" do shared_examples "adding one user with a given role doesn't trigger an overage modal" do |role|
it do it do
group.add_owner(user1) group.add_owner(user1)
add_user_by_name(user2.name, 'Developer') add_user_by_name(user2.name, role)
expect(page).not_to have_content("You are about to incur additional charges") expect(page).not_to have_content("You are about to incur additional charges")
wait_for_requests wait_for_requests
page.refresh page.refresh
page.within(second_row) do
expect(page).to have_content(user2.name)
expect(page).to have_button(role)
end
end
end
shared_examples "shows an overage for one Developer added and invites them to a group if confirmed" do
it do
group.add_owner(user1)
add_user_by_name(user2.name, 'Developer')
expect(page).to have_content("You are about to incur additional charges")
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.")
click_button 'Continue'
page.refresh
page.within(second_row) do page.within(second_row) do
expect(page).to have_content(user2.name) expect(page).to have_content(user2.name)
expect(page).to have_button('Developer') expect(page).to have_button('Developer')
...@@ -41,7 +61,7 @@ RSpec.describe 'Groups > Members > Manage members', :saas, :js do ...@@ -41,7 +61,7 @@ RSpec.describe 'Groups > Members > Manage members', :saas, :js do
create(:gitlab_subscription, namespace: group, hosted_plan: nil) create(:gitlab_subscription, namespace: group, hosted_plan: nil)
end end
it_behaves_like "adding one user doesn't trigger an overage modal" it_behaves_like "adding one user with a given role doesn't trigger an overage modal", 'Developer'
end end
context 'when adding a member to a premium group' do context 'when adding a member to a premium group' do
...@@ -50,7 +70,7 @@ RSpec.describe 'Groups > Members > Manage members', :saas, :js do ...@@ -50,7 +70,7 @@ RSpec.describe 'Groups > Members > Manage members', :saas, :js do
create(:gitlab_subscription, namespace: group, hosted_plan: premium_plan, seats: 2, seats_in_use: 1) create(:gitlab_subscription, namespace: group, hosted_plan: premium_plan, seats: 2, seats_in_use: 1)
end end
it_behaves_like "adding one user doesn't trigger an overage modal" it_behaves_like "adding one user with a given role doesn't trigger an overage modal", 'Developer'
it 'adding two users triggers overage modal', :aggregate_failures do it 'adding two users triggers overage modal', :aggregate_failures do
group.add_owner(user1) group.add_owner(user1)
...@@ -77,22 +97,7 @@ RSpec.describe 'Groups > Members > Manage members', :saas, :js do ...@@ -77,22 +97,7 @@ RSpec.describe 'Groups > Members > Manage members', :saas, :js do
create(:gitlab_subscription, namespace: group, hosted_plan: premium_plan, seats: 1, seats_in_use: 1) create(:gitlab_subscription, namespace: group, hosted_plan: premium_plan, seats: 1, seats_in_use: 1)
end end
it 'invites a member to a group if confirmed', :aggregate_failures do it_behaves_like "shows an overage for one Developer added and invites them to a group if confirmed"
group.add_owner(user1)
add_user_by_name(user2.name, 'Developer')
expect(page).to have_content("You are about to incur additional charges")
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.")
click_button 'Continue'
page.refresh
page.within(second_row) do
expect(page).to have_content(user2.name)
expect(page).to have_button('Developer')
end
end
it 'get back to initial modal if not confirmed', :aggregate_failures do it 'get back to initial modal if not confirmed', :aggregate_failures do
group.add_owner(user1) group.add_owner(user1)
...@@ -113,6 +118,15 @@ RSpec.describe 'Groups > Members > Manage members', :saas, :js do ...@@ -113,6 +118,15 @@ RSpec.describe 'Groups > Members > Manage members', :saas, :js do
end end
end end
context 'when adding a member to a ultimate group with no places left' do
before do
create(:gitlab_subscription, namespace: group, hosted_plan: ultimate_plan, seats: 1, seats_in_use: 1)
end
it_behaves_like "shows an overage for one Developer added and invites them to a group if confirmed"
it_behaves_like "adding one user with a given role doesn't trigger an overage modal", 'Guest'
end
def add_user_by_name(name, role) def add_user_by_name(name, role)
visit group_group_members_path(group) visit group_group_members_path(group)
......
...@@ -5,6 +5,7 @@ export const mockDataSubscription = { ...@@ -5,6 +5,7 @@ export const mockDataSubscription = {
code: 'gold', code: 'gold',
trial: false, trial: false,
upgradable: false, upgradable: false,
exclude_guests: true,
}, },
usage: { usage: {
seats_in_subscription: 100, seats_in_subscription: 100,
...@@ -25,6 +26,7 @@ export const mockDataSubscription = { ...@@ -25,6 +26,7 @@ export const mockDataSubscription = {
code: null, code: null,
trial: null, trial: null,
upgradable: null, upgradable: null,
exclude_guests: null,
}, },
usage: { usage: {
seats_in_subscription: 0, seats_in_subscription: 0,
...@@ -44,12 +46,14 @@ export const mockDataSubscription = { ...@@ -44,12 +46,14 @@ export const mockDataSubscription = {
code: 'gold', code: 'gold',
trial: true, trial: true,
upgradable: false, upgradable: false,
exclude_guests: false,
}, },
usage: { usage: {
seats_in_subscription: 100, seats_in_subscription: 100,
seats_in_use: 1, seats_in_use: 1,
max_seats_used: 0, max_seats_used: 0,
seats_owed: 0, seats_owed: 0,
exclude_guests: false,
}, },
billing: { billing: {
subscription_start_date: '2018-12-13', subscription_start_date: '2018-12-13',
......
...@@ -4,37 +4,52 @@ import { ...@@ -4,37 +4,52 @@ import {
oneFreeSeatSubscription, oneFreeSeatSubscription,
noFreePlacesSubscription, noFreePlacesSubscription,
subscriptionWithOverage, subscriptionWithOverage,
allowGuestsSubscription,
generateInvitedUsersData,
} from './mock_data'; } from './mock_data';
const secondUserAddedById = generateInvitedUsersData({
usersToAddById: [2],
});
describe('overage check', () => { describe('overage check', () => {
it('returns no overage on free plans', () => { it('returns no overage on free plans', () => {
const result = checkOverage(freePlanSubsciption, [], []); const result = checkOverage(freePlanSubsciption, secondUserAddedById);
expect(result.hasOverage).toBe(false); expect(result.hasOverage).toBe(false);
}); });
it('returns no overage when there is one free seat', () => { it('returns no overage when there is one free seat', () => {
const result = checkOverage(oneFreeSeatSubscription, [], ['new_user@email.com']); const result = checkOverage(
oneFreeSeatSubscription,
generateInvitedUsersData({ usersToInviteByEmail: ['new_user@email.com'] }),
);
expect(result.hasOverage).toBe(false); expect(result.hasOverage).toBe(false);
}); });
it('returns overage when new user added by id', () => { it('returns overage when new user added by id', () => {
const result = checkOverage(noFreePlacesSubscription, [2], []); const result = checkOverage(noFreePlacesSubscription, secondUserAddedById);
expect(result.hasOverage).toBe(true); expect(result.hasOverage).toBe(true);
expect(result.usersOverage).toBe(2); expect(result.usersOverage).toBe(2);
}); });
it('returns overage when new user added by email', () => { it('returns overage when new user added by email', () => {
const result = checkOverage(noFreePlacesSubscription, [], ['test2@example']); const result = checkOverage(
noFreePlacesSubscription,
generateInvitedUsersData({ usersToInviteByEmail: ['test2@example'] }),
);
expect(result.hasOverage).toBe(true); expect(result.hasOverage).toBe(true);
expect(result.usersOverage).toBe(2); expect(result.usersOverage).toBe(2);
}); });
it('returns overage for only overlapping users added by id', () => { it('returns overage for only overlapping users added by id', () => {
const result = checkOverage(noFreePlacesSubscription, [1, 2, 3], []); const result = checkOverage(
noFreePlacesSubscription,
generateInvitedUsersData({ usersToAddById: [1, 2, 3] }),
);
expect(result.hasOverage).toBe(true); expect(result.hasOverage).toBe(true);
expect(result.usersOverage).toBe(3); expect(result.usersOverage).toBe(3);
...@@ -43,8 +58,9 @@ describe('overage check', () => { ...@@ -43,8 +58,9 @@ describe('overage check', () => {
it('returns overage for only overlapping users added by emails', () => { it('returns overage for only overlapping users added by emails', () => {
const result = checkOverage( const result = checkOverage(
noFreePlacesSubscription, noFreePlacesSubscription,
[], generateInvitedUsersData({
['test@example', 'test2@example', 'test3@example'], usersToInviteByEmail: ['test@example', 'test2@example', 'test3@example'],
}),
); );
expect(result.hasOverage).toBe(true); expect(result.hasOverage).toBe(true);
...@@ -54,8 +70,10 @@ describe('overage check', () => { ...@@ -54,8 +70,10 @@ describe('overage check', () => {
it('returns overage for only overlapping users added by ids and emails', () => { it('returns overage for only overlapping users added by ids and emails', () => {
const result = checkOverage( const result = checkOverage(
noFreePlacesSubscription, noFreePlacesSubscription,
[1, 2], generateInvitedUsersData({
['test@example', 'test2@example'], usersToAddById: [1, 2],
usersToInviteByEmail: ['test@example', 'test2@example'],
}),
); );
expect(result.hasOverage).toBe(true); expect(result.hasOverage).toBe(true);
...@@ -63,8 +81,29 @@ describe('overage check', () => { ...@@ -63,8 +81,29 @@ describe('overage check', () => {
}); });
it('returns no overage if adding a user does not increase seats owed', () => { it('returns no overage if adding a user does not increase seats owed', () => {
const result = checkOverage(subscriptionWithOverage, [2], []); const result = checkOverage(subscriptionWithOverage, secondUserAddedById);
expect(result.hasOverage).toBe(false); expect(result.hasOverage).toBe(false);
}); });
describe('for subscriptions that don`\t bill guests', () => {
it('returns overage on adding developers', () => {
const result = checkOverage(allowGuestsSubscription, secondUserAddedById);
expect(result.hasOverage).toBe(true);
expect(result.usersOverage).toBe(2);
});
it('returns no overage on adding guests', () => {
const result = checkOverage(
allowGuestsSubscription,
generateInvitedUsersData({
isGuestRole: true,
usersToAddById: [2],
}),
);
expect(result.hasOverage).toBe(false);
});
});
}); });
...@@ -39,6 +39,9 @@ describe('EEInviteModalBase', () => { ...@@ -39,6 +39,9 @@ describe('EEInviteModalBase', () => {
provide: { provide: {
...glFeatures, ...glFeatures,
}, },
attrs: {
'access-levels': propsData.accessLevels,
},
stubs: { stubs: {
GlSprintf, GlSprintf,
InviteModalBase: CEInviteModalBase, InviteModalBase: CEInviteModalBase,
......
...@@ -25,6 +25,7 @@ describe('fetchUserIdsFromGroup', () => { ...@@ -25,6 +25,7 @@ describe('fetchUserIdsFromGroup', () => {
maxSeatsUsed: 104, maxSeatsUsed: 104,
seatsInUse: 98, seatsInUse: 98,
subscriptionSeats: 100, subscriptionSeats: 100,
excludeGuests: true,
}); });
}); });
...@@ -37,6 +38,7 @@ describe('fetchUserIdsFromGroup', () => { ...@@ -37,6 +38,7 @@ describe('fetchUserIdsFromGroup', () => {
maxSeatsUsed: 5, maxSeatsUsed: 5,
seatsInUse: 0, seatsInUse: 0,
subscriptionSeats: 0, subscriptionSeats: 0,
excludeGuests: false,
}); });
}); });
}); });
...@@ -5,6 +5,7 @@ const generateSubscriptionData = ({ ...@@ -5,6 +5,7 @@ const generateSubscriptionData = ({
seatsInUse = 0, seatsInUse = 0,
billedUserIds = [], billedUserIds = [],
billedUserEmails = [], billedUserEmails = [],
excludeGuests = false,
} = {}) => ({ } = {}) => ({
isFreeGroup, isFreeGroup,
subscriptionSeats, subscriptionSeats,
...@@ -12,6 +13,17 @@ const generateSubscriptionData = ({ ...@@ -12,6 +13,17 @@ const generateSubscriptionData = ({
seatsInUse, seatsInUse,
billedUserIds, billedUserIds,
billedUserEmails, billedUserEmails,
excludeGuests,
});
export const generateInvitedUsersData = ({
isGuestRole = false,
usersToInviteByEmail = [],
usersToAddById = [],
} = {}) => ({
isGuestRole,
usersToInviteByEmail,
usersToAddById,
}); });
export const freePlanSubsciption = generateSubscriptionData({ isFreeGroup: true }); export const freePlanSubsciption = generateSubscriptionData({ isFreeGroup: true });
...@@ -28,3 +40,10 @@ export const subscriptionWithOverage = generateSubscriptionData({ ...@@ -28,3 +40,10 @@ export const subscriptionWithOverage = generateSubscriptionData({
billedUserIds: [1], billedUserIds: [1],
billedUserEmails: ['test@example'], billedUserEmails: ['test@example'],
}); });
export const allowGuestsSubscription = generateSubscriptionData({
maxSeatsUsed: 1,
seatsInUse: 1,
billedUserIds: [1],
billedUserEmails: ['test@example'],
excludeGuests: true,
});
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