Experiments: A/B Testing v2
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Summary
Rebuild dotCMS A/B testing on the new analytics platform. Four parts, one epic:
- Migrate Experiments to the new analytics infrastructure — move goal evaluation off CubeJS onto CAEM + ClickHouse
- Build the new Experiments UI from scratch — per the approved design below
- Make the SDK and the traditional page experience work — one analytics SDK for both rendering modes
- Update the docs — rewritten against what ships
These ship together. The migration alone leaves a feature that is hard to operate; a UI rebuild alone has no working data source; SDK work alone fixes delivery for experiments that still cannot be measured or managed; docs written against current behavior go stale immediately. One acceptance bar: a user can run an experiment end to end and trust the result.
Design
The prototype is the spec for Part 2. It is interactive and covers all four screens — experiments list, configuration, UVE variant editor, results — including empty states, validation, and the configuration-lock behavior. Use the startScreen control to move between screens and lockRunningConfig to see the lock.
Two things it settles, rather than repeating them as a task list here:
- Configuration locking is one rule. When status is
RUNNING,ENDED, orARCHIVED, goal, variants, traffic, and scheduling lock as a set. Running is locked until stopped; ended and archived are read-only. - Four goal types, no more. Bounce rate, exit rate, reach page, URL parameter. Reach-page and URL-parameter reveal a conditions block. The "coming soon" placeholders get deleted, not built.
This is a redesign of existing capability, not new behavior. Variant promotion, archive/restore, the page picker, weight-split editing, and the Bayesian result values all exist today. The work is rebuilding the surface, not inventing what sits behind it. The one genuinely new screen is the cross-page experiments list — today entry is per-page only (/:pageId), so there is no way to see which pages have experiments in draft, scheduled, running, or finished states.
Background
Experiments evaluates every goal by querying CubeJS, which is being retired in favor of the CAEM + ClickHouse pipeline delivered by #35048. This is a dependency deadline, not a product preference: when CubeJS is removed, experiment reporting stops working.
Three other problems compound:
- Reporting is slow or fails. Reports for in-progress and completed experiments can take a long time to load, and in some environments do not load at all.
- Delivery is split across two unrelated implementations. Traditional pages get experiment code injected server-side against the legacy Jitsu collector (
ExperimentWebAPIImpl.getCode,experiment/js/init_script.js). Headless front ends use@dotcms/experiments, which still carries@jitsu/sdk-jsas its only runtime dependency. The two share no code and neither emits events into the new analytics pipeline.@dotcms/analytics, which does talk to the new pipeline, has no concept of experiment context. - Traditional variant delivery is rough. It halts page render, calls
POST /v1/experiments/isUserIncluded, then redirects and reloads with avariantNamequery parameter — visible flicker, and it requires JavaScript.
Full implementation plan (schema DDL, endpoint contracts, per-track sequencing, production migration steps) is the output of spike #36195.
Architecture
- Event context — when an experiment is running, analytics events carry a nested
experimentblock (experiment id, running id, assigned variant) in the event context, shared across a batch. - Storage — new
experiment_id/running_id/variantcolumns onanalytics.eventsand the session tables, a bloom-filter skip index onexperiment_id, and updated materialized views. Empty string is the sentinel for "no experiment active". - Query — session-level goals (bounce, exit) are served from the existing session summary via
GET /v1/analytics/sessions, extended with new metrics and avariantdimension. Goals requiring raw event ordering (reach-target, URL parameter) are served by a newGET /v1/analytics/sessions/behavior, always scoped by a strong experiment filter to bound the scan. - dotCMS core — the goal-result classes (
BounceRateResultQuery,ExitRateResultQuery,ReachTargetAfterExperimentPageResultQuery, plus a newUrlParameterResultQuery) call the analytics endpoints instead of building CubeJS queries. dotCMS holds all experiment configuration and passes it as query params; the analytics service stores no goal definitions. The result model (ExperimentResults,GoalResults,VariantResults) is unchanged. - SDKs —
@dotcms/analyticsgains an experiment-context API.@dotcms/experimentsnarrows to variant assignment plus setting that context, which removes the Jitsu dependency and the second set of analytics credentials. Consumers configure analytics once.
Acceptance criteria
Part 1 — Migration
- All four goal types return results from the new pipeline, matching CubeJS output within an agreed tolerance
-
UrlParameterResultQueryexists — URL-parameter goals currently reuse the reach-target query - No Experiments code path depends on CubeJS
- Production ClickHouse migration applied, sequenced last, with post-migration verification
- Decided and documented whether existing experiment data migrates from the old pipeline
Part 2 — UI
- All four screens match the approved prototype, including empty states and validation
- A cross-page experiment listing exists, with search and status filtering
- Configuration locks once an experiment is running, ended, or archived
- Reports load reliably at realistic data volume; the SLO is set from the analytics query API's measured performance
Part 3 — SDK and traditional pages
- No Experiments code path depends on Jitsu;
@dotcms/analyticsis the only analytics SDK required, with one set of credentials - The same experiment behaves identically on a dotCMS-rendered page and a headless front end
- Variant assignment and rendering verified in the reference Next.js app
- Traditional variant delivery no longer halts page render
Part 4 — Docs
- Headless integration guide with a working reference example. Traditional pages auto-inject experiment code; headless requires explicit SDK wiring, and the current docs do not make that clear enough
- Traditional-page setup guide, goal-type reference, and the configuration-locking rules documented so intended behavior is not reported as a defect
- Published
@dotcms/experimentsSDK docs refreshed (stale — see #36225) - Upgrade note covering what changes for existing experiments
Out of scope
- AI-generated page variants and AI-recommended segments — these belong to the dotAI area and must not gate this epic
- Multi-armed bandit / automatic traffic reallocation; traffic split stays manual
- Multi-page and funnel experiments; single-page experiments only
- Multi-touch attribution models (see #33553)
- Changes to the personalization rules engine; targeting continues to use dotCMS Rules
- Statistical engine rework — the Bayesian values shown in the new results UI are already computed today
Dependencies
- Blocks the query-layer work in Part 1: the Analytics Query REST API domain-driven redesign (#35736, first slice in
dot-ca-event-manager), because the experiment endpoints use the response envelope it introduces. Schema, ingest, SDK, and the core result-class migration do not depend on it and can start independently. - #35048 — the CAEM + ClickHouse pipeline this builds on (closed)
- The Analytics app must be configured before Experiments is usable; this coupling is unchanged
Open questions
- Can outbound-link clicks be surfaced as a conversion, with per-placement attribution when the same CTA appears in multiple locations? The
content_clickpayload already captures the elementhref,id,class, attributes, containing contentlet, and DOM index — so this may be a query/reporting change rather than new collection. Needs confirmation, and a decision on whether it belongs here or in analytics conversion tracking. - Is a JavaScript-triggered goal type in scope? Neither the implementation plan nor the approved design includes one.
- Does existing experiment data migrate from the old pipeline to the new one? Spike #36195 treats this as a feasibility question; the default is not to migrate.
- Is report slowness caused by the CubeJS query path, or by query shape and data volume? If the latter, the migration alone will not fix it and Part 2 needs different acceptance criteria.
References
- Approved UI prototype: https://claude.ai/design/p/36aed5a6-ca42-4624-b187-a465a23ce909?file=Experiments.dc.html&via=share
- Spike + full implementation plan: #36195
- Analytics infrastructure epic: #35048
- Analytics Query REST API redesign: #35736
- SDK defect (Next.js 15 App Router, stale docs): #36225
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 spike #36195 and the dependency notes for #35736, then inspect the named entry points: ExperimentWebAPIImpl, experiment/js/init_script.js, @dotcms/analytics, @dotcms/experiments, and the goal-result classes. The acceptance criteria define done across analytics migration, UI, SDK and traditional-page delivery, and documentation, so this is an epic rather than a self-contained first contribution.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- clickhouse, java, next.js
- Domain
- analytics, backend, databases, documentation, frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 20/100