chore: Track cloud signup conversions (#14852)
## Summary - enqueue `cloud_signup` conversion tracking after website attribution is stored on a new cloud account - keep authenticated add-workspace requests out of attribution/conversion tracking - reuse the existing marketing conversion tracking service and config already available on `develop` ## Notes - This PR only wires the signup path. - Billing/plan activation conversion tracking is intentionally left out for a separate rollout. - The conversion service still no-ops outside Chatwoot Cloud and when stored attribution has no supported click identifier.
This commit is contained in:
@@ -50,6 +50,7 @@ class Internal::Accounts::MarketingAttributionService
|
||||
'stored_at' => Time.current.iso8601
|
||||
}.compact
|
||||
)
|
||||
enqueue_signup_conversion
|
||||
end
|
||||
|
||||
private
|
||||
@@ -79,4 +80,8 @@ class Internal::Accounts::MarketingAttributionService
|
||||
def internal_attributes_service
|
||||
@internal_attributes_service ||= Internal::Accounts::InternalAttributesService.new(account)
|
||||
end
|
||||
|
||||
def enqueue_signup_conversion
|
||||
Internal::Accounts::MarketingConversionTrackingJob.perform_later(account.id, 'cloud_signup', account.created_at)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,7 +58,7 @@ class Internal::Accounts::MarketingConversionTrackingService
|
||||
def conversion_payload
|
||||
payload = {
|
||||
transactionId: "#{event_name}-account-#{account.id}",
|
||||
eventTimestamp: (occurred_at.present? ? Time.zone.parse(occurred_at.to_s) : Time.current).iso8601,
|
||||
eventTimestamp: event_timestamp.iso8601,
|
||||
eventSource: 'WEB',
|
||||
adIdentifiers: click_attributes
|
||||
}
|
||||
@@ -78,6 +78,10 @@ class Internal::Accounts::MarketingConversionTrackingService
|
||||
end.to_h
|
||||
end
|
||||
|
||||
def event_timestamp
|
||||
occurred_at || Time.current
|
||||
end
|
||||
|
||||
def attribution
|
||||
marketing_attribution = account.internal_attributes['marketing_attribution'] || {}
|
||||
[marketing_attribution['last_touch'], marketing_attribution['first_touch']].find do |touch|
|
||||
|
||||
@@ -28,19 +28,22 @@ RSpec.describe 'Enterprise Accounts API', type: :request do
|
||||
allow(AccountBuilder).to receive(:new).and_return(account_builder)
|
||||
allow(account_builder).to receive(:perform).and_return([user, account])
|
||||
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
|
||||
post api_v1_accounts_url,
|
||||
params: {
|
||||
account_name: 'test',
|
||||
email: email,
|
||||
user: nil,
|
||||
locale: nil,
|
||||
user_full_name: user_full_name,
|
||||
password: 'Password1!'
|
||||
},
|
||||
headers: attribution_cookie_header,
|
||||
as: :json
|
||||
end
|
||||
expect do
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
|
||||
post api_v1_accounts_url,
|
||||
params: {
|
||||
account_name: 'test',
|
||||
email: email,
|
||||
user: nil,
|
||||
locale: nil,
|
||||
user_full_name: user_full_name,
|
||||
password: 'Password1!'
|
||||
},
|
||||
headers: attribution_cookie_header,
|
||||
as: :json
|
||||
end
|
||||
end.to have_enqueued_job(Internal::Accounts::MarketingConversionTrackingJob)
|
||||
.with(account.id, 'cloud_signup', account.created_at)
|
||||
|
||||
attribution = account.reload.internal_attributes['marketing_attribution']
|
||||
expect(attribution['captured_from']).to eq('cookie')
|
||||
@@ -51,13 +54,15 @@ RSpec.describe 'Enterprise Accounts API', type: :request do
|
||||
it 'does not record marketing attribution for authenticated add-workspace requests' do
|
||||
existing_user = create(:user, password: 'Password1!')
|
||||
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
|
||||
post api_v1_accounts_url,
|
||||
params: { account_name: 'Second Account', email: existing_user.email,
|
||||
user_full_name: existing_user.name, password: 'Password1!' },
|
||||
headers: existing_user.create_new_auth_token.merge(attribution_cookie_header),
|
||||
as: :json
|
||||
end
|
||||
expect do
|
||||
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
|
||||
post api_v1_accounts_url,
|
||||
params: { account_name: 'Second Account', email: existing_user.email,
|
||||
user_full_name: existing_user.name, password: 'Password1!' },
|
||||
headers: existing_user.create_new_auth_token.merge(attribution_cookie_header),
|
||||
as: :json
|
||||
end
|
||||
end.not_to have_enqueued_job(Internal::Accounts::MarketingConversionTrackingJob)
|
||||
|
||||
account = Account.find(response.parsed_body.dig('data', 'account_id'))
|
||||
expect(account.internal_attributes).not_to include('marketing_attribution')
|
||||
|
||||
@@ -32,6 +32,15 @@ RSpec.describe Internal::Accounts::MarketingAttributionService do
|
||||
expect(attribution['last_touch']['source']).to eq('github')
|
||||
end
|
||||
|
||||
it 'enqueues signup conversion tracking after storing attribution' do
|
||||
cookies[described_class::LAST_TOUCH_COOKIE] = encoded_cookie('source' => 'github')
|
||||
|
||||
expect do
|
||||
described_class.new(account: account, cookies: cookies).perform
|
||||
end.to have_enqueued_job(Internal::Accounts::MarketingConversionTrackingJob)
|
||||
.with(account.id, 'cloud_signup', account.created_at)
|
||||
end
|
||||
|
||||
it 'does not store attribution outside Chatwoot Cloud' do
|
||||
allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(false)
|
||||
cookies[described_class::LAST_TOUCH_COOKIE] = encoded_cookie('source' => 'reddit')
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ require 'rails_helper'
|
||||
RSpec.describe Internal::Accounts::MarketingConversionTrackingService do
|
||||
let(:account) { create(:account) }
|
||||
let(:event_name) { 'cloud_signup' }
|
||||
let(:occurred_at) { '2026-06-23T10:30:00Z' }
|
||||
let(:occurred_at) { Time.zone.parse('2026-06-23T10:30:00Z') }
|
||||
let(:private_key) { OpenSSL::PKey::RSA.new(2048).to_pem }
|
||||
let(:credentials) do
|
||||
instance_double(Google::Auth::ServiceAccountCredentials, fetch_access_token!: { 'access_token' => 'access-token' })
|
||||
|
||||
Reference in New Issue
Block a user