diff --git a/services/web/frontend/js/features/editor-floating-menu/components/tracked-changes-actions.tsx b/services/web/frontend/js/features/editor-floating-menu/components/tracked-changes-actions.tsx index ae38eeb019..0b2172757a 100644 --- a/services/web/frontend/js/features/editor-floating-menu/components/tracked-changes-actions.tsx +++ b/services/web/frontend/js/features/editor-floating-menu/components/tracked-changes-actions.tsx @@ -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 } diff --git a/services/web/frontend/js/features/review-panel/components/review-tooltip-menu.tsx b/services/web/frontend/js/features/review-panel/components/review-tooltip-menu.tsx index 475c3e09d9..cd070d46dc 100644 --- a/services/web/frontend/js/features/review-panel/components/review-tooltip-menu.tsx +++ b/services/web/frontend/js/features/review-panel/components/review-tooltip-menu.tsx @@ -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({ diff --git a/services/web/frontend/js/features/source-editor/hooks/use-context-menu-items.tsx b/services/web/frontend/js/features/source-editor/hooks/use-context-menu-items.tsx index 61a1dbed83..c1a3bf56d5 100644 --- a/services/web/frontend/js/features/source-editor/hooks/use-context-menu-items.tsx +++ b/services/web/frontend/js/features/source-editor/hooks/use-context-menu-items.tsx @@ -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), diff --git a/services/web/test/frontend/features/editor-floating-menu/editor-floating-menu.spec.tsx b/services/web/test/frontend/features/editor-floating-menu/editor-floating-menu.spec.tsx index 58afd47870..5182c4474f 100644 --- a/services/web/test/frontend/features/editor-floating-menu/editor-floating-menu.spec.tsx +++ b/services/web/test/frontend/features/editor-floating-menu/editor-floating-menu.spec.tsx @@ -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('', 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('', function () { docOptions: { rangesOptions: { changes, getChanges, removeChangeIds }, }, + permissionsLevel, }) cy.mount( @@ -269,4 +273,16 @@ describe('', 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') + }) + }) }) diff --git a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx index f6eafbf0bf..4aecc39e89 100644 --- a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx +++ b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx @@ -684,6 +684,69 @@ describe('', function () { }) }) +describe(' 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( + + + + + + ) + + 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(' in mini mode', function () { function render({ comments = [], changes = [], threads = {} }: any) { window.metaAttributesCache.set('ol-preventCompileOnLoad', true) diff --git a/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts b/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts index fd19ce4f85..a5811a7332 100644 --- a/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts +++ b/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts @@ -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, } }