From e4dae982d2c08a9f2aa2d680e446993e435d2e7f Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Fri, 21 Mar 2025 07:59:00 -0400 Subject: [PATCH] Merge pull request #24225 from overleaf/em-reviewers-share-modal Count reviewers towards collaborator limit in share modal GitOrigin-RevId: 27ec3a787124be7590791412d914ec6da78bab35 --- .../web/frontend/extracted-translations.json | 4 ++ .../components/add-collaborators.jsx | 6 ++- .../components/edit-member.tsx | 40 +++++++++++++------ .../components/share-modal-body.tsx | 6 +-- .../shared/context/types/project-context.tsx | 2 +- services/web/locales/en.json | 4 ++ .../components/share-project-modal.test.jsx | 34 ++++++++++++++++ 7 files changed, 77 insertions(+), 19 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 009158e090..d3b2b41c8b 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -897,6 +897,10 @@ "limited_document_history": "", "limited_offer": "", "limited_to_n_editors": "", + "limited_to_n_editors_or_reviewers": "", + "limited_to_n_editors_or_reviewers_per_project": "", + "limited_to_n_editors_or_reviewers_per_project_plural": "", + "limited_to_n_editors_or_reviewers_plural": "", "limited_to_n_editors_per_project": "", "limited_to_n_editors_per_project_plural": "", "limited_to_n_editors_plural": "", diff --git a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx index 5052bf0ac7..9e86d5dcac 100644 --- a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx @@ -52,7 +52,7 @@ export default function AddCollaborators({ readOnly }) { const { reset, selectedItems } = multipleSelectionProps useEffect(() => { - if (readOnly && privileges === 'readAndWrite') { + if (readOnly && privileges !== 'readOnly') { setPrivileges('readOnly') } }, [privileges, readOnly]) @@ -171,7 +171,9 @@ export default function AddCollaborators({ readOnly }) { {t('can_edit')} {getMeta('ol-isReviewerRoleEnabled') && ( - + )} diff --git a/services/web/frontend/js/features/share-project-modal/components/edit-member.tsx b/services/web/frontend/js/features/share-project-modal/components/edit-member.tsx index 7964788cdf..f4f309ae75 100644 --- a/services/web/frontend/js/features/share-project-modal/components/edit-member.tsx +++ b/services/web/frontend/js/features/share-project-modal/components/edit-member.tsx @@ -16,6 +16,7 @@ import OLCol from '@/features/ui/components/ol/ol-col' import MaterialIcon from '@/shared/components/material-icon' import getMeta from '@/utils/meta' import { useUserContext } from '@/shared/context/user-context' +import { isSplitTestEnabled } from '@/utils/splitTestUtils' type PermissionsOption = PermissionsLevel | 'removeAccess' | 'downgraded' @@ -249,28 +250,41 @@ function SelectPrivilege({ } function getPrivilegeSubtitle(privilege: PermissionsOption) { - if (!hasBeenDowngraded) { - return !canAddCollaborators && - privilege === 'readAndWrite' && - value !== 'readAndWrite' - ? t('limited_to_n_editors_per_project', { - count: features.collaborators, - }) - : '' + if (!['readAndWrite', 'review'].includes(privilege)) { + return '' } - return privilege === 'readAndWrite' - ? t('limited_to_n_editors', { + if (hasBeenDowngraded) { + if (isSplitTestEnabled('reviewer-role')) { + return t('limited_to_n_editors_or_reviewers', { count: features.collaborators, }) - : '' + } else { + return t('limited_to_n_editors', { count: features.collaborators }) + } + } else if ( + !canAddCollaborators && + !['readAndWrite', 'review'].includes(value) + ) { + if (isSplitTestEnabled('reviewer-role')) { + return t('limited_to_n_editors_or_reviewers_per_project', { + count: features.collaborators, + }) + } else { + return t('limited_to_n_editors_per_project', { + count: features.collaborators, + }) + } + } else { + return '' + } } function isPrivilegeDisabled(privilege: PermissionsOption) { return ( !canAddCollaborators && - privilege === 'readAndWrite' && - (hasBeenDowngraded || value !== 'readAndWrite') + ['readAndWrite', 'review'].includes(privilege) && + (hasBeenDowngraded || !['readAndWrite', 'review'].includes(value)) ) } diff --git a/services/web/frontend/js/features/share-project-modal/components/share-modal-body.tsx b/services/web/frontend/js/features/share-project-modal/components/share-modal-body.tsx index aabb6f3628..1dc6689514 100644 --- a/services/web/frontend/js/features/share-project-modal/components/share-modal-body.tsx +++ b/services/web/frontend/js/features/share-project-modal/components/share-modal-body.tsx @@ -27,11 +27,11 @@ export default function ShareModalBody() { } const editorInvites = invites.filter( - invite => invite.privileges === 'readAndWrite' + invite => invite.privileges !== 'readOnly' ).length return ( - members.filter(member => member.privileges === 'readAndWrite').length + + members.filter(member => member.privileges !== 'readOnly').length + editorInvites < (features.collaborators ?? 1) ) @@ -67,7 +67,7 @@ export default function ShareModalBody() { } return ( - members.filter(member => member.privileges === 'readAndWrite').length > + members.filter(member => member.privileges !== 'readOnly').length > (features.collaborators ?? 1) ) }, [features, isProjectOwner, members]) diff --git a/services/web/frontend/js/shared/context/types/project-context.tsx b/services/web/frontend/js/shared/context/types/project-context.tsx index ce609d01d1..53911afb26 100644 --- a/services/web/frontend/js/shared/context/types/project-context.tsx +++ b/services/web/frontend/js/shared/context/types/project-context.tsx @@ -4,7 +4,7 @@ import { ProjectSnapshot } from '@/infrastructure/project-snapshot' export type ProjectContextMember = { _id: UserId - privileges: 'readOnly' | 'readAndWrite' + privileges: 'readOnly' | 'readAndWrite' | 'review' email: string first_name: string last_name: string diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 1c97fa6f8e..5b869d6b6b 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1180,6 +1180,10 @@ "license": "License", "limited_document_history": "Limited document history", "limited_to_n_editors": "Limited to __count__ editor", + "limited_to_n_editors_or_reviewers": "Limited to __count__ editor or reviewer", + "limited_to_n_editors_or_reviewers_per_project": "Limited to __count__ editor or reviewer per project", + "limited_to_n_editors_or_reviewers_per_project_plural": "Limited to __count__ editors or reviewers per project", + "limited_to_n_editors_or_reviewers_plural": "Limited to __count__ editors or reviewers", "limited_to_n_editors_per_project": "Limited to __count__ editor per project", "limited_to_n_editors_per_project_plural": "Limited to __count__ editors per project", "limited_to_n_editors_plural": "Limited to __count__ editors", diff --git a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx index 0199742803..69f325a9e7 100644 --- a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx +++ b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx @@ -101,6 +101,7 @@ describe('', function () { fetchMock.get('/user/contacts', { contacts }) window.metaAttributesCache.set('ol-user', { allowedFreeTrial: true }) window.metaAttributesCache.set('ol-showUpgradePrompt', true) + window.metaAttributesCache.set('ol-isReviewerRoleEnabled', true) }) afterEach(function () { @@ -691,6 +692,39 @@ describe('', function () { await screen.findByText('Add more editors') expect(screen.getByRole('option', { name: 'Can edit' }).disabled).to.be.true + expect(screen.getByRole('option', { name: 'Can review' }).disabled).to.be + .true + expect(screen.getByRole('option', { name: 'Can view' }).disabled).to.be + .false + + screen.getByText( + /Upgrade to add more editors and access collaboration features like track changes and full project history/ + ) + }) + + it('counts reviewers towards the collaborator limit', async function () { + renderWithEditorContext(, { + scope: { + project: { + ...project, + features: { + collaborators: 1, + }, + members: [ + { + _id: 'reviewer-id', + email: 'reviewer@example.com', + privileges: 'review', + }, + ], + }, + }, + }) + + await screen.findByText('Add more editors') + expect(screen.getByRole('option', { name: 'Can edit' }).disabled).to.be.true + expect(screen.getByRole('option', { name: 'Can review' }).disabled).to.be + .true expect(screen.getByRole('option', { name: 'Can view' }).disabled).to.be .false