Merge pull request #34872 from overleaf/mj-bulk-review-permissions

[web] Hide bulk track change actions for reviewers

GitOrigin-RevId: f636f4c82fdfb43a6dedf1abbb3cc53805eab7f8
This commit is contained in:
Mathias Jakobsen
2026-06-24 08:05:53 +00:00
committed by Copybot
parent c625ce428e
commit fe52c3df12
6 changed files with 88 additions and 7 deletions
@@ -11,7 +11,7 @@ const TrackedChangesActions: FC = () => {
const { changesInSelection, acceptChangesHandler, rejectChangesHandler } =
useTrackedChangesActions()
if (!permissions.comment || changesInSelection.length === 0) {
if (!permissions.write || changesInSelection.length === 0) {
return null
}
@@ -138,6 +138,7 @@ const ReviewTooltipMenuContent = memo<{ onAddComment: () => void }>(
const view = useCodeMirrorViewContext()
const state = useCodeMirrorStateContext()
const { reviewPanelOpen } = useLayoutContext()
const permissions = usePermissionsContext()
const ranges = useRangesContext()
const { acceptChanges, rejectChanges } = useRangesActionsContext()
const { showGenericConfirmModal } = useModalsContext()
@@ -208,7 +209,8 @@ const ReviewTooltipMenuContent = memo<{ onAddComment: () => void }>(
changesInSelection,
])
const showChangesButtons = changesInSelection.length > 0
const showChangesButtons =
permissions.write && changesInSelection.length > 0
useEffect(() => {
view.requestMeasure({
@@ -281,14 +281,14 @@ export const useContextMenuItems = () => {
label: t('accept_selected_changes'),
handler: handleAcceptChanges,
disabled: false,
show: permissions.comment && changesInSelection.length > 0,
show: permissions.write && changesInSelection.length > 0,
shortcut: undefined,
},
{
label: t('reject_selected_changes'),
handler: handleRejectChanges,
disabled: false,
show: permissions.comment && changesInSelection.length > 0,
show: permissions.write && changesInSelection.length > 0,
shortcut: undefined,
},
].filter(item => item.show),
@@ -4,6 +4,7 @@ import { mockScope } from '../source-editor/helpers/mock-scope'
import { TestContainer } from '../source-editor/helpers/test-container'
import { docId } from '../source-editor/helpers/mock-doc'
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
import type { PermissionsLevel } from '@/features/ide-react/types/permissions'
function FloatingMenuToggle() {
const { setUserSettings } = useUserSettingsContext()
@@ -64,7 +65,9 @@ describe('<EditorFloatingMenu />', function () {
)
}
function mountEditorWithChanges() {
function mountEditorWithChanges({
permissionsLevel,
}: { permissionsLevel?: PermissionsLevel } = {}) {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
window.metaAttributesCache.set('ol-splitTestVariants', {
'writefull-toolbar-migration': 'enabled',
@@ -100,6 +103,7 @@ describe('<EditorFloatingMenu />', function () {
docOptions: {
rangesOptions: { changes, getChanges, removeChangeIds },
},
permissionsLevel,
})
cy.mount(
@@ -269,4 +273,16 @@ describe('<EditorFloatingMenu />', function () {
cy.get('.editor-floating-menu').should('exist')
})
})
describe('bulk tracked-change actions without write permission', function () {
it('hides the accept and reject controls for a reviewer', function () {
mountEditorWithChanges({ permissionsLevel: 'review' })
cy.get('.editor-floating-menu').within(() => {
cy.findByLabelText('Add comment').should('exist')
})
cy.findByLabelText('Accept selected changes').should('not.exist')
cy.findByLabelText('Reject selected changes').should('not.exist')
})
})
})
@@ -684,6 +684,69 @@ describe('<ReviewPanel />', function () {
})
})
describe('<ReviewPanel /> review tooltip without write permission', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.intercept('GET', '/project/*/changes/users', [])
cy.intercept('GET', '/project/*/threads', {})
const changes = [
{
metadata: {
user_id: USER_ID,
ts: new Date('2025-01-01T00:00:00.000Z'),
},
id: 'inserted-op-id',
op: { p: 166, t: 'inserted-op-id', i: 'introduction' },
},
{
metadata: {
user_id: USER_ID,
ts: new Date('2025-01-01T01:00:00.000Z'),
},
id: 'deleted-op-id',
op: { p: 110, t: 'deleted-op-id', d: 'beautiful ' },
},
]
const scope = mockScope(undefined, {
docOptions: { rangesOptions: { changes } },
permissionsLevel: 'review',
})
const project = mockProject({
projectOwner: { _id: USER_ID },
projectFeatures: { trackChanges: true, trackChangesVisible: true },
})
cy.mount(
<TestContainer className="rp-size-expanded">
<EditorProviders
scope={scope}
providers={{ ProjectProvider: makeProjectProvider(project) }}
>
<CodeMirrorEditor />
</EditorProviders>
</TestContainer>
)
cy.get('.cm-content').should('have.css', 'opacity', '1')
// Select a deletion and an insertion so the tooltip's bulk actions would appear
cy.findByText('\\maketitle').type(
'{home}{shift}' + '{downArrow}'.repeat(10),
{ scrollBehavior: false }
)
})
it('shows the comment action but hides accept and reject for a reviewer', function () {
cy.get('.review-tooltip-menu').should('exist')
cy.get('.review-tooltip-add-comment-button').should('exist')
cy.findByLabelText('Accept selected changes').should('not.exist')
cy.findByLabelText('Reject selected changes').should('not.exist')
})
})
describe('<ReviewPanel /> in mini mode', function () {
function render({ comments = [], changes = [], threads = {} }: any) {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
@@ -4,7 +4,7 @@ import { sleep } from '../../../helpers/sleep'
export const rootFolderId = '012345678901234567890123'
export const mockScope = (
content?: string,
{ docOptions = {}, permissions = {} }: any = {}
{ docOptions = {}, permissions = {}, permissionsLevel = 'owner' }: any = {}
) => {
return {
editor: {
@@ -26,6 +26,6 @@ export const mockScope = (
toggleTrackChangesForEveryone: cy.stub(),
refreshResolvedCommentsDropdown: cy.stub(() => sleep(1000)),
onlineUserCursorHighlights: {},
permissionsLevel: 'owner',
permissionsLevel,
}
}