mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #5425 from overleaf/bg-add-feature-override-script
add feature override script GitOrigin-RevId: 90def174b4ac182b6f62984e8f9621f3dc16b92d
This commit is contained in:
parent
b9e7f6ab5e
commit
d2c76b05cf
2 changed files with 186 additions and 0 deletions
|
@ -39,6 +39,14 @@ module.exports = {
|
||||||
return callback(err, featuresChanged)
|
return callback(err, featuresChanged)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createFeaturesOverride(userId, featuresOverride, callback) {
|
||||||
|
User.updateOne(
|
||||||
|
{ _id: userId },
|
||||||
|
{ $push: { featuresOverrides: featuresOverride } },
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.promises = promisifyAll(module.exports, {
|
module.exports.promises = promisifyAll(module.exports, {
|
||||||
|
|
178
services/web/scripts/add_feature_override.js
Normal file
178
services/web/scripts/add_feature_override.js
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
// Script to add feature overrides
|
||||||
|
//
|
||||||
|
// A feature override is appended to the user's featuresOverride list if they do
|
||||||
|
// not already have the feature. The features are refreshed after adding the
|
||||||
|
// override.
|
||||||
|
//
|
||||||
|
// If the script detects that the user would have the feature just by refreshing
|
||||||
|
// then it skips adding the override and just refreshes the users features --
|
||||||
|
// this is to minimise the creation of unnecessary overrides.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// $ node scripts/add_feature_override.js --commit --note 'text description' --expires 2022-01-01 --override JSONFILE --ids IDFILE
|
||||||
|
//
|
||||||
|
// --commit do the update, remove this option for dry-run testing
|
||||||
|
// --note text description [optional]
|
||||||
|
// --expires expiry date for override [optional]
|
||||||
|
// --skip-existing don't create the override for users who already have the feature (e.g. via a subscription)
|
||||||
|
//
|
||||||
|
// IDFILE: file containing list of user ids, one per line
|
||||||
|
// JSONFILE: file containing JSON of the desired feature overrides e.g. {"symbolPalette": true}
|
||||||
|
//
|
||||||
|
// The feature override is specified with JSON to allow types to be set as string/number/boolean.
|
||||||
|
// It is contained in a file to avoid any issues with shell quoting.
|
||||||
|
|
||||||
|
const minimist = require('minimist')
|
||||||
|
const fs = require('fs')
|
||||||
|
const { ObjectId, waitForDb } = require('../app/src/infrastructure/mongodb')
|
||||||
|
const pLimit = require('p-limit')
|
||||||
|
const FeaturesUpdater = require('../app/src/Features/Subscription/FeaturesUpdater')
|
||||||
|
const FeaturesHelper = require('../app/src/Features/Subscription/FeaturesHelper')
|
||||||
|
const UserFeaturesUpdater = require('../app/src/Features/Subscription/UserFeaturesUpdater')
|
||||||
|
const UserGetter = require('../app/src/Features/User/UserGetter')
|
||||||
|
|
||||||
|
const processLogger = {
|
||||||
|
failed: [],
|
||||||
|
success: [],
|
||||||
|
skipped: [],
|
||||||
|
printSummary: () => {
|
||||||
|
console.log(
|
||||||
|
{
|
||||||
|
success: processLogger.success,
|
||||||
|
failed: processLogger.failed,
|
||||||
|
skipped: processLogger.skipped,
|
||||||
|
},
|
||||||
|
`\nDONE. ${processLogger.success.length} successful. ${processLogger.skipped.length} skipped. ${processLogger.failed.length} failed to update.`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function _validateUserIdList(userIds) {
|
||||||
|
userIds.forEach(userId => {
|
||||||
|
if (!ObjectId.isValid(userId))
|
||||||
|
throw new Error(`user ID not valid: ${userId}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _handleUser(userId) {
|
||||||
|
const user = await UserGetter.promises.getUser(userId, {
|
||||||
|
features: 1,
|
||||||
|
featuresOverrides: 1,
|
||||||
|
})
|
||||||
|
if (!user) {
|
||||||
|
console.log(userId, 'does not exist, failed')
|
||||||
|
processLogger.failed.push(userId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const desiredFeatures = OVERRIDE.features
|
||||||
|
// Does the user have the requested features already?
|
||||||
|
if (
|
||||||
|
SKIP_EXISTING &&
|
||||||
|
FeaturesHelper.isFeatureSetBetter(user.features, desiredFeatures)
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
userId,
|
||||||
|
`already has ${JSON.stringify(desiredFeatures)}, skipping`
|
||||||
|
)
|
||||||
|
processLogger.skipped.push(userId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Would the user have the requested feature if the features were refreshed?
|
||||||
|
const freshFeatures = await FeaturesUpdater.promises.computeFeatures(userId)
|
||||||
|
if (
|
||||||
|
SKIP_EXISTING &&
|
||||||
|
FeaturesHelper.isFeatureSetBetter(freshFeatures, desiredFeatures)
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
userId,
|
||||||
|
`would have ${JSON.stringify(
|
||||||
|
desiredFeatures
|
||||||
|
)} if refreshed, skipping override`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// create the override (if not in dry-run mode)
|
||||||
|
if (COMMIT) {
|
||||||
|
await UserFeaturesUpdater.promises.createFeaturesOverride(
|
||||||
|
userId,
|
||||||
|
OVERRIDE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!COMMIT) {
|
||||||
|
// not saving features; nothing else to do
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const refreshResult = await FeaturesUpdater.promises.refreshFeatures(
|
||||||
|
userId,
|
||||||
|
'add-feature-override-script'
|
||||||
|
)
|
||||||
|
const featureSetIncludesNewFeatures = FeaturesHelper.isFeatureSetBetter(
|
||||||
|
refreshResult.features,
|
||||||
|
desiredFeatures
|
||||||
|
)
|
||||||
|
if (featureSetIncludesNewFeatures) {
|
||||||
|
// features added successfully
|
||||||
|
processLogger.success.push(userId)
|
||||||
|
} else {
|
||||||
|
console.log('FEATURE NOT ADDED', refreshResult)
|
||||||
|
processLogger.failed.push(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const argv = minimist(process.argv.slice(2))
|
||||||
|
const CONCURRENCY = argv.async ? argv.async : 10
|
||||||
|
const overridesFilename = argv.override
|
||||||
|
const expires = argv.expires
|
||||||
|
const note = argv.note
|
||||||
|
const SKIP_EXISTING = argv['skip-existing'] || false
|
||||||
|
const COMMIT = argv.commit !== undefined
|
||||||
|
if (!COMMIT) {
|
||||||
|
console.warn('Doing dry run without --commit')
|
||||||
|
}
|
||||||
|
|
||||||
|
const idsFilename = argv.ids
|
||||||
|
if (!idsFilename) throw new Error('missing ids list filename')
|
||||||
|
|
||||||
|
const usersFile = fs.readFileSync(idsFilename, 'utf8')
|
||||||
|
const userIds = usersFile
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.map(id => id.trim())
|
||||||
|
|
||||||
|
const overridesFile = fs.readFileSync(overridesFilename, 'utf8')
|
||||||
|
const features = JSON.parse(overridesFile)
|
||||||
|
const OVERRIDE = { features }
|
||||||
|
if (note) {
|
||||||
|
OVERRIDE.note = note
|
||||||
|
}
|
||||||
|
if (expires) {
|
||||||
|
OVERRIDE.expiresAt = new Date(expires)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processUsers(userIds) {
|
||||||
|
console.log('---Starting add feature override script---')
|
||||||
|
|
||||||
|
console.log('Will update users to have', OVERRIDE)
|
||||||
|
console.log(
|
||||||
|
SKIP_EXISTING
|
||||||
|
? 'Users with this feature already will be skipped'
|
||||||
|
: 'Every user in file will get feature override'
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitForDb()
|
||||||
|
|
||||||
|
_validateUserIdList(userIds)
|
||||||
|
console.log(`---Starting to process ${userIds.length} users---`)
|
||||||
|
|
||||||
|
const limit = pLimit(CONCURRENCY)
|
||||||
|
await Promise.all(
|
||||||
|
userIds.map(userId => limit(() => _handleUser(ObjectId(userId))))
|
||||||
|
)
|
||||||
|
|
||||||
|
processLogger.printSummary()
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
processUsers(userIds)
|
Loading…
Reference in a new issue