Просмотр исходного кода

Merge pull request #20256 from overleaf/jdt-enable-writefull-unset

Differentiate between unset and disabled Writefull

GitOrigin-RevId: 3cf8f12ede851dab5a8067bdbcddba6c69870573
Jimmy Domagala-Tang 2 лет назад
Родитель
Сommit
707790e51e

+ 34 - 34
services/web/app/src/Features/Project/ProjectController.js

@@ -446,30 +446,6 @@ const _ProjectController = {
         usedLatex,
       } = userValues
 
-      // check if a user is not in the writefull-oauth-promotion, in which case they may be part of the auto trial group
-      if (
-        !anonymous &&
-        splitTestAssignments['writefull-oauth-promotion']?.variant === 'default'
-      ) {
-        // since we are auto-enrolling users into writefull if they are part of the group, we only want to
-        // auto enroll (set writefull to true) if its the first time they have entered the test
-        // this ensures that they can still turn writefull off (otherwise, we would be setting writefull on every time they access their projects)
-        const { variant, metadata } =
-          await SplitTestHandler.promises.getAssignment(
-            req,
-            res,
-            'writefull-auto-load'
-          )
-        if (variant === 'enabled' && metadata?.isFirstNonDefaultAssignment) {
-          await UserUpdater.promises.updateUser(userId, {
-            $set: {
-              writefull: { enabled: true },
-            },
-          })
-          user.writefull.enabled = true
-        }
-      }
-
       const brandVariation = project?.brandVariationId
         ? await BrandVariationsHandler.promises.getBrandVariationById(
             project.brandVariationId
@@ -636,16 +612,15 @@ const _ProjectController = {
         !userIsMemberOfGroupSubscription &&
         !userHasInstitutionLicence
 
-      let showAiErrorAssistant = false
+      let aiFeaturesAllowed = false
       if (userId && Features.hasFeature('saas')) {
         try {
           // exit early if the user couldnt use ai anyways, since permissions checks are expensive
-          const canUseAiOnProject =
-            user.features?.aiErrorAssistant &&
-            (privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
-              privilegeLevel === PrivilegeLevels.OWNER)
+          const canEditProject =
+            privilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
+            privilegeLevel === PrivilegeLevels.OWNER
 
-          if (canUseAiOnProject) {
+          if (canEditProject) {
             // check permissions for user and project owner, to see if they allow AI on the project
             const permissionsResults = await Modules.promises.hooks.fire(
               'projectAllowsCapability',
@@ -657,11 +632,34 @@ const _ProjectController = {
               result => result === true
             )
 
-            showAiErrorAssistant = aiAllowed
+            aiFeaturesAllowed = aiAllowed
           }
         } catch (err) {
           // still allow users to access project if we cant get their permissions, but disable AI feature
-          showAiErrorAssistant = false
+          aiFeaturesAllowed = false
+        }
+      }
+
+      // check if a user has never tried writefull before (writefull.enabled will be null)
+      //  if they previously accepted writefull. user.writefull will be true,
+      //  if they explicitly disabled it, user.writefull will be false
+      if (aiFeaturesAllowed && user.writefull?.enabled === null) {
+        // since we are auto-enrolling users into writefull if they are part of the group, we only want to
+        // auto enroll (set writefull to true) if its the first time they have entered the test
+        // this ensures that they can still turn writefull off (otherwise, we would be setting writefull on every time they access their projects)
+        const { variant, metadata } =
+          await SplitTestHandler.promises.getAssignment(
+            req,
+            res,
+            'writefull-auto-load'
+          )
+        if (variant === 'enabled' && metadata?.isFirstNonDefaultAssignment) {
+          await UserUpdater.promises.updateUser(userId, {
+            $set: {
+              writefull: { enabled: true },
+            },
+          })
+          user.writefull.enabled = true
         }
       }
 
@@ -693,7 +691,7 @@ const _ProjectController = {
           features: user.features,
           refProviders: _.mapValues(user.refProviders, Boolean),
           writefull: {
-            enabled: Boolean(user.writefull?.enabled),
+            enabled: Boolean(user.writefull?.enabled && aiFeaturesAllowed),
           },
           alphaProgram: user.alphaProgram,
           betaProgram: user.betaProgram,
@@ -741,7 +739,9 @@ const _ProjectController = {
         debugPdfDetach,
         showSymbolPalette,
         symbolPaletteAvailable: Features.hasFeature('symbol-palette'),
-        showAiErrorAssistant,
+        userRestrictions: Array.from(req.userRestrictions || []),
+        showAiErrorAssistant:
+          aiFeaturesAllowed && user.features?.aiErrorAssistant,
         detachRole,
         metadata: { viewport: false },
         showUpgradePrompt,

+ 0 - 7
services/web/app/src/Features/User/UserPagesController.js

@@ -71,13 +71,6 @@ async function settingsPage(req, res) {
     )
   }
 
-  // getAssignment sets res.locals, which will pass to the splitTest context
-  await SplitTestHandler.promises.getAssignment(
-    req,
-    res,
-    'writefull-oauth-promotion'
-  )
-
   let personalAccessTokens
   try {
     const results = await Modules.promises.hooks.fire(

+ 1 - 1
services/web/app/src/models/User.js

@@ -180,7 +180,7 @@ const UserSchema = new Schema(
       zotero: Schema.Types.Mixed,
     },
     writefull: {
-      enabled: { type: Boolean, default: false },
+      enabled: { type: Boolean, default: null },
     },
     alphaProgram: { type: Boolean, default: false }, // experimental features
     betaProgram: { type: Boolean, default: false },

+ 1 - 14
services/web/frontend/js/features/settings/components/linking-section.tsx

@@ -5,7 +5,6 @@ import { useSSOContext, SSOSubscription } from '../context/sso-context'
 import { SSOLinkingWidget } from './linking/sso-widget'
 import getMeta from '../../../utils/meta'
 import { useBroadcastUser } from '@/shared/hooks/user-channel/use-broadcast-user'
-import { useFeatureFlag } from '@/shared/context/split-test-context'
 import OLNotification from '@/features/ui/components/ol/ol-notification'
 
 const availableIntegrationLinkingWidgets = importOverleafModules(
@@ -48,19 +47,7 @@ function LinkingSection() {
     oauth2ServerComponents
   )
 
-  // currently the only thing that is in the langFeedback section is writefull,
-  // which is behind a split test. we should hide this section if the user is not in the split test
-  // todo: remove split test check, and split test context after gradual rollout is complete
-  const hasWritefullOauthPromotion = useFeatureFlag('writefull-oauth-promotion')
-
-  // even if they arent in the split test, if they have it enabled let them toggle it off
-  const user = getMeta('ol-user')
-  const shouldLoadWritefull =
-    (hasWritefullOauthPromotion || user.writefull?.enabled === true) &&
-    !window.writefull // check if the writefull extension is installed, in which case we dont handle the integration
-
-  const haslangFeedbackLinkingWidgets =
-    langFeedbackLinkingWidgets.length && shouldLoadWritefull
+  const haslangFeedbackLinkingWidgets = langFeedbackLinkingWidgets.length
   const hasIntegrationLinkingSection =
     renderSyncSection && allIntegrationLinkingWidgets.length
   const hasReferencesLinkingSection = referenceLinkingWidgets.length

+ 59 - 0
services/web/scripts/split_writefull_disabled_from_unset.js

@@ -0,0 +1,59 @@
+const { db, waitForDb } = require('../app/src/infrastructure/mongodb')
+const { batchedUpdate } = require('./helpers/batchedUpdate')
+const { ObjectId } = require('mongodb-legacy')
+const fs = require('fs')
+
+const CHUNK_SIZE = 1000
+
+// Function to chunk the array
+function chunkArray(array, size) {
+  const result = []
+  for (let i = 0; i < array.length; i += size) {
+    result.push(array.slice(i, i + size))
+  }
+  return result
+}
+
+async function main() {
+  // search for file of users who already explicitly opted out first
+  const optOutPath = process.argv[2]
+  const optedOutFile = fs.readFileSync(optOutPath, 'utf8')
+  let optedOutList = optedOutFile
+
+  optedOutList = optedOutFile.split('\n').map(id => new ObjectId(id))
+
+  console.log(`preserving opt-outs of ${optedOutList.length} users`)
+  await waitForDb()
+  // update all applicable user models
+  await batchedUpdate(
+    'users',
+    { 'writefull.enabled': false }, // and is false
+    { $set: { 'writefull.enabled': null } }
+  )
+
+  const chunks = chunkArray(optedOutList, CHUNK_SIZE)
+
+  // then reset any explicit false back to being false
+  // Iterate over each chunk and perform the query
+  for (const chunkedIds of chunks) {
+    console.log('batch update started')
+    await db.users.updateMany(
+      { _id: { $in: chunkedIds } },
+      { $set: { 'writefull.enabled': false } }
+    )
+    console.log('batch completed')
+  }
+}
+
+module.exports = main
+
+if (require.main === module) {
+  main()
+    .then(() => {
+      process.exit(0)
+    })
+    .catch(error => {
+      console.error({ error })
+      process.exit(1)
+    })
+}