Move UTM parameter tracking logic into a concern

This commit is contained in:
Eugene Burmakin 2025-10-30 19:59:31 +01:00
parent 6787273713
commit 8e35b8e09f
4 changed files with 218 additions and 25 deletions

View file

@ -1 +1 @@
0.34.0
0.34.1

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
extend ActiveSupport::Concern
UTM_PARAMS = %w[utm_source utm_medium utm_campaign utm_term utm_content].freeze
def store_utm_params
UTM_PARAMS.each do |param|
session[param] = params[param] if params[param].present?
end
end
def assign_utm_params(record)
utm_data = extract_utm_data_from_session
return unless utm_data.any?
record.update_columns(utm_data)
clear_utm_session
end
private
def extract_utm_data_from_session
UTM_PARAMS.each_with_object({}) do |param, hash|
hash[param] = session[param] if session[param].present?
end
end
def clear_utm_session
UTM_PARAMS.each { |param| session.delete(param) }
end
end

View file

@ -1,9 +1,11 @@
# frozen_string_literal: true
class Users::RegistrationsController < Devise::RegistrationsController
include UtmTrackable
before_action :set_invitation, only: %i[new create]
before_action :check_registration_allowed, only: %i[new create]
before_action :store_utm_params, only: %i[new]
before_action :store_utm_params, only: %i[new], unless: -> { DawarichSettings.self_hosted? }
def new
build_resource({})
@ -67,8 +69,8 @@ class Users::RegistrationsController < Devise::RegistrationsController
def invitation_token
@invitation_token ||= params[:invitation_token] ||
params.dig(:user, :invitation_token) ||
session[:invitation_token]
params.dig(:user, :invitation_token) ||
session[:invitation_token]
end
def accept_invitation_for_user(user)
@ -82,33 +84,16 @@ class Users::RegistrationsController < Devise::RegistrationsController
if service.call
flash[:notice] = "Welcome to #{@invitation.family.name}! You're now part of the family."
else
flash[:alert] = "Account created successfully, but there was an issue accepting the invitation: #{service.error_message}"
flash[:alert] =
"Account created successfully, but there was an issue accepting the invitation: #{service.error_message}"
end
rescue StandardError => e
Rails.logger.error "Error accepting invitation during registration: #{e.message}"
flash[:alert] = "Account created successfully, but there was an issue accepting the invitation. Please try accepting it again."
flash[:alert] =
'Account created successfully, but there was an issue accepting the invitation. Please try accepting it again.'
end
def sign_up_params
super
end
def store_utm_params
utm_params = %w[utm_source utm_medium utm_campaign utm_term utm_content]
utm_params.each do |param|
session[param] = params[param] if params[param].present?
end
end
def assign_utm_params(user)
utm_params = %w[utm_source utm_medium utm_campaign utm_term utm_content]
utm_data = {}
utm_params.each do |param|
utm_data[param] = session[param] if session[param].present?
session.delete(param) # Clean up session after assignment
end
user.update_columns(utm_data) if utm_data.any?
end
end

View file

@ -325,4 +325,179 @@ RSpec.describe 'Users::Registrations', type: :request do
end
end
end
describe 'UTM Parameter Tracking' do
let(:utm_params) do
{
utm_source: 'google',
utm_medium: 'cpc',
utm_campaign: 'winter_2025',
utm_term: 'location_tracking',
utm_content: 'banner_ad'
}
end
context 'when self-hosted mode is disabled' do
before do
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
end
it 'captures UTM parameters from registration page URL' do
get new_user_registration_path, params: utm_params
expect(response).to have_http_status(:ok)
expect(session[:utm_source]).to eq('google')
expect(session[:utm_medium]).to eq('cpc')
expect(session[:utm_campaign]).to eq('winter_2025')
expect(session[:utm_term]).to eq('location_tracking')
expect(session[:utm_content]).to eq('banner_ad')
end
it 'stores UTM parameters in user record after registration' do
# Visit registration page with UTM params
get new_user_registration_path, params: utm_params
# Create account
unique_email = "utm-user-#{Time.current.to_i}@example.com"
post user_registration_path, params: {
user: {
email: unique_email,
password: 'password123',
password_confirmation: 'password123'
}
}
# Verify UTM params were saved to user
user = User.find_by(email: unique_email)
expect(user.utm_source).to eq('google')
expect(user.utm_medium).to eq('cpc')
expect(user.utm_campaign).to eq('winter_2025')
expect(user.utm_term).to eq('location_tracking')
expect(user.utm_content).to eq('banner_ad')
end
it 'clears UTM parameters from session after registration' do
# Visit registration page with UTM params
get new_user_registration_path, params: utm_params
# Create account
unique_email = "utm-cleanup-#{Time.current.to_i}@example.com"
post user_registration_path, params: {
user: {
email: unique_email,
password: 'password123',
password_confirmation: 'password123'
}
}
# Verify session was cleaned up
expect(session[:utm_source]).to be_nil
expect(session[:utm_medium]).to be_nil
expect(session[:utm_campaign]).to be_nil
expect(session[:utm_term]).to be_nil
expect(session[:utm_content]).to be_nil
end
it 'handles partial UTM parameters' do
partial_utm = { utm_source: 'twitter', utm_campaign: 'spring_promo' }
get new_user_registration_path, params: partial_utm
unique_email = "partial-utm-#{Time.current.to_i}@example.com"
post user_registration_path, params: {
user: {
email: unique_email,
password: 'password123',
password_confirmation: 'password123'
}
}
user = User.find_by(email: unique_email)
expect(user.utm_source).to eq('twitter')
expect(user.utm_campaign).to eq('spring_promo')
expect(user.utm_medium).to be_nil
expect(user.utm_term).to be_nil
expect(user.utm_content).to be_nil
end
it 'does not store empty UTM parameters' do
empty_utm = {
utm_source: '',
utm_medium: '',
utm_campaign: 'campaign_only'
}
get new_user_registration_path, params: empty_utm
unique_email = "empty-utm-#{Time.current.to_i}@example.com"
post user_registration_path, params: {
user: {
email: unique_email,
password: 'password123',
password_confirmation: 'password123'
}
}
user = User.find_by(email: unique_email)
expect(user.utm_source).to be_nil
expect(user.utm_medium).to be_nil
expect(user.utm_campaign).to eq('campaign_only')
end
it 'works with family invitations' do
get new_user_registration_path, params: utm_params.merge(invitation_token: invitation.token)
post user_registration_path, params: {
user: {
email: invitation.email,
password: 'password123',
password_confirmation: 'password123'
},
invitation_token: invitation.token
}
user = User.find_by(email: invitation.email)
expect(user.utm_source).to eq('google')
expect(user.utm_campaign).to eq('winter_2025')
expect(user.family).to eq(family)
end
end
context 'when self-hosted mode is enabled' do
before do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('SELF_HOSTED').and_return('true')
end
it 'does not capture UTM parameters' do
# With valid invitation to allow registration in self-hosted mode
get new_user_registration_path, params: utm_params.merge(invitation_token: invitation.token)
expect(session[:utm_source]).to be_nil
expect(session[:utm_medium]).to be_nil
expect(session[:utm_campaign]).to be_nil
end
it 'does not store UTM parameters in user record' do
# With valid invitation to allow registration in self-hosted mode
get new_user_registration_path, params: utm_params.merge(invitation_token: invitation.token)
post user_registration_path, params: {
user: {
email: invitation.email,
password: 'password123',
password_confirmation: 'password123'
},
invitation_token: invitation.token
}
user = User.find_by(email: invitation.email)
expect(user.utm_source).to be_nil
expect(user.utm_medium).to be_nil
expect(user.utm_campaign).to be_nil
expect(user.utm_term).to be_nil
expect(user.utm_content).to be_nil
end
end
end
end