Commit 801c1f17 authored by Serhii Yarynovskyi's avatar Serhii Yarynovskyi Committed by Olena Horal-Koretska

Add user limit notification for invite members modal

Now when invite members modal appears, customer can see indication
how many seats are remaining when namespace approaches user limit

Changelog: added
parent e60f24fa
......@@ -24,6 +24,7 @@ import { responseMessageFromSuccess } from '../utils/response_message_parser';
import { getInvalidFeedbackMessage } from '../utils/get_invalid_feedback_message';
import ModalConfetti from './confetti.vue';
import MembersTokenSelect from './members_token_select.vue';
import UserLimitNotification from './user_limit_notification.vue';
export default {
name: 'InviteMembersModal',
......@@ -37,6 +38,7 @@ export default {
InviteModalBase,
MembersTokenSelect,
ModalConfetti,
UserLimitNotification,
},
inject: ['newProjectPath'],
props: {
......@@ -308,6 +310,11 @@ export default {
<span v-if="isCelebration">{{ $options.labels.modal.celebrate.intro }} </span>
<modal-confetti v-if="isCelebration" />
</template>
<template #user-limit-notification>
<user-limit-notification />
</template>
<template #select="{ validationState, labelId }">
<members-token-select
v-model="newUsersToInvite"
......
......@@ -215,6 +215,8 @@ export default {
<slot name="intro-text-after"></slot>
</div>
<slot name="user-limit-notification"></slot>
<gl-form-group
:invalid-feedback="invalidFeedbackMessage"
:state="validationState"
......
<script>
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import { s__, n__, sprintf } from '~/locale';
const CLOSE_TO_LIMIT_COUNT = 2;
const WARNING_ALERT_TITLE = s__(
'InviteMembersModal|You only have space for %{count} more %{members} in %{name}',
);
const DANGER_ALERT_TITLE = s__(
"InviteMembersModal|You've reached your %{count} %{members} limit for %{name}",
);
const CLOSE_TO_LIMIT_MESSAGE = s__(
'InviteMembersModal|To get more members an owner of this namespace can %{trialLinkStart}start a trial%{trialLinkEnd} or %{upgradeLinkStart}upgrade%{upgradeLinkEnd} to a paid tier.',
);
const REACHED_LIMIT_MESSAGE = s__(
'InviteMembersModal|New members will be unable to participate. You can manage your members by removing ones you no longer need.',
).concat(' ', CLOSE_TO_LIMIT_MESSAGE);
export default {
name: 'UserLimitNotification',
components: { GlAlert, GlSprintf, GlLink },
inject: ['name', 'newTrialRegistrationPath', 'purchasePath', 'freeUsersLimit', 'membersCount'],
computed: {
reachedLimit() {
return this.isLimit();
},
closeToLimit() {
return this.isLimit(CLOSE_TO_LIMIT_COUNT);
},
warningAlertTitle() {
return sprintf(WARNING_ALERT_TITLE, {
count: this.freeUsersLimit - this.membersCount,
members: this.pluralMembers(this.freeUsersLimit - this.membersCount),
name: this.name,
});
},
dangerAlertTitle() {
return sprintf(DANGER_ALERT_TITLE, {
count: this.freeUsersLimit,
members: this.pluralMembers(this.freeUsersLimit),
name: this.name,
});
},
variant() {
return this.reachedLimit ? 'danger' : 'warning';
},
title() {
return this.reachedLimit ? this.dangerAlertTitle : this.warningAlertTitle;
},
message() {
if (this.reachedLimit) {
return this.$options.i18n.reachedLimitMessage;
}
return this.$options.i18n.closeToLimitMessage;
},
},
methods: {
isLimit(deviation = 0) {
if (this.freeUsersLimit && this.membersCount) {
return this.membersCount >= this.freeUsersLimit - deviation;
}
return false;
},
pluralMembers(count) {
return n__('member', 'members', count);
},
},
i18n: {
reachedLimitMessage: REACHED_LIMIT_MESSAGE,
closeToLimitMessage: CLOSE_TO_LIMIT_MESSAGE,
},
};
</script>
<template>
<gl-alert
v-if="reachedLimit || closeToLimit"
:variant="variant"
:dismissible="false"
:title="title"
>
<gl-sprintf :message="message">
<template #trialLink="{ content }">
<gl-link :href="newTrialRegistrationPath" class="gl-label-link">{{ content }}</gl-link>
</template>
<template #upgradeLink="{ content }">
<gl-link :href="purchasePath" class="gl-label-link">{{ content }}</gl-link>
</template>
</gl-sprintf>
</gl-alert>
</template>
......@@ -24,7 +24,12 @@ export default (function initInviteMembersModal() {
el,
name: 'InviteMembersModalRoot',
provide: {
name: el.dataset.name,
newProjectPath: el.dataset.newProjectPath,
newTrialRegistrationPath: el.dataset.newTrialRegistrationPath,
purchasePath: el.dataset.purchasePath,
freeUsersLimit: el.dataset.freeUsersLimit && parseInt(el.dataset.freeUsersLimit, 10),
membersCount: el.dataset.membersCount && parseInt(el.dataset.membersCount, 10),
},
render: (createElement) =>
createElement(InviteMembersModal, {
......
......@@ -46,6 +46,7 @@ module InviteMembersHelper
}
end
# Overridden in EE
def common_invite_modal_dataset(source)
dataset = {
id: source.id,
......
......@@ -4,6 +4,22 @@ module EE
module InviteMembersHelper
extend ::Gitlab::Utils::Override
override :common_invite_modal_dataset
def common_invite_modal_dataset(source)
dataset = super
if source.root_ancestor.apply_free_user_cap? && !source.root_ancestor.user_namespace?
dataset.merge({
new_trial_registration_path: new_trial_path,
purchase_path: group_billings_path(source.root_ancestor),
free_users_limit: ::Plan::FREE_USER_LIMIT,
members_count: source.root_ancestor.free_plan_members_count
})
else
dataset
end
end
override :users_filter_data
def users_filter_data(group)
root_group = group&.root_ancestor
......
......@@ -2,7 +2,54 @@
require 'spec_helper'
RSpec.describe EE::InviteMembersHelper do
describe '.users_filter_data' do
include Devise::Test::ControllerHelpers
describe '#common_invite_modal_dataset', :saas do
let(:project) { build(:project) }
let(:notification_attributes) do
{
free_users_limit: 5,
members_count: 0,
new_trial_registration_path: '/-/trials/new',
purchase_path: "/groups/#{project.root_ancestor.path}/-/billings"
}
end
context 'when applying the free user cap is not valid' do
let!(:group) do
build(:group, projects: [project], gitlab_subscription: build(:gitlab_subscription, :default))
end
it 'does not include users limit notification data' do
expect(helper.common_invite_modal_dataset(project)).not_to include(notification_attributes)
end
end
context 'when applying the free user cap is valid' do
context 'when user namespace' do
let!(:user_namespace) do
build(:user_namespace, projects: [project], gitlab_subscription: build(:gitlab_subscription, :free))
end
it 'does not include users limit notification data' do
expect(helper.common_invite_modal_dataset(project)).not_to include(notification_attributes)
end
end
context 'when group namespace' do
let!(:group) do
build(:group, projects: [project], gitlab_subscription: build(:gitlab_subscription, :free))
end
it 'includes users limit notification data' do
expect(helper.common_invite_modal_dataset(project)).to include(notification_attributes)
end
end
end
end
describe '#users_filter_data' do
let_it_be(:group) { create(:group) }
let_it_be(:saml_provider) { create(:saml_provider, group: group) }
......
......@@ -20542,6 +20542,9 @@ msgstr ""
msgid "InviteMembersModal|Members were successfully added"
msgstr ""
msgid "InviteMembersModal|New members will be unable to participate. You can manage your members by removing ones you no longer need."
msgstr ""
msgid "InviteMembersModal|Search for a group to invite"
msgstr ""
......@@ -20560,6 +20563,12 @@ msgstr ""
msgid "InviteMembersModal|To assign issues to a new team member, you need a project for the issues. %{linkStart}Create a project to get started.%{linkEnd}"
msgstr ""
msgid "InviteMembersModal|To get more members an owner of this namespace can %{trialLinkStart}start a trial%{trialLinkEnd} or %{upgradeLinkStart}upgrade%{upgradeLinkEnd} to a paid tier."
msgstr ""
msgid "InviteMembersModal|You only have space for %{count} more %{members} in %{name}"
msgstr ""
msgid "InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} group."
msgstr ""
......@@ -20572,6 +20581,9 @@ msgstr ""
msgid "InviteMembersModal|You're inviting members to the %{strongStart}%{name}%{strongEnd} project."
msgstr ""
msgid "InviteMembersModal|You've reached your %{count} %{members} limit for %{name}"
msgstr ""
msgid "InviteMembers|Invite a group"
msgstr ""
......@@ -44459,6 +44471,11 @@ msgstr ""
msgid "math|There was an error rendering this math block"
msgstr ""
msgid "member"
msgid_plural "members"
msgstr[0] ""
msgstr[1] ""
msgid "merge request"
msgid_plural "merge requests"
msgstr[0] ""
......
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import UserLimitNotification from '~/invite_members/components/user_limit_notification.vue';
describe('UserLimitNotification', () => {
let wrapper;
const findAlert = () => wrapper.findComponent(GlAlert);
const createComponent = (providers = {}) => {
wrapper = shallowMountExtended(UserLimitNotification, {
provide: {
name: 'my group',
newTrialRegistrationPath: 'newTrialRegistrationPath',
purchasePath: 'purchasePath',
freeUsersLimit: 5,
membersCount: 1,
...providers,
},
stubs: { GlSprintf },
});
};
afterEach(() => {
wrapper.destroy();
});
describe('when limit is not reached', () => {
beforeEach(() => {
createComponent();
});
it('renders empty block', () => {
expect(findAlert().exists()).toBe(false);
});
});
describe('when close to limit', () => {
beforeEach(() => {
createComponent({ membersCount: 3 });
});
it("renders user's limit notification", () => {
const alert = findAlert();
expect(alert.attributes('title')).toEqual(
'You only have space for 2 more members in my group',
);
expect(alert.text()).toEqual(
'To get more members an owner of this namespace can start a trial or upgrade to a paid tier.',
);
});
});
describe('when limit is reached', () => {
beforeEach(() => {
createComponent({ membersCount: 5 });
});
it("renders user's limit notification", () => {
const alert = findAlert();
expect(alert.attributes('title')).toEqual("You've reached your 5 members limit for my group");
expect(alert.text()).toEqual(
'New members will be unable to participate. You can manage your members by removing ones you no longer need. To get more members an owner of this namespace can start a trial or upgrade to a paid tier.',
);
});
});
});
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