From 83f934f387596357a5f0ff48be86e108918f1231 Mon Sep 17 00:00:00 2001
From: Jimmy Domagala-Tang <Jimmy.Domagala-Tang@overleaf.com>
Date: Wed, 5 Jul 2023 08:32:40 -0400
Subject: [PATCH] Merge pull request #13620 from
 overleaf/jdt-survey-rollout-slider

Jdt survey rollout slider

GitOrigin-RevId: 958000c86fc79447484405b2382871bd118fb9fa
---
 .../app/src/Features/Survey/SurveyHandler.js  | 31 ++++++++++++++++++-
 services/web/app/src/models/Survey.js         |  4 +++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/services/web/app/src/Features/Survey/SurveyHandler.js b/services/web/app/src/Features/Survey/SurveyHandler.js
index 81b3f519c0..705f6efd4d 100644
--- a/services/web/app/src/Features/Survey/SurveyHandler.js
+++ b/services/web/app/src/Features/Survey/SurveyHandler.js
@@ -1,3 +1,4 @@
+const crypto = require('crypto')
 const SurveyCache = require('./SurveyCache')
 const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
 const { callbackify } = require('../../util/promises')
@@ -7,6 +8,8 @@ const { callbackify } = require('../../util/promises')
  */
 
 /**
+ * determines if there is a survey to show, given current surveys and rollout percentages
+ * uses userId in computation, to ensure that rollout groups always contain same users
  * @param {string} userId
  * @returns {Promise<Survey | undefined>}
  */
@@ -20,11 +23,37 @@ async function getSurvey(userId) {
         return
       }
     }
-    const { name, preText, linkText, url } = survey?.toObject() || {}
+
+    const { name, preText, linkText, url, options } = survey?.toObject() || {}
+    // default to full rollout for backwards compatibility
+    const rolloutPercentage = options?.rolloutPercentage || 100
+    if (!_userInRolloutPercentile(userId, name, rolloutPercentage)) {
+      return
+    }
+
     return { name, preText, linkText, url }
   }
 }
 
+function _userRolloutPercentile(userId, surveyName) {
+  const hash = crypto
+    .createHash('md5')
+    .update(userId + surveyName)
+    .digest('hex')
+  const hashPrefix = hash.substring(0, 8)
+  return Math.floor(
+    ((parseInt(hashPrefix, 16) % 0xffffffff) / 0xffffffff) * 100
+  )
+}
+
+function _userInRolloutPercentile(userId, surveyName, rolloutPercentage) {
+  if (rolloutPercentage === 100) {
+    return true
+  }
+  const userPercentile = _userRolloutPercentile(userId, surveyName)
+  return userPercentile < rolloutPercentage
+}
+
 module.exports = {
   getSurvey: callbackify(getSurvey),
   promises: {
diff --git a/services/web/app/src/models/Survey.js b/services/web/app/src/models/Survey.js
index 2898d94107..5d56a20b54 100644
--- a/services/web/app/src/models/Survey.js
+++ b/services/web/app/src/models/Survey.js
@@ -36,6 +36,10 @@ const SurveySchema = new Schema(
         type: Boolean,
         default: false,
       },
+      rolloutPercentage: {
+        type: Number,
+        default: 100,
+      },
     },
   },
   {