mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #9514 from overleaf/em-send-deleted-ids-to-tpds
Send a list of deleted entities to TPDS GitOrigin-RevId: 41813c31846338edc36c6ec6abc8c5a9e96731f4
This commit is contained in:
parent
0ef02ba969
commit
ff9c2c23b7
4 changed files with 191 additions and 143 deletions
|
@ -1130,10 +1130,13 @@ const ProjectEntityUpdateHandler = {
|
|||
path.fileSystem,
|
||||
userId,
|
||||
source,
|
||||
error => {
|
||||
(error, subtreeListing) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
const subtreeEntityIds = subtreeListing.map(entry =>
|
||||
entry.entity._id.toString()
|
||||
)
|
||||
TpdsUpdateSender.deleteEntity(
|
||||
{
|
||||
projectId,
|
||||
|
@ -1141,6 +1144,7 @@ const ProjectEntityUpdateHandler = {
|
|||
projectName: projectBeforeDeletion.name,
|
||||
entityId,
|
||||
entityType,
|
||||
subtreeEntityIds,
|
||||
},
|
||||
error => {
|
||||
if (error != null) {
|
||||
|
@ -1557,87 +1561,69 @@ const ProjectEntityUpdateHandler = {
|
|||
source,
|
||||
callback
|
||||
) {
|
||||
const subtreeListing = _listSubtree(entity, entityType, path)
|
||||
ProjectEntityUpdateHandler._updateProjectStructureWithDeletedEntity(
|
||||
project,
|
||||
newProject,
|
||||
entity,
|
||||
entityType,
|
||||
path,
|
||||
subtreeListing,
|
||||
userId,
|
||||
source,
|
||||
error => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
if (entityType.indexOf('file') !== -1) {
|
||||
ProjectEntityUpdateHandler._cleanUpFile(
|
||||
project,
|
||||
entity,
|
||||
path,
|
||||
userId,
|
||||
callback
|
||||
)
|
||||
} else if (entityType.indexOf('doc') !== -1) {
|
||||
ProjectEntityUpdateHandler._cleanUpDoc(
|
||||
project,
|
||||
entity,
|
||||
path,
|
||||
userId,
|
||||
callback
|
||||
)
|
||||
} else if (entityType.indexOf('folder') !== -1) {
|
||||
ProjectEntityUpdateHandler._cleanUpFolder(
|
||||
project,
|
||||
entity,
|
||||
path,
|
||||
userId,
|
||||
callback
|
||||
)
|
||||
} else {
|
||||
callback()
|
||||
const jobs = []
|
||||
|
||||
for (const entry of subtreeListing) {
|
||||
if (entry.type === 'doc') {
|
||||
jobs.push(cb => {
|
||||
ProjectEntityUpdateHandler._cleanUpDoc(
|
||||
project,
|
||||
entry.entity,
|
||||
entry.path,
|
||||
userId,
|
||||
cb
|
||||
)
|
||||
})
|
||||
} else if (entry.type === 'file') {
|
||||
jobs.push(cb => {
|
||||
ProjectEntityUpdateHandler._cleanUpFile(
|
||||
project,
|
||||
entry.entity,
|
||||
entry.path,
|
||||
userId,
|
||||
cb
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
async.series(jobs, err => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
callback(null, subtreeListing)
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
// Note: the _cleanUpEntity code and _updateProjectStructureWithDeletedEntity
|
||||
// methods both need to recursively iterate over the entities in folder.
|
||||
// These are currently using separate implementations of the recursion. In
|
||||
// future, these could be simplified using a common project entity iterator.
|
||||
_updateProjectStructureWithDeletedEntity(
|
||||
project,
|
||||
newProject,
|
||||
entity,
|
||||
entityType,
|
||||
entityPath,
|
||||
subtreeListing,
|
||||
userId,
|
||||
source,
|
||||
callback
|
||||
) {
|
||||
// compute the changes to the project structure
|
||||
let changes
|
||||
if (entityType.indexOf('file') !== -1) {
|
||||
changes = { oldFiles: [{ file: entity, path: entityPath }] }
|
||||
} else if (entityType.indexOf('doc') !== -1) {
|
||||
changes = { oldDocs: [{ doc: entity, path: entityPath }] }
|
||||
} else if (entityType.indexOf('folder') !== -1) {
|
||||
changes = { oldDocs: [], oldFiles: [] }
|
||||
const _recurseFolder = (folder, folderPath) => {
|
||||
for (const doc of iterablePaths(folder, 'docs')) {
|
||||
changes.oldDocs.push({ doc, path: Path.join(folderPath, doc.name) })
|
||||
}
|
||||
for (const file of iterablePaths(folder, 'fileRefs')) {
|
||||
changes.oldFiles.push({
|
||||
file,
|
||||
path: Path.join(folderPath, file.name),
|
||||
})
|
||||
}
|
||||
for (const childFolder of iterablePaths(folder, 'folders')) {
|
||||
_recurseFolder(childFolder, Path.join(folderPath, childFolder.name))
|
||||
}
|
||||
const changes = { oldDocs: [], oldFiles: [] }
|
||||
for (const entry of subtreeListing) {
|
||||
if (entry.type === 'doc') {
|
||||
changes.oldDocs.push({ doc: entry.entity, path: entry.path })
|
||||
} else if (entry.type === 'file') {
|
||||
changes.oldFiles.push({ file: entry.entity, path: entry.path })
|
||||
}
|
||||
_recurseFolder(entity, entityPath)
|
||||
}
|
||||
|
||||
// now send the project structure changes to the docupdater
|
||||
changes.newProject = newProject
|
||||
const projectId = project._id.toString()
|
||||
|
@ -1692,50 +1678,6 @@ const ProjectEntityUpdateHandler = {
|
|||
)
|
||||
},
|
||||
|
||||
_cleanUpFolder(project, folder, folderPath, userId, callback) {
|
||||
const jobs = []
|
||||
folder.docs.forEach(doc => {
|
||||
const docPath = Path.join(folderPath, doc.name)
|
||||
jobs.push(callback =>
|
||||
ProjectEntityUpdateHandler._cleanUpDoc(
|
||||
project,
|
||||
doc,
|
||||
docPath,
|
||||
userId,
|
||||
callback
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
folder.fileRefs.forEach(file => {
|
||||
const filePath = Path.join(folderPath, file.name)
|
||||
jobs.push(callback =>
|
||||
ProjectEntityUpdateHandler._cleanUpFile(
|
||||
project,
|
||||
file,
|
||||
filePath,
|
||||
userId,
|
||||
callback
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
folder.folders.forEach(childFolder => {
|
||||
folderPath = Path.join(folderPath, childFolder.name)
|
||||
jobs.push(callback =>
|
||||
ProjectEntityUpdateHandler._cleanUpFolder(
|
||||
project,
|
||||
childFolder,
|
||||
folderPath,
|
||||
userId,
|
||||
callback
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
async.series(jobs, callback)
|
||||
},
|
||||
|
||||
convertDocToFile: wrapWithLock({
|
||||
beforeLock(next) {
|
||||
return function (projectId, docId, userId, source, callback) {
|
||||
|
@ -1881,6 +1823,45 @@ const ProjectEntityUpdateHandler = {
|
|||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* List all descendants of an entity along with their type and path. Include
|
||||
* the top-level entity as well.
|
||||
*/
|
||||
function _listSubtree(entity, entityType, entityPath) {
|
||||
if (entityType.indexOf('file') !== -1) {
|
||||
return [{ type: 'file', entity, path: entityPath }]
|
||||
} else if (entityType.indexOf('doc') !== -1) {
|
||||
return [{ type: 'doc', entity, path: entityPath }]
|
||||
} else if (entityType.indexOf('folder') !== -1) {
|
||||
const listing = []
|
||||
const _recurseFolder = (folder, folderPath) => {
|
||||
listing.push({ type: 'folder', entity: folder, path: folderPath })
|
||||
for (const doc of iterablePaths(folder, 'docs')) {
|
||||
listing.push({
|
||||
type: 'doc',
|
||||
entity: doc,
|
||||
path: Path.join(folderPath, doc.name),
|
||||
})
|
||||
}
|
||||
for (const file of iterablePaths(folder, 'fileRefs')) {
|
||||
listing.push({
|
||||
type: 'file',
|
||||
entity: file,
|
||||
path: Path.join(folderPath, file.name),
|
||||
})
|
||||
}
|
||||
for (const childFolder of iterablePaths(folder, 'folders')) {
|
||||
_recurseFolder(childFolder, Path.join(folderPath, childFolder.name))
|
||||
}
|
||||
}
|
||||
_recurseFolder(entity, entityPath)
|
||||
return listing
|
||||
} else {
|
||||
// This shouldn't happen, but if it does, fail silently.
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProjectEntityUpdateHandler
|
||||
module.exports.promises = promisifyAll(ProjectEntityUpdateHandler, {
|
||||
without: ['isPathValidForRootDoc'],
|
||||
|
|
|
@ -108,7 +108,14 @@ function buildTpdsUrl(userId, projectName, filePath) {
|
|||
|
||||
async function deleteEntity(params) {
|
||||
metrics.inc('tpds.delete-entity')
|
||||
const { projectId, path, projectName, entityId, entityType } = params
|
||||
const {
|
||||
projectId,
|
||||
path,
|
||||
projectName,
|
||||
entityId,
|
||||
entityType,
|
||||
subtreeEntityIds,
|
||||
} = params
|
||||
|
||||
const projectUserIds = await getProjectUsersIds(projectId)
|
||||
|
||||
|
@ -123,6 +130,10 @@ async function deleteEntity(params) {
|
|||
sl_entity_type: entityType,
|
||||
},
|
||||
uri: buildTpdsUrl(userId, projectName, path),
|
||||
// We're sending a body with the DELETE request. This is unconventional,
|
||||
// but Express does handle it on the other side. Ideally, this operation
|
||||
// would be moved to a POST endpoint.
|
||||
json: { subtreeEntityIds },
|
||||
title: 'deleteEntity',
|
||||
sl_all_user_ids: JSON.stringify([userId]),
|
||||
}
|
||||
|
|
|
@ -1567,7 +1567,9 @@ describe('ProjectEntityUpdateHandler', function () {
|
|||
this.projectBeforeDeletion,
|
||||
this.newProject
|
||||
)
|
||||
this.ProjectEntityUpdateHandler._cleanUpEntity = sinon.stub().yields()
|
||||
this.ProjectEntityUpdateHandler._cleanUpEntity = sinon
|
||||
.stub()
|
||||
.yields(null, [{ type: 'doc', entity: this.doc, path: this.path }])
|
||||
|
||||
this.ProjectEntityUpdateHandler.deleteEntity(
|
||||
projectId,
|
||||
|
@ -1600,15 +1602,14 @@ describe('ProjectEntityUpdateHandler', function () {
|
|||
})
|
||||
|
||||
it('it notifies the tpds', function () {
|
||||
this.TpdsUpdateSender.deleteEntity
|
||||
.calledWith({
|
||||
projectId,
|
||||
path: this.path,
|
||||
projectName: this.projectBeforeDeletion.name,
|
||||
entityId: docId,
|
||||
entityType: 'doc',
|
||||
})
|
||||
.should.equal(true)
|
||||
this.TpdsUpdateSender.deleteEntity.should.have.been.calledWith({
|
||||
projectId,
|
||||
path: this.path,
|
||||
projectName: this.projectBeforeDeletion.name,
|
||||
entityId: docId,
|
||||
entityType: 'doc',
|
||||
subtreeEntityIds: [this.doc._id],
|
||||
})
|
||||
})
|
||||
|
||||
it('retuns the entity_id', function () {
|
||||
|
@ -2358,7 +2359,13 @@ describe('ProjectEntityUpdateHandler', function () {
|
|||
this.path,
|
||||
userId,
|
||||
this.source,
|
||||
done
|
||||
(err, subtreeListing) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
this.subtreeListing = subtreeListing
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -2378,20 +2385,25 @@ describe('ProjectEntityUpdateHandler', function () {
|
|||
this.DocumentUpdaterHandler.deleteDoc.called.should.equal(false)
|
||||
})
|
||||
|
||||
it('should should send the update to the doc updater', function () {
|
||||
it('should send the update to the doc updater', function () {
|
||||
const oldFiles = [{ file: this.entity, path: this.path }]
|
||||
this.DocumentUpdaterHandler.updateProjectStructure
|
||||
.calledWith(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
userId,
|
||||
{
|
||||
oldFiles,
|
||||
newProject: this.newProject,
|
||||
},
|
||||
this.source
|
||||
)
|
||||
.should.equal(true)
|
||||
this.DocumentUpdaterHandler.updateProjectStructure.should.have.been.calledWith(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
userId,
|
||||
{
|
||||
oldFiles,
|
||||
oldDocs: [],
|
||||
newProject: this.newProject,
|
||||
},
|
||||
this.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should return a subtree listing containing only the file', function () {
|
||||
expect(this.subtreeListing).to.deep.equal([
|
||||
{ type: 'file', entity: this.entity, path: this.path },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -2409,7 +2421,13 @@ describe('ProjectEntityUpdateHandler', function () {
|
|||
this.path,
|
||||
userId,
|
||||
this.source,
|
||||
done
|
||||
(err, subtreeListing) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
this.subtreeListing = subtreeListing
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -2419,20 +2437,25 @@ describe('ProjectEntityUpdateHandler', function () {
|
|||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should should send the update to the doc updater', function () {
|
||||
it('should send the update to the doc updater', function () {
|
||||
const oldDocs = [{ doc: this.entity, path: this.path }]
|
||||
this.DocumentUpdaterHandler.updateProjectStructure
|
||||
.calledWith(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
userId,
|
||||
{
|
||||
oldDocs,
|
||||
newProject: this.newProject,
|
||||
},
|
||||
this.source
|
||||
)
|
||||
.should.equal(true)
|
||||
this.DocumentUpdaterHandler.updateProjectStructure.should.have.been.calledWith(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
userId,
|
||||
{
|
||||
oldDocs,
|
||||
oldFiles: [],
|
||||
newProject: this.newProject,
|
||||
},
|
||||
this.source
|
||||
)
|
||||
})
|
||||
|
||||
it('should return a subtree listing containing only the doc', function () {
|
||||
expect(this.subtreeListing).to.deep.equal([
|
||||
{ type: 'doc', entity: this.entity, path: this.path },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -2465,7 +2488,13 @@ describe('ProjectEntityUpdateHandler', function () {
|
|||
path,
|
||||
userId,
|
||||
this.source,
|
||||
done
|
||||
(err, subtreeListing) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
this.subtreeListing = subtreeListing
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -2520,6 +2549,29 @@ describe('ProjectEntityUpdateHandler', function () {
|
|||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should return a subtree listing containing all sub-entities', function () {
|
||||
expect(this.subtreeListing).to.have.deep.members([
|
||||
{ type: 'folder', entity: this.folder, path: '/folder' },
|
||||
{
|
||||
type: 'folder',
|
||||
entity: this.folder.folders[0],
|
||||
path: '/folder/subfolder',
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
entity: this.file1,
|
||||
path: '/folder/subfolder/file-name-1',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
entity: this.doc1,
|
||||
path: '/folder/subfolder/doc-name-1',
|
||||
},
|
||||
{ type: 'file', entity: this.file2, path: '/folder/file-name-2' },
|
||||
{ type: 'doc', entity: this.doc2, path: '/folder/doc-name-2' },
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ const { ObjectId } = require('mongodb')
|
|||
const SandboxedModule = require('sandboxed-module')
|
||||
const path = require('path')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = require('chai')
|
||||
|
||||
const modulePath = path.join(
|
||||
__dirname,
|
||||
|
@ -200,11 +201,13 @@ describe('TpdsUpdateSender', function () {
|
|||
|
||||
it('deleting entity', async function () {
|
||||
const path = '/path/here/t.tex'
|
||||
const subtreeEntityIds = ['id1', 'id2']
|
||||
|
||||
await this.TpdsUpdateSender.promises.deleteEntity({
|
||||
projectId,
|
||||
path,
|
||||
projectName,
|
||||
subtreeEntityIds,
|
||||
})
|
||||
|
||||
const {
|
||||
|
@ -221,6 +224,7 @@ describe('TpdsUpdateSender', function () {
|
|||
)}${encodeURIComponent(path)}`
|
||||
job0.headers.sl_all_user_ids.should.eql(JSON.stringify([userId]))
|
||||
job0.uri.should.equal(expectedUrl)
|
||||
expect(job0.json).to.deep.equal({ subtreeEntityIds })
|
||||
|
||||
const { group: group1, job: job1 } = this.request.secondCall.args[0].json
|
||||
group1.should.equal(collaberatorRef)
|
||||
|
|
Loading…
Reference in a new issue