Merge pull request #23673 from overleaf/em-reviewer-role-delete-replies

Let reviewers delete their own comments

GitOrigin-RevId: bd5341a917a3e886dc573d7e42d12343da6700cd
This commit is contained in:
Eric Mc Sween 2025-02-18 08:57:32 -05:00 committed by Copybot
parent a34ebd3c7e
commit 79bb2e3d40
6 changed files with 82 additions and 2 deletions

View file

@ -74,6 +74,10 @@ export async function deleteMessage(context) {
return await callMessageHttpController(context, _deleteMessage)
}
export async function deleteUserMessage(context) {
return await callMessageHttpController(context, _deleteUserMessage)
}
export async function getResolvedThreadIds(context) {
return await callMessageHttpController(context, _getResolvedThreadIds)
}
@ -190,6 +194,13 @@ const _deleteMessage = async (req, res) => {
res.status(204)
}
const _deleteUserMessage = async (req, res) => {
const { projectId, threadId, userId, messageId } = req.params
const room = await ThreadManager.findOrCreateThread(projectId, threadId)
await MessageManager.deleteUserMessage(userId, room._id, messageId)
res.status(204)
}
const _getResolvedThreadIds = async (req, res) => {
const { projectId } = req.params
const resolvedThreadIds = await ThreadManager.getResolvedThreadIds(projectId)

View file

@ -77,6 +77,14 @@ export async function deleteMessage(roomId, messageId) {
await db.messages.deleteOne(query)
}
export async function deleteUserMessage(userId, roomId, messageId) {
await db.messages.deleteOne({
_id: new ObjectId(messageId),
user_id: new ObjectId(userId),
room_id: new ObjectId(roomId),
})
}
function _ensureIdsAreObjectIds(query) {
if (query.user_id && !(query.user_id instanceof ObjectId)) {
query.user_id = new ObjectId(query.user_id)

View file

@ -177,6 +177,34 @@ paths:
'204':
description: No Content
description: 'Delete message with Message ID provided, from the Thread with ThreadID and ProjectID provided'
'/project/{projectId}/thread/{threadId}/user/{userId}/messages/{messageId}':
parameters:
- schema:
type: string
name: projectId
in: path
required: true
- schema:
type: string
name: threadId
in: path
required: true
- schema:
type: string
name: userId
in: path
required: true
- schema:
type: string
name: messageId
in: path
required: true
delete:
summary: Delete message written by a given user
operationId: deleteUserMessage
responses:
'204':
description: No Content
'/project/{projectId}/thread/{threadId}/resolve':
parameters:
- schema:

View file

@ -90,6 +90,15 @@ async function deleteMessage(projectId, threadId, messageId) {
)
}
async function deleteUserMessage(projectId, threadId, userId, messageId) {
await fetchNothing(
chatApiUrl(
`/project/${projectId}/thread/${threadId}/user/${userId}/messages/${messageId}`
),
{ method: 'DELETE' }
)
}
async function getResolvedThreadIds(projectId) {
const body = await fetchJson(
chatApiUrl(`/project/${projectId}/resolved-thread-ids`)
@ -134,6 +143,7 @@ module.exports = {
deleteThread: callbackify(deleteThread),
editMessage: callbackify(editMessage),
deleteMessage: callbackify(deleteMessage),
deleteUserMessage: callbackify(deleteUserMessage),
getResolvedThreadIds: callbackify(getResolvedThreadIds),
duplicateCommentThreads: callbackify(duplicateCommentThreads),
generateThreadData: callbackify(generateThreadData),
@ -148,6 +158,7 @@ module.exports = {
deleteThread,
editMessage,
deleteMessage,
deleteUserMessage,
getResolvedThreadIds,
duplicateCommentThreads,
generateThreadData,

View file

@ -12,6 +12,7 @@ import {
ThreadId,
} from '../../../../../types/review-panel/review-panel'
import { useModalsContext } from '@/features/ide-react/context/modals-context'
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
import { useTranslation } from 'react-i18next'
import { debugConsole } from '@/utils/debugging'
@ -29,11 +30,13 @@ export const ReviewPanelComment = memo<{
resolveThread,
editMessage,
deleteMessage,
deleteOwnMessage,
deleteThread,
addMessage,
} = useThreadsActionsContext()
const { showGenericMessageModal } = useModalsContext()
const { t } = useTranslation()
const permissions = usePermissionsContext()
const [processing, setProcessing] = useState(false)
@ -74,7 +77,13 @@ export const ReviewPanelComment = memo<{
async (commentId: CommentId) => {
setProcessing(true)
try {
await deleteMessage(comment.op.t, commentId)
if (permissions.write) {
// Owners and editors can delete any message
await deleteMessage(comment.op.t, commentId)
} else {
// Reviewers can only delete their own messages
await deleteOwnMessage(comment.op.t, commentId)
}
} catch (err) {
debugConsole.error(err)
showGenericMessageModal(
@ -85,7 +94,14 @@ export const ReviewPanelComment = memo<{
setProcessing(false)
}
},
[comment.op.t, deleteMessage, showGenericMessageModal, t]
[
comment.op.t,
deleteMessage,
deleteOwnMessage,
showGenericMessageModal,
t,
permissions.write,
]
)
const handleDeleteThread = useCallback(

View file

@ -38,6 +38,7 @@ type ThreadsActions = {
content: string
) => Promise<void>
deleteMessage: (threadId: ThreadId, commentId: CommentId) => Promise<void>
deleteOwnMessage: (threadId: ThreadId, commentId: CommentId) => Promise<void>
}
const ThreadsActionsContext = createContext<ThreadsActions | undefined>(
@ -288,6 +289,11 @@ export const ThreadsProvider: FC = ({ children }) => {
`/project/${projectId}/thread/${threadId}/messages/${commentId}`
)
},
async deleteOwnMessage(threadId: ThreadId, commentId: CommentId) {
await deleteJSON(
`/project/${projectId}/thread/${threadId}/own-messages/${commentId}`
)
},
}),
[currentDocument, projectId]
)