Просмотр исходного кода

Add HTTP end point for accepting changes

James Allen 9 лет назад
Родитель
Сommit
be19532a1d

+ 1 - 0
services/document-updater/app.coffee

@@ -45,6 +45,7 @@ app.post   '/project/:project_id/doc/:doc_id/flush', HttpController.flushDocIfLo
 app.delete '/project/:project_id/doc/:doc_id',       HttpController.flushAndDeleteDoc
 app.delete '/project/:project_id/doc/:doc_id',       HttpController.flushAndDeleteDoc
 app.delete '/project/:project_id',                   HttpController.deleteProject
 app.delete '/project/:project_id',                   HttpController.deleteProject
 app.post   '/project/:project_id/flush',             HttpController.flushProject
 app.post   '/project/:project_id/flush',             HttpController.flushProject
+app.post   '/project/:project_id/doc/:doc_id/change/:change_id/accept', HttpController.acceptChange
 
 
 app.get '/total', (req, res)->
 app.get '/total', (req, res)->
 	timer = new Metrics.Timer("http.allDocList")	
 	timer = new Metrics.Timer("http.allDocList")	

+ 22 - 1
services/document-updater/app/coffee/DocumentManager.coffee

@@ -5,6 +5,8 @@ logger = require "logger-sharelatex"
 Metrics = require "./Metrics"
 Metrics = require "./Metrics"
 HistoryManager = require "./HistoryManager"
 HistoryManager = require "./HistoryManager"
 WebRedisManager = require "./WebRedisManager"
 WebRedisManager = require "./WebRedisManager"
+Errors = require "./Errors"
+RangesManager = require "./RangesManager"
 
 
 module.exports = DocumentManager =
 module.exports = DocumentManager =
 	getDoc: (project_id, doc_id, _callback = (error, lines, version, alreadyLoaded) ->) ->
 	getDoc: (project_id, doc_id, _callback = (error, lines, version, alreadyLoaded) ->) ->
@@ -83,7 +85,6 @@ module.exports = DocumentManager =
 						DocumentManager.flushAndDeleteDoc project_id, doc_id, (error) ->
 						DocumentManager.flushAndDeleteDoc project_id, doc_id, (error) ->
 							return callback(error) if error?
 							return callback(error) if error?
 							callback null
 							callback null
-		
 
 
 	flushDocIfLoaded: (project_id, doc_id, _callback = (error) ->) ->
 	flushDocIfLoaded: (project_id, doc_id, _callback = (error) ->) ->
 		timer = new Metrics.Timer("docManager.flushDocIfLoaded")
 		timer = new Metrics.Timer("docManager.flushDocIfLoaded")
@@ -119,6 +120,22 @@ module.exports = DocumentManager =
 			RedisManager.removeDocFromMemory project_id, doc_id, (error) ->
 			RedisManager.removeDocFromMemory project_id, doc_id, (error) ->
 				return callback(error) if error?
 				return callback(error) if error?
 				callback null
 				callback null
+	
+	acceptChange: (project_id, doc_id, change_id, _callback = (error) ->) ->
+		timer = new Metrics.Timer("docManager.acceptChange")
+		callback = (args...) ->
+			timer.done()
+			_callback(args...)
+
+		DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
+			return callback(error) if error?
+			if !lines? or !version?
+				return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
+			RangesManager.acceptChange change_id, ranges, (error, new_ranges) ->
+				return callback(error) if error?
+				RedisManager.updateDocument doc_id, lines, version, [], new_ranges, (error) ->
+					return callback(error) if error?
+					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"
@@ -139,3 +156,7 @@ module.exports = DocumentManager =
 	flushAndDeleteDocWithLock: (project_id, doc_id, callback = (error) ->) ->
 	flushAndDeleteDocWithLock: (project_id, doc_id, callback = (error) ->) ->
 		UpdateManager = require "./UpdateManager"
 		UpdateManager = require "./UpdateManager"
 		UpdateManager.lockUpdatesAndDo DocumentManager.flushAndDeleteDoc, project_id, doc_id, callback
 		UpdateManager.lockUpdatesAndDo DocumentManager.flushAndDeleteDoc, project_id, doc_id, callback
+
+	acceptChangeWithLock: (project_id, doc_id, change_id, callback = (error) ->) ->
+		UpdateManager = require "./UpdateManager"
+		UpdateManager.lockUpdatesAndDo DocumentManager.acceptChange, project_id, doc_id, change_id, callback

+ 11 - 0
services/document-updater/app/coffee/HttpController.coffee

@@ -97,4 +97,15 @@ module.exports = HttpController =
 			return next(error) if error?
 			return next(error) if error?
 			logger.log project_id: project_id, "deleted project via http"
 			logger.log project_id: project_id, "deleted project via http"
 			res.send 204 # No Content
 			res.send 204 # No Content
+	
+	acceptChange: (req, res, next = (error) ->) ->
+		{project_id, doc_id, change_id} = req.params
+		logger.log {project_id, doc_id, change_id}, "accepting change via http"
+		timer = new Metrics.Timer("http.acceptChange")
+		DocumentManager.acceptChangeWithLock project_id, doc_id, change_id, (error) ->
+			timer.done()
+			return next(error) if error?
+			logger.log {project_id, doc_id, change_id}, "accepted change via http"
+			res.send 204 # No Content
+		
 
 

+ 16 - 4
services/document-updater/app/coffee/RangesManager.coffee

@@ -4,7 +4,7 @@ logger = require "logger-sharelatex"
 module.exports = RangesManager =
 module.exports = RangesManager =
 	applyUpdate: (project_id, doc_id, entries = {}, updates = [], callback = (error, new_entries) ->) ->
 	applyUpdate: (project_id, doc_id, entries = {}, updates = [], callback = (error, new_entries) ->) ->
 		{changes, comments} = entries
 		{changes, comments} = entries
-		logger.log {changes, comments, updates}, "appliyng updates to ranges"
+		logger.log {changes, comments, updates}, "applying updates to ranges"
 		rangesTracker = new RangesTracker(changes, comments)
 		rangesTracker = new RangesTracker(changes, comments)
 		for update in updates
 		for update in updates
 			rangesTracker.track_changes = !!update.meta.tc
 			rangesTracker.track_changes = !!update.meta.tc
@@ -12,7 +12,20 @@ module.exports = RangesManager =
 				rangesTracker.setIdSeed(update.meta.tc)
 				rangesTracker.setIdSeed(update.meta.tc)
 			for op in update.op
 			for op in update.op
 				rangesTracker.applyOp(op, { user_id: update.meta?.user_id })
 				rangesTracker.applyOp(op, { user_id: update.meta?.user_id })
-		
+
+		response = RangesManager._getRanges rangesTracker
+		logger.log {response}, "applied updates to ranges"
+		callback null, response
+
+	acceptChange: (change_id, ranges, callback = (error, ranges) ->) ->
+		{changes, comments} = ranges
+		logger.log {changes, comments, change_id}, "accepting change in ranges"
+		rangesTracker = new RangesTracker(changes, comments)
+		rangesTracker.removeChangeId(change_id)
+		response = RangesManager._getRanges(rangesTracker)
+		callback null, response
+	
+	_getRanges: (rangesTracker) ->
 		# Return the minimal data structure needed, since most documents won't have any
 		# Return the minimal data structure needed, since most documents won't have any
 		# changes or comments
 		# changes or comments
 		response = {}
 		response = {}
@@ -22,5 +35,4 @@ module.exports = RangesManager =
 		if rangesTracker.comments?.length > 0
 		if rangesTracker.comments?.length > 0
 			response ?= {}
 			response ?= {}
 			response.comments = rangesTracker.comments
 			response.comments = rangesTracker.comments
-		logger.log {response}, "applied updates to ranges"
-		callback null, response
+		return response

+ 43 - 0
services/document-updater/test/acceptance/coffee/RangesTests.coffee

@@ -1,6 +1,7 @@
 sinon = require "sinon"
 sinon = require "sinon"
 chai = require("chai")
 chai = require("chai")
 chai.should()
 chai.should()
+expect = chai.expect
 async = require "async"
 async = require "async"
 rclient = require("redis").createClient()
 rclient = require("redis").createClient()
 
 
@@ -182,3 +183,45 @@ describe "Ranges", ->
 					changes[0].op.should.deep.equal { i: "123", p: 1 }
 					changes[0].op.should.deep.equal { i: "123", p: 1 }
 					changes[1].op.should.deep.equal { i: "456", p: 5 }
 					changes[1].op.should.deep.equal { i: "456", p: 5 }
 					done()
 					done()
+
+	describe "accepting a change", ->
+		before (done) ->
+			@project_id = DocUpdaterClient.randomId()
+			@user_id = DocUpdaterClient.randomId()
+			@id_seed = "587357bd35e64f6157"
+			@doc = {
+				id: DocUpdaterClient.randomId()
+				lines: ["aaa"]
+			}
+			@update = {
+				doc: @doc.id
+				op: [{ i: "456", p: 1 }]
+				v: 0
+				meta: { user_id: @user_id, tc: @id_seed }
+			}
+			MockWebApi.insertDoc @project_id, @doc.id, {
+				lines: @doc.lines
+				version: 0
+			}
+			DocUpdaterClient.preloadDoc @project_id, @doc.id, (error) =>
+				throw error if error?
+				DocUpdaterClient.sendUpdate @project_id, @doc.id, @update, (error) =>
+					throw error if error?
+					setTimeout () =>
+						DocUpdaterClient.getDoc @project_id, @doc.id, (error, res, data) =>
+							throw error if error?
+							ranges = data.ranges
+							change = ranges.changes[0]
+							change.op.should.deep.equal { i: "456", p: 1 }
+							change.id.should.equal @id_seed + "000001"
+							change.metadata.user_id.should.equal @user_id
+							done()
+					, 200
+		
+		it "should remove the change after accepting", (done) ->
+			DocUpdaterClient.acceptChange @project_id, @doc.id, @id_seed + "000001", (error) =>
+				throw error if error?
+				DocUpdaterClient.getDoc @project_id, @doc.id, (error, res, data) =>
+					throw error if error?
+					expect(data.ranges.changes).to.be.undefined
+					done()

+ 3 - 0
services/document-updater/test/acceptance/coffee/helpers/DocUpdaterClient.coffee

@@ -72,3 +72,6 @@ module.exports = DocUpdaterClient =
 
 
 	deleteProject: (project_id, callback = () ->) ->
 	deleteProject: (project_id, callback = () ->) ->
 		request.del "http://localhost:3003/project/#{project_id}", callback
 		request.del "http://localhost:3003/project/#{project_id}", callback
+
+	acceptChange: (project_id, doc_id, change_id, callback = () ->) ->
+		request.post "http://localhost:3003/project/#{project_id}/doc/#{doc_id}/change/#{change_id}/accept", callback

+ 49 - 1
services/document-updater/test/unit/coffee/DocumentManager/DocumentManagerTests.coffee

@@ -3,6 +3,7 @@ chai = require('chai')
 should = chai.should()
 should = chai.should()
 modulePath = "../../../../app/js/DocumentManager.js"
 modulePath = "../../../../app/js/DocumentManager.js"
 SandboxedModule = require('sandboxed-module')
 SandboxedModule = require('sandboxed-module')
+Errors = require "../../../../app/js/Errors"
 
 
 describe "DocumentManager", ->
 describe "DocumentManager", ->
 	beforeEach ->
 	beforeEach ->
@@ -18,6 +19,7 @@ describe "DocumentManager", ->
 			"./WebRedisManager": @WebRedisManager = {}
 			"./WebRedisManager": @WebRedisManager = {}
 			"./DiffCodec": @DiffCodec = {}
 			"./DiffCodec": @DiffCodec = {}
 			"./UpdateManager": @UpdateManager = {}
 			"./UpdateManager": @UpdateManager = {}
+			"./RangesManager": @RangesManager = {}
 		@project_id = "project-id-123"
 		@project_id = "project-id-123"
 		@doc_id = "doc-id-123"
 		@doc_id = "doc-id-123"
 		@callback = sinon.stub()
 		@callback = sinon.stub()
@@ -259,4 +261,50 @@ describe "DocumentManager", ->
 					@callback.calledWith(new Error("No lines were passed to setDoc"))
 					@callback.calledWith(new Error("No lines were passed to setDoc"))
 					
 					
 				it "should not try to get the doc lines", ->
 				it "should not try to get the doc lines", ->
-					@DocumentManager.getDoc.called.should.equal false
+					@DocumentManager.getDoc.called.should.equal false
+	
+	describe "acceptChanges", ->
+		beforeEach ->
+			@change_id = "mock-change-id"
+			@version = 34
+			@lines = ["original", "lines"]
+			@ranges = { entries: "mock", comments: "mock" }
+			@updated_ranges = { entries: "updated", comments: "updated" }
+			@DocumentManager.getDoc = sinon.stub().yields(null, @lines, @version, @ranges)
+			@RangesManager.acceptChange = sinon.stub().yields(null, @updated_ranges)
+			@RedisManager.updateDocument = sinon.stub().yields()
+		
+		describe "successfully", ->
+			beforeEach ->
+				@DocumentManager.acceptChange @project_id, @doc_id, @change_id, @callback
+			
+			it "should get the document's current ranges", ->
+				@DocumentManager.getDoc
+					.calledWith(@project_id, @doc_id)
+					.should.equal true
+			
+			it "should apply the accept change to the ranges", ->
+				@RangesManager.acceptChange
+					.calledWith(@change_id, @ranges)
+					.should.equal true
+					
+			it "should save the updated ranges", ->
+				@RedisManager.updateDocument
+					.calledWith(@doc_id, @lines, @version, [], @updated_ranges)
+					.should.equal true
+			
+			it "should call the callback", ->
+				@callback.called.should.equal true
+
+		describe "when the doc is not found", ->
+			beforeEach ->
+				@DocumentManager.getDoc = sinon.stub().yields(null, null, null, null)
+				@DocumentManager.acceptChange @project_id, @doc_id, @change_id, @callback
+
+			it "should not save anything", ->
+				@RedisManager.updateDocument.called.should.equal false
+			
+			it "should call the callback with a not found error", ->
+				error = new Errors.NotFoundError("document not found: #{@doc_id}")
+				@callback.calledWith(error).should.equal true
+			

+ 42 - 1
services/document-updater/test/unit/coffee/HttpController/HttpControllerTests.coffee

@@ -332,4 +332,45 @@ describe "HttpController", ->
 			it "should call next with the error", ->
 			it "should call next with the error", ->
 				@next
 				@next
 					.calledWith(new Error("oops"))
 					.calledWith(new Error("oops"))
-					.should.equal true
+					.should.equal true
+	
+	describe "acceptChange", ->
+		beforeEach ->
+			@req =
+				params:
+					project_id: @project_id
+					doc_id: @doc_id
+					change_id: @change_id = "mock-change-od-1"
+
+		describe "successfully", ->
+			beforeEach ->
+				@DocumentManager.acceptChangeWithLock = sinon.stub().callsArgWith(3)
+				@HttpController.acceptChange(@req, @res, @next)
+
+			it "should accept the change", ->
+				@DocumentManager.acceptChangeWithLock
+					.calledWith(@project_id, @doc_id, @change_id)
+					.should.equal true
+
+			it "should return a successful No Content response", ->
+				@res.send
+					.calledWith(204)
+					.should.equal true
+
+			it "should log the request", ->
+				@logger.log
+					.calledWith({@project_id, @doc_id, @change_id}, "accepting change via http")
+					.should.equal true
+
+			it "should time the request", ->
+				@Metrics.Timer::done.called.should.equal true
+
+		describe "when an errors occurs", ->
+			beforeEach ->
+				@DocumentManager.acceptChangeWithLock = sinon.stub().callsArgWith(3, new Error("oops"))
+				@HttpController.acceptChange(@req, @res, @next)
+
+			it "should call next with the error", ->
+				@next
+					.calledWith(new Error("oops"))
+					.should.equal true