[web] let docstore determine user ids of tracked changes (#26333)

* [docstore] add endpoint for getting user ids of tracked changes

* [web] let docstore determine user ids of tracked changes

GitOrigin-RevId: 8d0a131555aa827f7ff80690fedc1aca26cf0817
This commit is contained in:
Jakob Ackermann
2025-06-12 09:11:19 +02:00
committed by Copybot
parent 2e4b57bf81
commit 3862826589
6 changed files with 90 additions and 6 deletions

View File

@@ -50,6 +50,10 @@ app.param('doc_id', function (req, res, next, docId) {
app.get('/project/:project_id/doc-deleted', HttpController.getAllDeletedDocs)
app.get('/project/:project_id/doc', HttpController.getAllDocs)
app.get('/project/:project_id/ranges', HttpController.getAllRanges)
app.get(
'/project/:project_id/tracked-changes-user-ids',
HttpController.getTrackedChangesUserIds
)
app.get('/project/:project_id/has-ranges', HttpController.projectHasRanges)
app.get('/project/:project_id/doc/:doc_id', HttpController.getDoc)
app.get('/project/:project_id/doc/:doc_id/deleted', HttpController.isDocDeleted)

View File

@@ -132,6 +132,20 @@ const DocManager = {
return docs
},
async getTrackedChangesUserIds(projectId) {
const docs = await DocManager.getAllNonDeletedDocs(projectId, {
ranges: true,
})
const userIds = new Set()
for (const doc of docs) {
for (const change of doc.ranges?.changes || []) {
if (change.metadata.user_id === 'anonymous-user') continue
userIds.add(change.metadata.user_id)
}
}
return Array.from(userIds)
},
async projectHasRanges(projectId) {
const docs = await MongoManager.getProjectsDocs(projectId, {}, { _id: 1 })
const docIds = docs.map(doc => doc._id)

View File

@@ -83,6 +83,12 @@ async function getAllRanges(req, res) {
res.json(_buildDocsArrayView(projectId, docs))
}
async function getTrackedChangesUserIds(req, res) {
const { project_id: projectId } = req.params
const userIds = await DocManager.getTrackedChangesUserIds(projectId)
res.json(userIds)
}
async function projectHasRanges(req, res) {
const { project_id: projectId } = req.params
const projectHasRanges = await DocManager.projectHasRanges(projectId)
@@ -232,6 +238,7 @@ module.exports = {
getAllDocs: expressify(getAllDocs),
getAllDeletedDocs: expressify(getAllDeletedDocs),
getAllRanges: expressify(getAllRanges),
getTrackedChangesUserIds: expressify(getTrackedChangesUserIds),
projectHasRanges: expressify(projectHasRanges),
updateDoc: expressify(updateDoc),
patchDoc: expressify(patchDoc),

View File

@@ -24,26 +24,51 @@ describe('Getting all docs', function () {
{
_id: new ObjectId(),
lines: ['one', 'two', 'three'],
ranges: { mock: 'one' },
ranges: {
changes: [
{
id: new ObjectId().toString(),
metadata: { user_id: 'user-id-1' },
},
],
},
rev: 2,
},
{
_id: new ObjectId(),
lines: ['aaa', 'bbb', 'ccc'],
ranges: { mock: 'two' },
ranges: {
changes: [
{
id: new ObjectId().toString(),
metadata: { user_id: 'user-id-2' },
},
],
},
rev: 4,
},
{
_id: new ObjectId(),
lines: ['111', '222', '333'],
ranges: { mock: 'three' },
ranges: {
changes: [
{
id: new ObjectId().toString(),
metadata: { user_id: 'anonymous-user' },
},
],
},
rev: 6,
},
]
this.deleted_doc = {
_id: new ObjectId(),
lines: ['deleted'],
ranges: { mock: 'four' },
ranges: {
changes: [
{ id: new ObjectId().toString(), metadata: { user_id: 'user-id-3' } },
],
},
rev: 8,
}
const version = 42
@@ -96,7 +121,7 @@ describe('Getting all docs', function () {
})
})
return it('getAllRanges should return all the (non-deleted) doc ranges', function (done) {
it('getAllRanges should return all the (non-deleted) doc ranges', function (done) {
return DocstoreClient.getAllRanges(this.project_id, (error, res, docs) => {
if (error != null) {
throw error
@@ -109,4 +134,17 @@ describe('Getting all docs', function () {
return done()
})
})
it('getTrackedChangesUserIds should return all the user ids from (non-deleted) ranges', function (done) {
DocstoreClient.getTrackedChangesUserIds(
this.project_id,
(error, res, userIds) => {
if (error != null) {
throw error
}
userIds.should.deep.equal(['user-id-1', 'user-id-2'])
done()
}
)
})
})

View File

@@ -100,6 +100,16 @@ module.exports = DocstoreClient = {
)
},
getTrackedChangesUserIds(projectId, callback) {
request.get(
{
url: `http://127.0.0.1:${settings.internal.docstore.port}/project/${projectId}/tracked-changes-user-ids`,
json: true,
},
callback
)
},
updateDoc(projectId, docId, lines, version, ranges, callback) {
return request.post(
{

View File

@@ -1,10 +1,11 @@
const { promisify } = require('util')
const { promisifyMultiResult } = require('@overleaf/promise-utils')
const { promisifyMultiResult, callbackify } = require('@overleaf/promise-utils')
const request = require('request').defaults({ jar: false })
const OError = require('@overleaf/o-error')
const logger = require('@overleaf/logger')
const settings = require('@overleaf/settings')
const Errors = require('../Errors/Errors')
const { fetchJson } = require('@overleaf/fetch-utils')
const TIMEOUT = 30 * 1000 // request timeout
@@ -86,6 +87,14 @@ function getAllDeletedDocs(projectId, callback) {
})
}
/**
* @param {string} projectId
*/
async function getTrackedChangesUserIds(projectId) {
const url = `${settings.apis.docstore.url}/project/${projectId}/tracked-changes-user-ids`
return fetchJson(url, { signal: AbortSignal.timeout(TIMEOUT) })
}
/**
* @param {string} projectId
* @param {Callback} callback
@@ -292,6 +301,7 @@ module.exports = {
getAllDeletedDocs,
getAllRanges,
getDoc,
getTrackedChangesUserIds: callbackify(getTrackedChangesUserIds),
isDocDeleted,
updateDoc,
projectHasRanges,
@@ -304,6 +314,7 @@ module.exports = {
getAllDeletedDocs: promisify(getAllDeletedDocs),
getAllRanges: promisify(getAllRanges),
getDoc: promisifyMultiResult(getDoc, ['lines', 'rev', 'version', 'ranges']),
getTrackedChangesUserIds,
isDocDeleted: promisify(isDocDeleted),
updateDoc: promisifyMultiResult(updateDoc, ['modified', 'rev']),
projectHasRanges: promisify(projectHasRanges),