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

Differentiate between unset and disabled Writefull

GitOrigin-RevId: 3cf8f12ede851dab5a8067bdbcddba6c69870573
This commit is contained in:
Jimmy Domagala-Tang 2024-09-05 09:02:32 -04:00 committed by Copybot
parent edd8a7211f
commit 707790e51e
5 changed files with 95 additions and 56 deletions

View file

@ -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,

View file

@ -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(

View file

@ -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 },

View file

@ -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

View file

@ -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)
})
}