[chat] clone comment threads when cloning project with ranges (#32852)

* [project-history] add best effort flush when cloning project

* [web] update labels in clone project modal for admins

* [project-history] do not shadow history flush failure

* [web] fix accessible label for 'Add comment' button

* [chat] clone comment threads when cloning project with ranges

GitOrigin-RevId: ef30204c8a94b3d6204d56dcca2f62a46319996b
This commit is contained in:
Jakob Ackermann
2026-04-17 07:44:09 +02:00
committed by Copybot
parent 933c0c2943
commit 6117feef1b
9 changed files with 177 additions and 73 deletions

View File

@@ -3,6 +3,7 @@ import * as MessageManager from './MessageManager.js'
import * as MessageFormatter from './MessageFormatter.js'
import * as ThreadManager from '../Threads/ThreadManager.js'
import { ObjectId } from '../../mongodb.js'
import { promiseMapWithLimit } from '@overleaf/promise-utils'
const DEFAULT_MESSAGE_LIMIT = 50
const MAX_MESSAGE_LENGTH = 10 * 1024 // 10kb, about 1,500 words
@@ -114,6 +115,10 @@ export async function generateThreadData(context) {
return await callMessageHttpController(context, _generateThreadData)
}
export async function cloneCommentThreads(context) {
return await callMessageHttpController(context, _cloneCommentThreads)
}
export async function getStatus(context) {
const message = 'chat is alive'
context.res.status(200).setBody(message)
@@ -436,3 +441,16 @@ async function _duplicateCommentThreads(req, res) {
}
res.json({ newThreads: result })
}
async function _cloneCommentThreads(req, res) {
const { projectId: sourceProjectId } = req.params
const { targetProjectId } = req.body
const rooms = await ThreadManager.cloneThreads(
sourceProjectId,
targetProjectId
)
await promiseMapWithLimit(10, rooms, async ({ from, to }) => {
await MessageManager.duplicateRoomToOtherRoom(from, to)
})
res.status(204)
}

View File

@@ -4,6 +4,29 @@ export class MissingThreadError extends Error {}
export const GLOBAL_THREAD = 'GLOBAL'
/**
* @param {string} sourceProjectId
* @param {string} targetProjectId
* @return {Promise<{from:ObjectId, to: ObjectId}[]>}
*/
export async function cloneThreads(sourceProjectId, targetProjectId) {
sourceProjectId = new ObjectId(sourceProjectId)
targetProjectId = new ObjectId(targetProjectId)
const rooms = await db.rooms
.find({ project_id: sourceProjectId, thread_id: { $exists: true } })
.toArray()
const mapping = []
await db.rooms.insertMany(
rooms.map(room => {
const from = room._id
const to = new ObjectId()
mapping.push({ from, to })
return { ...room, _id: to, project_id: targetProjectId }
})
)
return mapping
}
export async function findOrCreateThread(projectId, threadId) {
let query, update
projectId = new ObjectId(projectId.toString())

View File

@@ -490,6 +490,28 @@ paths:
schema:
type: object
description: Load threads and generate a json blob containing all messages in all the threads
"/project/{projectId}/clone-comment-threads":
parameters:
- schema:
type: string
name: projectId
in: path
required: true
post:
summary: Clone a projects comment threads
operationId: cloneCommentThreads
requestBody:
content:
application/json:
schema:
type: object
properties:
targetProjectId:
type: string
responses:
"204":
description: OK
description: Clone a projects comment threads
components:
schemas:
Message:

View File

@@ -19,6 +19,7 @@
"@overleaf/logger": "*",
"@overleaf/metrics": "*",
"@overleaf/mongo-utils": "*",
"@overleaf/promise-utils": "*",
"@overleaf/settings": "*",
"async": "^3.2.5",
"body-parser": "1.20.4",