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