fix(analytics): record link-configured UTMs on click events (SIT-382) - #43
Merged
Merged
Conversation
click_events.utm_* was only ever populated from the inbound query string on the short-link request. UTMs configured in link settings are appended to the destination URL by buildRedirectUrl, so they reached the landing page but never the click row — campaign analytics read NULL for every link-tagged click. Add resolveClickUtms(), which resolves each of source/medium/campaign independently: an inbound query-string value wins, and the link's configured value fills anything left blank. Inbound takes precedence because a value a sharer put on the short URL is more specific than static link configuration (an ad platform appending a per-ad campaign id, for example), and letting configuration overwrite it would destroy the finer signal. Blank and whitespace-only values on either side resolve to undefined rather than '', since campaign analytics filters on IS NOT NULL AND <> '' but groups on the raw value. Values are stored trimmed so " ig" and "ig" do not split into two UTM library entries. A repeated query parameter arriving as an array falls through to configuration instead of being handed to a VARCHAR column. The destination URL and lf_click construction are untouched.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SIT-382
The problem
click_events.utm_*was only ever populated from the inbound query string on the short-link request. UTMs configured in link settings are appended to the destination URL bybuildRedirectUrl, so they reached the landing page but never the click row. Every campaign analytics surface readNULLfor a link-tagged click.Verified against production: a pro-tier workspace had 0 of 3,111 clicks carrying a campaign, all-time, while 3,758 of its web events carried one.
The change
resolveClickUtms()resolvessource/medium/campaignindependently — inbound query-string value wins, link configuration fills anything left blank.Inbound takes precedence because a value a sharer put on the short URL is more specific than static link configuration. Production has one workspace running Meta ads into a link whose configured campaign is the placeholder
test; letting configuration win would replace their real per-ad campaign IDs withtest.Blank and whitespace-only values resolve to
undefinedrather than'', since campaign analytics filters onIS NOT NULL AND <> ''but groups on the raw value. Values are stored trimmed so" ig"and"ig"don't split into two UTM library entries. A repeated query parameter arriving as an array falls through to configuration instead of being handed to aVARCHARcolumn.Blast radius
Strictly additive — no backfill, so no historical number changes. The write is inside
setImmediateafter the response, so redirect latency is untouched.redirect.tschanges are two hunks (the import and the resolution block). Nothing within 90 lines ofbuildRedirectUrl(467, 606) orlf_click(629) — the destination URL and click-id paths are provably untouched.Verification
npx tsc --noEmitcleanredirect.safety.test.tsnpm run buildcleanutils.test.tscovering precedence, per-key mixing, blanks, whitespace, non-objectutm_parameters, and array query paramsNote for whoever deploys
Campaign data will appear to start from zero on deploy day for workspaces that tag links. It's new data, not a spike — worth a changelog line so nobody trends across the discontinuity.