Update share modal dropdown with a description for reviewers in a free project (#24571)

* Update collaborator select dropdown with a description for reviewers in free project

* Update share-project-modal test

* Fix saas-e2e tests

* fix server pro tests

* fix cypress multiple inputs selection

* fix testid case

GitOrigin-RevId: 5369828334596d80988aba168385f0a51eea998f
This commit is contained in:
Domagoj Kriskovic
2025-04-02 12:27:11 +02:00
committed by Copybot
parent 830d0daa38
commit f36c87b301
12 changed files with 103 additions and 51 deletions

View File

@@ -95,7 +95,9 @@ describe('Project creation and compilation', function () {
cy.findByText('Share').click()
cy.findByRole('dialog').within(() => {
cy.get('input').type('collaborator@example.com,')
cy.findByTestId('collaborator-email-input').type(
'collaborator@example.com,'
)
cy.findByText('Invite').click({ force: true })
cy.findByText('Invite not yet accepted.')
})

View File

@@ -136,7 +136,7 @@ describe('git-bridge', function () {
shareProjectByEmailAndAcceptInviteViaDash(
projectName,
'collaborator-rw@example.com',
'Can edit'
'Editor'
)
maybeClearAllTokens()
openProjectByName(projectName)
@@ -147,7 +147,7 @@ describe('git-bridge', function () {
shareProjectByEmailAndAcceptInviteViaDash(
projectName,
'collaborator-ro@example.com',
'Can view'
'Viewer'
)
maybeClearAllTokens()
openProjectByName(projectName)

View File

@@ -100,7 +100,7 @@ export function openProjectViaInviteNotification(projectName: string) {
function shareProjectByEmail(
projectName: string,
email: string,
level: 'Can view' | 'Can edit'
level: 'Viewer' | 'Editor'
) {
openProjectByName(projectName)
cy.findByText('Share').click()
@@ -108,7 +108,13 @@ function shareProjectByEmail(
cy.findByLabelText('Add people', { selector: 'input' }).type(`${email},`)
cy.findByLabelText('Add people', { selector: 'input' })
.parents('form')
.within(() => cy.findByText('Can edit').parent().select(level))
.within(() => {
cy.findByTestId('add-collaborator-select')
.click()
.then(() => {
cy.findByText(level).click()
})
})
cy.findByText('Invite').click({ force: true })
cy.findByText('Invite not yet accepted.')
})
@@ -117,7 +123,7 @@ function shareProjectByEmail(
export function shareProjectByEmailAndAcceptInviteViaDash(
projectName: string,
email: string,
level: 'Can view' | 'Can edit'
level: 'Viewer' | 'Editor'
) {
shareProjectByEmail(projectName, email, level)
@@ -128,7 +134,7 @@ export function shareProjectByEmailAndAcceptInviteViaDash(
export function shareProjectByEmailAndAcceptInviteViaEmail(
projectName: string,
email: string,
level: 'Can view' | 'Can edit'
level: 'Viewer' | 'Editor'
) {
shareProjectByEmail(projectName, email, level)

View File

@@ -154,7 +154,7 @@ describe('Project Sharing', function () {
beforeEach(function () {
login('user@example.com')
shareProjectByEmailAndAcceptInviteViaEmail(projectName, email, 'Can view')
shareProjectByEmailAndAcceptInviteViaEmail(projectName, email, 'Viewer')
})
it('should grant the collaborator read access', () => {
@@ -169,7 +169,7 @@ describe('Project Sharing', function () {
beforeWithReRunOnTestRetry(function () {
login('user@example.com')
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Can view')
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Viewer')
})
it('should grant the collaborator read access', () => {
@@ -186,7 +186,7 @@ describe('Project Sharing', function () {
beforeWithReRunOnTestRetry(function () {
login('user@example.com')
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Can edit')
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Editor')
})
it('should grant the collaborator write access', () => {

View File

@@ -183,13 +183,10 @@
"blog": "",
"browser": "",
"by_subscribing_you_agree_to_our_terms_of_service": "",
"can_edit": "",
"can_edit_content": "",
"can_link_institution_email_acct_to_institution_acct": "",
"can_link_your_institution_acct_2": "",
"can_now_relink_dropbox": "",
"can_review": "",
"can_view": "",
"can_view_content": "",
"cancel": "",
"cancel_add_on": "",
@@ -268,6 +265,7 @@
"column_width_is_x_click_to_resize": "",
"comment": "",
"comment_only": "",
"comment_only_upgrade_for_track_changes": "",
"common": "",
"common_causes_of_compile_timeouts_include": "",
"commons_plan_tooltip": "",
@@ -1273,7 +1271,6 @@
"read_lines_from_path": "",
"read_more": "",
"read_more_about_free_compile_timeouts_servers": "",
"read_only": "",
"read_only_token": "",
"read_write_token": "",
"ready_to_join_x": "",

View File

@@ -12,7 +12,7 @@ import ClickableElementEnhancer from '@/shared/components/clickable-element-enha
import PropTypes from 'prop-types'
import OLForm from '@/features/ui/components/ol/ol-form'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLFormSelect from '@/features/ui/components/ol/ol-form-select'
import { Select } from '@/shared/components/select'
import OLButton from '@/features/ui/components/ol/ol-button'
import getMeta from '@/utils/meta'
@@ -27,7 +27,7 @@ export default function AddCollaborators({ readOnly }) {
const { updateProject, setInFlight, setError } = useShareProjectContext()
const { _id: projectId, members, invites } = useProjectContext()
const { _id: projectId, members, invites, features } = useProjectContext()
const currentMemberEmails = useMemo(
() => (members || []).map(member => member.email).sort(),
@@ -149,6 +149,32 @@ export default function AddCollaborators({ readOnly }) {
updateProject,
])
const privilegeOptions = useMemo(() => {
const options = [
{
key: 'readAndWrite',
label: t('editor'),
},
]
if (getMeta('ol-isReviewerRoleEnabled')) {
options.push({
key: 'review',
label: t('reviewer'),
description: !features.trackChanges
? t('comment_only_upgrade_for_track_changes')
: null,
})
}
options.push({
key: 'readOnly',
label: t('viewer'),
})
return options
}, [features.trackChanges, t])
return (
<OLForm className="add-collabs">
<OLFormGroup>
@@ -161,23 +187,19 @@ export default function AddCollaborators({ readOnly }) {
</OLFormGroup>
<OLFormGroup>
<div className="pull-right">
<OLFormSelect
className="privileges"
value={privileges}
onChange={event => setPrivileges(event.target.value)}
>
<option disabled={readOnly} value="readAndWrite">
{t('can_edit')}
</option>
{getMeta('ol-isReviewerRoleEnabled') && (
<option disabled={readOnly} value="review">
{t('can_review')}
</option>
<div className="pull-right add-collaborator-controls">
<Select
dataTestId="add-collaborator-select"
items={privilegeOptions}
itemToKey={item => item.key}
itemToString={item => item.label}
itemToSubtitle={item => item.description || ''}
itemToDisabled={item => readOnly && item.key === 'readAndWrite'}
selected={privilegeOptions.find(
option => option.key === privileges
)}
<option value="readOnly">{t('can_view')}</option>
</OLFormSelect>
<span>&nbsp;&nbsp;</span>
onSelectedItemChanged={item => setPrivileges(item.key)}
/>
<ClickableElementEnhancer
as={OLButton}
onClick={handleSubmit}

View File

@@ -6,13 +6,13 @@ export default function MemberPrivileges({ privileges }) {
switch (privileges) {
case 'readAndWrite':
return t('can_edit')
return t('editor')
case 'readOnly':
return t('read_only')
return t('viewer')
case 'review':
return t('can_review')
return t('reviewer')
default:
return null

View File

@@ -188,6 +188,7 @@ export default function SelectCollaborators({
))}
<input
data-testid="collaborator-email-input"
{...getInputProps(
getDropdownProps({
className: classnames('input', {

View File

@@ -52,6 +52,8 @@ export type SelectProps<T> = {
loading?: boolean
// Show a checkmark next to the selected item
selectedIcon?: boolean
// testId for the input element
dataTestId?: string
}
export const Select = <T,>({
@@ -70,6 +72,7 @@ export const Select = <T,>({
optionalLabel = false,
loading = false,
selectedIcon = false,
dataTestId,
}: SelectProps<T>) => {
const [selectedItem, setSelectedItem] = useState<T | undefined | null>(
defaultItem
@@ -247,6 +250,7 @@ export const Select = <T,>({
</Form.Label>
) : null}
<FormControl
data-testid={dataTestId}
{...getToggleButtonProps({
disabled,
onKeyDown,

View File

@@ -195,4 +195,15 @@
max-width: 175px;
}
}
.add-collaborator-controls {
display: flex;
align-items: center;
justify-content: flex-end;
gap: var(--spacing-03);
.select-wrapper {
max-width: 130px;
}
}
}

View File

@@ -244,15 +244,12 @@
"by_joining_labs": "By joining Labs, you agree to receive occasional emails and updates from Overleaf—for example, to request your feedback. You also agree to our <0>terms of service</0> and <1>privacy notice</1>.",
"by_registering_you_agree_to_our_terms_of_service": "By registering, you agree to our <0>terms of service</0> and <1>privacy notice</1>.",
"by_subscribing_you_agree_to_our_terms_of_service": "By subscribing, you agree to our <0>terms of service</0>.",
"can_edit": "Can edit",
"can_edit_content": "Can edit content",
"can_link_institution_email_acct_to_institution_acct": "You can now link your <b>__email__</b> <b>__appName__</b> account to your <b>__institutionName__</b> institutional account.",
"can_link_institution_email_by_clicking": "You can link your <b>__email__</b> <b>__appName__</b> account to your <b>__institutionName__</b> account by clicking <b>__clickText__</b>.",
"can_link_institution_email_to_login": "You can link your <b>__email__</b> <b>__appName__</b> account to your <b>__institutionName__</b> account, which will allow you to log in to <b>__appName__</b> through your institution and will reconfirm your institutional email address.",
"can_link_your_institution_acct_2": "You can now <0>link</0> your <0>__appName__</0> account to your <0>__institutionName__</0> institutional account.",
"can_now_relink_dropbox": "You can now <0>relink your Dropbox account</0>.",
"can_review": "Can review",
"can_view": "Can view",
"can_view_content": "Can view content",
"cancel": "Cancel",
"cancel_add_on": "Cancel add-on",
@@ -349,6 +346,7 @@
"column_width_is_x_click_to_resize": "Column width is __width__. Click to resize",
"comment": "Comment",
"comment_only": "Comment only",
"comment_only_upgrade_for_track_changes": "Comment only. Upgrade for track changes.",
"common": "Common",
"common_causes_of_compile_timeouts_include": "Common causes of compile timeouts include",
"commons_plan_tooltip": "Youre on the __plan__ plan because of your affiliation with __institution__. Click to find out how to make the most of your Overleaf premium features.",
@@ -1689,7 +1687,6 @@
"read_lines_from_path": "Read lines from __path__",
"read_more": "Read more",
"read_more_about_free_compile_timeouts_servers": "Read more about changes to free compile timeouts and servers",
"read_only": "Read only",
"read_only_token": "Read-Only Token",
"read_write_token": "Read-Write Token",
"ready_to_join_x": "Youre ready to join __inviterName__",

View File

@@ -638,8 +638,9 @@ describe('<ShareProjectModal/>', function () {
},
})
const privilegesElement = screen.getByDisplayValue('Can edit')
fireEvent.change(privilegesElement, { target: { value: 'readOnly' } })
const user = userEvent.setup()
await user.click(screen.getByTestId('add-collaborator-select'))
await user.click(screen.getByText('Viewer'))
const submitButton = screen.getByRole('button', { name: 'Invite' })
await userEvent.click(submitButton)
@@ -691,11 +692,16 @@ describe('<ShareProjectModal/>', 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
const user = userEvent.setup()
await user.click(screen.getByTestId('add-collaborator-select'))
const editorOption = screen.getByText('Editor').closest('button')
const reviewerOption = screen.getByText('Reviewer').closest('button')
const viewerOption = screen.getByText('Viewer').closest('button')
expect(editorOption.classList.contains('disabled')).to.be.true
expect(reviewerOption.classList.contains('disabled')).to.be.false
expect(viewerOption.classList.contains('disabled')).to.be.false
screen.getByText(
/Upgrade to add more editors and access collaboration features like track changes and full project history/
@@ -722,11 +728,17 @@ describe('<ShareProjectModal/>', 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
const user = userEvent.setup()
await user.click(screen.getByTestId('add-collaborator-select'))
const editorOption = screen.getByText('Editor').closest('button')
const reviewerOption = screen.getByText('Reviewer').closest('button')
const viewerOption = screen.getByText('Viewer').closest('button')
expect(editorOption.classList.contains('disabled')).to.be.true
expect(reviewerOption.classList.contains('disabled')).to.be.false
expect(viewerOption.classList.contains('disabled')).to.be.false
screen.getByText(
/Upgrade to add more editors and access collaboration features like track changes and full project history/