dotCMS / dotCMS/core

Analytics: Environment Filtering — CAEM: accept `environment` at ingest and filter on it in the query API

Open
#37,407 0 comments 0 reactions 1 assignee View on GitHub

@jcastro-dotcms is already working on this.

Since Sep 8, 2026.

dotCMS : Analytics Team : Falcon Type : New Functionality
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Summary

CAEM (Content Analytics Event Manager) accepts an optional environment parameter at ingest and honors it as a filter on the three live query endpoints — GET /v1/events, GET /v1/content, GET /v1/sessions — following the exact pattern already used for project. Omitted → today's unfiltered, blended-across-environments behavior, unchanged.

Parent epic: #37349
Spike: #37107
Depends on: the ClickHouse schema sub-task (recreates analytics.events + the 10 downstream tables with environment as a key dimension) — nothing here can write to or filter on a column that doesn't exist yet.

Background

Per ADR-0022 rule 2, both changes below are the safe, directly-shippable case: "new optional query parameter... default must be exactly what the endpoint already did before the parameter existed."

  • Ingest (POST /v1/event/ingest): today accepts tenant (from the Bearer token) and project (request param) only. Add environment, threaded the same way project already is: IngestionContextSaveEventRequestSaveEventRepository.insertChunk(). Omitted → environment = '' (today's implicit, fully-commingled behavior).
  • Query endpoints: EventsAnalyticsController, ContentAnalyticsController, SessionsAnalyticsController each get an optional environment @RequestParam, per the standard controller-validation conventions (.claude/conventions.md) — no @Size, no @Pattern (it's an open string set sourced from infra config, not a fixed enum, same reasoning as why project has no pattern constraint either). Omitted → no filter, preserving compatibility for any caller that hasn't adopted the parameter yet.

No EventAnalyticsProxyHelper (dotCMS-core) change needed for the query side — verified in the spike: it forwards every query parameter from the original request transparently (uriInfo.getQueryParameters().forEach(...)) before appending project= only if absent, and events/sessions/content are already in its ALLOWED_PATH_PREFIXES. Once Angular adds &environment=prod to a call, it reaches CAEM with zero core-proxy code changes. (The ingest side is different — the collector has to be told its own environment value to send it; that's covered by the dotCMS-core sub-task, not this one.)

The older, @Deprecated(forRemoval = true) per-metric controllers get no new work.

Scope

  • POST /v1/event/ingest: optional environment request param, same shape/threading as project, through IngestionContextSaveEventRequestSaveEventRepository.insertChunk().
  • GET /v1/events, GET /v1/content, GET /v1/sessions: optional environment @RequestParam, filtering the underlying QueryDSL predicates against the now-environment-aware tables.
  • Update the hand-written QueryDSL Q-types (QContentEventsCounter, QPageviewsByDeviceBrowserDaily, QContentPresentsInConversion, QEngagementDaily, QSessionsByDeviceDaily, QSessionsByBrowserDaily, QSessionsByLanguageDaily) to expose an environment field, mirroring how each already exposes tenant/project — moved here from #37406, which is schema-only (docker/init/*.sql). Matches this codebase's own precedent: the experiment columns added by #37016/#37017 never touched a single Q-type until a query actually needed to filter/select by them; a Q-type gains a field only when the query work that uses it lands, not when the underlying column is created.
  • Update the shared predicate builders (QueryPredicateBuilder, EventQueryPredicateBuilder, AttributionQueryPredicateBuilder, AnalyticsQueryPredicateBuilder) and AnalyticsQuery to carry environment alongside tenant/project/siteId.
  • Update .claude/conventions.md's controller-parameter table to document environment alongside the existing rows.
  • Per .claude/testing.md's Postman maintenance rule: update postman/CAEM-dotCMS-Proxy.postman_collection.json — add environment to relevant POST bodies and GET query strings, plus a negative/empty-environment case.
  • Update the IT suites per the existing checklist: EventIngestionControllerIT, EventsAnalyticsControllerIT, ContentAnalyticsControllerIT, SessionsAnalyticsControllerIT (happy path with environment set, omitted-param backward-compat case, cross-environment isolation case).

Out of scope

  • EventAnalyticsProxyHelper changes — confirmed unnecessary (see Background).
  • The deprecated per-metric controllers.
  • Authorization for cross-environment reads — explicitly deferred by the epic; CAEM auth stays tenant-scoped, nothing here restricts which environments a valid Bearer token can query.
  • ClickHouse schema/table changes (previous sub-task, #37406) — the column and its ORDER BY/PARTITION BY position are already defined there; this ticket only adds the Java-side Q-type fields and query-layer support.

Dependencies

Blocked on the ClickHouse schema sub-task landing first. The dotCMS-core sub-task (collector plumbing) and this sub-task can proceed in parallel — an unrecognized request param is silently ignored by Spring MVC, and an omitted param defaults safely on both sides, so there's no required ordering between "collector sends &environment=" and "CAEM reads it."

Acceptance Criteria

  • Ingesting an event with environment set persists it correctly on analytics.events; omitting it persists '' (unchanged from today)
  • All three query endpoints accept optional environment and return only that environment's data when set
  • All 7 Q-types (QContentEventsCounter, QPageviewsByDeviceBrowserDaily, QContentPresentsInConversion, QEngagementDaily, QSessionsByDeviceDaily, QSessionsByBrowserDaily, QSessionsByLanguageDaily) expose environment and compile
  • Omitting environment on any query endpoint reproduces today's unfiltered behavior exactly (epic acceptance criterion)
  • Cross-environment isolation verified end-to-end (ingest into two environments for the same tenant/project, query one, confirm the other's rows are excluded)
  • Postman collection updated per .claude/testing.md's maintenance rule
  • All new/changed IT classes pass under mvn verify

Next step

Run /speckit-specify against this issue to produce the formal spec before implementation.

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.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.