mirror of
https://github.com/overleaf/overleaf.git
synced 2024-10-31 21:21:03 -04:00
Merge pull request #29 from sharelatex/revert-28-revert-26-hof-deleted-project-history
Add projectHistoryId to updates
This commit is contained in:
commit
2ce19b13e3
17 changed files with 163 additions and 115 deletions
|
@ -13,39 +13,39 @@ async = require "async"
|
||||||
MAX_UNFLUSHED_AGE = 300 * 1000 # 5 mins, document should be flushed to mongo this time after a change
|
MAX_UNFLUSHED_AGE = 300 * 1000 # 5 mins, document should be flushed to mongo this time after a change
|
||||||
|
|
||||||
module.exports = DocumentManager =
|
module.exports = DocumentManager =
|
||||||
getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges, pathname, unflushedTime, alreadyLoaded) ->) ->
|
getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime, alreadyLoaded) ->) ->
|
||||||
timer = new Metrics.Timer("docManager.getDoc")
|
timer = new Metrics.Timer("docManager.getDoc")
|
||||||
callback = (args...) ->
|
callback = (args...) ->
|
||||||
timer.done()
|
timer.done()
|
||||||
_callback(args...)
|
_callback(args...)
|
||||||
|
|
||||||
RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, unflushedTime) ->
|
RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if !lines? or !version?
|
if !lines? or !version?
|
||||||
logger.log {project_id, doc_id}, "doc not in redis so getting from persistence API"
|
logger.log {project_id, doc_id}, "doc not in redis so getting from persistence API"
|
||||||
PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) ->
|
PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
logger.log {project_id, doc_id, lines, version, pathname}, "got doc from persistence API"
|
logger.log {project_id, doc_id, lines, version, pathname, projectHistoryId}, "got doc from persistence API"
|
||||||
RedisManager.putDocInMemory project_id, doc_id, lines, version, ranges, pathname, (error) ->
|
RedisManager.putDocInMemory project_id, doc_id, lines, version, ranges, pathname, projectHistoryId, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback null, lines, version, ranges, pathname, null, false
|
callback null, lines, version, ranges, pathname, projectHistoryId, null, false
|
||||||
else
|
else
|
||||||
callback null, lines, version, ranges, pathname, unflushedTime, true
|
callback null, lines, version, ranges, pathname, projectHistoryId, unflushedTime, true
|
||||||
|
|
||||||
getDocAndRecentOps: (project_id, doc_id, fromVersion, _callback = (error, lines, version, ops, ranges, pathname) ->) ->
|
getDocAndRecentOps: (project_id, doc_id, fromVersion, _callback = (error, lines, version, ops, ranges, pathname, projectHistoryId) ->) ->
|
||||||
timer = new Metrics.Timer("docManager.getDocAndRecentOps")
|
timer = new Metrics.Timer("docManager.getDocAndRecentOps")
|
||||||
callback = (args...) ->
|
callback = (args...) ->
|
||||||
timer.done()
|
timer.done()
|
||||||
_callback(args...)
|
_callback(args...)
|
||||||
|
|
||||||
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) ->
|
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if fromVersion == -1
|
if fromVersion == -1
|
||||||
callback null, lines, version, [], ranges, pathname
|
callback null, lines, version, [], ranges, pathname, projectHistoryId
|
||||||
else
|
else
|
||||||
RedisManager.getPreviousDocOps doc_id, fromVersion, version, (error, ops) ->
|
RedisManager.getPreviousDocOps doc_id, fromVersion, version, (error, ops) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback null, lines, version, ops, ranges, pathname
|
callback null, lines, version, ops, ranges, pathname, projectHistoryId
|
||||||
|
|
||||||
setDoc: (project_id, doc_id, newLines, source, user_id, undoing, _callback = (error) ->) ->
|
setDoc: (project_id, doc_id, newLines, source, user_id, undoing, _callback = (error) ->) ->
|
||||||
timer = new Metrics.Timer("docManager.setDoc")
|
timer = new Metrics.Timer("docManager.setDoc")
|
||||||
|
@ -57,7 +57,7 @@ module.exports = DocumentManager =
|
||||||
return callback(new Error("No lines were provided to setDoc"))
|
return callback(new Error("No lines were provided to setDoc"))
|
||||||
|
|
||||||
UpdateManager = require "./UpdateManager"
|
UpdateManager = require "./UpdateManager"
|
||||||
DocumentManager.getDoc project_id, doc_id, (error, oldLines, version, ranges, pathname, unflushedTime, alreadyLoaded) ->
|
DocumentManager.getDoc project_id, doc_id, (error, oldLines, version, ranges, pathname, projectHistoryId, unflushedTime, alreadyLoaded) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
|
|
||||||
if oldLines? and oldLines.length > 0 and oldLines[0].text?
|
if oldLines? and oldLines.length > 0 and oldLines[0].text?
|
||||||
|
@ -161,16 +161,16 @@ module.exports = DocumentManager =
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
renameDoc: (project_id, doc_id, user_id, update, _callback = (error) ->) ->
|
renameDoc: (project_id, doc_id, user_id, update, projectHistoryId, _callback = (error) ->) ->
|
||||||
timer = new Metrics.Timer("docManager.updateProject")
|
timer = new Metrics.Timer("docManager.updateProject")
|
||||||
callback = (args...) ->
|
callback = (args...) ->
|
||||||
timer.done()
|
timer.done()
|
||||||
_callback(args...)
|
_callback(args...)
|
||||||
|
|
||||||
RedisManager.renameDoc project_id, doc_id, user_id, update, callback
|
RedisManager.renameDoc project_id, doc_id, user_id, update, projectHistoryId, callback
|
||||||
|
|
||||||
getDocAndFlushIfOld: (project_id, doc_id, callback = (error, doc) ->) ->
|
getDocAndFlushIfOld: (project_id, doc_id, callback = (error, doc) ->) ->
|
||||||
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, unflushedTime, alreadyLoaded) ->
|
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime, alreadyLoaded) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
# if doc was already loaded see if it needs to be flushed
|
# if doc was already loaded see if it needs to be flushed
|
||||||
if alreadyLoaded and unflushedTime? and (Date.now() - unflushedTime) > MAX_UNFLUSHED_AGE
|
if alreadyLoaded and unflushedTime? and (Date.now() - unflushedTime) > MAX_UNFLUSHED_AGE
|
||||||
|
@ -181,21 +181,21 @@ module.exports = DocumentManager =
|
||||||
callback(null, lines, version)
|
callback(null, lines, version)
|
||||||
|
|
||||||
resyncDocContents: (project_id, doc_id, callback) ->
|
resyncDocContents: (project_id, doc_id, callback) ->
|
||||||
RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) ->
|
RedisManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
|
|
||||||
if !lines? or !version?
|
if !lines? or !version?
|
||||||
PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) ->
|
PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
ProjectHistoryRedisManager.queueResyncDocContent project_id, doc_id, lines, version, pathname, callback
|
ProjectHistoryRedisManager.queueResyncDocContent project_id, projectHistoryId, doc_id, lines, version, pathname, callback
|
||||||
else
|
else
|
||||||
ProjectHistoryRedisManager.queueResyncDocContent project_id, doc_id, lines, version, pathname, callback
|
ProjectHistoryRedisManager.queueResyncDocContent project_id, projectHistoryId, doc_id, lines, version, pathname, callback
|
||||||
|
|
||||||
getDocWithLock: (project_id, doc_id, callback = (error, lines, version) ->) ->
|
getDocWithLock: (project_id, doc_id, callback = (error, lines, version) ->) ->
|
||||||
UpdateManager = require "./UpdateManager"
|
UpdateManager = require "./UpdateManager"
|
||||||
UpdateManager.lockUpdatesAndDo DocumentManager.getDoc, project_id, doc_id, callback
|
UpdateManager.lockUpdatesAndDo DocumentManager.getDoc, project_id, doc_id, callback
|
||||||
|
|
||||||
getDocAndRecentOpsWithLock: (project_id, doc_id, fromVersion, callback = (error, lines, version, ops, ranges, pathname) ->) ->
|
getDocAndRecentOpsWithLock: (project_id, doc_id, fromVersion, callback = (error, lines, version, ops, ranges, pathname, projectHistoryId) ->) ->
|
||||||
UpdateManager = require "./UpdateManager"
|
UpdateManager = require "./UpdateManager"
|
||||||
UpdateManager.lockUpdatesAndDo DocumentManager.getDocAndRecentOps, project_id, doc_id, fromVersion, callback
|
UpdateManager.lockUpdatesAndDo DocumentManager.getDocAndRecentOps, project_id, doc_id, fromVersion, callback
|
||||||
|
|
||||||
|
@ -223,9 +223,9 @@ module.exports = DocumentManager =
|
||||||
UpdateManager = require "./UpdateManager"
|
UpdateManager = require "./UpdateManager"
|
||||||
UpdateManager.lockUpdatesAndDo DocumentManager.deleteComment, project_id, doc_id, thread_id, callback
|
UpdateManager.lockUpdatesAndDo DocumentManager.deleteComment, project_id, doc_id, thread_id, callback
|
||||||
|
|
||||||
renameDocWithLock: (project_id, doc_id, user_id, update, callback = (error) ->) ->
|
renameDocWithLock: (project_id, doc_id, user_id, update, projectHistoryId, callback = (error) ->) ->
|
||||||
UpdateManager = require "./UpdateManager"
|
UpdateManager = require "./UpdateManager"
|
||||||
UpdateManager.lockUpdatesAndDo DocumentManager.renameDoc, project_id, doc_id, user_id, update, callback
|
UpdateManager.lockUpdatesAndDo DocumentManager.renameDoc, project_id, doc_id, user_id, update, projectHistoryId, callback
|
||||||
|
|
||||||
resyncDocContentsWithLock: (project_id, doc_id, callback = (error) ->) ->
|
resyncDocContentsWithLock: (project_id, doc_id, callback = (error) ->) ->
|
||||||
UpdateManager = require "./UpdateManager"
|
UpdateManager = require "./UpdateManager"
|
||||||
|
|
|
@ -65,8 +65,8 @@ module.exports = HistoryManager =
|
||||||
newBlock = Math.floor(length / threshold)
|
newBlock = Math.floor(length / threshold)
|
||||||
return newBlock != prevBlock
|
return newBlock != prevBlock
|
||||||
|
|
||||||
resyncProjectHistory: (project_id, docs, files, callback) ->
|
resyncProjectHistory: (project_id, projectHistoryId, docs, files, callback) ->
|
||||||
ProjectHistoryRedisManager.queueResyncProjectStructure project_id, docs, files, (error) ->
|
ProjectHistoryRedisManager.queueResyncProjectStructure project_id, projectHistoryId, docs, files, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
DocumentManager = require "./DocumentManager"
|
DocumentManager = require "./DocumentManager"
|
||||||
resyncDoc = (doc, cb) ->
|
resyncDoc = (doc, cb) ->
|
||||||
|
|
|
@ -161,10 +161,10 @@ module.exports = HttpController =
|
||||||
updateProject: (req, res, next = (error) ->) ->
|
updateProject: (req, res, next = (error) ->) ->
|
||||||
timer = new Metrics.Timer("http.updateProject")
|
timer = new Metrics.Timer("http.updateProject")
|
||||||
project_id = req.params.project_id
|
project_id = req.params.project_id
|
||||||
{userId, docUpdates, fileUpdates, version} = req.body
|
{projectHistoryId, userId, docUpdates, fileUpdates, version} = req.body
|
||||||
logger.log {project_id, docUpdates, fileUpdates, version}, "updating project via http"
|
logger.log {project_id, docUpdates, fileUpdates, version}, "updating project via http"
|
||||||
|
|
||||||
ProjectManager.updateProjectWithLocks project_id, userId, docUpdates, fileUpdates, version, (error) ->
|
ProjectManager.updateProjectWithLocks project_id, projectHistoryId, userId, docUpdates, fileUpdates, version, (error) ->
|
||||||
timer.done()
|
timer.done()
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
logger.log project_id: project_id, "updated project via http"
|
logger.log project_id: project_id, "updated project via http"
|
||||||
|
@ -172,10 +172,10 @@ module.exports = HttpController =
|
||||||
|
|
||||||
resyncProjectHistory: (req, res, next = (error) ->) ->
|
resyncProjectHistory: (req, res, next = (error) ->) ->
|
||||||
project_id = req.params.project_id
|
project_id = req.params.project_id
|
||||||
{docs, files} = req.body
|
{projectHistoryId, docs, files} = req.body
|
||||||
|
|
||||||
logger.log {project_id, docs, files}, "queuing project history resync via http"
|
logger.log {project_id, docs, files}, "queuing project history resync via http"
|
||||||
HistoryManager.resyncProjectHistory project_id, docs, files, (error) ->
|
HistoryManager.resyncProjectHistory project_id, projectHistoryId, docs, files, (error) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
logger.log {project_id}, "queued project history resync via http"
|
logger.log {project_id}, "queued project history resync via http"
|
||||||
res.send 204
|
res.send 204
|
||||||
|
|
|
@ -13,7 +13,7 @@ request = (require("requestretry")).defaults({
|
||||||
MAX_HTTP_REQUEST_LENGTH = 5000 # 5 seconds
|
MAX_HTTP_REQUEST_LENGTH = 5000 # 5 seconds
|
||||||
|
|
||||||
module.exports = PersistenceManager =
|
module.exports = PersistenceManager =
|
||||||
getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges, pathname) ->) ->
|
getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges, pathname, projectHistoryId) ->) ->
|
||||||
timer = new Metrics.Timer("persistenceManager.getDoc")
|
timer = new Metrics.Timer("persistenceManager.getDoc")
|
||||||
callback = (args...) ->
|
callback = (args...) ->
|
||||||
timer.done()
|
timer.done()
|
||||||
|
@ -44,7 +44,7 @@ module.exports = PersistenceManager =
|
||||||
return callback(new Error("web API response had no valid doc version"))
|
return callback(new Error("web API response had no valid doc version"))
|
||||||
if !body.pathname?
|
if !body.pathname?
|
||||||
return callback(new Error("web API response had no valid doc pathname"))
|
return callback(new Error("web API response had no valid doc pathname"))
|
||||||
return callback null, body.lines, body.version, body.ranges, body.pathname
|
return callback null, body.lines, body.version, body.ranges, body.pathname, body.projectHistoryId
|
||||||
else if res.statusCode == 404
|
else if res.statusCode == 404
|
||||||
return callback(new Errors.NotFoundError("doc not not found: #{url}"))
|
return callback(new Errors.NotFoundError("doc not not found: #{url}"))
|
||||||
else
|
else
|
||||||
|
|
|
@ -7,7 +7,7 @@ module.exports = ProjectHistoryRedisManager =
|
||||||
queueOps: (project_id, ops..., callback) ->
|
queueOps: (project_id, ops..., callback) ->
|
||||||
rclient.rpush projectHistoryKeys.projectHistoryOps({project_id}), ops..., callback
|
rclient.rpush projectHistoryKeys.projectHistoryOps({project_id}), ops..., callback
|
||||||
|
|
||||||
queueRenameEntity: (project_id, entity_type, entity_id, user_id, projectUpdate, callback) ->
|
queueRenameEntity: (project_id, projectHistoryId, entity_type, entity_id, user_id, projectUpdate, callback) ->
|
||||||
projectUpdate =
|
projectUpdate =
|
||||||
pathname: projectUpdate.pathname
|
pathname: projectUpdate.pathname
|
||||||
new_pathname: projectUpdate.newPathname
|
new_pathname: projectUpdate.newPathname
|
||||||
|
@ -15,6 +15,7 @@ module.exports = ProjectHistoryRedisManager =
|
||||||
user_id: user_id
|
user_id: user_id
|
||||||
ts: new Date()
|
ts: new Date()
|
||||||
version: projectUpdate.version
|
version: projectUpdate.version
|
||||||
|
projectHistoryId: projectHistoryId
|
||||||
projectUpdate[entity_type] = entity_id
|
projectUpdate[entity_type] = entity_id
|
||||||
|
|
||||||
logger.log {project_id, projectUpdate}, "queue rename operation to project-history"
|
logger.log {project_id, projectUpdate}, "queue rename operation to project-history"
|
||||||
|
@ -22,7 +23,7 @@ module.exports = ProjectHistoryRedisManager =
|
||||||
|
|
||||||
ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
|
ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
|
||||||
|
|
||||||
queueAddEntity: (project_id, entity_type, entitiy_id, user_id, projectUpdate, callback = (error) ->) ->
|
queueAddEntity: (project_id, projectHistoryId, entity_type, entitiy_id, user_id, projectUpdate, callback = (error) ->) ->
|
||||||
projectUpdate =
|
projectUpdate =
|
||||||
pathname: projectUpdate.pathname
|
pathname: projectUpdate.pathname
|
||||||
docLines: projectUpdate.docLines
|
docLines: projectUpdate.docLines
|
||||||
|
@ -31,6 +32,7 @@ module.exports = ProjectHistoryRedisManager =
|
||||||
user_id: user_id
|
user_id: user_id
|
||||||
ts: new Date()
|
ts: new Date()
|
||||||
version: projectUpdate.version
|
version: projectUpdate.version
|
||||||
|
projectHistoryId: projectHistoryId
|
||||||
projectUpdate[entity_type] = entitiy_id
|
projectUpdate[entity_type] = entitiy_id
|
||||||
|
|
||||||
logger.log {project_id, projectUpdate}, "queue add operation to project-history"
|
logger.log {project_id, projectUpdate}, "queue add operation to project-history"
|
||||||
|
@ -38,21 +40,23 @@ module.exports = ProjectHistoryRedisManager =
|
||||||
|
|
||||||
ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
|
ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
|
||||||
|
|
||||||
queueResyncProjectStructure: (project_id, docs, files, callback) ->
|
queueResyncProjectStructure: (project_id, projectHistoryId, docs, files, callback) ->
|
||||||
logger.log {project_id, docs, files}, "queue project structure resync"
|
logger.log {project_id, docs, files}, "queue project structure resync"
|
||||||
projectUpdate =
|
projectUpdate =
|
||||||
resyncProjectStructure: { docs, files }
|
resyncProjectStructure: { docs, files }
|
||||||
|
projectHistoryId: projectHistoryId
|
||||||
meta:
|
meta:
|
||||||
ts: new Date()
|
ts: new Date()
|
||||||
jsonUpdate = JSON.stringify projectUpdate
|
jsonUpdate = JSON.stringify projectUpdate
|
||||||
ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
|
ProjectHistoryRedisManager.queueOps project_id, jsonUpdate, callback
|
||||||
|
|
||||||
queueResyncDocContent: (project_id, doc_id, lines, version, pathname, callback) ->
|
queueResyncDocContent: (project_id, projectHistoryId, doc_id, lines, version, pathname, callback) ->
|
||||||
logger.log {project_id, doc_id, lines, version, pathname}, "queue doc content resync"
|
logger.log {project_id, doc_id, lines, version, pathname}, "queue doc content resync"
|
||||||
projectUpdate =
|
projectUpdate =
|
||||||
resyncDocContent:
|
resyncDocContent:
|
||||||
content: lines.join("\n"),
|
content: lines.join("\n"),
|
||||||
version: version
|
version: version
|
||||||
|
projectHistoryId: projectHistoryId
|
||||||
path: pathname
|
path: pathname
|
||||||
doc: doc_id
|
doc: doc_id
|
||||||
meta:
|
meta:
|
||||||
|
|
|
@ -105,7 +105,7 @@ module.exports = ProjectManager =
|
||||||
clearProjectState: (project_id, callback = (error) ->) ->
|
clearProjectState: (project_id, callback = (error) ->) ->
|
||||||
RedisManager.clearProjectState project_id, callback
|
RedisManager.clearProjectState project_id, callback
|
||||||
|
|
||||||
updateProjectWithLocks: (project_id, user_id, docUpdates, fileUpdates, version, _callback = (error) ->) ->
|
updateProjectWithLocks: (project_id, projectHistoryId, user_id, docUpdates, fileUpdates, version, _callback = (error) ->) ->
|
||||||
timer = new Metrics.Timer("projectManager.updateProject")
|
timer = new Metrics.Timer("projectManager.updateProject")
|
||||||
callback = (args...) ->
|
callback = (args...) ->
|
||||||
timer.done()
|
timer.done()
|
||||||
|
@ -120,11 +120,11 @@ module.exports = ProjectManager =
|
||||||
doc_id = projectUpdate.id
|
doc_id = projectUpdate.id
|
||||||
projectUpdate.version = "#{project_version}.#{project_subversion++}"
|
projectUpdate.version = "#{project_version}.#{project_subversion++}"
|
||||||
if projectUpdate.docLines?
|
if projectUpdate.docLines?
|
||||||
ProjectHistoryRedisManager.queueAddEntity project_id, 'doc', doc_id, user_id, projectUpdate, (error, count) ->
|
ProjectHistoryRedisManager.queueAddEntity project_id, projectHistoryId, 'doc', doc_id, user_id, projectUpdate, (error, count) ->
|
||||||
project_ops_length = count
|
project_ops_length = count
|
||||||
cb(error)
|
cb(error)
|
||||||
else
|
else
|
||||||
DocumentManager.renameDocWithLock project_id, doc_id, user_id, projectUpdate, (error, count) ->
|
DocumentManager.renameDocWithLock project_id, doc_id, user_id, projectUpdate, projectHistoryId, (error, count) ->
|
||||||
project_ops_length = count
|
project_ops_length = count
|
||||||
cb(error)
|
cb(error)
|
||||||
|
|
||||||
|
@ -132,11 +132,11 @@ module.exports = ProjectManager =
|
||||||
file_id = projectUpdate.id
|
file_id = projectUpdate.id
|
||||||
projectUpdate.version = "#{project_version}.#{project_subversion++}"
|
projectUpdate.version = "#{project_version}.#{project_subversion++}"
|
||||||
if projectUpdate.url?
|
if projectUpdate.url?
|
||||||
ProjectHistoryRedisManager.queueAddEntity project_id, 'file', file_id, user_id, projectUpdate, (error, count) ->
|
ProjectHistoryRedisManager.queueAddEntity project_id, projectHistoryId, 'file', file_id, user_id, projectUpdate, (error, count) ->
|
||||||
project_ops_length = count
|
project_ops_length = count
|
||||||
cb(error)
|
cb(error)
|
||||||
else
|
else
|
||||||
ProjectHistoryRedisManager.queueRenameEntity project_id, 'file', file_id, user_id, projectUpdate, (error, count) ->
|
ProjectHistoryRedisManager.queueRenameEntity project_id, projectHistoryId, 'file', file_id, user_id, projectUpdate, (error, count) ->
|
||||||
project_ops_length = count
|
project_ops_length = count
|
||||||
cb(error)
|
cb(error)
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ historyKeys = Settings.redis.history.key_schema
|
||||||
module.exports = RedisManager =
|
module.exports = RedisManager =
|
||||||
rclient: rclient
|
rclient: rclient
|
||||||
|
|
||||||
putDocInMemory : (project_id, doc_id, docLines, version, ranges, pathname, _callback)->
|
putDocInMemory : (project_id, doc_id, docLines, version, ranges, pathname, projectHistoryId, _callback)->
|
||||||
timer = new metrics.Timer("redis.put-doc")
|
timer = new metrics.Timer("redis.put-doc")
|
||||||
callback = (error) ->
|
callback = (error) ->
|
||||||
timer.done()
|
timer.done()
|
||||||
|
@ -47,7 +47,7 @@ module.exports = RedisManager =
|
||||||
logger.error {err: error, doc_id: doc_id, docLines: docLines}, error.message
|
logger.error {err: error, doc_id: doc_id, docLines: docLines}, error.message
|
||||||
return callback(error)
|
return callback(error)
|
||||||
docHash = RedisManager._computeHash(docLines)
|
docHash = RedisManager._computeHash(docLines)
|
||||||
logger.log {project_id, doc_id, version, docHash, pathname}, "putting doc in redis"
|
logger.log {project_id, doc_id, version, docHash, pathname, projectHistoryId}, "putting doc in redis"
|
||||||
RedisManager._serializeRanges ranges, (error, ranges) ->
|
RedisManager._serializeRanges ranges, (error, ranges) ->
|
||||||
if error?
|
if error?
|
||||||
logger.error {err: error, doc_id, project_id}, error.message
|
logger.error {err: error, doc_id, project_id}, error.message
|
||||||
|
@ -62,6 +62,7 @@ module.exports = RedisManager =
|
||||||
else
|
else
|
||||||
multi.del keys.ranges(doc_id:doc_id)
|
multi.del keys.ranges(doc_id:doc_id)
|
||||||
multi.set keys.pathname(doc_id:doc_id), pathname
|
multi.set keys.pathname(doc_id:doc_id), pathname
|
||||||
|
multi.set keys.projectHistoryId(doc_id:doc_id), projectHistoryId
|
||||||
multi.exec (error, result) ->
|
multi.exec (error, result) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
# check the hash computed on the redis server
|
# check the hash computed on the redis server
|
||||||
|
@ -88,6 +89,7 @@ module.exports = RedisManager =
|
||||||
multi.del keys.docHash(doc_id:doc_id)
|
multi.del keys.docHash(doc_id:doc_id)
|
||||||
multi.del keys.ranges(doc_id:doc_id)
|
multi.del keys.ranges(doc_id:doc_id)
|
||||||
multi.del keys.pathname(doc_id:doc_id)
|
multi.del keys.pathname(doc_id:doc_id)
|
||||||
|
multi.del keys.projectHistoryId(doc_id:doc_id)
|
||||||
multi.del keys.unflushedTime(doc_id:doc_id)
|
multi.del keys.unflushedTime(doc_id:doc_id)
|
||||||
multi.exec (error) ->
|
multi.exec (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
|
@ -108,7 +110,7 @@ module.exports = RedisManager =
|
||||||
clearProjectState: (project_id, callback = (error) ->) ->
|
clearProjectState: (project_id, callback = (error) ->) ->
|
||||||
rclient.del keys.projectState(project_id:project_id), callback
|
rclient.del keys.projectState(project_id:project_id), callback
|
||||||
|
|
||||||
getDoc : (project_id, doc_id, callback = (error, lines, version, ranges, pathname, unflushedTime) ->)->
|
getDoc : (project_id, doc_id, callback = (error, lines, version, ranges, pathname, projectHistoryId, unflushedTime) ->)->
|
||||||
timer = new metrics.Timer("redis.get-doc")
|
timer = new metrics.Timer("redis.get-doc")
|
||||||
multi = rclient.multi()
|
multi = rclient.multi()
|
||||||
multi.get keys.docLines(doc_id:doc_id)
|
multi.get keys.docLines(doc_id:doc_id)
|
||||||
|
@ -117,8 +119,9 @@ module.exports = RedisManager =
|
||||||
multi.get keys.projectKey(doc_id:doc_id)
|
multi.get keys.projectKey(doc_id:doc_id)
|
||||||
multi.get keys.ranges(doc_id:doc_id)
|
multi.get keys.ranges(doc_id:doc_id)
|
||||||
multi.get keys.pathname(doc_id:doc_id)
|
multi.get keys.pathname(doc_id:doc_id)
|
||||||
|
multi.get keys.projectHistoryId(doc_id:doc_id)
|
||||||
multi.get keys.unflushedTime(doc_id:doc_id)
|
multi.get keys.unflushedTime(doc_id:doc_id)
|
||||||
multi.exec (error, [docLines, version, storedHash, doc_project_id, ranges, pathname, unflushedTime])->
|
multi.exec (error, [docLines, version, storedHash, doc_project_id, ranges, pathname, projectHistoryId, unflushedTime])->
|
||||||
timeSpan = timer.done()
|
timeSpan = timer.done()
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
# check if request took too long and bail out. only do this for
|
# check if request took too long and bail out. only do this for
|
||||||
|
@ -145,16 +148,19 @@ module.exports = RedisManager =
|
||||||
logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc not in project"
|
logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc not in project"
|
||||||
return callback(new Errors.NotFoundError("document not found"))
|
return callback(new Errors.NotFoundError("document not found"))
|
||||||
|
|
||||||
|
if projectHistoryId?
|
||||||
|
projectHistoryId = parseInt(projectHistoryId)
|
||||||
|
|
||||||
# doc is not in redis, bail out
|
# doc is not in redis, bail out
|
||||||
if !docLines?
|
if !docLines?
|
||||||
return callback null, docLines, version, ranges, pathname, unflushedTime
|
return callback null, docLines, version, ranges, pathname, projectHistoryId, unflushedTime
|
||||||
|
|
||||||
# doc should be in project set, check if missing (workaround for missing docs from putDoc)
|
# doc should be in project set, check if missing (workaround for missing docs from putDoc)
|
||||||
rclient.sadd keys.docsInProject(project_id:project_id), doc_id, (error, result) ->
|
rclient.sadd keys.docsInProject(project_id:project_id), doc_id, (error, result) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if result isnt 0 # doc should already be in set
|
if result isnt 0 # doc should already be in set
|
||||||
logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc missing from docsInProject set"
|
logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc missing from docsInProject set"
|
||||||
callback null, docLines, version, ranges, pathname, unflushedTime
|
callback null, docLines, version, ranges, pathname, projectHistoryId, unflushedTime
|
||||||
|
|
||||||
getDocVersion: (doc_id, callback = (error, version) ->) ->
|
getDocVersion: (doc_id, callback = (error, version) ->) ->
|
||||||
rclient.get keys.docVersion(doc_id: doc_id), (error, version) ->
|
rclient.get keys.docVersion(doc_id: doc_id), (error, version) ->
|
||||||
|
@ -272,16 +278,16 @@ module.exports = RedisManager =
|
||||||
else
|
else
|
||||||
callback null, docUpdateCount
|
callback null, docUpdateCount
|
||||||
|
|
||||||
renameDoc: (project_id, doc_id, user_id, update, callback = (error) ->) ->
|
renameDoc: (project_id, doc_id, user_id, update, projectHistoryId, callback = (error) ->) ->
|
||||||
RedisManager.getDoc project_id, doc_id, (error, lines, version) ->
|
RedisManager.getDoc project_id, doc_id, (error, lines, version) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
|
|
||||||
if lines? and version?
|
if lines? and version?
|
||||||
rclient.set keys.pathname(doc_id:doc_id), update.newPathname, (error) ->
|
rclient.set keys.pathname(doc_id:doc_id), update.newPathname, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
ProjectHistoryRedisManager.queueRenameEntity project_id, 'doc', doc_id, user_id, update, callback
|
ProjectHistoryRedisManager.queueRenameEntity project_id, projectHistoryId, 'doc', doc_id, user_id, update, callback
|
||||||
else
|
else
|
||||||
ProjectHistoryRedisManager.queueRenameEntity project_id, 'doc', doc_id, user_id, update, callback
|
ProjectHistoryRedisManager.queueRenameEntity project_id, projectHistoryId, 'doc', doc_id, user_id, update, callback
|
||||||
|
|
||||||
clearUnflushedTime: (doc_id, callback = (error) ->) ->
|
clearUnflushedTime: (doc_id, callback = (error) ->) ->
|
||||||
rclient.del keys.unflushedTime(doc_id:doc_id), callback
|
rclient.del keys.unflushedTime(doc_id:doc_id), callback
|
||||||
|
|
|
@ -71,7 +71,7 @@ module.exports = UpdateManager =
|
||||||
profile = new Profiler("applyUpdate", {project_id, doc_id})
|
profile = new Profiler("applyUpdate", {project_id, doc_id})
|
||||||
UpdateManager._sanitizeUpdate update
|
UpdateManager._sanitizeUpdate update
|
||||||
profile.log("sanitizeUpdate")
|
profile.log("sanitizeUpdate")
|
||||||
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname) ->
|
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId) ->
|
||||||
profile.log("getDoc")
|
profile.log("getDoc")
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if !lines? or !version?
|
if !lines? or !version?
|
||||||
|
@ -80,7 +80,7 @@ module.exports = UpdateManager =
|
||||||
profile.log("sharejs.applyUpdate")
|
profile.log("sharejs.applyUpdate")
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
RangesManager.applyUpdate project_id, doc_id, ranges, appliedOps, updatedDocLines, (error, new_ranges) ->
|
RangesManager.applyUpdate project_id, doc_id, ranges, appliedOps, updatedDocLines, (error, new_ranges) ->
|
||||||
UpdateManager._addProjectHistoryMetadataToOps(appliedOps, pathname, lines)
|
UpdateManager._addProjectHistoryMetadataToOps(appliedOps, pathname, projectHistoryId, lines)
|
||||||
profile.log("RangesManager.applyUpdate")
|
profile.log("RangesManager.applyUpdate")
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
RedisManager.updateDocument project_id, doc_id, updatedDocLines, version, appliedOps, new_ranges, (error, doc_ops_length, project_ops_length) ->
|
RedisManager.updateDocument project_id, doc_id, updatedDocLines, version, appliedOps, new_ranges, (error, doc_ops_length, project_ops_length) ->
|
||||||
|
@ -130,12 +130,13 @@ module.exports = UpdateManager =
|
||||||
op.i = op.i.replace(/[\uD800-\uDFFF]/g, "\uFFFD")
|
op.i = op.i.replace(/[\uD800-\uDFFF]/g, "\uFFFD")
|
||||||
return update
|
return update
|
||||||
|
|
||||||
_addProjectHistoryMetadataToOps: (updates, pathname, lines) ->
|
_addProjectHistoryMetadataToOps: (updates, pathname, projectHistoryId, lines) ->
|
||||||
doc_length = _.reduce lines,
|
doc_length = _.reduce lines,
|
||||||
(chars, line) -> chars + line.length,
|
(chars, line) -> chars + line.length,
|
||||||
0
|
0
|
||||||
doc_length += lines.length - 1 # count newline characters
|
doc_length += lines.length - 1 # count newline characters
|
||||||
updates.forEach (update) ->
|
updates.forEach (update) ->
|
||||||
|
update.projectHistoryId = projectHistoryId
|
||||||
update.meta ||= {}
|
update.meta ||= {}
|
||||||
update.meta.pathname = pathname
|
update.meta.pathname = pathname
|
||||||
update.meta.doc_length = doc_length
|
update.meta.doc_length = doc_length
|
||||||
|
|
|
@ -46,6 +46,7 @@ module.exports =
|
||||||
docsInProject: ({project_id}) -> "DocsIn:#{project_id}"
|
docsInProject: ({project_id}) -> "DocsIn:#{project_id}"
|
||||||
ranges: ({doc_id}) -> "Ranges:#{doc_id}"
|
ranges: ({doc_id}) -> "Ranges:#{doc_id}"
|
||||||
pathname: ({doc_id}) -> "Pathname:#{doc_id}"
|
pathname: ({doc_id}) -> "Pathname:#{doc_id}"
|
||||||
|
projectHistoryId: ({doc_id}) -> "ProjectHistoryId:#{doc_id}"
|
||||||
projectState: ({project_id}) -> "ProjectState:#{project_id}"
|
projectState: ({project_id}) -> "ProjectState:#{project_id}"
|
||||||
unflushedTime: ({doc_id}) -> "UnflushedTime:#{doc_id}"
|
unflushedTime: ({doc_id}) -> "UnflushedTime:#{doc_id}"
|
||||||
# cluster: [{
|
# cluster: [{
|
||||||
|
|
|
@ -25,6 +25,7 @@ describe "DocumentManager", ->
|
||||||
"./UpdateManager": @UpdateManager = {}
|
"./UpdateManager": @UpdateManager = {}
|
||||||
"./RangesManager": @RangesManager = {}
|
"./RangesManager": @RangesManager = {}
|
||||||
@project_id = "project-id-123"
|
@project_id = "project-id-123"
|
||||||
|
@projectHistoryId = "history-id-123"
|
||||||
@doc_id = "doc-id-123"
|
@doc_id = "doc-id-123"
|
||||||
@user_id = 1234
|
@user_id = 1234
|
||||||
@callback = sinon.stub()
|
@callback = sinon.stub()
|
||||||
|
@ -111,7 +112,7 @@ describe "DocumentManager", ->
|
||||||
describe "getDocAndRecentOps", ->
|
describe "getDocAndRecentOps", ->
|
||||||
describe "with a previous version specified", ->
|
describe "with a previous version specified", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname)
|
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId)
|
||||||
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
|
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
|
||||||
@DocumentManager.getDocAndRecentOps @project_id, @doc_id, @fromVersion, @callback
|
@DocumentManager.getDocAndRecentOps @project_id, @doc_id, @fromVersion, @callback
|
||||||
|
|
||||||
|
@ -126,14 +127,14 @@ describe "DocumentManager", ->
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should call the callback with the doc info", ->
|
it "should call the callback with the doc info", ->
|
||||||
@callback.calledWith(null, @lines, @version, @ops, @ranges, @pathname).should.equal true
|
@callback.calledWith(null, @lines, @version, @ops, @ranges, @pathname, @projectHistoryId).should.equal true
|
||||||
|
|
||||||
it "should time the execution", ->
|
it "should time the execution", ->
|
||||||
@Metrics.Timer::done.called.should.equal true
|
@Metrics.Timer::done.called.should.equal true
|
||||||
|
|
||||||
describe "with no previous version specified", ->
|
describe "with no previous version specified", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname)
|
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId)
|
||||||
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
|
@RedisManager.getPreviousDocOps = sinon.stub().callsArgWith(3, null, @ops)
|
||||||
@DocumentManager.getDocAndRecentOps @project_id, @doc_id, -1, @callback
|
@DocumentManager.getDocAndRecentOps @project_id, @doc_id, -1, @callback
|
||||||
|
|
||||||
|
@ -146,7 +147,7 @@ describe "DocumentManager", ->
|
||||||
@RedisManager.getPreviousDocOps.called.should.equal false
|
@RedisManager.getPreviousDocOps.called.should.equal false
|
||||||
|
|
||||||
it "should call the callback with the doc info", ->
|
it "should call the callback with the doc info", ->
|
||||||
@callback.calledWith(null, @lines, @version, [], @ranges, @pathname).should.equal true
|
@callback.calledWith(null, @lines, @version, [], @ranges, @pathname, @projectHistoryId).should.equal true
|
||||||
|
|
||||||
it "should time the execution", ->
|
it "should time the execution", ->
|
||||||
@Metrics.Timer::done.called.should.equal true
|
@Metrics.Timer::done.called.should.equal true
|
||||||
|
@ -154,7 +155,7 @@ describe "DocumentManager", ->
|
||||||
describe "getDoc", ->
|
describe "getDoc", ->
|
||||||
describe "when the doc exists in Redis", ->
|
describe "when the doc exists in Redis", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @unflushedTime)
|
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushedTime)
|
||||||
@DocumentManager.getDoc @project_id, @doc_id, @callback
|
@DocumentManager.getDoc @project_id, @doc_id, @callback
|
||||||
|
|
||||||
it "should get the doc from Redis", ->
|
it "should get the doc from Redis", ->
|
||||||
|
@ -163,7 +164,7 @@ describe "DocumentManager", ->
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should call the callback with the doc info", ->
|
it "should call the callback with the doc info", ->
|
||||||
@callback.calledWith(null, @lines, @version, @ranges, @pathname, @unflushedTime, true).should.equal true
|
@callback.calledWith(null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushedTime, true).should.equal true
|
||||||
|
|
||||||
it "should time the execution", ->
|
it "should time the execution", ->
|
||||||
@Metrics.Timer::done.called.should.equal true
|
@Metrics.Timer::done.called.should.equal true
|
||||||
|
@ -171,7 +172,7 @@ describe "DocumentManager", ->
|
||||||
describe "when the doc does not exist in Redis", ->
|
describe "when the doc does not exist in Redis", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null, null, null, null)
|
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null, null, null, null)
|
||||||
@PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname)
|
@PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId)
|
||||||
@RedisManager.putDocInMemory = sinon.stub().yields()
|
@RedisManager.putDocInMemory = sinon.stub().yields()
|
||||||
@DocumentManager.getDoc @project_id, @doc_id, @callback
|
@DocumentManager.getDoc @project_id, @doc_id, @callback
|
||||||
|
|
||||||
|
@ -187,11 +188,11 @@ describe "DocumentManager", ->
|
||||||
|
|
||||||
it "should set the doc in Redis", ->
|
it "should set the doc in Redis", ->
|
||||||
@RedisManager.putDocInMemory
|
@RedisManager.putDocInMemory
|
||||||
.calledWith(@project_id, @doc_id, @lines, @version, @ranges, @pathname)
|
.calledWith(@project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should call the callback with the doc info", ->
|
it "should call the callback with the doc info", ->
|
||||||
@callback.calledWith(null, @lines, @version, @ranges, @pathname, null, false).should.equal true
|
@callback.calledWith(null, @lines, @version, @ranges, @pathname, @projectHistoryId, null, false).should.equal true
|
||||||
|
|
||||||
it "should time the execution", ->
|
it "should time the execution", ->
|
||||||
@Metrics.Timer::done.called.should.equal true
|
@Metrics.Timer::done.called.should.equal true
|
||||||
|
@ -202,7 +203,7 @@ describe "DocumentManager", ->
|
||||||
@beforeLines = ["before", "lines"]
|
@beforeLines = ["before", "lines"]
|
||||||
@afterLines = ["after", "lines"]
|
@afterLines = ["after", "lines"]
|
||||||
@ops = [{ i: "foo", p: 4 }, { d: "bar", p: 42 }]
|
@ops = [{ i: "foo", p: 4 }, { d: "bar", p: 42 }]
|
||||||
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @beforeLines, @version, @ranges, @pathname, @unflushedTime, true)
|
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @beforeLines, @version, @ranges, @pathname, @projectHistoryId, @unflushedTime, true)
|
||||||
@DiffCodec.diffAsShareJsOp = sinon.stub().callsArgWith(2, null, @ops)
|
@DiffCodec.diffAsShareJsOp = sinon.stub().callsArgWith(2, null, @ops)
|
||||||
@UpdateManager.applyUpdate = sinon.stub().callsArgWith(3, null)
|
@UpdateManager.applyUpdate = sinon.stub().callsArgWith(3, null)
|
||||||
@DocumentManager.flushDocIfLoaded = sinon.stub().callsArg(2)
|
@DocumentManager.flushDocIfLoaded = sinon.stub().callsArg(2)
|
||||||
|
@ -402,7 +403,7 @@ describe "DocumentManager", ->
|
||||||
describe "when the doc is in Redis", ->
|
describe "when the doc is in Redis", ->
|
||||||
describe "and has changes to be flushed", ->
|
describe "and has changes to be flushed", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, Date.now() - 1e9, true)
|
@DocumentManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @projectHistoryId, @pathname, Date.now() - 1e9, true)
|
||||||
@DocumentManager.getDocAndFlushIfOld @project_id, @doc_id, @callback
|
@DocumentManager.getDocAndFlushIfOld @project_id, @doc_id, @callback
|
||||||
|
|
||||||
it "should get the doc", ->
|
it "should get the doc", ->
|
||||||
|
@ -459,11 +460,11 @@ describe "DocumentManager", ->
|
||||||
|
|
||||||
describe "successfully", ->
|
describe "successfully", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@DocumentManager.renameDoc @project_id, @doc_id, @user_id, @update, @callback
|
@DocumentManager.renameDoc @project_id, @doc_id, @user_id, @update, @projectHistoryId, @callback
|
||||||
|
|
||||||
it "should rename the document", ->
|
it "should rename the document", ->
|
||||||
@RedisManager.renameDoc
|
@RedisManager.renameDoc
|
||||||
.calledWith(@project_id, @doc_id, @user_id, @update)
|
.calledWith(@project_id, @doc_id, @user_id, @update, @projectHistoryId)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should call the callback", ->
|
it "should call the callback", ->
|
||||||
|
@ -472,7 +473,7 @@ describe "DocumentManager", ->
|
||||||
describe "resyncDocContents", ->
|
describe "resyncDocContents", ->
|
||||||
describe "when doc is loaded in redis", ->
|
describe "when doc is loaded in redis", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname)
|
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId)
|
||||||
@ProjectHistoryRedisManager.queueResyncDocContent = sinon.stub()
|
@ProjectHistoryRedisManager.queueResyncDocContent = sinon.stub()
|
||||||
@DocumentManager.resyncDocContents @project_id, @doc_id, @callback
|
@DocumentManager.resyncDocContents @project_id, @doc_id, @callback
|
||||||
|
|
||||||
|
@ -483,13 +484,13 @@ describe "DocumentManager", ->
|
||||||
|
|
||||||
it "queues a resync doc content update", ->
|
it "queues a resync doc content update", ->
|
||||||
@ProjectHistoryRedisManager.queueResyncDocContent
|
@ProjectHistoryRedisManager.queueResyncDocContent
|
||||||
.calledWith(@project_id, @doc_id, @lines, @version, @pathname, @callback)
|
.calledWith(@project_id, @projectHistoryId, @doc_id, @lines, @version, @pathname, @callback)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
describe "when doc is not loaded in redis", ->
|
describe "when doc is not loaded in redis", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null)
|
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null)
|
||||||
@PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname)
|
@PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId)
|
||||||
@ProjectHistoryRedisManager.queueResyncDocContent = sinon.stub()
|
@ProjectHistoryRedisManager.queueResyncDocContent = sinon.stub()
|
||||||
@DocumentManager.resyncDocContents @project_id, @doc_id, @callback
|
@DocumentManager.resyncDocContents @project_id, @doc_id, @callback
|
||||||
|
|
||||||
|
@ -505,5 +506,5 @@ describe "DocumentManager", ->
|
||||||
|
|
||||||
it "queues a resync doc content update", ->
|
it "queues a resync doc content update", ->
|
||||||
@ProjectHistoryRedisManager.queueResyncDocContent
|
@ProjectHistoryRedisManager.queueResyncDocContent
|
||||||
.calledWith(@project_id, @doc_id, @lines, @version, @pathname, @callback)
|
.calledWith(@project_id, @projectHistoryId, @doc_id, @lines, @version, @pathname, @callback)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
|
@ -164,6 +164,7 @@ describe "HistoryManager", ->
|
||||||
|
|
||||||
describe "resyncProjectHistory", ->
|
describe "resyncProjectHistory", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
@projectHistoryId = 'history-id-1234'
|
||||||
@docs = [
|
@docs = [
|
||||||
doc: @doc_id
|
doc: @doc_id
|
||||||
path: 'main.tex'
|
path: 'main.tex'
|
||||||
|
@ -175,11 +176,11 @@ describe "HistoryManager", ->
|
||||||
]
|
]
|
||||||
@ProjectHistoryRedisManager.queueResyncProjectStructure = sinon.stub().yields()
|
@ProjectHistoryRedisManager.queueResyncProjectStructure = sinon.stub().yields()
|
||||||
@DocumentManager.resyncDocContentsWithLock = sinon.stub().yields()
|
@DocumentManager.resyncDocContentsWithLock = sinon.stub().yields()
|
||||||
@HistoryManager.resyncProjectHistory @project_id, @docs, @files, @callback
|
@HistoryManager.resyncProjectHistory @project_id, @projectHistoryId, @docs, @files, @callback
|
||||||
|
|
||||||
it "should queue a project structure reync", ->
|
it "should queue a project structure reync", ->
|
||||||
@ProjectHistoryRedisManager.queueResyncProjectStructure
|
@ProjectHistoryRedisManager.queueResyncProjectStructure
|
||||||
.calledWith(@project_id, @docs, @files)
|
.calledWith(@project_id, @projectHistoryId, @docs, @files)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should queue doc content reyncs", ->
|
it "should queue doc content reyncs", ->
|
||||||
|
|
|
@ -509,23 +509,24 @@ describe "HttpController", ->
|
||||||
|
|
||||||
describe "updateProject", ->
|
describe "updateProject", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
@projectHistoryId = "history-id-123"
|
||||||
@userId = "user-id-123"
|
@userId = "user-id-123"
|
||||||
@docUpdates = sinon.stub()
|
@docUpdates = sinon.stub()
|
||||||
@fileUpdates = sinon.stub()
|
@fileUpdates = sinon.stub()
|
||||||
@version = 1234567
|
@version = 1234567
|
||||||
@req =
|
@req =
|
||||||
body: {@userId, @docUpdates, @fileUpdates, @version}
|
body: {@projectHistoryId, @userId, @docUpdates, @fileUpdates, @version}
|
||||||
params:
|
params:
|
||||||
project_id: @project_id
|
project_id: @project_id
|
||||||
|
|
||||||
describe "successfully", ->
|
describe "successfully", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(5)
|
@ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(6)
|
||||||
@HttpController.updateProject(@req, @res, @next)
|
@HttpController.updateProject(@req, @res, @next)
|
||||||
|
|
||||||
it "should accept the change", ->
|
it "should accept the change", ->
|
||||||
@ProjectManager.updateProjectWithLocks
|
@ProjectManager.updateProjectWithLocks
|
||||||
.calledWith(@project_id, @userId, @docUpdates, @fileUpdates, @version)
|
.calledWith(@project_id, @projectHistoryId, @userId, @docUpdates, @fileUpdates, @version)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should return a successful No Content response", ->
|
it "should return a successful No Content response", ->
|
||||||
|
@ -538,7 +539,7 @@ describe "HttpController", ->
|
||||||
|
|
||||||
describe "when an errors occurs", ->
|
describe "when an errors occurs", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(5, new Error("oops"))
|
@ProjectManager.updateProjectWithLocks = sinon.stub().callsArgWith(6, new Error("oops"))
|
||||||
@HttpController.updateProject(@req, @res, @next)
|
@HttpController.updateProject(@req, @res, @next)
|
||||||
|
|
||||||
it "should call next with the error", ->
|
it "should call next with the error", ->
|
||||||
|
@ -548,23 +549,24 @@ describe "HttpController", ->
|
||||||
|
|
||||||
describe "resyncProjectHistory", ->
|
describe "resyncProjectHistory", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
@projectHistoryId = "history-id-123"
|
||||||
@docs = sinon.stub()
|
@docs = sinon.stub()
|
||||||
@files = sinon.stub()
|
@files = sinon.stub()
|
||||||
@fileUpdates = sinon.stub()
|
@fileUpdates = sinon.stub()
|
||||||
@req =
|
@req =
|
||||||
body:
|
body:
|
||||||
{@docs, @files}
|
{@projectHistoryId, @docs, @files}
|
||||||
params:
|
params:
|
||||||
project_id: @project_id
|
project_id: @project_id
|
||||||
|
|
||||||
describe "successfully", ->
|
describe "successfully", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@HistoryManager.resyncProjectHistory = sinon.stub().callsArg(3)
|
@HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(4)
|
||||||
@HttpController.resyncProjectHistory(@req, @res, @next)
|
@HttpController.resyncProjectHistory(@req, @res, @next)
|
||||||
|
|
||||||
it "should accept the change", ->
|
it "should accept the change", ->
|
||||||
@HistoryManager.resyncProjectHistory
|
@HistoryManager.resyncProjectHistory
|
||||||
.calledWith(@project_id, @docs, @files)
|
.calledWith(@project_id, @projectHistoryId, @docs, @files)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should return a successful No Content response", ->
|
it "should return a successful No Content response", ->
|
||||||
|
@ -574,7 +576,7 @@ describe "HttpController", ->
|
||||||
|
|
||||||
describe "when an errors occurs", ->
|
describe "when an errors occurs", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(3, new Error("oops"))
|
@HistoryManager.resyncProjectHistory = sinon.stub().callsArgWith(4, new Error("oops"))
|
||||||
@HttpController.resyncProjectHistory(@req, @res, @next)
|
@HttpController.resyncProjectHistory(@req, @res, @next)
|
||||||
|
|
||||||
it "should call next with the error", ->
|
it "should call next with the error", ->
|
||||||
|
|
|
@ -17,6 +17,7 @@ describe "PersistenceManager", ->
|
||||||
done: sinon.stub()
|
done: sinon.stub()
|
||||||
"logger-sharelatex": @logger = {log: sinon.stub(), err: sinon.stub()}
|
"logger-sharelatex": @logger = {log: sinon.stub(), err: sinon.stub()}
|
||||||
@project_id = "project-id-123"
|
@project_id = "project-id-123"
|
||||||
|
@projectHistoryId = "history-id-123"
|
||||||
@doc_id = "doc-id-123"
|
@doc_id = "doc-id-123"
|
||||||
@lines = ["one", "two", "three"]
|
@lines = ["one", "two", "three"]
|
||||||
@version = 42
|
@version = 42
|
||||||
|
@ -36,6 +37,7 @@ describe "PersistenceManager", ->
|
||||||
version: @version,
|
version: @version,
|
||||||
ranges: @ranges
|
ranges: @ranges
|
||||||
pathname: @pathname,
|
pathname: @pathname,
|
||||||
|
projectHistoryId: @projectHistoryId
|
||||||
}
|
}
|
||||||
|
|
||||||
describe "with a successful response from the web api", ->
|
describe "with a successful response from the web api", ->
|
||||||
|
@ -60,7 +62,9 @@ describe "PersistenceManager", ->
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should call the callback with the doc lines, version and ranges", ->
|
it "should call the callback with the doc lines, version and ranges", ->
|
||||||
@callback.calledWith(null, @lines, @version, @ranges, @pathname).should.equal true
|
@callback
|
||||||
|
.calledWith(null, @lines, @version, @ranges, @pathname, @projectHistoryId)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
it "should time the execution", ->
|
it "should time the execution", ->
|
||||||
@Metrics.Timer::done.called.should.equal true
|
@Metrics.Timer::done.called.should.equal true
|
||||||
|
|
|
@ -8,6 +8,7 @@ tk = require "timekeeper"
|
||||||
describe "ProjectHistoryRedisManager", ->
|
describe "ProjectHistoryRedisManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@project_id = "project-id-123"
|
@project_id = "project-id-123"
|
||||||
|
@projectHistoryId = "history-id-123"
|
||||||
@user_id = "user-id-123"
|
@user_id = "user-id-123"
|
||||||
@callback = sinon.stub()
|
@callback = sinon.stub()
|
||||||
@rclient = {}
|
@rclient = {}
|
||||||
|
@ -50,9 +51,10 @@ describe "ProjectHistoryRedisManager", ->
|
||||||
@rawUpdate =
|
@rawUpdate =
|
||||||
pathname: @pathname = '/old'
|
pathname: @pathname = '/old'
|
||||||
newPathname: @newPathname = '/new'
|
newPathname: @newPathname = '/new'
|
||||||
|
version: @version = 2
|
||||||
|
|
||||||
@ProjectHistoryRedisManager.queueOps = sinon.stub()
|
@ProjectHistoryRedisManager.queueOps = sinon.stub()
|
||||||
@ProjectHistoryRedisManager.queueRenameEntity @project_id, 'file', @file_id, @user_id, @rawUpdate, @callback
|
@ProjectHistoryRedisManager.queueRenameEntity @project_id, @projectHistoryId, 'file', @file_id, @user_id, @rawUpdate, @callback
|
||||||
|
|
||||||
it "should queue an update", ->
|
it "should queue an update", ->
|
||||||
update =
|
update =
|
||||||
|
@ -61,6 +63,8 @@ describe "ProjectHistoryRedisManager", ->
|
||||||
meta:
|
meta:
|
||||||
user_id: @user_id
|
user_id: @user_id
|
||||||
ts: new Date()
|
ts: new Date()
|
||||||
|
version: @version
|
||||||
|
projectHistoryId: @projectHistoryId
|
||||||
file: @file_id
|
file: @file_id
|
||||||
|
|
||||||
@ProjectHistoryRedisManager.queueOps
|
@ProjectHistoryRedisManager.queueOps
|
||||||
|
@ -75,10 +79,11 @@ describe "ProjectHistoryRedisManager", ->
|
||||||
@rawUpdate =
|
@rawUpdate =
|
||||||
pathname: @pathname = '/old'
|
pathname: @pathname = '/old'
|
||||||
docLines: @docLines = 'a\nb'
|
docLines: @docLines = 'a\nb'
|
||||||
|
version: @version = 2
|
||||||
url: @url = 'filestore.example.com'
|
url: @url = 'filestore.example.com'
|
||||||
|
|
||||||
@ProjectHistoryRedisManager.queueOps = sinon.stub()
|
@ProjectHistoryRedisManager.queueOps = sinon.stub()
|
||||||
@ProjectHistoryRedisManager.queueAddEntity @project_id, 'doc', @doc_id, @user_id, @rawUpdate, @callback
|
@ProjectHistoryRedisManager.queueAddEntity @project_id, @projectHistoryId, 'doc', @doc_id, @user_id, @rawUpdate, @callback
|
||||||
|
|
||||||
it "should queue an update", ->
|
it "should queue an update", ->
|
||||||
update =
|
update =
|
||||||
|
@ -88,6 +93,8 @@ describe "ProjectHistoryRedisManager", ->
|
||||||
meta:
|
meta:
|
||||||
user_id: @user_id
|
user_id: @user_id
|
||||||
ts: new Date()
|
ts: new Date()
|
||||||
|
version: @version
|
||||||
|
projectHistoryId: @projectHistoryId
|
||||||
doc: @doc_id
|
doc: @doc_id
|
||||||
|
|
||||||
@ProjectHistoryRedisManager.queueOps
|
@ProjectHistoryRedisManager.queueOps
|
||||||
|
|
|
@ -18,6 +18,7 @@ describe "ProjectManager", ->
|
||||||
done: sinon.stub()
|
done: sinon.stub()
|
||||||
|
|
||||||
@project_id = "project-id-123"
|
@project_id = "project-id-123"
|
||||||
|
@projectHistoryId = 'history-id-123'
|
||||||
@user_id = "user-id-123"
|
@user_id = "user-id-123"
|
||||||
@version = 1234567
|
@version = 1234567
|
||||||
@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(false)
|
@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(false)
|
||||||
|
@ -27,7 +28,6 @@ describe "ProjectManager", ->
|
||||||
describe "updateProjectWithLocks", ->
|
describe "updateProjectWithLocks", ->
|
||||||
describe "rename operations", ->
|
describe "rename operations", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
|
||||||
@firstDocUpdate =
|
@firstDocUpdate =
|
||||||
id: 1
|
id: 1
|
||||||
pathname: 'foo'
|
pathname: 'foo'
|
||||||
|
@ -47,22 +47,22 @@ describe "ProjectManager", ->
|
||||||
|
|
||||||
describe "successfully", ->
|
describe "successfully", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
@ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
||||||
|
|
||||||
it "should rename the docs in the updates", ->
|
it "should rename the docs in the updates", ->
|
||||||
firstDocUpdateWithVersion = _.extend({}, @firstDocUpdate, {version: "#{@version}.0"})
|
firstDocUpdateWithVersion = _.extend({}, @firstDocUpdate, {version: "#{@version}.0"})
|
||||||
secondDocUpdateWithVersion = _.extend({}, @secondDocUpdate, {version: "#{@version}.1"})
|
secondDocUpdateWithVersion = _.extend({}, @secondDocUpdate, {version: "#{@version}.1"})
|
||||||
@DocumentManager.renameDocWithLock
|
@DocumentManager.renameDocWithLock
|
||||||
.calledWith(@project_id, @firstDocUpdate.id, @user_id, firstDocUpdateWithVersion)
|
.calledWith(@project_id, @firstDocUpdate.id, @user_id, firstDocUpdateWithVersion, @projectHistoryId)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
@DocumentManager.renameDocWithLock
|
@DocumentManager.renameDocWithLock
|
||||||
.calledWith(@project_id, @secondDocUpdate.id, @user_id, secondDocUpdateWithVersion)
|
.calledWith(@project_id, @secondDocUpdate.id, @user_id, secondDocUpdateWithVersion, @projectHistoryId)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should rename the files in the updates", ->
|
it "should rename the files in the updates", ->
|
||||||
firstFileUpdateWithVersion = _.extend({}, @firstFileUpdate, {version: "#{@version}.2"})
|
firstFileUpdateWithVersion = _.extend({}, @firstFileUpdate, {version: "#{@version}.2"})
|
||||||
@ProjectHistoryRedisManager.queueRenameEntity
|
@ProjectHistoryRedisManager.queueRenameEntity
|
||||||
.calledWith(@project_id, 'file', @firstFileUpdate.id, @user_id, firstFileUpdateWithVersion)
|
.calledWith(@project_id, @projectHistoryId, 'file', @firstFileUpdate.id, @user_id, firstFileUpdateWithVersion)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should not flush the history", ->
|
it "should not flush the history", ->
|
||||||
|
@ -77,7 +77,7 @@ describe "ProjectManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@error = new Error('error')
|
@error = new Error('error')
|
||||||
@DocumentManager.renameDocWithLock = sinon.stub().yields(@error)
|
@DocumentManager.renameDocWithLock = sinon.stub().yields(@error)
|
||||||
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
@ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
||||||
|
|
||||||
it "should call the callback with the error", ->
|
it "should call the callback with the error", ->
|
||||||
@callback.calledWith(@error).should.equal true
|
@callback.calledWith(@error).should.equal true
|
||||||
|
@ -86,7 +86,7 @@ describe "ProjectManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@error = new Error('error')
|
@error = new Error('error')
|
||||||
@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields(@error)
|
@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields(@error)
|
||||||
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
@ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
||||||
|
|
||||||
it "should call the callback with the error", ->
|
it "should call the callback with the error", ->
|
||||||
@callback.calledWith(@error).should.equal true
|
@callback.calledWith(@error).should.equal true
|
||||||
|
@ -94,7 +94,7 @@ describe "ProjectManager", ->
|
||||||
describe "with enough ops to flush", ->
|
describe "with enough ops to flush", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true)
|
@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true)
|
||||||
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
@ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
||||||
|
|
||||||
it "should flush the history", ->
|
it "should flush the history", ->
|
||||||
@HistoryManager.flushProjectChangesAsync
|
@HistoryManager.flushProjectChangesAsync
|
||||||
|
@ -121,26 +121,26 @@ describe "ProjectManager", ->
|
||||||
|
|
||||||
describe "successfully", ->
|
describe "successfully", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
@ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
||||||
|
|
||||||
it "should add the docs in the updates", ->
|
it "should add the docs in the updates", ->
|
||||||
firstDocUpdateWithVersion = _.extend({}, @firstDocUpdate, {version: "#{@version}.0"})
|
firstDocUpdateWithVersion = _.extend({}, @firstDocUpdate, {version: "#{@version}.0"})
|
||||||
secondDocUpdateWithVersion = _.extend({}, @secondDocUpdate, {version: "#{@version}.1"})
|
secondDocUpdateWithVersion = _.extend({}, @secondDocUpdate, {version: "#{@version}.1"})
|
||||||
@ProjectHistoryRedisManager.queueAddEntity.getCall(0)
|
@ProjectHistoryRedisManager.queueAddEntity.getCall(0)
|
||||||
.calledWith(@project_id, 'doc', @firstDocUpdate.id, @user_id, firstDocUpdateWithVersion)
|
.calledWith(@project_id, @projectHistoryId, 'doc', @firstDocUpdate.id, @user_id, firstDocUpdateWithVersion)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
@ProjectHistoryRedisManager.queueAddEntity.getCall(1)
|
@ProjectHistoryRedisManager.queueAddEntity.getCall(1)
|
||||||
.calledWith(@project_id, 'doc', @secondDocUpdate.id, @user_id, secondDocUpdateWithVersion)
|
.calledWith(@project_id, @projectHistoryId, 'doc', @secondDocUpdate.id, @user_id, secondDocUpdateWithVersion)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should add the files in the updates", ->
|
it "should add the files in the updates", ->
|
||||||
firstFileUpdateWithVersion = _.extend({}, @firstFileUpdate, {version: "#{@version}.2"})
|
firstFileUpdateWithVersion = _.extend({}, @firstFileUpdate, {version: "#{@version}.2"})
|
||||||
secondFileUpdateWithVersion = _.extend({}, @secondFileUpdate, {version: "#{@version}.3"})
|
secondFileUpdateWithVersion = _.extend({}, @secondFileUpdate, {version: "#{@version}.3"})
|
||||||
@ProjectHistoryRedisManager.queueAddEntity.getCall(2)
|
@ProjectHistoryRedisManager.queueAddEntity.getCall(2)
|
||||||
.calledWith(@project_id, 'file', @firstFileUpdate.id, @user_id, firstFileUpdateWithVersion)
|
.calledWith(@project_id, @projectHistoryId, 'file', @firstFileUpdate.id, @user_id, firstFileUpdateWithVersion)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
@ProjectHistoryRedisManager.queueAddEntity.getCall(3)
|
@ProjectHistoryRedisManager.queueAddEntity.getCall(3)
|
||||||
.calledWith(@project_id, 'file', @secondFileUpdate.id, @user_id, secondFileUpdateWithVersion)
|
.calledWith(@project_id, @projectHistoryId, 'file', @secondFileUpdate.id, @user_id, secondFileUpdateWithVersion)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should not flush the history", ->
|
it "should not flush the history", ->
|
||||||
|
@ -155,7 +155,7 @@ describe "ProjectManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@error = new Error('error')
|
@error = new Error('error')
|
||||||
@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error)
|
@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error)
|
||||||
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
@ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
||||||
|
|
||||||
it "should call the callback with the error", ->
|
it "should call the callback with the error", ->
|
||||||
@callback.calledWith(@error).should.equal true
|
@callback.calledWith(@error).should.equal true
|
||||||
|
@ -164,7 +164,7 @@ describe "ProjectManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@error = new Error('error')
|
@error = new Error('error')
|
||||||
@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error)
|
@ProjectHistoryRedisManager.queueAddEntity = sinon.stub().yields(@error)
|
||||||
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
@ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
||||||
|
|
||||||
it "should call the callback with the error", ->
|
it "should call the callback with the error", ->
|
||||||
@callback.calledWith(@error).should.equal true
|
@callback.calledWith(@error).should.equal true
|
||||||
|
@ -172,7 +172,7 @@ describe "ProjectManager", ->
|
||||||
describe "with enough ops to flush", ->
|
describe "with enough ops to flush", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true)
|
@HistoryManager.shouldFlushHistoryOps = sinon.stub().returns(true)
|
||||||
@ProjectManager.updateProjectWithLocks @project_id, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
@ProjectManager.updateProjectWithLocks @project_id, @projectHistoryId, @user_id, @docUpdates, @fileUpdates, @version, @callback
|
||||||
|
|
||||||
it "should flush the history", ->
|
it "should flush the history", ->
|
||||||
@HistoryManager.flushProjectChangesAsync
|
@HistoryManager.flushProjectChangesAsync
|
||||||
|
|
|
@ -33,6 +33,7 @@ describe "RedisManager", ->
|
||||||
docsInProject: ({project_id}) -> "DocsIn:#{project_id}"
|
docsInProject: ({project_id}) -> "DocsIn:#{project_id}"
|
||||||
ranges: ({doc_id}) -> "Ranges:#{doc_id}"
|
ranges: ({doc_id}) -> "Ranges:#{doc_id}"
|
||||||
pathname: ({doc_id}) -> "Pathname:#{doc_id}"
|
pathname: ({doc_id}) -> "Pathname:#{doc_id}"
|
||||||
|
projectHistoryId: ({doc_id}) -> "ProjectHistoryId:#{doc_id}"
|
||||||
projectState: ({project_id}) -> "ProjectState:#{project_id}"
|
projectState: ({project_id}) -> "ProjectState:#{project_id}"
|
||||||
unflushedTime: ({doc_id}) -> "UnflushedTime:#{doc_id}"
|
unflushedTime: ({doc_id}) -> "UnflushedTime:#{doc_id}"
|
||||||
history:
|
history:
|
||||||
|
@ -59,6 +60,7 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
@doc_id = "doc-id-123"
|
@doc_id = "doc-id-123"
|
||||||
@project_id = "project-id-123"
|
@project_id = "project-id-123"
|
||||||
|
@projectHistoryId = 123
|
||||||
@callback = sinon.stub()
|
@callback = sinon.stub()
|
||||||
|
|
||||||
describe "getDoc", ->
|
describe "getDoc", ->
|
||||||
|
@ -72,7 +74,7 @@ describe "RedisManager", ->
|
||||||
@unflushed_time = 12345
|
@unflushed_time = 12345
|
||||||
@pathname = '/a/b/c.tex'
|
@pathname = '/a/b/c.tex'
|
||||||
@multi.get = sinon.stub()
|
@multi.get = sinon.stub()
|
||||||
@multi.exec = sinon.stub().callsArgWith(0, null, [@jsonlines, @version, @hash, @project_id, @json_ranges, @pathname, @unflushed_time])
|
@multi.exec = sinon.stub().callsArgWith(0, null, [@jsonlines, @version, @hash, @project_id, @json_ranges, @pathname, @projectHistoryId.toString(), @unflushed_time])
|
||||||
@rclient.sadd = sinon.stub().yields(null, 0)
|
@rclient.sadd = sinon.stub().yields(null, 0)
|
||||||
|
|
||||||
describe "successfully", ->
|
describe "successfully", ->
|
||||||
|
@ -109,6 +111,11 @@ describe "RedisManager", ->
|
||||||
.calledWith("Pathname:#{@doc_id}")
|
.calledWith("Pathname:#{@doc_id}")
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
it "should get the projectHistoryId as an integer", ->
|
||||||
|
@multi.get
|
||||||
|
.calledWith("ProjectHistoryId:#{@doc_id}")
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
it "should check if the document is in the DocsIn set", ->
|
it "should check if the document is in the DocsIn set", ->
|
||||||
@rclient.sadd
|
@rclient.sadd
|
||||||
.calledWith("DocsIn:#{@project_id}")
|
.calledWith("DocsIn:#{@project_id}")
|
||||||
|
@ -116,7 +123,7 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
it 'should return the document', ->
|
it 'should return the document', ->
|
||||||
@callback
|
@callback
|
||||||
.calledWithExactly(null, @lines, @version, @ranges, @pathname, @unflushed_time)
|
.calledWithExactly(null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushed_time)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it 'should not log any errors', ->
|
it 'should not log any errors', ->
|
||||||
|
@ -125,7 +132,7 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
describe "when the document is not present", ->
|
describe "when the document is not present", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@multi.exec = sinon.stub().callsArgWith(0, null, [null, null, null, null, null, null, null])
|
@multi.exec = sinon.stub().callsArgWith(0, null, [null, null, null, null, null, null, null, null])
|
||||||
@rclient.sadd = sinon.stub().yields()
|
@rclient.sadd = sinon.stub().yields()
|
||||||
@RedisManager.getDoc @project_id, @doc_id, @callback
|
@RedisManager.getDoc @project_id, @doc_id, @callback
|
||||||
|
|
||||||
|
@ -136,7 +143,7 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
it 'should return an empty result', ->
|
it 'should return an empty result', ->
|
||||||
@callback
|
@callback
|
||||||
.calledWithExactly(null, null, 0, {}, null, null)
|
.calledWithExactly(null, null, 0, {}, null, null, null)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it 'should not log any errors', ->
|
it 'should not log any errors', ->
|
||||||
|
@ -154,7 +161,7 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
it 'should return the document', ->
|
it 'should return the document', ->
|
||||||
@callback
|
@callback
|
||||||
.calledWithExactly(null, @lines, @version, @ranges, @pathname, @unflushed_time)
|
.calledWithExactly(null, @lines, @version, @ranges, @pathname, @projectHistoryId, @unflushed_time)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
describe "with a corrupted document", ->
|
describe "with a corrupted document", ->
|
||||||
|
@ -532,7 +539,7 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
describe "with non-empty ranges", ->
|
describe "with non-empty ranges", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, done
|
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId, done
|
||||||
|
|
||||||
it "should set the lines", ->
|
it "should set the lines", ->
|
||||||
@multi.eval
|
@multi.eval
|
||||||
|
@ -564,6 +571,11 @@ describe "RedisManager", ->
|
||||||
.calledWith("Pathname:#{@doc_id}", @pathname)
|
.calledWith("Pathname:#{@doc_id}", @pathname)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
it "should set the projectHistoryId for the doc", ->
|
||||||
|
@multi.set
|
||||||
|
.calledWith("ProjectHistoryId:#{@doc_id}", @projectHistoryId)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
it "should add the doc_id to the project set", ->
|
it "should add the doc_id to the project set", ->
|
||||||
@rclient.sadd
|
@rclient.sadd
|
||||||
.calledWith("DocsIn:#{@project_id}", @doc_id)
|
.calledWith("DocsIn:#{@project_id}", @doc_id)
|
||||||
|
@ -575,7 +587,7 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
describe "with empty ranges", ->
|
describe "with empty ranges", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, {}, @pathname, done
|
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, {}, @pathname, @projectHistoryId, done
|
||||||
|
|
||||||
it "should delete the ranges key", ->
|
it "should delete the ranges key", ->
|
||||||
@multi.del
|
@multi.del
|
||||||
|
@ -590,7 +602,7 @@ describe "RedisManager", ->
|
||||||
describe "with a corrupted write", ->
|
describe "with a corrupted write", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
@multi.exec = sinon.stub().callsArgWith(0, null, ["INVALID-HASH-VALUE"])
|
@multi.exec = sinon.stub().callsArgWith(0, null, ["INVALID-HASH-VALUE"])
|
||||||
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, done
|
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId, done
|
||||||
|
|
||||||
it 'should log a hash error', ->
|
it 'should log a hash error', ->
|
||||||
@logger.error.calledWith()
|
@logger.error.calledWith()
|
||||||
|
@ -600,7 +612,7 @@ describe "RedisManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@_stringify = JSON.stringify
|
@_stringify = JSON.stringify
|
||||||
@JSON.stringify = () -> return '["bad bytes! \u0000 <- here"]'
|
@JSON.stringify = () -> return '["bad bytes! \u0000 <- here"]'
|
||||||
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @callback
|
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId, @callback
|
||||||
|
|
||||||
afterEach ->
|
afterEach ->
|
||||||
@JSON.stringify = @_stringify
|
@JSON.stringify = @_stringify
|
||||||
|
@ -614,7 +626,7 @@ describe "RedisManager", ->
|
||||||
describe "with ranges that are too big", ->
|
describe "with ranges that are too big", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager._serializeRanges = sinon.stub().yields(new Error("ranges are too large"))
|
@RedisManager._serializeRanges = sinon.stub().yields(new Error("ranges are too large"))
|
||||||
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @callback
|
@RedisManager.putDocInMemory @project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId, @callback
|
||||||
|
|
||||||
it 'should log an error', ->
|
it 'should log an error', ->
|
||||||
@logger.error.called.should.equal true
|
@logger.error.called.should.equal true
|
||||||
|
@ -664,6 +676,11 @@ describe "RedisManager", ->
|
||||||
.calledWith("Pathname:#{@doc_id}")
|
.calledWith("Pathname:#{@doc_id}")
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
it "should delete the pathname for the doc", ->
|
||||||
|
@multi.del
|
||||||
|
.calledWith("ProjectHistoryId:#{@doc_id}")
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
describe "clearProjectState", ->
|
describe "clearProjectState", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
@rclient.del = sinon.stub().callsArg(1)
|
@rclient.del = sinon.stub().callsArg(1)
|
||||||
|
@ -687,7 +704,7 @@ describe "RedisManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, 'lines', 'version')
|
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, 'lines', 'version')
|
||||||
@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields()
|
@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields()
|
||||||
@RedisManager.renameDoc @project_id, @doc_id, @userId, @update, @callback
|
@RedisManager.renameDoc @project_id, @doc_id, @userId, @update, @projectHistoryId, @callback
|
||||||
|
|
||||||
it "update the cached pathname", ->
|
it "update the cached pathname", ->
|
||||||
@rclient.set
|
@rclient.set
|
||||||
|
@ -696,19 +713,19 @@ describe "RedisManager", ->
|
||||||
|
|
||||||
it "should queue an update", ->
|
it "should queue an update", ->
|
||||||
@ProjectHistoryRedisManager.queueRenameEntity
|
@ProjectHistoryRedisManager.queueRenameEntity
|
||||||
.calledWithExactly(@project_id, 'doc', @doc_id, @userId, @update, @callback)
|
.calledWithExactly(@project_id, @projectHistoryId, 'doc', @doc_id, @userId, @update, @callback)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
describe "the document is not cached in redis", ->
|
describe "the document is not cached in redis", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null)
|
@RedisManager.getDoc = sinon.stub().callsArgWith(2, null, null, null)
|
||||||
@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields()
|
@ProjectHistoryRedisManager.queueRenameEntity = sinon.stub().yields()
|
||||||
@RedisManager.renameDoc @project_id, @doc_id, @userId, @update, @callback
|
@RedisManager.renameDoc @project_id, @doc_id, @userId, @update, @projectHistoryId, @callback
|
||||||
|
|
||||||
it "does not update the cached pathname", ->
|
it "does not update the cached pathname", ->
|
||||||
@rclient.set.called.should.equal false
|
@rclient.set.called.should.equal false
|
||||||
|
|
||||||
it "should queue an update", ->
|
it "should queue an update", ->
|
||||||
@ProjectHistoryRedisManager.queueRenameEntity
|
@ProjectHistoryRedisManager.queueRenameEntity
|
||||||
.calledWithExactly(@project_id, 'doc', @doc_id, @userId, @update, @callback)
|
.calledWithExactly(@project_id, @projectHistoryId, 'doc', @doc_id, @userId, @update, @callback)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
|
@ -7,6 +7,7 @@ SandboxedModule = require('sandboxed-module')
|
||||||
describe "UpdateManager", ->
|
describe "UpdateManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@project_id = "project-id-123"
|
@project_id = "project-id-123"
|
||||||
|
@projectHistoryId = "history-id-123"
|
||||||
@doc_id = "document-id-123"
|
@doc_id = "document-id-123"
|
||||||
@callback = sinon.stub()
|
@callback = sinon.stub()
|
||||||
@UpdateManager = SandboxedModule.require modulePath, requires:
|
@UpdateManager = SandboxedModule.require modulePath, requires:
|
||||||
|
@ -167,7 +168,7 @@ describe "UpdateManager", ->
|
||||||
@doc_ops_length = sinon.stub()
|
@doc_ops_length = sinon.stub()
|
||||||
@project_ops_length = sinon.stub()
|
@project_ops_length = sinon.stub()
|
||||||
@pathname = '/a/b/c.tex'
|
@pathname = '/a/b/c.tex'
|
||||||
@DocumentManager.getDoc = sinon.stub().yields(null, @lines, @version, @ranges, @pathname)
|
@DocumentManager.getDoc = sinon.stub().yields(null, @lines, @version, @ranges, @pathname, @projectHistoryId)
|
||||||
@RangesManager.applyUpdate = sinon.stub().yields(null, @updated_ranges)
|
@RangesManager.applyUpdate = sinon.stub().yields(null, @updated_ranges)
|
||||||
@ShareJsUpdateManager.applyUpdate = sinon.stub().yields(null, @updatedDocLines, @version, @appliedOps)
|
@ShareJsUpdateManager.applyUpdate = sinon.stub().yields(null, @updatedDocLines, @version, @appliedOps)
|
||||||
@RedisManager.updateDocument = sinon.stub().yields(null, @doc_ops_length, @project_ops_length)
|
@RedisManager.updateDocument = sinon.stub().yields(null, @doc_ops_length, @project_ops_length)
|
||||||
|
@ -196,7 +197,7 @@ describe "UpdateManager", ->
|
||||||
|
|
||||||
it "should add metadata to the ops" , ->
|
it "should add metadata to the ops" , ->
|
||||||
@UpdateManager._addProjectHistoryMetadataToOps
|
@UpdateManager._addProjectHistoryMetadataToOps
|
||||||
.calledWith(@appliedOps, @pathname, @lines)
|
.calledWith(@appliedOps, @pathname, @projectHistoryId, @lines)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should push the applied ops into the history queue", ->
|
it "should push the applied ops into the history queue", ->
|
||||||
|
@ -239,7 +240,7 @@ describe "UpdateManager", ->
|
||||||
@callback.calledWith(@error).should.equal true
|
@callback.calledWith(@error).should.equal true
|
||||||
|
|
||||||
describe "_addProjectHistoryMetadataToOps", ->
|
describe "_addProjectHistoryMetadataToOps", ->
|
||||||
it "should add pathname and doc_length metadata to the ops", ->
|
it "should add projectHistoryId, pathname and doc_length metadata to the ops", ->
|
||||||
lines = [
|
lines = [
|
||||||
'some'
|
'some'
|
||||||
'test'
|
'test'
|
||||||
|
@ -250,20 +251,23 @@ describe "UpdateManager", ->
|
||||||
{ v: 45, op: [{d: "qux", p: 4}, { i: "bazbaz", p: 14 }] },
|
{ v: 45, op: [{d: "qux", p: 4}, { i: "bazbaz", p: 14 }] },
|
||||||
{ v: 49, op: [{i: "penguin", p: 18}] }
|
{ v: 49, op: [{i: "penguin", p: 18}] }
|
||||||
]
|
]
|
||||||
@UpdateManager._addProjectHistoryMetadataToOps(appliedOps, @pathname, lines)
|
@UpdateManager._addProjectHistoryMetadataToOps(appliedOps, @pathname, @projectHistoryId, lines)
|
||||||
appliedOps.should.deep.equal [{
|
appliedOps.should.deep.equal [{
|
||||||
|
projectHistoryId: @projectHistoryId
|
||||||
v: 42
|
v: 42
|
||||||
op: [{i: "foo", p: 4}, { i: "bar", p: 6 }]
|
op: [{i: "foo", p: 4}, { i: "bar", p: 6 }]
|
||||||
meta:
|
meta:
|
||||||
pathname: @pathname
|
pathname: @pathname
|
||||||
doc_length: 14
|
doc_length: 14
|
||||||
}, {
|
}, {
|
||||||
|
projectHistoryId: @projectHistoryId
|
||||||
v: 45
|
v: 45
|
||||||
op: [{d: "qux", p: 4}, { i: "bazbaz", p: 14 }]
|
op: [{d: "qux", p: 4}, { i: "bazbaz", p: 14 }]
|
||||||
meta:
|
meta:
|
||||||
pathname: @pathname
|
pathname: @pathname
|
||||||
doc_length: 20 # 14 + 'foo' + 'bar'
|
doc_length: 20 # 14 + 'foo' + 'bar'
|
||||||
}, {
|
}, {
|
||||||
|
projectHistoryId: @projectHistoryId
|
||||||
v: 49
|
v: 49
|
||||||
op: [{i: "penguin", p: 18}]
|
op: [{i: "penguin", p: 18}]
|
||||||
meta:
|
meta:
|
||||||
|
|
Loading…
Reference in a new issue