[web] add includeReferer flag to SplitTestHandler.getAssignment (#33235)

* [web] add includeReferer flag to SplitTestHandler.getAssignment

* [web] tests: migrate User.getSplitTestAssignment to async/await

I don't want to fight with callbacks and optional arguments. Just move
it to async/await. New tests should use async/await, so there is no
point in making this work in callback-hell.

* [web] remove unused URL import

GitOrigin-RevId: 6251001e6ba7354f704fa663be8ef365ca0b9d23
This commit is contained in:
Jakob Ackermann
2026-05-12 08:06:21 +00:00
committed by Copybot
parent 6b28a4ee5a
commit 1df98c028d
4 changed files with 54 additions and 51 deletions
@@ -1,4 +1,3 @@
import { URL } from 'node:url'
import { pipeline } from 'node:stream/promises'
import Metrics from '@overleaf/metrics'
import ProjectGetter from '../Project/ProjectGetter.mjs'
@@ -46,23 +45,12 @@ function getOutputFilesArchiveSpecification(projectId, userId, buildId) {
}
}
function getPdfCachingMinChunkSize(req, res) {
return Settings.pdfCachingMinChunkSize
}
async function _getSplitTestOptions(req, res) {
// Use the query flags from the editor request for overriding the split test.
let query = {}
try {
const u = new URL(req.headers.referer || req.url, Settings.siteUrl)
query = Object.fromEntries(u.searchParams.entries())
} catch (e) {}
const editorReq = { ...req, query }
const { variant } = await SplitTestHandler.promises.getAssignment(
editorReq,
req,
res,
'compile-from-history'
'compile-from-history',
{ includeReferer: true }
)
const compileFromHistory = variant === 'enabled'
@@ -78,7 +66,7 @@ async function _getSplitTestOptions(req, res) {
}
}
const pdfCachingMinChunkSize = getPdfCachingMinChunkSize(editorReq, res)
const pdfCachingMinChunkSize = Settings.pdfCachingMinChunkSize
return {
compileFromHistory,
pdfDownloadDomain,
@@ -47,11 +47,17 @@ const DEFAULT_ASSIGNMENT = {
* @param req the request
* @param res the Express response object
* @param splitTestName the unique name of the split test
* @param options {Object<sync: boolean>} - for test purposes only, to force the synchronous update of the user's profile
* @param {Object} options
* @param {boolean} options.sync - for test purposes only, to force the synchronous update of the user's profile
* @param {boolean} options.includeReferer For ajax requests and downloads include the split test overrides of the page
* @returns {Promise<Assignment>}
*/
async function getAssignment(req, res, splitTestName, { sync = false } = {}) {
const query = req.query || {}
async function getAssignment(
req,
res,
splitTestName,
{ sync = false, includeReferer = false } = {}
) {
let assignment
try {
@@ -60,6 +66,20 @@ async function getAssignment(req, res, splitTestName, { sync = false } = {}) {
} else {
await _loadSplitTestInfoInLocals(res.locals, splitTestName, req.session)
let query = req.query || {}
if (includeReferer && req.headers.referer) {
// Pick up the query of the top-level page, i.e. what's in the browsers address bar, from ajax requests.
// E.g. /project/:id?split-test=foo -> ajax /project/:id/compile should see split-test=foo.
// E.g. /project/:id?split-test=foo -> redirect /project/:id/download/zip should see split-test=foo.
try {
const u = new URL(req.headers.referer, Settings.siteUrl)
query = {
...Object.fromEntries(u.searchParams.entries()),
...query,
}
} catch {}
}
// Check the query string for an override, ignoring an invalid value
const queryVariant = query[splitTestName]
if (queryVariant) {