From 50abfe8f0c75edb4099f017af659bf803f12bd0c Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Thu, 30 Apr 2026 08:31:42 +0200 Subject: [PATCH] [migrations] add migration for back filling db.users.analyticsId (#33115) * [migrations] add migration for back filling db.users.analyticsId Co-authored-by: Davinder Singh * [web] add acceptance test for backfilling db.users.analyticsId --------- Co-authored-by: Davinder Singh GitOrigin-RevId: a0840969ac0c4c84e874c6f00cf0a78857a4bb06 --- .../src/BackFillUsersAnalyticsId.mjs | 99 +++++++++++++++++++ ...0424120000_back_fill_users_analyticsId.mjs | 24 +++++ 2 files changed, 123 insertions(+) create mode 100644 services/web/test/acceptance/src/BackFillUsersAnalyticsId.mjs create mode 100644 tools/migrations/20260424120000_back_fill_users_analyticsId.mjs diff --git a/services/web/test/acceptance/src/BackFillUsersAnalyticsId.mjs b/services/web/test/acceptance/src/BackFillUsersAnalyticsId.mjs new file mode 100644 index 0000000000..08731faa42 --- /dev/null +++ b/services/web/test/acceptance/src/BackFillUsersAnalyticsId.mjs @@ -0,0 +1,99 @@ +import { expect } from 'chai' +import { db, ObjectId } from '../../../app/src/infrastructure/mongodb.mjs' +import { exec } from 'node:child_process' + +describe('BackFillUsersAnalyticsId', function () { + beforeEach('insert data', async function () { + await db.users.insertMany([ + { + _id: new ObjectId('50e434d90000000000000000'), + email: 'foo0@bar.com', + }, + { + _id: new ObjectId('50e434d90000000000000001'), + email: 'foo1@bar.com', + }, + { + _id: new ObjectId('50e434d90000000000000002'), + analyticsId: '32dc5866-1451-4209-ba9d-23e96caafecd', + email: 'foo2@bar.com', + }, + { + _id: new ObjectId('50e434d90000000000000003'), + analyticsId: '32dc5866-1451-4209-ba9d-23e96caafecf', + email: 'foo3@bar.com', + }, + ]) + }) + + beforeEach('run migration', function (done) { + exec( + 'cd ../../tools/migrations && east migrate -t saas --force 20260424120000_back_fill_users_analyticsId', + done + ) + }) + + it('should update the users', async function () { + expect( + await db.users + .find({}, { projection: { _id: 1, email: 1, analyticsId: 1 } }) + .toArray() + ).to.deep.equal([ + { + _id: new ObjectId('50e434d90000000000000000'), + analyticsId: '50e434d90000000000000000', + email: 'foo0@bar.com', + }, + { + _id: new ObjectId('50e434d90000000000000001'), + analyticsId: '50e434d90000000000000001', + email: 'foo1@bar.com', + }, + { + _id: new ObjectId('50e434d90000000000000002'), + analyticsId: '32dc5866-1451-4209-ba9d-23e96caafecd', + email: 'foo2@bar.com', + }, + { + _id: new ObjectId('50e434d90000000000000003'), + analyticsId: '32dc5866-1451-4209-ba9d-23e96caafecf', + email: 'foo3@bar.com', + }, + ]) + }) + + describe('after rolling back', function () { + beforeEach(function (done) { + exec( + 'cd ../../tools/migrations && east rollback -t saas --force 20260424120000_back_fill_users_analyticsId', + done + ) + }) + it('should update the users and roll back', async function () { + expect( + await db.users + .find({}, { projection: { _id: 1, email: 1, analyticsId: 1 } }) + .toArray() + ).to.deep.equal([ + { + _id: new ObjectId('50e434d90000000000000000'), + email: 'foo0@bar.com', + }, + { + _id: new ObjectId('50e434d90000000000000001'), + email: 'foo1@bar.com', + }, + { + _id: new ObjectId('50e434d90000000000000002'), + analyticsId: '32dc5866-1451-4209-ba9d-23e96caafecd', + email: 'foo2@bar.com', + }, + { + _id: new ObjectId('50e434d90000000000000003'), + analyticsId: '32dc5866-1451-4209-ba9d-23e96caafecf', + email: 'foo3@bar.com', + }, + ]) + }) + }) +}) diff --git a/tools/migrations/20260424120000_back_fill_users_analyticsId.mjs b/tools/migrations/20260424120000_back_fill_users_analyticsId.mjs new file mode 100644 index 0000000000..dd20bf234d --- /dev/null +++ b/tools/migrations/20260424120000_back_fill_users_analyticsId.mjs @@ -0,0 +1,24 @@ +import { db } from './lib/mongodb.mjs' +import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js' + +const tags = ['saas', 'server-ce', 'server-pro'] + +const migrate = async () => { + await batchedUpdate(db.users, { analyticsId: { $exists: false } }, [ + { $set: { analyticsId: { $toString: '$_id' } } }, + ]) +} + +const rollback = async () => { + await batchedUpdate( + db.users, + { $expr: { $eq: [{ $strLenCP: '$analyticsId' }, 24] } }, + { $unset: { analyticsId: 1 } } + ) +} + +export default { + tags, + migrate, + rollback, +}