From a1bd2e9b93dc290a404983b30b26ae54b1b94d26 Mon Sep 17 00:00:00 2001 From: Alexandre Bourdin Date: Fri, 27 Mar 2026 12:29:47 +0100 Subject: [PATCH] Merge pull request #32441 from overleaf/ab-script-insert-alpha-version-0 [web] Add script to insert missing 0% rollout alpha phase version to existing feature flag GitOrigin-RevId: 56dc4c74e5f7f5141647fd71f64df71a1264734e --- .../insert_feature_flag_alpha_phase_0.mjs | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 services/web/scripts/insert_feature_flag_alpha_phase_0.mjs diff --git a/services/web/scripts/insert_feature_flag_alpha_phase_0.mjs b/services/web/scripts/insert_feature_flag_alpha_phase_0.mjs new file mode 100644 index 0000000000..101d01a8c3 --- /dev/null +++ b/services/web/scripts/insert_feature_flag_alpha_phase_0.mjs @@ -0,0 +1,129 @@ +import { scriptRunner } from './lib/ScriptRunner.mjs' +import { db } from '../app/src/infrastructure/mongodb.mjs' +import minimist from 'minimist' + +const argv = minimist(process.argv.slice(2)) +const commit = argv.commit !== undefined +const splitTestName = argv._[0] + +if (!splitTestName) { + console.error( + 'Usage: node scripts/insert_feature_flag_alpha_phase_0.mjs [--commit]' + ) + console.error( + '\nInserts a new version 1 (alpha phase, 0% rollout, active) and renumbers existing versions.' + ) + console.error('Without --commit, runs in dry-run mode and shows a diff.') + process.exit(1) +} + +if (!commit) { + console.log('DOING DRY RUN. TO SAVE CHANGES PASS --commit') +} + +const RED = '\x1b[31m' +const GREEN = '\x1b[32m' +const RESET = '\x1b[0m' + +function formatDiff(before, after) { + const aLines = before.split('\n') + const bLines = after.split('\n') + const m = aLines.length + const n = bLines.length + + const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)) + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + if (aLines[i - 1] === bLines[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1 + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + } + } + } + + const result = [] + let i = m + let j = n + while (i > 0 || j > 0) { + if (i > 0 && j > 0 && aLines[i - 1] === bLines[j - 1]) { + result.unshift(` ${aLines[i - 1]}`) + i-- + j-- + } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) { + result.unshift(`${GREEN}+ ${bLines[j - 1]}${RESET}`) + j-- + } else { + result.unshift(`${RED}- ${aLines[i - 1]}${RESET}`) + i-- + } + } + + return result.join('\n') +} + +async function main() { + const splitTest = await db.splittests.findOne({ name: splitTestName }) + + if (!splitTest) { + throw new Error(`Split test '${splitTestName}' not found`) + } + + if (!splitTest.versions || splitTest.versions.length === 0) { + throw new Error(`Split test '${splitTestName}' has no versions`) + } + + const firstVersion = splitTest.versions[0] + + const newVersion = { + ...firstVersion, + versionNumber: 1, + phase: 'alpha', + active: true, + variants: firstVersion.variants.map(v => ({ + ...v, + rolloutPercent: 0, + rolloutStripes: [], + })), + createdAt: new Date(firstVersion.createdAt.getTime() - 1000), + comment: `Inserted alpha phase 0% rollout version (based on v${firstVersion.versionNumber})`, + } + + const updatedVersions = [ + newVersion, + ...splitTest.versions.map(v => ({ + ...v, + versionNumber: v.versionNumber + 1, + })), + ] + + const beforeStr = JSON.stringify(splitTest.versions, null, 2) + const afterStr = JSON.stringify(updatedVersions, null, 2) + + console.log(`\nSplit test: ${splitTestName}`) + console.log(`Current versions: ${splitTest.versions.length}`) + console.log(`After: ${updatedVersions.length} versions`) + console.log('\n--- versions diff ---') + console.log(formatDiff(beforeStr, afterStr)) + console.log('--- end diff ---\n') + + if (commit) { + await db.splittests.updateOne( + { name: splitTestName }, + { $set: { versions: updatedVersions } } + ) + console.log( + `Successfully inserted alpha phase 0% version for '${splitTestName}'` + ) + } else { + console.log('Dry run complete. Pass --commit to apply changes.') + } +} + +try { + await scriptRunner(main) + process.exit(0) +} catch (error) { + console.error(error) + process.exit(1) +}