From 9fe9ee5b5c8b1eb71c08a12e60907a1bccd5bc4f Mon Sep 17 00:00:00 2001 From: Ernesto Tagwerker Date: Fri, 22 May 2026 12:01:14 -0400 Subject: [PATCH] Expand UTM allowlists for paid LinkedIn + email campaigns Adds the values needed to track three classes of campaigns that the marketing stack runs today but couldn't accurately attribute through Librarian: - Paid LinkedIn: PaidSocial, CPC mediums; LinkedIn-TLA, LinkedIn-Roadmap-Test campaigns. Unblocks an in-flight Thought Leader Ad test promoting the Roadmap service. - HubSpot broadcasts: HubSpot source; Email medium; Rails-Upgrade-Guide and Re-engagement campaigns. Today these are tagged Organic which lumps them under organic social traffic in GA4. - ConvertKit newsletter: Newsletter medium and Newsletter campaign so recurring sends are separable from one-off broadcasts. A note on naming: existing campaign values are single-word PascalCase (Upgraderuby, Blogpromo). The new values are hyphenated (LinkedIn-TLA, Rails-Upgrade-Guide, Re-engagement) because most new campaigns will be multi- word and hyphens read better in analytics dashboards. Happy to switch them to the existing single-word style if you'd prefer (LinkedinTla, RailsUpgradeGuide, Reengagement). Tests follow the same pattern as the Bluesky utm_source case added in #37. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/models/share.rb | 12 +++++- spec/models/share_spec.rb | 80 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/app/models/share.rb b/app/models/share.rb index 8c1a233..4bfc4f7 100644 --- a/app/models/share.rb +++ b/app/models/share.rb @@ -7,6 +7,7 @@ class Share < ApplicationRecord ConvertKit Facebook Google + HubSpot LinkedIn Lobsters Mastodon @@ -19,17 +20,26 @@ class Share < ApplicationRecord ].sort! UTM_MEDIUMS = %w[ + CPC + Email + Newsletter Organic PaidPlacement + PaidSocial PPC ].sort! UTM_CAMPAIGN = %w[ + Blogpromo + LinkedIn-Roadmap-Test + LinkedIn-TLA + Newsletter + Rails-Upgrade-Guide + Re-engagement Recruitment Upgraderuby Upgradejs Upgradenodejs - Blogpromo ].sort! UTM_CONTENT = %w[ diff --git a/spec/models/share_spec.rb b/spec/models/share_spec.rb index 40639c2..8d3c2dc 100644 --- a/spec/models/share_spec.rb +++ b/spec/models/share_spec.rb @@ -32,6 +32,14 @@ end end + context "on create with HubSpot as utm_source" do + it "is valid" do + share = build(:share, utm_source: "HubSpot") + + expect(share.valid?(:create)).to be_truthy + end + end + context "on create with an unrecognized utm_medium" do it "is not valid" do share = build(:share, utm_medium: "community") @@ -41,6 +49,38 @@ end end + context "on create with PaidSocial as utm_medium" do + it "is valid" do + share = build(:share, utm_medium: "PaidSocial") + + expect(share.valid?(:create)).to be_truthy + end + end + + context "on create with CPC as utm_medium" do + it "is valid" do + share = build(:share, utm_medium: "CPC") + + expect(share.valid?(:create)).to be_truthy + end + end + + context "on create with Email as utm_medium" do + it "is valid" do + share = build(:share, utm_medium: "Email") + + expect(share.valid?(:create)).to be_truthy + end + end + + context "on create with Newsletter as utm_medium" do + it "is valid" do + share = build(:share, utm_medium: "Newsletter") + + expect(share.valid?(:create)).to be_truthy + end + end + context "on create with an unrecognized utm_campaign" do it "is not valid" do share = build(:share, utm_campaign: "campaignOne") @@ -50,6 +90,46 @@ end end + context "on create with LinkedIn-TLA as utm_campaign" do + it "is valid" do + share = build(:share, utm_campaign: "LinkedIn-TLA") + + expect(share.valid?(:create)).to be_truthy + end + end + + context "on create with LinkedIn-Roadmap-Test as utm_campaign" do + it "is valid" do + share = build(:share, utm_campaign: "LinkedIn-Roadmap-Test") + + expect(share.valid?(:create)).to be_truthy + end + end + + context "on create with Rails-Upgrade-Guide as utm_campaign" do + it "is valid" do + share = build(:share, utm_campaign: "Rails-Upgrade-Guide") + + expect(share.valid?(:create)).to be_truthy + end + end + + context "on create with Newsletter as utm_campaign" do + it "is valid" do + share = build(:share, utm_campaign: "Newsletter") + + expect(share.valid?(:create)).to be_truthy + end + end + + context "on create with Re-engagement as utm_campaign" do + it "is valid" do + share = build(:share, utm_campaign: "Re-engagement") + + expect(share.valid?(:create)).to be_truthy + end + end + context "on create with an unrecognized utm_content" do it "is not valid" do share = build(:share, utm_content: "Custom")