fix(ai): attribute utm_term and utm_content like the rest of the UTM family - #744
Conversation
…est of the family utm_terms and utm_content reported event-level UTM values while utm_sources, utm_mediums and utm_campaigns reported session-attributed ones, so the same report family answered the same question two different ways. The cause was not the builder config: needsSessionAttribution only fires for columns in SESSION_ATTRIBUTION_FIELDS, and utm_term and utm_content were never in that list, so setting the plugin on them was a silent no-op. Adding both to the list costs existing attributed queries nothing. Measured on the busiest client, 8 argMins vs 10 read an identical 1,459,682 rows and 144.1 MiB, because ClickHouse prunes aggregates the outer query never references. The two reports themselves gain the attribution join, measured at 13ms to 27ms, and their numbers rise: +3.9% pageviews and +0.2% visitors on the highest-volume utm_content client, since pageviews after the landing one now count toward the value that acquired the session. Also drops IS NOT NULL from the generated where clause. All five columns are Nullable, but NULL != '' is already NULL and discarded by WHERE: across 1,283,945 null rows the two predicates return identical counts (11,881 and 6,910). With both differences gone utmDimension has no behavioural flags left.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR makes
Confidence Score: 4/5The PR is not safe to merge until nullable UTM values are made to preserve the first event rather than allowing a later event to acquire the session retroactively. The new fields flow through plain Files Needing Attention: packages/ai/src/query/expressions.ts Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Landing event<br/>utm_term = NULL] --> B[Later event<br/>utm_term = paid-keyword]
A --> C[argMin nullable field by time]
B --> C
C --> D[NULL argument skipped]
D --> E[paid-keyword selected]
E --> F[Entire session retroactively attributed]
Reviews (1): Last reviewed commit: "fix(ai): attribute utm_term and utm_cont..." | Re-trigger Greptile |
| "utm_term", | ||
| "utm_content", |
There was a problem hiding this comment.
When a landing event has no utm_term or utm_content but a later event does, the new fields pass through argMin(field, time). Because these fields are nullable, ClickHouse skips the landing row's null argument and selects the later value. This retroactively credits every pageview in the session to a value that did not acquire it, contrary to the existing first-touch behavior. Preserve the first event by coalescing nullable values before argMin, as the revenue attribution path does.
There was a problem hiding this comment.
No issues found across 2 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant Report as Traffic Reports (utm_terms, utm_content)
participant Builder as utmDimension Builder
participant CTE as session_attribution CTE
participant Expression as SESSION_ATTRIBUTION_FIELDS
participant DB as ClickHouse Database
Note over Report,DB: UTM Term and Content Attribution Flow
Report->>Builder: Get report config for utm_term/utm_content
Builder->>Builder: Build query with sessionAttribution plugin enabled
Builder->>Builder: Set WHERE clause (column != '' AND event_name = 'screen_view')
Builder->>CTE: Request session-attributed UTM values
CTE->>Expression: Check if utm_term/utm_content need attribution
Expression-->>CTE: Include both fields in attribution set
CTE->>CTE: Apply 10 argMin aggregations (was 8)
CTE->>DB: Execute attribution query
alt Session attribution join
DB-->>CTE: Session-attributed UTM term/content values
CTE-->>Builder: Return attributed dimensions
Builder-->>Report: Pageviews + visitors (slightly higher)
else Event-level fallback
DB-->>CTE: Event-level UTM values only
CTE-->>Builder: Return event-scoped dimensions
Builder-->>Report: Pageviews + visitors (original counts)
end
Note over CTE,DB: NULL and '' values filtered by WHERE clause
Note over Builder: All five UTM dimensions now use same attribution path
Shadow auto-approve: would require human review. Aligns utm_terms/utm_content with session attribution, and this metric-affecting change alters customer-visible metrics and requires human sign-off.
Re-trigger cubic
Follow-up to #743, which surfaced this.
utm_termsandutm_contentreported event-level UTM values whileutm_sources,utm_mediumsandutm_campaignsreported session-attributed ones. The same report family answered the same question two different ways.Root cause
Not the builder config.
needsSessionAttribution()only fires for columns listed inSESSION_ATTRIBUTION_FIELDS, andutm_term/utm_contentwere never in it — so setting the plugin on those builders was a silent no-op. I confirmed that by setting it and watching the generated SQL not change.Cost to existing queries: zero
Adding both columns widens the shared
session_attributionCTE from 8argMins to 10, which touches every attributed query. Measured on the busiest client:Byte-identical. ClickHouse prunes aggregates the outer query never references, which is the same behaviour that made an earlier column-pruning attempt a no-op.
What does change
The two reports gain the attribution join, 13ms → 27ms, and their numbers rise: +3.9% pageviews, +0.2% visitors on the highest-volume
utm_contentclient. Pageviews after the landing one now count toward the value that acquired the session, which is what the other three already did.Flagging that plainly: these two reports will show slightly higher pageviews after this ships. That is the intended correction, not a regression.
Also
Drops
IS NOT NULLfrom the generatedwhere. All five columns areNullable(String), butNULL != ''isNULLand already discarded byWHERE— across 1,283,945 null rows both predicates return identical counts (11,881 and 6,910).With both differences gone,
utmDimensionhas no behavioural flags left; every call site is purely descriptive.653 tests pass, typecheck and lint clean.
Summary by cubic
Fixes
utm_termsandutm_contentreports so they attribute UTM values to the session like the rest of the UTM family. Previously these two reported event-level values whileutm_sources,utm_mediums, andutm_campaignsused session attribution.Side effects
utm_termsandutm_contentpageviews and visitors will rise slightly after this ships; this is the intended correction.Cleanup
IS NOT NULLfilter from the generatedwhereclause; results are identical.Written for commit 22f4aaa. Summary will update on new commits.