diff --git a/services/web/app/src/Features/UserMembership/UserMembershipController.mjs b/services/web/app/src/Features/UserMembership/UserMembershipController.mjs index 307d21de3e..cad4f4a02e 100644 --- a/services/web/app/src/Features/UserMembership/UserMembershipController.mjs +++ b/services/web/app/src/Features/UserMembership/UserMembershipController.mjs @@ -11,6 +11,7 @@ import { import { SSOConfig } from '../../models/SSOConfig.js' import { Parser as CSVParser } from 'json2csv' import { expressify } from '@overleaf/promise-utils' +import SplitTestHandler from '../SplitTests/SplitTestHandler.js' async function manageGroupMembers(req, res, next) { const { entity: subscription, entityConfig } = req @@ -29,6 +30,12 @@ async function manageGroupMembers(req, res, next) { ) const ssoConfig = await SSOConfig.findById(subscription.ssoConfig).exec() + await SplitTestHandler.promises.getAssignment( + req, + res, + 'flexible-group-licensing' + ) + res.render('user_membership/group-members-react', { name: entityName, groupId: entityPrimaryKey, diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 28494c29a4..6adf7cdcbb 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -73,6 +73,7 @@ "add_more_editors": "", "add_more_managers": "", "add_more_members": "", + "add_more_users": "", "add_new_email": "", "add_ons_are": "", "add_or_remove_project_from_tag": "", @@ -751,11 +752,13 @@ "invite": "", "invite_expired": "", "invite_more_collabs": "", + "invite_more_members": "", "invite_not_accepted": "", "invite_resend_limit_hit": "", "invited_to_group": "", "invited_to_group_have_individual_subcription": "", "invited_to_join": "", + "inviting": "", "ip_address": "", "is_email_affiliated": "", "issued_on": "", @@ -1871,10 +1874,12 @@ "you_dont_have_any_repositories": "", "you_have_0_free_suggestions_left": "", "you_have_1_free_suggestion_left": "", + "you_have_1_user_and_your_plan_supports_up_to_y": "", "you_have_added_x_of_group_size_y": "", "you_have_been_invited_to_transfer_management_of_your_account": "", "you_have_been_invited_to_transfer_management_of_your_account_to": "", "you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "", + "you_have_x_users_and_your_plan_supports_up_to_y": "", "you_need_to_configure_your_sso_settings": "", "you_will_be_able_to_reassign_subscription": "", "youll_get_best_results_in_visual_but_can_be_used_in_source": "", diff --git a/services/web/frontend/js/features/group-management/components/group-members.tsx b/services/web/frontend/js/features/group-management/components/group-members.tsx index 540e630a34..9b87c0ecfb 100644 --- a/services/web/frontend/js/features/group-management/components/group-members.tsx +++ b/services/web/frontend/js/features/group-management/components/group-members.tsx @@ -7,6 +7,7 @@ import getMeta from '../../../utils/meta' import { useGroupMembersContext } from '../context/group-members-context' import ErrorAlert from './error-alert' import MembersList from './members-table/members-list' +import { useFeatureFlag } from '@/shared/context/split-test-context' export default function GroupMembers() { const { isReady } = useWaitForI18n() @@ -23,6 +24,9 @@ export default function GroupMembers() { paths, } = useGroupMembersContext() const [emailString, setEmailString] = useState('') + const flexibleGroupLicensingEnabled = useFeatureFlag( + 'flexible-group-licensing' + ) const groupId = getMeta('ol-groupId') const groupName = getMeta('ol-groupName') @@ -44,6 +48,43 @@ export default function GroupMembers() { addMembers(emailString) } + const groupSizeDetails = () => { + if (flexibleGroupLicensingEnabled) { + return ( + + + {users.length === 1 + ? t('you_have_1_user_and_your_plan_supports_up_to_y', { + groupSize, + }) + : t('you_have_x_users_and_your_plan_supports_up_to_y', { + addedUsersSize: users.length, + groupSize, + })} + {' '} + + {t('add_more_users')} + + + ) + } + + return ( + + , ]} // eslint-disable-line react/jsx-key + values={{ addedUsersSize: users.length, groupSize }} + shouldUnescape + tOptions={{ interpolation: { escapeValue: true } }} + /> + + ) + } + return (
@@ -60,17 +101,7 @@ export default function GroupMembers() {
- {selectedUsers.length === 0 && ( - - , ]} // eslint-disable-line react/jsx-key - values={{ addedUsersSize: users.length, groupSize }} - shouldUnescape - tOptions={{ interpolation: { escapeValue: true } }} - /> - - )} + {selectedUsers.length === 0 && groupSizeDetails()} {removeMemberLoading ? (

{users.length < groupSize && ( -
-

{t('add_more_members')}

+
+

+ {flexibleGroupLicensingEnabled + ? t('invite_more_members') + : t('add_more_members')} +

@@ -110,11 +148,16 @@ export default function GroupMembers() { {inviteMemberLoading ? ( ) : ( )} diff --git a/services/web/frontend/js/pages/user/subscription/group-management/group-members.jsx b/services/web/frontend/js/pages/user/subscription/group-management/group-members.jsx index bc9200e8c3..446220c702 100644 --- a/services/web/frontend/js/pages/user/subscription/group-management/group-members.jsx +++ b/services/web/frontend/js/pages/user/subscription/group-management/group-members.jsx @@ -2,13 +2,16 @@ import '../base' import ReactDOM from 'react-dom' import GroupMembers from '../../../../features/group-management/components/group-members' import { GroupMembersProvider } from '../../../../features/group-management/context/group-members-context' +import { SplitTestProvider } from '@/shared/context/split-test-context' const element = document.getElementById('subscription-manage-group-root') if (element) { ReactDOM.render( - - - , + + + + + , element ) } diff --git a/services/web/locales/en.json b/services/web/locales/en.json index e63dedc252..bde0d138b3 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -85,6 +85,7 @@ "add_more_editors": "Add more editors", "add_more_managers": "Add more managers", "add_more_members": "Add more members", + "add_more_users": "Add more users.", "add_new_email": "Add new email", "add_ons_are": "Add-ons: __addOnName__", "add_or_remove_project_from_tag": "Add or remove project from tag __tagName__", @@ -1051,6 +1052,7 @@ "invite": "Invite", "invite_expired": "The invite may have expired", "invite_more_collabs": "Invite more collaborators", + "invite_more_members": "Invite more members", "invite_not_accepted": "Invite not yet accepted", "invite_not_valid": "This is not a valid project invite", "invite_not_valid_description": "The invite may have expired. Please contact the project owner", @@ -1062,6 +1064,7 @@ "invited_to_group_register": "To accept __inviterName__’s invitation you’ll need to create an account.", "invited_to_group_register_benefits": "__appName__ is a collaborative online LaTeX editor, with thousands of ready-to-use templates and an array of LaTeX learning resources to help you get started.", "invited_to_join": "You have been invited to join", + "inviting": "Inviting", "ip_address": "IP Address", "is_email_affiliated": "Is your email affiliated with an institution? ", "is_longer_than_n_characters": "is at least __n__ characters long", @@ -2535,10 +2538,12 @@ "you_get_access_to_info": "These features are available only to you (the subscriber).", "you_have_0_free_suggestions_left": "You have 0 free suggestions left", "you_have_1_free_suggestion_left": "You have 1 free suggestion left", + "you_have_1_user_and_your_plan_supports_up_to_y": "You have 1 user and your plan supports up to __groupSize__.", "you_have_added_x_of_group_size_y": "You have added <0>__addedUsersSize__ of <1>__groupSize__ available members", "you_have_been_invited_to_transfer_management_of_your_account": "You have been invited to transfer management of your account.", "you_have_been_invited_to_transfer_management_of_your_account_to": "You have been invited to transfer management of your account to __groupName__.", "you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard": "You have been removed from this project, and will no longer have access to it. You will be redirected to your project dashboard momentarily.", + "you_have_x_users_and_your_plan_supports_up_to_y": "You have __addedUsersSize__ users and your plan supports up to __groupSize__.", "you_need_to_configure_your_sso_settings": "You need to configure and test your SSO settings before enabling SSO", "you_plus_1": "You + 1", "you_plus_10": "You + 10", diff --git a/services/web/test/frontend/features/group-management/components/group-members.spec.tsx b/services/web/test/frontend/features/group-management/components/group-members.spec.tsx index 4bc7d2ab9d..b01194654c 100644 --- a/services/web/test/frontend/features/group-management/components/group-members.spec.tsx +++ b/services/web/test/frontend/features/group-management/components/group-members.spec.tsx @@ -2,6 +2,7 @@ import '../../../helpers/bootstrap-3' import GroupMembers from '@/features/group-management/components/group-members' import { GroupMembersProvider } from '@/features/group-management/context/group-members-context' import { User } from '../../../../../types/group-management/user' +import { SplitTestProvider } from '@/shared/context/split-test-context' const GROUP_ID = '777fff777fff' const PATHS = { @@ -14,9 +15,11 @@ const PATHS = { describe('GroupMembers', function () { function mountGroupMembersProvider() { cy.mount( - - - + + + + + ) } @@ -47,9 +50,11 @@ describe('GroupMembers', function () { }) cy.mount( - - - + + + + + ) }) @@ -456,4 +461,75 @@ describe('GroupMembers', function () { }) }) }) + + describe('with flexible group licensing enabled', function () { + const JOHN_DOE = { + _id: 'abc123def456', + first_name: 'John', + last_name: 'Doe', + email: 'john.doe@test.com', + last_active_at: new Date('2023-01-15'), + invite: false, + } + const BOBBY_LAPOINTE = { + _id: 'bcd234efa567', + first_name: 'Bobby', + last_name: 'Lapointe', + email: 'bobby.lapointe@test.com', + last_active_at: new Date('2023-01-02'), + invite: false, + } + + it('renders the group members page with the new text', function () { + cy.window().then(win => { + win.metaAttributesCache.set('ol-groupId', GROUP_ID) + win.metaAttributesCache.set('ol-groupName', 'My Awesome Team') + win.metaAttributesCache.set('ol-groupSize', 10) + win.metaAttributesCache.set('ol-users', [JOHN_DOE, BOBBY_LAPOINTE]) + win.metaAttributesCache.set('ol-splitTestVariants', { + 'flexible-group-licensing': 'enabled', + }) + }) + + cy.mount( + + + + + + ) + + cy.findByTestId('group-size-details').contains( + 'You have 2 users and your plan supports up to 10. Add more users.' + ) + cy.findByTestId('add-more-members-form').within(() => { + cy.contains('Invite more members') + cy.get('button').contains('Invite') + }) + }) + + it('renders the group members page with new text when only has one group member', function () { + cy.window().then(win => { + win.metaAttributesCache.set('ol-groupId', GROUP_ID) + win.metaAttributesCache.set('ol-groupName', 'My Awesome Team') + win.metaAttributesCache.set('ol-groupSize', 10) + win.metaAttributesCache.set('ol-users', [JOHN_DOE]) + win.metaAttributesCache.set('ol-splitTestVariants', { + 'flexible-group-licensing': 'enabled', + }) + }) + + cy.mount( + + + + + + ) + + cy.findByTestId('group-size-details').contains( + 'You have 1 user and your plan supports up to 10. Add more users.' + ) + }) + }) }) diff --git a/services/web/test/frontend/features/group-management/components/managed-group-members.spec.tsx b/services/web/test/frontend/features/group-management/components/managed-group-members.spec.tsx index f5f867d155..d45a85f193 100644 --- a/services/web/test/frontend/features/group-management/components/managed-group-members.spec.tsx +++ b/services/web/test/frontend/features/group-management/components/managed-group-members.spec.tsx @@ -2,6 +2,7 @@ import '../../../helpers/bootstrap-3' import GroupMembers from '@/features/group-management/components/group-members' import { GroupMembersProvider } from '@/features/group-management/context/group-members-context' import { User } from '../../../../../types/group-management/user' +import { SplitTestProvider } from '@/shared/context/split-test-context' const GROUP_ID = '777fff777fff' const JOHN_DOE: User = { @@ -57,9 +58,11 @@ const PATHS = { function mountGroupMembersProvider() { cy.mount( - - - + + + + + ) }