spec(experiments): CAEM-backed experiment goal result queries - #37287
Conversation
Introduces spec.md for issue #37227 — CAEM-backed result query implementations for bounce rate, exit rate, reach-target, and URL-param goals, alongside the existing CubeJS classes with a runtime config switch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @freddyDOTCMS's task in 1m 43s —— View job Spec Review — CAEM-backed experiment goal result queries
This PR carries New Issues
Notes (non-blocking)
Overall: solid spec, no blocking issues. The two design-seam findings above are the main things to reconcile before planning, since they change where the code lands. |
erickgonzalez
left a comment
There was a problem hiding this comment.
Verdict
REQUEST CHANGES — 5 blockers.
The spec assumes a switch seam that the current interface cannot support, declares unit tests only with no justification for the other types, and leaves both the config key and the authorization of the new path undefined.
Blockers
B1 — [CRITICAL] Unit tests only, other types not justified
- FR-015/016/017 and SC-004 declare unit tests exclusively. Principle V requires the layer-appropriate type(s), or an explicit statement from the developer as to why one cannot be implemented ("silence is not consent"). There is live coverage this change touches:
dotcms-integration/src/test/java/com/dotcms/experiments/business/ExperimentResultsQueryFactoryIntegrationTest.java:93asserts exactly the query the switch replaces. - Add FRs for integration tests (dispatch with flag on/off +
ExperimentResultsparity) and Postman against the results endpoint, or one explicit line per omitted type with its reason.
B2a — [CRITICAL] The existing interface returns CubeJSQuery
- Key Entities states the CAEM classes "implement this same interface", but
dotCMS/src/main/java/com/dotcms/experiments/business/result/MetricExperimentResultsQuery.java:18declaresCubeJSQuery getCubeJSQuery(Experiment). A CAEM implementation cannot return aCubeJSQuery. - Define the real seam (a provider-agnostic abstraction) and correct FR-001/FR-010 and Key Entities accordingly.
B2b — [CRITICAL] The seam does not fit in the factory, and call sites do change
- Legacy Considerations claims the
ExperimentsAPIImplcall sites stay "unchanged", butExperimentsAPIImpl.java:1429-1440returnCubeJSResultSetviaCubeJSClient, and the mapping toVariantResults/GoalResultsthat FR-013 attributes to the query classes actually happens inExperimentsAPIImpl.java:1324-1345, readingEvents.*keys. - Rewrite FR-008/FR-013 and Legacy Considerations to place the switch where
ExperimentResultsis produced, not where the query is built.
B3 — [HIGH] Configuration key undefined
- FR-008 and Key Entities say "a dotCMS
Configproperty" without naming the key; the PR body namesDOT_CAEM_ENABLED. The convention in this module is a constant indotCMS/src/main/java/com/dotcms/featureflag/FeatureFlagName.java:10read viaConfig.getBooleanProperty(ConfigExperimentUtil.java:113). Also, SC-003 (toggle without restart) conflicts with thestatic Lazythat caches the dispatch map inExperimentResultsQueryFactory.java:64. - Name the key and its default (
false), and require in an FR that it be read per evaluation or viaSystemTableUpdatedKeyEventas inConfigExperimentUtil.java:79-85.
B4 — [HIGH] Authorization and credential scope not expressed
- FR-012 says it reuses the
EventAnalyticsProxyHelpertoken, butbuildAuthHeader(Host)is package-private and site-scoped (EventAnalyticsProxyHelper.java:275), and with no token the request goes out without anAuthorizationheader (lines 283-291). The CubeJS path is user+site-scoped viaresolveAnalyticsApp(user)->getCurrentHost()(AnalyticsHelper.java:466); no FR carriesuser/site into the CAEM path. - Add FRs: preserve the same authorization as the CubeJS path, fail (do not call) when no token is available, and never log the token or a URL containing it.
B5 — [HIGH] SC-007 asserts behavior that does not exist today
- SC-007 says
CLICK_ON_ELEMENT"continues to use the CubeJS path", but the map inExperimentResultsQueryFactory.java:69-74has no entry for that type, sogetMetricCubeJSQuery(lines 163-165) NPEs today. The criterion is unverifiable as written. - Reword SC-007 as "no change to current behavior for unsupported types", or take it out of scope.
Questions for the author
Q1 — Where does the seam live: at the query-builder level, or an ExperimentResultsProvider that returns a complete ExperimentResults? Today the factory produces a query and the parsing lives in ExperimentsAPIImpl.getResults. My recommendation: a provider at the getResults level, leaving MetricExperimentResultsQuery and the three CubeJS classes untouched. Answering this resolves B2a and B2b, and rewrites FR-010/FR-013.
Q2 — Which Host resolves the CAEM base URL and token, given that getResults(experiment, user) has no request context? CubeJS uses getCurrentHost(), which does not exist in the experiment-finalization job (ExperimentsAPIImpl.java:1443). Suggested: derive the Host from the experiment's page and pass it explicitly. Resolves B4.
Q3 — Is the daily series in scope? getSummary uses createWithDayGranularity with timeDimension("Events.day") (ExperimentResultsQueryFactory.java:90-92) and the summarize loop consumes it. No FR mentions it: if CAEM does not return day granularity, the shape of ExperimentResults changes. Resolves part of B2b.
Q4 — Does URL_PARAMETER use paramName/paramValue (FR-006), or the single QUERY_PARAMETER with a regex parameter name declared in MetricType.java:41-45? Today that type reuses ReachTargetAfterExperimentPageResultQuery (ExperimentResultsQueryFactory.java:73). This determines the parameters sent to CAEM.
Recommended next steps
- Run
/speckit-clarifyfor Q1-Q4 before anything else; do not plan yet — Q1 and Q3 determine the abstraction and the shape ofExperimentResults, so a plan written first gets redone in full. - Rewrite FR-001, FR-008, FR-010, FR-013 and Key Entities (
MetricExperimentResultsQuery,ExperimentResultsQueryFactory) per the answer to Q1 — resolves B2a and B2b. - Name the config key, its default, and its re-read semantics in FR-008; add the proposed constant to
FeatureFlagName— resolves B3. - Add security FRs: authorization equivalent to the CubeJS path, explicit failure with no token, and a prohibition on logging the token — resolves B4.
- Add integration and Postman FRs (or the explicit per-type justification) and reword SC-004 — resolves B1; reword SC-007 — resolves B5.
- Convert the Edge Cases section from interrogative prose into Given/When/Then scenarios inside the story that owns each case; move the HTTP client choice and the response-envelope parsing detail to
plan.md. - Reorder US5 to ship first (US1-US4 are not independently testable without the flag), and move the physical-design claims in Legacy Considerations down to
plan.md. - Chain
/speckit-clarify-> spec re-review ->/speckit-plan->/speckit-tasks->/speckit-analyze, and only then/speckit-implement.
Important
Design Artifacts: View Spec & Implementation Details
Summary
spec.mdfor issue Add CAEM-backed experiment result query classes with legacy CubeJS fallback switch #37227 — CAEM-backed result query implementations (bounce rate, exit rate, reach-target, URL-param) that call the CAEM analytics API instead of CubeJSDOT_CAEM_ENABLED) selects between them with CubeJS as the safe defaultCloses #37227
Depends on: #37223 (CAEM sessions endpoint), #37224 (CAEM sessions behavior endpoint)
Review checklist
Test plan
spec.mdonly — no implementation code/speckit-planruns on the feature branch🤖 Generated with Claude Code