Support more utm tracking
This commit is contained in:
parent
bfbd266466
commit
3a4b900ebd
|
|
@ -0,0 +1,181 @@
|
|||
class Spacer
|
||||
def self.spacer(val, row)
|
||||
|
||||
percentage = ((val * 100) / row.total.to_f).round(1).to_s
|
||||
('%-5.5s' % percentage).gsub(' ', ' ') + '% - ' + val.to_s
|
||||
end
|
||||
end
|
||||
=begin
|
||||
select
|
||||
count(id) as total,
|
||||
count(first_downloaded_client_at) as downloaded,
|
||||
count(first_ran_client_at) as ran_client,
|
||||
count(first_certified_gear_at) as ftue,
|
||||
count(first_music_session_at) as any_session,
|
||||
count(first_real_music_session_at) as real_session,
|
||||
count(first_good_music_session_at) as good_session,
|
||||
count(first_invited_at) as invited,
|
||||
count(first_friended_at) as friended,
|
||||
count(first_subscribed_at) as subscribed
|
||||
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
|
||||
|
||||
select first_name, last_name, email
|
||||
from users where users.created_at >= '2024-11-01' AND users.created_at < '2025-04-01'
|
||||
AND first_music_session_at is NULL;
|
||||
|
||||
=end
|
||||
|
||||
ActiveAdmin.register_page "Jammers Subscription Cohorts" do
|
||||
menu :parent => 'Reports'
|
||||
|
||||
content :title => "Jammers Subscription Cohorts" do
|
||||
|
||||
filter_type = params[:filter_type] || 'All'
|
||||
filter_campaign = params[:filter_campaign]
|
||||
filter_campaign_id = params[:filter_campaign_id]
|
||||
filter_ad_set = params[:filter_ad_set]
|
||||
filter_ad_name = params[:filter_ad_name]
|
||||
|
||||
campaigns = User.where("origin_utm_source ILIKE '%meta%'").distinct.pluck(:origin_utm_campaign).compact.sort
|
||||
campaign_ids = User.where("origin_utm_source ILIKE '%meta%'").distinct.pluck(:origin_id).compact.sort
|
||||
ad_sets = User.where("origin_utm_source ILIKE '%meta%'").distinct.pluck(:origin_term).compact.sort
|
||||
ad_names = User.where("origin_utm_source ILIKE '%meta%'").distinct.pluck(:origin_content).compact.sort
|
||||
|
||||
div style: "margin-bottom: 20px; padding: 10px; background-color: #f4f4f4; border-radius: 4px;" do
|
||||
form action: admin_jammers_subscription_cohorts_path, method: :get do
|
||||
span "Source: ", style: "font-weight: bold; margin-right: 5px;"
|
||||
select name: 'filter_type', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||
option "All", value: 'All', selected: filter_type == 'All'
|
||||
option "Organic", value: 'Organic', selected: filter_type == 'Organic'
|
||||
option "Advertising", value: 'Advertising', selected: filter_type == 'Advertising'
|
||||
end
|
||||
|
||||
if filter_type == 'Advertising'
|
||||
div style: "margin-top: 10px;" do
|
||||
span "Campaign Name: ", style: "font-weight: bold; margin-right: 5px;"
|
||||
select name: 'filter_campaign', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||
option "All", value: ''
|
||||
option "Null", value: 'NULL', selected: filter_campaign == 'NULL'
|
||||
campaigns.each do |c|
|
||||
option c, value: c, selected: filter_campaign == c
|
||||
end
|
||||
end
|
||||
|
||||
span "Campaign ID: ", style: "font-weight: bold; margin-right: 5px;"
|
||||
select name: 'filter_campaign_id', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||
option "All", value: ''
|
||||
option "Null", value: 'NULL', selected: filter_campaign_id == 'NULL'
|
||||
campaign_ids.each do |c|
|
||||
option c, value: c, selected: filter_campaign_id == c
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
div style: "margin-top: 10px;" do
|
||||
span "Ad Set: ", style: "font-weight: bold; margin-right: 5px;"
|
||||
select name: 'filter_ad_set', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||
option "All", value: ''
|
||||
option "Null", value: 'NULL', selected: filter_ad_set == 'NULL'
|
||||
ad_sets.each do |c|
|
||||
option c, value: c, selected: filter_ad_set == c
|
||||
end
|
||||
end
|
||||
|
||||
span "Ad Name: ", style: "font-weight: bold; margin-right: 5px;"
|
||||
select name: 'filter_ad_name', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||
option "All", value: ''
|
||||
option "Null", value: 'NULL', selected: filter_ad_name == 'NULL'
|
||||
ad_names.each do |c|
|
||||
option c, value: c, selected: filter_ad_name == c
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
noscript { input type: :submit, value: "Filter" }
|
||||
end
|
||||
end
|
||||
|
||||
h2 "Users Grouped By Month as Paying Subscribers"
|
||||
|
||||
query = User.select(%Q{date_trunc('month', users.created_at) as month,
|
||||
count(id) as total,
|
||||
count(first_downloaded_client_at) as downloaded,
|
||||
count(first_ran_client_at) as ran_client,
|
||||
count(first_certified_gear_at) as ftue,
|
||||
count(first_music_session_at) as any_session,
|
||||
count(first_real_music_session_at) as real_session,
|
||||
count(first_good_music_session_at) as good_session,
|
||||
count(first_invited_at) as invited,
|
||||
count(first_friended_at) as friended,
|
||||
count(first_subscribed_at) as subscribed,
|
||||
count(first_played_jamtrack_at) as played_jamtrack
|
||||
})
|
||||
.joins(%Q{LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
j.created_at
|
||||
FROM
|
||||
jam_track_rights as j
|
||||
WHERE
|
||||
j.user_id = users.id
|
||||
ORDER BY
|
||||
j.created_at
|
||||
LIMIT 1 -- Select only that single row
|
||||
) j ON TRUE })
|
||||
|
||||
if filter_type == 'Organic'
|
||||
query = query.where("users.origin_utm_source = 'organic'")
|
||||
elsif filter_type == 'Advertising'
|
||||
query = query.where("users.origin_utm_source ILIKE '%meta%'")
|
||||
|
||||
if filter_campaign.present?
|
||||
if filter_campaign == 'NULL'
|
||||
query = query.where("users.origin_utm_campaign IS NULL")
|
||||
else
|
||||
query = query.where("users.origin_utm_campaign = ?", filter_campaign)
|
||||
end
|
||||
end
|
||||
|
||||
if filter_campaign_id.present?
|
||||
if filter_campaign_id == 'NULL'
|
||||
query = query.where("users.origin_id IS NULL")
|
||||
else
|
||||
query = query.where("users.origin_id = ?", filter_campaign_id)
|
||||
end
|
||||
end
|
||||
|
||||
if filter_ad_set.present?
|
||||
if filter_ad_set == 'NULL'
|
||||
query = query.where("users.origin_term IS NULL")
|
||||
else
|
||||
query = query.where("users.origin_term = ?", filter_ad_set)
|
||||
end
|
||||
end
|
||||
|
||||
if filter_ad_name.present?
|
||||
if filter_ad_name == 'NULL'
|
||||
query = query.where("users.origin_content IS NULL")
|
||||
else
|
||||
query = query.where("users.origin_content = ?", filter_ad_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table_for query.group("date_trunc('month', users.created_at)")
|
||||
.where("j.created_at IS NULL OR (j.created_at - users.created_at) >= INTERVAL '2 hours'")
|
||||
.order("date_trunc('month', users.created_at) DESC") do |row|
|
||||
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
|
||||
column "Total", :total
|
||||
column "Subscribed", Proc.new { |user| raw(Spacer.spacer(user.subscribed, user)) }
|
||||
column "Downloaded", Proc.new { |user| raw(Spacer.spacer(user.downloaded, user)) }
|
||||
column "Ran Client", Proc.new { |user| raw(Spacer.spacer(user.ran_client, user)) }
|
||||
column "FTUE", Proc.new { |user| raw(Spacer.spacer(user.ftue, user)) }
|
||||
column "Any Session", Proc.new { |user| raw(Spacer.spacer(user.any_session, user)) }
|
||||
column "2+ Session", Proc.new { |user| raw(Spacer.spacer(user.real_session, user)) }
|
||||
column "Good Session", Proc.new { |user| raw(Spacer.spacer(user.good_session, user)) }
|
||||
column "Invited", Proc.new { |user| raw(Spacer.spacer(user.invited, user)) }
|
||||
column "Friended", Proc.new { |user| raw(Spacer.spacer(user.friended, user)) }
|
||||
column "Played JT", Proc.new { |user| raw(Spacer.spacer(user.played_jamtrack, user)) }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -11,6 +11,7 @@ const MetaTracking = {
|
|||
init: function () {
|
||||
const location = window.location;
|
||||
this.handleFbc(location.search);
|
||||
this.handleUtm(location.search);
|
||||
this.handleFbp();
|
||||
},
|
||||
|
||||
|
|
@ -30,6 +31,22 @@ const MetaTracking = {
|
|||
}
|
||||
},
|
||||
|
||||
handleUtm: function (searchParams) {
|
||||
if (!searchParams) return;
|
||||
|
||||
const query = searchParams.substring(1);
|
||||
const vars = query.split('&');
|
||||
vars.forEach(v => {
|
||||
const pair = v.split('=');
|
||||
if (pair.length === 2) {
|
||||
const key = decodeURIComponent(pair[0]);
|
||||
const value = decodeURIComponent(pair[1]);
|
||||
if (key.indexOf('utm_') === 0) {
|
||||
this.setCookie(key, value, 90);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// 2. Handling _fbp (Browser ID)
|
||||
handleFbp: function () {
|
||||
if (!this.getCookie('_fbp')) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
class AddExtendedUtmToUsers < ActiveRecord::Migration[5.0]
|
||||
def up
|
||||
execute <<-SQL
|
||||
ALTER TABLE users ADD COLUMN origin_id character varying;
|
||||
ALTER TABLE users ADD COLUMN origin_term character varying;
|
||||
ALTER TABLE users ADD COLUMN origin_content character varying;
|
||||
|
||||
CREATE INDEX index_users_on_origin_id ON users (origin_id);
|
||||
CREATE INDEX index_users_on_origin_term ON users (origin_term);
|
||||
CREATE INDEX index_users_on_origin_content ON users (origin_content);
|
||||
|
||||
CREATE INDEX index_users_on_origin_utm_source ON users (origin_utm_source);
|
||||
CREATE INDEX index_users_on_origin_utm_medium ON users (origin_utm_medium);
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
execute <<-SQL
|
||||
DROP INDEX IF EXISTS index_users_on_origin_utm_medium;
|
||||
DROP INDEX IF EXISTS index_users_on_origin_utm_source;
|
||||
|
||||
DROP INDEX IF EXISTS index_users_on_origin_content;
|
||||
DROP INDEX IF EXISTS index_users_on_origin_term;
|
||||
DROP INDEX IF EXISTS index_users_on_origin_id;
|
||||
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS origin_content;
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS origin_term;
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS origin_id;
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
|
@ -1587,11 +1587,17 @@ module JamRuby
|
|||
user.origin_utm_source = origin["utm_source"]
|
||||
user.origin_utm_medium = origin["utm_medium"]
|
||||
user.origin_utm_campaign = origin["utm_campaign"]
|
||||
user.origin_id = origin["utm_id"]
|
||||
user.origin_term = origin["utm_term"]
|
||||
user.origin_content = origin["utm_content"]
|
||||
user.origin_referrer = origin["referrer"]
|
||||
else
|
||||
user.origin_utm_source = 'organic'
|
||||
user.origin_utm_medium = 'organic'
|
||||
user.origin_utm_campaign = nil
|
||||
user.origin_id = nil
|
||||
user.origin_term = nil
|
||||
user.origin_content = nil
|
||||
user.origin_referrer = nil
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -35,14 +35,23 @@
|
|||
},
|
||||
|
||||
handleUtm: function (searchParams) {
|
||||
var utmParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
|
||||
var self = this;
|
||||
// forEach not supported in IE8, but this is modern enough or we can use for loop
|
||||
for (var i = 0; i < utmParams.length; i++) {
|
||||
var param = utmParams[i];
|
||||
var value = self.getQueryParam(param, searchParams);
|
||||
if (value) {
|
||||
self.setCookie(param, value, 90);
|
||||
if (!searchParams) return;
|
||||
|
||||
// Logically, we want to capture all utm_ parameters.
|
||||
// We can either iterate a list or dynamic regex.
|
||||
// Given the requirement to be robust, let's look for "utm_"
|
||||
|
||||
var query = searchParams.substring(1); // remove '?'
|
||||
var vars = query.split('&');
|
||||
for (var i = 0; i < vars.length; i++) {
|
||||
var pair = vars[i].split('=');
|
||||
if (pair.length === 2) {
|
||||
var key = decodeURIComponent(pair[0]);
|
||||
var value = decodeURIComponent(pair[1]);
|
||||
if (key.indexOf('utm_') === 0) {
|
||||
self.setCookie(key, value, 90);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue