AnalyticsManager.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. const SessionManager = require('../Authentication/SessionManager')
  2. const UserAnalyticsIdCache = require('./UserAnalyticsIdCache')
  3. const Settings = require('@overleaf/settings')
  4. const Metrics = require('../../infrastructure/Metrics')
  5. const Queues = require('../../infrastructure/Queues')
  6. const uuid = require('uuid')
  7. const _ = require('lodash')
  8. const { expressify } = require('../../util/promises')
  9. const analyticsEventsQueue = Queues.getAnalyticsEventsQueue()
  10. const analyticsEditingSessionsQueue = Queues.getAnalyticsEditingSessionsQueue()
  11. const analyticsUserPropertiesQueue = Queues.getAnalyticsUserPropertiesQueue()
  12. const ONE_MINUTE_MS = 60 * 1000
  13. function identifyUser(userId, analyticsId, isNewUser) {
  14. if (!userId || !analyticsId) {
  15. return
  16. }
  17. if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
  18. return
  19. }
  20. Metrics.analyticsQueue.inc({ status: 'adding', event_type: 'identify' })
  21. analyticsEventsQueue
  22. .add(
  23. 'identify',
  24. { userId, analyticsId, isNewUser },
  25. { delay: ONE_MINUTE_MS }
  26. )
  27. .then(() => {
  28. Metrics.analyticsQueue.inc({ status: 'added', event_type: 'identify' })
  29. })
  30. .catch(() => {
  31. Metrics.analyticsQueue.inc({ status: 'error', event_type: 'identify' })
  32. })
  33. }
  34. async function recordEventForUser(userId, event, segmentation) {
  35. if (!userId) {
  36. return
  37. }
  38. if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
  39. return
  40. }
  41. const analyticsId = await UserAnalyticsIdCache.get(userId)
  42. if (analyticsId) {
  43. _recordEvent({ analyticsId, userId, event, segmentation, isLoggedIn: true })
  44. }
  45. }
  46. function recordEventForSession(session, event, segmentation) {
  47. const { analyticsId, userId } = getIdsFromSession(session)
  48. if (!analyticsId) {
  49. return
  50. }
  51. if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
  52. return
  53. }
  54. _recordEvent({
  55. analyticsId,
  56. userId,
  57. event,
  58. segmentation,
  59. isLoggedIn: !!userId,
  60. })
  61. }
  62. async function setUserPropertyForUser(userId, propertyName, propertyValue) {
  63. if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
  64. return
  65. }
  66. _checkPropertyValue(propertyValue)
  67. const analyticsId = await UserAnalyticsIdCache.get(userId)
  68. if (analyticsId) {
  69. _setUserProperty({ analyticsId, propertyName, propertyValue })
  70. }
  71. }
  72. async function setUserPropertyForAnalyticsId(
  73. analyticsId,
  74. propertyName,
  75. propertyValue
  76. ) {
  77. if (_isAnalyticsDisabled()) {
  78. return
  79. }
  80. _checkPropertyValue(propertyValue)
  81. _setUserProperty({ analyticsId, propertyName, propertyValue })
  82. }
  83. async function setUserPropertyForSession(session, propertyName, propertyValue) {
  84. const { analyticsId, userId } = getIdsFromSession(session)
  85. if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
  86. return
  87. }
  88. _checkPropertyValue(propertyValue)
  89. if (analyticsId) {
  90. _setUserProperty({ analyticsId, propertyName, propertyValue })
  91. }
  92. }
  93. function updateEditingSession(userId, projectId, countryCode) {
  94. if (!userId) {
  95. return
  96. }
  97. if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
  98. return
  99. }
  100. Metrics.analyticsQueue.inc({
  101. status: 'adding',
  102. event_type: 'editing-session',
  103. })
  104. analyticsEditingSessionsQueue
  105. .add({ userId, projectId, countryCode })
  106. .then(() => {
  107. Metrics.analyticsQueue.inc({
  108. status: 'added',
  109. event_type: 'editing-session',
  110. })
  111. })
  112. .catch(() => {
  113. Metrics.analyticsQueue.inc({
  114. status: 'error',
  115. event_type: 'editing-session',
  116. })
  117. })
  118. }
  119. function _recordEvent(
  120. { analyticsId, userId, event, segmentation, isLoggedIn },
  121. { delay } = {}
  122. ) {
  123. Metrics.analyticsQueue.inc({ status: 'adding', event_type: 'event' })
  124. analyticsEventsQueue
  125. .add(
  126. 'event',
  127. { analyticsId, userId, event, segmentation, isLoggedIn },
  128. { delay }
  129. )
  130. .then(() => {
  131. Metrics.analyticsQueue.inc({ status: 'added', event_type: 'event' })
  132. })
  133. .catch(() => {
  134. Metrics.analyticsQueue.inc({ status: 'error', event_type: 'event' })
  135. })
  136. }
  137. function _setUserProperty({ analyticsId, propertyName, propertyValue }) {
  138. Metrics.analyticsQueue.inc({
  139. status: 'adding',
  140. event_type: 'user-property',
  141. })
  142. analyticsUserPropertiesQueue
  143. .add({
  144. analyticsId,
  145. propertyName,
  146. propertyValue,
  147. })
  148. .then(() => {
  149. Metrics.analyticsQueue.inc({
  150. status: 'added',
  151. event_type: 'user-property',
  152. })
  153. })
  154. .catch(() => {
  155. Metrics.analyticsQueue.inc({
  156. status: 'error',
  157. event_type: 'user-property',
  158. })
  159. })
  160. }
  161. function _isSmokeTestUser(userId) {
  162. const smokeTestUserId = Settings.smokeTest && Settings.smokeTest.userId
  163. return (
  164. smokeTestUserId != null &&
  165. userId != null &&
  166. userId.toString() === smokeTestUserId
  167. )
  168. }
  169. function _isAnalyticsDisabled() {
  170. return !(Settings.analytics && Settings.analytics.enabled)
  171. }
  172. function _checkPropertyValue(propertyValue) {
  173. if (propertyValue === undefined) {
  174. throw new Error(
  175. 'propertyValue cannot be undefined, use null to unset a property'
  176. )
  177. }
  178. }
  179. function getIdsFromSession(session) {
  180. const analyticsId = _.get(session, ['analyticsId'])
  181. const userId = SessionManager.getLoggedInUserId(session)
  182. return { analyticsId, userId }
  183. }
  184. async function analyticsIdMiddleware(req, res, next) {
  185. const session = req.session
  186. const sessionUser = SessionManager.getSessionUser(session)
  187. if (session.analyticsId) {
  188. if (sessionUser && session.analyticsId !== sessionUser.analyticsId) {
  189. session.analyticsId = sessionUser.analyticsId
  190. }
  191. } else {
  192. if (sessionUser) {
  193. session.analyticsId = sessionUser.analyticsId
  194. } else {
  195. session.analyticsId = uuid.v4()
  196. }
  197. }
  198. next()
  199. }
  200. module.exports = {
  201. identifyUser,
  202. recordEventForSession,
  203. recordEventForUser,
  204. setUserPropertyForUser,
  205. setUserPropertyForSession,
  206. setUserPropertyForAnalyticsId,
  207. updateEditingSession,
  208. getIdsFromSession,
  209. analyticsIdMiddleware: expressify(analyticsIdMiddleware),
  210. }