Merge pull request #18654 from overleaf/mj-web-duplicate-threads

[web] Ensure single doc pointing to comment when reverting

GitOrigin-RevId: e86e566e1b21eed18bb08b285befcab0e740ec45
This commit is contained in:
Mathias Jakobsen 2024-06-07 12:07:11 +01:00 committed by Copybot
parent 304f572f9c
commit 0f869f9059
3 changed files with 95 additions and 5 deletions

View file

@ -97,6 +97,18 @@ async function getResolvedThreadIds(projectId) {
return body.resolvedThreadIds return body.resolvedThreadIds
} }
async function duplicateCommentThreads(projectId, threads) {
return await fetchJson(
chatApiUrl(`/project/${projectId}/duplicate-comment-threads`),
{
method: 'POST',
json: {
threads,
},
}
)
}
function chatApiUrl(path) { function chatApiUrl(path) {
return new URL(path, settings.apis.chat.internal_url) return new URL(path, settings.apis.chat.internal_url)
} }
@ -113,6 +125,7 @@ module.exports = {
editMessage: callbackify(editMessage), editMessage: callbackify(editMessage),
deleteMessage: callbackify(deleteMessage), deleteMessage: callbackify(deleteMessage),
getResolvedThreadIds: callbackify(getResolvedThreadIds), getResolvedThreadIds: callbackify(getResolvedThreadIds),
duplicateCommentThreads: callbackify(duplicateCommentThreads),
promises: { promises: {
getThreads, getThreads,
destroyProject, destroyProject,
@ -125,5 +138,6 @@ module.exports = {
editMessage, editMessage,
deleteMessage, deleteMessage,
getResolvedThreadIds, getResolvedThreadIds,
duplicateCommentThreads,
}, },
} }

View file

@ -9,6 +9,9 @@ const { callbackifyAll } = require('@overleaf/promise-utils')
const { fetchJson } = require('@overleaf/fetch-utils') const { fetchJson } = require('@overleaf/fetch-utils')
const ProjectLocator = require('../Project/ProjectLocator') const ProjectLocator = require('../Project/ProjectLocator')
const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler') const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
const ChatApiHandler = require('../Chat/ChatApiHandler')
const DocstoreManager = require('../Docstore/DocstoreManager')
const logger = require('@overleaf/logger')
const RestoreManager = { const RestoreManager = {
async restoreFileFromV2(userId, projectId, version, pathname) { async restoreFileFromV2(userId, projectId, version, pathname) {
@ -107,12 +110,60 @@ const RestoreManager = {
pathname pathname
) )
const documentCommentIds = new Set(
ranges.comments?.map(({ op: { t } }) => t)
)
await DocumentUpdaterHandler.promises.flushProjectToMongo(projectId)
const docsWithRanges =
await DocstoreManager.promises.getAllRanges(projectId)
const nonOrphanedThreadIds = new Set()
for (const { ranges } of docsWithRanges) {
for (const comment of ranges.comments ?? []) {
nonOrphanedThreadIds.add(comment.op.t)
}
}
const commentIdsToDuplicate = Array.from(documentCommentIds).filter(id =>
nonOrphanedThreadIds.has(id)
)
const newRanges = { changes: ranges.changes, comments: [] }
if (commentIdsToDuplicate.length > 0) {
const { newThreads: newCommentIds } =
await ChatApiHandler.promises.duplicateCommentThreads(
projectId,
commentIdsToDuplicate
)
logger.debug({ mapping: newCommentIds }, 'replacing comment threads')
for (const comment of ranges.comments ?? []) {
if (Object.prototype.hasOwnProperty.call(newCommentIds, comment.op.t)) {
const result = newCommentIds[comment.op.t]
if (result.error) {
// We couldn't duplicate the thread, so we need to delete it from
// the resulting ranges.
continue
}
// We have a new id for this comment thread
comment.op.t = result.duplicateId
newRanges.comments.push(comment)
}
}
} else {
newRanges.comments = ranges.comments
}
return await EditorController.promises.addDocWithRanges( return await EditorController.promises.addDocWithRanges(
projectId, projectId,
parentFolderId, parentFolderId,
basename, basename,
importInfo.lines, importInfo.lines,
ranges, newRanges,
'revert', 'revert',
userId userId
) )

View file

@ -24,7 +24,13 @@ describe('RestoreManager', function () {
}), }),
'../Project/ProjectLocator': (this.ProjectLocator = { promises: {} }), '../Project/ProjectLocator': (this.ProjectLocator = { promises: {} }),
'../DocumentUpdater/DocumentUpdaterHandler': '../DocumentUpdater/DocumentUpdaterHandler':
(this.DocumentUpdaterHandler = { promises: {} }), (this.DocumentUpdaterHandler = {
promises: { flushProjectToMongo: sinon.stub().resolves() },
}),
'../Docstore/DocstoreManager': (this.DocstoreManager = {
promises: {},
}),
'../Chat/ChatApiHandler': (this.ChatApiHandler = { promises: {} }),
}, },
}) })
this.user_id = 'mock-user-id' this.user_id = 'mock-user-id'
@ -297,7 +303,27 @@ describe('RestoreManager', function () {
describe("when reverting a file that doesn't current exist", function () { describe("when reverting a file that doesn't current exist", function () {
beforeEach(async function () { beforeEach(async function () {
this.pathname = 'foo.tex' this.pathname = 'foo.tex'
this.comments = [
(this.comment = { op: { t: 'comment-1', p: 0, c: 'foo' } }),
]
this.remappedComments = [{ op: { t: 'comment-2', p: 0, c: 'foo' } }]
this.ProjectLocator.promises.findElementByPath = sinon.stub().rejects() this.ProjectLocator.promises.findElementByPath = sinon.stub().rejects()
this.DocstoreManager.promises.getAllRanges = sinon.stub().resolves([
{
ranges: {
comments: [this.comment],
},
},
])
this.ChatApiHandler.promises.duplicateCommentThreads = sinon
.stub()
.resolves({
newThreads: {
'comment-1': {
duplicateId: 'comment-2',
},
},
})
this.tracked_changes = [ this.tracked_changes = [
{ {
op: { pos: 4, i: 'bar' }, op: { pos: 4, i: 'bar' },
@ -308,13 +334,12 @@ describe('RestoreManager', function () {
metadata: { ts: '2024-01-01T00:00:00.000Z', user_id: 'user-2' }, metadata: { ts: '2024-01-01T00:00:00.000Z', user_id: 'user-2' },
}, },
] ]
this.comments = [{ op: { t: 'comment-1', p: 0, c: 'foo' } }]
this.FileSystemImportManager.promises.importFile = sinon this.FileSystemImportManager.promises.importFile = sinon
.stub() .stub()
.resolves({ type: 'doc', lines: ['foo', 'bar', 'baz'] }) .resolves({ type: 'doc', lines: ['foo', 'bar', 'baz'] })
this.RestoreManager.promises._getRangesFromHistory = sinon this.RestoreManager.promises._getRangesFromHistory = sinon
.stub() .stub()
.resolves({ changes: this.tracked_changes, comment: this.comments }) .resolves({ changes: this.tracked_changes, comments: this.comments })
this.EditorController.promises.addDocWithRanges = sinon this.EditorController.promises.addDocWithRanges = sinon
.stub() .stub()
.resolves( .resolves(
@ -336,7 +361,7 @@ describe('RestoreManager', function () {
this.folder_id, this.folder_id,
'foo.tex', 'foo.tex',
['foo', 'bar', 'baz'], ['foo', 'bar', 'baz'],
{ changes: this.tracked_changes, comment: this.comments } { changes: this.tracked_changes, comments: this.remappedComments }
) )
}) })