From fdcb806518761c9e015559e072fe87c25bbfae71 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 20 Jul 2018 10:43:31 +0100 Subject: [PATCH] set a timestamp for the first entry in the projectHistory:Ops queue --- .../coffee/ProjectHistoryRedisManager.coffee | 14 +++++++++-- .../config/settings.defaults.coffee | 1 + .../coffee/ApplyingUpdatesToADocTests.coffee | 23 ++++++++++++++++++- .../ProjectHistoryRedisManagerTests.coffee | 17 +++++++++++--- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/services/document-updater/app/coffee/ProjectHistoryRedisManager.coffee b/services/document-updater/app/coffee/ProjectHistoryRedisManager.coffee index 42b9f16df2..1cc80ea722 100644 --- a/services/document-updater/app/coffee/ProjectHistoryRedisManager.coffee +++ b/services/document-updater/app/coffee/ProjectHistoryRedisManager.coffee @@ -4,8 +4,18 @@ rclient = require("redis-sharelatex").createClient(Settings.redis.documentupdate logger = require('logger-sharelatex') module.exports = ProjectHistoryRedisManager = - queueOps: (project_id, ops..., callback) -> - rclient.rpush projectHistoryKeys.projectHistoryOps({project_id}), ops..., callback + queueOps: (project_id, ops..., callback = (error, projectUpdateCount) ->) -> + multi = rclient.multi() + # Push the ops onto the project history queue + multi.rpush projectHistoryKeys.projectHistoryOps({project_id}), ops... + # To record the age of the oldest op on the queue set a timestamp if not + # already present (SETNX). + multi.setnx projectHistoryKeys.projectHistoryFirstOpTimestamp({project_id}), Date.now() + multi.exec (error, result) -> + return callback(error) if error? + # return the number of entries pushed onto the project history queue + callback null, result[0] + queueRenameEntity: (project_id, projectHistoryId, entity_type, entity_id, user_id, projectUpdate, callback) -> projectUpdate = diff --git a/services/document-updater/config/settings.defaults.coffee b/services/document-updater/config/settings.defaults.coffee index 18f2b1570b..dfce36f6e3 100755 --- a/services/document-updater/config/settings.defaults.coffee +++ b/services/document-updater/config/settings.defaults.coffee @@ -74,6 +74,7 @@ module.exports = project_history: key_schema: projectHistoryOps: ({project_id}) -> "ProjectHistory:Ops:#{project_id}" + projectHistoryFirstOpTimestamp: ({project_id}) -> "ProjectHistory:FirstOpTimestamp:#{project_id}" # cluster: [{ # port: "7000" # host: "localhost" diff --git a/services/document-updater/test/acceptance/coffee/ApplyingUpdatesToADocTests.coffee b/services/document-updater/test/acceptance/coffee/ApplyingUpdatesToADocTests.coffee index cb560a26bb..c6aa6fe856 100644 --- a/services/document-updater/test/acceptance/coffee/ApplyingUpdatesToADocTests.coffee +++ b/services/document-updater/test/acceptance/coffee/ApplyingUpdatesToADocTests.coffee @@ -33,7 +33,7 @@ describe "Applying updates to a doc", -> before (done) -> [@project_id, @doc_id] = [DocUpdaterClient.randomId(), DocUpdaterClient.randomId()] sinon.spy MockWebApi, "getDocument" - + @startTime = Date.now() MockWebApi.insertDoc @project_id, @doc_id, {lines: @lines, version: @version} DocUpdaterClient.sendUpdate @project_id, @doc_id, @update, (error) -> throw error if error? @@ -67,6 +67,27 @@ describe "Applying updates to a doc", -> JSON.parse(updates[0]).op.should.deep.equal @update.op done() + it "should set the first op timestamp", (done) -> + rclient_history.get ProjectHistoryKeys.projectHistoryFirstOpTimestamp({@project_id}), (error, result) => + throw error if error? + result.should.be.within(@startTime, Date.now()) + @firstOpTimestamp = result + done() + + describe "when sending another update", -> + before (done) -> + @second_update = Object.create(@update) + @second_update.v = @version + 1 + DocUpdaterClient.sendUpdate @project_id, @doc_id, @second_update, (error) -> + throw error if error? + setTimeout done, 200 + + it "should not change the first op timestamp", (done) -> + rclient_history.get ProjectHistoryKeys.projectHistoryFirstOpTimestamp({@project_id}), (error, result) => + throw error if error? + result.should.equal @firstOpTimestamp + done() + describe "when the document is loaded", -> before (done) -> [@project_id, @doc_id] = [DocUpdaterClient.randomId(), DocUpdaterClient.randomId()] diff --git a/services/document-updater/test/unit/coffee/ProjectHistoryRedisManager/ProjectHistoryRedisManagerTests.coffee b/services/document-updater/test/unit/coffee/ProjectHistoryRedisManager/ProjectHistoryRedisManagerTests.coffee index aa777b40d6..8c6ff5c7d7 100644 --- a/services/document-updater/test/unit/coffee/ProjectHistoryRedisManager/ProjectHistoryRedisManagerTests.coffee +++ b/services/document-updater/test/unit/coffee/ProjectHistoryRedisManager/ProjectHistoryRedisManagerTests.coffee @@ -20,6 +20,7 @@ describe "ProjectHistoryRedisManager", -> project_history: key_schema: projectHistoryOps: ({project_id}) -> "ProjectHistory:Ops:#{project_id}" + projectHistoryFirstOpTimestamp: ({project_id}) -> "ProjectHistory:FirstOpTimestamp:#{project_id}" } "redis-sharelatex": createClient: () => @rclient @@ -32,16 +33,26 @@ describe "ProjectHistoryRedisManager", -> describe "queueOps", -> beforeEach -> @ops = ["mock-op-1", "mock-op-2"] - @rclient.rpush = sinon.stub() + @multi = exec: sinon.stub() + @multi.rpush = sinon.stub() + @multi.setnx = sinon.stub() + @rclient.multi = () => @multi + # @rclient = multi: () => @multi @ProjectHistoryRedisManager.queueOps @project_id, @ops..., @callback it "should queue an update", -> - @rclient.rpush + @multi.rpush .calledWithExactly( "ProjectHistory:Ops:#{@project_id}" @ops[0] @ops[1] - @callback + ).should.equal true + + it "should set the queue timestamp if not present", -> + @multi.setnx + .calledWithExactly( + "ProjectHistory:FirstOpTimestamp:#{@project_id}" + Date.now() ).should.equal true describe "queueRenameEntity", ->