Просмотр исходного кода

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

[web] Hide bulk track change actions for reviewers

GitOrigin-RevId: f636f4c82fdfb43a6dedf1abbb3cc53805eab7f8
Mathias Jakobsen 1 месяц назад
Родитель
Сommit
fe52c3df12

+ 1 - 1
services/web/frontend/js/features/editor-floating-menu/components/tracked-changes-actions.tsx

@@ -11,7 +11,7 @@ const TrackedChangesActions: FC = () => {
   const { changesInSelection, acceptChangesHandler, rejectChangesHandler } =
   const { changesInSelection, acceptChangesHandler, rejectChangesHandler } =
     useTrackedChangesActions()
     useTrackedChangesActions()
 
 
-  if (!permissions.comment || changesInSelection.length === 0) {
+  if (!permissions.write || changesInSelection.length === 0) {
     return null
     return null
   }
   }
 
 

+ 3 - 1
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 view = useCodeMirrorViewContext()
     const state = useCodeMirrorStateContext()
     const state = useCodeMirrorStateContext()
     const { reviewPanelOpen } = useLayoutContext()
     const { reviewPanelOpen } = useLayoutContext()
+    const permissions = usePermissionsContext()
     const ranges = useRangesContext()
     const ranges = useRangesContext()
     const { acceptChanges, rejectChanges } = useRangesActionsContext()
     const { acceptChanges, rejectChanges } = useRangesActionsContext()
     const { showGenericConfirmModal } = useModalsContext()
     const { showGenericConfirmModal } = useModalsContext()
@@ -208,7 +209,8 @@ const ReviewTooltipMenuContent = memo<{ onAddComment: () => void }>(
       changesInSelection,
       changesInSelection,
     ])
     ])
 
 
-    const showChangesButtons = changesInSelection.length > 0
+    const showChangesButtons =
+      permissions.write && changesInSelection.length > 0
 
 
     useEffect(() => {
     useEffect(() => {
       view.requestMeasure({
       view.requestMeasure({

+ 2 - 2
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'),
         label: t('accept_selected_changes'),
         handler: handleAcceptChanges,
         handler: handleAcceptChanges,
         disabled: false,
         disabled: false,
-        show: permissions.comment && changesInSelection.length > 0,
+        show: permissions.write && changesInSelection.length > 0,
         shortcut: undefined,
         shortcut: undefined,
       },
       },
       {
       {
         label: t('reject_selected_changes'),
         label: t('reject_selected_changes'),
         handler: handleRejectChanges,
         handler: handleRejectChanges,
         disabled: false,
         disabled: false,
-        show: permissions.comment && changesInSelection.length > 0,
+        show: permissions.write && changesInSelection.length > 0,
         shortcut: undefined,
         shortcut: undefined,
       },
       },
     ].filter(item => item.show),
     ].filter(item => item.show),

+ 17 - 1
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 { TestContainer } from '../source-editor/helpers/test-container'
 import { docId } from '../source-editor/helpers/mock-doc'
 import { docId } from '../source-editor/helpers/mock-doc'
 import { useUserSettingsContext } from '@/shared/context/user-settings-context'
 import { useUserSettingsContext } from '@/shared/context/user-settings-context'
+import type { PermissionsLevel } from '@/features/ide-react/types/permissions'
 
 
 function FloatingMenuToggle() {
 function FloatingMenuToggle() {
   const { setUserSettings } = useUserSettingsContext()
   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-preventCompileOnLoad', true)
     window.metaAttributesCache.set('ol-splitTestVariants', {
     window.metaAttributesCache.set('ol-splitTestVariants', {
       'writefull-toolbar-migration': 'enabled',
       'writefull-toolbar-migration': 'enabled',
@@ -100,6 +103,7 @@ describe('<EditorFloatingMenu />', function () {
       docOptions: {
       docOptions: {
         rangesOptions: { changes, getChanges, removeChangeIds },
         rangesOptions: { changes, getChanges, removeChangeIds },
       },
       },
+      permissionsLevel,
     })
     })
 
 
     cy.mount(
     cy.mount(
@@ -269,4 +273,16 @@ describe('<EditorFloatingMenu />', function () {
       cy.get('.editor-floating-menu').should('exist')
       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')
+    })
+  })
 })
 })

+ 63 - 0
services/web/test/frontend/features/review-panel/review-panel.spec.tsx

@@ -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 () {
 describe('<ReviewPanel /> in mini mode', function () {
   function render({ comments = [], changes = [], threads = {} }: any) {
   function render({ comments = [], changes = [], threads = {} }: any) {
     window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
     window.metaAttributesCache.set('ol-preventCompileOnLoad', true)

+ 2 - 2
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 rootFolderId = '012345678901234567890123'
 export const mockScope = (
 export const mockScope = (
   content?: string,
   content?: string,
-  { docOptions = {}, permissions = {} }: any = {}
+  { docOptions = {}, permissions = {}, permissionsLevel = 'owner' }: any = {}
 ) => {
 ) => {
   return {
   return {
     editor: {
     editor: {
@@ -26,6 +26,6 @@ export const mockScope = (
     toggleTrackChangesForEveryone: cy.stub(),
     toggleTrackChangesForEveryone: cy.stub(),
     refreshResolvedCommentsDropdown: cy.stub(() => sleep(1000)),
     refreshResolvedCommentsDropdown: cy.stub(() => sleep(1000)),
     onlineUserCursorHighlights: {},
     onlineUserCursorHighlights: {},
-    permissionsLevel: 'owner',
+    permissionsLevel,
   }
   }
 }
 }