diff --git a/services/web/migrations/20210726083523_convert_confirmedAt_strings_to_dates.mjs b/services/web/migrations/20210726083523_convert_confirmedAt_strings_to_dates.mjs index 8dbac841c6..064e365a86 100644 --- a/services/web/migrations/20210726083523_convert_confirmedAt_strings_to_dates.mjs +++ b/services/web/migrations/20210726083523_convert_confirmedAt_strings_to_dates.mjs @@ -1,12 +1,35 @@ -import updateStringDates from '../scripts/confirmed_at_to_dates.mjs' +import { db } from '../app/src/infrastructure/mongodb.js' +import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js' const tags = ['saas'] -const migrate = async client => { - await updateStringDates() +const migrate = async () => { + await batchedUpdate( + db.users, + { 'emails.confirmedAt': { $type: 'string' } }, + async function (batch) { + for (const user of batch) { + for (const email of user.emails) { + if (typeof email.confirmedAt === 'string') { + await db.users.updateOne( + { _id: user._id, 'emails.email': email.email }, + { + $set: { + 'emails.$.confirmedAt': new Date( + email.confirmedAt.replace(/ UTC$/, '') + ), + }, + } + ) + } + } + } + }, + { emails: 1 } + ) } -const rollback = async client => { +const rollback = async () => { /* nothing to do */ } diff --git a/services/web/migrations/20210726083523_convert_split_tests_assigned_at_strings_to_dates.mjs b/services/web/migrations/20210726083523_convert_split_tests_assigned_at_strings_to_dates.mjs index 094f3501c3..b13d636d0f 100644 --- a/services/web/migrations/20210726083523_convert_split_tests_assigned_at_strings_to_dates.mjs +++ b/services/web/migrations/20210726083523_convert_split_tests_assigned_at_strings_to_dates.mjs @@ -1,12 +1,29 @@ -import updateStringDates from '../scripts/split_tests_assigned_at_to_dates.mjs' +import { db } from '../app/src/infrastructure/mongodb.js' +import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js' const tags = ['saas'] -const migrate = async client => { - await updateStringDates() +const migrate = async () => { + await batchedUpdate( + db.users, + { splitTests: { $exists: true } }, + async function (batch) { + for (const user of batch) { + const splitTests = user.splitTests + for (const splitTest of Object.values(user.splitTests)) { + for (const variant of splitTest) { + variant.assignedAt = new Date(variant.assignedAt) + } + } + + await db.users.updateOne({ _id: user._id }, { $set: { splitTests } }) + } + }, + { splitTests: 1 } + ) } -const rollback = async client => { +const rollback = async () => { /* nothing to do */ } diff --git a/services/web/scripts/confirmed_at_to_dates.mjs b/services/web/scripts/confirmed_at_to_dates.mjs deleted file mode 100644 index 60fcc543fd..0000000000 --- a/services/web/scripts/confirmed_at_to_dates.mjs +++ /dev/null @@ -1,53 +0,0 @@ -import { db } from '../app/src/infrastructure/mongodb.js' -import { fileURLToPath } from 'node:url' - -async function updateStringDates() { - const users = await db.users.aggregate([ - { $unwind: { path: '$emails' } }, - { - $match: { 'emails.confirmedAt': { $exists: true, $type: 'string' } }, - }, - { - $project: { - _id: 1, - 'emails.email': 1, - 'emails.confirmedAt': 1, - }, - }, - ]) - - let user - let count = 0 - while ((user = await users.next())) { - count += 1 - if (count % 10000 === 0) { - console.log(`processed ${count} users`) - } - const confirmedAt = user.emails.confirmedAt - const dateConfirmedAt = new Date(confirmedAt.replace(/ UTC$/, '')) - await db.users.updateOne( - { - _id: user._id, - 'emails.email': user.emails.email, - }, - { - $set: { - 'emails.$.confirmedAt': dateConfirmedAt, - }, - } - ) - } - console.log(`Updated ${count} confirmedAt strings to dates!`) -} - -if (fileURLToPath(import.meta.url) === process.argv[1]) { - try { - await updateStringDates() - process.exit(0) - } catch (error) { - console.error(error) - process.exit(1) - } -} - -export default updateStringDates diff --git a/services/web/scripts/split_tests_assigned_at_to_dates.mjs b/services/web/scripts/split_tests_assigned_at_to_dates.mjs deleted file mode 100644 index ea6fd3fbea..0000000000 --- a/services/web/scripts/split_tests_assigned_at_to_dates.mjs +++ /dev/null @@ -1,46 +0,0 @@ -import { db } from '../app/src/infrastructure/mongodb.js' -import { fileURLToPath } from 'node:url' - -async function updateStringDates() { - const users = db.users.find({ - splitTests: { $exists: true }, - }) - - let user - let count = 0 - while ((user = await users.next())) { - count += 1 - if (count % 10000 === 0) { - console.log(`processed ${count} users...`) - } - - const splitTests = user.splitTests - for (const splitTestKey of Object.keys(splitTests)) { - for (const variantIndex in splitTests[splitTestKey]) { - splitTests[splitTestKey][variantIndex].assignedAt = new Date( - splitTests[splitTestKey][variantIndex].assignedAt - ) - } - } - - await db.users.updateOne( - { - _id: user._id, - }, - { $set: { splitTests } } - ) - } - console.log(`Updated ${count} assignedAt strings to dates!`) -} - -if (fileURLToPath(import.meta.url) === process.argv[1]) { - try { - await updateStringDates() - process.exit(0) - } catch (error) { - console.error(error) - process.exit(1) - } -} - -export default updateStringDates diff --git a/services/web/test/acceptance/src/ConvertEmailConfirmedAtToDates.js b/services/web/test/acceptance/src/ConvertEmailConfirmedAtToDates.js new file mode 100644 index 0000000000..da5f842bb6 --- /dev/null +++ b/services/web/test/acceptance/src/ConvertEmailConfirmedAtToDates.js @@ -0,0 +1,69 @@ +import { expect } from 'chai' +import { db } from '../../../app/src/infrastructure/mongodb.js' +import { exec } from 'node:child_process' + +describe('ConvertEmailConfirmedAtToDates', function () { + beforeEach('insert data', async function () { + await db.users.insertMany([ + { email: 'foo0@bar.com', emails: [{ email: 'foo0@bar.com' }] }, + { + email: 'foo1@bar.com', + emails: [ + { email: 'foo1@bar.com', confirmedAt: '2025-06-20 15:53:31 UTC' }, + ], + }, + { + email: 'foo2@bar.com', + emails: [ + { email: 'foo2@bar.com', confirmedAt: '2025-06-20 15:53:32 UTC' }, + { email: 'foo3@bar.com', confirmedAt: '2025-06-20 15:53:33 UTC' }, + { + email: 'foo4@bar.com', + confirmedAt: new Date('2025-06-20T15:53:31.134Z'), + }, + ], + }, + ]) + }) + + beforeEach('run migration', function (done) { + exec( + 'east migrate -t saas --force 20210726083523_convert_confirmedAt_strings_to_dates', + done + ) + }) + + it('should update the dates', async function () { + expect( + await db.users.find({}, { projection: { _id: 0 } }).toArray() + ).to.deep.equal([ + { email: 'foo0@bar.com', emails: [{ email: 'foo0@bar.com' }] }, + { + email: 'foo1@bar.com', + emails: [ + { + email: 'foo1@bar.com', + confirmedAt: new Date('2025-06-20T15:53:31.000Z'), + }, + ], + }, + { + email: 'foo2@bar.com', + emails: [ + { + email: 'foo2@bar.com', + confirmedAt: new Date('2025-06-20T15:53:32.000Z'), + }, + { + email: 'foo3@bar.com', + confirmedAt: new Date('2025-06-20T15:53:33.000Z'), + }, + { + email: 'foo4@bar.com', + confirmedAt: new Date('2025-06-20T15:53:31.134Z'), + }, + ], + }, + ]) + }) +}) diff --git a/services/web/test/acceptance/src/ConvertSplitTestAssignedAtToDates.js b/services/web/test/acceptance/src/ConvertSplitTestAssignedAtToDates.js new file mode 100644 index 0000000000..bfec1d1fb2 --- /dev/null +++ b/services/web/test/acceptance/src/ConvertSplitTestAssignedAtToDates.js @@ -0,0 +1,134 @@ +import { expect } from 'chai' +import { db } from '../../../app/src/infrastructure/mongodb.js' +import { exec } from 'node:child_process' + +describe('ConvertSplitTestAssignedAtToDates', function () { + beforeEach('insert data', async function () { + await db.users.insertMany([ + { + email: 'foo0@bar.com', + splitTests: { + 'split-test-1': [ + { + variantName: 'enabled', + versionNumber: 4, + phase: 'release', + assignedAt: '2025-03-18T13:19:46.627Z', + }, + { + variantName: 'default', + versionNumber: 5, + phase: 'release', + assignedAt: new Date('2025-04-30T08:52:13.783Z'), + }, + ], + 'split-test-2': [ + { + variantName: 'active', + versionNumber: 5, + phase: 'release', + assignedAt: new Date('2025-02-14T09:08:30.190Z'), + }, + { + variantName: 'active', + versionNumber: 7, + phase: 'release', + assignedAt: new Date('2025-03-11T11:05:13.640Z'), + }, + ], + }, + }, + { + email: 'foo1@bar.com', + splitTests: { + 'split-test-3': [ + { + variantName: 'default', + versionNumber: 1, + phase: 'release', + assignedAt: '2025-02-11T14:55:38.470Z', + }, + { + variantName: 'enabled', + versionNumber: 21, + phase: 'release', + assignedAt: '2025-03-18T13:19:46.826Z', + }, + ], + }, + }, + { + email: 'foo2@bar.com', + }, + ]) + }) + + beforeEach('run migration', function (done) { + exec( + 'east migrate -t saas --force 20210726083523_convert_split_tests_assigned_at_strings_to_dates', + done + ) + }) + + it('should update the dates', async function () { + expect( + await db.users.find({}, { projection: { _id: 0 } }).toArray() + ).to.deep.equal([ + { + email: 'foo0@bar.com', + splitTests: { + 'split-test-1': [ + { + variantName: 'enabled', + versionNumber: 4, + phase: 'release', + assignedAt: new Date('2025-03-18T13:19:46.627Z'), + }, + { + variantName: 'default', + versionNumber: 5, + phase: 'release', + assignedAt: new Date('2025-04-30T08:52:13.783Z'), + }, + ], + 'split-test-2': [ + { + variantName: 'active', + versionNumber: 5, + phase: 'release', + assignedAt: new Date('2025-02-14T09:08:30.190Z'), + }, + { + variantName: 'active', + versionNumber: 7, + phase: 'release', + assignedAt: new Date('2025-03-11T11:05:13.640Z'), + }, + ], + }, + }, + { + email: 'foo1@bar.com', + splitTests: { + 'split-test-3': [ + { + variantName: 'default', + versionNumber: 1, + phase: 'release', + assignedAt: new Date('2025-02-11T14:55:38.470Z'), + }, + { + variantName: 'enabled', + versionNumber: 21, + phase: 'release', + assignedAt: new Date('2025-03-18T13:19:46.826Z'), + }, + ], + }, + }, + { + email: 'foo2@bar.com', + }, + ]) + }) +})