diff --git a/services/chat/app/coffee/Features/Messages/MessageFormatter.coffee b/services/chat/app/coffee/Features/Messages/MessageFormatter.coffee index ddfa34f49d..af2472973d 100644 --- a/services/chat/app/coffee/Features/Messages/MessageFormatter.coffee +++ b/services/chat/app/coffee/Features/Messages/MessageFormatter.coffee @@ -16,17 +16,31 @@ module.exports = MessageFormatter = (@formatMessageForClientSide(message) for message in messages) groupMessagesByThreads: (rooms, messages) -> - room_id_to_thread_id = {} + rooms_by_id = {} for room in rooms - room_id_to_thread_id[room._id.toString()] = room.thread_id.toString() - + rooms_by_id[room._id.toString()] = room + threads = {} + getThread = (room) -> + thread_id = room.thread_id.toString() + if threads[thread_id]? + return threads[thread_id] + else + thread = { messages: [] } + if room.resolved? + thread.resolved = true + thread.resolved_at = room.resolved.ts + thread.resolved_by_user = UserFormatter.formatUserForClientSide(room.resolved.user) + threads[thread_id] = thread + return thread + for message in messages - thread_id = room_id_to_thread_id[message.room_id.toString()] - threads[thread_id] ?= [] - threads[thread_id].push MessageFormatter.formatMessageForClientSide(message) + room = rooms_by_id[message.room_id.toString()] + if room? + thread = getThread(room) + thread.messages.push MessageFormatter.formatMessageForClientSide(message) - for thread_id, messages of threads - messages.sort (a,b) -> a.timestamp - b.timestamp + for thread_id, thread of threads + thread.messages.sort (a,b) -> a.timestamp - b.timestamp return threads \ No newline at end of file diff --git a/services/chat/app/coffee/Features/Messages/MessageHttpController.coffee b/services/chat/app/coffee/Features/Messages/MessageHttpController.coffee index 2ac046cb79..db582f3ba8 100644 --- a/services/chat/app/coffee/Features/Messages/MessageHttpController.coffee +++ b/services/chat/app/coffee/Features/Messages/MessageHttpController.coffee @@ -25,10 +25,25 @@ module.exports = MessageHttpController = room_ids = rooms.map (r) -> r._id MessageManager.findAllMessagesInRooms room_ids, (error, messages) -> return next(error) if error? - MessageManager.populateMessagesWithUsers messages, (error, messages) -> + MessageManager.populateMessagesAndRoomsWithUsers messages, rooms, (error) -> return next(error) if error? threads = MessageFormatter.groupMessagesByThreads rooms, messages res.json threads + + resolveThread: (req, res, next) -> + {project_id, thread_id} = req.params + {user_id} = req.body + logger.log {user_id, project_id, thread_id}, "marking thread as resolved" + ThreadManager.resolveThread project_id, thread_id, user_id, (error) -> + return next(error) if error? + res.send 204 # No content + + reopenThread: (req, res, next) -> + {project_id, thread_id} = req.params + logger.log {project_id, thread_id}, "reopening thread" + ThreadManager.reopenThread project_id, thread_id, (error) -> + return next(error) if error? + res.send 204 # No content _sendMessage: (client_thread_id, req, res, next) -> {user_id, content} = req?.body @@ -40,9 +55,9 @@ module.exports = MessageHttpController = return next(error) if error? MessageManager.createMessage thread._id, user_id, content, Date.now(), (error, message) -> return next(error) if error? - MessageManager.populateMessagesWithUsers [message], (error, messages) -> + MessageManager.populateMessagesAndRoomsWithUsers [message], [], (error) -> return next(error) if error? - message = MessageFormatter.formatMessageForClientSide(messages[0]) + message = MessageFormatter.formatMessageForClientSide(message) message.room = id: project_id res.send(201, message) @@ -64,7 +79,7 @@ module.exports = MessageHttpController = logger.log {limit, before, project_id, client_thread_id, thread_object_id}, "found or created thread" MessageManager.getMessages thread_object_id, limit, before, (error, messages) -> return next(error) if error? - MessageManager.populateMessagesWithUsers messages, (error, messages) -> + MessageManager.populateMessagesAndRoomsWithUsers messages, [], (error) -> return next(error) if error? messages = MessageFormatter.formatMessagesForClientSide messages logger.log {project_id, messages}, "got messages" diff --git a/services/chat/app/coffee/Features/Messages/MessageManager.coffee b/services/chat/app/coffee/Features/Messages/MessageManager.coffee index 7c2f8541fa..5e8f0e3974 100644 --- a/services/chat/app/coffee/Features/Messages/MessageManager.coffee +++ b/services/chat/app/coffee/Features/Messages/MessageManager.coffee @@ -28,7 +28,7 @@ module.exports = MessageManager = room_id: { $in: room_ids } }, callback - populateMessagesWithUsers: (messages, callback = (error, messages) ->) -> + populateMessagesAndRoomsWithUsers: (messages, rooms, callback = (error) ->) -> jobs = new Array() userCache = {} @@ -48,7 +48,18 @@ module.exports = MessageManager = return callback(error) if error? delete message.user_id message.user = user - callback(null, message) + callback() + + for room in rooms + do (room) -> + if !room?.resolved?.user_id? + return + jobs.push (callback) -> + getUserDetails room.resolved.user_id.toString(), (error, user) -> + return callback(error) if error? + delete room.resolved.user_id + room.resolved.user = user + callback() async.series jobs, callback diff --git a/services/chat/app/coffee/Features/Threads/ThreadManager.coffee b/services/chat/app/coffee/Features/Threads/ThreadManager.coffee index 9a3cc87eda..8adee1591e 100644 --- a/services/chat/app/coffee/Features/Threads/ThreadManager.coffee +++ b/services/chat/app/coffee/Features/Threads/ThreadManager.coffee @@ -27,8 +27,29 @@ module.exports = ThreadManager = project_id: ObjectId(project_id.toString()) thread_id: { $exists: true } }, { - thread_id: 1 + thread_id: 1, + resolved: 1 }, callback + resolveThread: (project_id, thread_id, user_id, callback = (error) ->) -> + db.rooms.update { + project_id: ObjectId(project_id.toString()) + thread_id: ObjectId(thread_id.toString()) + }, { + $set: { + resolved: { + user_id: user_id + ts: new Date() + } + } + }, callback - + reopenThread: (project_id, thread_id, callback = (error) ->) -> + db.rooms.update { + project_id: ObjectId(project_id.toString()) + thread_id: ObjectId(thread_id.toString()) + }, { + $unset: { + resolved: true + } + }, callback diff --git a/services/chat/app/coffee/router.coffee b/services/chat/app/coffee/router.coffee index c8c71fc567..88179b7f3a 100644 --- a/services/chat/app/coffee/router.coffee +++ b/services/chat/app/coffee/router.coffee @@ -24,15 +24,14 @@ module.exports = Router = app.post "/project/:project_id/thread/:thread_id/messages", MessageHttpController.sendThreadMessage app.get "/project/:project_id/threads", MessageHttpController.getAllThreads - # app.get "/project/:project_id/thread", MessageHttpController.getAllThreadMessages - # + # app.post "/project/:project_id/thread/:thread_id/messages/:message_id/edit", MessageHttpController.editMessage # app.del "/project/:project_id/thread/:thread_id/messages/:message_id", MessageHttpController.deleteMessage - # - # app.post "/project/:project_id/thread/:thread_id/resolve", MessageHttpController.resolveThread - # app.post "/project/:project_id/thread/:thread_id/reopen", MessageHttpController.reopenThread + + app.post "/project/:project_id/thread/:thread_id/resolve", MessageHttpController.resolveThread + app.post "/project/:project_id/thread/:thread_id/reopen", MessageHttpController.reopenThread # app.del "/project/:project_id/thread/:thread_id", MessageHttpController.deleteThread - # + app.get "/status", (req, res, next) -> res.send("chat is alive") diff --git a/services/chat/test/acceptance/coffee/GettingMessagesTests.coffee b/services/chat/test/acceptance/coffee/GettingMessagesTests.coffee index 4379cd46db..06489ee04c 100644 --- a/services/chat/test/acceptance/coffee/GettingMessagesTests.coffee +++ b/services/chat/test/acceptance/coffee/GettingMessagesTests.coffee @@ -71,20 +71,20 @@ describe "Getting messages", -> ChatClient.getThreads @project_id, (error, response, threads) => expect(Object.keys(threads).length).to.equal 2 thread1 = threads[@thread_id1] - expect(thread1.length).to.equal 2 + expect(thread1.messages.length).to.equal 2 thread2 = threads[@thread_id2] - expect(thread2.length).to.equal 2 + expect(thread2.messages.length).to.equal 2 - expect(thread1[0].content).to.equal "one" - expect(thread1[0].user).to.deep.equal { + expect(thread1.messages[0].content).to.equal "one" + expect(thread1.messages[0].user).to.deep.equal { id: @user_id1 first_name: "Jane" last_name: "Smith" email: "jane@example.com" gravatar_url: "//www.gravatar.com/avatar/#{crypto.createHash("md5").update("jane@example.com").digest("hex")}" } - expect(thread1[1].content).to.equal "three" - expect(thread1[1].user).to.deep.equal { + expect(thread1.messages[1].content).to.equal "three" + expect(thread1.messages[1].user).to.deep.equal { id: @user_id1 first_name: "Jane" last_name: "Smith" @@ -92,16 +92,16 @@ describe "Getting messages", -> gravatar_url: "//www.gravatar.com/avatar/#{crypto.createHash("md5").update("jane@example.com").digest("hex")}" } - expect(thread2[0].content).to.equal "two" - expect(thread2[0].user).to.deep.equal { + expect(thread2.messages[0].content).to.equal "two" + expect(thread2.messages[0].user).to.deep.equal { id: @user_id2 first_name: "John" last_name: "Doe" email: "john@example.com" gravatar_url: "//www.gravatar.com/avatar/#{crypto.createHash("md5").update("john@example.com").digest("hex")}" } - expect(thread2[1].content).to.equal "four" - expect(thread2[1].user).to.deep.equal { + expect(thread2.messages[1].content).to.equal "four" + expect(thread2.messages[1].user).to.deep.equal { id: @user_id2 first_name: "John" last_name: "Doe" diff --git a/services/chat/test/acceptance/coffee/ResolvingAThreadTests.coffee b/services/chat/test/acceptance/coffee/ResolvingAThreadTests.coffee new file mode 100644 index 0000000000..1b83b839a4 --- /dev/null +++ b/services/chat/test/acceptance/coffee/ResolvingAThreadTests.coffee @@ -0,0 +1,83 @@ +{ObjectId} = require "../../../app/js/mongojs" +expect = require("chai").expect +crypto = require "crypto" + +MockWebApi = require "./helpers/MockWebApi" +ChatClient = require "./helpers/ChatClient" + +describe "Resolving a thread", -> + before -> + @project_id = ObjectId().toString() + @user_id = ObjectId().toString() + MockWebApi.addUser @user_id, @user = { + id: @user_id + first_name: "Jane" + last_name: "Smith" + email: "jane@example.com" + } + + describe "with a resolved thread", -> + before (done) -> + @thread_id = ObjectId().toString() + @content = "resolved message" + ChatClient.sendMessage @project_id, @thread_id, @user_id, @content, (error, response, body) => + expect(error).to.be.null + expect(response.statusCode).to.equal 201 + ChatClient.resolveThread @project_id, @thread_id, @user_id, (error, response, body) => + expect(error).to.be.null + expect(response.statusCode).to.equal 204 + done() + + it "should then list the thread as resolved", (done) -> + ChatClient.getThreads @project_id, (error, response, threads) => + expect(error).to.be.null + expect(response.statusCode).to.equal 200 + expect(threads[@thread_id].resolved).to.equal true + expect(threads[@thread_id].resolved_by_user).to.deep.equal { + id: @user_id + first_name: "Jane" + last_name: "Smith" + email: "jane@example.com" + gravatar_url: "//www.gravatar.com/avatar/#{crypto.createHash("md5").update("jane@example.com").digest("hex")}" + } + resolved_at = new Date(threads[@thread_id].resolved_at) + expect(new Date() - resolved_at).to.be.below 1000 + done() + + describe "when a thread is not resolved", -> + before (done) -> + @thread_id = ObjectId().toString() + @content = "open message" + ChatClient.sendMessage @project_id, @thread_id, @user_id, @content, (error, response, body) => + expect(error).to.be.null + expect(response.statusCode).to.equal 201 + done() + + it "should not list the thread as resolved", (done) -> + ChatClient.getThreads @project_id, (error, response, threads) => + expect(error).to.be.null + expect(response.statusCode).to.equal 200 + expect(threads[@thread_id].resolved).to.be.undefined + done() + + describe "when a thread is resolved then reopened", -> + before (done) -> + @thread_id = ObjectId().toString() + @content = "resolved message" + ChatClient.sendMessage @project_id, @thread_id, @user_id, @content, (error, response, body) => + expect(error).to.be.null + expect(response.statusCode).to.equal 201 + ChatClient.resolveThread @project_id, @thread_id, @user_id, (error, response, body) => + expect(error).to.be.null + expect(response.statusCode).to.equal 204 + ChatClient.reopenThread @project_id, @thread_id, (error, response, body) => + expect(error).to.be.null + expect(response.statusCode).to.equal 204 + done() + + it "should not list the thread as resolved", (done) -> + ChatClient.getThreads @project_id, (error, response, threads) => + expect(error).to.be.null + expect(response.statusCode).to.equal 200 + expect(threads[@thread_id].resolved).to.be.undefined + done() diff --git a/services/chat/test/acceptance/coffee/SendingAMessageTests.coffee b/services/chat/test/acceptance/coffee/SendingAMessageTests.coffee index f0c233029a..fb1ea63d9a 100644 --- a/services/chat/test/acceptance/coffee/SendingAMessageTests.coffee +++ b/services/chat/test/acceptance/coffee/SendingAMessageTests.coffee @@ -50,8 +50,8 @@ describe "Sending a message", -> ChatClient.getThreads @project_id, (error, response, threads) => expect(error).to.be.null expect(response.statusCode).to.equal 200 - expect(threads[@thread_id].length).to.equal 1 - expect(threads[@thread_id][0].content).to.equal @content + expect(threads[@thread_id].messages.length).to.equal 1 + expect(threads[@thread_id].messages[0].content).to.equal @content done() describe "with a malformed user_id", -> diff --git a/services/chat/test/acceptance/coffee/helpers/ChatClient.coffee b/services/chat/test/acceptance/coffee/helpers/ChatClient.coffee index 36365726f9..1b7d747bbe 100644 --- a/services/chat/test/acceptance/coffee/helpers/ChatClient.coffee +++ b/services/chat/test/acceptance/coffee/helpers/ChatClient.coffee @@ -27,4 +27,17 @@ module.exports = request.get { url: "/project/#{project_id}/threads", json: true + }, callback + + resolveThread: (project_id, thread_id, user_id, callback) -> + request.post { + url: "/project/#{project_id}/thread/#{thread_id}/resolve", + json: { + user_id: user_id + } + }, callback + + reopenThread: (project_id, thread_id, callback) -> + request.post { + url: "/project/#{project_id}/thread/#{thread_id}/reopen", }, callback \ No newline at end of file