mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #18637 from overleaf/mj-chat-duplicate-threads
[chat] Add endpoint for duplicating comment threads GitOrigin-RevId: 0b3fb1b836150ccb6d213ab2bba6ce7ff6c69b4a
This commit is contained in:
parent
f0eba8e742
commit
304f572f9c
6 changed files with 199 additions and 0 deletions
|
@ -82,6 +82,10 @@ export async function destroyProject(context) {
|
||||||
return await callMessageHttpController(context, _destroyProject)
|
return await callMessageHttpController(context, _destroyProject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function duplicateCommentThreads(context) {
|
||||||
|
return await callMessageHttpController(context, _duplicateCommentThreads)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getStatus(context) {
|
export async function getStatus(context) {
|
||||||
const message = 'chat is alive'
|
const message = 'chat is alive'
|
||||||
context.res.status(200).setBody(message)
|
context.res.status(200).setBody(message)
|
||||||
|
@ -254,3 +258,29 @@ async function _getMessages(clientThreadId, req, res) {
|
||||||
logger.debug({ projectId, messages }, 'got messages')
|
logger.debug({ projectId, messages }, 'got messages')
|
||||||
res.status(200).setBody(messages)
|
res.status(200).setBody(messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function _duplicateCommentThreads(req, res) {
|
||||||
|
const { projectId } = req.params
|
||||||
|
const { threads } = req.body
|
||||||
|
const result = {}
|
||||||
|
for (const id of threads) {
|
||||||
|
logger.debug({ projectId, thread: id }, 'duplicating thread')
|
||||||
|
try {
|
||||||
|
const { oldRoom, newRoom } = await ThreadManager.duplicateThread(
|
||||||
|
projectId,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
await MessageManager.duplicateRoomToOtherRoom(oldRoom._id, newRoom._id)
|
||||||
|
result[id] = { duplicateId: newRoom.thread_id }
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ThreadManager.MissingThreadError) {
|
||||||
|
// Expected error when the comment has been deleted prior to duplication
|
||||||
|
result[id] = { error: 'not found' }
|
||||||
|
} else {
|
||||||
|
logger.err({ error }, 'error duplicating thread')
|
||||||
|
result[id] = { error: 'unknown' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.json({ newThreads: result })
|
||||||
|
}
|
||||||
|
|
|
@ -89,3 +89,16 @@ function _ensureIdsAreObjectIds(query) {
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function duplicateRoomToOtherRoom(sourceRoomId, targetRoomId) {
|
||||||
|
const sourceMessages = await findAllMessagesInRooms([sourceRoomId])
|
||||||
|
const targetMessages = sourceMessages.map(comment => {
|
||||||
|
return _ensureIdsAreObjectIds({
|
||||||
|
room_id: targetRoomId,
|
||||||
|
content: comment.content,
|
||||||
|
timestamp: comment.timestamp,
|
||||||
|
user_id: comment.user_id,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await db.messages.insertMany(targetMessages)
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { db, ObjectId } from '../../mongodb.js'
|
import { db, ObjectId } from '../../mongodb.js'
|
||||||
|
|
||||||
|
export class MissingThreadError extends Error {}
|
||||||
|
|
||||||
export const GLOBAL_THREAD = 'GLOBAL'
|
export const GLOBAL_THREAD = 'GLOBAL'
|
||||||
|
|
||||||
export async function findOrCreateThread(projectId, threadId) {
|
export async function findOrCreateThread(projectId, threadId) {
|
||||||
|
@ -124,3 +126,23 @@ export async function getResolvedThreadIds(projectId) {
|
||||||
.toArray()
|
.toArray()
|
||||||
return resolvedThreadIds
|
return resolvedThreadIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function duplicateThread(projectId, threadId) {
|
||||||
|
const room = await db.rooms.findOne({
|
||||||
|
project_id: new ObjectId(projectId),
|
||||||
|
thread_id: new ObjectId(threadId),
|
||||||
|
})
|
||||||
|
if (!room) {
|
||||||
|
throw new MissingThreadError('Trying to duplicate a non-existent thread')
|
||||||
|
}
|
||||||
|
const newRoom = {
|
||||||
|
project_id: room.project_id,
|
||||||
|
thread_id: new ObjectId(),
|
||||||
|
}
|
||||||
|
if (room.resolved) {
|
||||||
|
newRoom.resolved = room.resolved
|
||||||
|
}
|
||||||
|
const confirmation = await db.rooms.insertOne(newRoom)
|
||||||
|
newRoom._id = confirmation.insertedId
|
||||||
|
return { oldRoom: room, newRoom }
|
||||||
|
}
|
||||||
|
|
|
@ -303,6 +303,37 @@ paths:
|
||||||
description: chat is alive
|
description: chat is alive
|
||||||
operationId: getStatus
|
operationId: getStatus
|
||||||
description: Check that the Chat service is alive
|
description: Check that the Chat service is alive
|
||||||
|
'/project/{projectId}/duplicate-comment-threads':
|
||||||
|
parameters:
|
||||||
|
- schema:
|
||||||
|
type: string
|
||||||
|
name: projectId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
post:
|
||||||
|
summary: Duplicate comment threads
|
||||||
|
operationId: duplicateCommentThreads
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
threads:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
newThreads:
|
||||||
|
type: object
|
||||||
|
description: Mapping of old thread ids to their duplicated thread ids
|
||||||
|
description: Duplicate a list of comment threads
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
Message:
|
Message:
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { ObjectId } from '../../../app/js/mongodb.js'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
|
||||||
|
import * as ChatClient from './helpers/ChatClient.js'
|
||||||
|
import * as ChatApp from './helpers/ChatApp.js'
|
||||||
|
|
||||||
|
const user1Id = new ObjectId().toString()
|
||||||
|
const user2Id = new ObjectId().toString()
|
||||||
|
|
||||||
|
async function createCommentThread(projectId, threadId = new ObjectId()) {
|
||||||
|
const { response: response1 } = await ChatClient.sendMessage(
|
||||||
|
projectId,
|
||||||
|
threadId.toString(),
|
||||||
|
user1Id,
|
||||||
|
'message 1'
|
||||||
|
)
|
||||||
|
expect(response1.statusCode).to.equal(201)
|
||||||
|
const { response: response2 } = await ChatClient.sendMessage(
|
||||||
|
projectId,
|
||||||
|
threadId,
|
||||||
|
user2Id,
|
||||||
|
'message 2'
|
||||||
|
)
|
||||||
|
expect(response2.statusCode).to.equal(201)
|
||||||
|
return threadId.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Cloning comment threads', async function () {
|
||||||
|
const projectId = new ObjectId().toString()
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
await ChatApp.ensureRunning()
|
||||||
|
this.thread1Id = await createCommentThread(projectId)
|
||||||
|
this.thread2Id = await createCommentThread(projectId)
|
||||||
|
this.thread3Id = await createCommentThread(projectId)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with non-orphaned threads', async function () {
|
||||||
|
before(async function () {
|
||||||
|
const {
|
||||||
|
response: { body: result, statusCode },
|
||||||
|
} = await ChatClient.duplicateCommentThreads(projectId, [this.thread3Id])
|
||||||
|
this.result = result
|
||||||
|
expect(statusCode).to.equal(200)
|
||||||
|
expect(this.result).to.have.property('newThreads')
|
||||||
|
this.newThreadId = this.result.newThreads[this.thread3Id].duplicateId
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should duplicate threads', function () {
|
||||||
|
expect(this.result.newThreads).to.have.property(this.thread3Id)
|
||||||
|
expect(this.result.newThreads[this.thread3Id]).to.have.property(
|
||||||
|
'duplicateId'
|
||||||
|
)
|
||||||
|
expect(this.result.newThreads[this.thread3Id].duplicateId).to.not.equal(
|
||||||
|
this.thread3Id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not duplicate other threads threads', function () {
|
||||||
|
expect(this.result.newThreads).to.not.have.property(this.thread1Id)
|
||||||
|
expect(this.result.newThreads).to.not.have.property(this.thread2Id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should duplicate the messages in the thread', async function () {
|
||||||
|
const {
|
||||||
|
response: { body: threads },
|
||||||
|
} = await ChatClient.getThreads(projectId)
|
||||||
|
function ignoreId(comment) {
|
||||||
|
return {
|
||||||
|
...comment,
|
||||||
|
id: undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(threads[this.thread3Id].messages.map(ignoreId)).to.deep.equal(
|
||||||
|
threads[this.newThreadId].messages.map(ignoreId)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should have two separate unlinked threads', async function () {
|
||||||
|
await ChatClient.sendMessage(
|
||||||
|
projectId,
|
||||||
|
this.newThreadId,
|
||||||
|
user1Id,
|
||||||
|
'third message'
|
||||||
|
)
|
||||||
|
const {
|
||||||
|
response: { body: threads },
|
||||||
|
} = await ChatClient.getThreads(projectId)
|
||||||
|
expect(threads[this.thread3Id].messages.length).to.equal(2)
|
||||||
|
expect(threads[this.newThreadId].messages.length).to.equal(3)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -144,3 +144,13 @@ export async function destroyProject(projectId) {
|
||||||
url: `/project/${projectId}`,
|
url: `/project/${projectId}`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function duplicateCommentThreads(projectId, threads) {
|
||||||
|
return await asyncRequest({
|
||||||
|
method: 'post',
|
||||||
|
url: `/project/${projectId}/duplicate-comment-threads`,
|
||||||
|
json: {
|
||||||
|
threads,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue