Преглед изворни кода

Merge pull request #9383 from overleaf/em-file-tree-histories

Track source for all file tree operations

GitOrigin-RevId: ff95ea8e99bfa30203a2a42968519bbaba65e708
Eric Mc Sween пре 4 година
родитељ
комит
8b2f8ce243
24 измењених фајлова са 666 додато и 527 уклоњено
  1. 2 1
      services/document-updater/app/js/HttpController.js
  2. 49 49
      services/document-updater/app/js/ProjectHistoryRedisManager.js
  3. 6 0
      services/document-updater/app/js/ProjectManager.js
  4. 4 1
      services/document-updater/test/unit/js/HttpController/HttpControllerTests.js
  5. 37 48
      services/document-updater/test/unit/js/ProjectHistoryRedisManager/ProjectHistoryRedisManagerTests.js
  6. 20 5
      services/document-updater/test/unit/js/ProjectManager/updateProjectTests.js
  7. 2 0
      services/web/app/src/Features/DocumentUpdater/DocumentUpdaterHandler.js
  8. 249 319
      services/web/app/src/Features/Editor/EditorController.js
  9. 6 3
      services/web/app/src/Features/Editor/EditorHttpController.js
  10. 6 3
      services/web/app/src/Features/Project/ProjectCreationHandler.js
  11. 2 1
      services/web/app/src/Features/Project/ProjectDuplicator.js
  12. 70 21
      services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js
  13. 2 1
      services/web/app/src/Features/Uploads/ProjectUploadManager.js
  14. 2 1
      services/web/scripts/convert_doc_to_file.js
  15. 6 3
      services/web/scripts/create_project.js
  16. 2 1
      services/web/scripts/history/HistoryUpgradeHelper.js
  17. 2 1
      services/web/scripts/recover_docs_from_redis.js
  18. 13 4
      services/web/test/unit/src/DocumentUpdater/DocumentUpdaterHandlerTests.js
  19. 24 8
      services/web/test/unit/src/Editor/EditorControllerTests.js
  20. 9 6
      services/web/test/unit/src/Editor/EditorHttpControllerTests.js
  21. 2 1
      services/web/test/unit/src/Project/ProjectDuplicatorTests.js
  22. 1 0
      services/web/test/unit/src/Project/ProjectEntityRestoreHandlerTests.js
  23. 146 48
      services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js
  24. 4 2
      services/web/test/unit/src/Uploads/ProjectUploadManagerTests.js

+ 2 - 1
services/document-updater/app/js/HttpController.js

@@ -322,7 +322,7 @@ function deleteComment(req, res, next) {
 function updateProject(req, res, next) {
   const timer = new Metrics.Timer('http.updateProject')
   const projectId = req.params.project_id
-  const { projectHistoryId, userId, updates = [], version } = req.body
+  const { projectHistoryId, userId, updates = [], version, source } = req.body
   logger.debug({ projectId, updates, version }, 'updating project via http')
   ProjectManager.updateProjectWithLocks(
     projectId,
@@ -330,6 +330,7 @@ function updateProject(req, res, next) {
     userId,
     updates,
     version,
+    source,
     error => {
       timer.done()
       if (error) {

+ 49 - 49
services/document-updater/app/js/ProjectHistoryRedisManager.js

@@ -1,16 +1,3 @@
-/* eslint-disable
-    camelcase,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS201: Simplify complex destructure assignments
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 let ProjectHistoryRedisManager
 const Settings = require('@overleaf/settings')
 const projectHistoryKeys = Settings.redis?.project_history?.key_schema
@@ -22,106 +9,119 @@ const metrics = require('./Metrics')
 const { docIsTooLarge } = require('./Limits')
 
 module.exports = ProjectHistoryRedisManager = {
-  queueOps(project_id, ...rest) {
+  queueOps(projectId, ...rest) {
     // Record metric for ops pushed onto queue
     const callback = rest.pop()
     const ops = rest
-    for (const op of Array.from(ops)) {
+    for (const op of ops) {
       metrics.summary('redis.projectHistoryOps', op.length, { status: 'push' })
     }
     const multi = rclient.multi()
     // Push the ops onto the project history queue
     multi.rpush(
-      projectHistoryKeys.projectHistoryOps({ project_id }),
-      ...Array.from(ops)
+      projectHistoryKeys.projectHistoryOps({ project_id: projectId }),
+      ...ops
     )
     // To record the age of the oldest op on the queue set a timestamp if not
     // already present (SETNX).
     multi.setnx(
-      projectHistoryKeys.projectHistoryFirstOpTimestamp({ project_id }),
+      projectHistoryKeys.projectHistoryFirstOpTimestamp({
+        project_id: projectId,
+      }),
       Date.now()
     )
-    return multi.exec(function (error, result) {
-      if (error != null) {
+    multi.exec(function (error, result) {
+      if (error) {
         return callback(error)
       }
       // return the number of entries pushed onto the project history queue
-      return callback(null, result[0])
+      callback(null, result[0])
     })
   },
 
   queueRenameEntity(
-    project_id,
+    projectId,
     projectHistoryId,
-    entity_type,
-    entity_id,
-    user_id,
+    entityType,
+    entityId,
+    userId,
     projectUpdate,
+    source,
     callback
   ) {
     projectUpdate = {
       pathname: projectUpdate.pathname,
       new_pathname: projectUpdate.newPathname,
       meta: {
-        user_id,
+        user_id: userId,
         ts: new Date(),
       },
       version: projectUpdate.version,
       projectHistoryId,
     }
-    projectUpdate[entity_type] = entity_id
+    projectUpdate[entityType] = entityId
+    if (source != null) {
+      projectUpdate.meta.source = source
+      if (source !== 'editor') {
+        projectUpdate.meta.type = 'external'
+      }
+    }
 
     logger.debug(
-      { project_id, projectUpdate },
+      { projectId, projectUpdate },
       'queue rename operation to project-history'
     )
     const jsonUpdate = JSON.stringify(projectUpdate)
 
-    return ProjectHistoryRedisManager.queueOps(project_id, jsonUpdate, callback)
+    ProjectHistoryRedisManager.queueOps(projectId, jsonUpdate, callback)
   },
 
   queueAddEntity(
-    project_id,
+    projectId,
     projectHistoryId,
-    entity_type,
-    entitiy_id,
-    user_id,
+    entityType,
+    entityId,
+    userId,
     projectUpdate,
+    source,
     callback
   ) {
-    if (callback == null) {
-      callback = function () {}
-    }
     projectUpdate = {
       pathname: projectUpdate.pathname,
       docLines: projectUpdate.docLines,
       url: projectUpdate.url,
       meta: {
-        user_id,
+        user_id: userId,
         ts: new Date(),
       },
       version: projectUpdate.version,
       projectHistoryId,
     }
-    projectUpdate[entity_type] = entitiy_id
+    projectUpdate[entityType] = entityId
+    if (source != null) {
+      projectUpdate.meta.source = source
+      if (source !== 'editor') {
+        projectUpdate.meta.type = 'external'
+      }
+    }
 
     logger.debug(
-      { project_id, projectUpdate },
+      { projectId, projectUpdate },
       'queue add operation to project-history'
     )
     const jsonUpdate = JSON.stringify(projectUpdate)
 
-    return ProjectHistoryRedisManager.queueOps(project_id, jsonUpdate, callback)
+    ProjectHistoryRedisManager.queueOps(projectId, jsonUpdate, callback)
   },
 
   queueResyncProjectStructure(
-    project_id,
+    projectId,
     projectHistoryId,
     docs,
     files,
     callback
   ) {
-    logger.debug({ project_id, docs, files }, 'queue project structure resync')
+    logger.debug({ projectId, docs, files }, 'queue project structure resync')
     const projectUpdate = {
       resyncProjectStructure: { docs, files },
       projectHistoryId,
@@ -130,20 +130,20 @@ module.exports = ProjectHistoryRedisManager = {
       },
     }
     const jsonUpdate = JSON.stringify(projectUpdate)
-    return ProjectHistoryRedisManager.queueOps(project_id, jsonUpdate, callback)
+    ProjectHistoryRedisManager.queueOps(projectId, jsonUpdate, callback)
   },
 
   queueResyncDocContent(
-    project_id,
+    projectId,
     projectHistoryId,
-    doc_id,
+    docId,
     lines,
     version,
     pathname,
     callback
   ) {
     logger.debug(
-      { project_id, doc_id, lines, version, pathname },
+      { projectId, docId, lines, version, pathname },
       'queue doc content resync'
     )
     const projectUpdate = {
@@ -153,7 +153,7 @@ module.exports = ProjectHistoryRedisManager = {
       },
       projectHistoryId,
       path: pathname,
-      doc: doc_id,
+      doc: docId,
       meta: {
         ts: new Date(),
       },
@@ -166,9 +166,9 @@ module.exports = ProjectHistoryRedisManager = {
       const err = new Error(
         'blocking resync doc content insert into project history queue: doc is too large'
       )
-      logger.error({ project_id, doc_id, err, docSize: sizeBound }, err.message)
+      logger.error({ projectId, docId, err, docSize: sizeBound }, err.message)
       return callback(err)
     }
-    return ProjectHistoryRedisManager.queueOps(project_id, jsonUpdate, callback)
+    ProjectHistoryRedisManager.queueOps(projectId, jsonUpdate, callback)
   },
 }

+ 6 - 0
services/document-updater/app/js/ProjectManager.js

@@ -209,6 +209,7 @@ function updateProjectWithLocks(
   userId,
   updates,
   projectVersion,
+  source,
   _callback
 ) {
   const timer = new Metrics.Timer('projectManager.updateProject')
@@ -231,6 +232,7 @@ function updateProjectWithLocks(
           update.id,
           userId,
           update,
+          source,
           (error, count) => {
             projectOpsLength = count
             cb(error)
@@ -248,6 +250,7 @@ function updateProjectWithLocks(
             update.id,
             userId,
             update,
+            source,
             (error, count) => {
               projectOpsLength = count
               cb(error)
@@ -272,6 +275,7 @@ function updateProjectWithLocks(
                 update.id,
                 userId,
                 update,
+                source,
                 (error, count) => {
                   projectOpsLength = count
                   cb(error)
@@ -289,6 +293,7 @@ function updateProjectWithLocks(
           update.id,
           userId,
           update,
+          source,
           (error, count) => {
             projectOpsLength = count
             cb(error)
@@ -303,6 +308,7 @@ function updateProjectWithLocks(
           update.id,
           userId,
           update,
+          source,
           (error, count) => {
             projectOpsLength = count
             cb(error)

+ 4 - 1
services/document-updater/test/unit/js/HttpController/HttpControllerTests.js

@@ -24,6 +24,7 @@ describe('HttpController', function () {
 
     this.project_id = 'project-id-123'
     this.doc_id = 'doc-id-123'
+    this.source = 'editor'
     this.next = sinon.stub()
     this.res = {
       send: sinon.stub(),
@@ -837,6 +838,7 @@ describe('HttpController', function () {
           userId: this.userId,
           updates: this.updates,
           version: this.version,
+          source: this.source,
         },
         params: {
           project_id: this.project_id,
@@ -857,7 +859,8 @@ describe('HttpController', function () {
             this.projectHistoryId,
             this.userId,
             this.updates,
-            this.version
+            this.version,
+            this.source
           )
           .should.equal(true)
       })

+ 37 - 48
services/document-updater/test/unit/js/ProjectHistoryRedisManager/ProjectHistoryRedisManagerTests.js

@@ -1,16 +1,3 @@
-/* eslint-disable
-    camelcase,
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 const sinon = require('sinon')
 const modulePath = '../../../../app/js/ProjectHistoryRedisManager.js'
 const SandboxedModule = require('sandboxed-module')
@@ -23,43 +10,41 @@ describe('ProjectHistoryRedisManager', function () {
     this.user_id = 'user-id-123'
     this.callback = sinon.stub()
     this.rclient = {}
+    this.source = 'editor'
     tk.freeze(new Date())
 
     this.Limits = {
       docIsTooLarge: sinon.stub().returns(false),
     }
 
-    return (this.ProjectHistoryRedisManager = SandboxedModule.require(
-      modulePath,
-      {
-        requires: {
-          '@overleaf/settings': (this.settings = {
-            max_doc_length: 123,
-            redis: {
-              project_history: {
-                key_schema: {
-                  projectHistoryOps({ project_id }) {
-                    return `ProjectHistory:Ops:${project_id}`
-                  },
-                  projectHistoryFirstOpTimestamp({ project_id }) {
-                    return `ProjectHistory:FirstOpTimestamp:${project_id}`
-                  },
+    this.ProjectHistoryRedisManager = SandboxedModule.require(modulePath, {
+      requires: {
+        '@overleaf/settings': (this.settings = {
+          max_doc_length: 123,
+          redis: {
+            project_history: {
+              key_schema: {
+                projectHistoryOps({ project_id: projectId }) {
+                  return `ProjectHistory:Ops:${projectId}`
+                },
+                projectHistoryFirstOpTimestamp({ project_id: projectId }) {
+                  return `ProjectHistory:FirstOpTimestamp:${projectId}`
                 },
               },
             },
-          }),
-          '@overleaf/redis-wrapper': {
-            createClient: () => this.rclient,
           },
-          './Metrics': (this.metrics = { summary: sinon.stub() }),
-          './Limits': this.Limits,
+        }),
+        '@overleaf/redis-wrapper': {
+          createClient: () => this.rclient,
         },
-      }
-    ))
+        './Metrics': (this.metrics = { summary: sinon.stub() }),
+        './Limits': this.Limits,
+      },
+    })
   })
 
   afterEach(function () {
-    return tk.reset()
+    tk.reset()
   })
 
   describe('queueOps', function () {
@@ -70,15 +55,15 @@ describe('ProjectHistoryRedisManager', function () {
       this.multi.setnx = sinon.stub()
       this.rclient.multi = () => this.multi
       // @rclient = multi: () => @multi
-      return this.ProjectHistoryRedisManager.queueOps(
+      this.ProjectHistoryRedisManager.queueOps(
         this.project_id,
-        ...Array.from(this.ops),
+        ...this.ops,
         this.callback
       )
     })
 
     it('should queue an update', function () {
-      return this.multi.rpush
+      this.multi.rpush
         .calledWithExactly(
           `ProjectHistory:Ops:${this.project_id}`,
           this.ops[0],
@@ -87,8 +72,8 @@ describe('ProjectHistoryRedisManager', function () {
         .should.equal(true)
     })
 
-    return it('should set the queue timestamp if not present', function () {
-      return this.multi.setnx
+    it('should set the queue timestamp if not present', function () {
+      this.multi.setnx
         .calledWithExactly(
           `ProjectHistory:FirstOpTimestamp:${this.project_id}`,
           Date.now()
@@ -108,31 +93,33 @@ describe('ProjectHistoryRedisManager', function () {
       }
 
       this.ProjectHistoryRedisManager.queueOps = sinon.stub()
-      return this.ProjectHistoryRedisManager.queueRenameEntity(
+      this.ProjectHistoryRedisManager.queueRenameEntity(
         this.project_id,
         this.projectHistoryId,
         'file',
         this.file_id,
         this.user_id,
         this.rawUpdate,
+        this.source,
         this.callback
       )
     })
 
-    return it('should queue an update', function () {
+    it('should queue an update', function () {
       const update = {
         pathname: this.pathname,
         new_pathname: this.newPathname,
         meta: {
           user_id: this.user_id,
           ts: new Date(),
+          source: this.source,
         },
         version: this.version,
         projectHistoryId: this.projectHistoryId,
         file: this.file_id,
       }
 
-      return this.ProjectHistoryRedisManager.queueOps
+      this.ProjectHistoryRedisManager.queueOps
         .calledWithExactly(
           this.project_id,
           JSON.stringify(update),
@@ -142,7 +129,7 @@ describe('ProjectHistoryRedisManager', function () {
     })
   })
 
-  return describe('queueAddEntity', function () {
+  describe('queueAddEntity', function () {
     beforeEach(function () {
       this.rclient.rpush = sinon.stub().yields()
       this.doc_id = 1234
@@ -155,13 +142,14 @@ describe('ProjectHistoryRedisManager', function () {
       }
 
       this.ProjectHistoryRedisManager.queueOps = sinon.stub()
-      return this.ProjectHistoryRedisManager.queueAddEntity(
+      this.ProjectHistoryRedisManager.queueAddEntity(
         this.project_id,
         this.projectHistoryId,
         'doc',
         this.doc_id,
         this.user_id,
         this.rawUpdate,
+        this.source,
         this.callback
       )
     })
@@ -174,13 +162,14 @@ describe('ProjectHistoryRedisManager', function () {
         meta: {
           user_id: this.user_id,
           ts: new Date(),
+          source: this.source,
         },
         version: this.version,
         projectHistoryId: this.projectHistoryId,
         doc: this.doc_id,
       }
 
-      return this.ProjectHistoryRedisManager.queueOps
+      this.ProjectHistoryRedisManager.queueOps
         .calledWithExactly(
           this.project_id,
           JSON.stringify(update),
@@ -190,7 +179,7 @@ describe('ProjectHistoryRedisManager', function () {
     })
 
     describe('queueResyncProjectStructure', function () {
-      return it('should queue an update', function () {})
+      it('should queue an update', function () {})
     })
 
     describe('queueResyncDocContent', function () {

+ 20 - 5
services/document-updater/test/unit/js/ProjectManager/updateProjectTests.js

@@ -36,6 +36,7 @@ describe('ProjectManager', function () {
     this.projectHistoryId = 'history-id-123'
     this.user_id = 'user-id-123'
     this.version = 1234567
+    this.source = 'editor'
     this.callback = sinon.stub()
   })
 
@@ -75,6 +76,7 @@ describe('ProjectManager', function () {
             this.user_id,
             this.updates,
             this.version,
+            this.source,
             this.callback
           )
         })
@@ -121,7 +123,8 @@ describe('ProjectManager', function () {
               'file',
               this.firstFileUpdate.id,
               this.user_id,
-              firstFileUpdateWithVersion
+              firstFileUpdateWithVersion,
+              this.source
             )
             .should.equal(true)
         })
@@ -147,6 +150,7 @@ describe('ProjectManager', function () {
             this.user_id,
             this.updates,
             this.version,
+            this.source,
             this.callback
           )
         })
@@ -166,6 +170,7 @@ describe('ProjectManager', function () {
             this.user_id,
             this.updates,
             this.version,
+            this.source,
             this.callback
           )
         })
@@ -184,6 +189,7 @@ describe('ProjectManager', function () {
             this.user_id,
             this.updates,
             this.version,
+            this.source,
             this.callback
           )
         })
@@ -234,6 +240,7 @@ describe('ProjectManager', function () {
             this.user_id,
             this.updates,
             this.version,
+            this.source,
             this.callback
           )
         })
@@ -255,7 +262,8 @@ describe('ProjectManager', function () {
               'doc',
               this.firstDocUpdate.id,
               this.user_id,
-              firstDocUpdateWithVersion
+              firstDocUpdateWithVersion,
+              this.source
             )
             .should.equal(true)
           this.ProjectHistoryRedisManager.queueAddEntity
@@ -266,7 +274,8 @@ describe('ProjectManager', function () {
               'doc',
               this.secondDocUpdate.id,
               this.user_id,
-              secondDocUpdateWithVersion
+              secondDocUpdateWithVersion,
+              this.source
             )
             .should.equal(true)
         })
@@ -290,7 +299,8 @@ describe('ProjectManager', function () {
               'file',
               this.firstFileUpdate.id,
               this.user_id,
-              firstFileUpdateWithVersion
+              firstFileUpdateWithVersion,
+              this.source
             )
             .should.equal(true)
           this.ProjectHistoryRedisManager.queueAddEntity
@@ -301,7 +311,8 @@ describe('ProjectManager', function () {
               'file',
               this.secondFileUpdate.id,
               this.user_id,
-              secondFileUpdateWithVersion
+              secondFileUpdateWithVersion,
+              this.source
             )
             .should.equal(true)
         })
@@ -327,6 +338,7 @@ describe('ProjectManager', function () {
             this.user_id,
             this.updates,
             this.version,
+            this.source,
             this.callback
           )
         })
@@ -346,6 +358,7 @@ describe('ProjectManager', function () {
             this.user_id,
             this.updates,
             this.version,
+            this.source,
             this.callback
           )
         })
@@ -364,6 +377,7 @@ describe('ProjectManager', function () {
             this.user_id,
             this.updates,
             this.version,
+            this.source,
             this.callback
           )
         })
@@ -385,6 +399,7 @@ describe('ProjectManager', function () {
           this.user_id,
           this.updates,
           this.version,
+          this.source,
           this.callback
         )
       })

+ 2 - 0
services/web/app/src/Features/DocumentUpdater/DocumentUpdaterHandler.js

@@ -243,6 +243,7 @@ function updateProjectStructure(
   projectHistoryId,
   userId,
   changes,
+  source,
   callback
 ) {
   if (
@@ -293,6 +294,7 @@ function updateProjectStructure(
         userId,
         version: projectVersion,
         projectHistoryId,
+        source,
       },
       method: 'POST',
     },

+ 249 - 319
services/web/app/src/Features/Editor/EditorController.js

@@ -1,18 +1,3 @@
-/* eslint-disable
-    camelcase,
-    n/handle-callback-err,
-    max-len,
-    no-dupe-keys,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
 const logger = require('@overleaf/logger')
 const OError = require('@overleaf/o-error')
 const Metrics = require('@overleaf/metrics')
@@ -20,181 +5,159 @@ const ProjectEntityUpdateHandler = require('../Project/ProjectEntityUpdateHandle
 const ProjectOptionsHandler = require('../Project/ProjectOptionsHandler')
 const ProjectDetailsHandler = require('../Project/ProjectDetailsHandler')
 const ProjectDeleter = require('../Project/ProjectDeleter')
-const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
 const EditorRealTimeController = require('./EditorRealTimeController')
 const async = require('async')
 const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
-const _ = require('underscore')
 const { promisifyAll } = require('../../util/promises')
 
 const EditorController = {
-  addDoc(project_id, folder_id, docName, docLines, source, user_id, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return EditorController.addDocWithRanges(
-      project_id,
-      folder_id,
+  addDoc(projectId, folderId, docName, docLines, source, userId, callback) {
+    EditorController.addDocWithRanges(
+      projectId,
+      folderId,
       docName,
       docLines,
       {},
       source,
-      user_id,
+      userId,
       callback
     )
   },
 
   addDocWithRanges(
-    project_id,
-    folder_id,
+    projectId,
+    folderId,
     docName,
     docLines,
     docRanges,
     source,
-    user_id,
+    userId,
     callback
   ) {
-    if (callback == null) {
-      callback = function () {}
-    }
     docName = docName.trim()
     Metrics.inc('editor.add-doc')
-    return ProjectEntityUpdateHandler.addDocWithRanges(
-      project_id,
-      folder_id,
+    ProjectEntityUpdateHandler.addDocWithRanges(
+      projectId,
+      folderId,
       docName,
       docLines,
       docRanges,
-      user_id,
-      (err, doc, folder_id) => {
-        if (err != null) {
+      userId,
+      source,
+      (err, doc, folderId) => {
+        if (err) {
           OError.tag(err, 'error adding doc without lock', {
-            project_id,
+            projectId,
             docName,
           })
           return callback(err)
         }
         EditorRealTimeController.emitToRoom(
-          project_id,
+          projectId,
           'reciveNewDoc',
-          folder_id,
+          folderId,
           doc,
           source,
-          user_id
+          userId
         )
-        return callback(err, doc)
+        callback(err, doc)
       }
     )
   },
 
   addFile(
-    project_id,
-    folder_id,
+    projectId,
+    folderId,
     fileName,
     fsPath,
     linkedFileData,
     source,
-    user_id,
+    userId,
     callback
   ) {
-    if (callback == null) {
-      callback = function () {}
-    }
     fileName = fileName.trim()
     Metrics.inc('editor.add-file')
-    return ProjectEntityUpdateHandler.addFile(
-      project_id,
-      folder_id,
+    ProjectEntityUpdateHandler.addFile(
+      projectId,
+      folderId,
       fileName,
       fsPath,
       linkedFileData,
-      user_id,
-      (err, fileRef, folder_id) => {
-        if (err != null) {
+      userId,
+      source,
+      (err, fileRef, folderId) => {
+        if (err) {
           OError.tag(err, 'error adding file without lock', {
-            project_id,
-            folder_id,
+            projectId,
+            folderId,
             fileName,
           })
           return callback(err)
         }
         EditorRealTimeController.emitToRoom(
-          project_id,
+          projectId,
           'reciveNewFile',
-          folder_id,
+          folderId,
           fileRef,
           source,
           linkedFileData,
-          user_id
+          userId
         )
-        return callback(err, fileRef)
+        callback(err, fileRef)
       }
     )
   },
 
-  upsertDoc(
-    project_id,
-    folder_id,
-    docName,
-    docLines,
-    source,
-    user_id,
-    callback
-  ) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return ProjectEntityUpdateHandler.upsertDoc(
-      project_id,
-      folder_id,
+  upsertDoc(projectId, folderId, docName, docLines, source, userId, callback) {
+    ProjectEntityUpdateHandler.upsertDoc(
+      projectId,
+      folderId,
       docName,
       docLines,
       source,
-      user_id,
+      userId,
       function (err, doc, didAddNewDoc) {
         if (didAddNewDoc) {
           EditorRealTimeController.emitToRoom(
-            project_id,
+            projectId,
             'reciveNewDoc',
-            folder_id,
+            folderId,
             doc,
             source,
-            user_id
+            userId
           )
         }
-        return callback(err, doc)
+        callback(err, doc)
       }
     )
   },
 
   upsertFile(
-    project_id,
-    folder_id,
+    projectId,
+    folderId,
     fileName,
     fsPath,
     linkedFileData,
     source,
-    user_id,
+    userId,
     callback
   ) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return ProjectEntityUpdateHandler.upsertFile(
-      project_id,
-      folder_id,
+    ProjectEntityUpdateHandler.upsertFile(
+      projectId,
+      folderId,
       fileName,
       fsPath,
       linkedFileData,
-      user_id,
+      userId,
+      source,
       function (err, newFile, didAddFile, existingFile) {
-        if (err != null) {
+        if (err) {
           return callback(err)
         }
         if (!didAddFile) {
           // replacement, so remove the existing file from the client
           EditorRealTimeController.emitToRoom(
-            project_id,
+            projectId,
             'removeEntity',
             existingFile._id,
             source
@@ -202,55 +165,55 @@ const EditorController = {
         }
         // now add the new file on the client
         EditorRealTimeController.emitToRoom(
-          project_id,
+          projectId,
           'reciveNewFile',
-          folder_id,
+          folderId,
           newFile,
           source,
           linkedFileData,
-          user_id
+          userId
         )
-        return callback(null, newFile)
+        callback(null, newFile)
       }
     )
   },
 
   upsertDocWithPath(
-    project_id,
+    projectId,
     elementPath,
     docLines,
     source,
-    user_id,
+    userId,
     callback
   ) {
-    return ProjectEntityUpdateHandler.upsertDocWithPath(
-      project_id,
+    ProjectEntityUpdateHandler.upsertDocWithPath(
+      projectId,
       elementPath,
       docLines,
       source,
-      user_id,
+      userId,
       function (err, doc, didAddNewDoc, newFolders, lastFolder) {
-        if (err != null) {
+        if (err) {
           return callback(err)
         }
-        return EditorController._notifyProjectUsersOfNewFolders(
-          project_id,
+        EditorController._notifyProjectUsersOfNewFolders(
+          projectId,
           newFolders,
           function (err) {
-            if (err != null) {
+            if (err) {
               return callback(err)
             }
             if (didAddNewDoc) {
               EditorRealTimeController.emitToRoom(
-                project_id,
+                projectId,
                 'reciveNewDoc',
                 lastFolder._id,
                 doc,
                 source,
-                user_id
+                userId
               )
             }
-            return callback()
+            callback()
           }
         )
       }
@@ -258,20 +221,21 @@ const EditorController = {
   },
 
   upsertFileWithPath(
-    project_id,
+    projectId,
     elementPath,
     fsPath,
     linkedFileData,
     source,
-    user_id,
+    userId,
     callback
   ) {
-    return ProjectEntityUpdateHandler.upsertFileWithPath(
-      project_id,
+    ProjectEntityUpdateHandler.upsertFileWithPath(
+      projectId,
       elementPath,
       fsPath,
       linkedFileData,
-      user_id,
+      userId,
+      source,
       function (
         err,
         newFile,
@@ -280,20 +244,20 @@ const EditorController = {
         newFolders,
         lastFolder
       ) {
-        if (err != null) {
+        if (err) {
           return callback(err)
         }
-        return EditorController._notifyProjectUsersOfNewFolders(
-          project_id,
+        EditorController._notifyProjectUsersOfNewFolders(
+          projectId,
           newFolders,
           function (err) {
-            if (err != null) {
+            if (err) {
               return callback(err)
             }
             if (!didAddFile) {
               // replacement, so remove the existing file from the client
               EditorRealTimeController.emitToRoom(
-                project_id,
+                projectId,
                 'removeEntity',
                 existingFile._id,
                 source
@@ -301,193 +265,189 @@ const EditorController = {
             }
             // now add the new file on the client
             EditorRealTimeController.emitToRoom(
-              project_id,
+              projectId,
               'reciveNewFile',
               lastFolder._id,
               newFile,
               source,
               linkedFileData,
-              user_id
+              userId
             )
-            return callback()
+            callback()
           }
         )
       }
     )
   },
 
-  addFolder(project_id, folder_id, folderName, source, userId, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
+  addFolder(projectId, folderId, folderName, source, userId, callback) {
     folderName = folderName.trim()
     Metrics.inc('editor.add-folder')
-    return ProjectEntityUpdateHandler.addFolder(
-      project_id,
-      folder_id,
+    ProjectEntityUpdateHandler.addFolder(
+      projectId,
+      folderId,
       folderName,
-      (err, folder, folder_id) => {
-        if (err != null) {
+      (err, folder, folderId) => {
+        if (err) {
           OError.tag(err, 'could not add folder', {
-            project_id,
-            folder_id,
+            projectId,
+            folderId,
             folderName,
             source,
           })
           return callback(err)
         }
-        return EditorController._notifyProjectUsersOfNewFolder(
-          project_id,
-          folder_id,
+        EditorController._notifyProjectUsersOfNewFolder(
+          projectId,
+          folderId,
           folder,
           userId,
           function (err) {
-            if (err != null) {
+            if (err) {
               return callback(err)
             }
-            return callback(null, folder)
+            callback(null, folder)
           }
         )
       }
     )
   },
 
-  mkdirp(project_id, path, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    logger.debug({ project_id, path }, "making directories if they don't exist")
-    return ProjectEntityUpdateHandler.mkdirp(
-      project_id,
+  mkdirp(projectId, path, callback) {
+    logger.debug({ projectId, path }, "making directories if they don't exist")
+    ProjectEntityUpdateHandler.mkdirp(
+      projectId,
       path,
       (err, newFolders, lastFolder) => {
-        if (err != null) {
+        if (err) {
           OError.tag(err, 'could not mkdirp', {
-            project_id,
+            projectId,
             path,
           })
           return callback(err)
         }
 
-        return EditorController._notifyProjectUsersOfNewFolders(
-          project_id,
+        EditorController._notifyProjectUsersOfNewFolders(
+          projectId,
           newFolders,
           function (err) {
-            if (err != null) {
+            if (err) {
               return callback(err)
             }
-            return callback(null, newFolders, lastFolder)
+            callback(null, newFolders, lastFolder)
           }
         )
       }
     )
   },
 
-  deleteEntity(project_id, entity_id, entityType, source, userId, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
+  deleteEntity(projectId, entityId, entityType, source, userId, callback) {
     Metrics.inc('editor.delete-entity')
-    return ProjectEntityUpdateHandler.deleteEntity(
-      project_id,
-      entity_id,
+    ProjectEntityUpdateHandler.deleteEntity(
+      projectId,
+      entityId,
       entityType,
       userId,
+      source,
       function (err) {
-        if (err != null) {
+        if (err) {
           OError.tag(err, 'could not delete entity', {
-            project_id,
-            entity_id,
+            projectId,
+            entityId,
             entityType,
           })
           return callback(err)
         }
         logger.debug(
-          { project_id, entity_id, entityType },
+          { projectId, entityId, entityType },
           'telling users entity has been deleted'
         )
         EditorRealTimeController.emitToRoom(
-          project_id,
+          projectId,
           'removeEntity',
-          entity_id,
+          entityId,
           source
         )
-        return callback()
+        callback()
       }
     )
   },
 
-  deleteEntityWithPath(project_id, path, source, user_id, callback) {
-    return ProjectEntityUpdateHandler.deleteEntityWithPath(
-      project_id,
+  deleteEntityWithPath(projectId, path, source, userId, callback) {
+    ProjectEntityUpdateHandler.deleteEntityWithPath(
+      projectId,
       path,
-      user_id,
-      function (err, entity_id) {
-        if (err != null) {
+      userId,
+      source,
+      function (err, entityId) {
+        if (err) {
           return callback(err)
         }
         EditorRealTimeController.emitToRoom(
-          project_id,
+          projectId,
           'removeEntity',
-          entity_id,
+          entityId,
           source
         )
-        return callback(null, entity_id)
+        callback(null, entityId)
       }
     )
   },
 
-  updateProjectDescription(project_id, description, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    logger.debug({ project_id, description }, 'updating project description')
-    return ProjectDetailsHandler.setProjectDescription(
-      project_id,
+  updateProjectDescription(projectId, description, callback) {
+    logger.debug({ projectId, description }, 'updating project description')
+    ProjectDetailsHandler.setProjectDescription(
+      projectId,
       description,
       function (err) {
-        if (err != null) {
+        if (err) {
           OError.tag(
             err,
             'something went wrong setting the project description',
             {
-              project_id,
+              projectId,
               description,
             }
           )
           return callback(err)
         }
         EditorRealTimeController.emitToRoom(
-          project_id,
+          projectId,
           'projectDescriptionUpdated',
           description
         )
-        return callback()
+        callback()
       }
     )
   },
 
-  deleteProject(project_id, callback) {
+  deleteProject(projectId, callback) {
     Metrics.inc('editor.delete-project')
-    return ProjectDeleter.deleteProject(project_id, callback)
+    ProjectDeleter.deleteProject(projectId, callback)
   },
 
-  renameEntity(project_id, entity_id, entityType, newName, userId, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
+  renameEntity(
+    projectId,
+    entityId,
+    entityType,
+    newName,
+    userId,
+    source,
+    callback
+  ) {
     Metrics.inc('editor.rename-entity')
-    return ProjectEntityUpdateHandler.renameEntity(
-      project_id,
-      entity_id,
+    ProjectEntityUpdateHandler.renameEntity(
+      projectId,
+      entityId,
       entityType,
       newName,
       userId,
+      source,
       function (err) {
-        if (err != null) {
+        if (err) {
           OError.tag(err, 'error renaming entity', {
-            project_id,
-            entity_id,
+            projectId,
+            entityId,
             entityType,
             newName,
           })
@@ -495,204 +455,177 @@ const EditorController = {
         }
         if (newName.length > 0) {
           EditorRealTimeController.emitToRoom(
-            project_id,
+            projectId,
             'reciveEntityRename',
-            entity_id,
+            entityId,
             newName
           )
         }
-        return callback()
+        callback()
       }
     )
   },
 
-  moveEntity(project_id, entity_id, folder_id, entityType, userId, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
+  moveEntity(
+    projectId,
+    entityId,
+    folderId,
+    entityType,
+    userId,
+    source,
+    callback
+  ) {
     Metrics.inc('editor.move-entity')
-    return ProjectEntityUpdateHandler.moveEntity(
-      project_id,
-      entity_id,
-      folder_id,
+    ProjectEntityUpdateHandler.moveEntity(
+      projectId,
+      entityId,
+      folderId,
       entityType,
       userId,
+      source,
       function (err) {
-        if (err != null) {
+        if (err) {
           OError.tag(err, 'error moving entity', {
-            project_id,
-            entity_id,
-            folder_id,
+            projectId,
+            entityId,
+            folderId,
           })
           return callback(err)
         }
         EditorRealTimeController.emitToRoom(
-          project_id,
+          projectId,
           'reciveEntityMove',
-          entity_id,
-          folder_id
+          entityId,
+          folderId
         )
-        return callback()
+        callback()
       }
     )
   },
 
-  renameProject(project_id, newName, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return ProjectDetailsHandler.renameProject(
-      project_id,
-      newName,
-      function (err) {
-        if (err != null) {
-          OError.tag(err, 'error renaming project', {
-            project_id,
-            newName,
-          })
-          return callback(err)
-        }
-        EditorRealTimeController.emitToRoom(
-          project_id,
-          'projectNameUpdated',
-          newName
-        )
-        return callback()
+  renameProject(projectId, newName, callback) {
+    ProjectDetailsHandler.renameProject(projectId, newName, function (err) {
+      if (err) {
+        OError.tag(err, 'error renaming project', {
+          projectId,
+          newName,
+        })
+        return callback(err)
       }
-    )
+      EditorRealTimeController.emitToRoom(
+        projectId,
+        'projectNameUpdated',
+        newName
+      )
+      callback()
+    })
   },
 
-  setCompiler(project_id, compiler, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return ProjectOptionsHandler.setCompiler(
-      project_id,
-      compiler,
-      function (err) {
-        if (err != null) {
-          return callback(err)
-        }
-        EditorRealTimeController.emitToRoom(
-          project_id,
-          'compilerUpdated',
-          compiler
-        )
-        return callback()
+  setCompiler(projectId, compiler, callback) {
+    ProjectOptionsHandler.setCompiler(projectId, compiler, function (err) {
+      if (err) {
+        return callback(err)
       }
-    )
+      EditorRealTimeController.emitToRoom(
+        projectId,
+        'compilerUpdated',
+        compiler
+      )
+      callback()
+    })
   },
 
-  setImageName(project_id, imageName, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return ProjectOptionsHandler.setImageName(
-      project_id,
-      imageName,
-      function (err) {
-        if (err != null) {
-          return callback(err)
-        }
-        EditorRealTimeController.emitToRoom(
-          project_id,
-          'imageNameUpdated',
-          imageName
-        )
-        return callback()
+  setImageName(projectId, imageName, callback) {
+    ProjectOptionsHandler.setImageName(projectId, imageName, function (err) {
+      if (err) {
+        return callback(err)
       }
-    )
+      EditorRealTimeController.emitToRoom(
+        projectId,
+        'imageNameUpdated',
+        imageName
+      )
+      callback()
+    })
   },
 
-  setSpellCheckLanguage(project_id, languageCode, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return ProjectOptionsHandler.setSpellCheckLanguage(
-      project_id,
+  setSpellCheckLanguage(projectId, languageCode, callback) {
+    ProjectOptionsHandler.setSpellCheckLanguage(
+      projectId,
       languageCode,
       function (err) {
-        if (err != null) {
+        if (err) {
           return callback(err)
         }
         EditorRealTimeController.emitToRoom(
-          project_id,
+          projectId,
           'spellCheckLanguageUpdated',
           languageCode
         )
-        return callback()
+        callback()
       }
     )
   },
 
-  setPublicAccessLevel(project_id, newAccessLevel, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return ProjectDetailsHandler.setPublicAccessLevel(
-      project_id,
+  setPublicAccessLevel(projectId, newAccessLevel, callback) {
+    ProjectDetailsHandler.setPublicAccessLevel(
+      projectId,
       newAccessLevel,
       function (err) {
-        if (err != null) {
+        if (err) {
           return callback(err)
         }
         EditorRealTimeController.emitToRoom(
-          project_id,
+          projectId,
           'project:publicAccessLevel:changed',
           { newAccessLevel }
         )
         if (newAccessLevel === PublicAccessLevels.TOKEN_BASED) {
-          return ProjectDetailsHandler.ensureTokensArePresent(
-            project_id,
+          ProjectDetailsHandler.ensureTokensArePresent(
+            projectId,
             function (err, tokens) {
-              if (err != null) {
+              if (err) {
                 return callback(err)
               }
               EditorRealTimeController.emitToRoom(
-                project_id,
+                projectId,
                 'project:tokens:changed',
                 { tokens }
               )
-              return callback()
+              callback()
             }
           )
         } else {
-          return callback()
+          callback()
         }
       }
     )
   },
 
-  setRootDoc(project_id, newRootDocID, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return ProjectEntityUpdateHandler.setRootDoc(
-      project_id,
+  setRootDoc(projectId, newRootDocID, callback) {
+    ProjectEntityUpdateHandler.setRootDoc(
+      projectId,
       newRootDocID,
       function (err) {
-        if (err != null) {
+        if (err) {
           return callback(err)
         }
         EditorRealTimeController.emitToRoom(
-          project_id,
+          projectId,
           'rootDocUpdated',
           newRootDocID
         )
-        return callback()
+        callback()
       }
     )
   },
 
-  _notifyProjectUsersOfNewFolders(project_id, folders, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return async.eachSeries(
+  _notifyProjectUsersOfNewFolders(projectId, folders, callback) {
+    async.eachSeries(
       folders,
       (folder, cb) =>
         EditorController._notifyProjectUsersOfNewFolder(
-          project_id,
+          projectId,
           folder.parentFolder_id,
           folder,
           null,
@@ -703,23 +636,20 @@ const EditorController = {
   },
 
   _notifyProjectUsersOfNewFolder(
-    project_id,
-    folder_id,
+    projectId,
+    folderId,
     folder,
     userId,
     callback
   ) {
-    if (callback == null) {
-      callback = function () {}
-    }
     EditorRealTimeController.emitToRoom(
-      project_id,
+      projectId,
       'reciveNewFolder',
-      folder_id,
+      folderId,
       folder,
       userId
     )
-    return callback()
+    callback()
   },
 }
 

+ 6 - 3
services/web/app/src/Features/Editor/EditorHttpController.js

@@ -212,7 +212,7 @@ async function renameEntity(req, res, next) {
   const projectId = req.params.Project_id
   const entityId = req.params.entity_id
   const entityType = req.params.entity_type
-  const { name } = req.body
+  const { name, source = 'editor' } = req.body
   if (!_nameIsAcceptableLength(name)) {
     return res.sendStatus(400)
   }
@@ -222,7 +222,8 @@ async function renameEntity(req, res, next) {
     entityId,
     entityType,
     name,
-    userId
+    userId,
+    source
   )
   res.sendStatus(204)
 }
@@ -232,13 +233,15 @@ async function moveEntity(req, res, next) {
   const entityId = req.params.entity_id
   const entityType = req.params.entity_type
   const folderId = req.body.folder_id
+  const source = req.body.source ?? 'editor'
   const userId = SessionManager.getLoggedInUserId(req.session)
   await EditorController.promises.moveEntity(
     projectId,
     entityId,
     folderId,
     entityType,
-    userId
+    userId,
+    source
   )
   res.sendStatus(204)
 }

+ 6 - 3
services/web/app/src/Features/Project/ProjectCreationHandler.js

@@ -112,7 +112,8 @@ async function _addExampleProjectFiles(ownerId, projectName, project) {
     project.rootFolder[0]._id,
     'sample.bib',
     bibDocLines,
-    ownerId
+    ownerId,
+    null
   )
 
   const frogPath = path.join(
@@ -125,7 +126,8 @@ async function _addExampleProjectFiles(ownerId, projectName, project) {
     'frog.jpg',
     frogPath,
     null,
-    ownerId
+    ownerId,
+    null
   )
 }
 
@@ -177,7 +179,8 @@ async function _createRootDoc(project, ownerId, docLines) {
       project.rootFolder[0]._id,
       'main.tex',
       docLines,
-      ownerId
+      ownerId,
+      null
     )
     await ProjectEntityUpdateHandler.promises.setRootDoc(project._id, doc._id)
   } catch (error) {

+ 2 - 1
services/web/app/src/Features/Project/ProjectDuplicator.js

@@ -205,6 +205,7 @@ async function _notifyDocumentUpdater(project, userId, changes) {
     project._id,
     projectHistoryId,
     userId,
-    changes
+    changes,
+    null
   )
 }

+ 70 - 21
services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js

@@ -298,7 +298,7 @@ const ProjectEntityUpdateHandler = {
     )
   },
 
-  addDoc(projectId, folderId, docName, docLines, userId, callback) {
+  addDoc(projectId, folderId, docName, docLines, userId, source, callback) {
     ProjectEntityUpdateHandler.addDocWithRanges(
       projectId,
       folderId,
@@ -306,6 +306,7 @@ const ProjectEntityUpdateHandler = {
       docLines,
       {},
       userId,
+      source,
       callback
     )
   },
@@ -319,6 +320,7 @@ const ProjectEntityUpdateHandler = {
         docLines,
         ranges,
         userId,
+        source,
         callback
       ) {
         if (!SafePath.isCleanFilename(docName)) {
@@ -345,6 +347,7 @@ const ProjectEntityUpdateHandler = {
               docLines,
               ranges,
               userId,
+              source,
               callback
             )
           }
@@ -359,6 +362,7 @@ const ProjectEntityUpdateHandler = {
       docLines,
       ranges,
       userId,
+      source,
       callback
     ) {
       ProjectEntityUpdateHandler._addDocAndSendToTpds(
@@ -386,6 +390,7 @@ const ProjectEntityUpdateHandler = {
             projectHistoryId,
             userId,
             { newDocs, newProject: project },
+            source,
             error => {
               if (error != null) {
                 return callback(error)
@@ -468,6 +473,7 @@ const ProjectEntityUpdateHandler = {
         fsPath,
         linkedFileData,
         userId,
+        source,
         callback
       ) {
         if (!SafePath.isCleanFilename(fileName)) {
@@ -492,6 +498,7 @@ const ProjectEntityUpdateHandler = {
               userId,
               fileRef,
               fileStoreUrl,
+              source,
               callback
             )
           }
@@ -507,6 +514,7 @@ const ProjectEntityUpdateHandler = {
       userId,
       fileRef,
       fileStoreUrl,
+      source,
       callback
     ) {
       ProjectEntityUpdateHandler._addFileAndSendToTpds(
@@ -533,6 +541,7 @@ const ProjectEntityUpdateHandler = {
             projectHistoryId,
             userId,
             { newFiles, newProject: project },
+            source,
             error => {
               if (error != null) {
                 return callback(error)
@@ -554,6 +563,7 @@ const ProjectEntityUpdateHandler = {
         fsPath,
         linkedFileData,
         userId,
+        source,
         callback
       ) {
         // create a new file
@@ -577,6 +587,7 @@ const ProjectEntityUpdateHandler = {
               userId,
               fileRef,
               fileStoreUrl,
+              source,
               callback
             )
           }
@@ -591,6 +602,7 @@ const ProjectEntityUpdateHandler = {
       userId,
       newFileRef,
       fileStoreUrl,
+      source,
       callback
     ) {
       ProjectEntityMongoUpdateHandler.replaceFileWithNew(
@@ -643,6 +655,7 @@ const ProjectEntityUpdateHandler = {
                 projectHistoryId,
                 userId,
                 { oldFiles, newFiles, newProject },
+                source,
                 callback
               )
             }
@@ -732,6 +745,7 @@ const ProjectEntityUpdateHandler = {
                         projectHistoryId,
                         userId,
                         { oldFiles, newDocs, newProject: project },
+                        source,
                         error => {
                           if (error != null) {
                             return callback(error)
@@ -786,6 +800,7 @@ const ProjectEntityUpdateHandler = {
             docLines,
             {},
             userId,
+            source,
             (err, doc) => {
               if (err != null) {
                 return callback(err)
@@ -807,6 +822,7 @@ const ProjectEntityUpdateHandler = {
         fsPath,
         linkedFileData,
         userId,
+        source,
         callback
       ) {
         if (!SafePath.isCleanFilename(fileName)) {
@@ -834,6 +850,7 @@ const ProjectEntityUpdateHandler = {
               userId,
               fileRef,
               fileStoreUrl,
+              source,
               callback
             )
           }
@@ -849,6 +866,7 @@ const ProjectEntityUpdateHandler = {
       userId,
       newFileRef,
       fileStoreUrl,
+      source,
       callback
     ) {
       ProjectLocator.findElement(
@@ -918,6 +936,7 @@ const ProjectEntityUpdateHandler = {
                             ],
                             newProject: project,
                           },
+                          source,
                           err => {
                             if (err) {
                               return callback(err)
@@ -947,6 +966,7 @@ const ProjectEntityUpdateHandler = {
               userId,
               newFileRef,
               fileStoreUrl,
+              source,
               err => {
                 if (err != null) {
                   return callback(err)
@@ -965,6 +985,7 @@ const ProjectEntityUpdateHandler = {
               userId,
               newFileRef,
               fileStoreUrl,
+              source,
               err => {
                 if (err != null) {
                   return callback(err)
@@ -1024,6 +1045,7 @@ const ProjectEntityUpdateHandler = {
         fsPath,
         linkedFileData,
         userId,
+        source,
         callback
       ) {
         if (!SafePath.isCleanPath(elementPath)) {
@@ -1053,6 +1075,7 @@ const ProjectEntityUpdateHandler = {
               userId,
               fileRef,
               fileStoreUrl,
+              source,
               callback
             )
           }
@@ -1068,6 +1091,7 @@ const ProjectEntityUpdateHandler = {
       userId,
       fileRef,
       fileStoreUrl,
+      source,
       callback
     ) {
       ProjectEntityUpdateHandler.mkdirp.withoutLock(
@@ -1087,6 +1111,7 @@ const ProjectEntityUpdateHandler = {
             userId,
             fileRef,
             fileStoreUrl,
+            source,
             (err, newFile, isNewFile, existingFile) => {
               if (err != null) {
                 return callback(err)
@@ -1111,6 +1136,7 @@ const ProjectEntityUpdateHandler = {
     entityId,
     entityType,
     userId,
+    source,
     callback
   ) {
     logger.debug({ entityId, entityType, projectId }, 'deleting project entity')
@@ -1134,6 +1160,7 @@ const ProjectEntityUpdateHandler = {
           entityType,
           path.fileSystem,
           userId,
+          source,
           error => {
             if (error != null) {
               return callback(error)
@@ -1157,25 +1184,27 @@ const ProjectEntityUpdateHandler = {
     )
   }),
 
-  deleteEntityWithPath: wrapWithLock((projectId, path, userId, callback) =>
-    ProjectLocator.findElementByPath(
-      { project_id: projectId, path },
-      (err, element, type) => {
-        if (err != null) {
-          return callback(err)
-        }
-        if (element == null) {
-          return callback(new Errors.NotFoundError('project not found'))
+  deleteEntityWithPath: wrapWithLock(
+    (projectId, path, userId, source, callback) =>
+      ProjectLocator.findElementByPath(
+        { project_id: projectId, path },
+        (err, element, type) => {
+          if (err != null) {
+            return callback(err)
+          }
+          if (element == null) {
+            return callback(new Errors.NotFoundError('project not found'))
+          }
+          ProjectEntityUpdateHandler.deleteEntity.withoutLock(
+            projectId,
+            element._id,
+            type,
+            userId,
+            source,
+            callback
+          )
         }
-        ProjectEntityUpdateHandler.deleteEntity.withoutLock(
-          projectId,
-          element._id,
-          type,
-          userId,
-          callback
-        )
-      }
-    )
+      )
   ),
 
   mkdirp: wrapWithLock(function (projectId, path, callback) {
@@ -1229,6 +1258,7 @@ const ProjectEntityUpdateHandler = {
     destFolderId,
     entityType,
     userId,
+    source,
     callback
   ) {
     logger.debug(
@@ -1270,6 +1300,7 @@ const ProjectEntityUpdateHandler = {
           projectHistoryId,
           userId,
           changes,
+          source,
           callback
         )
       }
@@ -1282,6 +1313,7 @@ const ProjectEntityUpdateHandler = {
     entityType,
     newName,
     userId,
+    source,
     callback
   ) {
     if (!SafePath.isCleanFilename(newName)) {
@@ -1324,6 +1356,7 @@ const ProjectEntityUpdateHandler = {
           projectHistoryId,
           userId,
           changes,
+          source,
           callback
         )
       }
@@ -1501,6 +1534,7 @@ const ProjectEntityUpdateHandler = {
             projectHistoryId,
             null,
             changes,
+            'automatic-fix',
             cb
           )
         }
@@ -1543,6 +1577,7 @@ const ProjectEntityUpdateHandler = {
     entityType,
     path,
     userId,
+    source,
     callback
   ) {
     ProjectEntityUpdateHandler._updateProjectStructureWithDeletedEntity(
@@ -1552,6 +1587,7 @@ const ProjectEntityUpdateHandler = {
       entityType,
       path,
       userId,
+      source,
       error => {
         if (error != null) {
           return callback(error)
@@ -1598,6 +1634,7 @@ const ProjectEntityUpdateHandler = {
     entityType,
     entityPath,
     userId,
+    source,
     callback
   ) {
     // compute the changes to the project structure
@@ -1636,6 +1673,7 @@ const ProjectEntityUpdateHandler = {
       projectHistoryId,
       userId,
       changes,
+      source,
       callback
     )
   },
@@ -1723,7 +1761,7 @@ const ProjectEntityUpdateHandler = {
 
   convertDocToFile: wrapWithLock({
     beforeLock(next) {
-      return function (projectId, docId, userId, callback) {
+      return function (projectId, docId, userId, source, callback) {
         DocumentUpdaterHandler.flushDocToMongo(projectId, docId, err => {
           if (err) {
             return callback(err)
@@ -1778,6 +1816,7 @@ const ProjectEntityUpdateHandler = {
                                 fileRef,
                                 fileStoreUrl,
                                 userId,
+                                source,
                                 callback
                               )
                             })
@@ -1793,7 +1832,16 @@ const ProjectEntityUpdateHandler = {
         })
       }
     },
-    withLock(projectId, doc, path, fileRef, fileStoreUrl, userId, callback) {
+    withLock(
+      projectId,
+      doc,
+      path,
+      fileRef,
+      fileStoreUrl,
+      userId,
+      source,
+      callback
+    ) {
       ProjectEntityMongoUpdateHandler.replaceDocWithFile(
         projectId,
         doc._id,
@@ -1815,6 +1863,7 @@ const ProjectEntityUpdateHandler = {
               newFiles: [{ file: fileRef, path, url: fileStoreUrl }],
               newProject: project,
             },
+            source,
             err => {
               if (err) {
                 return callback(err)

+ 2 - 1
services/web/app/src/Features/Uploads/ProjectUploadManager.js

@@ -204,6 +204,7 @@ async function _notifyDocumentUpdater(project, userId, changes) {
     project._id,
     projectHistoryId,
     userId,
-    changes
+    changes,
+    null
   )
 }

+ 2 - 1
services/web/scripts/convert_doc_to_file.js

@@ -21,7 +21,8 @@ async function main() {
     await ProjectEntityUpdateHandler.promises.convertDocToFile(
       projectId,
       docId,
-      userId
+      userId,
+      null
     )
   } catch (err) {
     if (err instanceof Errors.NotFoundError) {

+ 6 - 3
services/web/scripts/create_project.js

@@ -36,7 +36,8 @@ async function _createRootDoc(project, ownerId, docLines) {
       project.rootFolder[0]._id,
       'main.tex',
       docLines,
-      ownerId
+      ownerId,
+      null
     )
     await ProjectEntityUpdateHandler.promises.setRootDoc(project._id, doc._id)
   } catch (error) {
@@ -62,7 +63,8 @@ async function _addDefaultExampleProjectFiles(ownerId, projectName, project) {
     project.rootFolder[0]._id,
     'sample.bib',
     bibDocLines,
-    ownerId
+    ownerId,
+    null
   )
 
   const frogPath = path.join(
@@ -75,7 +77,8 @@ async function _addDefaultExampleProjectFiles(ownerId, projectName, project) {
     'frog.jpg',
     frogPath,
     null,
-    ownerId
+    ownerId,
+    null
   )
 }
 

+ 2 - 1
services/web/scripts/history/HistoryUpgradeHelper.js

@@ -304,7 +304,8 @@ async function convertLargeDocsToFile(projectId, userId) {
       await ProjectEntityUpdateHandler.promises.convertDocToFile(
         projectId,
         doc._id,
-        userId
+        userId,
+        null
       )
       convertedDocCount++
     }

+ 2 - 1
services/web/scripts/recover_docs_from_redis.js

@@ -123,7 +123,8 @@ async function processDoc(docId) {
         restoredName,
         doc.lines,
         doc.ranges,
-        null
+        null,
+        'recovery-script'
       )
       await deleteDocFromRedis(projectId, docId)
     } catch (err) {

+ 13 - 4
services/web/test/unit/src/DocumentUpdater/DocumentUpdaterHandlerTests.js

@@ -29,6 +29,7 @@ describe('DocumentUpdaterHandler', function () {
         },
       },
     }
+    this.source = 'dropbox'
 
     this.callback = sinon.stub()
     this.handler = SandboxedModule.require(modulePath, {
@@ -359,10 +360,6 @@ describe('DocumentUpdaterHandler', function () {
   })
 
   describe('setDocument', function () {
-    beforeEach(function () {
-      this.source = 'dropbox'
-    })
-
     describe('successfully', function () {
       beforeEach(function () {
         this.request.callsArgWith(1, null, { statusCode: 204 }, '')
@@ -852,6 +849,7 @@ describe('DocumentUpdaterHandler', function () {
           this.projectHistoryId,
           this.user_id,
           {},
+          this.source,
           this.callback
         )
       })
@@ -909,6 +907,7 @@ describe('DocumentUpdaterHandler', function () {
             this.projectHistoryId,
             this.user_id,
             this.changes,
+            this.source,
             () => {
               this.request
                 .calledWith({
@@ -919,6 +918,7 @@ describe('DocumentUpdaterHandler', function () {
                     userId: this.user_id,
                     version: this.version,
                     projectHistoryId: this.projectHistoryId,
+                    source: this.source,
                   },
                   timeout: 30 * 1000,
                 })
@@ -955,6 +955,7 @@ describe('DocumentUpdaterHandler', function () {
             this.projectHistoryId,
             this.user_id,
             this.changes,
+            this.source,
             () => {
               this.request
                 .calledWith({
@@ -965,6 +966,7 @@ describe('DocumentUpdaterHandler', function () {
                     userId: this.user_id,
                     version: this.version,
                     projectHistoryId: this.projectHistoryId,
+                    source: this.source,
                   },
                   timeout: 30 * 1000,
                 })
@@ -1005,6 +1007,7 @@ describe('DocumentUpdaterHandler', function () {
             this.projectHistoryId,
             this.user_id,
             this.changes,
+            this.source,
             () => {
               this.request
                 .calledWith({
@@ -1015,6 +1018,7 @@ describe('DocumentUpdaterHandler', function () {
                     userId: this.user_id,
                     version: this.version,
                     projectHistoryId: this.projectHistoryId,
+                    source: this.source,
                   },
                   timeout: 30 * 1000,
                 })
@@ -1049,6 +1053,7 @@ describe('DocumentUpdaterHandler', function () {
             this.projectHistoryId,
             this.user_id,
             this.changes,
+            this.source,
             () => {
               this.request
                 .calledWith({
@@ -1059,6 +1064,7 @@ describe('DocumentUpdaterHandler', function () {
                     userId: this.user_id,
                     version: this.version,
                     projectHistoryId: this.projectHistoryId,
+                    source: this.source,
                   },
                   timeout: 30 * 1000,
                 })
@@ -1113,6 +1119,7 @@ describe('DocumentUpdaterHandler', function () {
             this.projectHistoryId,
             this.user_id,
             this.changes,
+            this.source,
             () => {
               this.request.should.have.been.calledWith({
                 url: this.url,
@@ -1122,6 +1129,7 @@ describe('DocumentUpdaterHandler', function () {
                   userId: this.user_id,
                   version: this.version,
                   projectHistoryId: this.projectHistoryId,
+                  source: this.source,
                 },
                 timeout: 30 * 1000,
               })
@@ -1145,6 +1153,7 @@ describe('DocumentUpdaterHandler', function () {
             this.projectHistoryId,
             this.user_id,
             this.changes,
+            this.source,
             this.callback
           )
 

+ 24 - 8
services/web/test/unit/src/Editor/EditorControllerTests.js

@@ -96,7 +96,9 @@ describe('EditorController', function () {
           this.folder_id,
           this.docName,
           this.docLines,
-          {}
+          {},
+          this.user_id,
+          this.source
         )
         .should.equal(true)
     })
@@ -144,7 +146,8 @@ describe('EditorController', function () {
           this.fileName,
           this.fsPath,
           this.linkedFileData,
-          this.user_id
+          this.user_id,
+          this.source
         )
         .should.equal(true)
     })
@@ -256,7 +259,8 @@ describe('EditorController', function () {
           this.fileName,
           this.fsPath,
           this.linkedFileData,
-          this.user_id
+          this.user_id,
+          this.source
         )
         .should.equal(true)
     })
@@ -414,7 +418,9 @@ describe('EditorController', function () {
           this.project_id,
           this.filePath,
           this.fsPath,
-          this.linkedFileData
+          this.linkedFileData,
+          this.user_id,
+          this.source
         )
         .should.equal(true)
     })
@@ -583,7 +589,13 @@ describe('EditorController', function () {
 
     it('should delete the folder using the project entity handler', function () {
       return this.ProjectEntityUpdateHandler.deleteEntity
-        .calledWith(this.project_id, this.entity_id, this.type, this.user_id)
+        .calledWith(
+          this.project_id,
+          this.entity_id,
+          this.type,
+          this.user_id,
+          this.source
+        )
         .should.equal(true)
     })
 
@@ -617,7 +629,7 @@ describe('EditorController', function () {
 
     it('should delete the folder using the project entity handler', function () {
       return this.ProjectEntityUpdateHandler.deleteEntityWithPath
-        .calledWith(this.project_id, this.path, this.user_id)
+        .calledWith(this.project_id, this.path, this.user_id, this.source)
         .should.equal(true)
     })
 
@@ -692,6 +704,7 @@ describe('EditorController', function () {
         this.entityType,
         this.newName,
         this.user_id,
+        this.source,
         done
       )
     })
@@ -703,7 +716,8 @@ describe('EditorController', function () {
           this.entity_id,
           this.entityType,
           this.newName,
-          this.user_id
+          this.user_id,
+          this.source
         )
         .should.equal(true)
     })
@@ -731,6 +745,7 @@ describe('EditorController', function () {
         this.folder_id,
         this.entityType,
         this.user_id,
+        this.source,
         this.callback
       )
     })
@@ -742,7 +757,8 @@ describe('EditorController', function () {
           this.entity_id,
           this.folder_id,
           this.entityType,
-          this.user_id
+          this.user_id,
+          this.source
         )
         .should.equal(true)
     })

+ 9 - 6
services/web/test/unit/src/Editor/EditorHttpControllerTests.js

@@ -34,6 +34,7 @@ describe('EditorHttpController', function () {
     this.doc = { _id: new ObjectId(), name: 'excellent-original-idea.tex' }
     this.file = { _id: new ObjectId() }
     this.folder = { _id: new ObjectId() }
+    this.source = 'editor'
 
     this.parentFolderId = 'mock-folder-id'
     this.req = { i18n: { translate: string => string } }
@@ -405,7 +406,7 @@ describe('EditorHttpController', function () {
     describe('successfully', function () {
       beforeEach(function (done) {
         this.newName = 'new-name'
-        this.req.body = { name: this.newName }
+        this.req.body = { name: this.newName, source: this.source }
         this.res.sendStatus.callsFake(() => done())
         this.EditorHttpController.renameEntity(this.req, this.res)
       })
@@ -418,7 +419,8 @@ describe('EditorHttpController', function () {
           this.entityId,
           this.entityType,
           this.newName,
-          this.user._id
+          this.user._id,
+          this.source
         )
       })
 
@@ -429,7 +431,7 @@ describe('EditorHttpController', function () {
     describe('with long name', function () {
       beforeEach(function () {
         this.newName = 'long'.repeat(100)
-        this.req.body = { name: this.newName }
+        this.req.body = { name: this.newName, source: this.source }
         this.EditorHttpController.renameEntity(this.req, this.res)
       })
 
@@ -441,7 +443,7 @@ describe('EditorHttpController', function () {
     describe('with 0 length name', function () {
       beforeEach(function () {
         this.newName = ''
-        this.req.body = { name: this.newName }
+        this.req.body = { name: this.newName, source: this.source }
         this.EditorHttpController.renameEntity(this.req, this.res)
       })
 
@@ -461,7 +463,7 @@ describe('EditorHttpController', function () {
         entity_id: this.entityId,
         entity_type: this.entityType,
       }
-      this.req.body = { folder_id: this.folderId }
+      this.req.body = { folder_id: this.folderId, source: this.source }
       this.res.sendStatus.callsFake(() => done())
       this.EditorHttpController.moveEntity(this.req, this.res)
     })
@@ -472,7 +474,8 @@ describe('EditorHttpController', function () {
         this.entityId,
         this.folderId,
         this.entityType,
-        this.user._id
+        this.user._id,
+        this.source
       )
     })
 

+ 2 - 1
services/web/test/unit/src/Project/ProjectDuplicatorTests.js

@@ -303,7 +303,8 @@ describe('ProjectDuplicator', function () {
           newDocs: this.docEntries,
           newFiles: this.fileEntries,
           newProject: { version: this.newProjectVersion },
-        }
+        },
+        null
       )
     })
 

+ 1 - 0
services/web/test/unit/src/Project/ProjectEntityRestoreHandlerTests.js

@@ -18,6 +18,7 @@ describe('ProjectEntityRestoreHandler', function () {
     }
 
     this.docId = '4eecb1c1bffa66588e0000a2'
+
     this.DocModel = class Doc {
       constructor(options) {
         this.name = options.name

+ 146 - 48
services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js

@@ -531,6 +531,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.docName,
           this.docLines,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -550,10 +551,16 @@ describe('ProjectEntityUpdateHandler', function () {
           },
         ]
         this.DocumentUpdaterHandler.updateProjectStructure
-          .calledWith(projectId, projectHistoryId, userId, {
-            newDocs,
-            newProject: this.project,
-          })
+          .calledWith(
+            projectId,
+            projectHistoryId,
+            userId,
+            {
+              newDocs,
+              newProject: this.project,
+            },
+            this.source
+          )
           .should.equal(true)
       })
     })
@@ -569,6 +576,7 @@ describe('ProjectEntityUpdateHandler', function () {
           `*${this.docName}`,
           this.docLines,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -609,6 +617,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.fileSystemPath,
           this.linkedFileData,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -661,10 +670,16 @@ describe('ProjectEntityUpdateHandler', function () {
           },
         ]
         this.DocumentUpdaterHandler.updateProjectStructure
-          .calledWith(projectId, projectHistoryId, userId, {
-            newFiles,
-            newProject: this.project,
-          })
+          .calledWith(
+            projectId,
+            projectHistoryId,
+            userId,
+            {
+              newFiles,
+              newProject: this.project,
+            },
+            this.source
+          )
           .should.equal(true)
       })
     })
@@ -692,6 +707,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.fileSystemPath,
           this.linkedFileData,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -735,6 +751,7 @@ describe('ProjectEntityUpdateHandler', function () {
         this.fileSystemPath,
         this.linkedFileData,
         userId,
+        this.source,
         this.callback
       )
     })
@@ -792,11 +809,17 @@ describe('ProjectEntityUpdateHandler', function () {
         },
       ]
       this.DocumentUpdaterHandler.updateProjectStructure
-        .calledWith(projectId, projectHistoryId, userId, {
-          oldFiles,
-          newFiles,
-          newProject: this.newProject,
-        })
+        .calledWith(
+          projectId,
+          projectHistoryId,
+          userId,
+          {
+            oldFiles,
+            newFiles,
+            newProject: this.newProject,
+          },
+          this.source
+        )
         .should.equal(true)
     })
   })
@@ -916,7 +939,8 @@ describe('ProjectEntityUpdateHandler', function () {
             this.docName,
             this.docLines,
             {},
-            userId
+            userId,
+            this.source
           )
           .should.equal(true)
       })
@@ -1035,11 +1059,17 @@ describe('ProjectEntityUpdateHandler', function () {
         ]
         expect(
           this.DocumentUpdaterHandler.updateProjectStructure
-        ).to.have.been.calledWith(projectId, projectHistoryId, userId, {
-          oldFiles,
-          newDocs,
-          newProject: this.newProject,
-        })
+        ).to.have.been.calledWith(
+          projectId,
+          projectHistoryId,
+          userId,
+          {
+            oldFiles,
+            newDocs,
+            newProject: this.newProject,
+          },
+          this.source
+        )
       })
 
       it('should notify everyone of the file deletion', function () {
@@ -1074,6 +1104,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.fileSystemPath,
           this.linkedFileData,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -1100,6 +1131,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.fileSystemPath,
           this.linkedFileData,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -1112,7 +1144,10 @@ describe('ProjectEntityUpdateHandler', function () {
           fileId,
           this.fileSystemPath,
           this.linkedFileData,
-          userId
+          userId,
+          this.newFile,
+          this.fileUrl,
+          this.source
         )
       })
 
@@ -1126,6 +1161,11 @@ describe('ProjectEntityUpdateHandler', function () {
         this.folder = { _id: folderId, fileRefs: [], docs: [] }
         this.newFile = { _id: fileId }
         this.ProjectLocator.findElement.yields(null, this.folder)
+        this.FileStoreHandler.uploadFileFromDisk.yields(
+          null,
+          this.fileUrl,
+          this.newFile
+        )
         this.ProjectEntityUpdateHandler.addFile = {
           mainTask: sinon.stub().yields(null, this.newFile),
         }
@@ -1137,6 +1177,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.fileSystemPath,
           this.linkedFileData,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -1152,16 +1193,19 @@ describe('ProjectEntityUpdateHandler', function () {
       })
 
       it('adds the file', function () {
-        this.ProjectEntityUpdateHandler.addFile.mainTask
-          .calledWith(
-            projectId,
-            folderId,
-            this.fileName,
-            this.fileSystemPath,
-            this.linkedFileData,
-            userId
-          )
-          .should.equal(true)
+        expect(
+          this.ProjectEntityUpdateHandler.addFile.mainTask
+        ).to.have.been.calledWith(
+          projectId,
+          folderId,
+          this.fileName,
+          this.fileSystemPath,
+          this.linkedFileData,
+          userId,
+          this.newFile,
+          this.fileUrl,
+          this.source
+        )
       })
 
       it('returns the file', function () {
@@ -1185,6 +1229,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.fileSystemPath,
           this.linkedFileData,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -1247,6 +1292,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.fileSystemPath,
           this.linkedFileData,
           userId,
+          this.source,
           done
         )
       })
@@ -1278,7 +1324,13 @@ describe('ProjectEntityUpdateHandler', function () {
         }
         expect(
           this.DocumentUpdaterHandler.updateProjectStructure
-        ).to.have.been.calledWith(projectId, projectHistoryId, userId, updates)
+        ).to.have.been.calledWith(
+          projectId,
+          projectHistoryId,
+          userId,
+          updates,
+          this.source
+        )
       })
 
       it('tells everyone in the room the doc is removed', function () {
@@ -1438,6 +1490,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.fileSystemPath,
           this.linkedFileData,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -1456,7 +1509,10 @@ describe('ProjectEntityUpdateHandler', function () {
             'file.png',
             this.fileSystemPath,
             this.linkedFileData,
-            userId
+            userId,
+            this.newFile,
+            this.fileUrl,
+            this.source
           )
           .should.equal(true)
       })
@@ -1495,6 +1551,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.fileSystemPath,
           this.linkedFileData,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -1525,6 +1582,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.fileSystemPath,
           this.linkedFileData,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -1556,6 +1614,7 @@ describe('ProjectEntityUpdateHandler', function () {
         docId,
         'doc',
         userId,
+        this.source,
         this.callback
       )
     })
@@ -1574,7 +1633,8 @@ describe('ProjectEntityUpdateHandler', function () {
           this.doc,
           'doc',
           this.path,
-          userId
+          userId,
+          this.source
         )
         .should.equal(true)
     })
@@ -1607,6 +1667,7 @@ describe('ProjectEntityUpdateHandler', function () {
           projectId,
           this.path,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -1619,7 +1680,14 @@ describe('ProjectEntityUpdateHandler', function () {
 
       it('deletes the entity', function () {
         this.ProjectEntityUpdateHandler.deleteEntity.withoutLock
-          .calledWith(projectId, this.doc._id, 'doc', userId, this.callback)
+          .calledWith(
+            projectId,
+            this.doc._id,
+            'doc',
+            userId,
+            this.source,
+            this.callback
+          )
           .should.equal(true)
       })
     })
@@ -1632,6 +1700,7 @@ describe('ProjectEntityUpdateHandler', function () {
           projectId,
           this.path,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -1743,6 +1812,7 @@ describe('ProjectEntityUpdateHandler', function () {
         folderId,
         'doc',
         userId,
+        this.source,
         this.callback
       )
     })
@@ -1772,6 +1842,7 @@ describe('ProjectEntityUpdateHandler', function () {
           projectHistoryId,
           userId,
           this.changes,
+          this.source,
           this.callback
         )
         .should.equal(true)
@@ -1802,6 +1873,7 @@ describe('ProjectEntityUpdateHandler', function () {
           'doc',
           this.newDocName,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -1831,6 +1903,7 @@ describe('ProjectEntityUpdateHandler', function () {
             projectHistoryId,
             userId,
             this.changes,
+            this.source,
             this.callback
           )
           .should.equal(true)
@@ -1860,6 +1933,7 @@ describe('ProjectEntityUpdateHandler', function () {
           'doc',
           this.newDocName,
           userId,
+          this.source,
           this.callback
         )
       })
@@ -2315,6 +2389,7 @@ describe('ProjectEntityUpdateHandler', function () {
           'file',
           this.path,
           userId,
+          this.source,
           done
         )
       })
@@ -2338,10 +2413,16 @@ describe('ProjectEntityUpdateHandler', function () {
       it('should 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,
-          })
+          .calledWith(
+            projectId,
+            projectHistoryId,
+            userId,
+            {
+              oldFiles,
+              newProject: this.newProject,
+            },
+            this.source
+          )
           .should.equal(true)
       })
     })
@@ -2359,6 +2440,7 @@ describe('ProjectEntityUpdateHandler', function () {
           'doc',
           this.path,
           userId,
+          this.source,
           done
         )
       })
@@ -2372,10 +2454,16 @@ describe('ProjectEntityUpdateHandler', function () {
       it('should 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,
-          })
+          .calledWith(
+            projectId,
+            projectHistoryId,
+            userId,
+            {
+              oldDocs,
+              newProject: this.newProject,
+            },
+            this.source
+          )
           .should.equal(true)
       })
     })
@@ -2408,6 +2496,7 @@ describe('ProjectEntityUpdateHandler', function () {
           'folder',
           path,
           userId,
+          this.source,
           done
         )
       })
@@ -2450,11 +2539,17 @@ describe('ProjectEntityUpdateHandler', function () {
           { doc: this.doc1, path: '/folder/subfolder/doc-name-1' },
         ]
         this.DocumentUpdaterHandler.updateProjectStructure
-          .calledWith(projectId, projectHistoryId, userId, {
-            oldFiles,
-            oldDocs,
-            newProject: this.newProject,
-          })
+          .calledWith(
+            projectId,
+            projectHistoryId,
+            userId,
+            {
+              oldFiles,
+              oldDocs,
+              newProject: this.newProject,
+            },
+            this.source
+          )
           .should.equal(true)
       })
     })
@@ -2571,6 +2666,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.project._id,
           this.doc._id,
           this.user._id,
+          this.source,
           done
         )
       })
@@ -2615,7 +2711,8 @@ describe('ProjectEntityUpdateHandler', function () {
               { file: this.file, path: this.path, url: this.fileStoreUrl },
             ],
             newProject: this.project,
-          }
+          },
+          this.source
         )
       })
 
@@ -2654,6 +2751,7 @@ describe('ProjectEntityUpdateHandler', function () {
           this.project._id,
           this.doc._id,
           this.user._id,
+          this.source,
           err => {
             expect(err).to.be.instanceof(Errors.DocHasRangesError)
             done()

+ 4 - 2
services/web/test/unit/src/Uploads/ProjectUploadManagerTests.js

@@ -211,7 +211,8 @@ describe('ProjectUploadManager', function () {
             newDocs: this.docEntries,
             newFiles: this.fileEntries,
             newProject: { version: this.newProjectVersion },
-          }
+          },
+          null
         )
       })
 
@@ -298,7 +299,8 @@ describe('ProjectUploadManager', function () {
           newDocs: this.docEntries,
           newFiles: this.fileEntries,
           newProject: { version: this.newProjectVersion },
-        }
+        },
+        null
       )
     })