Allow changing the page of a SCHEDULED experiment that has not started
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Description
Raised by @erickgonzalez while reviewing the spec for #37176 (PR #37180):
I think we should be able to change/update an experiment which is Scheduled but haven't started?
#37176 allows changing the page of an experiment only while it is DRAFT and the control is its only variant. SCHEDULED was deliberately left out, and this issue is about lifting that restriction safely.
Why it was excluded. Starting an experiment validates that its page is free of overlapping experiments — validateNoConflictsWithScheduledExperiments, plus the running-experiment-on-page check in ExperimentsAPIImpl.start() — and it validates that against the page the experiment had at that moment. A SCHEDULED experiment is therefore a promise that has already been validated, and it is committed to starting on its own via the quartz job.
The blocker is that nothing re-examines that promise. When the start time arrives, ExperimentsAPIImpl.startScheduled() (~L793) re-validates only three things:
- the license,
- the user's page-edit permission,
- that the status is still
SCHEDULED.
It does not re-check hasAtLeastOneVariant, does not re-check that a goal is set, and does not re-check page schedule conflicts. It goes straight to save(... RUNNING) and innerStart.
That gap is already reachable today, with no page change involved. deleteVariant does not restrict by status, so an editor can delete every non-control variant from a SCHEDULED experiment and it will still fire — becoming a RUNNING A/B test with nothing to test. As @freddyDOTCMS put it in the same thread:
we need to make sure that the Experiment is valid to be running after the changes […] if the user remove all the variants to be allow to change the page, then this Experiment can be in the scheduled state before it has new variants in it
So this issue carries two parts: harden startScheduled() first, then allow the page change on top of it.
Design note — why the two parts cannot be separated in practice
start() requires hasAtLeastOneVariant, which is really variants().size() > 1 (ExperimentsAPIImpl L1577) — the control plus at least one real variant. The page-change eligibility rule from #37176 requires the opposite: exactly one variant, the control.
Those two conditions cannot both hold for an experiment that was scheduled normally. The only SCHEDULED experiment eligible for a page change is one whose variants were deleted after it was scheduled — i.e. one that is already in the invalid state described above.
That is what makes the hardening a prerequisite rather than a nice-to-have, and it is also the shape of the real user flow this enables:
- An editor schedules an experiment, then realises the page is wrong.
- They delete its variants (which is what unlocks the page change).
- They change the page.
- They add variants again.
The experiment stays SCHEDULED throughout, and fires correctly. Step 2 is exactly the window in which the experiment is invalid, and the fire-time re-validation is the safety net for the case where the job fires mid-flow.
Acceptance Criteria
Harden the scheduled start (prerequisite)
-
startScheduled()re-validates, at fire time, that the experiment still has at least one non-control variant. - It re-validates that a goal is still set.
- It re-validates that the experiment's page is still free of overlapping scheduled or running experiments.
- When any of those fail, the experiment is returned to
DRAFTinstead of starting, and the reason is logged viaLogger. - An experiment sent back to
DRAFTthis way keeps its data intact so the editor can correct it and schedule it again. - A
SCHEDULEDexperiment that is still valid starts exactly as it does today — no behavior change on the happy path.
Allow the page change on a scheduled experiment
-
patchExperimentaccepts a differingpageIdwhen the status isSCHEDULEDand the control is the only variant, under the same rule #37176 applies toDRAFT. - For
SCHEDULEDonly, the page change does validate schedule conflicts on the target page and is refused with HTTP 400 when the new page has an overlapping scheduled or running experiment. This is a deliberate exception to #37176's FR-007, which defers conflict validation tostart(); a scheduled experiment has already passed that gate, so the check cannot be deferred. - A
pageIdequal to the stored one remains a no-op forSCHEDULED, as it is for every other status. - On a successful change, the control variant's
urlis regenerated from the new page, as in #37176. -
RUNNING,ENDEDandARCHIVEDcontinue to refuse the change with HTTP 400.
Frontend
- On a
SCHEDULEDexperiment whose only variant is the control, the page Select control is enabled. - On a
SCHEDULEDexperiment with at least one non-control variant, Select is disabled with the variants reason, matching theDRAFTbehavior from #37176. - A refused change surfaces the server error through
DotHttpErrorManagerServiceand does not leave the rejected page showing as selected.
Tests
- Integration: a
SCHEDULEDexperiment whose variants were deleted is returned toDRAFTat fire time rather than starting. - Integration: a
SCHEDULEDexperiment whose page now conflicts is returned toDRAFTat fire time. - Integration: a valid
SCHEDULEDexperiment still starts normally. - Integration: PATCH
pageIdon aSCHEDULEDexperiment with only the control succeeds and regenerates the control'surl. - Integration: PATCH
pageIdon aSCHEDULEDexperiment whose target page has an overlapping experiment returns 400. - Unit: the page card's enabled / disabled / tooltip matrix for
SCHEDULED.
Priority
Medium
Additional Context
Related: #37176 (the DRAFT case, which this extends) · epic #36763 · discussion in PR #37180.
Out of scope: changing the page of a RUNNING, ENDED or ARCHIVED experiment; migrating variant content between pages; any user-facing notification beyond logging when an experiment is returned to DRAFT at fire time.
Open question for the implementer: whether returning an experiment to DRAFT should also surface in the UI beyond the status change itself (a toast on next load, an entry in a history panel). Logging is the assumed minimum here.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with ExperimentsAPIImpl.startScheduled() and the existing start() validation, then trace patchExperiment and deleteVariant for scheduled-state rules. Review the page card and DotHttpErrorManagerService for the frontend behavior, and locate the existing experiment integration and unit tests. Done means fire-time invalid experiments return to DRAFT, valid ones still start, scheduled page changes obey the stated conflict rules, and the test matrix passes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- api, backend, frontend, testing
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100