SplitTestUserGetter.mjs 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import { callbackify } from 'node:util'
  2. import Metrics from '@overleaf/metrics'
  3. import UserGetter from '../User/UserGetter.mjs'
  4. /**
  5. * A mongo user fetched with the projection from `getProjection`, carrying the
  6. * fields the split test assignment logic reads. This is the shape the
  7. * `*ForMongoUser` SplitTestHandler methods expect.
  8. *
  9. * @typedef {object} SplitTestUser
  10. * @property {import('mongodb').ObjectId} _id
  11. * @property {string} analyticsId
  12. * @property {boolean} [alphaProgram]
  13. * @property {boolean} [betaProgram]
  14. * @property {boolean} labsProgram
  15. * @property {string[]} [labsExperiments]
  16. * @property {Record<string, unknown>} [splitTests]
  17. */
  18. /**
  19. * Build the mongo projection needed to compute split test assignments for a user.
  20. *
  21. * Call-sites that already fetch a user and want to pass it to one of the
  22. * `*ForMongoUser` SplitTestHandler methods should spread this into their own
  23. * projection, so the user carries exactly the fields the assignment logic reads.
  24. *
  25. * @param {string} [splitTestName] restrict the `splitTests` sub-document to a
  26. * single test (for feature-flag style lookups); omit to fetch all assignments.
  27. */
  28. function getProjection(splitTestName) {
  29. const projection = {
  30. analyticsId: 1,
  31. alphaProgram: 1,
  32. betaProgram: 1,
  33. labsProgram: 1,
  34. labsExperiments: 1,
  35. }
  36. if (splitTestName) {
  37. projection[`splitTests.${splitTestName}`] = 1
  38. } else {
  39. projection.splitTests = 1
  40. }
  41. return projection
  42. }
  43. /**
  44. * @param id
  45. * @param {string} splitTestName
  46. * @param {string} path
  47. * @return {Promise<SplitTestUser>}
  48. */
  49. async function getUser(id, splitTestName, path) {
  50. Metrics.inc('split_test_get_user', 1, { path })
  51. const projection = getProjection(splitTestName)
  52. const user = await UserGetter.promises.getUser(id, projection)
  53. Metrics.histogram(
  54. 'split_test_get_user_from_mongo_size',
  55. JSON.stringify(user).length,
  56. [0, 100, 500, 1000, 2000, 5000, 10000, 15000, 20000, 50000, 100000]
  57. )
  58. return user
  59. }
  60. export default {
  61. getProjection,
  62. getUser: callbackify(getUser),
  63. promises: {
  64. getUser,
  65. },
  66. }