Skip to content

Experiments API gaps for the standalone portlet: archive/restore, PATCH side effects, goal operators, variant cap #36988

Description

@oidacra

Description

The new Experiments portlet (#36987) turns /experiments/new into a routed, autosaving Configure screen and adds list actions the current backend cannot support. Four API gaps stand in the way — all other mutations already exist (create, PATCH, start, end, cancel, archive, delete, variants, promote, results).

1. Archive from DRAFT + restore/unarchive

Today archive is only allowed from ENDED, and no unarchive/restore method exists at all. The approved design archives from DRAFT and ENDED, and restores an ARCHIVED experiment back to DRAFT. Add both transitions to ExperimentsAPIImpl + ExperimentsResource.

Frontend gating note: the matrix AllowedActionsByExperimentStatus (core-web/libs/dotcms-models/src/lib/dot-experiments-constants.ts:256-287) is read by the existing UVE list menu. Do not widen the existing archive: [ENDED] key — the new transitions get new keys (archiveDraft, restore) so the old UI's menu cannot change.

2. PATCH side effects (autosave blocker)

Every PATCH /api/v1/experiments/{id} currently overwrites targetingConditions with []: ExperimentForm defaults it to a non-null empty list, so the resource's != null guard always fires, creating an empty Rule + ConditionGroup on every call. The new screen autosaves on edit, so this would create one Rule per keystroke pause. Fix so an absent targetingConditions leaves the stored value untouched.

Related, document-only: trafficAllocation can never be PATCHed to 0 (if (getTrafficAllocation() > 0)) — harmless, the design's minimum is 1.

3. Goal operators: validate, then extend

The Operator enum has only EQUALS / CONTAINS / EXISTS, and operators are never validated: an unsupported operator is silently accepted and then silently ignored, producing a goal that never converts with no error anywhere. Validate on write (400 on unsupported). Then extend with the operators the design offers — STARTS_WITH, ENDS_WITH, REGEX — wired through the metric/query layer so they actually evaluate. If extending is too large for this issue, validation+reject still lands here (it is the bug) and the new operators split out; the UI then trims to the supported set.

4. Variant cap enforcement

VARIANTS_NUMBER_MAX = 3 (ExperimentsAPIImpl.java:105) is only enforced in calcBayesian (:1344) — not in addVariant (:964). A user can build 5 variants, run for two weeks, and discover the failure when opening Results. Enforce the cap in addVariant with a clear error. Open question: the design says "Up to 5 Variants" — raising the cap requires validating the Bayesian implementation for 5 arms; decide before implementing (the UI ships with whatever cap this issue lands).

Acceptance Criteria

  • Archive succeeds from DRAFT and ENDED.
  • A restore/unarchive endpoint moves ARCHIVEDDRAFT.
  • AllowedActionsByExperimentStatus gains archiveDraft and restore keys; existing keys byte-identical.
  • A PATCH without targetingConditions does not create a Rule/ConditionGroup and leaves stored targeting untouched (integration test proving no new Rule rows).
  • An unsupported goal operator is rejected with a 400 (today: silently ignored).
  • addVariant rejects the variant that would exceed VARIANTS_NUMBER_MAX.
  • Integration tests for all of the above; openapi.yaml regenerated (./mvnw compile -pl :dotcms-core -DskipTests) and committed with the annotation changes.

Priority

High — the PATCH fix blocks the Create/Update screen of #36987 (autosave is the creation mechanism).

Additional Context

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Status
    Next Sprint

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions