reviewing
This commit is contained in:
parent
0d82f6ee16
commit
fbd871d204
|
|
@ -29,8 +29,36 @@ ActiveAdmin.register_page "Jammers Subscription Cohorts" do
|
||||||
menu :parent => 'Reports'
|
menu :parent => 'Reports'
|
||||||
|
|
||||||
content :title => "Jammers Subscription Cohorts" do
|
content :title => "Jammers Subscription Cohorts" do
|
||||||
|
|
||||||
|
filter_type = params[:filter_type] || 'All'
|
||||||
|
filter_campaign = params[:filter_campaign]
|
||||||
|
campaigns = User.where("origin_utm_source ILIKE '%facebook%'").distinct.pluck(:origin_utm_campaign).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'
|
||||||
|
span "Campaign: ", style: "font-weight: bold; margin-right: 5px;"
|
||||||
|
select name: 'filter_campaign', onchange: 'this.form.submit()', style: "margin-right: 15px;" do
|
||||||
|
option "All Campaigns", value: ''
|
||||||
|
campaigns.each do |c|
|
||||||
|
option c, value: c, selected: filter_campaign == c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
noscript { input type: :submit, value: "Filter" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
h2 "Users Grouped By Month as Paying Subscribers"
|
h2 "Users Grouped By Month as Paying Subscribers"
|
||||||
table_for User.select(%Q{date_trunc('month', users.created_at) as month,
|
|
||||||
|
query = User.select(%Q{date_trunc('month', users.created_at) as month,
|
||||||
count(id) as total,
|
count(id) as total,
|
||||||
count(first_downloaded_client_at) as downloaded,
|
count(first_downloaded_client_at) as downloaded,
|
||||||
count(first_ran_client_at) as ran_client,
|
count(first_ran_client_at) as ran_client,
|
||||||
|
|
@ -54,7 +82,17 @@ ActiveAdmin.register_page "Jammers Subscription Cohorts" do
|
||||||
j.created_at
|
j.created_at
|
||||||
LIMIT 1 -- Select only that single row
|
LIMIT 1 -- Select only that single row
|
||||||
) j ON TRUE })
|
) j ON TRUE })
|
||||||
.group("date_trunc('month', users.created_at)")
|
|
||||||
|
if filter_type == 'Organic'
|
||||||
|
query = query.where("users.origin_utm_source = 'organic'")
|
||||||
|
elsif filter_type == 'Advertising'
|
||||||
|
query = query.where("users.origin_utm_source ILIKE '%facebook%'")
|
||||||
|
if filter_campaign.present?
|
||||||
|
query = query.where("users.origin_utm_campaign = ?", filter_campaign)
|
||||||
|
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'")
|
.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|
|
.order("date_trunc('month', users.created_at) DESC") do |row|
|
||||||
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
|
column "Month", Proc.new { |user| user.month.strftime('%B %Y') }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
class AddFacebookTrackingToUsers < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
add_column :users, :facebook_click_id, :string
|
||||||
|
add_column :users, :facebook_browser_id, :string
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :users, :facebook_click_id
|
||||||
|
remove_column :users, :facebook_browser_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -56,6 +56,7 @@ require "jam_ruby/lib/em_helper"
|
||||||
require "jam_ruby/lib/nav"
|
require "jam_ruby/lib/nav"
|
||||||
require "jam_ruby/lib/html_sanitize"
|
require "jam_ruby/lib/html_sanitize"
|
||||||
require "jam_ruby/lib/guitar_center"
|
require "jam_ruby/lib/guitar_center"
|
||||||
|
require "jam_ruby/lib/capi_transmitter"
|
||||||
require "jam_ruby/subscription_definitions"
|
require "jam_ruby/subscription_definitions"
|
||||||
require "jam_ruby/resque/resque_jam_error"
|
require "jam_ruby/resque/resque_jam_error"
|
||||||
require "jam_ruby/resque/resque_hooks"
|
require "jam_ruby/resque/resque_hooks"
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ module JamRuby
|
||||||
after_save :update_teacher_pct
|
after_save :update_teacher_pct
|
||||||
|
|
||||||
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection, :used_current_month, :used_month_play_time,
|
attr_accessible :first_name, :last_name, :email, :city, :password, :password_confirmation, :state, :country, :birth_date, :subscribe_email, :terms_of_service, :original_fpfile, :cropped_fpfile, :cropped_large_fpfile, :cropped_s3_path, :cropped_large_s3_path, :photo_url, :large_photo_url, :crop_selection, :used_current_month, :used_month_play_time,
|
||||||
:v2_photo_url, :v2_photo_uploaded
|
:v2_photo_url, :v2_photo_uploaded, :facebook_click_id, :facebook_browser_id
|
||||||
|
|
||||||
# updating_password corresponds to a lost_password
|
# updating_password corresponds to a lost_password
|
||||||
attr_accessor :test_drive_packaging, :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card, :purchase_required, :user_type
|
attr_accessor :test_drive_packaging, :validate_instruments, :updating_password, :updating_email, :updated_email, :update_email_confirmation_url, :administratively_created, :current_password, :setting_password, :confirm_current_password, :updating_avatar, :updating_progression_field, :mods_json, :expecting_gift_card, :purchase_required, :user_type
|
||||||
|
|
@ -542,6 +542,21 @@ module JamRuby
|
||||||
@updating_progression_field = true
|
@updating_progression_field = true
|
||||||
if self[field_name].nil?
|
if self[field_name].nil?
|
||||||
self[field_name] = time
|
self[field_name] = time
|
||||||
|
|
||||||
|
# CAPI Hooks
|
||||||
|
begin
|
||||||
|
case field_name.to_s
|
||||||
|
when 'first_ran_client_at'
|
||||||
|
CapiTransmitter.send_event('OpenClient', self)
|
||||||
|
when 'first_certified_gear_at'
|
||||||
|
CapiTransmitter.send_event('CompleteGearWizard', self)
|
||||||
|
when 'first_music_session_at'
|
||||||
|
CapiTransmitter.send_event('FirstMusicSession', self)
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.error("Error sending CAPI event: #{e.message}")
|
||||||
|
end
|
||||||
|
|
||||||
self.save
|
self.save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -1579,6 +1594,8 @@ module JamRuby
|
||||||
license_end = options[:license_end]
|
license_end = options[:license_end]
|
||||||
import_source = options[:import_source]
|
import_source = options[:import_source]
|
||||||
desired_plan_code = options[:desired_plan_code]
|
desired_plan_code = options[:desired_plan_code]
|
||||||
|
facebook_click_id = options[:facebook_click_id]
|
||||||
|
facebook_browser_id = options[:facebook_browser_id]
|
||||||
|
|
||||||
if desired_plan_code == ''
|
if desired_plan_code == ''
|
||||||
desired_plan_code = nil
|
desired_plan_code = nil
|
||||||
|
|
@ -1640,6 +1657,8 @@ module JamRuby
|
||||||
musician = true
|
musician = true
|
||||||
end
|
end
|
||||||
user.musician = !!musician
|
user.musician = !!musician
|
||||||
|
user.facebook_click_id = facebook_click_id
|
||||||
|
user.facebook_browser_id = facebook_browser_id
|
||||||
|
|
||||||
if origin
|
if origin
|
||||||
user.origin_utm_source = origin["utm_source"]
|
user.origin_utm_source = origin["utm_source"]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* meta_tracking.js
|
||||||
|
* A standalone module to capture and persist Meta attribution signals (fbclid, _fbp) in cookies.
|
||||||
|
*
|
||||||
|
* Logic adapted for legacy environment (no React hooks).
|
||||||
|
* - Checks URL for `fbclid` and sets `_fbc` cookie.
|
||||||
|
* - Checks for `_fbp` cookie; if missing, generates and sets it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function(window, document) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var MetaTracking = {
|
||||||
|
init: function() {
|
||||||
|
var location = window.location;
|
||||||
|
this.handleFbc(location.search);
|
||||||
|
this.handleFbp();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 1. Parsing and storing _fbc (Click ID)
|
||||||
|
handleFbc: function(searchParams) {
|
||||||
|
var fbclid = this.getQueryParam('fbclid', searchParams);
|
||||||
|
|
||||||
|
if (fbclid) {
|
||||||
|
var version = 'fb';
|
||||||
|
var subdomainIndex = 1; // 1 = example.com
|
||||||
|
var creationTime = new Date().getTime(); // Unix timestamp in ms
|
||||||
|
|
||||||
|
// Format: fb.1.timestamp.id
|
||||||
|
var fbcValue = version + '.' + subdomainIndex + '.' + creationTime + '.' + fbclid;
|
||||||
|
|
||||||
|
this.setCookie('_fbc', fbcValue, 90);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 2. Handling _fbp (Browser ID)
|
||||||
|
handleFbp: function() {
|
||||||
|
if (!this.getCookie('_fbp')) {
|
||||||
|
var version = 'fb';
|
||||||
|
var subdomainIndex = 1;
|
||||||
|
var creationTime = new Date().getTime();
|
||||||
|
var randomInt = Math.floor(Math.random() * 10000000000); // 10-digit random number
|
||||||
|
|
||||||
|
// Format: fb.1.timestamp.randomDigits
|
||||||
|
var fbpValue = version + '.' + subdomainIndex + '.' + creationTime + '.' + randomInt;
|
||||||
|
|
||||||
|
this.setCookie('_fbp', fbpValue, 90);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper: Get query param by name
|
||||||
|
getQueryParam: function(name, search) {
|
||||||
|
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
|
||||||
|
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
|
||||||
|
var results = regex.exec(search);
|
||||||
|
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper: Set cookie
|
||||||
|
setCookie: function(name, value, days) {
|
||||||
|
var expires = "";
|
||||||
|
if (days) {
|
||||||
|
var date = new Date();
|
||||||
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
expires = "; expires=" + date.toUTCString();
|
||||||
|
}
|
||||||
|
// Ensure path is root and domain is included if needed (defaults to current host)
|
||||||
|
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper: Get cookie
|
||||||
|
getCookie: function(name) {
|
||||||
|
var nameEQ = name + "=";
|
||||||
|
var ca = document.cookie.split(';');
|
||||||
|
for(var i=0;i < ca.length;i++) {
|
||||||
|
var c = ca[i];
|
||||||
|
while (c.charAt(0) == ' ') c = c.substring(1,c.length);
|
||||||
|
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize on ready
|
||||||
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||||
|
MetaTracking.init();
|
||||||
|
} else {
|
||||||
|
// IE9+ support for DOMContentLoaded
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
MetaTracking.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
})(window, document);
|
||||||
|
|
@ -158,7 +158,9 @@ class ApiUsersController < ApiController
|
||||||
affiliate_referral_id: cookies[:affiliate_visitor],
|
affiliate_referral_id: cookies[:affiliate_visitor],
|
||||||
origin: origin_cookie,
|
origin: origin_cookie,
|
||||||
test_drive_package: params[:test_drive_package],
|
test_drive_package: params[:test_drive_package],
|
||||||
timezone: current_timezone
|
timezone: current_timezone,
|
||||||
|
facebook_click_id: cookies[:_fbc],
|
||||||
|
facebook_browser_id: cookies[:_fbp]
|
||||||
}
|
}
|
||||||
|
|
||||||
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,11 @@ class UsersController < ApplicationController
|
||||||
affiliate_partner: @affiliate_partner,
|
affiliate_partner: @affiliate_partner,
|
||||||
timezone: current_timezone,
|
timezone: current_timezone,
|
||||||
origin: origin_cookie,
|
origin: origin_cookie,
|
||||||
desired_plan_code: desired_plan_code )
|
timezone: current_timezone,
|
||||||
|
origin: origin_cookie,
|
||||||
|
desired_plan_code: desired_plan_code,
|
||||||
|
facebook_click_id: cookies[:_fbc],
|
||||||
|
facebook_browser_id: cookies[:_fbp])
|
||||||
|
|
||||||
rend = _render('new')
|
rend = _render('new')
|
||||||
# check for errors
|
# check for errors
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,11 @@ if defined?(Bundler)
|
||||||
config.google_secret = 'UwzIcvtErv9c2-GIsNfIo7bA'
|
config.google_secret = 'UwzIcvtErv9c2-GIsNfIo7bA'
|
||||||
config.google_email = '785931784279-gd0g8on6sc0tuesj7cu763pitaiv2la8@developer.gserviceaccount.com'
|
config.google_email = '785931784279-gd0g8on6sc0tuesj7cu763pitaiv2la8@developer.gserviceaccount.com'
|
||||||
config.google_public_server_key = "AIzaSyCPTPq5PEcl4XWcm7NZ2IGClZlbsiE8JNo"
|
config.google_public_server_key = "AIzaSyCPTPq5PEcl4XWcm7NZ2IGClZlbsiE8JNo"
|
||||||
|
|
||||||
|
# Facebook Conversions API
|
||||||
|
config.facebook_pixel_id = "1234567890" # Replace with valid default or remove if strictly env-based
|
||||||
|
config.facebook_access_token = "placeholder" # Replace with valid default or remove if strictly env-based
|
||||||
|
config.facebook_conversion_api_tls = true
|
||||||
|
|
||||||
# Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more.
|
# Use Private API Keys to communicate with Recurly's API v2. See https://docs.recurly.com/api/basics/authentication to learn more.
|
||||||
config.recurly_private_api_key = '55f2fdfa4d014e64a94eaba1e93f39bb'
|
config.recurly_private_api_key = '55f2fdfa4d014e64a94eaba1e93f39bb'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
namespace :capi do
|
||||||
|
desc "Test Facebook CAPI connection"
|
||||||
|
task :test_connection => :environment do
|
||||||
|
puts "Testing CAPI connection..."
|
||||||
|
pixel_id = APP_CONFIG[:facebook_pixel_id]
|
||||||
|
access_token = APP_CONFIG[:facebook_access_token]
|
||||||
|
|
||||||
|
puts "Pixel ID: #{pixel_id}"
|
||||||
|
puts "Access Token: #{access_token.try(:truncate, 10)}"
|
||||||
|
|
||||||
|
if pixel_id.blank? || access_token.blank? || access_token == 'placeholder'
|
||||||
|
puts "WARNING: Configuration missing or placeholder."
|
||||||
|
end
|
||||||
|
|
||||||
|
# Mock user
|
||||||
|
user = User.last
|
||||||
|
if user
|
||||||
|
puts "Sending test event 'TestEvent' for User ID: #{user.id}"
|
||||||
|
CapiTransmitter.send_event('TestEvent', user)
|
||||||
|
puts "Check Rails log for output."
|
||||||
|
else
|
||||||
|
puts "No user found in database."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -52,6 +52,8 @@ class UserManager < BaseManager
|
||||||
license_end = options[:license_end]
|
license_end = options[:license_end]
|
||||||
import_source = options[:import_source]
|
import_source = options[:import_source]
|
||||||
desired_plan_code = options[:desired_plan_code]
|
desired_plan_code = options[:desired_plan_code]
|
||||||
|
facebook_click_id = options[:facebook_click_id]
|
||||||
|
facebook_browser_id = options[:facebook_browser_id]
|
||||||
|
|
||||||
recaptcha_failed = false
|
recaptcha_failed = false
|
||||||
unless options[:skip_recaptcha] # allow callers to opt-of recaptcha
|
unless options[:skip_recaptcha] # allow callers to opt-of recaptcha
|
||||||
|
|
@ -114,7 +116,10 @@ class UserManager < BaseManager
|
||||||
license_start: license_start,
|
license_start: license_start,
|
||||||
license_end: license_end,
|
license_end: license_end,
|
||||||
import_source: import_source,
|
import_source: import_source,
|
||||||
desired_plan_code: desired_plan_code)
|
import_source: import_source,
|
||||||
|
desired_plan_code: desired_plan_code,
|
||||||
|
facebook_click_id: facebook_click_id,
|
||||||
|
facebook_browser_id: facebook_browser_id)
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue