-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
1,498 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
posthog/clickhouse/migrations/0050_add_ga4_channel_type.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from posthog.clickhouse.client.migration_tools import run_sql_with_exceptions | ||
from posthog.models.channel_type.sql import ( | ||
GA4_CHANNEL_DEFINITION_TABLE_SQL, | ||
GA_CHANNEL_DEFINITIONS_DATA_SQL, | ||
GA4_CHANNEL_DEFINITION_DICTIONARY_SQL, | ||
) | ||
|
||
operations = [ | ||
run_sql_with_exceptions(GA4_CHANNEL_DEFINITION_TABLE_SQL), | ||
run_sql_with_exceptions(GA_CHANNEL_DEFINITIONS_DATA_SQL), | ||
run_sql_with_exceptions(GA4_CHANNEL_DEFINITION_DICTIONARY_SQL), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# Create a virtual field that uses GA's channel grouping logic to group events into acquisition channels. | ||
# The source for this logic is: | ||
# UA: https://support.google.com/analytics/answer/3297892?hl=en | ||
# GA4: https://support.google.com/analytics/answer/9756891?hl=en | ||
|
||
# I'm not fully convinced that this approach will work on its own, as GA4 will have a lot more information on paid ads | ||
# than what we will have access to. We'll need to get this live and see what it looks like on Posthog data. | ||
from posthog.hogql.database.models import ExpressionField | ||
from posthog.hogql.parser import parse_expr | ||
|
||
|
||
def create_initial_domain_type(name: str): | ||
return ExpressionField( | ||
name=name, | ||
expr=parse_expr( | ||
""" | ||
if( | ||
properties.$initial_referring_domain = '$direct', | ||
'$direct', | ||
dictGetOrNull( | ||
'ga4_channel_definition_dict', | ||
'type', | ||
cutToFirstSignificantSubdomain(coalesce(properties.$initial_referring_domain, '')) | ||
) | ||
) | ||
""" | ||
), | ||
) | ||
|
||
|
||
def create_initial_channel_type(name: str): | ||
return ExpressionField( | ||
name=name, | ||
expr=parse_expr( | ||
""" | ||
multiIf( | ||
match(properties.$initial_utm_campaign, 'cross-network'), | ||
'Cross Network', | ||
match(properties.$initial_utm_medium, '^(.*cp.*|ppc|retargeting|paid.*)$'), | ||
CASE dictGetOrNull('ga4_channel_definition_dict', 'type', cutToFirstSignificantSubdomain(coalesce(properties.$initial_referring_domain, ''))) | ||
WHEN 'Shopping' THEN 'Paid Shopping' | ||
WHEN 'Search' THEN 'Paid Search' | ||
WHEN 'Video' THEN 'Paid Video' | ||
WHEN 'Social' THEN 'Paid Social' | ||
ELSE multiIf( | ||
match(properties.$initial_utm_campaign, '^(.*(([^a-df-z]|^)shop|shopping).*)$'), | ||
'Paid Shopping', | ||
properties.$initial_utm_medium IN | ||
('display', 'banner', 'expandable', 'interstitial', 'cpm'), | ||
'Display', | ||
'Paid Other' | ||
) | ||
END, | ||
properties.$initial_referring_domain = '$direct' AND (properties.$initial_utm_medium IS NULL OR properties.$initial_utm_medium = ''), | ||
'Direct', | ||
CASE dictGetOrNull('ga4_channel_definition_dict', 'type', cutToFirstSignificantSubdomain(coalesce(properties.$initial_referring_domain, ''))) | ||
WHEN 'Shopping' THEN 'Organic Shopping' | ||
WHEN 'Search' THEN 'Organic Search' | ||
WHEN 'Video' THEN 'Organic Video' | ||
WHEN 'Social' THEN 'Organic Social' | ||
ELSE multiIf( | ||
match(properties.$initial_utm_campaign, '^(.*(([^a-df-z]|^)shop|shopping).*)$'), | ||
'Organic Shopping', | ||
properties.$initial_utm_medium IN | ||
('social', 'social-network', 'social-media', 'sm', 'social network', 'social media'), | ||
'Organic Social', | ||
match(properties.$initial_utm_campaign, '^(.*video.*)$'), | ||
'Organic Video', | ||
properties.$initial_utm_medium = 'organic', | ||
'Organic Search', | ||
properties.$initial_utm_medium IN ('referral', 'app', 'link'), | ||
'Referral', | ||
properties.$initial_utm_source IN ('email', 'e-mail', 'e_mail', 'e mail') | ||
OR properties.$initial_utm_medium IN ('email', 'e-mail', 'e_mail', 'e mail'), | ||
'Email', | ||
properties.$initial_utm_medium = 'affiliate', | ||
'Affiliate', | ||
properties.$initial_utm_medium = 'audio', | ||
'Audio', | ||
properties.$initial_utm_source = 'sms' OR properties.$initial_utm_medium = 'sms', | ||
'SMS', | ||
match(properties.$initial_utm_medium, '(push$|mobile|notification)') | ||
OR properties.$initial_utm_source = 'firebase', | ||
'Push', | ||
NULL | ||
) | ||
END | ||
)""", | ||
start=None, | ||
), | ||
) |
160 changes: 160 additions & 0 deletions
160
posthog/hogql/database/schema/test/test_channel_type.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import uuid | ||
|
||
from posthog.hogql import ast | ||
from posthog.hogql.parser import parse_select | ||
from posthog.hogql.query import execute_hogql_query | ||
from posthog.test.base import ( | ||
APIBaseTest, | ||
ClickhouseTestMixin, | ||
_create_person, | ||
) | ||
|
||
|
||
class ReferringDomainTypeQueryRunner(ClickhouseTestMixin, APIBaseTest): | ||
maxDiff = None | ||
|
||
def _get_initial_referring_domain_type(self, initial_referring_domain: str): | ||
person_id = str(uuid.uuid4()) | ||
|
||
_create_person( | ||
uuid=person_id, | ||
team_id=self.team.pk, | ||
distinct_ids=[person_id], | ||
properties={ | ||
"$initial_referring_domain": initial_referring_domain, | ||
}, | ||
) | ||
|
||
response = execute_hogql_query( | ||
parse_select( | ||
"select $initial_referring_domain_type as channel_type from persons where id = {person_id}", | ||
placeholders={"person_id": ast.Constant(value=person_id)}, | ||
), | ||
self.team, | ||
) | ||
|
||
return response.results[0][0] | ||
|
||
def test_direct(self): | ||
self.assertEqual( | ||
"$direct", | ||
self._get_initial_referring_domain_type("$direct"), | ||
) | ||
|
||
def test_search(self): | ||
self.assertEqual( | ||
"Search", | ||
self._get_initial_referring_domain_type("www.google.co.uk"), | ||
) | ||
self.assertEqual( | ||
"Search", | ||
self._get_initial_referring_domain_type("yahoo.co.jp"), | ||
) | ||
|
||
def test_shopping(self): | ||
self.assertEqual( | ||
"Shopping", | ||
self._get_initial_referring_domain_type("m.alibaba.com"), | ||
) | ||
self.assertEqual( | ||
"Shopping", | ||
self._get_initial_referring_domain_type("stripe.com"), | ||
) | ||
|
||
def test_social(self): | ||
self.assertEqual( | ||
"Social", | ||
self._get_initial_referring_domain_type("lnkd.in"), | ||
) | ||
self.assertEqual( | ||
"Social", | ||
self._get_initial_referring_domain_type("old.reddit.com"), | ||
) | ||
|
||
|
||
class ChannelTypeQueryRunner(ClickhouseTestMixin, APIBaseTest): | ||
maxDiff = None | ||
|
||
def _get_initial_channel_type(self, properties=None): | ||
person_id = str(uuid.uuid4()) | ||
|
||
_create_person( | ||
uuid=person_id, | ||
team_id=self.team.pk, | ||
distinct_ids=[person_id], | ||
properties=properties, | ||
) | ||
|
||
response = execute_hogql_query( | ||
parse_select( | ||
"select $initial_channel_type as channel_type from persons where id = {person_id}", | ||
placeholders={"person_id": ast.Constant(value=person_id)}, | ||
), | ||
self.team, | ||
) | ||
|
||
return response.results[0][0] | ||
|
||
def test_direct(self): | ||
self.assertEqual( | ||
"Direct", | ||
self._get_initial_channel_type( | ||
{ | ||
"$initial_referring_domain": "$direct", | ||
} | ||
), | ||
) | ||
|
||
def test_cross_network(self): | ||
self.assertEqual( | ||
"Cross Network", | ||
self._get_initial_channel_type( | ||
{ | ||
"$initial_referring_domain": "$direct", | ||
"$initial_utm_campaign": "cross-network", | ||
} | ||
), | ||
) | ||
|
||
def test_paid_shopping(self): | ||
self.assertEqual( | ||
"Paid Shopping", | ||
self._get_initial_channel_type( | ||
{ | ||
"$initial_referring_domain": "www.ebay.co.uk", | ||
"$initial_utm_medium": "ppc", | ||
} | ||
), | ||
) | ||
|
||
def test_paid_search(self): | ||
self.assertEqual( | ||
"Paid Shopping", | ||
self._get_initial_channel_type( | ||
{ | ||
"$initial_referring_domain": "www.ebay.co.uk", | ||
"$initial_utm_medium": "ppc", | ||
} | ||
), | ||
) | ||
|
||
def test_paid_video(self): | ||
self.assertEqual( | ||
"Paid Video", | ||
self._get_initial_channel_type( | ||
{ | ||
"$initial_referring_domain": "youtube.com", | ||
"$initial_utm_medium": "cpm", | ||
} | ||
), | ||
) | ||
|
||
def test_organic_video(self): | ||
self.assertEqual( | ||
"Organic Video", | ||
self._get_initial_channel_type( | ||
{ | ||
"$initial_referring_domain": "youtube.com", | ||
} | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.