From 1a1ccc9f46adc9ced612df5dec41c26ebce9ebf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Mon, 22 Jan 2018 15:00:56 +0000 Subject: [PATCH 01/14] Remove trailing whitespace --- .../app/coffee/Features/Analytics/AnalyticsManager.coffee | 2 +- services/web/public/coffee/main/event.coffee | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee index 12e5b68c84..119b7c70b4 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee @@ -49,7 +49,7 @@ module.exports = timeout:1000 url: "/user/#{user_id}/event/last_occurnace" makeRequest opts, (err, response, body)-> - if err? + if err? console.log response, opts logger.err {user_id, err}, "error getting last occurance of event" return callback err diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee index b788057b77..d71f865620 100644 --- a/services/web/public/coffee/main/event.coffee +++ b/services/web/public/coffee/main/event.coffee @@ -8,15 +8,15 @@ define [ ga('send', 'event', category, action) event_name = "#{action}-#{category}" Intercom?("trackEvent", event_name, attributes) - + App.factory "event_tracking", ($http, localStorage) -> - _getEventCache = () -> + _getEventCache = () -> eventCache = localStorage CACHE_KEY # Initialize as an empy object if the event cache is still empty. if !eventCache? eventCache = {} - localStorage CACHE_KEY, eventCache + localStorage CACHE_KEY, eventCache return eventCache @@ -45,7 +45,7 @@ define [ } sendMBSampled: (key, segmentation) -> - @sendMB key, segmentation if Math.random() < .01 + @sendMB key, segmentation if Math.random() < .01 sendMBOnce: (key, segmentation) -> if ! _eventInCache(key) From 868e32c1bc17facce0cddb68bf8708d9458922df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Mon, 22 Jan 2018 15:10:52 +0000 Subject: [PATCH 02/14] Send edit sessions heartbeat to the analytics service --- .../Features/Analytics/AnalyticsController.coffee | 13 +++++++++++++ .../Features/Analytics/AnalyticsManager.coffee | 12 ++++++++++++ .../Features/Analytics/AnalyticsRouter.coffee | 1 + .../coffee/ide/editor/directives/aceEditor.coffee | 4 ++++ services/web/public/coffee/main/event.coffee | 13 +++++++++++++ 5 files changed, 43 insertions(+) diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee index 18a7de5e24..0fad2ed32e 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee @@ -3,6 +3,19 @@ Errors = require "../Errors/Errors" AuthenticationController = require("../Authentication/AuthenticationController") module.exports = AnalyticsController = + updateEditSession: (req, res, next) -> + userId = AuthenticationController.getLoggedInUserId(req) or req.sessionID + projectId = req.params.projectId + + AnalyticsManager.updateEditSession userId, projectId, {}, (error) -> + if error instanceof Errors.ServiceNotConfiguredError + # ignore, no-op + return res.send(204) + else if error? + return next(error) + else + return res.send 204 + recordEvent: (req, res, next) -> user_id = AuthenticationController.getLoggedInUserId(req) or req.sessionID AnalyticsManager.recordEvent user_id, req.params.event, req.body, (error) -> diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee index 119b7c70b4..08a5a11c01 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee @@ -39,6 +39,18 @@ module.exports = url: "/user/#{user_id}/event" makeRequest opts, callback + updateEditSession: (userId, projectId, segmentation = {}, callback = (error) ->) -> + if userId+"" == settings.smokeTest?.userId+"" + return callback() + opts = + body: + segmentation: segmentation + json: true + method: "PUT" + timeout: 1000 + url: "/editSession?userId=#{userId}&projectId=#{projectId}" + makeRequest opts, callback + getLastOccurance: (user_id, event, callback = (error) ->) -> opts = diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee index 4fcba0c748..639110f2a3 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee @@ -5,6 +5,7 @@ AnalyticsProxy = require('./AnalyticsProxy') module.exports = apply: (webRouter, privateApiRouter, publicApiRouter) -> webRouter.post '/event/:event', AnalyticsController.recordEvent + webRouter.put '/editSession/:projectId', AnalyticsController.updateEditSession publicApiRouter.use '/analytics/graphs', AuthenticationController.httpAuth, AnalyticsProxy.call('/graphs') diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index ff5d1554e2..e05e278625 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -300,6 +300,10 @@ define [ updateCount = 0 onChange = () -> updateCount++ + + projectId = _.last( location.pathname.split("/")) + event_tracking.editSessionHeartbeat(projectId) + if updateCount == 100 event_tracking.send 'editor-interaction', 'multi-doc-update' scope.$emit "#{scope.name}:change" diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee index d71f865620..c2f3bc417a 100644 --- a/services/web/public/coffee/main/event.coffee +++ b/services/web/public/coffee/main/event.coffee @@ -3,6 +3,7 @@ define [ "modules/localStorage" ], (App) -> CACHE_KEY = "mbEvents" + EDIT_SESSION_HEARTBEAT_INTERVAL = 5 * 60 * 1000 # 5min send = (category, action, attributes = {})-> ga('send', 'event', category, action) @@ -34,6 +35,18 @@ define [ send: (category, action, label, value)-> ga('send', 'event', category, action, label, value) + editSessionHeartbeat: _.throttle( (projectId, segmentation = {}) -> + $http({ + url: "/editSession/#{projectId}", + method: "PUT", + data: segmentation, + headers: { + "X-CSRF-Token": window.csrfToken + } + }) + , EDIT_SESSION_HEARTBEAT_INTERVAL) + + sendMB: (key, segmentation = {}) -> $http { url: "/event/#{key}", From 0bef5531bb6929296ca90cf5fc6f915f21ac6897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Mon, 22 Jan 2018 16:20:08 +0000 Subject: [PATCH 03/14] Rename editSession -> editingSession --- .../app/coffee/Features/Analytics/AnalyticsController.coffee | 4 ++-- .../app/coffee/Features/Analytics/AnalyticsManager.coffee | 4 ++-- .../web/app/coffee/Features/Analytics/AnalyticsRouter.coffee | 5 ++++- .../web/public/coffee/ide/editor/directives/aceEditor.coffee | 4 ++-- services/web/public/coffee/main/event.coffee | 4 ++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee index 0fad2ed32e..5166bd2765 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee @@ -3,11 +3,11 @@ Errors = require "../Errors/Errors" AuthenticationController = require("../Authentication/AuthenticationController") module.exports = AnalyticsController = - updateEditSession: (req, res, next) -> + updateEditingSession: (req, res, next) -> userId = AuthenticationController.getLoggedInUserId(req) or req.sessionID projectId = req.params.projectId - AnalyticsManager.updateEditSession userId, projectId, {}, (error) -> + AnalyticsManager.updateEditingSession userId, projectId, {}, (error) -> if error instanceof Errors.ServiceNotConfiguredError # ignore, no-op return res.send(204) diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee index 08a5a11c01..e77139ed7b 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee @@ -39,7 +39,7 @@ module.exports = url: "/user/#{user_id}/event" makeRequest opts, callback - updateEditSession: (userId, projectId, segmentation = {}, callback = (error) ->) -> + updateEditingSession: (userId, projectId, segmentation = {}, callback = (error) ->) -> if userId+"" == settings.smokeTest?.userId+"" return callback() opts = @@ -48,7 +48,7 @@ module.exports = json: true method: "PUT" timeout: 1000 - url: "/editSession?userId=#{userId}&projectId=#{projectId}" + url: "/editingSession?userId=#{userId}&projectId=#{projectId}" makeRequest opts, callback diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee index 639110f2a3..e7a6051c41 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsRouter.coffee @@ -5,7 +5,10 @@ AnalyticsProxy = require('./AnalyticsProxy') module.exports = apply: (webRouter, privateApiRouter, publicApiRouter) -> webRouter.post '/event/:event', AnalyticsController.recordEvent - webRouter.put '/editSession/:projectId', AnalyticsController.updateEditSession + + webRouter.put '/editingSession/:projectId', + AnalyticsController.updateEditingSession + publicApiRouter.use '/analytics/graphs', AuthenticationController.httpAuth, AnalyticsProxy.call('/graphs') diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index e05e278625..a91ccc119c 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -301,8 +301,8 @@ define [ onChange = () -> updateCount++ - projectId = _.last( location.pathname.split("/")) - event_tracking.editSessionHeartbeat(projectId) + projectId = _.last(location.pathname.split("/")) + event_tracking.editingSessionHeartbeat(projectId) if updateCount == 100 event_tracking.send 'editor-interaction', 'multi-doc-update' diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee index c2f3bc417a..9956400979 100644 --- a/services/web/public/coffee/main/event.coffee +++ b/services/web/public/coffee/main/event.coffee @@ -35,9 +35,9 @@ define [ send: (category, action, label, value)-> ga('send', 'event', category, action, label, value) - editSessionHeartbeat: _.throttle( (projectId, segmentation = {}) -> + editingSessionHeartbeat: _.throttle( (projectId, segmentation = {}) -> $http({ - url: "/editSession/#{projectId}", + url: "/editingSession/#{projectId}", method: "PUT", data: segmentation, headers: { From 8dbb89c09ebbeca3b069e545ae4c63bda4abe7e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Mon, 22 Jan 2018 17:20:55 +0000 Subject: [PATCH 04/14] Add test for the updateEditingSession endpoint --- .../Analytics/AnalyticsControllerTests.coffee | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee b/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee index 2c52b8b5f0..8ac73c5af1 100644 --- a/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee +++ b/services/web/test/unit/coffee/Analytics/AnalyticsControllerTests.coffee @@ -14,23 +14,38 @@ describe 'AnalyticsController', -> getLoggedInUserId: sinon.stub() @AnalyticsManager = + updateEditingSession: sinon.stub().callsArgWith(3) recordEvent: sinon.stub().callsArgWith(3) - @req = - params: - event:"i_did_something" - body:"stuff" - sessionID: "sessionIDHere" - - @res = - send:-> @controller = SandboxedModule.require modulePath, requires: "./AnalyticsManager":@AnalyticsManager "../Authentication/AuthenticationController":@AuthenticationController "logger-sharelatex": log:-> + @res = + send:-> + + describe "updateEditingSession", -> + beforeEach -> + @req = + params: + projectId: "a project id" + + it "delegates to the AnalyticsManager", (done) -> + @AuthenticationController.getLoggedInUserId.returns("1234") + @controller.updateEditingSession @req, @res + + @AnalyticsManager.updateEditingSession.calledWith("1234", "a project id", {}).should.equal true + done() + describe "recordEvent", -> + beforeEach -> + @req = + params: + event:"i_did_something" + body:"stuff" + sessionID: "sessionIDHere" it "should use the user_id", (done)-> @AuthenticationController.getLoggedInUserId.returns("1234") From b3b05d1af3b45dfc81eb99c73f9b92a0e1a432e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Tue, 23 Jan 2018 10:14:24 +0000 Subject: [PATCH 05/14] Disable the trailing throttle event in the editing session heartbeat Throttle sends an event as soon as the function is called. Then we wait X minutes, but we only want to send another event if there's any activity *after* those X minutes. --- services/web/public/coffee/main/event.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee index 9956400979..b1abf6c674 100644 --- a/services/web/public/coffee/main/event.coffee +++ b/services/web/public/coffee/main/event.coffee @@ -44,7 +44,7 @@ define [ "X-CSRF-Token": window.csrfToken } }) - , EDIT_SESSION_HEARTBEAT_INTERVAL) + , EDIT_SESSION_HEARTBEAT_INTERVAL, trailing: false) sendMB: (key, segmentation = {}) -> From 8714909a9a2435b67c361f35f9528d91da7775e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Tue, 23 Jan 2018 12:47:51 +0000 Subject: [PATCH 06/14] Build query string with style --- .../app/coffee/Features/Analytics/AnalyticsManager.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee index e77139ed7b..085d9d19b3 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee @@ -48,7 +48,10 @@ module.exports = json: true method: "PUT" timeout: 1000 - url: "/editingSession?userId=#{userId}&projectId=#{projectId}" + url: "/editingSession" + qs: + userId: userId + projectId: projectId makeRequest opts, callback From 4ca71fb3799a95b408f9c961bec8df9b5ca89ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Tue, 23 Jan 2018 15:32:42 +0000 Subject: [PATCH 07/14] Do not track anonymous sessions And DRY the controller a bit. --- .../Analytics/AnalyticsController.coffee | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee index 5166bd2765..a57118723e 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsController.coffee @@ -4,25 +4,25 @@ AuthenticationController = require("../Authentication/AuthenticationController") module.exports = AnalyticsController = updateEditingSession: (req, res, next) -> - userId = AuthenticationController.getLoggedInUserId(req) or req.sessionID + userId = AuthenticationController.getLoggedInUserId(req) projectId = req.params.projectId - AnalyticsManager.updateEditingSession userId, projectId, {}, (error) -> - if error instanceof Errors.ServiceNotConfiguredError - # ignore, no-op - return res.send(204) - else if error? - return next(error) - else - return res.send 204 + if userId? + AnalyticsManager.updateEditingSession userId, projectId, {}, (error) -> + respondWith(error, res, next) + else + res.send 204 recordEvent: (req, res, next) -> user_id = AuthenticationController.getLoggedInUserId(req) or req.sessionID AnalyticsManager.recordEvent user_id, req.params.event, req.body, (error) -> - if error instanceof Errors.ServiceNotConfiguredError - # ignore, no-op - return res.send(204) - else if error? - return next(error) - else - return res.send 204 + respondWith(error, res, next) + +respondWith = (error, res, next) -> + if error instanceof Errors.ServiceNotConfiguredError + # ignore, no-op + res.send(204) + else if error? + next(error) + else + res.send 204 From 8a1523cc03b79fac26d90bb27dad4466033643d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Tue, 23 Jan 2018 15:58:59 +0000 Subject: [PATCH 08/14] Read project id from the global window var --- .../web/public/coffee/ide/editor/directives/aceEditor.coffee | 3 +-- services/web/public/coffee/main/event.coffee | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index a91ccc119c..80b58cb003 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -301,8 +301,7 @@ define [ onChange = () -> updateCount++ - projectId = _.last(location.pathname.split("/")) - event_tracking.editingSessionHeartbeat(projectId) + event_tracking.editingSessionHeartbeat() if updateCount == 100 event_tracking.send 'editor-interaction', 'multi-doc-update' diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee index b1abf6c674..87b50e49d4 100644 --- a/services/web/public/coffee/main/event.coffee +++ b/services/web/public/coffee/main/event.coffee @@ -35,9 +35,9 @@ define [ send: (category, action, label, value)-> ga('send', 'event', category, action, label, value) - editingSessionHeartbeat: _.throttle( (projectId, segmentation = {}) -> + editingSessionHeartbeat: _.throttle( (segmentation = {}) -> $http({ - url: "/editingSession/#{projectId}", + url: "/editingSession/#{window.project_id}", method: "PUT", data: segmentation, headers: { From f0777f996c71559543c8331e4ea916deec6402ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Wed, 24 Jan 2018 15:15:18 +0000 Subject: [PATCH 09/14] Space editing sessions heartbeats with an increasing backoff We send the first heartbeat as soon as the session start. The next ones are sent (if there's any activity) when the editing session is about 30 and 60 seconds. Then at 2min, 3min, 4min, 5min and later on every 5min. The backoff is not reset, so if due to inactivity the session expires in the server the backoff will still be the same. --- services/web/public/coffee/main/event.coffee | 26 +++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee index 87b50e49d4..fc203fde26 100644 --- a/services/web/public/coffee/main/event.coffee +++ b/services/web/public/coffee/main/event.coffee @@ -1,10 +1,15 @@ define [ + "moment" "base" "modules/localStorage" -], (App) -> + +], (moment, App) -> CACHE_KEY = "mbEvents" EDIT_SESSION_HEARTBEAT_INTERVAL = 5 * 60 * 1000 # 5min + sessionStart = new Date() + nextHeartbeat = new Date() + send = (category, action, attributes = {})-> ga('send', 'event', category, action) event_name = "#{action}-#{category}" @@ -35,7 +40,22 @@ define [ send: (category, action, label, value)-> ga('send', 'event', category, action, label, value) - editingSessionHeartbeat: _.throttle( (segmentation = {}) -> + + editingSessionHeartbeat: (segmentation = {}) -> + return unless nextHeartbeat <= new Date() + + @_sendEditingSessionHeartbeat(segmentation) + + sessionDuration = (new Date().getTime() - sessionStart.getTime())/1000 + + backoffSecs = switch + when sessionDuration < 60 then 30 + when sessionDuration < 300 then 60 + else 300 + + nextHeartbeat = moment().add(backoffSecs, 'seconds').toDate() + + _sendEditingSessionHeartbeat: (segmentation) -> $http({ url: "/editingSession/#{window.project_id}", method: "PUT", @@ -44,8 +64,6 @@ define [ "X-CSRF-Token": window.csrfToken } }) - , EDIT_SESSION_HEARTBEAT_INTERVAL, trailing: false) - sendMB: (key, segmentation = {}) -> $http { From dd2e9d7431498b29d6c0e018f5558e5e44ef559a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Wed, 24 Jan 2018 15:37:16 +0000 Subject: [PATCH 10/14] Remove unused var --- services/web/public/coffee/main/event.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee index fc203fde26..515b791ee2 100644 --- a/services/web/public/coffee/main/event.coffee +++ b/services/web/public/coffee/main/event.coffee @@ -5,8 +5,9 @@ define [ ], (moment, App) -> CACHE_KEY = "mbEvents" - EDIT_SESSION_HEARTBEAT_INTERVAL = 5 * 60 * 1000 # 5min + # Keep track of when the editing session started and when we should + # send the next heartbeat so we can space them properly sessionStart = new Date() nextHeartbeat = new Date() From e42d1f9b139d70a6be1c1283c9809abe8c8aa8e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Wed, 24 Jan 2018 16:03:43 +0000 Subject: [PATCH 11/14] Change heartbeat backoff logic Send first two heartbeats at 0 and 30 secs, then increase the backoff interval 1min per call until a maximun of 5min. --- services/web/public/coffee/main/event.coffee | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee index 515b791ee2..894737557a 100644 --- a/services/web/public/coffee/main/event.coffee +++ b/services/web/public/coffee/main/event.coffee @@ -6,9 +6,9 @@ define [ ], (moment, App) -> CACHE_KEY = "mbEvents" - # Keep track of when the editing session started and when we should - # send the next heartbeat so we can space them properly - sessionStart = new Date() + # keep track of how many heartbeats we've sent so we can calculate how + # long wait until the next one + heartbeatsSent = 0 nextHeartbeat = new Date() send = (category, action, attributes = {})-> @@ -47,12 +47,16 @@ define [ @_sendEditingSessionHeartbeat(segmentation) - sessionDuration = (new Date().getTime() - sessionStart.getTime())/1000 + heartbeatsSent++ - backoffSecs = switch - when sessionDuration < 60 then 30 - when sessionDuration < 300 then 60 - else 300 + # send two first heartbeats at 0 and 30s then increase the backoff time + # 1min per call until we reach 5 min + backoffSecs = if heartbeatsSent <= 2 + 30 + else if heartbeatsSent <= 6 + (heartbeatsSent - 2) * 60 + else + 300 nextHeartbeat = moment().add(backoffSecs, 'seconds').toDate() From 7d0d18ea5ed333a00b8b3f875756bc29af126949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Wed, 24 Jan 2018 17:20:30 +0000 Subject: [PATCH 12/14] Use the cursor:editor:update event to track user activity It is strictly a superset of the change event we were using before: if there's a change, there will be always a cursor update, but there can be cursor updates (like when the user presses the arrow keys) that don't change the document. This is coherent with what we do to track user activity at https://github.com/sharelatex/web-sharelatex-internal/blob/dfb994352112e3535aac7a2c415e12d8050b2987/public/coffee/ide/connection/ConnectionManager.coffee#L49-L50 --- services/web/public/coffee/ide.coffee | 2 ++ .../ide/editor/directives/aceEditor.coffee | 2 -- services/web/public/coffee/main/event.coffee | 22 +++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee index 3d514d093c..7a5738dbc5 100644 --- a/services/web/public/coffee/ide.coffee +++ b/services/web/public/coffee/ide.coffee @@ -176,6 +176,8 @@ define [ $scope.$broadcast('ide:loaded') _loaded = true + $scope.$on 'cursor:editor:update', event_tracking.editingSessionHeartbeat + DARK_THEMES = [ "ambiance", "chaos", "clouds_midnight", "cobalt", "idle_fingers", "merbivore", "merbivore_soft", "mono_industrial", "monokai", diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index 80b58cb003..e90fa2f4c0 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -301,8 +301,6 @@ define [ onChange = () -> updateCount++ - event_tracking.editingSessionHeartbeat() - if updateCount == 100 event_tracking.send 'editor-interaction', 'multi-doc-update' scope.$emit "#{scope.name}:change" diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee index 894737557a..e03a0a8afb 100644 --- a/services/web/public/coffee/main/event.coffee +++ b/services/web/public/coffee/main/event.coffee @@ -37,6 +37,16 @@ define [ localStorage CACHE_KEY, curCache + _sendEditingSessionHeartbeat = (segmentation) -> + $http({ + url: "/editingSession/#{window.project_id}", + method: "PUT", + data: segmentation, + headers: { + "X-CSRF-Token": window.csrfToken + } + }) + return { send: (category, action, label, value)-> ga('send', 'event', category, action, label, value) @@ -45,7 +55,7 @@ define [ editingSessionHeartbeat: (segmentation = {}) -> return unless nextHeartbeat <= new Date() - @_sendEditingSessionHeartbeat(segmentation) + _sendEditingSessionHeartbeat(segmentation) heartbeatsSent++ @@ -60,16 +70,6 @@ define [ nextHeartbeat = moment().add(backoffSecs, 'seconds').toDate() - _sendEditingSessionHeartbeat: (segmentation) -> - $http({ - url: "/editingSession/#{window.project_id}", - method: "PUT", - data: segmentation, - headers: { - "X-CSRF-Token": window.csrfToken - } - }) - sendMB: (key, segmentation = {}) -> $http { url: "/event/#{key}", From a68489a604053ef062e7f61943081732f1742bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Thu, 25 Jan 2018 16:10:47 +0000 Subject: [PATCH 13/14] Track session activvity if the user scrolls the editor --- .../web/public/coffee/ide/editor/directives/aceEditor.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index e90fa2f4c0..6e18fb2aeb 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -376,6 +376,8 @@ define [ # deletes and then inserts document content session.setAnnotations scope.annotations + session.on "changeScrollTop", event_tracking.editingSessionHeartbeat + if scope.eventsBridge? session.on "changeScrollTop", onScroll From 839ea496f11068c325e27a7868968ce58dd9e2da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez=20Capel?= Date: Thu, 25 Jan 2018 16:22:19 +0000 Subject: [PATCH 14/14] Track clicks anywhere on the window as activity when the editor is open --- .../coffee/ide/editor/directives/aceEditor.coffee | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index 6e18fb2aeb..3b43a05afd 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -36,7 +36,7 @@ define [ url = ace.config._moduleUrl(args...) return url - App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, metadata, graphics, preamble, files, $http, $q) -> + App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, metadata, graphics, preamble, files, $http, $q, $window) -> monkeyPatchSearch($rootScope, $compile) return { @@ -376,8 +376,16 @@ define [ # deletes and then inserts document content session.setAnnotations scope.annotations + session.on "changeScrollTop", event_tracking.editingSessionHeartbeat + angular.element($window).on('click', + event_tracking.editingSessionHeartbeat) + + scope.$on "$destroy", () -> + angular.element($window).off('click', + event_tracking.editingSessionHeartbeat) + if scope.eventsBridge? session.on "changeScrollTop", onScroll