[monorepo] prepare per-project queues (#35386)

GitOrigin-RevId: ab020603ec04c705d08be466563adac0ce30bc04
This commit is contained in:
Jakob Ackermann
2026-09-17 11:26:50 +00:00
committed by Copybot
parent 3700cf5968
commit cdba372dfc
23 changed files with 1583 additions and 154 deletions
@@ -7,6 +7,7 @@ import Metrics from '@overleaf/metrics'
import CollaboratorsInviteGetter from '../Collaborators/CollaboratorsInviteGetter.mjs'
import PrivilegeLevels from '../Authorization/PrivilegeLevels.mjs'
import SessionManager from '../Authentication/SessionManager.mjs'
import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
import Errors from '../Errors/Errors.js'
import { expressify } from '@overleaf/promise-utils'
import Settings from '@overleaf/settings'
@@ -67,12 +68,22 @@ async function joinProject(req, res, next) {
)
}
let usePerProjectPendingUpdates = false
if (userId) {
usePerProjectPendingUpdates =
await SplitTestHandler.promises.featureFlagEnabledForUser(
userId,
'per-project-pending-updates'
)
}
res.json({
project,
privilegeLevel,
isRestrictedUser,
isTokenMember,
isInvitedMember,
usePerProjectPendingUpdates,
})
}
+6 -1
View File
@@ -152,7 +152,12 @@ async function deleteDocFromRedis(projectId, docId) {
`lastUpdatedAt:{${docId}}`,
`lastUpdatedBy:{${docId}}`
)
await redis.srem(`DocsIn:{${projectId}}`, projectId)
// NOTE: once the update-queue migration reaches phase >= 2, a doc's pending
// updates live in the shared `PendingProjectUpdates:{projectId}` list (on the
// document-updater redis) rather than `PendingUpdates:{docId}`. That list is
// per-project, so it cannot be cleared here without dropping other docs'
// updates - flush/drain the project before running this script.
await redis.srem(`DocsIn:{${projectId}}`, docId)
}
try {
@@ -170,7 +170,12 @@ async function deleteDocFromRedis(projectId, docId) {
`lastUpdatedAt:{${docId}}`,
`lastUpdatedBy:{${docId}}`
)
await redis.srem(`DocsIn:{${projectId}}`, projectId)
// NOTE: once the update-queue migration reaches phase >= 2, a doc's pending
// updates live in the shared `PendingProjectUpdates:{projectId}` list (on the
// document-updater redis) rather than `PendingUpdates:{docId}`. That list is
// per-project, so it cannot be cleared here without dropping other docs'
// updates - flush/drain the project before running this script.
await redis.srem(`DocsIn:{${projectId}}`, docId)
}
try {
@@ -160,6 +160,7 @@ describe('EditorHttpController', function () {
ctx.SplitTestHandler = {
promises: {
getAssignmentForUser: sinon.stub().resolves({ variant: 'default' }),
featureFlagEnabledForUser: sinon.stub().resolves(false),
},
}
ctx.UserGetter = { promises: { getUser: sinon.stub().resolves(null, {}) } }
@@ -304,6 +305,7 @@ describe('EditorHttpController', function () {
isRestrictedUser: false,
isTokenMember: false,
isInvitedMember: true,
usePerProjectPendingUpdates: false,
})
})
@@ -361,6 +363,7 @@ describe('EditorHttpController', function () {
isRestrictedUser: true,
isTokenMember: false,
isInvitedMember: false,
usePerProjectPendingUpdates: false,
})
})
})
@@ -417,8 +420,14 @@ describe('EditorHttpController', function () {
isRestrictedUser: true,
isTokenMember: false,
isInvitedMember: false,
usePerProjectPendingUpdates: false,
})
})
it('should not fetch a split test assignment', function (ctx) {
expect(ctx.SplitTestHandler.promises.featureFlagEnabledForUser).not.to
.have.been.called
})
})
describe('with a token access user', function () {
@@ -451,10 +460,36 @@ describe('EditorHttpController', function () {
isRestrictedUser: false,
isTokenMember: true,
isInvitedMember: false,
usePerProjectPendingUpdates: false,
})
})
})
describe('with the per-project-pending-updates flag enabled', function () {
beforeEach(async function (ctx) {
ctx.SplitTestHandler.promises.featureFlagEnabledForUser.resolves(true)
await new Promise(resolve => {
ctx.res.callback = resolve
ctx.EditorHttpController.joinProject(ctx.req, ctx.res)
})
})
it('should fetch the assignment for the joining user', function (ctx) {
expect(
ctx.SplitTestHandler.promises.featureFlagEnabledForUser
).to.have.been.calledWith(
ctx.user._id.toString(),
'per-project-pending-updates'
)
})
it('should return usePerProjectPendingUpdates=true', function (ctx) {
expect(ctx.res.json).toHaveBeenCalledWith(
expect.objectContaining({ usePerProjectPendingUpdates: true })
)
})
})
describe('when project is not found', function () {
beforeEach(async function (ctx) {
ctx.ProjectGetter.promises.getProject.resolves(null)