mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 07:33:36 -05:00
filter track-changes updates for projects using project-history
This commit is contained in:
parent
e2c75b43ba
commit
dcd7649bad
8 changed files with 82 additions and 29 deletions
|
@ -23,10 +23,12 @@ module.exports = DocumentManager =
|
||||||
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, projectHistoryId) ->
|
PersistenceManager.getDoc project_id, doc_id, (error, lines, version, ranges, pathname, projectHistoryId, projectHistoryType) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
logger.log {project_id, doc_id, lines, version, pathname, projectHistoryId}, "got doc from persistence API"
|
logger.log {project_id, doc_id, lines, version, pathname, projectHistoryId, projectHistoryType}, "got doc from persistence API"
|
||||||
RedisManager.putDocInMemory project_id, doc_id, lines, version, ranges, pathname, projectHistoryId, (error) ->
|
RedisManager.putDocInMemory project_id, doc_id, lines, version, ranges, pathname, projectHistoryId, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
RedisManager.setHistoryType doc_id, projectHistoryType, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback null, lines, version, ranges, pathname, projectHistoryId, null, false
|
callback null, lines, version, ranges, pathname, projectHistoryId, null, false
|
||||||
else
|
else
|
||||||
|
|
|
@ -11,9 +11,14 @@ module.exports = HistoryManager =
|
||||||
if !Settings.apis?.trackchanges?
|
if !Settings.apis?.trackchanges?
|
||||||
logger.warn { doc_id }, "track changes API is not configured, so not flushing"
|
logger.warn { doc_id }, "track changes API is not configured, so not flushing"
|
||||||
return
|
return
|
||||||
|
RedisManager.getHistoryType doc_id, (err, projectHistoryType) ->
|
||||||
|
if err?
|
||||||
|
logger.error {err, doc_id}, "error getting history type"
|
||||||
|
else if projectHistoryType is "project-history"
|
||||||
|
logger.debug {doc_id, projectHistoryType}, "skipping track-changes flush"
|
||||||
|
else
|
||||||
url = "#{Settings.apis.trackchanges.url}/project/#{project_id}/doc/#{doc_id}/flush"
|
url = "#{Settings.apis.trackchanges.url}/project/#{project_id}/doc/#{doc_id}/flush"
|
||||||
logger.log { project_id, doc_id, url }, "flushing doc in track changes api"
|
logger.log { project_id, doc_id, url, projectHistoryType }, "flushing doc in track changes api"
|
||||||
request.post url, (error, res, body)->
|
request.post url, (error, res, body)->
|
||||||
if error?
|
if error?
|
||||||
logger.error { error, doc_id, project_id}, "track changes doc to track changes api"
|
logger.error { error, doc_id, project_id}, "track changes doc to track changes api"
|
||||||
|
@ -52,6 +57,7 @@ module.exports = HistoryManager =
|
||||||
if ops.length == 0
|
if ops.length == 0
|
||||||
return callback()
|
return callback()
|
||||||
|
|
||||||
|
# record updates for project history
|
||||||
if Settings.apis?.project_history?.enabled
|
if Settings.apis?.project_history?.enabled
|
||||||
if HistoryManager.shouldFlushHistoryOps(project_ops_length, ops.length, HistoryManager.FLUSH_PROJECT_EVERY_N_OPS)
|
if HistoryManager.shouldFlushHistoryOps(project_ops_length, ops.length, HistoryManager.FLUSH_PROJECT_EVERY_N_OPS)
|
||||||
# Do this in the background since it uses HTTP and so may be too
|
# Do this in the background since it uses HTTP and so may be too
|
||||||
|
@ -59,6 +65,13 @@ module.exports = HistoryManager =
|
||||||
logger.log { project_ops_length, project_id }, "flushing project history api"
|
logger.log { project_ops_length, project_id }, "flushing project history api"
|
||||||
HistoryManager.flushProjectChangesAsync project_id
|
HistoryManager.flushProjectChangesAsync project_id
|
||||||
|
|
||||||
|
# if the doc_ops_length is undefined it means the project is not using track-changes
|
||||||
|
# so we can bail out here
|
||||||
|
if typeof(doc_ops_length) is 'undefined'
|
||||||
|
logger.debug { project_id, doc_id}, "skipping flush to track-changes, only using project-history"
|
||||||
|
return callback()
|
||||||
|
|
||||||
|
# record updates for track-changes
|
||||||
HistoryRedisManager.recordDocHasHistoryOps project_id, doc_id, ops, (error) ->
|
HistoryRedisManager.recordDocHasHistoryOps project_id, doc_id, ops, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if HistoryManager.shouldFlushHistoryOps(doc_ops_length, ops.length, HistoryManager.FLUSH_DOC_EVERY_N_OPS)
|
if HistoryManager.shouldFlushHistoryOps(doc_ops_length, ops.length, HistoryManager.FLUSH_DOC_EVERY_N_OPS)
|
||||||
|
|
|
@ -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, projectHistoryId) ->) ->
|
getDoc: (project_id, doc_id, _callback = (error, lines, version, ranges, pathname, projectHistoryId, projectHistoryType) ->) ->
|
||||||
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, body.projectHistoryId
|
return callback null, body.lines, body.version, body.ranges, body.pathname, body.projectHistoryId, body.projectHistoryType
|
||||||
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
|
||||||
|
|
|
@ -78,6 +78,7 @@ module.exports = RedisManager =
|
||||||
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.projectHistoryId(doc_id:doc_id)
|
||||||
|
multi.del keys.projectHistoryType(doc_id:doc_id)
|
||||||
multi.del keys.unflushedTime(doc_id:doc_id)
|
multi.del keys.unflushedTime(doc_id:doc_id)
|
||||||
multi.del keys.lastUpdatedAt(doc_id: doc_id)
|
multi.del keys.lastUpdatedAt(doc_id: doc_id)
|
||||||
multi.del keys.lastUpdatedBy(doc_id: doc_id)
|
multi.del keys.lastUpdatedBy(doc_id: doc_id)
|
||||||
|
@ -154,11 +155,11 @@ module.exports = RedisManager =
|
||||||
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, projectHistoryId, unflushedTime, lastUpdatedAt, lastUpdatedBy
|
callback null, docLines, version, ranges, pathname, projectHistoryId, unflushedTime, lastUpdatedAt, lastUpdatedBy
|
||||||
|
|
||||||
getDocVersion: (doc_id, callback = (error, version) ->) ->
|
getDocVersion: (doc_id, callback = (error, version, projectHistoryType) ->) ->
|
||||||
rclient.get keys.docVersion(doc_id: doc_id), (error, version) ->
|
rclient.mget keys.docVersion(doc_id: doc_id), keys.projectHistoryType(doc_id:doc_id), (error, version, projectHistoryType) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
version = parseInt(version, 10)
|
version = parseInt(version, 10)
|
||||||
callback null, version
|
callback null, version, projectHistoryType
|
||||||
|
|
||||||
getDocLines: (doc_id, callback = (error, version) ->) ->
|
getDocLines: (doc_id, callback = (error, version) ->) ->
|
||||||
rclient.get keys.docLines(doc_id: doc_id), (error, docLines) ->
|
rclient.get keys.docLines(doc_id: doc_id), (error, docLines) ->
|
||||||
|
@ -200,10 +201,18 @@ module.exports = RedisManager =
|
||||||
return callback(error)
|
return callback(error)
|
||||||
callback null, ops
|
callback null, ops
|
||||||
|
|
||||||
|
getHistoryType: (doc_id, callback = (error, projectHistoryType) ->) ->
|
||||||
|
rclient.get keys.projectHistoryType(doc_id:doc_id), (error, projectHistoryType) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
callback null, projectHistoryType
|
||||||
|
|
||||||
|
setHistoryType: (doc_id, projectHistoryType, callback = (error) ->) ->
|
||||||
|
rclient.set keys.projectHistoryType(doc_id:doc_id), projectHistoryType, callback
|
||||||
|
|
||||||
DOC_OPS_TTL: 60 * minutes
|
DOC_OPS_TTL: 60 * minutes
|
||||||
DOC_OPS_MAX_LENGTH: 100
|
DOC_OPS_MAX_LENGTH: 100
|
||||||
updateDocument : (project_id, doc_id, docLines, newVersion, appliedOps = [], ranges, updateMeta, callback = (error) ->)->
|
updateDocument : (project_id, doc_id, docLines, newVersion, appliedOps = [], ranges, updateMeta, callback = (error) ->)->
|
||||||
RedisManager.getDocVersion doc_id, (error, currentVersion) ->
|
RedisManager.getDocVersion doc_id, (error, currentVersion, projectHistoryType) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if currentVersion + appliedOps.length != newVersion
|
if currentVersion + appliedOps.length != newVersion
|
||||||
error = new Error("Version mismatch. '#{doc_id}' is corrupted.")
|
error = new Error("Version mismatch. '#{doc_id}' is corrupted.")
|
||||||
|
@ -249,6 +258,10 @@ module.exports = RedisManager =
|
||||||
multi.rpush keys.docOps(doc_id: doc_id), jsonOps... # index 5
|
multi.rpush keys.docOps(doc_id: doc_id), jsonOps... # index 5
|
||||||
# expire must come after rpush since before it will be a no-op if the list is empty
|
# expire must come after rpush since before it will be a no-op if the list is empty
|
||||||
multi.expire keys.docOps(doc_id: doc_id), RedisManager.DOC_OPS_TTL # index 6
|
multi.expire keys.docOps(doc_id: doc_id), RedisManager.DOC_OPS_TTL # index 6
|
||||||
|
if projectHistoryType is "project-history"
|
||||||
|
logger.debug {doc_id}, "skipping push of uncompressed ops for project using project-history"
|
||||||
|
else
|
||||||
|
# project is using old track-changes history service
|
||||||
multi.rpush historyKeys.uncompressedHistoryOps(doc_id: doc_id), jsonOps... # index 7
|
multi.rpush historyKeys.uncompressedHistoryOps(doc_id: doc_id), jsonOps... # index 7
|
||||||
# Set the unflushed timestamp to the current time if the doc
|
# Set the unflushed timestamp to the current time if the doc
|
||||||
# hasn't been modified before (the content in mongo has been
|
# hasn't been modified before (the content in mongo has been
|
||||||
|
@ -262,8 +275,11 @@ module.exports = RedisManager =
|
||||||
multi.exec (error, result) ->
|
multi.exec (error, result) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
|
|
||||||
# length of uncompressedHistoryOps queue (index 7)
|
if projectHistoryType is 'project-history'
|
||||||
docUpdateCount = result[7]
|
docUpdateCount = undefined # only using project history, don't bother with track-changes
|
||||||
|
else
|
||||||
|
# project is using old track-changes history service
|
||||||
|
docUpdateCount = result[7] # length of uncompressedHistoryOps queue (index 7)
|
||||||
|
|
||||||
if jsonOps.length > 0 && Settings.apis?.project_history?.enabled
|
if jsonOps.length > 0 && Settings.apis?.project_history?.enabled
|
||||||
ProjectHistoryRedisManager.queueOps project_id, jsonOps..., (error, projectUpdateCount) ->
|
ProjectHistoryRedisManager.queueOps project_id, jsonOps..., (error, projectUpdateCount) ->
|
||||||
|
|
|
@ -70,6 +70,7 @@ module.exports =
|
||||||
unflushedTime: ({doc_id}) -> "UnflushedTime:{#{doc_id}}"
|
unflushedTime: ({doc_id}) -> "UnflushedTime:{#{doc_id}}"
|
||||||
pathname: ({doc_id}) -> "Pathname:{#{doc_id}}"
|
pathname: ({doc_id}) -> "Pathname:{#{doc_id}}"
|
||||||
projectHistoryId: ({doc_id}) -> "ProjectHistoryId:{#{doc_id}}"
|
projectHistoryId: ({doc_id}) -> "ProjectHistoryId:{#{doc_id}}"
|
||||||
|
projectHistoryType: ({doc_id}) -> "ProjectHistoryType:{#{doc_id}}"
|
||||||
projectState: ({project_id}) -> "ProjectState:{#{project_id}}"
|
projectState: ({project_id}) -> "ProjectState:{#{project_id}}"
|
||||||
pendingUpdates: ({doc_id}) -> "PendingUpdates:{#{doc_id}}"
|
pendingUpdates: ({doc_id}) -> "PendingUpdates:{#{doc_id}}"
|
||||||
lastUpdatedBy: ({doc_id}) -> "lastUpdatedBy:{#{doc_id}}"
|
lastUpdatedBy: ({doc_id}) -> "lastUpdatedBy:{#{doc_id}}"
|
||||||
|
|
|
@ -27,6 +27,7 @@ describe "DocumentManager", ->
|
||||||
"./RangesManager": @RangesManager = {}
|
"./RangesManager": @RangesManager = {}
|
||||||
@project_id = "project-id-123"
|
@project_id = "project-id-123"
|
||||||
@projectHistoryId = "history-id-123"
|
@projectHistoryId = "history-id-123"
|
||||||
|
@projectHistoryType = "project-history"
|
||||||
@doc_id = "doc-id-123"
|
@doc_id = "doc-id-123"
|
||||||
@user_id = 1234
|
@user_id = 1234
|
||||||
@callback = sinon.stub()
|
@callback = sinon.stub()
|
||||||
|
@ -178,8 +179,9 @@ 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, @projectHistoryId)
|
@PersistenceManager.getDoc = sinon.stub().callsArgWith(2, null, @lines, @version, @ranges, @pathname, @projectHistoryId, @projectHistoryType)
|
||||||
@RedisManager.putDocInMemory = sinon.stub().yields()
|
@RedisManager.putDocInMemory = sinon.stub().yields()
|
||||||
|
@RedisManager.setHistoryType = sinon.stub().yields()
|
||||||
@DocumentManager.getDoc @project_id, @doc_id, @callback
|
@DocumentManager.getDoc @project_id, @doc_id, @callback
|
||||||
|
|
||||||
it "should try to get the doc from Redis", ->
|
it "should try to get the doc from Redis", ->
|
||||||
|
@ -197,6 +199,11 @@ describe "DocumentManager", ->
|
||||||
.calledWith(@project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId)
|
.calledWith(@project_id, @doc_id, @lines, @version, @ranges, @pathname, @projectHistoryId)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
it "should set the history type in Redis", ->
|
||||||
|
@RedisManager.setHistoryType
|
||||||
|
.calledWith(@doc_id, @projectHistoryType)
|
||||||
|
.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, @projectHistoryId, null, false).should.equal true
|
@callback.calledWith(null, @lines, @version, @ranges, @pathname, @projectHistoryId, null, false).should.equal true
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ describe "HistoryManager", ->
|
||||||
trackchanges:
|
trackchanges:
|
||||||
url: "http://trackchanges.example.com"
|
url: "http://trackchanges.example.com"
|
||||||
}
|
}
|
||||||
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
|
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub(), debug: sinon.stub() }
|
||||||
"./DocumentManager": @DocumentManager = {}
|
"./DocumentManager": @DocumentManager = {}
|
||||||
"./HistoryRedisManager": @HistoryRedisManager = {}
|
"./HistoryRedisManager": @HistoryRedisManager = {}
|
||||||
"./RedisManager": @RedisManager = {}
|
"./RedisManager": @RedisManager = {}
|
||||||
|
@ -28,6 +28,9 @@ describe "HistoryManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@request.post = sinon.stub().callsArgWith(1, null, statusCode: 204)
|
@request.post = sinon.stub().callsArgWith(1, null, statusCode: 204)
|
||||||
|
|
||||||
|
describe "when the project uses track changes", ->
|
||||||
|
beforeEach ->
|
||||||
|
@RedisManager.getHistoryType = sinon.stub().yields(null, 'track-changes')
|
||||||
@HistoryManager.flushDocChangesAsync @project_id, @doc_id
|
@HistoryManager.flushDocChangesAsync @project_id, @doc_id
|
||||||
|
|
||||||
it "should send a request to the track changes api", ->
|
it "should send a request to the track changes api", ->
|
||||||
|
@ -35,6 +38,16 @@ describe "HistoryManager", ->
|
||||||
.calledWith("#{@Settings.apis.trackchanges.url}/project/#{@project_id}/doc/#{@doc_id}/flush")
|
.calledWith("#{@Settings.apis.trackchanges.url}/project/#{@project_id}/doc/#{@doc_id}/flush")
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
describe "when the project uses project history", ->
|
||||||
|
beforeEach ->
|
||||||
|
@RedisManager.getHistoryType = sinon.stub().yields(null, 'project-history')
|
||||||
|
@HistoryManager.flushDocChangesAsync @project_id, @doc_id
|
||||||
|
|
||||||
|
it "should not send a request to the track changes api", ->
|
||||||
|
@request.post
|
||||||
|
.called
|
||||||
|
.should.equal false
|
||||||
|
|
||||||
describe "flushProjectChangesAsync", ->
|
describe "flushProjectChangesAsync", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@request.post = sinon.stub().callsArgWith(1, null, statusCode: 204)
|
@request.post = sinon.stub().callsArgWith(1, null, statusCode: 204)
|
||||||
|
|
|
@ -34,6 +34,7 @@ describe "RedisManager", ->
|
||||||
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}"
|
projectHistoryId: ({doc_id}) -> "ProjectHistoryId:#{doc_id}"
|
||||||
|
projectHistoryType: ({doc_id}) -> "ProjectHistoryType:#{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}"
|
||||||
lastUpdatedBy: ({doc_id}) -> "lastUpdatedBy:#{doc_id}"
|
lastUpdatedBy: ({doc_id}) -> "lastUpdatedBy:#{doc_id}"
|
||||||
|
|
Loading…
Reference in a new issue