Merge pull request #21782 from overleaf/ls-update-group-member-management-page

update group member management page for flexible licensing

GitOrigin-RevId: 605fb760a1f73763e49978cf4aea81bb88ffb425
This commit is contained in:
Liangjun Song 2024-11-21 11:21:21 +00:00 committed by Copybot
parent ac8d8d6edc
commit 56f6f77ba2
7 changed files with 169 additions and 27 deletions

View file

@ -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,

View file

@ -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": "",

View file

@ -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<string>('')
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 (
<small data-testid="group-size-details">
<strong>
{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,
})}
</strong>{' '}
<a
href="/user/subscription/group/add-users"
rel="noreferrer noopener"
>
{t('add_more_users')}
</a>
</small>
)
}
return (
<small>
<Trans
i18nKey="you_have_added_x_of_group_size_y"
components={[<strong />, <strong />]} // eslint-disable-line react/jsx-key
values={{ addedUsersSize: users.length, groupSize }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
</small>
)
}
return (
<div className="container">
<Row>
@ -60,17 +101,7 @@ export default function GroupMembers() {
<div className="card">
<div className="page-header">
<div className="pull-right">
{selectedUsers.length === 0 && (
<small>
<Trans
i18nKey="you_have_added_x_of_group_size_y"
components={[<strong />, <strong />]} // eslint-disable-line react/jsx-key
values={{ addedUsersSize: users.length, groupSize }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
</small>
)}
{selectedUsers.length === 0 && groupSizeDetails()}
{removeMemberLoading ? (
<Button bsStyle="danger" disabled>
{t('removing')}&hellip;
@ -93,8 +124,15 @@ export default function GroupMembers() {
</div>
<hr />
{users.length < groupSize && (
<div className="add-more-members-form">
<p className="small">{t('add_more_members')}</p>
<div
className="add-more-members-form"
data-testid="add-more-members-form"
>
<p className="small">
{flexibleGroupLicensingEnabled
? t('invite_more_members')
: t('add_more_members')}
</p>
<ErrorAlert error={inviteError} />
<Form horizontal onSubmit={onAddMembersSubmit} className="form">
<Row>
@ -110,11 +148,16 @@ export default function GroupMembers() {
<Col xs={4}>
{inviteMemberLoading ? (
<Button bsStyle="primary" disabled>
{t('adding')}&hellip;
{flexibleGroupLicensingEnabled
? t('inviting')
: t('adding')}
&hellip;
</Button>
) : (
<Button bsStyle="primary" onClick={onAddMembersSubmit}>
{t('add')}
{flexibleGroupLicensingEnabled
? t('invite')
: t('add')}
</Button>
)}
</Col>

View file

@ -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(
<GroupMembersProvider>
<GroupMembers />
</GroupMembersProvider>,
<SplitTestProvider>
<GroupMembersProvider>
<GroupMembers />
</GroupMembersProvider>
</SplitTestProvider>,
element
)
}

View file

@ -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": "<strong>Add-ons:</strong> __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 youll 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__</0> of <1>__groupSize__</1> 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",

View file

@ -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(
<GroupMembersProvider>
<GroupMembers />
</GroupMembersProvider>
<SplitTestProvider>
<GroupMembersProvider>
<GroupMembers />
</GroupMembersProvider>
</SplitTestProvider>
)
}
@ -47,9 +50,11 @@ describe('GroupMembers', function () {
})
cy.mount(
<GroupMembersProvider>
<GroupMembers />
</GroupMembersProvider>
<SplitTestProvider>
<GroupMembersProvider>
<GroupMembers />
</GroupMembersProvider>
</SplitTestProvider>
)
})
@ -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(
<SplitTestProvider>
<GroupMembersProvider>
<GroupMembers />
</GroupMembersProvider>
</SplitTestProvider>
)
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(
<SplitTestProvider>
<GroupMembersProvider>
<GroupMembers />
</GroupMembersProvider>
</SplitTestProvider>
)
cy.findByTestId('group-size-details').contains(
'You have 1 user and your plan supports up to 10. Add more users.'
)
})
})
})

View file

@ -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(
<GroupMembersProvider>
<GroupMembers />
</GroupMembersProvider>
<SplitTestProvider>
<GroupMembersProvider>
<GroupMembers />
</GroupMembersProvider>
</SplitTestProvider>
)
}