dotCMS / dotCMS/core

Allow changing the page of a SCHEDULED experiment that has not started

Open
#37,214 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

dotCMS : Experiments Team : Falcon Type : Task
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:

  1. An editor schedules an experiment, then realises the page is wrong.
  2. They delete its variants (which is what unlocks the page change).
  3. They change the page.
  4. 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 DRAFT instead of starting, and the reason is logged via Logger.
  • An experiment sent back to DRAFT this way keeps its data intact so the editor can correct it and schedule it again.
  • A SCHEDULED experiment 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

  • patchExperiment accepts a differing pageId when the status is SCHEDULED and the control is the only variant, under the same rule #37176 applies to DRAFT.
  • For SCHEDULED only, 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 to start(); a scheduled experiment has already passed that gate, so the check cannot be deferred.
  • A pageId equal to the stored one remains a no-op for SCHEDULED, as it is for every other status.
  • On a successful change, the control variant's url is regenerated from the new page, as in #37176.
  • RUNNING, ENDED and ARCHIVED continue to refuse the change with HTTP 400.

Frontend

  • On a SCHEDULED experiment whose only variant is the control, the page Select control is enabled.
  • On a SCHEDULED experiment with at least one non-control variant, Select is disabled with the variants reason, matching the DRAFT behavior from #37176.
  • A refused change surfaces the server error through DotHttpErrorManagerService and does not leave the rejected page showing as selected.

Tests

  • Integration: a SCHEDULED experiment whose variants were deleted is returned to DRAFT at fire time rather than starting.
  • Integration: a SCHEDULED experiment whose page now conflicts is returned to DRAFT at fire time.
  • Integration: a valid SCHEDULED experiment still starts normally.
  • Integration: PATCH pageId on a SCHEDULED experiment with only the control succeeds and regenerates the control's url.
  • Integration: PATCH pageId on a SCHEDULED experiment 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.